c++ - 播放具有可变参数的正弦波 - 如何进行相移?

标签 c++ math audio sdl waveform

我正在生成正弦波并将其发送到 SDL 音频缓冲区以生成声音。所有参数如幅度和频率都可以用键盘的方向键改变。

现在,问题是当我改变频率时,我听到“抓取”的声音。我明白为什么会这样:当我在 f(x) 中继续迭代我的 x 时,我得到了一个完全错误的值,而函数本身已更改。但我没有看到或理解如何通过相移解决这个问题。

关于如何开始的任何提示?

#include "WaveGenerator.h"
#include <thread>
#include <iostream>
#include <sstream>
#include <string>
#include <algorithm>    // std::min


int main(int argc, char* argv[]){

    WaveGenerator* wg = new WaveGenerator();

    int i;
    std::cin >> i;
    return 0;
}

int graphThreadFunc(void *pointer){
    WaveGenerator* wg = (WaveGenerator*)pointer;
    wg->init();

    return 0;
}



// SDL calls this function whenever it wants its buffer to be filled with samples
// length = 2048
void SDLAudioCallback(void *data, Uint8 *buffer, int length){
    uint8_t *stream = (uint8_t*)buffer;

    WaveGenerator* wg = (WaveGenerator*)data;   // pointer to our WaveGenerator object where the voice data is stored

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

        if (wg->voice.audioLength <= 0)
            stream[i] = wg->getSpec()->silence;      // 128 is silence in a uint8 stream
        else
        {
            stream[i] = wg->voice.getSample();      // calculate the current sample value

        }

        wg->voice.audioPosition++;
    }
}


WaveGenerator::WaveGenerator()
{
    // spawn thread
    SDL_Thread *refresh_thread = SDL_CreateThread(graphThreadFunc, NULL, this);
}

SDL_AudioSpec* WaveGenerator::getSpec(){
    return &this->spec;
}


void WaveGenerator::init()
{
    // Init SDL & SDL_ttf
    SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER);

    SDL_zero(desiredDeviceSpec);

    desiredDeviceSpec.freq = SAMPLING_RATE;     // Sample Rate
    desiredDeviceSpec.format = AUDIO_U8;        // Unsigned 8-Bit Samples
    desiredDeviceSpec.channels = 1;             // Mono
    desiredDeviceSpec.samples = 2048;           // The size of the Audio Buffer (in number of samples, eg: 2048 * 1 Byte (AUDIO_U8)
    desiredDeviceSpec.callback = SDLAudioCallback;
    desiredDeviceSpec.userdata = this;


    dev = SDL_OpenAudioDevice(NULL, 0, &desiredDeviceSpec, &spec, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE);
    if (dev == 0) {
        printf("\nFailed to open audio: %s\n", SDL_GetError());
    }
    else {
        SDL_PauseAudioDevice(dev, 1); /* pause! */
        SDL_PauseAudio(1);
    }

     //Create an application window with the following settings:
        window = SDL_CreateWindow(
            WINDOW_TITLE.c_str(),              // window title
            SDL_WINDOWPOS_UNDEFINED,           // initial x position
            SDL_WINDOWPOS_UNDEFINED,           // initial y position
            WINDOW_WIDTH,                      // width, in pixels
            WINDOW_HEIGHT,                     // height, in pixels
            SDL_WINDOW_SHOWN                  // flags - see below
            );

        // Check if the window was successfully created
        if (window == NULL) {
            // In case the window could not be created...
            printf("Could not create window: %s\n", SDL_GetError());
            return;
        }
        else{
            // Initial wave parameters
            voice.waveForm = WaveGenerator::Voice::WaveForm::SINE;
            voice.amp = 120;
            voice.frequency = 440;
            SDL_PauseAudioDevice(dev, 1);        // pause       
            voice.audioLength = SAMPLING_RATE;
            voice.audioPosition = 0;

            SDL_PauseAudioDevice(dev, 0);        // play
            SDL_Delay(SAMPLING_RATE / voice.audioLength * 1000);    // 44100 / length of the audio  * 1000 (to get milliseconds)

            mainLoop();
        }
    return;

}

void WaveGenerator::mainLoop()
{
    bool waveHasChanged = false;

    // poll SDL events until we terminate the thread
    while (thread_exit == 0){
        SDL_Event event;

        while (SDL_PollEvent(&event)) {
            switch (event.type)
            {
            case SDL_KEYDOWN:
            {       
                if (event.key.keysym.scancode == SDL_SCANCODE_SPACE){
                    switch (voice.waveForm){
                    case Voice::SINE:
                    {
                        voice.waveForm = WaveGenerator::Voice::WaveForm::TRIANGLE;
                        break;
                    }
                    case Voice::TRIANGLE:
                    {
                        voice.waveForm = WaveGenerator::Voice::WaveForm::RECT;
                        break;
                    }
                    case Voice::RECT:
                    {
                        voice.waveForm = WaveGenerator::Voice::WaveForm::SAWTOOTH;
                        break;
                    }
                    case Voice::SAWTOOTH:
                    {
                        voice.waveForm = WaveGenerator::Voice::WaveForm::NOISE;
                        break;
                    }
                    case Voice::NOISE:
                    {
                        voice.waveForm = WaveGenerator::Voice::WaveForm::SINE;
                        break;
                    }
                    default:
                        break;
                    }
                    waveHasChanged = true;
                }
                else if (event.key.keysym.scancode == SDL_SCANCODE_ESCAPE){
                    exit();
                }
                else if (event.key.keysym.scancode == SDL_SCANCODE_LEFT){               
                    voice.frequency -= 10;
                    waveHasChanged = true;
                }
                else if (event.key.keysym.scancode == SDL_SCANCODE_RIGHT){                  
                    voice.frequency += 10;
                    waveHasChanged = true;
                }
                else if (event.key.keysym.scancode == SDL_SCANCODE_UP){                 
                    voice.amp += 2;
                    waveHasChanged = true;
                }
                else if (event.key.keysym.scancode == SDL_SCANCODE_DOWN){                   
                    voice.amp -= 2;
                    waveHasChanged = true;
                }
                else{

                }

                break;
            }

            case SDL_QUIT:
            {
                exit();
                return;
                break;
            }
            default: /* unhandled event */
                break;
            }
        }

        if (!pause_thread && waveHasChanged)
        {

            // calculate phase shifting?
        }


        SDL_Delay(50);
    }


    return;
}

void WaveGenerator::exit(){
    thread_exit = 1;
    // Clean up
    SDL_Quit();
}

WaveGenerator::Voice::Voice(){
}

uint8_t WaveGenerator::Voice::getSample(){

    switch (waveForm){
    case SINE:
    {
        return (amp * sin(2 * M_PI * audioPosition * frequency / SAMPLING_RATE)) + 128;
        break;
    }
    // .....
    default:
        return 0;
    }
}

和头文件:

#ifndef WAVEGENERATOR_H
#define WAVEGENERATOR_H
#include "SDL.h"
#include "SDL_audio.h"
#include <stdio.h>
#include <cmath>
#include <string>
#include <stack>
#include <io.h> // unistd.h for mac/linux, io.h for windows
#include <vector>
#include <fstream>

/* Window Constants */
const std::string WINDOW_TITLE = "Wave Graph";
const int WINDOW_WIDTH = 1980;
const int WINDOW_HEIGHT = 255;

/* Audio Constants */
const int SAMPLING_RATE = 44100;                // number of samples per second


class WaveGenerator
{
private:
    SDL_Window *window;

    // SDL Audio
    SDL_AudioSpec desiredDeviceSpec;
    SDL_AudioSpec spec;
    SDL_AudioDeviceID dev;

    int thread_exit = 0;
    bool pause_thread = false;


public:
    WaveGenerator();
    void init();
    void mainLoop();

    void exit();
    SDL_AudioSpec* getSpec();


    // SDL audio members
    struct Voice{
        Voice();

        // WaveForm parameters
        enum WaveForm{
            SINE = 0, RECT = 1, SAWTOOTH = 2, TRIANGLE = 3, NOISE = 4
        } waveForm;
        int frequency;              // the frequency of the voice
        int amp;                    // the amplitude of the voice


        // SDL buffer handling members
        int audioLength;            // number of samples to be played, eg: 1.2 seconds * 44100 samples per second
        int audioPosition = 0;      // counter

        uint8_t getSample();


    } voice;

};

#endif

最佳答案

通过从等式中删除 audioPosition 来改变频率而不发生相位跳跃的最简单方法:

class WaveGenerator
{
private:
    double m_sinePhase;
    double m_sinePhaseInc;

uint8_t WaveGenerator::Voice::getSample(){
    switch (waveForm){
    case SINE:
    {
        uint8_t sample = (amp * sin(2 * M_PI * m_sinePhase)) + 128;
        m_sinePhase += m_sinePhaseInc;
        return sample;
    }
}

然后当你改变频率时,只需重新计算相位增量

m_sinePhaseInc = freq/sampleRate;

关于c++ - 播放具有可变参数的正弦波 - 如何进行相移?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33594836/

相关文章:

c++ - 这是什么疯狂的 C++11 语法 ==> struct : bar {} foo {};?

c++ - 编译时未检测到主要功能

mysql - 计算集合的密度

java - 在 Java 的双重算术中得到错误的答案

html - HTML5-音频标签由于某种原因无法正常工作

c++ - 更新 AVL 树代码以执行查找或插入

c++ - 如何将 Iplimage 放在图片框上?

c++ - 如何在 C++ 中设置浮点变量的精度

c++ - 在 Qt 中捕获音频信号

c# - 在Gstreamer(C#)中使用管道播放音频