mirror of
https://github.com/ultimatepp/ultimatepp.git
synced 2026-05-15 14:16:07 -06:00
376 lines
7.7 KiB
C++
376 lines
7.7 KiB
C++
#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(...) {}
|
|
}
|