4

I'm writing a simple synth in assembly as a learning project, and I'd like to implement a few more advanced features found on modern synths, namely the ADSR envelope and pulse width modulation using a sine wave. At the moment, I'm basically generating the samples by hand and pushing them to the audio out, which has a sample buffer and an interrupt it produces when the buffer gets near empty.

What I'm stuck on is how to generate a 'continuous' waveform. At the moment, I generate samples for a single instance of a wave (be it saw or pulse), and simply loop that one wave to generate continuous output. As you can imagine, that doesn't scale well to PWM and the ADSR envelope. I, therefore, need to generate the wave on demand, with potential adjustments to it on the fly, such as modulation of the pulse width or pitch (e.g. for legato), but I'm stumped as to how to represent that efficiently in memory, as well how to 'pause' the waveform generation when the buffer has been filled and 'resume' when the interrupt comes along.

I'm not seeking a solution so much as a nudge in the right direction of thinking :-)

Thanks!

dmkc
  • 1,161
  • 1
  • 15
  • 22

2 Answers2

2

Generating the wave on demand would work for simple waves, although if you later want to add extra features/dsps you will still need some kind of buffer.

ADSR for amplitude is fairly simple, since you just scale the waveform, for frequency modulation it's a bit more complicated, here is an article explaining about it link

Also you can check farbraush github, there should be some nice inspiration for you.

mrvux
  • 8,523
  • 1
  • 27
  • 61
1

It seems that your way of generating a wave is a suitable approach for subtractive synthesis. If you want to adjust PWM of your waveform then you will have to regenerate the samples (or retrieve a pre-computed waveform stored in memory).

In most cases, you will also need to rebuild the wavetable if the pitch changes. You could read the wave at a faster speed by calculating the read pointer increments relative to the wave fundamental, but this will require interpolating between values in the wavetable and may introduce aliasing with more complex waves.

In most cases, of course, it is unlikely that the wave generated will be exactly 2^n samples. Therefore, at the beginning of the process routine, copy the samples from the previous wave first before copying the current wave to the output buffer.

You don't want the regeneration process to disrupt your DSP process routine, so I would construct the updated waveform at a seperate memory location and copy it over when ready.

An ADSR envelope (the subtractive way) should be applied as a moving gain coefficient after the wave has been generated, rather than affect the wave itself.

Hope that helps :)

elSnape
  • 302
  • 1
  • 12
  • So you're saying, generate the waveform somewhere in RAM and then just feed that instead, rather than bother with on-the-fly rendering? But at the moment I only have a single wave. With a more complex wave stored somewhere in memory, how would I go about doing things like sustain of arbitrary length? I thought about setting 'loop points' in a pre-rendered sample, but if I'm modulating the frequency or pulse width, the loop will have to be of potentially massive length. I think I'm missing a piece of the puzzle… – dmkc Nov 03 '12 at 23:43
  • I need to know more about how your synth is being driven. In the case of most plug-in effects/synths (which I am familiar with), the client periodically requests power of 2 blocks of samples by calling a 'process' function. Updates to the DSP are done via a parameter changed event callback between process requests. Is your implementation similar? – elSnape Nov 04 '12 at 11:29
  • Similar to that, yeah. My question is mostly about how one would generate such blocks of 2^, particularly considering things pitch and pulse width modulation. – dmkc Nov 04 '12 at 18:07