Overview
Examples
Screenshots
Comparisons
Applications
Download
Documentation
Tutorials
UppHub
Status & Roadmap
FAQ
Authors & License
Forums
Funding U++
Search on this site











SourceForge.net Logo

SourceForge.net Logo

GitHub Logo

Discord Logo

Synth

 

 

 

 

Synth.h

 

#ifndef _Synth_Synth_h

#define _Synth_Synth_h

 

#include <Core/Core.h>

 

using namespace Upp;

 

enum FORMS {

    WAVEFORM_SIN = 0,

    WAVEFORM_SQUARE = 1,

    WAVEFORM_TRIANGLE = 2,

    WAVEFORM_SAWTOOTH = 3,

 

    WAVEFORM_FIRSTSAMPLE = 4,

    WAVEFORM_SAXOPHONE = WAVEFORM_FIRSTSAMPLE,

    WAVEFORM_VIOLIN = 5,

    WAVEFORM_DOUBLEBASS = 6,

    WAVEFORM_BANJO = 7,

    WAVEFORM_TRUMPET = 8,

    WAVEFORM_LASTSAMPLE = 8,

 

    WAVEFORM_BROWN = 100,

    WAVEFORM_WHITE = 101,

};

 

struct FMOP {

    double duration = 99000;

    double volume = 0;

 

    double f = 1;

    double fdrift = 0;

 

    double attack = 100;

    double decay = 100;

    double sustain = 100;

    double release = 100;

    

    int    waveform = WAVEFORM_SIN;

    

    String Save() const;

    const char *Load(const char *s);

};

 

#define OPCOUNT 5

 

struct Sound {

    double f = 440;

    FMOP   op[OPCOUNT];

    double pan = 0.5;

    

    String Save() const;

    void   Load(const char *s);

    

    Sound();

};

 

struct SoundGen {

    struct FMOPGen : FMOP {

        int     p;

        double  v;

        double  n;

        double *wave_tab;

        

        void   Start() { v = 1e-3; p = 0; n = 0; }

        void   Comp();

        String ToString() const;

        

        double Evaluate(int t, double mf, double mod, double& current_volume);

    };

    

    int      serial;

    int      param_serial;

    int      id;

    int      priority;

    double   f = 440;

    float    lpan = 0.5f;

    float    rpan = 0.5f;

    int      t;

    int      delay;

    FMOPGen  op[OPCOUNT];

    float    feedback[8192];

    double   current_volume = 0;

    double   lfo_mod = 0;

    

    void     Start(const Sound& s);

    float    Get();

    String   ToString() const;

};

 

#define CHUNK_SIZE   512

#define NUM_CHANNELS 20

 

void InitSoundSynth(bool initsdl = true);

void CloseSoundSynth(bool exitsdl = true);

 

void SetChannel(int chi, const Sound& c, int priority = INT_MAX, int id = 0);

void SetChannelVolume(int chi, double volume);

void StopChannelById(int id);

int  FindChannel(int priority, int from, double new_volume);

void StopChannels(int id);

 

void SetGlobalVolume(float vol);

 

struct SoundEvent : Moveable<SoundEvent> {

    Sound *snd;

    float  duration;

    float  frequency;

    float  volume;

};

 

struct SoundSequence {

    mutable int  at = 0;

    int cursor = 0;

    int loop = Null;

    ArrayMap<String, Sound>    bank;

    Vector<Vector<SoundEvent>> event;

 

    int                 GetAt(double at)  { return (int)(at * 44100 / 512); }

    Vector<SoundEvent>& At(double at)     { return event.At(GetAt(at)); }

    void                LoopAt(double at) { loop = GetAt(at); }

    int  SoundIndex(const String& s);

    void Put(double at, int i,

             double volume, double freqency, double duration, bool direct = false);

    void Put(double at, const String& snd,

             double volume, double freqency, double duration, bool direct = false);

};

 

void PlaySequence(const SoundSequence& s);

void PlayTempSequence(SoundSequence&& s);

 

void StopSequencer();

bool IsPlayingSequence();

 

SoundSequence ParseQSF(const String& data);

 

#endif

 

 

 

QSF.cpp

 

#include "Synth.h"

 

struct QSFStatus : Moveable<QSFStatus> {

    double tempo = 120;

    int    sound = -1;

    double duration = 1;

    int    shift = 24;

 

    double volume = 1;

    double volume_low = 0;

    double volume_high = 1;

    double volume_start_at = 0;

    double volume_mul = 0;

    

    double GetVolume(double at) const { return clamp(volume + (at - volume_start_at) * volume_mul, volume_low, volume_high); }

};

 

struct QSFParser : SoundSequence, QSFStatus {

    int         lineno;

    String      line;

    const char *ptr;

    VectorMap<String, String> macro;

    VectorMap<int, String> errors;

 

    double at = 0;

    double at0 = 0;

    double end = 0;

 

    Vector<QSFStatus> stack;

    Vector<double> at_stack;

    

    void   Error(const char *s);

    void   Spaces();

    String ReadID();

    void   Expand();

    

    void   Sound(const char *s);

    void   Tone(int tone, double duration);

    void   Process();

    

    void   Parse(Stream& s);

    

    QSFParser() {

        Sound("130.81:L250V100R30::::");

    }

};

 

void QSFParser::Error(const char *s)

{

    errors.Add(lineno, s);

}

 

void QSFParser::Spaces()

{

    while(IsSpace(*ptr)) ptr++;

}

 

String QSFParser::ReadID()

{

    Spaces();

    String id;

    if(IsAlpha(*ptr) || *ptr == '_') {

        const char *b = ptr;

        while(IsAlNum(*ptr) || *ptr == '_')

            ptr++;

        id = String(b, ptr);

    }

    return id;

}

 

void QSFParser::Expand()

{

    bool expanded;

    auto Error = [&](const char *s) { errors.Add(lineno, s); };

    Index<int> used_macro;

    do {

        expanded = false;

        String r;

        ptr = line;

        Index<int> pm;

        auto GetMacro = [&]()->String {

            String id = ReadID();

            if(id.GetCount()) {

                int q = macro.Find(id);

                if(q >= 0) {

                    if(used_macro.Find(q) < 0) {

                        pm.FindAdd(q);

                        expanded = true;

                        return macro[q];

                    }

                    else

                        Error("recursive macro " + id);

                }

                else

                    Error("uknown macro " + id);

            }

            else

                Error("missing macro id");

            return Null;

        };

        while(*ptr) {

            if(*ptr == '$') {

                ptr++;

                Spaces();

                if(IsDigit(*ptr)) {

                    int n = 0;

                    while(IsDigit(*ptr)) {

                        n = 10 * n + *ptr - '0';

                        ptr++;

                    }

                    Spaces();

                    String txt;

                    int lvl = 0;

                    auto Pars = [&](int l, int r) {

                        while(*ptr) {

                            if(*ptr == l)

                                lvl++;

                            if(*ptr == r && lvl-- == 0)

                                break;

                            ptr++;

                        }

                    };

                    if(*ptr == '[') {

                        const char *b = ptr++;

                        Pars('[', ']');

                        if(*ptr) ptr++;

                        txt = String(b, ptr);

                    }

                    else

                    if(*ptr == '(') {

                        const char *b = ++ptr;

                        Pars('(', ')');

                        txt = String(b, ptr);

                        if(*ptr) ptr++;

                    }

                    else

                        txt = GetMacro();

                    

                    if(n > 0 && n < 100000) {

                        while(n--)

                            r << txt << ' ';

                        expanded = true;

                    }

                    else

                        Error("invalid repetition number");

                }

                else

                    r << GetMacro();

            }

            else

                r << *ptr++;

        }

        line = r;

        for(int q : pm)

            used_macro.Add(q);

    }

    while(expanded);

}

 

void QSFParser::Parse(Stream& src)

{

    while(!src.IsEof()) {

        lineno++;

        line = src.GetLine();

        int q = line.Find("//");

        if(q >= 0)

            line.Trim(q);

        line = TrimBoth(line);

        if(*line == '#') {

            ptr = ~line + 1;

            String id = ReadID();

            Spaces();

            if(id.GetCount())

                macro.GetAdd(id) = ptr;

            else

                Error("missing macro name");

        }

        else {

            Expand();

            Process();

        }

    }

    

}

 

void QSFParser::Sound(const char *s)

{

    sound = SoundIndex(s);

}

 

void QSFParser::Tone(int tone, double duration)

{

    duration = duration / tempo * 240;

    if(tone == -1)

        At(at);

    else

        Put(at, sound, 100 * GetVolume(at), 65.406 * exp2(tone / 12.0), 1000 * duration, IsNull(tone));

    at += duration;

    end = max(at, end);

}

 

void QSFParser::Process()

{

    ptr = line;

    auto ReadNumber = [&]() -> double {

        CParser p(ptr);

        p.NoSkipSpaces().NoSkipComments();

        p.SkipSpaces();

        if(p.IsDouble()) {

            double h = p.ReadDouble();

            ptr = p.GetPtr();

            return h;

        }

        return 0;

    };

    auto ReadCNumber = [&]() -> double {

        Spaces();

        if(*ptr == ':')

            ptr++;

        return ReadNumber();

    };

 

    auto DoTone = [&](int tone) {

        ptr++;

        double ln = 1;

        bool chrd = false;

        for(;;) {

            if(*ptr == '\'' || *ptr == '^')

                tone += 12;

            else

            if(*ptr == ',')

                tone -= 12;

            else

            if(*ptr == '=')

                ln = ln + 1;

            else

            if(*ptr == '.')

                ln = ln + 0.5;

            else

            if(*ptr == ':')

                ln = ln + 0.75;

            else

            if(*ptr == '&')

                chrd = true;

            else

            if(*ptr != ' ')

                break;

            ptr++;

        }

        double h = at;

        Tone(shift + tone, ln * duration);

        if(chrd)

            at = h;

    };

    while(*ptr) {

        if(*ptr == ';') {

            ptr++;

            if(*ptr == ';') {

                at0 = at;

                ptr++;

            }

            else

                at = at0;

            duration = 1;

            shift = 24;

        }

        else

        if(*ptr == '$') {

            ptr++;

            tempo = ReadNumber();

            if(tempo < 1) {

                Error("invalid tempo value");

                tempo = 600;

            }

        }

        else

        if(*ptr == '+') {

            ptr++;

            shift += 12;

        }

        else

        if(*ptr == '-') {

            ptr++;

            shift -= 12;

        }

        else

        if(*ptr == '@') {

            ptr++;

            shift += (int)ReadNumber();

        }

        else

        if(*ptr >= '0' && *ptr <= '9')

            DoTone(*ptr - '0');

        else

        if(*ptr == 't')

            DoTone(10);

        else

        if(*ptr == 'e')

            DoTone(11);

        else

        if(*ptr == '_') {

            Tone(-1, duration);

            ptr++;

        }

        else

        if(*ptr == '*') {

            Tone(Null, duration);

            ptr++;

        }

        else

        if(*ptr == '/') {

            ptr++;

            duration = 1.0 / ReadNumber();

        }

        else

        if(*ptr == 'q') {

            ptr++;

            duration = 1.0 / 4;

        }

        else

        if(*ptr == 'o' || *ptr == 'w') {

            ptr++;

            duration = 1.0 / 8;

        }

        else

        if(*ptr == 'x') {

            ptr++;

            duration = 1.0 / 16;

        }

        else

        if(*ptr == 'y') {

            ptr++;

            duration = 1.0 / 32;

        }

        else

        if(*ptr == 'm') {

            ptr++;

            duration = 1.0 / 2;

        }

        else

        if(*ptr == 'n') {

            ptr++;

            duration = 1.0;

        }

        else

        if(*ptr == '%') {

            ptr++;

            duration *= ReadNumber();

            if(duration <= 0) {

                Error("invalid tone length");

                duration = 1;

            }

        }

        else

        if(*ptr == '!') {

            ptr++;

            String snd;

            Spaces();

            if(IsAlpha(*ptr)) {

                String id = ReadID();

                if(id == "loop")

                    LoopAt(at);

                if(id == "shift")

                    shift += (int)ReadCNumber();

                if(id == "volume") {

                    volume = ReadCNumber();

                    volume_mul = 0;

                }

                if(id == "volume_to") {

                    volume = GetVolume(at);

                    double v = ReadCNumber();

                    double len = ReadCNumber();

                    volume_low = min(v, volume);

                    volume_high = max(v, volume);

                    volume_start_at = at;

                    volume_mul = (v - volume) / (240 / tempo) / len;

                }

                if(id == "cursor")

                    SoundSequence::cursor = GetAt(at);

                if(id == "cut") {

                    event.Trim(GetAt(at));

                    end = at;

                }

                if(id == "tempo") {

                    double t = ReadCNumber();

                    if(t < 1 || t > 10000) {

                        Error("invalid tempo value");

                    }

                    else

                        tempo = t;

                }

            }

            else {

                while(*ptr && *ptr != ' ')

                    snd.Cat(*ptr++);

                Sound(snd);

            }

        }

        else

        if(*ptr == '[') {

            ptr++;

            if(stack.GetCount() < 100)

                stack.Add() = *this;

            else

                Error("stack full");

        }

        else

        if(*ptr == ']') {

            ptr++;

            if(stack.GetCount())

                (QSFStatus&)*this = stack.Pop();

            else

                Error("stack empty");

        }

        else

        if(*ptr == '{') {

            ptr++;

            if(stack.GetCount() < 100) {

                stack.Add() = *this;

                at_stack.Add(at);

            }

            else

                Error("stack full");

        }

        else

        if(*ptr == '}') {

            ptr++;

            if(stack.GetCount() && at_stack.GetCount()) {

                (QSFStatus&)*this = stack.Pop();

                at = at_stack.Pop();

            }

            else

                Error("stack empty");

        }

        else

            ptr++;

    }

    int q = GetAt(end);

    if(q > 0)

        event.At(q);

}

 

SoundSequence ParseQSF(const String& data)

{

    StringStream ss(data);

    QSFParser p;

    p.Parse(ss);

    return pick(p);

}

 

 

 

Operator.cpp

 

#include "Synth.h"

 

Sound::Sound()

{

    op[0].duration = 250;

    op[0].volume = 100;

}

 

String Aformat(double x)

{

    return (int)x == x ? AsString(x) : x < 100000 ? Format("%.2f", x) : Format("%g", x);

}

 

String FMOP::Save() const

{

    String r;

 

    auto Put = [&](char c, double val, double def) {

        if(val != def)

            r << c << Aformat(val);

    };

 

    Put('L', duration, 99000);

    Put('V', volume, 0);

    Put('f', f, 1);

    Put('r', fdrift, 0);

    Put('A', attack, 100);

    Put('D', decay, 100);

    Put('S', sustain, 100);

    Put('R', release, 100);

    

    return r + decode(waveform, WAVEFORM_SQUARE, "Q",

                                WAVEFORM_TRIANGLE, "T",

                                WAVEFORM_SAWTOOTH, "W",

                                WAVEFORM_BROWN, "B",

                                WAVEFORM_WHITE, "N",

                                WAVEFORM_SIN, "",

                                ~("w" + AsString(waveform - WAVEFORM_FIRSTSAMPLE)));

}

 

const char * FMOP::Load(const char *s)

{

    auto Get = [&](double& r) {

        try {

            CParser p(s);

            if(p.IsDouble())

                r = p.ReadDouble();

            s = p.GetPtr();

        }

        catch(CParser::Error) {}

    };

    

    while(*s) {

        if(*s == ':') {

            s++;

            break;

        }

        switch(*s++) {

        case 'L': Get(duration); break;

        case 'V': Get(volume); break;

        case 'f': Get(f); break;

        case 'r': Get(fdrift); break;

        case 'A': Get(attack); break;

        case 'D': Get(decay); break;

        case 'S': Get(sustain); break;

        case 'R': Get(release); break;

        case 'Q': waveform = WAVEFORM_SQUARE; break;

        case 'T': waveform = WAVEFORM_TRIANGLE; break;

        case 'W': waveform = WAVEFORM_SAWTOOTH; break;

        case 'B': waveform = WAVEFORM_BROWN; break;

        case 'N': waveform = WAVEFORM_WHITE; break;

        case 'w':

            double w;

            Get(w);

            waveform = clamp(WAVEFORM_FIRSTSAMPLE + (int)w, (int)WAVEFORM_FIRSTSAMPLE, (int)WAVEFORM_LASTSAMPLE);

        }

    }

    return s;

}

 

String Sound::Save() const

{

    String r;

    r << Aformat(f) << ':';

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

        if(i)

            r << ':';

        r << op[i].Save();

    }

    return r;

}

 

void Sound::Load(const char *s)

{

    try {

        CParser p(s);

        if(p.IsDouble())

            f = p.ReadDouble();

        p.Char(':');

        s = p.GetPtr();

        for(int i = 0; i < OPCOUNT; i++)

            s = op[i].Load(s);

    }

    catch(CParser::Error) {}

}

 

 

 

Synth.cpp

 

#include "Synth.h"

 

#include <Core/Core.h>

 

using namespace Upp;

 

enum {

    WAVECOUNT = 2048,

    WAVEMASK = 2047,

    

    NOISECOUNT = 1024 * 1024 * 4,

    NOISEMASK = NOISECOUNT - 1,

};

 

struct WaveForm {

    float wave[2048];

};

 

float BrownNoise[NOISECOUNT];

 

INITBLOCK {

    float p;

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

        p = clamp(p + (0.02 * (Randomf() * 2 - 1)) / 1.02, -1/3.5, 1/3.5);

        BrownNoise[i] = p;

    }

}

 

void MakeWave(const char *s, WaveForm& h)

{

    for(int i = 0; i < WAVECOUNT; i++)

        h.wave[i] = 0;

    

    try {

        CParser p(s);

        int harm = 1;

        int i = 0;

        double a = 1;

        auto fn0 = [&]() -> double { return sin(M_2PI * i * harm / WAVECOUNT); };

        Function<double ()> fn = fn0;

        for(;;) {

            int ii = (i * harm)/* & WAVEMASK*/;

            if(p.Char('T'))

                fn = [&]() -> double { return (1024.0 - abs(WAVECOUNT / 2 - ii)) / WAVECOUNT / 4 - 1; };

            else

            if(p.Char('t'))

                fn = [&]() -> double { return -(1024.0 - abs(WAVECOUNT / 2 - ii)) / WAVECOUNT / 4 + 1; };

            else

            if(p.Char('Z'))

                fn = [&]() -> double { return 2.0f * ii / (WAVECOUNT - 1) - 1; };

            else

            if(p.Char('z'))

                fn = [&]() -> double { return -2.0f * ii / (WAVECOUNT - 1) + 1; };

            else

            if(p.Char('S'))

                fn = fn0;

            else

            if(p.IsDouble()) {

                double a = p.ReadDouble();

                if(p.Char(':'))

                    harm = (int)a;

                else {

                    for(i = 0; i < WAVECOUNT; i++)

                        h.wave[i] += (float)(a * fn());

                    harm++;

                }

            }

            else

                break;

        }

    }

    catch(...) {}

}

 

float *GetWave(const String& h)

{

    static Mutex _;

    Mutex::Lock __(_);

    static ArrayMap<String, WaveForm> cache;

    int q = cache.Find(h);

    if(q >= 0)

        return cache[q].wave;

    MakeWave(h, cache.Add(h));

    return cache.Top().wave;

}

 

Instrument::Instrument()

{

    delay = 0;

    attack = 0;

    decay = 0;

    sustain = 1;

    release = 0;

    wave = "1";

    mod_wave = "1";

    mod_amplitude = 0;

    mod_frequency = 1;

    noise_kind = 0;

    noise_amplitude = 1;

};

 

const int TABN = 4096;

 

static double table[TABN + 2];

 

double LOGVOL(double x)

{

    return pow(10, x * 60 / 20) / pow(10, 60 / 20);

}

 

force_inline

double LogVol(double x)

{

    double id = TABN * x;

    int ii = (int)id;

    if(ii < 0) return 0;

    if(ii > TABN) return x;

    double f = id - ii;

    return (1 - f) * table[ii] + f * table[ii + 1];

}

 

INITBLOCK {

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

        double q = i / (double)TABN;

        table[i] = LOGVOL(q);

    }

    table[TABN + 1] = (TABN + 1) / 256.0;

    table[0] = 0;

}

 

double MakeNoise(int kind)

{

    static dword state = 1;

    state ^= state << 13;

    state ^= state >> 17;

    state ^= state << 5;

    double white = 2.0 / 4294967295.0 * state - 1;

 

    static double p;

    static double b0, b1, b2;

 

    switch(kind) {

    case 1:

        return white;

    case 2:

        b0 = 0.99765 * b0 + white * 0.0990460;

        b1 = 0.96300 * b1 + white * 0.2965164;

        b2 = 0.57000 * b2 + white * 1.0526913;

        return b0 + b1 + b2 + white * 0.1848;

    case 3:

        p = clamp(p + (0.02 * (Randomf() * 2 - 1)) / 1.02, -1/3.5, 1/3.5);

        return p * 3.5;

    }

    return 0;

}

 

struct SynthSound : SoundGenerator {

    virtual bool Get(float *data, int len);

 

    float  volume;

    float  fdelta;

    float  sustain;

    float  mdelta;

    float  mod_amp;

    int    duration;

    int    delay;

    int    attack;

    int    decay;

    int    release;

    float *wave;

    float *mod_wave;

    int    noise_kind;

    float  noise_amplitude;

    float  release_from = 0;

    float  last = 0;

    float  frequency_mul;

    bool   has_noise;

    float  noise[8];

 

    float  q = 0;

    float  w = 0;

    int    t = 0;

 

    void SetVolume(float vol)          { volume = vol; }

    void SetFrequency(float frequency) { fdelta = (WAVEMASK + 1) * frequency * frequency_mul / 44200; }

    

    void Set(float volume, float frequency, float duration, const Instrument& m);

 

    SynthSound(float volume, float frequency, float duration, const Instrument& m) {

        Set(volume, frequency, duration, m);

    }

};

 

void SynthSound::Set(float volume, float frequency, float duration_, const Instrument& m)

{

    frequency_mul = m.frequency_mul;

    

    SetVolume(volume);

    SetFrequency(frequency);

    

    sustain = m.sustain;

    mdelta = (WAVEMASK + 1) * m.mod_frequency / 44200;

    mod_amp = (WAVEMASK + 1) * m.mod_amplitude / 44200;

    duration = int(44200 * duration_);

    delay = int(44200 * m.delay);

    attack = int(44200 * m.attack);

    decay = int(44200 * m.decay);

    release = int(44200 * m.release);

    wave = GetWave(m.wave);

    mod_wave = GetWave(m.mod_wave);

    noise_kind = m.noise_kind;

    noise_amplitude = m.noise_amplitude;

    

    has_noise = m.has_noise;

    for(int i = 0; i < 8; i++)

        noise[i] = m.noise[i];

}

 

bool SynthSound::Get(float *b, int len)

{

    bool plays = true;

    float sustain_volume = sustain * volume;

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

        if(t < delay)

            *b++ = 0;

        else {

            float envelope = 0;

            if(t < delay + attack) {

                envelope = (t * volume) / attack;

                release_from = envelope;

                last = t;

            }

            else

            if(t < delay + attack + decay) {

                envelope = volume - (volume - sustain_volume) * (t - delay - attack) / decay;

                release_from = envelope;

                last = t;

            }

            else

            if(t < duration || duration < 0) {

                envelope = sustain_volume;

                release_from = envelope;

                last = t;

            }

            else

            if(release) {

                envelope = release_from - release_from * (t - last) / release;

                if(envelope <= 0) {

                    plays = false;

                    envelope = 0;

                }

            }

            else

                plays = false;

            double a = wave[(int)q & WAVEMASK];

            if(has_noise) {

                a += BrownNoise[(int)q & NOISEMASK];

            }

            if(noise_kind)

                a += noise_amplitude * MakeNoise(noise_kind);

            *b++ = envelope * a;

            w += mdelta;

            q += fdelta + mod_amp * mod_wave[(int)w & WAVEMASK];

        }

        t++;

    }

    return plays;

}

 

int64 Play(float volume, float frequency, float duration, const Instrument& m)

{

    return AddSound(new SynthSound(volume, frequency, duration, m));

}

 

int64 Play(float volume, float frequency, const Instrument& m)

{

    return Play(volume, frequency, -1, m);

}

 

 

void SetVolume(int64 id, float volume)

{

    AlterSound(id, [=](SoundGenerator *sg) {

        SynthSound *ss = dynamic_cast<SynthSound *>(sg);

        if(ss)

            ss->SetVolume(volume);

    });

}

 

void SetFrequency(int64 id, float frequency)

{

    AlterSound(id, [=](SoundGenerator *sg) {

        SynthSound *ss = dynamic_cast<SynthSound *>(sg);

        if(ss)

            ss->SetFrequency(frequency);

    });

}

 

void StopSound(int64 id)

{

    AlterSound(id, [=](SoundGenerator *sg) {

        SynthSound *ss = dynamic_cast<SynthSound *>(sg);

        if(ss)

            ss->duration = 0;

    });

}

 

void Instrument::Read(CParser& p)

{

    while(!p.IsEof()) {

        if(p.Char('{')) {

            const char *s = p.GetPtr();

            for(;;) {

                if(p.IsChar('}') || p.IsEof()) {

                    wave = String(s, p.GetPtr());

                    break;

                }

                p.SkipTerm();

            }

            p.Char('}');

            if(p.Char('@'))

                frequency_mul = p.ReadDouble();

        }

        else

        if(p.Char('[')) {

            const char *s = p.GetPtr();

            for(;;) {

                if(p.IsChar(']') || p.IsEof()) {

                    mod_wave = String(s, p.GetPtr());

                    break;

                }

                p.SkipTerm();

            }

            p.Char(']');

            for(;;) {

                if(p.Char('@'))

                    mod_amplitude = p.ReadDouble();

                else

                if(p.Char('^'))

                    mod_frequency = p.ReadDouble();

                else

                    break;

            }

        }

        else

        if(p.Char('<')) {

            has_noise = true;

            int ii = 0;

            while(!p.Char('>') && ii < 8)

                noise[ii++] = p.ReadDouble();

        }

        else

        if(p.Char('a'))

            attack = p.ReadDouble();

        else

        if(p.Char('d'))

            decay = p.ReadDouble();

        else

        if(p.Char('s'))

            sustain = p.ReadDouble();

        else

        if(p.Char('r'))

            release = p.ReadDouble();

        else

            break;

    }

}

 

void Instrument::Read(const char *s)

{

    try {

        CParser p(s);

        Read(p);

    }

    catch(...) {}

}

 

 

 

Sequencer.cpp

 

#include "Synth.h"

 

void SoundSequence::Put(double at, int i, double volume, double frequency, double duration, bool direct)

{

    SoundEvent& e = At(at).Add();

    e.frequency = (float)frequency;

    e.duration = (float)duration;

    e.volume = (float)volume;

    e.snd = &bank[i];

    if(direct) {

        e.duration = (float)e.snd->op[0].duration;

        e.frequency = (float)e.snd->f;

    }

}

 

int SoundSequence::SoundIndex(const String& s)

{

    int q = bank.Find(s);

    if(q < 0) {

        q = bank.GetCount();

        bank.Add(s).Load(s);

    }

    return q;

}

 

void SoundSequence::Put(double at, const String& snd, double volume, double frequency, double duration,

bool direct)

{

    Put(at, SoundIndex(snd), volume, frequency, duration, direct);

}

 

 

const int SEQUENCER_CHANNEL_ID = -123;

std::atomic<const SoundSequence *> sS;

 

void PlaySequence(const SoundSequence& s)

{

    s.at = s.cursor;

    sS = &s;

}

 

bool IsPlayingSequence()

{

    return sS.load();

}

 

void StopSequencer()

{

    sS = NULL;

    StopChannels(SEQUENCER_CHANNEL_ID);

}

 

void PlayTempSequence(SoundSequence&& s)

{

    static int ii;

    static SoundSequence h[2];

    

    h[ii] = pick(s);

    PlaySequence(h[ii]);

    ii = !ii;

}

 

void Sequencer(int chunk_size)

{

    static int ch;

    ch += chunk_size;

    if(ch < CHUNK_SIZE)

        return;

    ch -= CHUNK_SIZE;

 

    int PR = 10;

    const SoundSequence *ss = sS.load();

    if(ss) {

        if(ss->at >= ss->event.GetCount()) {

            if(!IsNull(ss->loop) && ss->event.GetCount())

                ss->at = ss->loop;

            else {

                sS = NULL;

                return;

            }

        }

        const Vector<SoundEvent>& e = ss->event[ss->at++];

        for(const SoundEvent& s : e) {

            int ii = FindChannel(PR, 1, s.volume);

            if(ii >= 0) {

                Sound snd = *s.snd;

                if(s.frequency > 0) {

                    snd.f = s.frequency;

                    snd.op[0].volume *= s.volume / 100;

                    snd.op[0].duration = s.duration;

                }

                SetChannel(ii, snd, 10, SEQUENCER_CHANNEL_ID);

            }

        }

    }

}

 

 

 

Core.cpp

 

#include "Synth.h"

 

#ifdef PLATFORM_POSIX

#include <SDL2/SDL.h>

#else

#include <SDL.h>

#endif

 

SoundGen app_sch[NUM_CHANNELS]; // to avoid lengthy locking, keep copy under different mutex

SoundGen gen_sch[NUM_CHANNELS]; // this is copied to generator and used to actually generate the sound

 

INITBLOCK {

    for(int i = 0; i < NUM_CHANNELS; i++) // initialize wave_tab

        for(int j = 0; j < OPCOUNT; j++) {

            app_sch[i].op[j].Comp();

            gen_sch[i].op[j].Comp();

        }

}

 

int      in_sch_serial;

 

#if 1

struct AppSoundLock {

    AppSoundLock() { SDL_LockAudio(); }

    ~AppSoundLock() { SDL_UnlockAudio(); }

};

 

struct GenSoundLock {

    GenSoundLock() {}

    ~GenSoundLock() {}

};

#else

SpinLock s_in_sch_lock;

 

struct AppSoundLock {

    AppSoundLock() { s_in_sch_lock.Enter(); }

    ~AppSoundLock() { s_in_sch_lock.Leave(); }

};

 

struct GenSoundLock {

    GenSoundLock() { s_in_sch_lock.Enter(); }

    ~GenSoundLock() { s_in_sch_lock.Leave(); }

};

#endif

 

void SetChannel(int chi, const Sound& c, int priority, int id)

{

    AppSoundLock __;

    SoundGen& ch = app_sch[chi];

    ch.Start(c);

    ch.priority = priority;

    ch.serial = ++in_sch_serial;

    ch.id = id;

}

 

void StopChannels(int id)

{

    AppSoundLock __;

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

        SoundGen& ch = app_sch[i];

        if(ch.id == id) {

            ch.serial = ++in_sch_serial;

            ch.current_volume = app_sch[i].op[0].volume = 0;

        }

    }

}

 

void SetChannelVolume(int chi, double volume)

{

    AppSoundLock __;

    SoundGen& ch = app_sch[chi];

    ch.param_serial = ++in_sch_serial;

    ch.op[0].volume = pow(10, (volume - 100) / 40);

}

 

int FindChannel(int priority, int from, double new_volume)

{

    new_volume = pow(10, (new_volume - 100) / 40);

    AppSoundLock __;

    int    besti = -1;

    int    best_priority = INT_MAX;

    double best_volume = 100;

    for(int i = from; i < NUM_CHANNELS; i++) {

        SoundGen& ch = app_sch[i];

//        if(ch.current_volume < 0.001) {

//            besti = i;

//            break;

//        }

        if(/*ch.priority <= priority &&

           (ch.priority < best_priority || ch.priority == best_priority && */ch.current_volume < best_volume) {

               besti = i;

//               best_priority = ch.priority;

               best_volume = ch.current_volume;

        }

    }

//    return besti;

    return best_volume < new_volume ? besti : -1;

//    DDUMP(best_volume);

//    DDUMP(besti);

/*    if(besti < 0)

        for(int i = from; i < NUM_CHANNELS; i++)

            if(app_sch[i].current_volume < 0.01) {

                besti = i;

                break;

            }

    return besti;*/

}

 

extern void Sequencer(int chunk_size);

 

float global_volume = 1;

 

void SetGlobalVolume(float vol)

{

    AppSoundLock __;

    global_volume = vol;

}

 

void MyAudioCallback(void *, Uint8 *stream, int len)

{

    int chunk_size = len / (2 * sizeof(float));

    Sequencer(chunk_size);

    {

#if 0

        RLOG("====================================");

#endif

        GenSoundLock __;

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

            if(app_sch[i].serial != gen_sch[i].serial)

                gen_sch[i] = app_sch[i];

            if(app_sch[i].param_serial != gen_sch[i].param_serial) {

                gen_sch[i].op[0].volume = app_sch[i].op[0].volume;

                gen_sch[i].current_volume = 1;

                app_sch[i].param_serial = gen_sch[i].param_serial;

            }

#if 0

            RLOG(i);

            RLOG(gen_sch[i]);

#endif

        }

    }

    

    float *d = (float *)stream;

 

    memset(d, 0, len);

    for(int j = 0; j < NUM_CHANNELS; j++) {

        SoundGen& ch = gen_sch[j];

        d = (float *)stream;

        double v = 0;

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

            float h = ch.Get();

            *d++ += ch.lpan * h;

            *d++ += ch.rpan * h;

        }

    }

 

    float *e = (float *)stream + 2 * chunk_size;

    for(float *d = (float *)stream; d < e; d++)

        *d = clamp(*d * global_volume, -1.0f, 1.0f);

 

    {

        GenSoundLock __;

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

            app_sch[i].current_volume = gen_sch[i].current_volume;

        }

    }

}

 

void InitSoundSynth(bool initsdl)

{

    if(initsdl && SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO) < 0)

        return;

    

    SDL_AudioSpec want;

    memset(&want, 0, sizeof(want));

    want.freq = 44100;

    want.format = AUDIO_F32SYS;

    want.channels = 2;

    want.samples = CHUNK_SIZE;

    want.callback = MyAudioCallback;

    

    if(SDL_OpenAudio(&want, NULL) < 0)

       LOG("Failed to open audio: " + (String)SDL_GetError());

    

    SDL_PauseAudio(0);

}

 

void CloseSoundSynth(bool exitsdl)

{

    SDL_CloseAudio();

    if(exitsdl)

        SDL_Quit();

}

 

 

 

FM.cpp

 

#include "Synth.h"

 

double ADSR::Evaluate(double t, double duration)

{

    if(t < delay)

        return 0;

    if(t > duration) {

        t -= duration;

        if(t > release)

            return 0;

        return sustain * (release - t) / release;

    }

    t -= delay;

    if(t < attack)

        return t / attack;

    t -= attack;

    if(t < decay) {

        return (sustain * t + decay - t) / decay;

    }

    return sustain;

}

 

struct FMSound : SoundGenerator, FMPatch {

    virtual bool Get(float *data, int len);

 

    double  volume;

    double  duration;

 

    double  fc, fm1, fm2, fmf;

 

    int     ti = 0;

    

    double  noisedir = 0;

    double  noiseval = 0;

 

    void SetVolume(double vol)          { volume = vol; }

    

    void Set(double volume, double frequency, double duration, const FMPatch& m);

 

    FMSound(double volume, double frequency, double duration, const FMPatch& m) {

        Set(volume, frequency, duration, m);

    }

};

 

void FMSound::Set(double volume, double frequency, double duration, const FMPatch& m)

{

    (FMPatch&)*this = m;

 

    this->volume = volume;

    this->duration = duration;

    

    fc = frequency;

    fm1 = fc * m1;

    fm2 = fc * m2;

    fmf = fc * mf;

}

 

bool FMSound::Get(float *b, int len)

{

    bool plays = true;

    double t;

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

        t = ti * (1.0 / 44200);

        double pt = M_2PI * t;

        

        static dword state = 1;

        state ^= state << 13;

        state ^= state >> 17;

        state ^= state << 5;

        double white = 2.0 / 4294967295.0 * state - 1;

/*        noisedir = clamp(noisedir + 0.0001 * fc * white, -0.08, 0.08);

        noiseval = noisedir;

        if(noiseval > 1) {

            noisedir = -abs(noisedir);

            noiseval = 1;

        }

        if(noiseval < 0) {

            noisedir = abs(noisedir);

            noiseval = -1;

        }

        

        *b++ = tanh(noiseval);

*/        

        

        *b++ = float(volume * adsr.Evaluate(t, duration)

                            * sin(pt * fc + adsr1.Evaluate(t, duration)

                                            * beta1

                                            * sin(pt * fm1 + betan * white + betaf * adsrf.Evaluate(t, duration) * sin(pt * fmf))

                                           + adsr2.Evaluate(t, duration) * beta2 * sin(pt * fm2)));

 

        ti++;

    }

    return t < duration + adsr.release;

}

 

int64 Play(double volume, double frequency, double duration, const FMPatch& patch)

{

    return AddSound(new FMSound(volume, frequency, duration, patch));

}

 

 

 

Gen.cpp

 

#include "Synth.h"

 

#include "sample.i"

 

double waveform_table[9][4096];

 

INITBLOCK {

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

        double arg = M_2PI * i / 4096;

        waveform_table[WAVEFORM_SIN][i] = sin(arg);

        waveform_table[WAVEFORM_SQUARE][i] = sgn(waveform_table[WAVEFORM_SIN][i]);

        waveform_table[WAVEFORM_TRIANGLE][i] = abs(2048 - i) / 1024.0 - 1;

        waveform_table[WAVEFORM_SAWTOOTH][i] = 2 * i / 4096.0 - 1;

    }

    

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

        waveform_table[4][i] = wave_saxophone[i];

        waveform_table[5][i] = wave_violin[i];

        waveform_table[6][i] = wave_doublebass[i];

        waveform_table[7][i] = wave_banjo[i];

        waveform_table[8][i] = wave_trumpet[i];

    }

}

 

double Rate(double x)

{

    return pow(10, (6 * x / 100 - 6));

}

 

void SoundGen::FMOPGen::Comp()

{

    attack = 1 + Rate(attack);

    decay = 1 - Rate(decay);

    release = 1 - Rate(release);

    duration = 44100 * duration / 1000;

    fdrift = exp2(fdrift / 12 / 44100);

    if(volume > 0)

        volume = pow(10, (volume - 100) / 40);

    if(sustain > 0)

        sustain = pow(10, (sustain - 100) / 40);

    wave_tab = waveform_table[clamp(waveform, 0, (int)WAVEFORM_LASTSAMPLE)];

}

 

String SoundGen::FMOPGen::ToString() const

{

    return String() << "Volume: " << volume << ", phase: " << p << ", ADSR volume: " << v;

}

 

force_inline

double SoundGen::FMOPGen::Evaluate(int t, double mf, double mod, double& cv)

{

    if(volume == 0)

        return 0;

    if(t >= duration) {

        v *= release;

        cv = v;

    }

    else

    switch(p) {

    case 0:

        v *= attack;

        if(v >= 1.0) {

            v = 1.0;

            p = 1;

        }

        cv = v > 0;

        break;

    case 1:

        v *= decay;

        if(v < sustain) {

            v = sustain;

            p = 2;

        }

        cv = v;

        break;

    default:

        cv = v = sustain;

    }

    cv *= volume;

    f *= fdrift;

    double fn;

    if(findarg(waveform, WAVEFORM_BROWN, WAVEFORM_WHITE) >= 0) {

        static dword state = 1;

        state ^= state << 13;

        state ^= state >> 17;

        state ^= state << 5;

        fn = 2.0 / 4294967295.0 * state - 1;

        if(waveform == WAVEFORM_BROWN)

            fn = n = clamp(n + 0.06 * fn, -1.0, 1.0);

    }

    else {

#if 0

        double arg = mf * f * t + mod;

        if(waveform == WAVEFORM_SIN)

            fn = sin(arg);

        else {

            arg = fmod(arg, M_2PI);

            fn = waveform == WAVEFORM_SQUARE ? sgn(abs(M_PI - arg) / (M_PI / 2) - 1) :

                 waveform == WAVEFORM_TRIANGLE ? abs(M_PI - arg) / (M_PI / 2) - 1 :

                 waveform == WAVEFORM_SAWTOOTH ? arg / M_PI - 1 :

                 0;

        }

#else

        double arg = mf * f * t + mod;

        fn = wave_tab[dword((4096 / M_2PI) * arg) & 4095];

#endif

    }

    

    return volume * v * fn;

}

 

void SoundGen::Start(const Sound& s)

{

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

        (FMOP&)op[i] = s.op[i];

        op[i].Comp();

        op[i].Start();

    }

    f = s.f / 22100 * M_PI;

    lpan = (float)s.pan;

    rpan = (float)(1 - s.pan);

    for(int i = 3; i <= 4; i++) // vibrato, tremolo

        op[i].f = op[i].f / 22100 * M_PI;

    t = 0;

    memset(feedback, 0, sizeof(feedback));

    current_volume = 1;

    lfo_mod = Randomf() * M_2PI;

}

 

float SoundGen::Get()

{

#if 0

    float r = op[0].Evaluate(t, f, 0, current_volume);

#else

    if(current_volume < 0.001)

        return 0;

    double dummy;

    double lfo1 = op[3].Evaluate(t, 1, lfo_mod, dummy);

    double lfo2 = op[4].Evaluate(t, 1, lfo_mod, dummy);

    double v = (1 - op[4].volume + lfo2);

    float r =

        (float)(v *

            op[0].Evaluate(t, f, op[1].Evaluate(t, f, lfo1, dummy) + op[2].Evaluate(t, f, lfo1, dummy) + lfo1, current_volume)

         );

#endif

    t++;

    return r;

}

 

String SoundGen::ToString() const

{

    String r;

    r << "----- Current volume: " << current_volume << ", time: " << t << '\n';

    for(int i = 0; i < OPCOUNT; i++)

        r << i << ": " << op[i] << '\n';

    return r;

}

 

 

 

 

Do you want to contribute?