0
$\begingroup$

I want to know how I could implement a Low Frequency Oscilator on a simple synth I've coded in C. First in order to understand how to make this happen I would like just to modulate a sine wave by a sine LFO, and then I would adapt this to other oscilators and so on.

I tried adapting to adapt this LFO formula I found on my sine oscilator:

dLFOAmplitude * dHertz * (sin(w(dLFOHertz) * dTime

But I didn't found a correct way to do this. Here's my sinewave generator function:

static void build_sine_table(int16_t *data, int wave_length) {

    double phase_increment = (2.0f * pi) / (double)wave_length;
    double current_phase = 0;

    for(int i = 0; i < wave_length; i++) {
      int sample = (int) (sin(current_phase) * INT16_MAX);     
        data[i] = (int16_t)sample;
        current_phase += phase_increment;
    }
}

EDIT: I tried again to multiply my lfo signal with my oscilator. Each time I'm doing it I got no sound released. Same by divinding. When I sum up the two signal I got sound but no modulation. Here's what I tried in order to follow advices from the comment:

static void build_sine_table(int16_t *data, int wave_length) {

    double phase_increment = (2.0f * pi) / (double)wave_length;
    int lfo_phase = (2.0f * pi) / 20 / 44100;
    double lfo = 0;
    double current_phase = 0;

    for(int i = 0; i < wave_length; i++) {
      int sample = (int) (0.5 * sin(current_phase) * INT16_MAX + 0.5 * sin(lfo) * INT16_MAX);

        data[i] = (int16_t)sample;
        current_phase += phase_increment;
        lfo += lfo_phase;
    }
}

I tried many other thing, and the "best result" I got was a wave looking like this : enter image description here

I've spent a lot of time trying to figure out a to make it work, and the more I looking into it, the less I'm confident to be able of coding it.

$\endgroup$
  • $\begingroup$ Your sine table function seems correct to me. For the amplitude modulation, you just need to multiply the two signals together. Since your sine table goes from -INT16_MAX to INT16_MAX, passing through 0, there will be a point where your audio signal will disappear. You might want to scale the amplitude of your sine table before multiplying it and maybe add an offset so your signal never disappears. As for the formula, it's just a matter of skipping ahead in the sine table based on the sample rate and the frequency you want (f/Fs). $\endgroup$ – TehWan Jun 12 at 18:37
  • $\begingroup$ Thanks for the answer. What I want to know is how to generate my lfo signal. I tried something like this " double lfo_phase = (2.0f * pi) / (44100/20);" and the processing the lfo phase like I'm doing for my oscilators. Each time a tried to multiply the signal with my oscilator I got a modulation of th esine, but it gives something sounding like a square wave. I think scaling my amplitude would maybe fix things but not I'm not sure how to do this. $\endgroup$ – valentin diverchy Jun 12 at 19:05
  • $\begingroup$ May I suggest keeping floating-point values for your sine table? This way you would have values between -1 and 1 and could scale the weight much more easily. You could also convert your signal to floating-point value by dividing by INT16_MAX (assuming your signal is 16-bit signed integer), and then multiply the two floating-point values, then convert back to 16-bit signed integer (by multiplying back by INT16_MAX). If you want to avoid floating-point values, you can use fixed-point math for that and keep everything with integers. There are a few tutorials out there for fixed-point math. $\endgroup$ – TehWan Jun 12 at 19:31
  • $\begingroup$ Well I see wht to do in theory to sccale, unfortunatley I still struggle to get the lfo working. $\endgroup$ – valentin diverchy Jun 12 at 19:41
0
$\begingroup$

I personally prefer to not use a sine table when prototyping, since it's premature optimization and it can be added later when the algorithm works.

Here's an example of how you could do it. It's essentially creating an NCO and mixing it with the signal.

// Parameters
const double LFO_FREQUENCY = 20; // LFO frequency [Hz]
const double SAMPLE_RATE = 44100; // Sample rate [Hz]
const double CONVERSION_FACTOR = 32768.0;
double phase = 0;  // Initial phase [rad]
double dphase = 2 * M_PI * LFO_FREQUENCY / SAMPLE_RATE; // Phase rate of change [rad/sample]
const double LFO_WEIGHT = 0.3; // Maximum amplitude variation of the LFO
const double LFO_OFFSET = 0.5; // Offset of the LFO amplitude
int n;

// configure these with your signal and output buffer
int16_t* signal;
int16_t* new_signal;
unsigned int num_samples;

for (n = 0; n < num_samples; ++n) {
    // Convert the input signal to a double and multiply by the LFO output
    double sample = (signal[n] / CONVERSION_FACTOR) * (sin(phase) * LFO_WEIGHT + LFO_OFFSET);

    // Increment the phase of the LFO by 1 sample, keep it within 2pi
    phase = fmod(phase + dphase, 2*M_PI);

    // Convert the sample to be used in the audio output
    new_signal[n] = sample * CONVERSION_FACTOR;
}
| improve this answer | |
$\endgroup$
  • $\begingroup$ I tried implementing it, I got sound out but got very strange waveform. $\endgroup$ – valentin diverchy Jun 12 at 19:57
  • $\begingroup$ The waveform will look strange because of the phase inversion when the sine goes negative. If you want to avoid this, you need to limit the sine to be only positive. You could also do the following: double sample = (signal[n] / CONVERSION_FACTOR) * (sin(phase) * weight + offset); If you make the weight = 0.3, the amplitude of the sine will be from -0.3 to 0.3, and you can move it up using the offset, say offset = 0.5, your amplitude will vary between 0.2 and 0.8. $\endgroup$ – TehWan Jun 12 at 20:00
  • $\begingroup$ Well even with weight and offset the sound is still horrible. $\endgroup$ – valentin diverchy Jun 12 at 20:08
  • $\begingroup$ And... something that is very strange is that even if I commented all this code the horrible sound is still happening, so this function doesn't do any sound on my code and on other part of my code is sending a broken sound. $\endgroup$ – valentin diverchy Jun 12 at 20:19
  • $\begingroup$ You have to consider the frequencies of the signals you are mixing. By multiply the two signals together, you will get extra frequencies introduced, notably the sum and the difference. Try using with an LFO frequency of 15 or 20 Hz, or even 5 Hz. $\endgroup$ – TehWan Jun 12 at 20:27
0
$\begingroup$

Thanks a lot to TehWan ! Well I'm not quite sure if I succeded into building a proper LFO, but at least I've got a wave modulation function with interesting sound. Check this out:result of my wave modulation

In order to achieve this I implemented a very small part of the code provided by TehWan and try different approach and got the best result with this code:


static void build_sine_table(int16_t *data, int wave_length) {

    const double LFO_FREQUENCY = 5; // LFO frequency [Hz]
    const double SAMPLE_RATE = 44100; // Sample rate [Hz]
    const double CONVERSION_FACTOR = 32768.0;
    double phase_increment = (2 * pi) / (double)wave_length;
    double current_phase = 0;  // Initial phase [rad]
    double dphase = ((2 * pi * LFO_FREQUENCY) / SAMPLE_RATE); // Phase rate of change [rad/sample]
    double lfo = 0;



    for(int i = 0; i < wave_length; i++) {

      (int)(sin(lfo) * INT16_MAX);
      int sample = (int)((sin(current_phase) * INT16_MAX));
      lfo += dphase;
      current_phase += phase_increment + (0.2 * lfo);

      data[i] =  (data[i] / CONVERSION_FACTOR) + (int16_t)sample;


}

If someone sees something wrong here I'm reading to correct it. Anyway for the time being I will consider this my "LFO".

EDIT: TehWan thank you once again, well I didn't used much your code but at least it did help find out how to change my function.

| improve this answer | |
$\endgroup$
  • $\begingroup$ Glad it worked out for you. The LFO I sent you modulates the amplitude of the signal using a constant frequency, but if you want to modulate the frequency of the input signal, it's best to use complex numbers and a slightly different mixing function. What you have created is a chirp by varying the rate of change of the phase (lfo variable in your case), i.e. not using a constant step for your phase increment. $\endgroup$ – TehWan Jun 13 at 6:41
  • $\begingroup$ Ok, since I will implement that to a a sequencer I made I could use this to otjer feature to my chirp $\endgroup$ – valentin diverchy Jun 13 at 12:16
  • $\begingroup$ @valentindiverchy If you are happy with TehWan answer, would you mark it as accepted? $\endgroup$ – jojek Jul 15 at 8:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.