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;
}
|