Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 21 of 21

Thread: string ensemble chorus - anyone try it?

  1. #1

    string ensemble chorus - anyone try it?

    I want a good string ensemble chorus sound without resorting to expensive implementations of actual BBD circuits. It should not be hard, I think. Here is what is needed at a high level:

    0.6 Hz and 6.0 Hz sinewaves (with a phase of zero) are combined producing an original signal with phase zero, plus two more having phase 120 and 240, respectively. These are each used to modulate the time parameter of a delay. Something between 5ms and 30ms, I am not sure, but that's not critical as it should be a physical control input anyway.

    All I should need are three pairs of 0.6 Hz and 6.0 sinewaves, with phase 0, 120, and 240. I am guessing sine objects are phase-locked, otherwise specifying the phase would not make much sense.

    Are the sine objects phase-locked? If they are off by a sample or two, that will probably make no audible difference.

    Thanks!

  2. #2
    Hi @quarterturn,

    in fact I am trying this. See my post about modulated delay just before your post. But I currently have some trouble with clicking noise.

    At night I had an idea how to fix this. When it is fixed, I think it should not be a big problem to add multiple taps.

    Regards, Holger

  3. #3
    Ah yes - looks like delay does not play nice with continually updating the time parameter. That's too bad. I have an Electrosmash Pedalshield lying around, I should get another Due for it and look back through the example code for delays. It should not be hard to create the sinewave modulators via a lookup wavetable. If I can get it working there I'll get a Teensy 3.6 and get it going there - should be even easier since there is hardware floating point.

  4. #4
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    20,064
    Quote Originally Posted by quarterturn View Post
    Ah yes - looks like delay does not play nice with continually updating the time parameter
    Can you post a program which demonstrates the problem?

    As far as I know, this problem isn't anywhere on my list of known issues. It won't get on that list unless there's a program which clearly demonstrates the problem.

  5. #5
    Quote Originally Posted by PaulStoffregen View Post
    Can you post a program which demonstrates the problem?

    As far as I know, this problem isn't anywhere on my list of known issues. It won't get on that list unless there's a program which clearly demonstrates the problem.
    It would be COd3man's code linked in this post: https://forum.pjrc.com/threads/56532...Chorus-Flanger. I haven't tried it myself.

  6. #6
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    20,064
    Ah, I see... it's an issue with code posted on a forum message, not something we've published in the audio lib, right?

  7. #7
    Quote Originally Posted by PaulStoffregen View Post
    Ah, I see... it's an issue with code posted on a forum message, not something we've published in the audio lib, right?
    Hopefully COd3man will update.

    In the meantime, I will try it the "hard way" which is to create a wavetable in setup() something like:
    Code:
    2+sin(((2.0*pi)/samples)*index)) + sin(((20.0*pi)/samples)*index))
    and fiddle around with interrupts per sample until the output looks like 6.0 Hz and 0.6 Hz added together. The Electrosmash sinewave generator example used 600 samples for their wavetable. That would be 60 samples for the 6.0 Hz component and the full 600 for the 0.6 Hz. The wavetable size probably needs to be increased.

    Creating the phasors should be as simple as setting the pointer to the right location such that they are separated by thirds in the wavetable.

  8. #8
    Senior Member
    Join Date
    Oct 2015
    Location
    Vermont, USA
    Posts
    167
    Quote Originally Posted by quarterturn View Post
    Ah yes - looks like delay does not play nice with continually updating the time parameter. That's too bad. I have an Electrosmash Pedalshield lying around, I should get another Due for it and look back through the example code for delays. It should not be hard to create the sinewave modulators via a lookup wavetable. If I can get it working there I'll get a Teensy 3.6 and get it going there - should be even easier since there is hardware floating point.
    As you found, the delay object does not expect to be changed very often...it isn't designed that way. At best, it might be possible to change the delay time for every block of audio data, which is every 128 samples. At 44.1 kHz, this would be an update every ~3ms. Even if you were able to change the delay time that fast, I think that it'll sound bad as this updating will sound nothing like the smooth changes that would happen if it were updated every sample.

    To update every sample (via a software LFO or via a wavetable...however you want), you'd need to re-write the guts of the delay effect object. And if you're going to do that, you should probably start with the chorus object instead as that might be easier.


    Chip

  9. #9
    Senior Member
    Join Date
    Oct 2015
    Location
    Vermont, USA
    Posts
    167
    I just looked at the chorus effect code that's in the Teensy audio library...

    h file: https://github.com/PaulStoffregen/Au...ffect_chorus.h
    cpp file: https://github.com/PaulStoffregen/Au...ect_chorus.cpp

    Unfortunately, the chorus code in the Teensy audio library (I'm looking at the "update" method on the cpp file) is pretty terse and lacks comments to help newbies (like myself) to clearly see the algorithm.

    In trying to understand the code, I'm assuming that the underlying algorithm is something like:
    * A delay line is used to store the incoming audio
    * One or more LFOs are used to continuously vary lookup points into the delay lines
    * The delayed version(s) of the audio are mixed with the original (dry) audio

    In looking at the Teensy code, I do see some variable names that allude to these elements, but I can't seem to understand what's going on. Or, perhaps this code is implementing an algorithm that is very different from my assumption?

    Can anyone help explain how the Teensy chorus works? I have questions like:
    * Where is the memory allocated to implement the delay line?
    * Surely, there is an LFO that is sweeping the delay in order to make the chorus effect...how do I deduce its basic properties like LFO frequency?

    Depending upon the answers, it might be that this code won't be very helpful as a guide for the OP in making their own chorus/ensemble effect.

    Chip
    Last edited by chipaudette; 06-10-2019 at 08:58 PM.

  10. #10
    Quote Originally Posted by chipaudette View Post
    As you found, the delay object does not expect to be changed very often...it isn't designed that way. At best, it might be possible to change the delay time for every block of audio data, which is every 128 samples. At 44.1 kHz, this would be an update every ~3ms. Even if you were able to change the delay time that fast, I think that it'll sound bad as this updating will sound nothing like the smooth changes that would happen if it were updated every sample.

    To update every sample (via a software LFO or via a wavetable...however you want), you'd need to re-write the guts of the delay effect object. And if you're going to do that, you should probably start with the chorus object instead as that might be easier.


    Chip
    If I get it working I'll certainly post a follow-up. I will probably start from scratch and not use the audio library though.

  11. #11
    It looks like it is varying a pointer offset to the audio block from 0 to delaytime and back down. It is like a triangle wave. It takes the delay offset to the pointer and sums the sample data from that to the index. I am not great with C++ but it does not appear to allocate new memory, just uses pointers to the existing audio block.

    I suppose keeping a phase relationship would be offsetting the delay pointer by thirds of the audio sample block. This gives me a good deal to think about.

  12. #12
    Hi Guys,

    I am a little bit confused: you are discussing about an algorithm which is exactly the one I posted some hours before this thread was opened. I also pointed to my thread (Modulated-Delay-Chorus-Flanger) at the start of _this_ thread, but noone seems to took a look at the code I posted. My code is "nearly" working... there is a problem with some noise at changing signs of the modulation (perhaps an access outside the array boundaries?). So we can now try to solve the problem on our own or build a functional version together.

    Regards, Holger

    P.S.: Code is now located at https://codeberg.org/dcoredump/Test_ModulatedDelay
    Last edited by C0d3man; 06-11-2019 at 09:22 AM.

  13. #13
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    20,064
    Sorry, I can't get involved in helping with this. Later this year, well after Teensy 4.0 is released, I probably will be able to do this sort of help. But right now, I just can't. I can barely even keep up with the most essential support requests.

  14. #14
    Quote Originally Posted by PaulStoffregen View Post
    Sorry, I can't get involved in helping with this. Later this year, well after Teensy 4.0 is released, I probably will be able to do this sort of help. But right now, I just can't. I can barely even keep up with the most essential support requests.
    Absolute no problem @PaulStoffregen! I'm really curious about the new Teensy. Maybe by then we'll have a modulated delay / chorus that works.

  15. #15
    Senior Member
    Join Date
    Oct 2015
    Location
    Vermont, USA
    Posts
    167
    Yes, it does look like Holger did what he said...extended the chorus to use an external signal source as the modulation. So to get ensemble working, you'd need several of these in parallel.

    Bummer that it still has audio artifacts. It's not obvious in looking at the code what the problem is.

    Chip

  16. #16
    Quote Originally Posted by chipaudette View Post
    So to get ensemble working, you'd need several of these in parallel.
    I think this can be done inside the class with multiple taps (like effect_chorus.cpp). This would no eat so much memory like parallel instances.

  17. #17
    As promised, here's my attempt: https://github.com/quarterturn/due_ensenble_chorus

    It is not working well - there's a TON of noise and aliasing. This may well be down to the Due plus PedalShield hardware. Sorry I could not write it for the Teensy (don't have one at the moment) but it won't be hard to adapt the code to it. You just have to figure out the ADC init and interrupt timing to get 44100 interrupts/sec for audio.

    I used a circular buffer concept. The index for the current sample and the delayed versions wrap around if it reaches the end of the buffer. The modulation wavetable is scaled to provide something like a 5 - 10 ms delay range, and it is sized so if it is updated every 10 interrupts it will "play back" at the right speed.

    All in theory. I am sure there is an obvious mistake or two. I've been working on it for a while and I'm in need of a break to let it mentally digest so I can maybe see what's wrong.

  18. #18
    Senior Member
    Join Date
    Oct 2015
    Location
    Vermont, USA
    Posts
    167
    Quote Originally Posted by quarterturn View Post
    As promised, here's my attempt: https://github.com/quarterturn/due_ensenble_chorus

    It is not working well - there's a TON of noise and aliasing. This may well be down to the Due plus PedalShield hardware. Sorry I could not write it for the Teensy (don't have one at the moment) but it won't be hard to adapt the code to it. You just have to figure out the ADC init and interrupt timing to get 44100 interrupts/sec for audio.

    I used a circular buffer concept. The index for the current sample and the delayed versions wrap around if it reaches the end of the buffer. The modulation wavetable is scaled to provide something like a 5 - 10 ms delay range, and it is sized so if it is updated every 10 interrupts it will "play back" at the right speed.

    All in theory. I am sure there is an obvious mistake or two. I've been working on it for a while and I'm in need of a break to let it mentally digest so I can maybe see what's wrong.
    Congrats on getting something to work! Sorry it's not satisfying sounding. Some thoughts after looking at your code:

    Because you're using the Due's ADC, your audio signal has a big DC component to it, which might slowly change. So, separate from your quest to make a chorus ensemble effect, I would recommend that you go from uint32 to int32 and then do a highpass filter to bring it all down to be zero mean. That'll make you bit-shift operation in place of a divide-by-2 to be more reliably artifact free on your audio.

    Second, if you're getting a Teensy 3.5 or 3.6, I recommend that you go floating-point. That'll let you worry about the algorithm and not the numerics.

    Third, if you're getting a Teensy, are you getting an audio shield, too? The ADC/DAC on any of these microcontrollers are not really up to the task of audio (IMO). They'll always sound weird and/or noisy. Switching to an audio codec will help. The Teensy Audio board is pretty noisy, but it is low cost and easy to use and better (again, IMO) than the built-in ADC/DAC.

    Fourth, and this is the only one that has to do with the actual algorithm, your current algorithm slowly increments the index into the memory buffer to get the delayed signal value. That's totally great. The problem is that the LFOs are slow enough that you really want to increment only a fraction of an index. Instead, though, your algorithm (I believe) stays at one index for a while and then jumps to the next index, stays there for a while, and then jumps to the next, and so on. The overall speed is probably right, but the quantization surely contributes to aliasing/unsatisfying sound that you report.

    To fix this problem, you could introduce interpolation (which has its own artifacts, but it's better than quantization), or you can run at a higher sampling rate (but that's a pain), or you could use a completely different algorithm where phase is more easily controlled (such as in the frequency domain or with all pass filters or something).

    Congrats on getting something to work, though! That's pretty hard! (Sadly, getting something to sound great is also really really hard)

    Chip

  19. #19
    Quote Originally Posted by chipaudette View Post
    Congrats on getting something to work! Sorry it's not satisfying sounding. Some thoughts after looking at your code:

    Because you're using the Due's ADC, your audio signal has a big DC component to it, which might slowly change. So, separate from your quest to make a chorus ensemble effect, I would recommend that you go from uint32 to int32 and then do a highpass filter to bring it all down to be zero mean. That'll make you bit-shift operation in place of a divide-by-2 to be more reliably artifact free on your audio.

    Second, if you're getting a Teensy 3.5 or 3.6, I recommend that you go floating-point. That'll let you worry about the algorithm and not the numerics.

    Third, if you're getting a Teensy, are you getting an audio shield, too? The ADC/DAC on any of these microcontrollers are not really up to the task of audio (IMO). They'll always sound weird and/or noisy. Switching to an audio codec will help. The Teensy Audio board is pretty noisy, but it is low cost and easy to use and better (again, IMO) than the built-in ADC/DAC.

    Fourth, and this is the only one that has to do with the actual algorithm, your current algorithm slowly increments the index into the memory buffer to get the delayed signal value. That's totally great. The problem is that the LFOs are slow enough that you really want to increment only a fraction of an index. Instead, though, your algorithm (I believe) stays at one index for a while and then jumps to the next index, stays there for a while, and then jumps to the next, and so on. The overall speed is probably right, but the quantization surely contributes to aliasing/unsatisfying sound that you report.

    To fix this problem, you could introduce interpolation (which has its own artifacts, but it's better than quantization), or you can run at a higher sampling rate (but that's a pain), or you could use a completely different algorithm where phase is more easily controlled (such as in the frequency domain or with all pass filters or something).

    Congrats on getting something to work, though! That's pretty hard! (Sadly, getting something to sound great is also really really hard)

    Chip
    No doubt the audio shield would sound better, as would using floating point to update the delay time each sample, vs having to stretch it out to avoid a very large wavetable. The PedalShield does take an AC waveform and adds an offset for the ADC, and does the opposite the other way around. It's not a bad design. I know at least the DACs are capable of very fast readout using DMA - Nuts and Volts had a 2018 project called Arduino Graphics Interface which used it as a vector generator to run a scope in XY mode and it looks great. They have a version with the Teensy 3.6 as well, but there they use the CPU speed to brute-force it and don't use DMA.

    There's definitely a problem in my code. I've tried a few other simpler algorithms (like just delay swept with an up-down counter) and they sound better - though not great.

    I've got the boards for an Oakley Sound Systems ensemble chorus on-order, just in case! It's an old-school BBD-based chorus and it fully nails the 1970's string machine sound. It's a ton of rather hard-to-source and expensive through-hole - I've told myself no more through-hole after this project!
    Last edited by quarterturn; 06-14-2019 at 09:05 PM.

  20. #20
    Senior Member
    Join Date
    Oct 2015
    Location
    Vermont, USA
    Posts
    167
    Hey, if you do that Oakley Sound project, and if you post any pics or a writeup on the web, be sure to share the link here. Definitely interested.

    Back to the audio issues that you're having, I was not trying to suggest that the problem was with your wavetable...no, no...the problem is inherent with digital delay systems being used for this kind of thing.

    With a naive digital delay system (like we're doing here with Teensy or your Due), we are only doing delays that are an integer number of audio samples. For example, you can delay 342 samples or you can delay by 343 samples. In a naive system, you cannot delay by 342.2 samples.

    Chorus, though, is trying to slowly and continuously sweep the delay of one signal versus another. It doesn't want to jump between delay times, it wants to smoothly sweep between them. Your sweep might want 342.3. samples of delay, then 342.4, then 342.5, etc. But, you won't get that. Instead, you get 342 samples of delay, then you get 342 again, then it jumps to 343 samples, then you get 343 again, etc. *That* is the quantization that (I think) is the problem.

    When sampling at 44.1kHz, one sample of delay is 0.023 msec. That seems to be a nice and small number, but remember that your chorus is trying to smoothly (and slowly) sweep through only ~20 msec of delay. Its sweep rates are usually somewhere around 1 Hz (so, about 20 msec/sec). Because the digital delay amount is quantized to the nearest sample, you're going to get an undesirable little jump in delay time a whole bunch of times: (20 msec/sec) / 0.023 msec = 870 times per sec. Yeah, you're going to have this quantization artifact happening around 870 Hz (along with lots of other weird frequencies related to this one). You're gonna hear that. Ick.

    It's this quantization of the delay that is a problem. Interpolation will definitely help. Algorithms that are designed for smooth shifting of phase (ie, delay) are really the right answer. Those kinds of algorithms take more smarts, though. I've not studied them and I don't know how to implement them.

    Chip

  21. #21
    Senior Member Blackaddr's Avatar
    Join Date
    Mar 2017
    Location
    Canada
    Posts
    199
    Quote Originally Posted by chipaudette View Post
    As you found, the delay object does not expect to be changed very often...it isn't designed that way. At best, it might be possible to change the delay time for every block of audio data, which is every 128 samples. At 44.1 kHz, this would be an update every ~3ms. Even if you were able to change the delay time that fast, I think that it'll sound bad as this updating will sound nothing like the smooth changes that would happen if it were updated every sample.

    To update every sample (via a software LFO or via a wavetable...however you want), you'd need to re-write the guts of the delay effect object. And if you're going to do that, you should probably start with the chorus object instead as that might be easier.

    Chip
    Earlier this year I was working on a chorus for my BALibrary. In order to get the chorus sounding right with no glitches, I had to do exactly what Chip says here. You cannot use a static delay value for each audio block. The delay value must be uniquely calculated for every single sample. And this delay value is unlikely to fall right on the buffered samples so you must linearly interpolate between the samples. It gets worse because delay can be increasing or decreasing, the actual two audio samples from the delay buffer you're interpolating from can overflow into the next audio buffer block, or underflow into the previous one! So, you need more than one audio block.

    If you don't smoothly modulate the delay on a sample-by-sample basis, you will get glitching as the delay value jumps at each audio block boundary.

    In order to support a proper chorus effect, I added some helper tools to my BALibrary, an LFO class, and updated my AudioDelay class to support fractional sample delays via linear interpolation.

    I have a working version of my chorus on a branch here:
    https://github.com/Blackaddr/BALibra...ctAnalogChorus

    It works just fine but I haven't had time to fine tune the the IIR filters or the parameter ranges yet to make it sound really musical which is why it's not merged or announced in a thread yet. It takes a lot more work to get an effect model sounding "good" after you get it sounding "functional", ie. glitch free.

    It starts to get interesting around here:
    AudioEffectAnalogChorus.cpp

    A working demo where the chorus parameters are controlled by the TGA Pro wtih Expansion Board can be found here.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •