Я генерирую синусоидальную волну и посылаю ее в звуковой буфер SDL для генерации звука. Все параметры, такие как амплитуда и частота, могут быть изменены с помощью клавиш со стрелками на клавиатуре.
Теперь проблема в том, что когда я меняю частоту, я слышу «царапину». Я понимаю, почему это происходит: я получаю совершенно неверное значение, когда просто продолжаю повторять x
в f(x)
когда сама функция изменилась. Но я не вижу и не понимаю, как я могу решить эту проблему путем сдвига фаз.
Любые советы, как начать?
#include "WaveGenerator.h"#include <thread>
#include <iostream>
#include <sstream>
#include <string>
#include <algorithm> // std::minint 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 secondclass 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;
Других решений пока нет …