ultimatepp/examples/Synth/QSF.cpp
cxl 376df036cd .examples
git-svn-id: svn://ultimatepp.org/upp/trunk@12698 f0d560ea-af0d-0410-9eb7-867de7ffcac7
2019-01-22 09:33:28 +00:00

449 lines
7.8 KiB
C++

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