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

Author Topic: Simple Real-time Audio Synthesizer with SFML2 Audio(Solved by myself)  (Read 6357 times)

0 Members and 1 Guest are viewing this topic.

kileyi

  • Newbie
  • *
  • Posts: 10
    • View Profile
    • Email
I have read this tutorial.

https://github.com/SFML/SFML/wiki/Tutorial:-Play-Sine-Wave

This is good if I can change the sound buffer tone real time.

Means if I press some key,then I will change the sine wave buffer to other pitch or completely change sine wave to saw or square wave or noise.

And I need to play an editable buffer and the buffer will play endlessly,or loop I guess.

========================================

OK.After a few days work,finally figure it out.

I will paste the code here in case someone may need it.

This is a Simple Real-time Audio Synthesizer which can generate tones on the fly.

I'm using Win7 VS2017 SFML2.5.1.And I only tested this on my computer.Not sure if it is 100% work for others.

Use Keys QWERT ... or ZXCVB ... just below Keys 12345 will trigger the notes,similar to FL Studio Keys.

Code is not optimized, but should be easy enough to understand how to fill audio callback real-time with SFML Audio.





#include <string>
#include <vector>

#include <SFML/Audio.hpp>
#include <SFML/Graphics.hpp>

using namespace std;

int ScreenWidth = 800;
int ScreenHeight = 600;

#define MAXVOL (0.22*32768)
#define DECAY (MAXVOL/4000)

#define KEYS 32
#define M_PI 3.14159265358979323846

float vol[KEYS] = { 0 };
float phase[KEYS] = { 0 };

int sampleRate = 44100;
int numChannel = 2;

int audioBufSize = 2048;

int octave = 0;

//0 sin
//1 triangle
//2 saw
//3 square
//4 white noise
int waveShapeType = 0;

class ShapeMode
{
public:
        static int const sine = 0;
        static int const triangle = 1;
        static int const saw = 2;
        static int const square = 3;
        static int const whiteNoise = 4;
};

sf::RenderWindow window;

string titleStr = "";
string noteStr = "0";
string octStr = "0";
string waveShapeStr = "0";

void UpdateAllString()
{
        titleStr = "waveShape: " + waveShapeStr + ", octave: " + octStr + ", note: " + noteStr;
        window.setTitle(titleStr);
}

const double PI2 = M_PI * 2;

float baseAFreq = 440;

bool keyBuf[sf::Keyboard::Key::KeyCount] = { false };
bool keyBuf_last[sf::Keyboard::Key::KeyCount] = { false };

int keymap[]{
        sf::Keyboard::Key::Z,
        sf::Keyboard::Key::S,
        sf::Keyboard::Key::X,
        sf::Keyboard::Key::D,
        sf::Keyboard::Key::C,
        sf::Keyboard::Key::V,
        sf::Keyboard::Key::G,
        sf::Keyboard::Key::B,
        sf::Keyboard::Key::H,
        sf::Keyboard::Key::N,
        sf::Keyboard::Key::J,
        sf::Keyboard::Key::M,
        sf::Keyboard::Key::Q,
        sf::Keyboard::Key::Num2,
        sf::Keyboard::Key::W,
        sf::Keyboard::Key::Num3,
        sf::Keyboard::Key::E,
        sf::Keyboard::Key::R,
        sf::Keyboard::Key::Num5,
        sf::Keyboard::Key::T,
        sf::Keyboard::Key::Num6,
        sf::Keyboard::Key::Y,
        sf::Keyboard::Key::Num7,
        sf::Keyboard::Key::U,
        sf::Keyboard::Key::I,
        sf::Keyboard::Key::Num9,
        sf::Keyboard::Key::O,
        sf::Keyboard::Key::Num0,
        sf::Keyboard::Key::P,
        sf::Keyboard::Key::LBracket,
        sf::Keyboard::Key::RBracket,
        sf::Keyboard::Key::Backslash
};

int numKeys = sizeof(keymap) / sizeof(keymap[0]);

bool IsKeyPressOnce(int keyCode)
{
        if (keyBuf[keyCode] && !keyBuf_last[keyCode]) return true;
        else return false;
}

class MyStream : public sf::SoundStream
{
public:

        void SetBufSize(int sz, int numChannel, int sampleRate)
        {
                m_samples.resize(sz, 0);
                m_currentSample = 0;
                initialize(numChannel, sampleRate);
        }

private:

        virtual bool onGetData(Chunk& data)
        {
                int len = m_samples.size();
                for (int i = 0; i < len; ++i)
                {
                        m_samples[i] = 0;
                }

                data.samples = &m_samples[m_currentSample];

                int k = 0;
                int c = 0;
                float s = 0;
                float increment = 0;

                for (k = 0; k < numKeys; k++)
                {
                        if (vol[k] <= 0) continue;

                        increment = PI2 * pow(2.0, (k + 3 + 12 * octave) / 12.0) * baseAFreq / sampleRate;

                        for (c = 0; c < audioBufSize; c += numChannel)
                        {
                                if (waveShapeType == ShapeMode::sine)
                                {
                                        s = m_samples[c] + sin(phase[k])*vol[k];
                                }
                                else if (waveShapeType == ShapeMode::triangle)
                                {
                                        double t2 = fmodf(phase[k], PI2) / (PI2);
                                        int t3 = floor(t2 + 0.5f);
                                        double t4 = fabs(t2 - t3) * 4 - 1;
                                        s = m_samples[c] + t4 * vol[k];
                                }
                                else if (waveShapeType == ShapeMode::saw)
                                {
                                        double t2 = phase[k] / PI2;
                                        int t3 = floor(t2 + 0.5f);
                                        double t4 = (t2 - t3) * 2;
                                        s = m_samples[c] + t4 * vol[k];
                                }
                                else if (waveShapeType == ShapeMode::square)
                                {
                                        double t2 = fmodf(phase[k], PI2) / (PI2);
                                        double t3 = t2 > 0.5 ? 1 : -1;
                                        s = m_samples[c] + t3 * vol[k];
                                }
                                else if (waveShapeType == ShapeMode::whiteNoise)
                                {
                                        s = rand() % 65536 - 32768;
                                }
                               
                                if (s > 32767) s = 32767;
                                else if (s < -32768) s = -32768;

                                m_samples[c] = s;
                                m_samples[c + 1] = s;

                                phase[k] += increment;

                                if (vol[k] < MAXVOL)
                                {
                                        vol[k] -= DECAY;
                                        if (vol[k] <= 0)
                                        {
                                                vol[k] = 0;
                                                break;
                                        }
                                }
                        }
                        phase[k] = fmod(phase[k], PI2);
                }

                data.sampleCount = audioBufSize;
                m_currentSample = 0;

                return true;
        }

        virtual void onSeek(sf::Time timeOffset)
        {
                m_currentSample = static_cast<std::size_t>(timeOffset.asSeconds() * getSampleRate() * getChannelCount());
        }

        std::vector<sf::Int16> m_samples;
        std::size_t m_currentSample;
};



int main()
{
        window.create(sf::VideoMode(ScreenWidth, ScreenHeight), "SFML2 Simple Audio Synth");

        MyStream stream;
        stream.SetBufSize(audioBufSize, numChannel, sampleRate);
        stream.play();

        while (window.isOpen())
        {
                sf::Event ev;
                while (window.pollEvent(ev))
                {
                        switch (ev.type)
                        {
                        case sf::Event::Closed: window.close(); break;
                        case sf::Event::KeyPressed:
                                keyBuf[ev.key.code] = true;
                                break;
                        case sf::Event::KeyReleased:
                                keyBuf[ev.key.code] = false;
                                break;
                        default:
                                break;
                        }
                }

                if (keyBuf[sf::Keyboard::Escape])
                {
                        window.close();
                }

                for (int key = 0; key < numKeys; ++key)
                {
                        if (keyBuf[keymap[key]] && vol[key] < MAXVOL)
                        {
                                phase[key] = 0;
                                vol[key] = MAXVOL + DECAY / 2;

                                int note = key + 12 * octave;

                                noteStr = to_string(note);
                                UpdateAllString();

                        }
                       
                        if(!keyBuf[keymap[key]] && vol[key] > 0)
                        {
                                vol[key] -= DECAY;
                        }
                }

                if (IsKeyPressOnce(sf::Keyboard::Key::Left))
                {
                        --waveShapeType;
                        waveShapeType = waveShapeType < 0 ? 4 : waveShapeType;

                        waveShapeStr = to_string(waveShapeType);
                        UpdateAllString();
                }
                if (IsKeyPressOnce(sf::Keyboard::Key::Right))
                {
                        ++waveShapeType;
                        waveShapeType = waveShapeType > 4 ? 0 : waveShapeType;

                        waveShapeStr = to_string(waveShapeType);
                        UpdateAllString();
                }

                if (IsKeyPressOnce(sf::Keyboard::Key::Down))
                {
                        --octave;
                        octave = octave < -3 ? -3 : octave;

                        octStr = to_string(octave);
                        UpdateAllString();
                }
                if (IsKeyPressOnce(sf::Keyboard::Key::Up))
                {
                        ++octave;
                        octave = octave > 4 ? 4 : octave;

                        octStr = to_string(octave);
                        UpdateAllString();
                }

                window.display();
                window.clear();

                sf::sleep(sf::microseconds(1));

                for (int k = 0; k < sf::Keyboard::Key::KeyCount; ++k)
                {
                        keyBuf_last[k] = keyBuf[k];
                }

        }

        return 0;
}

 
« Last Edit: December 22, 2018, 01:27:24 pm by kileyi »

kileyi

  • Newbie
  • *
  • Posts: 10
    • View Profile
    • Email
Re: Sustain Synthesizer like Sine Wave Tutorial?
« Reply #1 on: December 19, 2018, 12:18:32 am »
Any body help?

If SFML can't do this,just tell me no.

Thank you.

eXpl0it3r

  • SFML Team
  • Hero Member
  • *****
  • Posts: 11030
    • View Profile
    • development blog
    • Email
Re: Sustain Synthesizer like Sine Wave Tutorial?
« Reply #2 on: December 19, 2018, 02:41:49 am »
You can definitely write a synth somehow, but no idea about the details.

Checkout FM Composer: https://github.com/stephanedamo/fmcomposer
Official FAQ: https://www.sfml-dev.org/faq.php
Official Discord Server: https://discord.gg/nr4X7Fh
——————————————————————
Dev Blog: https://duerrenberger.dev/blog/

kileyi

  • Newbie
  • *
  • Posts: 10
    • View Profile
    • Email
Re: Sustain Synthesizer like Sine Wave Tutorial?
« Reply #3 on: December 19, 2018, 06:38:47 am »
Thank you,didn't pass building that project.

But give it a quick look.

That app uses a audio callback function with patestCallback,which is portaudio who really does the job.Not SFLM.

It seems the author uses SFML most like a graphic library.

You can check below to conform it is portaudio not SFML audio who does the synth part.

in globalFunctions.cpp
patestCallback function lies 2 line codes,mark that fm_render

char *out = (char*)outputBuffer;
fm_render((fmsynth*)userData, &out[0], framesPerBuffer * 2, FM_RENDER_16);


in globalFunctions.cpp
...
callbackFunc = (void*)&patestCallback;
...

in configEditor_soundDevice.cpp
...
Pa_OpenStream(&stream, NULL, &out, _samplerate, 0, 0, (PaStreamCallback*)callbackFunc, fm)
...
Pa_OpenStream is in portaudio.h


Although this project does #include <SFML/Audio.hpp> in main.cpp

And I don't know what he is doing with SFML audio.
« Last Edit: December 19, 2018, 06:47:19 am by kileyi »