mirror of
https://github.com/ultimatepp/ultimatepp.git
synced 2026-05-15 14:16:07 -06:00
.examples
git-svn-id: svn://ultimatepp.org/upp/trunk@12698 f0d560ea-af0d-0410-9eb7-867de7ffcac7
This commit is contained in:
parent
f6bdb66753
commit
376df036cd
12 changed files with 1671 additions and 0 deletions
10
examples/SDLSoundDemo/SDLSoundDemo.upp
Normal file
10
examples/SDLSoundDemo/SDLSoundDemo.upp
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
uses
|
||||
CtrlLib,
|
||||
Synth;
|
||||
|
||||
file
|
||||
main.cpp;
|
||||
|
||||
mainconfig
|
||||
"" = "GUI";
|
||||
|
||||
33
examples/SDLSoundDemo/main.cpp
Normal file
33
examples/SDLSoundDemo/main.cpp
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
#include <CtrlLib/CtrlLib.h>
|
||||
#include <Synth/Synth.h>
|
||||
|
||||
using namespace Upp;
|
||||
|
||||
const char *score_qsf =
|
||||
R"(
|
||||
#ORGAN !261.62:L250V65A70D40R40w1:L50V90f0.50A98R50w-4:V120D60S0B:V70f3w-4:w-4
|
||||
#TOMTOM !174.61:L2000V100r-8D40S0R35:V90f3r-2D50S0B:V80r2D55S0N:f5r-2:
|
||||
#PINGER !415.30:L250V100A98D40S0R40w3:L50V90f0.50A98R50w-4:V120D60S0B:w-4:w-4
|
||||
|
||||
!loop !volume:0.85
|
||||
{ /8 $TOMTOM -5757 5777 5757 5777 5757577757575777 5757 5777 5757 5777 5757577757575777 }
|
||||
{ /8 $TOMTOM -/64_ /8 5757 5777 5757 57 /16 57 57 /8 5757 5777 5757 57 /16 57 57 /8 5757 5777 5757 57 /16 57 57 /8 5757 5777 5757 57 /16 57 57 }
|
||||
{ /8 $PINGER 0_3_2_3_ 0_3_2_3_ 003_2_33 5_8_7_55 5875 5875 0323 0323 5875 ____ +3&8&7 5&8&7 0&8&7 _ }
|
||||
{ $ORGAN $4(0&5&7 0&5&9) }
|
||||
)";
|
||||
|
||||
|
||||
struct MyApp : TopWindow {
|
||||
};
|
||||
|
||||
GUI_APP_MAIN
|
||||
{
|
||||
InitSoundSynth();
|
||||
|
||||
SoundSequence s = ParseQSF(score_qsf);
|
||||
PlaySequence(s);
|
||||
|
||||
MyApp().Run();
|
||||
|
||||
CloseSoundSynth();
|
||||
}
|
||||
193
examples/Synth/Core.cpp
Normal file
193
examples/Synth/Core.cpp
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
#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();
|
||||
}
|
||||
99
examples/Synth/FM.cpp
Normal file
99
examples/Synth/FM.cpp
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
#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));
|
||||
}
|
||||
157
examples/Synth/Gen.cpp
Normal file
157
examples/Synth/Gen.cpp
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
#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;
|
||||
}
|
||||
105
examples/Synth/Operator.cpp
Normal file
105
examples/Synth/Operator.cpp
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
#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) {}
|
||||
}
|
||||
449
examples/Synth/QSF.cpp
Normal file
449
examples/Synth/QSF.cpp
Normal file
|
|
@ -0,0 +1,449 @@
|
|||
#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);
|
||||
}
|
||||
96
examples/Synth/Sequencer.cpp
Normal file
96
examples/Synth/Sequencer.cpp
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
#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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
376
examples/Synth/Synth.cpp
Normal file
376
examples/Synth/Synth.cpp
Normal file
|
|
@ -0,0 +1,376 @@
|
|||
#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(...) {}
|
||||
}
|
||||
136
examples/Synth/Synth.h
Normal file
136
examples/Synth/Synth.h
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
#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
|
||||
12
examples/Synth/Synth.upp
Normal file
12
examples/Synth/Synth.upp
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
library
|
||||
"SDL2 SDL2main";
|
||||
|
||||
file
|
||||
Synth.h,
|
||||
Core.cpp,
|
||||
sample.i,
|
||||
Gen.cpp,
|
||||
Operator.cpp,
|
||||
Sequencer.cpp,
|
||||
QSF.cpp;
|
||||
|
||||
5
examples/Synth/sample.i
Normal file
5
examples/Synth/sample.i
Normal file
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue