#include "artsmodulessynth.h" #include "artsbuilder.h" #include "stdsynthmodule.h" #include "objectmanager.h" #include "connect.h" #include "flowsystem.h" #include "debug.h" #include "dynamicrequest.h" #include "audiosubsys.h" #include #include #include using namespace Arts; using namespace std; /*-------- instrument mapping ---------*/ class InstrumentMap { protected: struct InstrumentData; class Tokenizer; list instruments; string directory; void loadLine(const string& line); public: struct InstrumentParam; void loadFromList(const string& filename, const vector& list); StructureDesc getInstrument(mcopbyte channel, mcopbyte note, mcopbyte velocity, mcopbyte program, vector*& params); }; struct InstrumentMap::InstrumentParam { string param; Any value; InstrumentParam() { } InstrumentParam(const InstrumentParam& src) : param(src.param), value(src.value) { } InstrumentParam(const string& param, const string& strValue) : param(param) { /* put the string into the any */ value.type = "string"; Buffer b; b.writeString(strValue); b.read(value.value, b.size()); } }; struct InstrumentMap::InstrumentData { struct Range { int minValue, maxValue; Range() : minValue(0), maxValue(0) { } Range(int minValue, int maxValue) : minValue(minValue), maxValue(maxValue) { } bool match(int value) { return (value >= minValue) && (value <= maxValue); } }; Range channel, pitch, program, velocity; vector params; StructureDesc instrument; }; class InstrumentMap::Tokenizer { protected: bool haveToken, haveNextToken; string token, nextToken, input; string::iterator ii; public: Tokenizer(const string& line) : haveToken(false), haveNextToken(false), input(line+"\n"), ii(input.begin()) { /* adding a \n ensures that we will definitely find the last token */ } string getToken() { if(!haveMore()) return ""; if(haveNextToken) { string t = token; haveNextToken = false; token = nextToken; return t; } else { haveToken = false; return token; } } bool haveMore() { if(haveToken) return true; token = ""; while(ii != input.end() && !haveToken) { const char& c = *ii++; if(c == ' ' || c == '\t' || c == '\n') { if(!token.empty()) haveToken = true; } else if(c == '=') /* || c == '-' || c == '+')*/ { if(!token.empty()) { haveNextToken = true; nextToken = c; } else { token = c; } haveToken = true; } else { token += c; } } return haveToken; } }; void InstrumentMap::loadLine(const string& line) { Tokenizer t(line); InstrumentData id; /* default: no filtering */ id.channel = InstrumentData::Range(0,15); id.pitch = id.program = id.velocity = InstrumentData::Range(0,127); string s[3]; int i = 0; bool seenDo = false; bool loadOk = false; if(t.getToken() != "ON") { arts_warning("error in arts-map: lines must start with ON (did start with %s)\n", t.getToken().c_str()); return; } while(t.haveMore()) { const string& token = t.getToken(); if(token == "DO") seenDo = true; else { s[i] = token; if(i == 2) /* evaluate */ { if(s[1] != "=") { arts_warning("error in arts-map: no = operator\n"); return; } if(seenDo) { if(s[0] == "structure") { string filename = s[2]; /* if it's no absolute path, its relative to the map */ if(!filename.empty() && filename[0] != '/') filename = directory + "/" + s[2]; ifstream infile(filename.c_str()); string line; vector strseq; while(getline(infile,line)) strseq.push_back(line); id.instrument.loadFromList(strseq); if(id.instrument.name() != "unknown") { loadOk = true; } else { arts_warning("mapped instrument: " "can't load structure %s",s[2].c_str()); } } else { /* TODO: handle different datatypes */ id.params.push_back(InstrumentParam(s[0], s[2])); } } else { InstrumentData::Range range; range.minValue = atoi(s[2].c_str()); range.maxValue = range.minValue; int i = s[2].find("-",0); if(i != 0) { range.minValue = atoi(s[2].substr(0,i).c_str()); range.maxValue = atoi(s[2].substr(i+1,s[2].size()-(i+1)).c_str()); } if(s[0] == "pitch") id.pitch = range; if(s[0] == "channel") id.channel = range; if(s[0] == "program") id.program = range; if(s[0] == "velocity") id.velocity = range; } i = 0; } else i++; } } if(loadOk) instruments.push_back(id); } void InstrumentMap::loadFromList(const string& filename, const vector& list) { int r = filename.rfind('/'); if(r > 0) directory = filename.substr(0,r); else directory = ""; vector::const_iterator i; instruments.clear(); for(i = list.begin(); i != list.end(); i++) loadLine(*i); } StructureDesc InstrumentMap::getInstrument(mcopbyte channel, mcopbyte note, mcopbyte velocity, mcopbyte program, vector*& params) { list::iterator i; for(i = instruments.begin(); i != instruments.end(); i++) { InstrumentData &id = *i; if(id.channel.match(channel) && id.pitch.match(note) && id.velocity.match(velocity) && id.program.match(program)) { params = &id.params; return id.instrument; } } return StructureDesc::null(); } /*-------instrument mapping end -------*/ static SynthModule get_AMAN_PLAY(Object structure) { Object resultObj = structure._getChild("play"); assert(!resultObj.isNull()); SynthModule result = DynamicCast(resultObj); assert(!result.isNull()); return result; } struct TSNote { MidiPort port; MidiEvent event; TSNote(MidiPort port, const MidiEvent& event) : port(port), event(event) { } }; class AutoMidiRelease : public TimeNotify { public: vector impls; AutoMidiRelease() { Dispatcher::the()->ioManager()->addTimer(10, this); } virtual ~AutoMidiRelease() { Dispatcher::the()->ioManager()->removeTimer(this); } void notifyTime() { vector::iterator i = impls.begin(); while(i != impls.end()) { if(i->terminate()) { MidiReleaseHelper& helper = *i; arts_debug("one voice terminated"); // put the MidiReleaseHelper and the voice into the ObjectCache // (instead of simply freeing it) ObjectCache cache = helper.cache(); SynthModule voice = helper.voice(); get_AMAN_PLAY(voice).stop(); voice.stop(); cache.put(voice,helper.name()); impls.erase(i); return; } else i++; } } } *autoMidiRelease; // cache startup & shutdown static class AutoMidiReleaseStart :public StartupClass { public: void startup() { autoMidiRelease = new AutoMidiRelease(); } void shutdown() { delete autoMidiRelease; } } autoMidiReleaseStart; class MidiReleaseHelper_impl : virtual public MidiReleaseHelper_skel, virtual public StdSynthModule { protected: bool _terminate; SynthModule _voice; ObjectCache _cache; string _name; public: MidiReleaseHelper_impl() { autoMidiRelease->impls.push_back(MidiReleaseHelper::_from_base(_copy())); } ~MidiReleaseHelper_impl() { artsdebug("MidiReleaseHelper: one voice is gone now\n"); } SynthModule voice() { return _voice; } void voice(SynthModule voice) { _voice = voice; } ObjectCache cache() { return _cache; } void cache(ObjectCache cache) { _cache = cache; } string name() { return _name; } void name(const string& name) { _name = name; } bool terminate() { return _terminate; } void streamStart() { _terminate = false; } void calculateBlock(unsigned long /*samples*/) { if(done[0] > 0.5) _terminate = true; } }; REGISTER_IMPLEMENTATION(MidiReleaseHelper_impl); class Synth_MIDI_TEST_impl : virtual public Synth_MIDI_TEST_skel, virtual public StdSynthModule { protected: struct ChannelData { SynthModule voice[128]; string name[128]; float pitchShiftValue; mcopbyte program; ChannelData() { // initialize all voices with NULL objects (no lazy create) for(int i = 0; i < 128; i++) voice[i] = SynthModule::null(); pitchShiftValue = 0.0; program = 0; } } *channelData; /* data for all 16 midi channels */ bool useMap; InstrumentMap map; StructureDesc instrument; StructureBuilder builder; AudioManagerClient amClient; ObjectCache cache; MidiClient client; MidiTimer timer; string _filename; string _busname; string _title; public: Synth_MIDI_TEST self() { return Synth_MIDI_TEST::_from_base(_copy()); } Synth_MIDI_TEST_impl(); ~Synth_MIDI_TEST_impl(); void filename(const string& newname); string filename() { return _filename; } void busname(const string& newname); string busname() { return _busname; } string title() { return _title; } void noteOn(mcopbyte channel, mcopbyte note, mcopbyte velocity); void noteOff(mcopbyte channel, mcopbyte note); void pitchWheel(mcopbyte channel, mcopbyte lsb, mcopbyte msb); float getFrequency(mcopbyte note,mcopbyte channel); void streamStart(); void streamEnd(); TimeStamp time() { return timer.time(); } TimeStamp playTime() { /* * what the user currently hears is exactly latencySec before our * port timeStamp (as this is the size of the audio buffer) */ double latencySec = AudioSubSystem::the()->outputDelay(); TimeStamp t = time(); int sec = int(latencySec); t.sec -= sec; latencySec -= double(sec); t.usec -= int(latencySec * 1000000.0); if (t.usec < 0) { t.usec += 1000000; t.sec -= 1; } arts_assert(t.usec >= 0 && t.usec < 1000000); return t; } void processEvent(const MidiEvent& event) { timer.queueEvent(self(),event); } void processCommand(const MidiCommand& command) { mcopbyte channel = command.status & mcsChannelMask; switch(command.status & mcsCommandMask) { case mcsNoteOn: noteOn(channel,command.data1,command.data2); return; case mcsNoteOff: noteOff(channel,command.data1); return; case mcsPitchWheel: pitchWheel(channel,command.data1,command.data2); return; case mcsProgram: channelData[channel].program = command.data1; return; case mcsParameter: if(command.data1 == mcpAllNotesOff && command.data2 == 0) for(mcopbyte note=0; note<128; note++) noteOff(channel,note); return; } } }; REGISTER_IMPLEMENTATION(Synth_MIDI_TEST_impl); void Synth_MIDI_TEST_impl::busname(const string& newname) { // TODO: _busname = newname; } void Synth_MIDI_TEST_impl::filename(const string& newname) { ifstream infile(newname.c_str()); string line; vector strseq; while(getline(infile,line)) strseq.push_back(line); _filename = newname; /* search extension */ string::const_reverse_iterator i; string extension; bool extensionok = false; for(i = newname.rbegin(); i != newname.rend() && !extensionok; i++) { if(*i == '.') extensionok = true; else extension.insert(extension.begin(), (char)tolower(*i)); } if(extensionok && extension == "arts") { instrument.loadFromList(strseq); _title = "aRts Instrument ("+instrument.name()+")"; useMap = false; } else if(extensionok && extension == "arts-map") { map.loadFromList(newname, strseq); _title = "aRts Instrument (mapped)"; useMap = true; } if(!client.isNull()) client.title(title()); amClient.title(title()); } Synth_MIDI_TEST_impl::Synth_MIDI_TEST_impl() : amClient(amPlay, "aRts Instrument","Synth_MIDI_TEST") { useMap = false; client = MidiClient::null(); timer = SubClass("Arts::AudioMidiTimer"); channelData = new ChannelData[16]; } Synth_MIDI_TEST_impl::~Synth_MIDI_TEST_impl() { delete[] channelData; } void Synth_MIDI_TEST_impl::streamStart() { // register with the midi manager MidiManager manager = Reference("global:Arts_MidiManager"); if(!manager.isNull()) { client = manager.addClient(mcdRecord,mctDestination,title(), "Arts::Synth_MIDI_TEST"); client.addInputPort(self()); } else arts_warning("Synth_MIDI_TEST: no midi manager found - not registered"); } void Synth_MIDI_TEST_impl::streamEnd() { client = MidiClient::null(); } void Synth_MIDI_TEST_impl::noteOn(mcopbyte channel, mcopbyte note, mcopbyte velocity) { if(velocity == 0) { noteOff(channel,note); return; } if(!channelData[channel].voice[note].isNull()) { noteOff(channel,note); arts_info("Synth_MIDI_TEST: duplicate noteOn (mixed channels?)"); } vector *params = 0; if(useMap) { mcopbyte program = channelData[channel].program; StructureDesc sd = map.getInstrument(channel,note,velocity,program,params); if(sd.isNull()) return; instrument = sd; } Object structureObject = cache.get(instrument.name()); if(structureObject.isNull()) { arts_debug("creating new structure"); structureObject = builder.createObject(instrument); SynthModule play; // TODO: allow changing busname! if(!_busname.empty()) { Synth_BUS_UPLINK b; b.busname(_busname); play = b; } else { Synth_AMAN_PLAY a(amClient); play = a; } structureObject._addChild(play,"play"); connect(structureObject,"left",play,"left"); connect(structureObject,"right",play,"right"); } else { arts_debug("used cached structure"); } SynthModule structure = DynamicCast(structureObject); assert(!structure.isNull()); if(params) { vector::iterator pi; for(pi = params->begin(); pi != params->end(); pi++) { DynamicRequest req(structure); req.method("_set_"+pi->param).param(pi->value).invoke(); } } setValue(structure,"frequency",getFrequency(note,channel)); setValue(structure,"velocity",(float)velocity/127.0); setValue(structure,"pressed",1.0); get_AMAN_PLAY(structure).start(); structure.start(); channelData[channel].voice[note] = structure; channelData[channel].name[note] = instrument.name(); } void Synth_MIDI_TEST_impl::noteOff(mcopbyte channel, mcopbyte note) { if(!channelData[channel].voice[note].isNull()) { setValue(channelData[channel].voice[note],"pressed",0.0); MidiReleaseHelper h; h.voice(channelData[channel].voice[note]); h.cache(cache); h.name(channelData[channel].name[note]); connect(channelData[channel].voice[note],"done",h,"done"); h.start(); assert(!h.terminate()); channelData[channel].voice[note] = SynthModule::null(); } } float Synth_MIDI_TEST_impl::getFrequency(mcopbyte note, mcopbyte channel) { /* 2 semitones pitchshift */ return 261.63 * pow(2,((float)(note)+(channelData[channel].pitchShiftValue*2.0))/12.0)/32.0; } void Synth_MIDI_TEST_impl::pitchWheel(mcopbyte channel, mcopbyte lsb, mcopbyte msb) { mcopbyte note; channelData[channel].pitchShiftValue = (float)((lsb + msb*128) - (0x40*128))/8192.0; for(note = 0; note < 128; note++) { if(!channelData[channel].voice[note].isNull()) setValue(channelData[channel].voice[note],"frequency",getFrequency(note,channel)); } }