使用 SDL 编写波形发生器

Writing a wave generator with SDL

我用 C 语言用 SDL 1.2 和 SDL_mixer(播放 .wav 文件)编写了一个简单的音序器。它运行良好,我想向该程序添加一些音频合成。我已经查找并使用 SDL2(https://github.com/lundstroem/synth-samples-sdl2/blob/master/src/synth_samples_sdl2_2.c)

找到了这个正弦波代码

正弦波在程序中的编码方式如下:

static void build_sine_table(int16_t *data, int wave_length)
 {
    /* 
        Build sine table to use as oscillator:
        Generate a 16bit signed integer sinewave table with 1024 samples.
        This table will be used to produce the notes.
        Different notes will be created by stepping through
        the table at different intervals (phase).
    */

    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;
    }
}

static double get_pitch(double note) {

    /*
        Calculate pitch from note value.
        offset note by 57 halfnotes to get correct pitch from the range we have chosen for the notes.
    */
    double p = pow(chromatic_ratio, note - 57);
    p *= 440;
    return p;
}

static void audio_callback(void *unused, Uint8 *byte_stream, int byte_stream_length) {

    /*
        This function is called whenever the audio buffer needs to be filled to allow
        for a continuous stream of audio.
        Write samples to byteStream according to byteStreamLength.
        The audio buffer is interleaved, meaning that both left and right channels exist in the same
        buffer.
    */

    // zero the buffer
    memset(byte_stream, 0, byte_stream_length);

    if(quit) {
        return;
    }

    // cast buffer as 16bit signed int.
    Sint16 *s_byte_stream = (Sint16*)byte_stream;

    // buffer is interleaved, so get the length of 1 channel.
    int remain = byte_stream_length / 2;

    // split the rendering up in chunks to make it buffersize agnostic.
    long chunk_size = 64;
    int iterations = remain/chunk_size;
    for(long i = 0; i < iterations; i++) {
        long begin = i*chunk_size;
        long end = (i*chunk_size) + chunk_size;
        write_samples(s_byte_stream, begin, end, chunk_size);
    }
}

static void write_samples(int16_t *s_byteStream, long begin, long end, long length) {

    if(note > 0) {
        double d_sample_rate = sample_rate;
        double d_table_length = table_length;
        double d_note = note;

        /*
            get correct phase increment for note depending on sample rate and table length.
        */
        double phase_increment = (get_pitch(d_note) / d_sample_rate) * d_table_length;

        /*
            loop through the buffer and write samples.
        */
        for (int i = 0; i < length; i+=2) {
            phase_double += phase_increment;
            phase_int = (int)phase_double;
            if(phase_double >= table_length) {
                double diff = phase_double - table_length;
                phase_double = diff;
                phase_int = (int)diff;
            }

            if(phase_int < table_length && phase_int > -1) {
                if(s_byteStream != NULL) {
                    int16_t sample = sine_wave_table[phase_int];
                    sample *= 0.6; // scale volume.
                    s_byteStream[i+begin] = sample; // left channel
                    s_byteStream[i+begin+1] = sample; // right channel
                }
            }
        }
    }
}

我不明白如何更改正弦波公式以生成其他波形,例如 square/triangle/saw 等...

编辑: 因为忘了解释,下面是我试过的。 我遵循了我在这个视频系列 (https://www.youtube.com/watch?v=tgamhuQnOkM) 中看到的示例。视频提供的方法源码在github,生成波的代码是这样的:

double w(double dHertz)
{
    return dHertz * 2.0 * PI;
}

// General purpose oscillator


double osc(double dHertz, double dTime, int nType = OSC_SINE)
{
    switch (nType)
    {
    case OSC_SINE: // Sine wave bewteen -1 and +1
        return sin(w(dHertz) * dTime);

    case OSC_SQUARE: // Square wave between -1 and +1
        return sin(w(dHertz) * dTime) > 0 ? 1.0 : -1.0;

    case OSC_TRIANGLE: // Triangle wave between -1 and +1
        return asin(sin(w(dHertz) * dTime)) * (2.0 / PI);
}

因为这里的 C++ 代码使用 windows 声音 api 我无法 copy/paste 这种方法使其适用于我使用 SDL2 找到的代码片段。 所以我尝试这样做以获得方波:

static void build_sine_table(int16_t *data, int wave_length)
 {
    double phase_increment = ((2.0f * pi) / (double)wave_length) > 0 ? 1.0 : -1.0;
    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;
    }
}

这没有给我一个方波,而是一个锯齿波。 这是我尝试获得三角波的方法:

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)(asin(sin(current_phase) * INT16_MAX)) * (2 / pi);
        data[i] = (int16_t)sample;
        current_phase += phase_increment;
    }
}

这也给了我另一种波形,不是三角形。

您可以将 sin 函数调用替换为调用以下函数之一:

// this is a helper function only
double normalize(double phase)
{
  double cycles = phase/(2.0*M_PI);
  phase -= trunc(cycles) * 2.0 * M_PI;
  if (phase < 0) phase += 2.0*M_PI;
  return phase;
}

double square(double phase)
{ return (normalize(phase) < M_PI) ? 1.0 : -1.0; }

double sawtooth(double phase)
{ return -1.0 + normalize(phase) / M_PI; }

double triangle(double phase)
{
  phase = normalize(phase);
  if (phase >= M_PI)
    phase = 2*M_PI - phase;
  return -1.0 + 2.0 * phase / M_PI;
}

您将像构建正弦波一样构建表格,只是它们分别是正方形、锯齿波和三角形表格。