You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdemultimedia/arts/modules/synth/synth_midi_test_impl.cpp

693 lines
15 KiB

#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 <fstream>
#include <math.h>
#include <stdlib.h>
using namespace Arts;
using namespace std;
/*-------- instrument mapping ---------*/
class InstrumentMap {
protected:
struct InstrumentData;
class Tokenizer;
list<InstrumentData> instruments;
string directory;
void loadLine(const string& line);
public:
struct InstrumentParam;
void loadFromList(const string& filename, const vector<string>& list);
StructureDesc getInstrument(mcopbyte channel, mcopbyte note,
mcopbyte velocity, mcopbyte program,
vector<InstrumentParam>*& 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<InstrumentParam> 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<string> 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<string>& list)
{
int r = filename.rfind('/');
if(r > 0)
directory = filename.substr(0,r);
else
directory = "";
vector<string>::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<InstrumentParam>*& params)
{
list<InstrumentData>::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<MidiReleaseHelper> impls;
AutoMidiRelease()
{
Dispatcher::the()->ioManager()->addTimer(10, this);
}
virtual ~AutoMidiRelease()
{
Dispatcher::the()->ioManager()->removeTimer(this);
}
void notifyTime()
{
vector<MidiReleaseHelper>::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<string> 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<InstrumentMap::InstrumentParam> *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<InstrumentMap::InstrumentParam>::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));
}
}