1
Audio / Continuous sound stream from generator function
« on: February 17, 2017, 05:28:45 pm »
Hi, I have been having some trouble developing a project on SFML.
The idea:
I have a class object "boite" that contains a function s_xt that outputs a signal as a function of time and space based on its internal structure. What I would like to do is play this signal at a given location across time. Essentially, I need to sample the generator function s_xt(double x, double t) for a constant space value x and a variable time value t. This function is normalized such that the output is between zero and one. The function is subject to dynamic change, so writing to a .wav file is not a good idea.
The problem:
I need to output the function at a given spatial position directly to the sound card. Essentially the operation:
Of course things are more complicated. I believe a soundstream is the answer to my question. Regular buffering will not work because the function is not harmonic, so there will always be discontinuities at the end of the buffer leading to clicking sounds.
An attempt at a solution:
I think that perhaps the way to do this is to have two buffers stores in an inherited class from sf::SoundStream. One buffer would be active, and the other buffer would be filled as the active one is played. So, when the program asks for more data and calls onGetData, we point the data chunk to the previous filled buffer, delete the old buffer, and then fill the next buffer from the generator function.
class soundstream:
#ifndef SOUNDSTREAM_H
#define SOUNDSTREAM_H
#include <SFML/Audio.hpp>
#include "../boite/boite.h"
#define SOUNDSAMPLES 1500
#define DTSOUND 0.01
#define AMPSOUND 30000
class soundstream : public sf::SoundStream{
public:
// load the soundBox into the soundstream and intialize
void load(boite* boxX)
{
soundBox_m = boxX;
t_m = 0;
x_m = 0.5;
fillBuffer(activeBuffer_m);
// set the next buffer to null for the onGetData function
nextBuffer_m = nullptr;
initialize(1,44100);
}
// function to call to fill a buffer by sampling the soundBox
void fillBuffer(sf::Int16* paramBuffer)
{
// allocate a new block of memeory for the buffer
paramBuffer = new sf::Int16[SOUNDSAMPLES];
// fill
for(int i = 0; i < SOUNDSAMPLES; i++)
{
paramBuffer[i] = AMPSOUND * soundBox_m->s_xt(x_m, t_m).real(); // s_xt is a complex function, only play real part
// to make sure that we're not getting zeros, print the signal
std::cout << soundBox_m->s_xt(x_m, t_m) << '\n';
t_m += DTSOUND;
}
}
private:
// overload onGetData
virtual bool onGetData(Chunk& data)
{
// if the next buffer is not null (first iteration) delete the current buffer and change the buffers
if(nextBuffer_m != nullptr)
{
delete activeBuffer_m;
activeBuffer_m = nextBuffer_m;
}
// point the data to the first sample of the new sample and set its count
data.samples = activeBuffer_m;
data.sampleCount = SOUNDSAMPLES;
// fill next sample
fillBuffer(nextBuffer_m);
return true;
}
// overlad onSeek as empty function (for now)
virtual void onSeek(sf::Time timeOffset){};
// internal time and position
double x_m;
double t_m;
// two buffers
sf::Int16* activeBuffer_m;
sf::Int16* nextBuffer_m;
// soundBox pointer
boite* soundBox_m;
};
#endif
basic main code idea
What happens:
I get no sound. The cout gives a sample of values on load, and another sample of values on play, but then stops. So it does not continue to sample for more than one iteration.
The Question:
Can someone please help? Either a way to fix this implementation, or a way to constantly output the values of the signal to the soundcard. Also, comments on the implementation would be nice as this is part of a larger project.
Thanks in advance!
The idea:
I have a class object "boite" that contains a function s_xt that outputs a signal as a function of time and space based on its internal structure. What I would like to do is play this signal at a given location across time. Essentially, I need to sample the generator function s_xt(double x, double t) for a constant space value x and a variable time value t. This function is normalized such that the output is between zero and one. The function is subject to dynamic change, so writing to a .wav file is not a good idea.
The problem:
I need to output the function at a given spatial position directly to the sound card. Essentially the operation:
while(running)
{
soundcard << boite.s_xt(x,t);
t += dt;
}
{
soundcard << boite.s_xt(x,t);
t += dt;
}
Of course things are more complicated. I believe a soundstream is the answer to my question. Regular buffering will not work because the function is not harmonic, so there will always be discontinuities at the end of the buffer leading to clicking sounds.
An attempt at a solution:
I think that perhaps the way to do this is to have two buffers stores in an inherited class from sf::SoundStream. One buffer would be active, and the other buffer would be filled as the active one is played. So, when the program asks for more data and calls onGetData, we point the data chunk to the previous filled buffer, delete the old buffer, and then fill the next buffer from the generator function.
class soundstream:
#ifndef SOUNDSTREAM_H
#define SOUNDSTREAM_H
#include <SFML/Audio.hpp>
#include "../boite/boite.h"
#define SOUNDSAMPLES 1500
#define DTSOUND 0.01
#define AMPSOUND 30000
class soundstream : public sf::SoundStream{
public:
// load the soundBox into the soundstream and intialize
void load(boite* boxX)
{
soundBox_m = boxX;
t_m = 0;
x_m = 0.5;
fillBuffer(activeBuffer_m);
// set the next buffer to null for the onGetData function
nextBuffer_m = nullptr;
initialize(1,44100);
}
// function to call to fill a buffer by sampling the soundBox
void fillBuffer(sf::Int16* paramBuffer)
{
// allocate a new block of memeory for the buffer
paramBuffer = new sf::Int16[SOUNDSAMPLES];
// fill
for(int i = 0; i < SOUNDSAMPLES; i++)
{
paramBuffer[i] = AMPSOUND * soundBox_m->s_xt(x_m, t_m).real(); // s_xt is a complex function, only play real part
// to make sure that we're not getting zeros, print the signal
std::cout << soundBox_m->s_xt(x_m, t_m) << '\n';
t_m += DTSOUND;
}
}
private:
// overload onGetData
virtual bool onGetData(Chunk& data)
{
// if the next buffer is not null (first iteration) delete the current buffer and change the buffers
if(nextBuffer_m != nullptr)
{
delete activeBuffer_m;
activeBuffer_m = nextBuffer_m;
}
// point the data to the first sample of the new sample and set its count
data.samples = activeBuffer_m;
data.sampleCount = SOUNDSAMPLES;
// fill next sample
fillBuffer(nextBuffer_m);
return true;
}
// overlad onSeek as empty function (for now)
virtual void onSeek(sf::Time timeOffset){};
// internal time and position
double x_m;
double t_m;
// two buffers
sf::Int16* activeBuffer_m;
sf::Int16* nextBuffer_m;
// soundBox pointer
boite* soundBox_m;
};
#endif
basic main code idea
#include "boite.h"
#include "soundstream.h"
boite box1;
soundstream box1stream
int main()
{
box1.defineS(params) // a bit more complicated, but that's the idea
box1stream.load(box1);
box1stream.play();
}
#include "soundstream.h"
boite box1;
soundstream box1stream
int main()
{
box1.defineS(params) // a bit more complicated, but that's the idea
box1stream.load(box1);
box1stream.play();
}
What happens:
I get no sound. The cout gives a sample of values on load, and another sample of values on play, but then stops. So it does not continue to sample for more than one iteration.
The Question:
Can someone please help? Either a way to fix this implementation, or a way to constantly output the values of the signal to the soundcard. Also, comments on the implementation would be nice as this is part of a larger project.
Thanks in advance!