diff options
author | Florian Jung <flo@thinkpad.(none)> | 2010-12-29 16:55:25 +0100 |
---|---|---|
committer | Florian Jung <flo@thinkpad.(none)> | 2010-12-29 16:55:25 +0100 |
commit | 7113f02ae87482211aec5046f9ac46c3cc9ad017 (patch) | |
tree | b6484b45317e7e80567d9902cf94843d227ce30e /synth |
Initial commit
Diffstat (limited to 'synth')
-rw-r--r-- | synth/.gitignore | 1 | ||||
-rw-r--r-- | synth/CHANGELOG | 131 | ||||
-rw-r--r-- | synth/Makefile | 33 | ||||
-rw-r--r-- | synth/OPTIMIZATIONS | 20 | ||||
-rw-r--r-- | synth/README.developer | 28 | ||||
-rw-r--r-- | synth/TODO | 32 | ||||
-rw-r--r-- | synth/TODO.done | 71 | ||||
-rw-r--r-- | synth/channel.cpp | 338 | ||||
-rw-r--r-- | synth/channel.h | 63 | ||||
-rw-r--r-- | synth/cli.cpp | 174 | ||||
-rw-r--r-- | synth/cli.h | 6 | ||||
-rw-r--r-- | synth/defines.cpp | 3 | ||||
-rw-r--r-- | synth/defines.h | 62 | ||||
-rw-r--r-- | synth/envelope.cpp | 168 | ||||
-rw-r--r-- | synth/envelope.h | 65 | ||||
-rw-r--r-- | synth/filter.cpp | 59 | ||||
-rw-r--r-- | synth/filter.h | 27 | ||||
-rw-r--r-- | synth/fixed.h | 10 | ||||
-rw-r--r-- | synth/globals.cpp | 60 | ||||
-rw-r--r-- | synth/globals.h | 73 | ||||
-rw-r--r-- | synth/jack.cpp | 467 | ||||
-rw-r--r-- | synth/jack.h | 12 | ||||
-rw-r--r-- | synth/load.cpp | 191 | ||||
-rw-r--r-- | synth/load.h | 11 | ||||
-rw-r--r-- | synth/main.cpp | 190 | ||||
-rw-r--r-- | synth/note.cpp | 437 | ||||
-rw-r--r-- | synth/note.h | 80 | ||||
-rw-r--r-- | synth/parser.cpp | 618 | ||||
-rw-r--r-- | synth/parser.h | 47 | ||||
-rw-r--r-- | synth/programs.cpp | 224 | ||||
-rw-r--r-- | synth/programs.h | 193 | ||||
-rw-r--r-- | synth/readwave.cpp | 137 | ||||
-rw-r--r-- | synth/readwave.h | 9 | ||||
-rw-r--r-- | synth/util.cpp | 206 | ||||
-rw-r--r-- | synth/util.h | 31 |
35 files changed, 4277 insertions, 0 deletions
diff --git a/synth/.gitignore b/synth/.gitignore new file mode 100644 index 0000000..6569f67 --- /dev/null +++ b/synth/.gitignore @@ -0,0 +1 @@ +synth diff --git a/synth/CHANGELOG b/synth/CHANGELOG new file mode 100644 index 0000000..81c9756 --- /dev/null +++ b/synth/CHANGELOG @@ -0,0 +1,131 @@ +änderungen in 42b:winzige optimierung bei envelope-attack-phase +änderungen in 42: sample and hold wird als LFO angesehen und funzt. +änderungen in 40c:lfo_ und filter_update_freq im cli und in der + konfig einstellbar +änderungen in 40b:kleinigkeiten verbessert +änderungen in 40: auf LFOs umgestellt + + STABILE VERSION: 38b: FM-Synthese mit einstellungen und controllern + möglich. wellenformen können auch aus wav-dateien geladen werden. + vibrato, tremolo, portamento, reattack, festsetzbare stimmenanzahl, + "abgewürgte" noten können ausgeblendet werden. channel-modulation + muss manuell definiert werden. pitchbend und controllerreset, + frameskip, stereo, KSR, KSL funktionieren. CLI, configs und + instrumentendefinitionen funktionieren. anschlagsdynamik kann + verschiedene parameter steuern. analogsynthese und osc-sync. + +änderungen in 38b:bugfixes: sustain_orig eingeführt, bei reattack + wird die vel neu gesetzt, divisionen durch null + verhindert +änderungen in 38: frameskip ist nun absolut alltagstauglich + valgrind-gecheckt, halbwegs clean +änderungen in 37e:velocity kann fm-stärke, vol und filter steuern + 37d coredumpt am ende... sehr merkwürdig. dort + wird result im parser direkt geführt + 37d_ii funktioniert. hier wird das result wie + gehabt erst am ende gesetzt. merkwürdig... + außerdem: bug gefixt (s|atoi|atof| beim parsen) +änderungen in 37b:velocity-kontrolle implementiert, kann aber nicht + geladen werden +änderungen in 36: frameskipping berichtigt (hängt von bufsize ab) +änderungen in 35c:auto-connect für midi-in funktioniert + diverse bugs behoben +änderungen in 35b:debugging-outputs entfernt oder auf output_verbose + umgestellt +änderungen in 35: osc-sync funktioniert; seltener segfault-bug + behoben. xrun-panic funktioniert +änderungen in 34: phase auf hohen wert geinitet, +WAVE_RES entfällt + es wurde berechnet, dass mit diesen einstellungen + oscillator.phase in 44 Jahren überlaufen wird. +änderungen in 33: filter-settings werden aus konfig geladen. per + controller setzen muss noch getestet werden + filter-update-frames kann per CLI gesetzt werden. + bug in parameter_t::operator< gefixt +änderungen in 32e:größtenteils wie 33 +änderungen in 32d:Note-ctor geändert; er erhält nun nur noch ein + program_t& statt den einzelnen settings-pointern +änderungen in 32c:tiefpassfilter funktioniert, kann aber noch + nicht geladen oder per cont. gesetzt werden. +änderungen in 31: sample-and-hold-generator geschrieben, kann aber + nirgends angewendet werden! +änderungen in 30d:custom-waves können nun via program-datei gesetzt + werden; cw verbessert +änderungen in 30b:custom-waves können geladen werden. deren samp_rate + ist nun in fixed_t angegeben, um ungenauigkeiten + wegen sr=sr/gegebene_freq zu beheben (detuned) +änderungen in 30: custom-waves können benutzt, aber nicht geladen + werden +änderungen in 29k:fehler nun über eigene funktionen ausgegeben. + diese funktionen können entscheiden, ob sie + das programm beenden (fatal-warnings etc.) oder + den hinweis ignorieren (quiet) +änderungen in 29j:fehlerbehandlung fortgesetzt: einheitliches + schema +änderungen in 29i:fehlerbehandlung begonnen: nicht-fatale fehler + werden ausgegeben, aber ignoriert +änderungen in 29h:CLI verwendet nun floats statt ints + zusätzliche throw-anweisungen +änderungen in 29g:space-sicher gemacht, frameskip=0 kann per CLI + gesetzt werden +änderungen in 29f:konfig-dateien-parsen begonnen +änderungen in 29e:clean_int., vib- und trem-freq. per CLI einstellbar +änderungen in 29d:last_cleanup durch next_cleanup ersetzt +änderungen in 29c:CLI begonnen; einlesen aller programme eines + verzeichnisses und einzelnes einlesen sollte + funktionieren. wird keine passende programm- + definition gefunden, wird ein sinus verwendet + + STABILE VERSION: 28: FM-Synthese mit einstellungen und controllern + möglich. vibrato, tremolo, portamento, reattack, festsetzbare + stimmenanzahl. "abgewürgte" noten können ausgeblendet werden. + channel-modulation muss manuell definiert werden. pitchbend und + controllerreset, frameskip, stereo, KSR, KSL funktionieren. + +änderungen in 28: knistern bei frameskip behoben +änderungen in 27g:noch mehr bugfixes +änderungen in 27f:einige bugfixes +änderungen in 27e:bei set_note wird jetzt geprüft, ob das program + gewechselt wurde. wenn ja: neue note anlegen +änderungen in 27d:reattack im monomode nurnoch wenn always_reattack=true +änderungen in 27c:tremolo/vibrato an samplingrate angepasst +änderungen in 27: samp_rate von jack erhalten +änderungen in 26: stimmenlimit implementiert, aber ungetestet! + bei verwerfen einer stimme kann sie schnell weg- + gefadet werden. auch das ist ungetestet! +änderungen in 25: stereo wurde implementiert, aber nicht getestet! +änderungen in 24: frameskip funktioniert +änderungen in 23c:KSR und KSL werden eingelesen +änderungen in 23b:KSR funktioniert +änderungen in 23: KSL funktioniert +änderungen in 22b:controllerreset hinzugefügt +änderungen in 22: pitchbend hinzugefügt +änderungen in 21: memcpy nur noch bei trivialen typen (int), sonst + copy. osc_t hat operator=. folge: deep-copy funzt +änderungen in 20: memory-leaks (bei einem simplen 5-sec-testlauf + stolze 8kb!) entfernt. sollte jetzt leakfrei sein. +änderungen in 19: reattack und portamento funktionieren +änderungen in 18: code aufgeräumt: t entfernt, TO DOs entfernt +änderungen in 17: envelopes können werte ändern +änderungen in 16: controllerdefaults werden aus cfg gelesen + und in program_t gepackt +änderungen in 15: jeder channel hat einen port +bugfix in 14d:program_t hat nun einen deep-copy-=-operator +änderungen in 14c:geänderte parameter werden gesichert und an + neue noten weitergegeben +änderungen in 13: controller für bereits spielende noten funktioniert. + fehlt noch das der-nächsten-note-mitgeben +änderungen in 12: programm-presets fertig, funktioniert. + außerdem: bug beim parser ausgebessert +änderungen in 11: parsen und programme angefangen +änderungen in 10: wie 09, aber mit (unbenutzter) Parser-klasse +änderungen in 09: notes besitzen nun eigenen framecounter +änderungen in 08: vibratoeffekt hinzugefügt +änderungen in 07: tremoloeffekt hinzugefügt +änderungen zu 06_opt3: alles arbeitet mit fixed_t (bitshifting) +änderungen zu 06_opt2: envelope-generator arbeitet mit fixed_t, aber jetzt + mit bitshifting statt normalen divisionen +änderungen zu 06_optimized: envelope-generator arbeitet mit fixed_t +änderungen zu 06: floats statt doubles +stand in 06: FM-synthese (und theoretisch AM) funktioniert, aber langsam + MIDI via jack und sound-out via jack funktioniert. + diff --git a/synth/Makefile b/synth/Makefile new file mode 100644 index 0000000..3870262 --- /dev/null +++ b/synth/Makefile @@ -0,0 +1,33 @@ +CXX=g++ +CXXFLAGS=-Wall -g +LDFLAGS=-lm `pkg-config --cflags --libs jack` + +OBJ=channel.o cli.o defines.o envelope.o filter.o globals.o jack.o load.o main.o note.o parser.o programs.o readwave.o util.o +BIN=synth + +DEPENDFILE = .depend + + +SRC = $(OBJ:%.o=%.cpp) + +all: $(BIN) + + +$(BIN): $(OBJ) + $(CXX) $(CFLAGS) -o synth $(OBJ) $(LDFLAGS) + + +dep: $(SRC) + $(CC) -MM $(SRC) > $(DEPENDFILE) + +-include $(DEPENDFILE) + + +%.o: %.cpp + $(CXX) $(CXXFLAGS) -g -c $< + +.PHONY: clean + +clean: + rm -f $(OBJ) $(BIN) + diff --git a/synth/OPTIMIZATIONS b/synth/OPTIMIZATIONS new file mode 100644 index 0000000..2dd003f --- /dev/null +++ b/synth/OPTIMIZATIONS @@ -0,0 +1,20 @@ +Sinnlose Optimierungen + o if(foo.fm_strength!=0) ...: kein effekt, höchstens leichter anstieg! + + Mögliche Optimierungen + o 10% filter ganz auf fixed_t umstellen? + o 5% envelope::get_level nur alle n frames arbeiten lassen, sonst cachen? + o 2% bei LFOs: bei jedem LFO-update die werte für env-max, freqfactor + und filter-offset aus orig berechnen + o 2% beim filter: evtl nur mit floats statt mit doubles rechnen? + o <2% in note::get_sample u.a.: pitch-bending effizienter lösen? + x 0% beim channel::get_sample: pro note immer mehrere samples auf + einmal holen (iterator braucht recht viel leistung) + wird von g++ automatisch wegoptimiert -> ok + + Mögliche Bugs und ihre Lösung: + o frequenz wird nicht genau eingehalten: phase um + foo*WAVE_RES erhöhen, entsprechend wave[][bar] ändern. + ABER: im testfall um bis zu 15% langsamer + + diff --git a/synth/README.developer b/synth/README.developer new file mode 100644 index 0000000..c2ee3a2 --- /dev/null +++ b/synth/README.developer @@ -0,0 +1,28 @@ +Wenn neue Instrumentenparameter definiert werden: + o bei parser::parse : case 1: + o bei parameter_enum + o param_t:: und Note::set_param + o param_to_enum + o param_needs_index + anpassen. + + +regeln: TODO für todos. DEBUG für debugging-outputs und -dinge. + IMPLEMENTME für sachen, die in ferner zukunft gemacht werden sollten + FINDMICH nur für marker, die unmittelbar gebraucht werden. + +Abweichungen von General MIDI: + controller 3 steuert always_reattack im polymode + controller 1 muss von der config definiert werden, sonst wird er ignoriert + controller 119 steuert quick-release-zeit im falle eines voice-limits. + 0 bedeutet: sofort abschalten + + +oscillator.phase will overflow in t seconds, with t being: +t=fixed_t.max_value / (max_possible_freq << SCALE) +with fixed_t being a uint64, max_possible_freq being 12544 Hz, +the highest freq possible with MIDI and SCALE being 20, +t = 1402438300 sec = 44,47 years. +--> phase will never overflow + + diff --git a/synth/TODO b/synth/TODO new file mode 100644 index 0000000..d507df0 --- /dev/null +++ b/synth/TODO @@ -0,0 +1,32 @@ +TODO für den synth + o notes compilieren und als .so-datei laden + + o RAM aufräumen? + + o jedes programm eigene LFOs? + o andere wellenformen bei LFOs? + o mehr wellen für wave[] + + o parser: sehr redundante funktionen zusammenführen + o parser: direkt in result schreiben? + + o attack und release ggf. auf niedrigen wert (<=0.01) initen, um + knackser zu vermeiden? + + o chorus, reverb etc. + + o konnte-nicht-verbinden-warnung weniger schlimm machen + + o max_pitchbend per controller setzen? + o nur auf bestimmte channels reagieren + + o diverse pedale (soft, sostenuto, halte, legato (?)) + + (o)programs on-the-fly ändern (n_osc ändern) + (o)lfo-maxima getrennt regeln. nää + (o)bei filter-envelopes: ksr/ksl? nää. + (o)resonanz-tremolo bei tiefpass? nää. + + +TODO fürs CLI + x ... diff --git a/synth/TODO.done b/synth/TODO.done new file mode 100644 index 0000000..0cd6d16 --- /dev/null +++ b/synth/TODO.done @@ -0,0 +1,71 @@ +TODO für den synth + x knistern bei aktivem frameskip + x tremolo und vibrato: phase nurnoch ++ + x rettack im monomode abschalten (sondern einfach nur freq ändern) + x bei freqänderung: aufpassen, ob nicht das programm auch geändert + wurde. wenn ja: delete && new + x note-limit + x stereo: pan/balance + x envelopes on-the-fly ändern + x programs laden + x beim note-limit: statt abzuschneiden ausblenden + x einstellmöglichkeiten via MIDI-controller + x grund-controller bearbeiten + x ein port pro channel + x controllern einen defaultwert pro programm mitgeben + x monophoner modus + x reattack + x alwaysreattack setzen können + x portamento testen + x memcheck-clean! + x memcpy durch copy() plus operator= ersetzen? + x pitch-bend + x controller-reset + x key scale level, key scale rate + x akkurates note-on + x frameskip + x globale config-datei, oder alle programs in einem verz. einlesen etc. + x auf dateifehler reagieren! + x sampler-"oscs", d.h. laden von wav-dateien, die sich sonst + wie oscs verhalten (fm möglich usw.) + x analoge synthese auf den output jeder note anwenden + x tiefpass via def.datei setzen: trem, env, res + x tiefpass via controller setzen + x osc-sync + x bei genügend xruns noten töten + x filter knackst + x bei self-mod mit faktor 1 (auch ohne filter): segfault + x bei starken vibratos: segfault, weil fm zu extrem wird + x bei bug.prog: auch ohne filter: knacksen bei den meisten noten (z.B. C) + x per velocity statt lautstärke andere params steuern + x frameskip so implementieren, dass bufsize irrelevant ist + x lfo_update_frames einstellbar machen + sollte durch frameskip dividiert werden + x tremolo- und vibrato-arrays sind mit mehreren MB zu groß! + ein wert pro sample ist unnötig. könnte auch rechenzeit in + calc_foo sparen, da seltener aufgerufen + x sample-and-hold -> fm_strength, -> freq, -> VCF + * bei osc-envelopes ggf. auch nur alle n frames neu setzen? [verschoben] + * filter optimieren? (arbeiten momentan mit floats) [verschoben] + verstehen, optimieren und dann profilen + x stimmt die stereo-implementierung? [ja] + + +TODO fürs CLI + x filter_update_frames, lfo_update_frames in config, in sec (auch im CLI) + x max_port_time einstellen + x manuelle program -> datei - mappings + x automatische mappings ( xxxIGNORIERT.prg, xxx ist die programmnummer), + alle dateien eines verzeichnisses einlesen. bei nichtexistenz + auf normalen sinus zurückfallen + x konfigdateien lesen (inhalt wie CLI-optionen) + x vibrato- und tremolo-frequenzen einstellen + x cleanup-intervall setzen + x space-sicher machen + x fehlerbehandlung: + syntaxfehler sollten übergangen werden, sofern möglich + in parser: throw!, sonst: meckern und nächste zeile parsen + x automatisch an alle midi-outs hängen + x interface div-by-zero-sicher machen + + diff --git a/synth/channel.cpp b/synth/channel.cpp new file mode 100644 index 0000000..35ee375 --- /dev/null +++ b/synth/channel.cpp @@ -0,0 +1,338 @@ +#include "channel.h" + +#include "math.h" +#include "globals.h" + +Channel::Channel() +{ + volume=ONE; + set_program(0); + curr_prg.controller[NO_CONT]=1; + quick_release=0; + always_reattack=false; + portamento_frames2=portamento_frames=0; + do_portamento=false; + pitchbend=ONE; + n_voices=0; + + max_pitchbend=1.0; + + set_balance(64); +} + +Channel::~Channel() +{ + panic(); //deletes all notes and empties notes-list +} + +void Channel::cleanup() +{ + list<Note*>::iterator it; + for (it=notes.begin(); it!=notes.end(); it++) + if ((*it)->still_active()==false) + { + delete *it; + it=notes.erase(it); + } +} + +fixed_t Channel::get_sample() +{ + fixed_t sum=0; + + for (list<Note*>::iterator it=notes.begin(); it!=notes.end(); it++) + sum+=(*it)->get_sample(); + + return sum*volume >>SCALE; +} + +void Channel::event(uint8_t a, uint8_t b, uint8_t c) +{ + switch(a & 0xF0) + { + case 0x80: note_off(b); break; + case 0x90: note_on(b,c); break; + case 0xA0: break; //IMPLEMENTME: polyphonic aftertouch (note, dynamic) + case 0xB0: set_controller(b,c); break; + case 0xC0: set_program(b); break; + case 0xD0: break; //IMPLEMENTME: monotonic aftertouch (dynamic) + case 0xE0: set_pitch_bend( ( (((b&0x7F) + ((c&0x7F)<<7)) - 8192) / 8192.0 ) * max_pitchbend ); break; + case 0xF0: break; //own controls/sysex (to be implemented) IMPLEMENTME + default: output_verbose("NOTE: got unknown command "+ IntToStrHex(a&0xF0) +", ignoring it\n"); + ; + } +} + +void Channel::note_off(int note) +{ + note_on(note,0); +} + +void Channel::note_on(int note, int vel) +{ + list<Note*>::iterator it; + if (vel>0) //note on + { + if ( (n_voices==1) && (!notes.empty()) ) + { + //no need to create a new note; reuse the existing + Note *n; //i'm lazy + n= *(notes.begin()); + + if (n->get_program() != program) + { + //if the program has changed, kill the previous note and + //create a new one + delete n; + notes.clear(); + + notes.push_back( new Note(note,(float)vel/128.0, + curr_prg, + portamento_frames, + pitchbend, + program) ); + + } + else //program did not change + { + //if not still active, don't do portamento + n->set_note(note,n->still_active()); + n->set_vel((float)vel/128.0); + if (always_reattack || !n->still_active()) n->reattack(); + //no need to push back. would become #1 instead of #1 + } + } + else + { + bool neednewnote=true; + if (always_reattack) + { + for (it=notes.begin(); it!=notes.end(); it++) + if ( ((*it)->get_note()==note) && ((*it)->get_program()==program) ) + { + neednewnote=false; + (*it)->reattack(); + (*it)->set_vel((float)vel/128.0); + notes.push_back(*it); //reorder notes + notes.erase(it); + break; + } + } + if (neednewnote) + notes.push_back( new Note(note,(float)vel/128.0, + curr_prg, + portamento_frames, + pitchbend, + program) ); + apply_voice_limit(); + } + } + else //note off + { + for (it=notes.begin(); it!=notes.end(); it++) + if ((*it)->get_note()==note) + (*it)->release(); + } + +} + +void Channel::set_n_voices(int val) +{ + n_voices=val; + + if ((n_voices<=0) || (n_voices>=128)) + n_voices=0; //unlimited + + apply_voice_limit(); +} + +void Channel::apply_voice_limit() +{ + if (n_voices) //is a limit defined? + { + int diff=notes.size()-n_voices; + if (diff>0) + { + list<Note*>::iterator it=notes.begin(); + + if (quick_release) + for (int i=0;i<diff;i++) + { + (*it)->release_quickly(quick_release); + it++; + } + else + for (int i=0;i<diff;i++) + { + delete (*it); + it=notes.erase(it); + } + } + } +} + + +void Channel::set_controller(int con,int val) +{ + switch (con) + { + case 3: always_reattack=(val>=64); + case 5: set_portamento_time(val); break; + case 7: set_volume(val); break; + case 8: set_balance(val); break; + case 65: set_portamento(val); break; + case 119: set_quick_release(val); + case 120: panic(); break; + case 121: reset_controllers(); break; + case 123: release_all(); break; + case 126: set_n_voices(val); break; + case 127: set_n_voices(999); break; + default: set_user_controller(con,val); break; + } +} + +void Channel::set_user_controller(int con, int val) +{ + curr_prg.controller[con]=val; + for (set<parameter_t>::iterator it=curr_prg.controller_affects[con].begin(); it!=curr_prg.controller_affects[con].end(); it++) + recalc_param(*it,curr_prg); +} + +void Channel::recalc_param(const parameter_t &par, program_t &prg) +{ + fixed_t val=0; + + list<term_t> *l; + l=&(prg.formula[par]); + + for (list<term_t>::iterator it=l->begin(); it!=l->end(); it++) + val+=curr_prg.controller[it->c]*it->f; + + if (val<0) val=0; + + // now we have the final value of the formula in units of fixed_t + // in the range 0..+infinity + + switch(par.par) + { + case SUSTAIN: + case FILTER_SUSTAIN: if (val>ONE) val=ONE; break; + + case TREM_LFO: + case VIB_LFO: + case FILTER_TREM_LFO: val=val>>SCALE; if (val>=N_LFOS+1) val=N_LFOS+1 -1; break; + + case TREMOLO: + case VIBRATO: + case FILTER_TREMOLO: val=val>>SCALE; if (val>=N_LFO_LEVELS) val=N_LFO_LEVELS-1; break; + + case WAVEFORM: val=val>>SCALE; if (val>=N_WAVEFORMS) val=N_WAVEFORMS-1; break; + + case FILTER_RESONANCE: if (val>ONE) val=ONE; break; + + default: break; + } + + // now we have the value clipped to the valid range. for stuff + // expecting real numbers, it's in units of fixed_t. for booleans + // it's zero or nonzero. for stuff expecting integers, like lfo, + // waveform etc it's in int (i.e., val/ONE is very small, while + // val is what we want) + + for (list<Note*>::iterator it=notes.begin(); it!=notes.end(); it++) + (*it)->set_param(par, val); + + curr_prg.set_param(par, val); +} + +void Channel::reset_controllers() +{ + program_t *orig=&program_settings[program]; + + for (int i=0;i<128;i++) + set_user_controller(i,orig->controller[i]); +} + +void Channel::set_quick_release(int val) +//ranges from zero to one second. +{ + quick_release=samp_rate*val/128; +} + +void Channel::set_volume(int val) +{ + volume=val*ONE/128; +} + +void Channel::set_balance(int val) +{ +#ifdef STEREO + balR=val/64.0; + balL=(128-val)/64.0; +#endif +} + +void Channel::set_portamento(int val) +{ + if (val>=64) + { + do_portamento=true; + set_real_portamento_frames(); + } + else + { + do_portamento=false; + set_real_portamento_frames(); + } +} + +void Channel::set_portamento_time(int val) +{ + portamento_frames2=samp_rate*val*max_port_time_sec/128; + if (do_portamento) + set_real_portamento_frames(); +} + +void Channel::set_real_portamento_frames() +{ + if (do_portamento) + portamento_frames=portamento_frames2; + else + portamento_frames=0; + + list<Note*>::iterator it; + for (it=notes.begin(); it!=notes.end(); it++) + (*it)->set_portamento_frames(portamento_frames); +} + +void Channel::panic() +{ + list<Note*>::iterator it; + for (it=notes.begin(); it!=notes.end();) + { + delete *it; + it=notes.erase(it); + } +} + +void Channel::release_all() +{ + list<Note*>::iterator it; + for (it=notes.begin(); it!=notes.end(); it++) + (*it)->release(); +} + +void Channel::set_program(int prog) +{ + program=prog; + curr_prg=program_settings[program]; +} + +void Channel::set_pitch_bend(float val) +{ + pitchbend=pow(2.0,val/12.0)*ONE; + + list<Note*>::iterator it; + for (it=notes.begin(); it!=notes.end(); it++) + (*it)->set_pitchbend(pitchbend); +} diff --git a/synth/channel.h b/synth/channel.h new file mode 100644 index 0000000..ead3a67 --- /dev/null +++ b/synth/channel.h @@ -0,0 +1,63 @@ +#ifndef __CHANNEL_H__ +#define __CHANNEL_H__ + + +#include <stdint.h> +#include <list> + +#include "fixed.h" +#include "programs.h" +#include "note.h" +#include "defines.h" +#include "util.h" + + +class Channel +{ + public: + Channel(); + ~Channel(); + fixed_t get_sample(); + void event(uint8_t a, uint8_t b, uint8_t c); + void set_controller(int con,int val); + void set_program(int prog); + void set_pitch_bend(float val); + void note_on(int note, int vel); + void note_off(int note); + void cleanup(); + void release_all(); + void panic(); + void set_real_portamento_frames(); + void set_portamento_time(int val); + void set_portamento(int val); + void set_volume(int val); + void set_balance(int val); + void set_n_voices(int val); + void set_quick_release(int val); + void reset_controllers(); + + float balL, balR; + private: + void recalc_param(const parameter_t &par, program_t &prg); + void set_user_controller(int con, int val); + void apply_voice_limit(); + + fixed_t volume; + fixed_t portamento_frames, portamento_frames2; + int program; + program_t curr_prg; + + fixed_t pitchbend; + float max_pitchbend; + + std::list<Note*> notes; + + bool always_reattack; + bool do_portamento; + + int n_voices; + jack_nframes_t quick_release; + +}; + +#endif diff --git a/synth/cli.cpp b/synth/cli.cpp new file mode 100644 index 0000000..376edc3 --- /dev/null +++ b/synth/cli.cpp @@ -0,0 +1,174 @@ +#include <iostream> +#include <cstdlib> +#include <getopt.h> + +#include "util.h" +#include "globals.h" +#include "load.h" + +using namespace std; + +void show_help() +{ + cout << "TODO: help text" << endl; +} + +void show_version() +{ + cout << "TODO: softsynth version foo" << endl; +} + +void parse_args(int argc, char** argv) +{ + static const struct option long_options[]={ + {"help", no_argument, 0, 'h'}, + {"version", no_argument, 0, 'V'}, + {"verbose", no_argument, 0, 'v'}, + {"quiet", no_argument, 0, 'q'}, + {"fatal-warnings", no_argument, 0, 'F'}, + {"frameskip", required_argument, 0, 'f'}, + {"xruns", required_argument, 0, 'x'}, + {"dir", required_argument, 0, 'd'}, + {"directory", required_argument, 0, 'd'}, + {"program", required_argument, 0, 'p'}, + {"cleanup-interval", required_argument, 0, 'i'}, + {"lfo0-freq", required_argument, 0, 400}, //FINDLFO + {"lfo1-freq", required_argument, 0, 401}, + {"lfo2-freq", required_argument, 0, 402}, + {"snh-freq", required_argument, 0, 304}, + {"sample-and-hold-freq", required_argument, 0, 304}, + {"conf", required_argument, 0, 'c'}, + {"config", required_argument, 0, 'c'}, + {"max-port", required_argument, 0, 303}, + {"max-port-time", required_argument, 0, 303}, + {"max-portamento-time", required_argument, 0, 303}, + {"filter-update-freq", required_argument, 0, 305}, + {"lfo-update-freq", required_argument, 0, 306}, + {"no-connect-audio-out", no_argument, 0, 'a'}, + {"no-connect-audio", no_argument, 0, 'a'}, + {"dont-connect-audio-out", no_argument, 0, 'a'}, + {"dont-connect-audio", no_argument, 0, 'a'}, + {"no-connect-midi-in", no_argument, 0, 'm'}, + {"no-connect-midi", no_argument, 0, 'm'}, + {"dont-connect-midi-in", no_argument, 0, 'm'}, + {"dont-connect-midi", no_argument, 0, 'm'}, + 0 }; + + while (optind<argc) + { + int index=-1; + int result=getopt_long(argc,argv,"hVf:d:p:i:c:x:vqFam", long_options, &index); + if (result==-1) break; + + switch (result) + { + case 'h': show_help(); exit(0); break; + case 'V': show_version(); exit(0); break; + case 'v': verbose=true; quiet=false; break; + case 'q': quiet=true; verbose=false; break; + case 'F': fatal_warnings=true; break; + case 'c': read_config(optarg); break; + case 'a': connect_audio=false; break; + case 'm': connect_midi=false; break; + case 'f': if (optarg) + { + frameskip=atoi(optarg); + if (frameskip<=0) frameskip=0; + } + break; + case 'x': if (optarg) + { + string stropt=optarg; + size_t pos=stropt.find(':'); + if (pos==string::npos) + output_warning("expected 'n_xruns:time' in --xruns option, found no ':'"); + + xrun_n=atoi(stropt.substr(0,pos).c_str()); + xrun_time=atof(stropt.substr(pos+1).c_str()); + + if (xrun_n<=0) xrun_n=0; + if (xrun_time<=0) xrun_time=0; + } + break; + case 'd': add_dir(optarg); break; + case 'p': { + string str=optarg; + size_t pos=str.find_first_of(":=,"); + if (pos!=string::npos) + { + string numstr=str.substr(0,pos); + if (isnum(numstr)) + { + int num=atoi(numstr.c_str()); + + if ((num>=0) && (num<=127)) + { + if (programfile[num]=="") + programfile[num]=str.substr(pos+1); + else + output_note("NOTE: program #"+IntToStr(num)+" has already been defined. ignoring it..."); + } + else + { + output_warning("WARNING: number out of range (0..127) in --program option.\n ignoring the option..."); + } + } + else + { + output_warning("WARNING: not a number in --program option. ignoring the option..."); + } + } + else + { + output_warning("WARNING: missing number in --program option. ignoring the option..."); + } + } + break; + case 'i': if (isfloat(optarg)) + cleanup_interval_sec=atof(optarg); + else + output_warning("WARNING: not a number in --interval option. ignoring it..."); + break; + case 304: if (isfloat(optarg)) + snh_freq_hz=atof(optarg); + else + output_warning("WARNING: not a number in --sample-and-hold-freq option. ignoring it..."); + break; + case 400: //FINDLFO + case 401: + case 402: + if (isfloat(optarg)) + lfo_freq_hz[result-400]=atof(optarg); + else + output_warning("WARNING: not a number in --lfoN-freq option. ignoring it..."); + break; + case 303: if (isfloat(optarg)) + max_port_time_sec=atof(optarg); + else + output_warning("WARNING: not a number in --max-portamento-time option. ignoring it..."); + break; + + case 305: if (isfloat(optarg)) + if (atoi(optarg)<=0) + output_warning("WARNING: filter-update-freq must be positive. ignoring it..."); + else + filter_update_freq_hz=atof(optarg); + else + output_warning("WARNING: not a number in --filter-update-freq option. ignoring it..."); + break; + case 306: if (isfloat(optarg)) + if (atoi(optarg)<=0) + output_warning("WARNING: lfo-update-freq must be positive. ignoring it..."); + else + lfo_update_freq_hz=atof(optarg); + else + output_warning("WARNING: not a number in --lfo-update-freq option. ignoring it..."); + break; + + default: cout << "ERROR: invalid command line options. try the --help switch" << endl; + exit(1); + } + } + + +} diff --git a/synth/cli.h b/synth/cli.h new file mode 100644 index 0000000..d1f7a2b --- /dev/null +++ b/synth/cli.h @@ -0,0 +1,6 @@ +#ifndef __CLI_H__ +#define __CLI_H__ + +void parse_args(int argc, char** argv); + +#endif diff --git a/synth/defines.cpp b/synth/defines.cpp new file mode 100644 index 0000000..5b94954 --- /dev/null +++ b/synth/defines.cpp @@ -0,0 +1,3 @@ +#include "defines.h" + +float LFO_FREQ_HZ[]=__LFO_FREQ_HZ; diff --git a/synth/defines.h b/synth/defines.h new file mode 100644 index 0000000..4a2e929 --- /dev/null +++ b/synth/defines.h @@ -0,0 +1,62 @@ +#ifndef __DEFINES_H__ +#define __DEFINES_H__ + + +#define XRUN_TIME 2.0 +#define XRUN_N 8 + +#define CLEANUP_INTERVAL_SEC 1.0 + +#define MAX_PORTAMENTO_TIME 2.0 + +#define FILTER_UPDATE_FREQ_HZ 250 +#define LFO_UPDATE_FREQ_HZ 500 + +//when changing this, also change code marked with FINDLFO! +#define N_LFOS 3 +#define N_LFO_LEVELS 1024 +#define LFO_MAX 1 +extern float LFO_FREQ_HZ[]; +#define __LFO_FREQ_HZ {7.0, 5.0, 1.0} + +#if N_LFO_LEVELS <= 1 + #error "N_LFO_LEVELS must be greater than one!" +#endif + +#define SNH_FREQ_HZ 10 +#define SNH_LFO N_LFOS + + +//init the oscillator phases to wave_res * PHASE_INIT +//negative values are not allowed, zero will cause the program +//to segfault if phase modulation is done, higher values will make +//the probability for a segfault smaller (i.e., zero) +//only decrease if you know what you're doing! +#define PHASE_INIT 100 + + +#define MIDI_IN_NAME "midi_in" +#define OUT_NAME "output" + +#define N_CHANNELS 16 + + +//#define STEREO +#define FRAMESKIP + +#define VOL_FACTOR (1/20.0) + + + +#define PI 3.141592654 + + + + + +#define WAVE_RES 44100 +#define N_WAVEFORMS 5 + +#define NO_CONT 128 + +#endif diff --git a/synth/envelope.cpp b/synth/envelope.cpp new file mode 100644 index 0000000..481b78f --- /dev/null +++ b/synth/envelope.cpp @@ -0,0 +1,168 @@ +#include "envelope.h" + +Envelope::Envelope(jack_nframes_t a, jack_nframes_t d, fixed_t s, jack_nframes_t r, bool h) +{ + level=0; + t=0; + state=ATTACK; + max=ONE; + + set_ratefactor(1.0); + + set_attack(a); + set_decay(d); + set_sustain(s); + set_release(r); + set_hold(h); +} + +Envelope::Envelope(env_settings_t s) +{ + level=0; + t=0; + state=ATTACK; + max=ONE; + + set_ratefactor(1.0); + + set_attack(s.attack); + set_decay(s.decay); + set_sustain(s.sustain); + set_release(s.release); + set_hold(s.hold); +} + +void Envelope::set_ratefactor(double factor) +{ + ratefactor=ONE*factor; + + set_attack(attack_orig); + set_decay(decay_orig); + set_release(release_orig); +} + +void Envelope::set_attack(jack_nframes_t a) +{ + attack_orig=a; + attack=a*ratefactor >>SCALE; + + if (state==ATTACK) + t=attack*level >>SCALE; +} + +void Envelope::set_decay(jack_nframes_t d) +{ + decay_orig=d; + decay=d*ratefactor >>SCALE; + + if ((state==DECAY) && (sustain!=ONE)) + if (sustain<ONE) //to avoid a div. by zero + t=decay*(ONE-level)/(ONE-sustain); +} + +void Envelope::set_sustain(fixed_t s) +{ + sustain=s; + sustain_orig=s; +} + +void Envelope::set_release(jack_nframes_t r) +{ + release_orig=r; + release=r*ratefactor >>SCALE; + + if (state==RELEASE) + if (sustain>0) //to avoid a div. by zero + t=release*(sustain-level)/sustain; +} + + +void Envelope::set_hold(bool h) +{ + hold=h; + if ((h==false) && (state==HOLD)) + { + t=0; + state=RELEASE; + } +} + +void Envelope::reattack() +{ + state=ATTACK; + t=attack*level >>SCALE; + sustain=sustain_orig; +} + +void Envelope::release_key() +{ + if ((state!=RELEASE) && (state!=DONE)) + { + t=0; + state=RELEASE; + sustain=level; + } +} + +bool Envelope::still_active() +{ + return (state!=DONE); +} + +fixed_t Envelope::get_level() //must be called each frame +{ + switch (state) + { + case ATTACK: + if (t>=attack) + { + level=max; + state=DECAY; + t=0; + } + else //will only happen, if t < attack. so attack will + { //always be greater than zero -> no div. by zero + level=max * t / attack ; + } + break; + + case DECAY: + if (t>=decay) + { + level=max*sustain >>SCALE; + if (hold) + state=HOLD; + else + state=RELEASE; + t=0; + } + else //will only happen, if t < decay. so decay will + { //always be greater than zero -> no div. by zero + level=(ONE - (ONE-sustain)*t/decay)*max >>SCALE; + } + break; + + case HOLD: + level=sustain*max >>SCALE; + break; + + case RELEASE: + if (t>=release) + { + level=0; + state=DONE; + } + else //will only happen, if t < release. so release will + { //always be greater than zero -> no div. by zero + level=(sustain - sustain * t/release)*max >>SCALE; + } + break; + + case DONE: + level=0; + break; + } + + t++; + return level; +} diff --git a/synth/envelope.h b/synth/envelope.h new file mode 100644 index 0000000..e0d3502 --- /dev/null +++ b/synth/envelope.h @@ -0,0 +1,65 @@ +#ifndef __ENVELOPE_H__ +#define __ENVELOPE_H__ + +#include <jack/jack.h> + +#include "programs.h" +#include "fixed.h" + + +class Envelope +{ + public: + Envelope(jack_nframes_t a, jack_nframes_t d, fixed_t s, jack_nframes_t r, bool h); + Envelope(env_settings_t s); + void release_key(); + void reattack(); + fixed_t get_level(); + bool still_active(); + void set_hold(bool h); + void set_attack(jack_nframes_t a); + void set_decay(jack_nframes_t d); + void set_sustain(fixed_t s); + void set_release(jack_nframes_t r); + void set_max(fixed_t m) + { + max=m; + if (max>ONE) max=ONE; + else if (max<0) max=0; + } + void set_ratefactor(double factor); + + bool get_hold() { return hold; } + jack_nframes_t get_attack() { return attack_orig; } + jack_nframes_t get_decay() { return decay_orig; } + fixed_t get_sustain() { return sustain; } + jack_nframes_t get_release() { return release_orig; } + + + private: + fixed_t max; + jack_nframes_t attack; + jack_nframes_t decay; + jack_nframes_t release; + jack_nframes_t attack_orig; + jack_nframes_t decay_orig; + jack_nframes_t release_orig; + jack_nframes_t rel_t; + fixed_t sustain; + fixed_t sustain_orig; + fixed_t level; + bool hold; + jack_nframes_t t; + fixed_t ratefactor; + + enum + { + ATTACK, + DECAY, + HOLD, + RELEASE, + DONE + } state; +}; + +#endif diff --git a/synth/filter.cpp b/synth/filter.cpp new file mode 100644 index 0000000..0ed7778 --- /dev/null +++ b/synth/filter.cpp @@ -0,0 +1,59 @@ +#include "math.h" + +#include "filter.h" +#include "defines.h" +#include "globals.h" + +LowPassFilter::LowPassFilter() +{ + rate=samp_rate; + nyquist=rate/2; + reset(); +} + +void LowPassFilter::reset() +{ + d1 = d2 = d3 = d4 = 0; +} + +void LowPassFilter::set_params(float fc, float res) +{ + // constrain cutoff +#define SAFE 0.99f // filter is unstable _AT_ PI + if (fc>(nyquist*SAFE)) + fc=nyquist*SAFE; + if (fc<10) + {fc = 10;/*d1=d2=d3=d4=0;*/} + float w = (fc/(float)rate); // cutoff freq [ 0 <= w <= 0.5 ] + + // find final coeff values for end of this buffer + double k, k2, bh; + double r = 2*(1-res); + if(r==0.0) r = 0.001; + k=tan(w*PI); + k2 = k*k; + bh = 1 + (r*k) + k2; + a0 = a2 = double(k2/bh); + a1 = a0 * 2; + b1 = double(2*(k2-1)/-bh); + b2 = double((1-(r*k)+k2)/-bh); +} + +void LowPassFilter::process_sample (fixed_t *smp) +{ + fixed_t x,y; + x = *smp; + + // first 2nd-order unit + y = ( a0*x ) + d1; + d1 = d2 + ( (a1)*x ) + ( (b1)*y ); + d2 = ( (a2)*x ) + ( (b2)*y ); + x=y; + // and the second + + y = ( a0*x ) + d3; + d3 = d4 + ( a1*x ) + ( b1*y ); + d4 = ( a2*x ) + ( b2*y ); + + *smp = y; +} diff --git a/synth/filter.h b/synth/filter.h new file mode 100644 index 0000000..10db5d7 --- /dev/null +++ b/synth/filter.h @@ -0,0 +1,27 @@ +#ifndef __FILTER_H__ +#define __FILTER_H__ + +#include "fixed.h" + +/** + * A 24 dB/octave resonant low-pass filter. + **/ +class LowPassFilter +{ +public: + LowPassFilter(); + + /** + * Reset the filter - clear anything in the delay units of the filter. + */ + void reset(); + void set_params(float fc, float res); + void process_sample(fixed_t* smp); +private: + float rate; + float nyquist; + double d1, d2, d3, d4; + double a0, a1, a2, b1, b2; +}; + +#endif diff --git a/synth/fixed.h b/synth/fixed.h new file mode 100644 index 0000000..41c1b78 --- /dev/null +++ b/synth/fixed.h @@ -0,0 +1,10 @@ +#ifndef __FIXED_H__ +#define __FIXED_H__ + + +#define SCALE 20 +#define ONE ((fixed_t)(1<<SCALE)) + +typedef long long int fixed_t; + +#endif diff --git a/synth/globals.cpp b/synth/globals.cpp new file mode 100644 index 0000000..5b256b3 --- /dev/null +++ b/synth/globals.cpp @@ -0,0 +1,60 @@ +#include "globals.h" + +fixed_t **lfo[N_LFOS]; +fixed_t *curr_lfo[N_LFOS+1]; + +fixed_t wave[N_WAVEFORMS][WAVE_RES]; + +fixed_t sample_and_hold[N_LFO_LEVELS]; + +int sample_and_hold_frames=0; +int lfo_res[N_LFOS]; +int lfo_phase[N_LFOS]; +float lfo_freq_hz[N_LFOS]; + +bool verbose=false; +bool fatal_warnings=false; +bool quiet=false; + +bool connect_audio=true, connect_midi=true; + + +float cleanup_interval_sec=0; +float snh_freq_hz=0; +float max_port_time_sec=0; + +float filter_update_freq_hz; +float lfo_update_freq_hz; + +int filter_update_frames; +int lfo_update_frames; + +float xrun_time=0; +int xrun_n=0; + +#ifndef FRAMESKIP + int samp_rate; +#else + int frameskip=-1; + int samp_rate; + + jack_default_audio_sample_t outtemp[N_CHANNELS]; + #ifdef STEREO + jack_default_audio_sample_t outtemp2[N_CHANNELS]; + #endif + + jack_nframes_t outtemp_nframes_left=0; +#endif + + +string programfile[128]; + + + +program_t *program_settings; + + +Channel *channel[N_CHANNELS]; + + +jack_nframes_t cleanup_interval; //in jack frames diff --git a/synth/globals.h b/synth/globals.h new file mode 100644 index 0000000..b7c9109 --- /dev/null +++ b/synth/globals.h @@ -0,0 +1,73 @@ +#ifndef __GLOBALS_H__ +#define __GLOBALS_H__ + +#include <jack/jack.h> + +#include <string> + +#include "programs.h" +#include "channel.h" + +using namespace std; + + +extern fixed_t **lfo[N_LFOS]; +extern fixed_t *curr_lfo[N_LFOS+1]; + +extern fixed_t wave[N_WAVEFORMS][WAVE_RES]; + +extern fixed_t sample_and_hold[N_LFO_LEVELS]; + +extern int sample_and_hold_frames; +extern int lfo_res[N_LFOS]; +extern int lfo_phase[N_LFOS]; +extern float lfo_freq_hz[N_LFOS]; + +extern bool verbose; +extern bool fatal_warnings; +extern bool quiet; + +extern bool connect_audio, connect_midi; + + +extern float cleanup_interval_sec; +extern float snh_freq_hz; +extern float max_port_time_sec; + +extern float filter_update_freq_hz; +extern float lfo_update_freq_hz; + +extern int filter_update_frames; +extern int lfo_update_frames; + +extern float xrun_time; +extern int xrun_n; + +#ifndef FRAMESKIP + extern int samp_rate; +#else + extern int frameskip; + extern int samp_rate; + + extern jack_default_audio_sample_t outtemp[N_CHANNELS]; + #ifdef STEREO + extern jack_default_audio_sample_t outtemp2[N_CHANNELS]; + #endif + + extern jack_nframes_t outtemp_nframes_left; +#endif + + +extern string programfile[128]; + + + +extern program_t *program_settings; + +extern Channel *channel[N_CHANNELS]; + + +extern jack_nframes_t cleanup_interval; //in jack frames + + +#endif diff --git a/synth/jack.cpp b/synth/jack.cpp new file mode 100644 index 0000000..093e9b5 --- /dev/null +++ b/synth/jack.cpp @@ -0,0 +1,467 @@ +#include <string> +#include <list> +#include <iostream> +#include <cstdlib> +#include <jack/jack.h> +#include <jack/midiport.h> + +#include "defines.h" +#include "globals.h" + +#include "jack.h" + +using namespace std; + +//#define DO_DEBUGGING_EVENTS + +jack_port_t *midi_in; +jack_port_t *out_port[N_CHANNELS]; +#ifdef STEREO +jack_port_t *out_port2[N_CHANNELS]; +#endif + +jack_client_t *jack_client = NULL; + + +void maybe_calc_stuff() //TODO woandershinschieben? lfo.cpp oder so? +{ + static int lfocnt=0; + static int snhcnt=0; + + if (lfocnt==0) + { + lfocnt=lfo_update_frames; + + for (int i=0;i<N_LFOS;i++) + { + lfo_phase[i]=(lfo_phase[i]+1)%lfo_res[i]; + curr_lfo[i]=lfo[i][lfo_phase[i]]; + } + } + + if (snhcnt==0) + { + snhcnt=sample_and_hold_frames; + + //temp ranges between -ONE and ONE + fixed_t temp = (float(rand())/(RAND_MAX/2) - 1.0) * ONE; + + for (int i=0;i<N_LFO_LEVELS;i++) + sample_and_hold[i]= temp*i/(N_LFO_LEVELS-1) + ONE; + + curr_lfo[SNH_LFO]=sample_and_hold; + // could be moved to some init function, but looks clearer and + // does not eat up the cpu too much ;) + } + + lfocnt--; + snhcnt--; +} + + + +//connect to jack, init some stuff and get information +void init_jack() +{ + jack_client = jack_client_open("flosoftsynth", JackNullOption, NULL); + if (jack_client == NULL) + throw string("Registering client failed"); + + if (jack_set_process_callback(jack_client, process_callback, 0)) + throw string("Registering callback failed"); + + if (jack_set_xrun_callback(jack_client, xrun_callback, 0)) + throw string("Registering xrun-callback failed"); + + midi_in = jack_port_register(jack_client, MIDI_IN_NAME, + JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0); + + if (midi_in == NULL) + throw string ("Registering MIDI IN failed"); + + for (int i=0;i<N_CHANNELS;i++) + { + #ifndef STEREO + out_port[i]=jack_port_register(jack_client, (OUT_NAME+IntToStr(i)).c_str(), + JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); + if (out_port[i]==NULL) + throw string ("Registering some output port failed"); + #else + out_port[i]=jack_port_register(jack_client, (OUT_NAME+IntToStr(i)+"L").c_str(), + JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); + out_port2[i]=jack_port_register(jack_client, (OUT_NAME+IntToStr(i)+"R").c_str(), + JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); + + if ((out_port[i]==NULL) || (out_port2[i]==NULL)) + throw string ("Registering some output port failed"); + #endif + } + + samp_rate=jack_get_sample_rate(jack_client); +} + + +//activate client and connect ports +void start_jack(bool connect_audio_out, bool connect_midi_in) +{ + const char **ports; + + if (jack_activate(jack_client)) + throw string("Activating client failed"); + + if (connect_audio_out) + { + if ((ports = jack_get_ports (jack_client, NULL, JACK_DEFAULT_AUDIO_TYPE, + JackPortIsPhysical|JackPortIsInput)) == NULL) + { + output_warning("WARNING: Could not find any physical playback ports. Leaving my ports\n" + " unconnected. You will not hear anything, but you can connect\n" + " them on your own. proceeding..."); + } + else + { + #ifndef STEREO + int i=0; + while(ports[i]!=NULL) + { + for (int j=0;j<N_CHANNELS;j++) + if (jack_connect (jack_client, jack_port_name (out_port[j]), ports[i])) + output_warning("WARNING: could not connect some output port. this may or may not result\n" + " in being unable to produce sound. you can still connect them\n" + " manually. proceeding..."); + i++; + } + #else + if (ports[1]==NULL) + { + output_note("NOTE: could not find two output ports. connecting to one, making everything\n" + " mono. this is not fatal, proceeding..."); + for (int j=0;j<N_CHANNELS;j++) + { + if (jack_connect (jack_client, jack_port_name (out_port[j]), ports[0])) + output_warning("WARNING: could not connect some output port. this may or may not result\n" + " in being unable to produce sound. you can still connect them\n" + " manually. this is not fatal, proceeding..."); + + if (jack_connect (jack_client, jack_port_name (out_port2[j]), ports[0])) + output_warning("WARNING: could not connect some output port. this may or may not result\n" + " in being unable to produce sound. you can still connect them\n" + " manually. this is not fatal, proceeding..."); + } + } + else + { + for (int j=0;j<N_CHANNELS;j++) + { + if (jack_connect (jack_client, jack_port_name (out_port[j]), ports[0])) + output_warning("WARNING: could not connect some output port. this may or may not result\n" + " in being unable to produce sound. you can still connect them\n" + " manually. proceeding..."); + + if (jack_connect (jack_client, jack_port_name (out_port2[j]), ports[1])) + output_warning("WARNING: could not connect some output port. this may or may not result\n" + " in being unable to produce sound. you can still connect them\n" + " manually. proceeding..."); + } + } + #endif + + free (ports); + } + } + + if (connect_midi_in) + { + if ((ports = jack_get_ports (jack_client, NULL, JACK_DEFAULT_MIDI_TYPE, + JackPortIsOutput)) == NULL) + { + output_warning("WARNING: Could not find any MIDI OUT ports. Leaving my MIDI IN port\n" + " unconnected. I cannot do anything unless you connect it to\n" + " some MIDI OUT port. proceeding..."); + } + else + { + int i=0; + while(ports[i]!=NULL) + { + if (jack_connect (jack_client, ports[i],jack_port_name(midi_in))) + output_warning("WARNING: could not connect some MIDI OUT to my MIDI IN. this may or may not\n" + " result in being unable to receive any notes. you can still connect\n" + " the port manually. proceeding..."); + i++; + } + free(ports); + } + } +} + +void exit_jack() +{ + jack_deactivate(jack_client); + jack_client_close(jack_client); + + jack_client=NULL; +} + +int xrun_callback(void *notused) +{ + static list<float> history; + + list<float>::iterator it; + + float now=float(jack_get_time())/1000000; + + cout << "got an XRUN! if this happens too often, consider reducing CPU usage, for\n example by setting a voice limit or by quitting other programs"<<endl<<endl; + + history.push_back(now); + + //erase all entries older than xrun_time + it=history.begin(); + while (it!=history.end()) + { + if (*it < now-xrun_time) + it=history.erase(it); + else + break; + } + + if (history.size() >= xrun_n) + { + cout << "PANIC -- TOO MANY XRUNs! killing all voices" << endl<<endl; + for (int i=0;i<N_CHANNELS;i++) + channel[i]->panic(); + + history.clear(); + } + + return 0; +} + +#define IGNORE_MIDI_OFFSET +int process_callback(jack_nframes_t nframes, void *notused) +{ + #ifdef DO_DEBUGGING_EVENTS + static jack_nframes_t tmp=0, tmp2=0; + #endif + + static jack_nframes_t next_cleanup=0; + + size_t curr_event=0, n_events, i, chan; + void *inport; + + jack_default_audio_sample_t *outbuf[N_CHANNELS]; + #ifdef STEREO + jack_default_audio_sample_t *outbuf2[N_CHANNELS]; + #endif + + jack_midi_event_t event; + jack_nframes_t lastframe; + lastframe=jack_last_frame_time(jack_client); + + if (nframes <= 0) { + output_note ("NOTE: Process callback called with nframes = 0; bug in JACK?"); + return 0; + } + + for (i=0;i<N_CHANNELS;i++) + { + outbuf[i]=(jack_default_audio_sample_t*) jack_port_get_buffer(out_port[i], nframes); + #ifdef STEREO + outbuf2[i]=(jack_default_audio_sample_t*) jack_port_get_buffer(out_port2[i], nframes); + #endif + + if ( (outbuf[i]==NULL) + #ifdef STEREO + || (outbuf2[i]==NULL) + #endif + ) + { + output_warning("WARNING: jack_port_get_buffer failed, cannot output anything."); + return 0; + } + } + + inport = jack_port_get_buffer(midi_in, nframes); + if (inport == NULL) + { + output_warning("WARNING: jack_port_get_buffer failed, cannot receive anything."); + return 0; + } + + n_events = jack_midi_get_event_count(inport /*, nframes */); + + //as long as there are some events left and getting one fails, get the next + while ((n_events) && (jack_midi_event_get(&event, inport, curr_event /*, nframes */))) + { + output_note("NOTE: lost a note :("); + n_events--; + curr_event++; + } + + if (lastframe>=next_cleanup) + { + next_cleanup=lastframe+cleanup_interval; + for (i=0;i<N_CHANNELS;i++) + channel[i]->cleanup(); + } +#ifdef DO_DEBUGGING_EVENTS + if (tmp==0) //DEBUG !!! + { + tmp=lastframe; + channel[0]->set_controller(5,10); + channel[0]->set_controller(65,127); + + channel[0]->event(0x90,80,64); + +// channel[0]->event(0x90,84,64); + } + else if (tmp2==0) + { + if (lastframe>tmp+44100*2) + { + tmp2=1; + cout << "BÄÄM" << endl; + channel[0]->event(0x90,84,64); + } + } + else if (tmp2==1) + { + if (lastframe>tmp+44100*4) + { + tmp2=2; + channel[0]->event(0x90,87,5); + channel[0]->set_controller(57, 127); + cout << "BÄÄM2" << endl; + } + } + else + { + if (lastframe>tmp+44100*10) + { + cout << "finished" << endl; + exit(0); + } + } +#endif + +#ifdef FRAMESKIP + if (outtemp_nframes_left) + { + jack_nframes_t real_nframes; + if (outtemp_nframes_left > nframes) + { + real_nframes=nframes; + outtemp_nframes_left-=nframes; + } + else + { + real_nframes=outtemp_nframes_left; + outtemp_nframes_left=0; + } + + for (i=0;i<real_nframes;i++) + { + for (int j=0;j<N_CHANNELS;j++) + { + outbuf[j][i]=outtemp[j]; + #ifdef STEREO + outbuf2[j][i]=outtemp2[j]; + #endif + } + } + } + else + i=0; + + //begin where the above loop has stopped, at 0 if the loop wasn't + //executed + int upperbound=nframes-frameskip+1; + if (upperbound<0) upperbound=0; + for (i=i;i<upperbound;i+=frameskip) +#else + for (i=0;i<nframes;i++) +#endif + { + while ((n_events) && (i>=event.time)) + { + output_verbose("processing event #"+IntToStr(curr_event)+" of "+IntToStr(n_events)+" events"); + if (event.size > 3) + { + output_verbose(" Ignoring MIDI message longer than three bytes, probably a SysEx."); + } + else + { + chan=event.buffer[0] & 0x0F; + output_verbose(" channel="+IntToStr(chan)+", data is "+IntToStrHex(event.buffer[0])+" "+IntToStrHex(event.buffer[1])+" "+IntToStrHex(event.buffer[2])); + + channel[chan]->event(event.buffer[0], event.buffer[1], event.buffer[2]); + } + + n_events--; + curr_event++; + + //as long as there are some events left and getting one fails, get the next + while ((n_events) && (jack_midi_event_get(&event, inport, curr_event /*, nframes */))) + { + output_note("NOTE: lost a note :("); + n_events--; + curr_event++; + } + } + + maybe_calc_stuff(); + + for (int j=0;j<N_CHANNELS;j++) + { + #ifndef STEREO + outbuf[j][i]=jack_default_audio_sample_t(channel[j]->get_sample())/ONE*VOL_FACTOR; + #else + jack_default_audio_sample_t sample=jack_default_audio_sample_t(channel[j]->get_sample())/ONE*VOL_FACTOR; + outbuf[j][i]=channel[j]->balL*sample; + outbuf2[j][i]=channel[j]->balR*sample; + #endif // if the above changes, (1) must also change + + #ifdef FRAMESKIP + for (size_t k=i+frameskip-1;k>i;k--) + { + outbuf[j][k]=outbuf[j][i]; + #ifdef STEREO + outbuf2[j][k]=outbuf2[j][i]; + #endif + } + #endif + } + } + +#ifdef FRAMESKIP + if (i!=nframes) // nicht aufgegangen? + { + for (int j=0;j<N_CHANNELS;j++) + { // (1) + #ifndef STEREO + outtemp[j]=jack_default_audio_sample_t(channel[j]->get_sample())/ONE*VOL_FACTOR; + #else + jack_default_audio_sample_t sample=jack_default_audio_sample_t(channel[j]->get_sample())/ONE*VOL_FACTOR; + outtemp[j]=channel[j]->balL*sample; + outtemp2[j]=channel[j]->balR*sample; + #endif + } + + outtemp_nframes_left=frameskip-nframes+i; + + for (i=i; i<nframes; i++) + { + for (int j=0;j<N_CHANNELS;j++) + { + outbuf[j][i]=outtemp[j]; + #ifdef STEREO + outbuf2[j][i]=outtemp2[j]; + #endif + } + } + } +#endif + return 0; +} + + diff --git a/synth/jack.h b/synth/jack.h new file mode 100644 index 0000000..c0b4baa --- /dev/null +++ b/synth/jack.h @@ -0,0 +1,12 @@ +#ifndef __JACK_H__FLO +#define __JACK_H__FLO + +#include <jack/jack.h> + +int process_callback(jack_nframes_t nframes, void *notused); +int xrun_callback(void *notused); +void init_jack(); +void start_jack(bool connect_audio_out=true, bool connect_midi_in=true); +void exit_jack(); + +#endif diff --git a/synth/load.cpp b/synth/load.cpp new file mode 100644 index 0000000..d88ec3e --- /dev/null +++ b/synth/load.cpp @@ -0,0 +1,191 @@ +#include <string> +#include <fstream> +#include <cstdlib> +#include <sys/types.h> +#include <dirent.h> + + +#include "util.h" +#include "globals.h" + + +using namespace std; + +void add_dir(string directory, bool complain=true) +{ + DIR *dir; + dir=opendir(directory.c_str()); + if (dir) + { + string n, snum; + int num; + dirent *entry=NULL; + + while ( (entry=readdir(dir)) != NULL ) + { + n=entry->d_name; + if (fileext(n)=="prog") + { + snum=n.substr(0,3); + if (isnum(snum)) + { + num=atoi(snum.c_str()); + if ((num>=0) && (num<=127)) + { + if (programfile[num]=="") + programfile[num]=n; + else + output_verbose ("NOTE: found two or more .prog files with same number. ignoring '"+n+"'"); + } + else + { + output_note ("NOTE: found .prog file with invalid number, ignoring it... ('"+n+"')"); + } + } + else + { + output_note ("NOTE: found .prog file which does not start with a number, ignoring it... ('"+n+"')"); + } + } + } + closedir(dir); + } + else + { + if (complain) + output_warning("WARNING: could not open directory '"+directory+"'!\n" + " this is not fatal, ignoring and proceeding..."); + } +} + +void read_config(const char *cfg, bool complain=true) +{ + char buf[2000]; + + ifstream f; + f.open(cfg); + + if (f.good()) + { + string line; + while (!f.eof()) + { + f.getline(buf,sizeof(buf)/sizeof(*buf)-1); + line=buf; + line=trim_spaces(line); + + if ((line!="") && (line[0]!='#')) //ignore comments and empty lines + { + if (line.substr(0,string("include ").length())=="include ") + { + add_dir(trim_spaces(line.substr(string("include ").length()))); + } + else + { + string var,val; + var=trim_spaces(extract_var(line)); + val=trim_spaces(extract_val(line)); + + if (isnum(var)) //programmzuweisung + { + int num=atoi(var.c_str()); + if ((num>=0) && (num<=127)) + { + if (programfile[num]=="") + programfile[num]=val; + else + output_verbose ("NOTE: program #"+IntToStr(num)+" has already been defined. ignoring it..."); + } + else + { + output_warning("WARNING: number out of range (0..127) in program assignment. ignoring it..."); + } + } + else + { + float valf=atof(val.c_str()); + + if (var=="frameskip") + { + if (valf<0) + output_warning("WARNING: invalid value for '"+var+"' ("+val+"). ignoring it..."); + + if (frameskip==-1) + frameskip=valf; + else + output_verbose("NOTE: ignoring value for frameskip, another setting overrides this."); + } + else + { + if (valf<=0) + output_warning("WARNING: invalid value for '"+var+"' ("+val+"). ignoring it..."); + + if ((var=="snh_freq") || (var=="sample_and_hold_freq")) + { + if (snh_freq_hz==0) + snh_freq_hz=valf; + else + output_verbose("NOTE: ignoring value for sample_and_hold_freq, another setting overrides this."); + } + if (var=="lfo0_freq") //FINDLFO + { + if (lfo_freq_hz[0]==0) + lfo_freq_hz[0]=valf; + else + output_verbose("NOTE: ignoring value for "+var+", another setting overrides this."); + } + if (var=="lfo1_freq") + { + if (lfo_freq_hz[1]==0) + lfo_freq_hz[1]=valf; + else + output_verbose("NOTE: ignoring value for "+var+", another setting overrides this."); + } + if (var=="lfo2_freq") + { + if (lfo_freq_hz[2]==0) + lfo_freq_hz[2]=valf; + else + output_verbose("NOTE: ignoring value for "+var+", another setting overrides this."); + } + else if ((var=="cleanup-interval") || (var=="clean")) + { + if (cleanup_interval_sec==0) + cleanup_interval_sec=valf; + else + output_verbose("NOTE: ignoring value for cleanup-interval, another setting overrides this."); + } + else if ((var=="max_port") || (var=="max_port_time") || (var=="max_portamento_time")) + { + if (max_port_time_sec==0) + max_port_time_sec=valf; + else + output_verbose("NOTE: ignoring value for max-portamento-time, another setting overrides this."); + } + else if (var=="lfo_update_freq") + { + if (lfo_update_freq_hz==0) + lfo_update_freq_hz=valf; + else + output_verbose("NOTE: ignoring value for lfo_update_freq, another setting overrides this."); + } + else if (var=="filter_update_freq") + { + if (filter_update_freq_hz==0) + filter_update_freq_hz=valf; + else + output_verbose("NOTE: ignoring value for filter_update_freq, another setting overrides this."); + } + else + output_warning("WARNING: unknown variable '"+var+"'. ignoring it..."); + } + } + } + } + } + } + else + { + output_warning("WARNING: could not open config file '"+string(cfg)+"'.\nignoring this file..."); + } +} diff --git a/synth/load.h b/synth/load.h new file mode 100644 index 0000000..9724e92 --- /dev/null +++ b/synth/load.h @@ -0,0 +1,11 @@ +#ifndef __LOAD_H__ +#define __LOAD_H__ + +#include <string> + +using namespace std; + +void add_dir(string directory, bool complain=true); +void read_config(const char *cfg, bool complain=true); + +#endif diff --git a/synth/main.cpp b/synth/main.cpp new file mode 100644 index 0000000..deebf06 --- /dev/null +++ b/synth/main.cpp @@ -0,0 +1,190 @@ +#include <string> +#include <iostream> +#include <cmath> +#include <cstdlib> + +#include "jack.h" +#include "load.h" +#include "cli.h" +#include "parser.h" +#include "channel.h" +#include "fixed.h" +#include "programs.h" +#include "defines.h" +#include "globals.h" + +using namespace std; + + +void cleanup(); +void dump_options(); + + +int main(int argc, char** argv) +{ + for (int i=0;i<N_LFOS;i++) + lfo_freq_hz[i]=0; + + try + { + parse_args(argc, argv); + + add_dir("~/.flosynth", false); + add_dir("/etc/flosynth", false); + + if (cleanup_interval_sec<=0) cleanup_interval_sec=CLEANUP_INTERVAL_SEC; + for (int i=0;i<N_LFOS;i++) + if (lfo_freq_hz[i]<=0) lfo_freq_hz[i]=LFO_FREQ_HZ[i]; + + if (snh_freq_hz<=0) snh_freq_hz=SNH_FREQ_HZ; + if (frameskip<=-1) frameskip=0; + if (max_port_time_sec<=0) max_port_time_sec=MAX_PORTAMENTO_TIME; + if (filter_update_freq_hz<=0) filter_update_freq_hz=FILTER_UPDATE_FREQ_HZ; + if (lfo_update_freq_hz<=0) lfo_update_freq_hz=LFO_UPDATE_FREQ_HZ; + if (xrun_n<=0) xrun_n=XRUN_N; + if (xrun_time<=0) xrun_time=XRUN_TIME; + + dump_options(); + + frameskip++; //because a value of 0 means using each frame, + //a value of 1 means using each 2nd frame and so on + + init_jack(); + + //this calculation needs the real sampling rate. others don't + cleanup_interval=cleanup_interval_sec*samp_rate; + + + #ifdef FRAMESKIP + samp_rate/=frameskip; + #endif + + + filter_update_frames=samp_rate/filter_update_freq_hz; + lfo_update_frames=samp_rate/lfo_update_freq_hz; + if (filter_update_frames<1) filter_update_frames=1; + if (lfo_update_frames<1) lfo_update_frames=1; + + + int i,j; + + + program_t default_program; + init_default_program(default_program); + + //two possible divisions by zero are avoided, because + //values <= 0 will make the program use the default + //(nonzero) values. + for (i=0;i<N_LFOS;i++) + lfo_res[i]=samp_rate/lfo_freq_hz[i]/lfo_update_frames; + + sample_and_hold_frames=samp_rate/snh_freq_hz; + + for (i=0;i<N_LFOS;i++) + { + lfo[i]=new fixed_t* [lfo_res[i]]; + for (j=0;j<lfo_res[i];j++) + lfo[i][j]=new fixed_t [N_LFO_LEVELS]; + } + + for (i=0;i<N_LFOS;i++) + for (j=0;j<lfo_res[i];j++) + { + float temp=sin(j*2.0*3.141592654/lfo_res[i]); + for (int k=0;k<N_LFO_LEVELS;k++) + lfo[i][j][k]= (1.0 + temp*(float(LFO_MAX)*k/N_LFO_LEVELS)) * ONE; + } + + Parser parser; + program_settings=new program_t[128]; + + for (i=0;i<128;i++) + { + if (programfile[i]!="") + { + try + { + parser.parse(programfile[i]); + program_settings[i]=parser.get_results(); + } + catch (string err) + { + output_warning("WARNING: error parsing '"+programfile[i]+"': "+err+"\n" + " this is not fatal, but the program has NOT been loaded! defaulting to a\n" + " simple program and going on..."); + program_settings[i]=default_program; + } + } + else + { + program_settings[i]=default_program; + } + } + + + for (i=0;i<WAVE_RES;i++) + { + wave[0][i]=sin(i*2.0*3.141592654/WAVE_RES)*ONE; + wave[1][i]=abs(wave[0][i]); + wave[2][i]=(wave[0][i]>=0) ? wave[0][i] : 0; + wave[3][i]=(i<=WAVE_RES/4) ? wave[0][i] : 0; + wave[4][i]=(i<WAVE_RES/2) ? ONE : -ONE; + } + + for (int i=0;i<N_CHANNELS;i++) + channel[i]=new Channel; + + srand (time(NULL)); + + start_jack(connect_audio, connect_midi); + + char tmp[10]; + gets(tmp); + cout << "end."<< endl; + + cleanup(); + } + catch(string err) + { + cout << endl<<endl<< "FATAL: caught an exception: "<<endl<<err<<endl<<"exiting..." << endl; + } +/* catch(...) + { + cout << "FATAL: caught an unknown exception... exiting..." << endl; + }*/ + return 0; +} + +void cleanup() +{ + exit_jack(); + + for (int i=0;i<N_CHANNELS;i++) + { + delete channel[i]; + channel[i]=NULL; + } + + delete [] program_settings; +} + +void dump_options() +{ + for (int i=0;i<128;i++) + if (programfile[i]!="") + cout << "program #"<<i<<":\t'"<<programfile[i]<<"'"<<endl; + + cout << endl; + + cout << "frameskip:\t\t"<<frameskip<<endl; + cout << "cleanup-interval:\t"<<cleanup_interval_sec<<endl; + for (int i=0;i<N_LFOS;i++) + cout << "lfo"<<i<<" freq:\t\t"<<lfo_freq_hz[i]<<endl; + + cout << "sample and hold freq:\t"<<snh_freq_hz<<endl; + cout << "max portamento time:\t"<<max_port_time_sec<<endl; + cout << "xrun n/time:\t\t"<<xrun_n<<"/"<<xrun_time<<"s"<<endl; + cout << "lfo update freq:\t"<<lfo_update_freq_hz<<endl; + cout << "filter update freq:\t"<<filter_update_freq_hz<<endl; + +} diff --git a/synth/note.cpp b/synth/note.cpp new file mode 100644 index 0000000..87f1806 --- /dev/null +++ b/synth/note.cpp @@ -0,0 +1,437 @@ +#include <string> +#include <cmath> + +#include "note.h" +#include "globals.h" +#include "defines.h" + +using namespace std; + +//this function returns the smallest phase_init possible for a +//given custom_wave which is greater or equal than PHASE_INIT +inline fixed_t init_custom_osc_phase(int len, fixed_t sr) +{ + return ( (fixed_t(ceil( float(PHASE_INIT) * sr / len / ONE )) *len << (2*SCALE)) / sr); +} + + +Note::Note(int n, float v, program_t &prg, jack_nframes_t pf, fixed_t pb, int prg_no) +{ + curr_prg=&prg; + + n_oscillators=prg.n_osc; + + + pfactor.out=new fixed_t [n_oscillators]; + pfactor.fm=new fixed_t* [n_oscillators]; + for (int i=0;i<n_oscillators;i++) + pfactor.fm[i]=new fixed_t [n_oscillators]; + + oscval=new fixed_t[n_oscillators]; + old_oscval=new fixed_t[n_oscillators]; + + for (int i=0;i<n_oscillators;i++) + oscval[i]=old_oscval[i]=0; + + envelope=new Envelope*[n_oscillators]; + + for (int i=0;i<n_oscillators;i++) + envelope[i]=new Envelope(prg.env_settings[i]); + + oscillator=new oscillator_t[n_oscillators]; + orig.oscillator=new oscillator_t[n_oscillators]; + copy(&prg.osc_settings[0],&prg.osc_settings[n_oscillators],oscillator); + copy(&prg.osc_settings[0],&prg.osc_settings[n_oscillators],orig.oscillator); + + + //initalize oscillator.phase to multiples of their wave resolution + //this has the following effect: the actual phase, i.e. the index + //in the wave-array (wave[phase]) doesn't change, because + // (n * wave_res) % wave_res is always zero. + //however, if doing phase modulation, it's very unlikely now that + //phase ever becomes negative (which would cause the program to + //segfault, or at least to produce noise). this saves an additional + //(slow) sanity check for the phase. + for (int i=0;i<n_oscillators;i++) + { + if (oscillator[i].custom_wave) + oscillator[i].phase=init_custom_osc_phase(oscillator[i].custom_wave->wave_len, oscillator[i].custom_wave->samp_rate); + else + oscillator[i].phase=ONE * PHASE_INIT; + } + + + portamento_frames=0; + set_portamento_frames(pf); + + set_note(n); + freq=dest_freq; + set_vel(v); + do_ksl(); + + pitchbend=pb; + + program=prg_no; + + filter_params=prg.filter_settings; + orig.filter_params=prg.filter_settings; + + if (filter_params.enabled) + { + filter_envelope=new Envelope( + filter_params.env_settings.attack, + filter_params.env_settings.decay, + filter_params.env_settings.sustain, + filter_params.env_settings.release, + filter_params.env_settings.hold ); + + filter_update_counter=filter_update_frames; + } + + sync_factor=prg.sync_factor; + sync_phase=0; +} + +Note::~Note() +{ + int i; + + for (i=0;i<n_oscillators;i++) + { + delete [] oscillator[i].fm_strength; + delete envelope[i]; + + delete [] pfactor.fm[i]; + } + + delete [] oscillator; + delete [] envelope; + + delete [] oscval; + delete [] old_oscval; + + delete [] pfactor.out; + delete [] pfactor.fm; + +} + +void Note::recalc_factors() +{ + pfactor.filter_env=calc_pfactor(curr_prg->pfactor.filter_env, vel); + pfactor.filter_res=calc_pfactor(curr_prg->pfactor.filter_res, vel); + pfactor.filter_offset=calc_pfactor(curr_prg->pfactor.filter_offset, vel); + + for (int i=0;i<n_oscillators;i++) + { + pfactor.out[i]=calc_pfactor(curr_prg->pfactor.out[i], vel); + + for (int j=0;j<n_oscillators;j++) + pfactor.fm[i][j]=calc_pfactor(curr_prg->pfactor.fm[i][j], vel); + } +} + +void Note::apply_pfactor() +{ + //apply pfactor to all necessary parameters + for (int i=0;i<n_oscillators;i++) + { + oscillator[i].output=orig.oscillator[i].output*pfactor.out[i] >>SCALE; + + for (int j=0;j<n_oscillators;j++) + oscillator[i].fm_strength[j]=orig.oscillator[i].fm_strength[j]*pfactor.fm[i][j] >>SCALE; + } + filter_params.env_amount=orig.filter_params.env_amount*pfactor.filter_env /ONE; + filter_params.freqfactor_offset=orig.filter_params.freqfactor_offset*pfactor.filter_offset /ONE; + filter_params.resonance=orig.filter_params.resonance*pfactor.filter_res /ONE; +} + +void Note::set_param(const parameter_t &p, fixed_t v) //ACHTUNG: +{ + //wenn das verändert wird, muss auch program_t::set_param verändert werden! + switch(p.par) + { + case ATTACK: envelope[p.osc]->set_attack(v*samp_rate >>SCALE); break; + case DECAY: envelope[p.osc]->set_decay(v*samp_rate >>SCALE); break; + case SUSTAIN: envelope[p.osc]->set_sustain(v); break; + case RELEASE: envelope[p.osc]->set_release(v*samp_rate >>SCALE); break; + case HOLD: envelope[p.osc]->set_hold(v!=0); break; + + case KSR: oscillator[p.osc].ksr=float(v)/ONE; break; + case KSL: oscillator[p.osc].ksl=float(v)/ONE; break; + + case FACTOR: oscillator[p.osc].factor=v; break; + case MODULATION: oscillator[p.osc].fm_strength[p.index]=v*pfactor.fm[p.osc][p.index] >>SCALE; break; + case OUTPUT: oscillator[p.osc].output=v*pfactor.out[p.osc] >>SCALE; break; + case TREMOLO: oscillator[p.osc].tremolo_depth=v; break; + case TREM_LFO: oscillator[p.osc].tremolo_lfo=v; break; + case VIBRATO: oscillator[p.osc].vibrato_depth=v; break; + case VIB_LFO: oscillator[p.osc].vibrato_lfo=v; break; + case WAVEFORM: oscillator[p.osc].waveform=v; break; + case SYNC: oscillator[p.osc].sync=(v!=0); break; + + case FILTER_ENABLED: output_note("NOTE: cannot enable filter in playing notes"); break; + case FILTER_ENV_AMOUNT: filter_params.env_amount=float(v*pfactor.filter_env)/ONE/ONE; break; + + case FILTER_ATTACK: + if (filter_params.enabled) + filter_envelope->set_attack(v*samp_rate/filter_update_frames >>SCALE); + else + output_note("NOTE: cannot set filter-attack when filter is disabled"); + break; + + case FILTER_DECAY: + if (filter_params.enabled) + filter_envelope->set_decay(v*samp_rate/filter_update_frames >>SCALE); + else + output_note("NOTE: cannot set filter-decay when filter is disabled"); + break; + + case FILTER_SUSTAIN: + if (filter_params.enabled) + filter_envelope->set_sustain(v); + else + output_note("NOTE: cannot set filter-sustain when filter is disabled"); + break; + + case FILTER_RELEASE: + if (filter_params.enabled) + filter_envelope->set_release(v*samp_rate/filter_update_frames >>SCALE); + else + output_note("NOTE: cannot set filter-release when filter is disabled"); + break; + + case FILTER_HOLD: + if (filter_params.enabled) + filter_envelope->set_hold(v!=0); + else + output_note("NOTE: cannot set filter-hold when filter is disabled"); + break; + + case FILTER_OFFSET: filter_params.freqfactor_offset=float(v*pfactor.filter_offset)/ONE/ONE; break; + case FILTER_RESONANCE: filter_params.resonance=float(v*pfactor.filter_res)/ONE/ONE; break; + case FILTER_TREMOLO: filter_params.trem_strength=v; break; + case FILTER_TREM_LFO: filter_params.trem_lfo=v; break; + + case SYNC_FACTOR: sync_factor=v; break; + default: throw string("trying to set an unknown parameter"); + + } +} + +bool Note::still_active() +{ + for (int i=0; i<n_oscillators; i++) + if ((oscillator[i].output>0) && (envelope[i]->still_active())) + return true; + + return false; +} + + +//this function must still work properly if called multiple times +//when called a second time, there shall be no effect +void Note::release_quickly(jack_nframes_t maxt) +{ + for (int i=0;i<n_oscillators;i++) + { + if (envelope[i]->get_release() > maxt) + envelope[i]->set_release(maxt); + + envelope[i]->release_key(); + + // i don't release the filter-env because lacking to do so + // does not generate a hearable difference (or would you hear + // when in the last half second a tone is filtered or not?) + } +} + +void Note::release() +{ + for (int i=0;i<n_oscillators;i++) + envelope[i]->release_key(); + + if (filter_params.enabled) + filter_envelope->release_key(); +} + +void Note::reattack() +{ + for (int i=0;i<n_oscillators;i++) + envelope[i]->reattack(); +} + +void Note::set_pitchbend(fixed_t pb) +{ + pitchbend=pb; +} + +void Note::set_freq(float f) +{ + old_freq=freq; + dest_freq=f*ONE; + portamento_t=0; + + do_ksr(); +} + +void Note::set_freq(float f, bool do_port) +{ + set_freq(f); + + if (!do_port) + old_freq=dest_freq; +} + +void Note::set_note(int n) +{ + note=n; + set_freq(440.0*pow(2.0,(float)(n-69)/12.0)); +} + +void Note::set_note(int n, bool do_port) +{ + note=n; + set_freq(440.0*pow(2.0,(float)(n-69)/12.0), do_port); +} + +int Note::get_note() +{ + return note; +} + +void Note::set_vel(float v) +{ + vel=v*ONE; + + recalc_factors(); + apply_pfactor(); +} + +void Note::do_ksl() +{ //osc.ksl is in Bel/octave (i.e. dB/10) + //if ksl=1, this means that for each octave the loudness + //decreases by half + for (int i=0;i<n_oscillators;i++) + { + if (oscillator[i].ksl==0) + envelope[i]->set_max(ONE); + else + envelope[i]->set_max( fixed_t(double(ONE) / pow(freq>>SCALE, oscillator[i].ksl)) ); + } +} + +void Note::do_ksr() +{ + for (int i=0;i<n_oscillators;i++) + envelope[i]->set_ratefactor(1.0 / pow(freq>>SCALE, oscillator[i].ksr)); +} + +void Note::set_portamento_frames(jack_nframes_t t) +{ + portamento_frames=t; + portamento_t=0; +} + +fixed_t Note::get_sample() +{ + if (freq!=dest_freq) + { + // the div.by.zero if p_frames=0 is avoided because then the + // if-condition below is always true + if (portamento_t>=portamento_frames) + freq=dest_freq; + else //will only happen if p_t < p_frames -> p_frames is always > 0 -> div. ok + freq = old_freq + (dest_freq-old_freq)*portamento_t/portamento_frames; + + do_ksl(); + + portamento_t++; + } + + fixed_t actual_freq=freq*pitchbend >>SCALE; + + fixed_t *temp; + temp=old_oscval; //swap the current and old oscval-pointers + old_oscval=oscval; + oscval=temp; + + fixed_t fm=0; + fixed_t out=0; + + int i,j; + + if (sync_factor) + { + sync_phase+=(actual_freq*sync_factor/samp_rate) >> SCALE; + + if (sync_phase >= ONE) + { + sync_phase-=ONE; + + for (i=0;i<n_oscillators;i++) + if (oscillator[i].sync) + { + if (oscillator[i].custom_wave) + oscillator[i].phase=init_custom_osc_phase(oscillator[i].custom_wave->wave_len, oscillator[i].custom_wave->samp_rate); + else + oscillator[i].phase=ONE * PHASE_INIT; + } + } + } + + for (i=0;i<n_oscillators;i++) + { + fm=0; + oscval[i]=0; + + for (j=0;j<n_oscillators;j++) + if (oscillator[i].fm_strength[j]!=0) //osc_j affects osc_i (FM) + fm+=(old_oscval[j]*oscillator[i].fm_strength[j])>>SCALE; + + //phase increases in one second, i.e. in samp_rate frames, by the osc's freq + if (oscillator[i].vibrato_depth!=0) + oscillator[i].phase+=( (curr_lfo[oscillator[i].vibrato_lfo][oscillator[i].vibrato_depth]*actual_freq >>SCALE)*oscillator[i].factor/samp_rate)>>SCALE; + else + oscillator[i].phase+=(actual_freq*oscillator[i].factor/samp_rate)>>SCALE; + + if (oscillator[i].custom_wave) + { + //sampler + custom_wave_t *cw=oscillator[i].custom_wave; + oscval[i]=cw->wave[ ((oscillator[i].phase + fm) * cw->samp_rate >>(2*SCALE)) % cw->wave_len ] * envelope[i]->get_level() >> (SCALE); + } + else + { + //normal oscillator + oscval[i]=wave[oscillator[i].waveform][ ((oscillator[i].phase + fm) * WAVE_RES >>SCALE) % WAVE_RES ] * envelope[i]->get_level() >> (SCALE); + } + + if (oscillator[i].tremolo_depth!=0) + oscval[i]=oscval[i]* curr_lfo[oscillator[i].tremolo_lfo][oscillator[i].tremolo_depth] >> SCALE; + + if (oscillator[i].output!=0) + out+=oscillator[i].output*oscval[i] >>SCALE; + } + + if (filter_params.enabled) + { + filter_update_counter++; + if (filter_update_counter>=filter_update_frames) + { + filter_update_counter=0; + + float cutoff= float(actual_freq)/ONE * + float(curr_lfo[filter_params.trem_lfo][filter_params.trem_strength])/ONE * + ( filter_params.freqfactor_offset + filter_envelope->get_level() * filter_params.env_amount / float(ONE) ); + filter.set_params( cutoff, filter_params.resonance ); + } + + fixed_t tmp=out; + filter.process_sample(&tmp); + return tmp; + } + else + { + return out; + } +} diff --git a/synth/note.h b/synth/note.h new file mode 100644 index 0000000..5035bd2 --- /dev/null +++ b/synth/note.h @@ -0,0 +1,80 @@ +#ifndef __NOTE_H__ +#define __NOTE_H__ + +#include <jack/jack.h> + +#include "programs.h" +#include "envelope.h" +#include "fixed.h" +#include "filter.h" + +class Note +{ + public: + Note(int n, float v,program_t &prg, jack_nframes_t pf, fixed_t pb, int prg_no); + ~Note(); + fixed_t get_sample(); + int get_note(); + void set_note(int n); + void set_note(int n, bool do_port); + void set_freq(float f); + void set_freq(float f, bool do_port); + void set_pitchbend(fixed_t pb); + void set_vel(float v); + void set_portamento_frames(jack_nframes_t f); + void release_quickly(jack_nframes_t maxt); + void release(); + void reattack(); + bool still_active(); + void set_param(const parameter_t &p, fixed_t v); + int get_program(){return program;} + + private: + void do_ksl(); + void do_ksr(); + + void recalc_factors(); + void apply_pfactor(); + + Envelope **envelope; + fixed_t freq, dest_freq, old_freq; + fixed_t vel; + jack_nframes_t portamento_t, portamento_frames; + + fixed_t *oscval; + fixed_t *old_oscval; + int n_oscillators; + oscillator_t *oscillator; + + fixed_t sync_factor; + fixed_t sync_phase; + + pfactor_value_t pfactor; + + int note; + int program; + program_t *curr_prg; + + fixed_t pitchbend; + + LowPassFilter filter; + Envelope *filter_envelope; + filter_params_t filter_params; + int filter_update_counter; + + struct + { + oscillator_t *oscillator; + filter_params_t filter_params; + } orig; + +/* *einstellungen: oszillatoren, deren lautstärke etc. + * note + * lautstärke + * *pitchbend + * *portamento time + */ +}; + + +#endif diff --git a/synth/parser.cpp b/synth/parser.cpp new file mode 100644 index 0000000..52717a5 --- /dev/null +++ b/synth/parser.cpp @@ -0,0 +1,618 @@ +#include <cstdlib> +#include <fstream> + +#include "parser.h" +#include "defines.h" +#include "programs.h" +#include "globals.h" +#include "util.h" +#include "readwave.h" + + +Parser::Parser() +{ + n_osc=0; + osc=NULL; + env=NULL; + for (int i=0;i<128;i++) + controller_default[i]=0; +} + +Parser::~Parser() +{ + uninit_stuff(); +} + +list<string> Parser::extract_terms(string s) +{ + list<string> result; + + size_t p=-1,p2; + + s="+"+s+"+"; + + p=0; + p2=s.find_first_of("+-",p+1); + + while(p2!=string::npos) + { + result.push_back(s.substr(p,p2-p)); + p=p2; + p2=s.find_first_of("+-",p+1); + } + return result; +} + +list<string> Parser::extract_factors(string s) +{ + list<string> result; + + size_t p=-1,p2; + + s="*"+s+"*"; + + p=0; + p2=s.find_first_of("*/",p+1); + + while(p2!=string::npos) + { + result.push_back(s.substr(p,p2-p)); + p=p2; + p2=s.find_first_of("*/",p+1); + } + return result; +} + +list<term_t> Parser::extract_formula(string s) +{ + list<term_t> result; + term_t tmp; + list<string> terms=extract_terms(s); + + for (list<string>::iterator term=terms.begin(); term!=terms.end(); term++) + { + list<string> factors=extract_factors(term->substr(1)); + double fac= ((*term)[0]=='+') ? 1.0 : -1.0; + string cont=""; + for (list<string>::iterator factor=factors.begin(); factor!=factors.end(); factor++) + { + if (factor->find_first_not_of("0123456789.*/+-")==string::npos) + { + if ((*factor)[0]=='*') + fac*=atof((*factor).substr(1).c_str()); + else + { + if (atof((*factor).substr(1).c_str())==0) + throw string("dividing by zero is not allowed"); + + fac/=atof((*factor).substr(1).c_str()); + } + } + else + { + if (cont!="") + throw string("multiplicating controllers is not allowed"); + + if ((*factor)[0]!='*') + throw string("dividing through a controller is not allowed"); + + cont=(*factor).substr(1); + } + } + if (cont=="") + tmp.c=NO_CONT; + else + { + if (extract_array_name(cont)!="cont") + throw string("expected 'cont', found '"+extract_array_name(cont)+"'"); + + tmp.c=extract_array_index(cont,1); + if ((tmp.c<0) || (tmp.c>127)) + throw string("invalid controller specified"); + } + tmp.f=fac*ONE; + result.push_back(tmp); + } + return result; +} + +param_factor_t Parser::parse_pfactor(string s) //TODO fast dasselbe wie oben. mergen? +{ //TODO cont müsste vel heißen FINDMICH ---> ^ ^ ^ + param_factor_t result; + result.offset=0; + result.vel_amount=0; + + list<string> terms=extract_terms(s); + + for (list<string>::iterator term=terms.begin(); term!=terms.end(); term++) + { + list<string> factors=extract_factors(term->substr(1)); + double fac= ((*term)[0]=='+') ? 1.0 : -1.0; + string cont=""; + for (list<string>::iterator factor=factors.begin(); factor!=factors.end(); factor++) + { + if (factor->find_first_not_of("0123456789.*/+-")==string::npos) + { + if ((*factor)[0]=='*') + fac*=atof((*factor).substr(1).c_str()); + else + { + if (atof((*factor).substr(1).c_str())==0) + throw string("dividing by zero is not allowed"); + + fac/=atof((*factor).substr(1).c_str()); + } + } + else + { + if (cont!="") + throw string("multiplicating velocity is not allowed"); + + if ((*factor)[0]!='*') + throw string("dividing through velocity is not allowed"); + + cont=(*factor).substr(1); + } + } + if (cont=="") + { + result.offset+= fac*ONE; + } + else + { + if (cont!="vel") + throw string("expected 'vel', found '"+cont+"'"); + + result.vel_amount+= fac*ONE; + } + } + return result; +} + +void Parser::init_stuff() +{ + env=new env_settings_t[n_osc]; + osc=new oscillator_t[n_osc]; + for (int i=0;i<n_osc;i++) + { + osc[i].n_osc=n_osc; + + osc[i].fm_strength=new fixed_t[n_osc]; + for (int j=0;j<n_osc;j++) + osc[i].fm_strength[j]=0; + + osc[i].output=0; + osc[i].waveform=0; + osc[i].factor=ONE; + osc[i].tremolo_depth=0; + osc[i].tremolo_lfo=0; + osc[i].vibrato_depth=0; + osc[i].vibrato_lfo=0; + osc[i].custom_wave=NULL; + + + env[i].attack=0; + env[i].decay=0; + env[i].sustain=ONE; + env[i].release=0; + env[i].hold=true; + } + + filter.enabled=false; + filter.env_amount=0; + filter.env_settings.attack=filter.env_settings.decay= + filter.env_settings.release=0; + filter.env_settings.sustain=0; + filter.env_settings.hold=true; + + filter.freqfactor_offset=0; + filter.resonance=0; + filter.trem_strength=0; + filter.trem_lfo=0; + + + + pfactor.out=new param_factor_t [n_osc]; + pfactor.fm=new param_factor_t* [n_osc]; + + pfactor.filter_env.offset=ONE; + pfactor.filter_env.vel_amount=0; + + pfactor.filter_res.offset=ONE; + pfactor.filter_res.vel_amount=0; + + pfactor.filter_offset.offset=ONE; + pfactor.filter_offset.vel_amount=0; + + for (int i=0;i<n_osc;i++) + { + pfactor.out[i].offset=0; + pfactor.out[i].vel_amount=ONE; + + pfactor.fm[i]=new param_factor_t [n_osc]; + for (int j=0;j<n_osc;j++) + { + pfactor.fm[i][j].offset=ONE; + pfactor.fm[i][j].vel_amount=0; + } + } + +} +void Parser::uninit_stuff() +{ + if (osc) + { + for (int i=0;i<n_osc;i++) + delete [] osc[i].fm_strength; + + delete [] osc; + osc=NULL; + } + if (env) + delete [] env; +} + +string Parser::extract_array_name(string s) +{ + size_t p; + p=s.find('['); + if (p!=string::npos) + return s.substr(0,p); + else + return s; +} + +int Parser::extract_array_index(string s, int dim) +{ + size_t p=-1,p2; + for (int i=0;i<dim;i++) + { + p=s.find('[',p+1); + if (p==string::npos) return -1; + } + + p2=s.find(']',p+1); + if (p2==string::npos) return -1; + + return atoi(s.substr(p+1,p2-p-1).c_str()); +} + +//if this function fails, this WILL BE fatal if unhandled in the +//caller function. so this function throws errors +//if this function fails the settings are in an undefined, illegal +//state. if these settings are given to some oscillator_t by +//operator=, it will probably die while trying to create an array +//with size 0 or so. +void Parser::parse(string fn) +{ + char buf[2000]; + list<term_t> terms; + string line; + string var; + string array; + string strval; + float val; + + parameter_enum p; + + int ind,ind2=0; + + int state; + + uninit_stuff(); + + ifstream f; + f.open(fn.c_str()); + if (f.good()) + { + state=0; + while (!f.eof()) + { + f.getline(buf,sizeof(buf)/sizeof(*buf)-1); + line=buf; + line=remove_all_spaces(buf); + if ((line!="") && (line[0]!='#')) //ignore comments and empty lines + { + if (line=="controllers:") + { + state=2; + continue; + } + else if (line=="defaults:") + { + state=3; + continue; + } + else if (line=="velocity:") + { + state=4; + continue; + } + + var=extract_var(line); + array=extract_array_name(var); + strval=extract_val(line); + val=atof(strval.c_str()); + + switch (state) + { + case 0: //expect and read number of oscillators + if (var!="oscillators") + throw string("need to know number of oscillators"); + else + n_osc=val; + + if (n_osc<=0) throw string("invalid number of oscillators"); + + init_stuff(); + + state=1; + break; + + case 1: //read and set information about oscillator settings + p=param_to_enum(array); + + ind=extract_array_index(var,1); + if ( param_needs_index(p) && (!((ind>=0) && (ind<n_osc))) ) + throw string("out of array bounds"); + + + switch (p) + { + case MODULATION: + ind2=extract_array_index(var,2); + if (!((ind2>=0) && (ind2<n_osc))) + throw string("out of array bounds"); + + osc[ind].fm_strength[ind2]=val*ONE; + break; + case OUTPUT: + osc[ind].output=val*ONE; + break; + case WAVEFORM: + if (isfloat(strval)) + { + osc[ind].waveform=int(val); + } + else + { + size_t pos=strval.find(':'); + if (pos==string::npos) + throw string("expected 'freq:file.wav', found no ':'"); + + float given_freq=atof(strval.substr(0,pos).c_str()); + string wavefile=strval.substr(pos+1); + + if (given_freq<=0) + throw string("illegal freq specified for custom wave '"+wavefile+"'"); + + osc[ind].custom_wave=new custom_wave_t; + read_wave(wavefile.c_str(), osc[ind].custom_wave); + osc[ind].custom_wave->samp_rate/=given_freq; + } + break; + case FACTOR: + osc[ind].factor=val*ONE; + break; + case TREMOLO: + osc[ind].tremolo_depth=int(val); + break; + case TREM_LFO: + if (strval=="snh") + osc[ind].tremolo_lfo=SNH_LFO; + else + { + osc[ind].tremolo_lfo= int(val); + if ((val<0) || (val>=N_LFOS)) + throw string("invalid value for tremolo_lfo"); + } + break; + case VIBRATO: + osc[ind].vibrato_depth=val; + break; + case VIB_LFO: + if (strval=="snh") + osc[ind].vibrato_lfo= SNH_LFO; + else + { + osc[ind].vibrato_lfo= int(val); + if ((val<0) || (val>=N_LFOS)) + throw string("invalid value for vibrato_lfo"); + } + break; + case ATTACK: + env[ind].attack=val*samp_rate; + break; + case DECAY: + env[ind].decay=val*samp_rate; + break; + case SUSTAIN: + env[ind].sustain=val*ONE; + break; + case RELEASE: + env[ind].release=val*samp_rate; + break; + case HOLD: + env[ind].hold=(val!=0); + break; + case KSR: + osc[ind].ksr=val; + break; + case KSL: + osc[ind].ksl=val; + break; + case SYNC: + osc[ind].sync=(val!=0); + break; + case FILTER_ENABLED: + filter.enabled=(val!=0); + break; + case FILTER_ENV_AMOUNT: + filter.env_amount=val; + break; + case FILTER_ATTACK: + filter.env_settings.attack=val*samp_rate/filter_update_frames; + break; + case FILTER_DECAY: + filter.env_settings.decay=val*samp_rate/filter_update_frames; + break; + case FILTER_SUSTAIN: + filter.env_settings.sustain=val*ONE; + break; + case FILTER_RELEASE: + filter.env_settings.release=val*samp_rate/filter_update_frames; + break; + case FILTER_HOLD: + filter.env_settings.hold=(val!=0); + break; + case FILTER_OFFSET: + filter.freqfactor_offset=val; + break; + case FILTER_RESONANCE: + filter.resonance=val; + break; + case FILTER_TREMOLO: + filter.trem_strength=int(val); + break; + case FILTER_TREM_LFO: + if (strval=="snh") + filter.trem_lfo=SNH_LFO; + else + { + filter.trem_lfo=int(val); + if ((val<0) || (val>=N_LFOS)) + throw string("invalid value for filter_trem_lfo"); + } + break; + case SYNC_FACTOR: + sync_factor=val*ONE; + break; + default: + throw string("unknown variable ('"+array+"')"); + } + break; + + case 2: //read how controllers influence parameters + p=param_to_enum(array); + + ind=extract_array_index(var,1); + if ( param_needs_index(p) && (!((ind>=0) && (ind<n_osc))) ) + throw string("out of array bounds"); + + parameter_t par; + par.par=p; + + if (par.par==UNKNOWN) + throw string("unknown variable ('"+array+"')"); + + if (par.par==MODULATION) + { + ind2=extract_array_index(var,2); + if (!((ind2>=0) && (ind2<n_osc))) + throw string("out of array bounds"); + } + + par.osc=ind; + par.index=ind2; + + terms=extract_formula(strval); + for (list<term_t>::iterator it=terms.begin(); it!=terms.end(); it++) + if (it->c!=NO_CONT) + affect[it->c].insert(par); + + + formula[par]=terms; + break; + + case 3: //read controller default values + if (array=="cont") + { + ind=extract_array_index(var,1); + + if ((ind<0) || (ind>127)) + throw string("out of array bounds"); + + if ((val<0) || (val>127)) + throw string("value out of range"); + + controller_default[ind]=val; + } + else + throw string("expected cont, found '"+array+"'"); + + break; + + case 4: //read velocity-influence over certain params + p=param_to_enum(array); + + ind=extract_array_index(var,1); + if ( param_needs_index(p) && (!((ind>=0) && (ind<n_osc))) ) + throw string("out of array bounds"); + + switch(p) + { + case MODULATION: + ind2=extract_array_index(var,2); + if (!((ind2>=0) && (ind2<n_osc))) + throw string("out of array bounds"); + + pfactor.fm[ind][ind2]=parse_pfactor(strval); + break; + + case OUTPUT: + pfactor.out[ind]=parse_pfactor(strval); + break; + + case FILTER_ENV_AMOUNT: + pfactor.filter_env=parse_pfactor(strval); + break; + + case FILTER_RESONANCE: + pfactor.filter_res=parse_pfactor(strval); + break; + + case FILTER_OFFSET: + pfactor.filter_offset=parse_pfactor(strval); + break; + + default: + throw string("velocity cannot influence parameter '"+array+"'"); + + + } + } + } + } + } + else + throw string ("could not open '"+fn+"'"); +} + + +program_t Parser::get_results() const +{ + program_t result; + + result.n_osc=n_osc; + + copy(&affect[0],&affect[128],result.controller_affects); + + result.formula=formula; + + result.osc_settings=new oscillator_t[n_osc]; + + copy(&osc[0],&osc[n_osc],result.osc_settings); + result.env_settings=new env_settings_t[n_osc]; + copy(&env[0],&env[n_osc],result.env_settings); + + for (int i=0;i<128;i++) + result.controller[i]=controller_default[i]; + + result.filter_settings=filter; + + result.sync_factor=sync_factor; + + result.pfactor=pfactor; + + return result; +} diff --git a/synth/parser.h b/synth/parser.h new file mode 100644 index 0000000..192226e --- /dev/null +++ b/synth/parser.h @@ -0,0 +1,47 @@ +#ifndef __PARSER_H__ +#define __PARSET_H__ + + +#include <set> +#include <map> +#include <list> +#include <string> + +#include "fixed.h" +#include "programs.h" + +using namespace std; + +class Parser +{ + public: + Parser(); + ~Parser(); + void parse(string fn); + program_t get_results() const; + + private: + void init_stuff(); + void uninit_stuff(); + static string extract_array_name(string s); + static list<string> extract_terms(string s); + static list<string> extract_factors(string s); + static list<term_t> extract_formula(string s); + static param_factor_t parse_pfactor(string s); + static int extract_array_index(string s, int dim); + + int n_osc; + oscillator_t *osc; + env_settings_t *env; + set<parameter_t> affect[128]; + map< parameter_t, list<term_t> > formula; + int controller_default[128]; + filter_params_t filter; + + pfactor_formula_t pfactor; + + fixed_t sync_factor; +}; + + +#endif diff --git a/synth/programs.cpp b/synth/programs.cpp new file mode 100644 index 0000000..be82fed --- /dev/null +++ b/synth/programs.cpp @@ -0,0 +1,224 @@ +#include <string> +#include <cstring> + +#include "programs.h" +#include "globals.h" + +using namespace std; + +oscillator_t::oscillator_t() +{ + phase=0; + fm_strength=NULL; + + ksl=0; + ksr=0; + + sync=false; + + custom_wave=NULL; +} + +oscillator_t& oscillator_t::operator=(const oscillator_t &that) +{ + if (this!=&that) + { + if (this->fm_strength) + delete [] this->fm_strength; + + memcpy(this, &that, sizeof(*this)); + + this->fm_strength=new fixed_t[n_osc]; + memcpy(this->fm_strength, that.fm_strength, sizeof(*that.fm_strength)*n_osc); + + this->custom_wave=that.custom_wave; + + return *this; + } + else + return *this; +} + +program_t::program_t() +{ + osc_settings=NULL; + env_settings=NULL; + filter_settings.enabled=false; + sync_factor=0; + n_osc=0; + + pfactor.fm=NULL; + pfactor.out=NULL; +} + +program_t::~program_t() +{ + cleanup(); +} + +void program_t::cleanup() +{ + if (osc_settings) + { + for (unsigned int i=0;i<n_osc;i++) + delete [] osc_settings[i].fm_strength; + delete [] osc_settings; + } + if (env_settings) + delete [] env_settings; + + if (pfactor.out) + delete [] pfactor.out; + if (pfactor.fm) + { + for (unsigned int i=0;i<n_osc;i++) + delete [] pfactor.fm[i]; + delete [] pfactor.fm; + } +} + +program_t& program_t::operator=(const program_t &that) +{ + if (this!=&that) + { + unsigned int i; + + this->cleanup(); + + for (i=0;i<(sizeof(controller_affects)/sizeof(*controller_affects));i++) + this->controller_affects[i]=that.controller_affects[i]; + this->formula=that.formula; + this->n_osc=that.n_osc; + + this->osc_settings=new oscillator_t[n_osc]; + this->env_settings=new env_settings_t[n_osc]; + + memcpy(this->controller, that.controller, sizeof(that.controller)); + copy(&that.osc_settings[0], &that.osc_settings[n_osc], this->osc_settings); + copy(&that.env_settings[0], &that.env_settings[n_osc], this->env_settings); + + this->filter_settings=that.filter_settings; + + this->sync_factor=that.sync_factor; + + + + this->pfactor=that.pfactor; + + this->pfactor.out=new param_factor_t [n_osc]; + memcpy(this->pfactor.out, that.pfactor.out, sizeof(param_factor_t)*n_osc); + + this->pfactor.fm=new param_factor_t* [n_osc]; + for (i=0;i<n_osc;i++) + { + this->pfactor.fm[i]=new param_factor_t [n_osc]; + memcpy(this->pfactor.fm[i], that.pfactor.fm[i], sizeof(param_factor_t)*n_osc); + } + + return *this; + } + else + return *this; +} + +void program_t::set_param(const parameter_t &p, fixed_t v) //ACHTUNG: +{ //wenn das verändert wird, muss auch Note::set_param verändert werden! + switch(p.par) + { + case ATTACK: env_settings[p.osc].attack=v*samp_rate >>SCALE; break; + case DECAY: env_settings[p.osc].decay=v*samp_rate >>SCALE; break; + case SUSTAIN: env_settings[p.osc].sustain=v; break; + case RELEASE: env_settings[p.osc].release=v*samp_rate >>SCALE; break; + case HOLD: env_settings[p.osc].hold=(v!=0); break; + + case KSR: osc_settings[p.osc].ksr=float(v)/ONE; break; + case KSL: osc_settings[p.osc].ksl=float(v)/ONE; break; + + case FACTOR: osc_settings[p.osc].factor=v; break; + case MODULATION: osc_settings[p.osc].fm_strength[p.index]=v; break; + case OUTPUT: osc_settings[p.osc].output=v; break; + case TREMOLO: osc_settings[p.osc].tremolo_depth=v; break; + case TREM_LFO: osc_settings[p.osc].tremolo_lfo=v; break; + case VIBRATO: osc_settings[p.osc].vibrato_depth=v; break; + case VIB_LFO: osc_settings[p.osc].vibrato_lfo=v; break; + case WAVEFORM: osc_settings[p.osc].waveform=v; break; + case SYNC: osc_settings[p.osc].sync=(v!=0); break; + + case FILTER_ENABLED: filter_settings.enabled=(v!=0); break; + case FILTER_ENV_AMOUNT: filter_settings.env_amount=float(v)/ONE; break; + case FILTER_ATTACK: filter_settings.env_settings.attack=v*samp_rate/filter_update_frames >>SCALE; break; + case FILTER_DECAY: filter_settings.env_settings.decay=v*samp_rate/filter_update_frames >>SCALE; break; + case FILTER_SUSTAIN: filter_settings.env_settings.sustain=v; break; + case FILTER_RELEASE: filter_settings.env_settings.release=v*samp_rate/filter_update_frames >>SCALE; break; + case FILTER_HOLD: filter_settings.env_settings.hold=(v!=0); break; + case FILTER_OFFSET: filter_settings.freqfactor_offset=float(v)/ONE; break; + case FILTER_RESONANCE: filter_settings.resonance=float(v)/ONE; break; + case FILTER_TREMOLO: filter_settings.trem_strength=v; break; + case FILTER_TREM_LFO: filter_settings.trem_lfo=v; break; + + case SYNC_FACTOR: sync_factor=v; break; + + default: throw string("trying to set an unknown parameter"); + } +} + + +bool parameter_t::operator< (const struct parameter_t &b) const +{ + if (par!=b.par) return (par < b.par); + else if (osc!=b.osc) return (osc < b.osc); + else if (index!=b.index) return (index < b.index); + else return false; //they're equal +} + + + + +fixed_t calc_pfactor(param_factor_t &formula, fixed_t vel) +{ + return formula.offset + (formula.vel_amount * vel >>SCALE); +} + +void init_default_program(program_t &p) +{ + p.n_osc=1; + p.osc_settings=new oscillator_t[1]; + p.env_settings=new env_settings_t[1]; + p.osc_settings[0].n_osc=1; + p.osc_settings[0].fm_strength=new fixed_t[1]; + p.osc_settings[0].factor=ONE; + p.osc_settings[0].fm_strength[0]=0; + p.osc_settings[0].ksl=0; + p.osc_settings[0].ksr=0; + p.osc_settings[0].output=ONE; + p.osc_settings[0].tremolo_depth=0; + p.osc_settings[0].vibrato_depth=0; + p.osc_settings[0].waveform=0; + p.env_settings[0].attack=0; + p.env_settings[0].decay=0; + p.env_settings[0].sustain=ONE; + p.env_settings[0].release=3*samp_rate; + p.env_settings[0].hold=true; + p.filter_settings.enabled=false; + + + p.pfactor.out=new param_factor_t [1]; + p.pfactor.fm=new param_factor_t* [1]; + + p.pfactor.filter_env.offset=ONE; + p.pfactor.filter_env.vel_amount=0; + + p.pfactor.filter_res.offset=ONE; + p.pfactor.filter_res.vel_amount=0; + p.pfactor.filter_offset.offset=ONE; + p.pfactor.filter_offset.vel_amount=0; + + p.pfactor.out[0].offset=0; + p.pfactor.out[0].vel_amount=ONE; + + p.pfactor.fm[0]=new param_factor_t [1]; + p.pfactor.fm[0][0].offset=ONE; + p.pfactor.fm[0][0].vel_amount=0; + +} + diff --git a/synth/programs.h b/synth/programs.h new file mode 100644 index 0000000..e0b409c --- /dev/null +++ b/synth/programs.h @@ -0,0 +1,193 @@ +#ifndef __PROGRAMS_H__ +#define __PROGRAMS_H__ + + +#include <map> +#include <set> +#include <list> + +#include "fixed.h" +#include <jack/jack.h> + +using namespace std; + +struct term_t +{ + int c; + fixed_t f; +}; + +enum parameter_enum +{ + MODULATION=0, + OUTPUT, + WAVEFORM, + FACTOR, + TREMOLO, + TREM_LFO, + VIBRATO, + VIB_LFO, + ATTACK, + DECAY, + SUSTAIN, + RELEASE, + HOLD, + KSR, + KSL, + SYNC, + FILTER_ENABLED, + FILTER_ENV_AMOUNT, + FILTER_ATTACK, + FILTER_DECAY, + FILTER_SUSTAIN, + FILTER_RELEASE, + FILTER_HOLD, + FILTER_OFFSET, + FILTER_RESONANCE, + FILTER_TREMOLO, + FILTER_TREM_LFO, + SYNC_FACTOR, + + + PARAMETER_N_ENTRIES, + UNKNOWN=-1 +}; + +struct parameter_t +{ + int osc; + int index; + enum parameter_enum par; + + bool operator< (const struct parameter_t &b) const; +}; + + + +struct param_factor_t +{ + fixed_t offset; + fixed_t vel_amount; +}; + +struct pfactor_formula_t +{ + param_factor_t **fm; + param_factor_t *out; + param_factor_t filter_env; + param_factor_t filter_res; + param_factor_t filter_offset; +}; + +struct pfactor_value_t +{ + fixed_t **fm; + fixed_t *out; + fixed_t filter_env; + fixed_t filter_res; + fixed_t filter_offset; +}; + +fixed_t calc_pfactor(param_factor_t &formula, fixed_t vel); + + + +struct custom_wave_t +{ + fixed_t samp_rate; + int wave_len; + fixed_t* wave; + + custom_wave_t() + { + wave=NULL; + wave_len=0; + samp_rate=0; + } + + ~custom_wave_t() + { + if (wave) delete [] wave; + } +}; + + +struct oscillator_t +{ + fixed_t *fm_strength; //this osc gets modulated by osc #i by fm_strength[i]. + fixed_t output; //NOT: osc #i gets modulated by this osc! + int waveform; + fixed_t factor; + fixed_t phase; + + fixed_t tremolo_depth; + int tremolo_lfo; + fixed_t vibrato_depth; + int vibrato_lfo; + + custom_wave_t *custom_wave; + + int n_osc; + + float ksl; + float ksr; + + bool sync; + + oscillator_t(); + oscillator_t& operator=(const oscillator_t &that); +}; + +struct env_settings_t +{ + jack_nframes_t attack; + jack_nframes_t decay; + fixed_t sustain; + jack_nframes_t release; + bool hold; +}; + +struct filter_params_t +{ + bool enabled; + float resonance; + float freqfactor_offset; + float env_amount; + int trem_strength; + int trem_lfo; + env_settings_t env_settings; +}; + + + +struct program_t +{ + unsigned int n_osc; + oscillator_t *osc_settings; + env_settings_t *env_settings; + set<parameter_t> controller_affects[128]; + map< parameter_t, list<term_t> > formula; + int controller[128+1]; + filter_params_t filter_settings; + fixed_t sync_factor; + + pfactor_formula_t pfactor; + + + + program_t(); + ~program_t(); + + void set_param(const parameter_t &p, fixed_t v); + + void cleanup(); + + program_t& operator=(const program_t &that); +}; + + + + +void init_default_program(program_t &p); + +#endif diff --git a/synth/readwave.cpp b/synth/readwave.cpp new file mode 100644 index 0000000..08106ca --- /dev/null +++ b/synth/readwave.cpp @@ -0,0 +1,137 @@ +#include <string> +#include <cstring> + +#include "readwave.h" +#include "util.h" +#include "fixed.h" + +using namespace std; + + +unsigned long int le_dword(unsigned char *b) +{ + return b[0]+256*b[1]+256*256*b[2]+256*256*256*b[3]; +} +unsigned int le_word(unsigned char *b) +{ + return b[0]+256*b[1]; +} + +signed int le_sword(unsigned char *b) +{ + int x=le_word(b); + if (x & (1<<15) ) + return - ((~(x-1))&0xFFFF); + else + return x; +} + +void safe_fread(void* buf, int size, int n, FILE* f) +{ + int x=fread(buf,size,n,f); + + if (x!=n) + throw string("got end-of-file or error while reading from file"); +} + +void read_wave(const char *fn, custom_wave_t *result) +{ + try + { + int fmt=0, chans=0, sr=0, bits=0, bytes=0, filelen=0; + unsigned char buf[100]; + + FILE *f=fopen(fn,"r"); + if (f==NULL) + throw string("could not open file"); + + safe_fread(buf, 1, 12, f); + + if ((memcmp(buf,"RIFF",4)==0) && (memcmp(buf+8,"WAVE",4)==0)) + { + filelen=le_dword(buf+4); + + while (!feof(f)) + { + int chunklen; + + safe_fread(buf,1,8,f); //read chunk name and chunk size + + if (memcmp(buf,"fmt ",4)==0) //it's the fmt-chunk! + { + chunklen=le_dword(buf+4); + safe_fread(buf,1,chunklen,f); + + fmt=le_word(buf); //should be 1 for PCM + chans=le_word(buf+2); //number of channels + sr=le_dword(buf+4); //sampling rate + bits=le_word(buf+14); //bits per sample (8 or 16) + + if (fmt!=1) + throw string("invalid format, expected PCM"); + + if ((bits!=8) && (bits!=16)) + throw string("invalid format, expected 8 or 16 bits"); + + if (chans==0) + throw string("invalid format, n_channels may not be zero"); + + if (chans>=2) + output_note("NOTE: wavefile '"+string(fn)+"' is multichannel, using the left\nchannel and ignoring the rest..."); + + if (sr==0) + throw string("sampling rate may not be zero"); + + } + else if (memcmp(buf,"data",4)==0) //it's the data-chunk! + { + chunklen=le_dword(buf+4); + + if (sr==0) + throw string("found data chunk before the fmt chunk"); + + if (bits==8) + bytes=1; + else if (bits==16) + bytes=2; + + int n_samples=chunklen/(bytes * chans); + + result->samp_rate=fixed_t(sr)<<SCALE; + result->wave_len=n_samples; + result->wave=new fixed_t[n_samples]; + + double sample; + for (int i=0;i<n_samples;i++) + { + if (feof(f)) + throw string("unexpected end-of-file"); + + safe_fread(buf,1,bytes*chans,f); + if (bits==8) + sample=(buf[0]-128)/128.0; + else + sample=le_sword(buf)/double(1<<15); + + result->wave[i]=sample*ONE; + } + break; + } + else //unknown chunk, skip it + { + chunklen=le_dword(buf+4); + safe_fread(buf,1,chunklen,f); + } + } + } + else + { + //not a valid wave file! + throw string("not a valid RIFF-WAVE file"); + } + } + catch (string err) + { + output_warning("ERROR: could not read '"+string(fn)+"': "+err+"\n a default waveform (sine) will be loaded"); + } +} diff --git a/synth/readwave.h b/synth/readwave.h new file mode 100644 index 0000000..48738bf --- /dev/null +++ b/synth/readwave.h @@ -0,0 +1,9 @@ +#ifndef __READWAVE_H__ +#define __READWAVE_H__ + + +#include "programs.h" + +void read_wave(const char *fn, custom_wave_t *result); + +#endif diff --git a/synth/util.cpp b/synth/util.cpp new file mode 100644 index 0000000..ca213fd --- /dev/null +++ b/synth/util.cpp @@ -0,0 +1,206 @@ +#include <iostream> +#include <sstream> + +#include "util.h" +#include "globals.h" + +string IntToStr(int i) +{ + ostringstream s; + s<<i; + return s.str(); +} + +string IntToStrHex(int i) +{ + ostringstream s; + s<<std::hex << i; + return s.str(); +} + +bool isnum(string s) +{ + for (size_t i=0;i<s.length();i++) + if (!isdigit(s[i])) + return false; + + return true; +} + +bool isfloat(string s) +{ + return (s.find_first_not_of("0123456789.")==string::npos); +} + + +string remove_all_spaces(string s) +{ + string result; + + for (size_t i=0; i<s.length(); i++) + if ((s[i]!=' ') && (s[i]!='\t')) + result+=s[i]; + + return result; +} + +string trim_spaces(string s) +{ + string result; + int i; + + for (i=0;i<s.length();i++) + if ((s[i]!=' ') && (s[i]!='\t')) + break; + + if (i!=s.length()) + { + result=s.substr(i); + for (i=result.length()-1;i>=0;i--) + if ((result[i]!=' ') && (result[i]!='\t')) + break; + + if (i>=0) + return result.substr(0,i+1); + else + return ""; + } + else + { + return ""; + } +} + +void output_warning(string s) +{ + cout << s << endl; + if (fatal_warnings) throw string(s); +} + +void output_note(string s) +{ + if (!quiet) cout << s << endl; +} + +void output_verbose(string s) +{ + if (verbose) cout << s << endl; +} + +bool param_needs_index(parameter_enum p) +{ + switch (p) + { + case FILTER_ENABLED: + + case FILTER_ATTACK: + case FILTER_DECAY: + case FILTER_SUSTAIN: + case FILTER_RELEASE: + case FILTER_HOLD: + case FILTER_ENV_AMOUNT: + + case FILTER_OFFSET: + case FILTER_RESONANCE: + + case FILTER_TREMOLO: + case FILTER_TREM_LFO: + + case SYNC_FACTOR: + return false; + + default: return true; + } +} + +parameter_enum param_to_enum(string param) +{ + if (param=="mod") + return MODULATION; + else if (param=="out") + return OUTPUT; + else if (param=="waveform") + return WAVEFORM; + else if (param=="sync") + return SYNC; + else if (param=="factor") + return FACTOR; + else if (param=="trem") + return TREMOLO; + else if (param=="trem_lfo") + return TREM_LFO; + else if (param=="vib") + return VIBRATO; + else if (param=="vib_lfo") + return VIB_LFO; + else if (param=="attack") + return ATTACK; + else if (param=="decay") + return DECAY; + else if (param=="sustain") + return SUSTAIN; + else if (param=="release") + return RELEASE; + else if (param=="hold") + return HOLD; + else if (param=="ksl") + return KSL; + else if (param=="ksr") + return KSR; + else if (param=="filter.enabled") + return FILTER_ENABLED; + else if (param=="filter.env_amount") + return FILTER_ENV_AMOUNT; + else if (param=="filter.attack") + return FILTER_ATTACK; + else if (param=="filter.decay") + return FILTER_DECAY; + else if (param=="filter.sustain") + return FILTER_SUSTAIN; + else if (param=="filter.release") + return FILTER_RELEASE; + else if (param=="filter.hold") + return FILTER_HOLD; + else if (param=="filter.offset") + return FILTER_OFFSET; + else if (param=="filter.resonance") + return FILTER_RESONANCE; + else if (param=="filter.trem") + return FILTER_TREMOLO; + else if (param=="filter.trem_lfo") + return FILTER_TREM_LFO; + else if (param=="sync_factor") + return SYNC_FACTOR; + else + return UNKNOWN; +} + +string extract_var(string s) +{ + size_t p; + p=s.find('='); + if (p!=string::npos) + return s.substr(0,p); + else + return ""; +} + +string extract_val(string s) +{ + size_t p; + p=s.find('='); + if (p!=string::npos) + return s.substr(p+1); + else + return ""; +} + +string fileext(string f) +{ + size_t pos; + pos=f.rfind('.'); + if (pos!=string::npos) + return f.substr(pos+1); + else + return ""; +} diff --git a/synth/util.h b/synth/util.h new file mode 100644 index 0000000..e571c7d --- /dev/null +++ b/synth/util.h @@ -0,0 +1,31 @@ +#ifndef __UTIL_H__ +#define __UTIL_H__ + +#include <string> +#include "programs.h" + +using namespace std; + +string IntToStr(int i); +string IntToStrHex(int i); + +bool isnum(string s); +bool isfloat(string s); + +string remove_all_spaces(string s); +string trim_spaces(string s); + +void output_warning(string s); +void output_note(string s); +void output_verbose(string s); + +bool param_needs_index(parameter_enum p); +parameter_enum param_to_enum(string param); + +string extract_var(string s); +string extract_val(string s); + +string fileext(string f); + + +#endif |