Using Teensy Audio Library function generators as Control Voltage Sources

blakeAlbion

Well-known member
Hello All,

I have written about this problem before, but never quite realized my general question. I would like to use the Teensy envelopes, oscillators, noise generators, etc as control voltage sources.
How would I do this? For example, how could I transform the output of a slow-moving triangle wave oscillator in the Teensy into a stream of numbers I could use to drive PWM or a DAC? (making the equivalent of a modular synth LFO?)

Essentially I am asking if it would be possible to make a new AudioOutputXXX component for the library that just dumped the output of function to a callback that delivered an unsigned long or something like that I could then use to drive the device of my choice?

I'm getting tired. My verbal skills are dropping off.
Thanks,

Ben
 
Well using the Peak object is one way to do it, possibly not ideal.

I had a similar idea in my SID-based project where I need integers to feed the SID bus. I tried Peak for some time and then gave in and did my own "numeric" oscillators and envelopes.
 
The audio library Record Queue object does what you wish? It dumps audio data to buffers that can be accessed by the Teensy program.

Before I was aware that the queue object existed, I wrote a custom library object by modifying the mixer object. It was not as difficult to do as I thought it would be. The main issue to be solved at that point was how to use the data at the correct rate so as to not overrun or under-run the buffered data. There may/must be an easier way to solve that issue, but I fine tuned the rate of a timer interrupt.

The application was a polar radio transmitter - so the algorithm just explained is complicated by all the other things happening in the radio. Source is here:
https://github.com/roncarr880/uSDX_Teensy
 
Thanks, these are good ideas. I'm a little disappointed that ALL types of data flow through the audio buffer undifferentiated/unchannelized. I wish I could reach into the buffer and query for only control signals for example.
In analog modular synths, and in MIDI too, there's the ability to keep control voltages/signals separate from the audio signal path. Even when you do plug in an "audio output" into a "control input" (example: ring modulator) you're still able to say "I am using the audio as a control source" and keep things clean.
The Audio Library sez: All signal is signal. It's all in the stream. Once you throw it into the buffer, it's part of a whole.
I would like to literally monitor the control signals as simple sources of numeric value without them being converted into an encoded audio stream.
The fact that the envelope generator is an attenuator, not a signal source, is weird, but it makes the envelope generator more versatile. What's frustrating is if I pass in a "DC" synth node into an envelope generator, I wish I could tap the output of the envelope generator and get a callback/interrupt handler with the current value of the envelope generator's output as a floating point number. (ideally with the ability to resample this at a lower rate if needed)
I want to be able to use these basic features as number-in/number-out processors. Or better yet, be able to easily extract the number streams from the audio stream "by id".
 
In summary, the Teensy Audio Library lets you make complex graphs of signals, but frustratingly regards control signals as part of the very same stream as audio signals.
For example, if I have a synthesizer using a low-frequency oscillator to generate a triangle wave at 6Hz, and I use a mixer to attenuate this signal and then pass it into the modulation input of another oscillator, the only data that passes through the system is the resulting oscillator signal. The fluctuating LFO value is ephemeral: I can't monitor it, or use it in any way except to influence other components that receive its output. What I want in this example is to drive an LED with the triangle wave to show the speed of the LFO. Or same with an envelope, or a capacitive or resistive control strip feeding a voltage into a "voltage processor" in the library adding portamento or changing the linear response.
What would be a very nice feature would be the ability to tap control signals and use them for software and hardware external to the Teensy Audio environment.
 
@blakeAlbion I could see how that could be useful.

For control purposes (flashing leds etc) could you live with an average of that audio block (128 samples per block by default so updated every 2.9ms)?

If you could live with it, it'd be very easy to do a custom object to return that average.

If that's not accurate enough, you could change the block size to AUDIO_BLOCK_SAMPLES=16 which give a new average every 360us ? Would that work?
 
In summary, the Teensy Audio Library lets you make complex graphs of signals, but frustratingly regards control signals as part of the very same stream as audio signals.
For example, if I have a synthesizer using a low-frequency oscillator to generate a triangle wave at 6Hz, and I use a mixer to attenuate this signal and then pass it into the modulation input of another oscillator, the only data that passes through the system is the resulting oscillator signal. The fluctuating LFO value is ephemeral: I can't monitor it, or use it in any way except to influence other components that receive its output. What I want in this example is to drive an LED with the triangle wave to show the speed of the LFO. Or same with an envelope, or a capacitive or resistive control strip feeding a voltage into a "voltage processor" in the library adding portamento or changing the linear response.
What would be a very nice feature would be the ability to tap control signals and use them for software and hardware external to the Teensy Audio environment.

I may not be completely understanding what you're describing (my experience with the audio library is strictly as used in my TeensyMIDIPolySynth project, discussed elsewhere here on the forum), but if you took your LFO output & in addition to feeding it into the modulator input of your oscillator, could you also send that same LFO output (one output can feed multiple inputs) to an I2S output device (from the audio library) which then feeds an additional CODEC/DAC (e.g. Teensy Audio Adapter, etc.) which would thus put out the actual LFO analog signal for your external use ??

Mark J Culross
KD5RXT
 
Thanks all!
I am pretty far along with a project that needs the LEDs I mentioned.
I am using 1/2 the MQS audio interface on a Teensy 4.
I doubt I can use the unused pin 10 of MQS to generate something I could use as a DC signal. But I'll experiment. I also need to drive an analog VCF's control voltage with this. A 12 bit DAC would be fine for my applications.

Ben
 
Yeah.... the Peak component is limited to finding the max within each buffer frame.
I guess ideally I would need to set it up to be a smooth (but low-latency) stream of numbers and then be able to resample it at the desired rate?
But I should be very careful what I wish for.

Really what I want to do is have an "LFO" that can have an adjustable frequency over a pretty wide range. If it could be oscillator 3 on a minimoog, that would be nice, but I will settle for 0.05 to 220Hz or something like that.

I'm just going to have to look at the code in the waveform synth components and look where the waveform functions are computed and try to see how I could provide a "test point" in there where I could insert a fast callback. If the function transforms state N to state N+1, I just need to capture the value of state N, right?

It'll be some massive 32 bit number I will need to mask into something I can use.

Ben
 
Yes, the buffer pointer gets populated by the waveform functions.
*bp++ = (((int32_t)ph >> 15) * magnitude) >> 16;
Obviously I don't want to do something crazy that would slow down this fast loop.
 
Thanks MrCanvas!

The Peak component should be adequate for my needs. I am able to show nice colorful functions on neopixels.

Ben
 
void loop() {
if (peakLFO.available()) {
fPval = 255.0 * (peakLFO.read());
bPval = (byte) fPval;
colorPixel(0, bPval, 0, 0);
}
if (peakVCF.available()) {
float fP = peakVCF.read();
fPval = 255.0f * (fP);
bPval = (byte) fPval;
colorPixel(1, 0, 0, bPval);
}
....
 
Back
Top