Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: Continuous sound stream from generator function  (Read 2076 times)

0 Members and 1 Guest are viewing this topic.

pauly70605

  • Newbie
  • *
  • Posts: 1
    • View Profile
    • Email
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:
while(running)
{
     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();
}
     
 

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!

dabbertorres

  • Hero Member
  • *****
  • Posts: 506
    • View Profile
    • website/blog
Re: Continuous sound stream from generator function
« Reply #1 on: February 17, 2017, 08:19:45 pm »
Your fillBuffer function is copying your pointer (activeBuffer_m in this case), so it's not changing activeBuffer_m like you want. You want fillBuffer to take a reference to a pointer, or a pointer to a pointer.
So you actually have a memory leak (paramBuffer is never deleted).

BUT, since it's a member function, you don't need to do that. In fact, I'd get rid of fillBuffer. onGetData should be doing what fillBuffer is currently doing in my mind.

Furthermore, the two buffers is complicating things more than helping, I think. Personally, I'd make a static sized array of size SOUNDSAMPLES and just reuse it every time onGetData() is called (SFML makes a copy of what you give it, so this if fine).
Then, you don't have to do any manual memory management, and it technically would be more efficient as well, since you won't be allocating and freeing memory every SOUNDSAMPLES samples.

Doing this stuff would also make implementing onSeek() easier to implement when you get around to it too (just a change of t_m).

Other design stuff:
If you're not ever changing x_m, I'd make it a constant.
load() should really be a constructor.
x_m and x_t don't indicate to me their meanings without context, I'd rename them to something that indicates their meaning better (I'm actually not 100% sure what x_m is. I'd rename t_m to something like "currentTime", "timePosition", etc).

Hope this helps.
« Last Edit: February 17, 2017, 08:25:54 pm by dabbertorres »