ha ! good fun! Are you using an external preamp for the mic?
@lokki may be interested
ha ! good fun! Are you using an external preamp for the mic?
@lokki may be interested
giuliomoro Are you using an external preamp for the mic?
Yes. That electrical outlet box you see on top of the amp (to the right) is a DIY preamp. It's just an op amp with a pot in the feedback. The diagram in the github README shows how the Analog input is configured for mic input.
Nice
yes! nice... will certainly play with this once i find the time again. :-( just some quick questions, how many bands are you using in the video? 8? is the code easily adjustable for more or less bands and specific frequencies? (i have only looked at it very briefly)
If you look in Setup() in render.cpp you can see the call to make_vocoder(), one of the parameters is the number of bands. Another two parameters define the start and end frequencies.
lokki just some quick questions
In the video 16 bands.
Yes, the code is easily adjustable for more or less bands and specific frequencies and Q. You can do all of this from within setup()
in render.cpp
.
For number of bands just change these lines in setup()
:
int bands = 16;
float fstart = 150.0;
float fstop = 4000.0;
vcd = make_vocoder(vcd, fs, nsamps, bands, fstart, fstop);
fstart and fstop don't matter if you are going to set these explicitly (default behavior is to log-space the bands from fstart to fstop)
Setting specific frequencies is also easy but I doubt it is intuitive how to do this unless you understand the code. Here's a crash course: To select a specific frequency and Q for a specific band, you need to make a call to the following explicitly on the band of interest after you have created the filterbanks with make_vocoder(vcd, fs, nsamps, bands, fstart, fstop);
:
// from eq.h, eq.cpp
void
eq_compute_coeffs(eq_coeffs* cf, int type, float fs, float f0, float Q, float G)
You access the pointer to the eq coefficients from the vocoder struct like this:
//Make sure you do _m and _c alike or you will have mismatch between carrier and modulator (which could be a somewhat cool effect if you did it on purpose)
eq_compute_coeffs(vcd->filterbank_m->band[n], BPF, SAMPLE_RATE, f0, Q, Gain);
eq_compute_coeffs(vcd->filterbank_c->band[n], BPF, SAMPLE_RATE, f0, Q, Gain);
Header file"eq.h"
is included from render.cpp
, so you can make this call in setup()
without having to drill deeper or create helper functions in vocoder.cpp
.
I'm working in the background to try to rework it into something a little more neat and efficient.
One of the untidy things in the code is the bandpass does not give zero dB at pass-band so you will notice the gain is a bit hot. It gets hotter as you increase Q. I have got out the mechanical pencil and paper to work this out from the basics but for now you can work around it by adding manual filter gain control. Center frequency and Q are correct.
I have been looking at more analog vocoder circuits. It appears to be more common that vocoder circuits use 8th order filters (I am only using 2nd order). One example I found recently is the Jurgen Haible vocoder:
http://www.jhaible.info/vocoder/living_vocoder.html
Also a really cool discovery I made is @andrew McPherson was involved in the design and construction of what appears to be very ambitious analog vocoder and harmonizer unit. This one also uses 8th order filters.
http://www.eecs.qmul.ac.uk/~andrewm/composer/media/vocoder.pdf
My current implementation uses roughly 30% CPU, so I can't possibly run 8th order filters on this many channels with the current implementation, so I'm refactoring for better efficiency and make it so I can run the audio analysis chain at lower processing sample rates.
I have succeeded in developing the formulae for correct stagger-tuned filters, so this is the first step toward creating well-behaved higher order filters. Below is the 4rth order but, going to 8th-order is a simple matter of cascading two 4rth-order stages.
I'm thinking it may be possible to do 8th-order filters if I improve code efficiency, run analysis bands and envelope detectors at lower processing sample rates. I suppose I will find out when I get to that point.
In the meantime, this implementation still sounds good enough to be fun.
the mam vf-11 vocoder, sounds good and seems to be a rather simple vocoder. there are some infos floating on the net (filter frequencies etc.), but i have not found a schematic for it...
that said, i would absolutely love it, if you take this further. i will definitely try all your evolutions :-)
lokki the mam vf-11 vocoder, sounds good and seems to be a rather simple vocoder.
One thing you could do that would help "crack the code" on the VF-11 would be a frequency sweep and step response from one of the filter bands. Set it up for filterbank operation and zero all except the 330 Hz band since the spacing on the manual indicates these are all 1/2 octave spaced from 225 to 4700 Hz.
Here is one utility to make a frequency sweep easy (but use whatever tool you want if you know of others)
https://github.com/transmogrifox/Bela_Misc/tree/master/frequency_sweep
I would recommend testing one of the filter bands between 200 Hz and 500 Hz, set sweep range to 20 Hz to 2 kHz. Slow the sweep time to a couple minutes and set size to 1000 (this is the bin resolution). It is such a crude utility you will have to set the sweep parameters in the freq_resp_anal.h and re-compile (which is easy on Bela)
Step response: Drive the input with a square wave (low enough frequency that the filter response is settled before it changes again). If you have an oscilloscope, that is a great way to capture it, but if not you can output to a computer sound card and record with Audacity, or use Bela to log to a test file.
Post an image or text or wav/mp3 files full of the measured data.
If you do that much I will try to design a filter to match the VF-1 filter response characteristic.
ryjobil One thing you could do that would help "crack the code" on the VF-11 would be a frequency sweep and step response from one of the filter bands. Set it up for filterbank operation and zero all except the 330 Hz band since the spacing on the manual indicates these are all 1/2 octave spaced from 225 to 4700 Hz.
will do, it could be a couple of weeks before i get to it. not really 1/2 octave, but close :-)
so filter 1 and 11 are actually a lp and a hp respectively i guess.
the bands are listed like this:
100 (50-190), 225, 330, 470, 700, 1030, 1500, 2280, 3300, 4700, 9000(6800-12700)
Just a thought expanding on obtaining the frequency response of filters - if you have a synth that can make square waves with pulsewidth control, the best way to get an accurate picture of the frequency response is to use a low frequency square wave as the input to your vocoder like ryjobil suggested, but when you do that make sure the pulsewidth is set as low as possible, essentially turning the square wave into a pulse train. This will "completely characterize the frequency response of the system" as my DSP instructor would put it. I've found that Bela's frequency analyzer is pretty capable of capturing this but a nice scope may get you a better picture.
lokki I haven't forgot about the promise I made in your other thread to upload my filter code to help with your project. I've implemented 1 and 2 pole filters using the zero delay feedback method which may increase the efficiency of your vocoder allowing you to add more bands and also improve the smoothness of the filter (and it's already sounding very good!).
My goal is to make my GitHub public and make a post on here describing my project by the end of this weekend - for real this time! I hope it can be of some use to you and I look forward to seeing how your vocoder develops
lokki will do, it could be a couple of weeks before i get to it
No hurry. This is all for fun and education
lokki 100 (50-190), 225, 330, 470, 700, 1030, 1500, 2280, 3300, 4700, 9000(6800-12700)
In the current code these can be manually assigned as my prior instructions suggested. Q = fc/BW. The main interest in frequency sweep and/or step response data is to get an estimate of filter order used.
matt but when you do that make sure the pulsewidth is set as low as possible, essentially turning the square wave into a pulse train.
@lokki Please use the step response (long pulses)
@matt Here is why I advise against using short pulses: What you say is true in theory, but the impulse response will be coming into your measurement system on the order of microvolts (signal-noise ratio becomes a very big deal). If you jack up the impulse amplitude, then you drive the system nonlinear and then the assumption of an LTI system is no longer a valid approximation. If you amplify the output then you amplify the noise with it so both your system and post-amplification needs to have noise amplitude < a few microvolts. I tried this on a Crybaby circuit and convinced myself you just can't win this way without expensive test equipment. That is why the Vector Network Analyzer was invented -- it is a practical measurement method that is largely immune to noise . It is expensive test equipment. In the absence of a VNA, the next best thing is a step response.
A step response overcomes the the signal/noise challenge by injecting energy into the system over a longer period of time rather than impractically high amplitudes (e.g. 44.1 kV). The response has enough power to give you acceptable signal-to-noise ratio in the measurement system. With a step response you still get all the information you need about the system in a relatively tidy package, that is,
M(s) = U(s)*H(s) = (1/s)*H(s)
M(s) : Measured signal input
U(s) : Unit step response
H(s) : System being characterized
Hence,
H(s) = s*(1/s)*H(s)
where convolution with "s" is the time-domain derivative
The impulse response is therefore the time-domain derivative of the step response in an LTI system. The main advantage is you get (1/Tpulse) more signal power than what the approximated impulse dumps into the system. It is not difficult for post-processing because you can apply the bilinear transform of 1/s for a band-limited stable derivative function. Executing the derivative (or band-limited difference) in the time domain side-steps additional FFT windowing considerations for the step response (the bilinear transform becomes the windowing function, which has a good match to continuous time systems up to Fs/6).
I would still recommend doing a step response because you will get significantly better signal/noise ratio in the measurement than you will get from approximating the impulse response with narrow pulse width. It is true that after the derivative is applied, you end up with the same small amplitudes you would get from the impulse response (these are mathematical equivalents). The difference is you're starting with a signal that is lower SNR, and not adding significant noise in the derivative function (quantization & truncation error of a 32-bit FPU).
matt using the zero delay feedback method which may increase the efficiency of your vocoder
I haven't dug very deep into the zero-delay feedback method, much but I'm looking forward to when you upload the code. In the mean time would you post a short snippet of the filter difference equation that gets computed every cycle?
Right now I have a clear vision for how to get the current biquad implemention down to this:
4 MUL, 3 ADD
If a ZDF implementation can knock off another MUL, then score
However, I think my main efficiency drag is the whole program structure, in how functions are called and how coefficients are accessed from memory and less due to FPU cycles.
For example, CPU usage with 8 samples per block goes from 48% (all-on) down to about 33% when I disable ONLY the envelope detectors (all 32 biquads still running) -- I clearly have some work to do on the EVD section. CPU usage split is about 1/2 biquads, and 1/2 EVD right now.
ryjobil i also found some forum messages (have to dig up where) suggesting a 10 band multipole filter (instead of 10 bandpass filters) on both carrier and modulator with autoadjusted q (is this even a thing?) would be more processor friendly...
i don't see how that would be better then 10 bp filters, but maybe you know :-)
ryjobil In the current code these can be manually assigned as my prior instructions suggested. Q = fc/BW. The main interest in frequency sweep and/or step response data is to get an estimate of filter order used.
yeah, got that. i just thought i would write those numbers down, for reference. i read through the manual and in the filterbank section "very" steep filter response is mentioned which would suggest 4th order at least. looking forward to do those measurements!
That's good info about the impulse vs. step response, thanks for sharing. Of course it's never as easy as what the theory says - makes sense that because your impulse can't be infinitely high the results will be different than expected. Most of the time to see the shape of a filter I just use a saw/square wave.
Regarding the ZDF stuff, my main resources for implementing them in C++ were "The Art of VA Filter Design" (free PDF) and Will Pirkle's app note 4 (https://www.willpirkle.com/app-notes/). Below is a screenshot of the state variable filter block diagram, difference equation, and a breakdown of the different digital integrators. Essentially, ZDF uses the bilinear transform instead of forward or reverse Euler which approximates the area under the curve more accurately. The result is a "smoother" sound that responds better to modulation than a biquad.
I'll make a dedicated thread about it later where I'll post my code, but if you're curious about how to implement it in the meantime here's a link to a GitHub of some Mutable Instruments code that shows how to implement 1 and 2 pole ZDF filters. Ironically I found Olivier's code months after I had come up with my own way of doing it in C++. It was pretty vindicating to find out we did it very similarly!
https://github.com/jakplugg/stmlib/blob/master/dsp/filter.h
Cheers
@matt Thanks for the teaser . This stuff will be really useful for refactoring my envelope filter SVF. I 2x oversampled the naive chamberlin SVF to overcome the stability problem, but the ZDF approach will definitely reduce CPU usage on that one.
Based on Oliver's code, I see 7 MUL, 4 ADD, 3 SUB, which is going the opposite direction I need to go. This is efficient when you want to have arbitrary access to any or all of the 3 outputs of the biquad structure, but that isn't needed in a vocoder.
Here's a bonus for what I have come up with for the envelope detector to improve efficiency in the EVD section:
//
// Lazy RMS detector
// Inputs:
// sz = processing block size
// x = input signal (typically AC waveform)
// y = detector state variable (current RMS measurement)
// k = integration time constant, typically (1/RC)*(1/Fs)
//
void
lazy_rms_detector_tick(int sz, float *x, float *y, float k)
{
int i = 0;
for(i=0; i < sz; i++)
{
y[i] += k*(x[i]*x[i] - y[i]*y[i]); // Forward Euler integration, resolves to sqrt((1/k)*integral(x*x))
}
}
It gives me sqrt() function and averaging in a single shot with 3 MUL, 1 ADD, 1 SUB
The basic principle is that in an envelope detector you already want a slow/averaged response. Time required for this algorithm to resolve on sqrt(x) can be tuned with constant "k". I call it a "lazy" RMS detector because it lazily iterates the sqrt() solver at audio sample rate and "gets there when it gets there".
The time to resolve represent the desired averaging (attack/release times).
Below we see slow time constants for low signal amplitudes and fast time constants for large signal amplitudes (exaggerated here to demonstrate the effect). Would use longer time constants in a real application to reduce ripple. A second-stage smoothing filter might also be a good idea.
Yes, now that I think about it the state variable filter doesn't make sense for a vocoder because the bandpass response you get it actually only 1 pole. The benefit is having three simultaneous and related filter outputs but that's not important in this application.
That being said, you can adapt any filter structure to be ZDF, so maybe a ladder style bandpass or some other filter topology adapted to ZDF would be ideal for an efficient vocoder.
I like your "lazy" approach! My envelope detectors use SVFs and a rectifier (absolute value) and are CPU hogs but I can control the damping and cutoff of my control signal which returns some interesting results. Anyways, I'll post that code soon enough!
matt My envelope detectors use SVFs and a rectifier (absolute value)
I made an analog envelope filter about 12 years ago (has it really been that long?) that was using an SVF with a pot to control resonance and FC on the detector, and as you pointed out -- some interesting results in the control signal.
As a side-note the lazy RMS detector could be increased to a second-order response if you purposely wanted to add the oscillatory response to it. This would be just one more MAC (so 4 instead of 7 from SVF). I developed this detector structure using analog circuit design techniques, prototyping it in LTSpice. I digitized using both bilinear transform and forward Euler integration, and found forward and/or Euler worked well enough that the extra CPU cycles required to realize the BLT structure weren't justified (step responses look identical and you only see a difference as you approach the stability limits). As would be expected, the BLT version is much better behaved near instability, but instability happens at fast enough convergence times to be impractical for its intended function -- so the practical use of the circuit automatically dictates that it does not operate near instability.
On the topic of more "interesting" envelope detectors, this one was inspired by a design by Harry Bissel (link and information in comments in source code):
https://github.com/transmogrifox/Bela_Misc/blob/master/vocoder/envelope_detector.cpp
I assume you're still working on your own Vocoder -- I'm definitely interested in seeing that when you are done. These things are just a lot of fun
hmm, i tried my mam vf-11 side by side with an ehx v256 at louder levels today and the mam is very feedback prone once things get a little louder. but it sounds fat! the ehx is very feedback resistant but being an fft vocoder (i assume) it sounds a bit dull and washed in comparison.
do you think a digital recreation of the bp filter vocoder will also be so feedback prone, or are there tricks to avoid that? (apart from a "nearfield" mic)
The short answer is yes, I think we can make it less feedback prone.
I found the VF-11 manual and it looks like it has a compressor in the mic (analysis) input but no user controls for the compressor. A dynamic range compressor definitely has the propensity to increase feedback because the gain will be higher for small amplitude signals.
If the compressor is the reason for feedback, then a digitization of the response has two advantages:
1) We can make compressor params adjustable, and make it possible for the user to bypass it altogether.
2) We can add a noise gate.
The compressor code I used is fully adjustable, I just need the time to hook Analog inputs (for pots) into the code to expose these to the user.
You can see pieces of a noise gate in the works in the code (currently commented out). It's partially implemented but needs some work to fully develop it.
great! the mam has a noise gate as well, but i suspect it is on the output rather then the input