~ubuntu-branches/ubuntu/hoary/kdemultimedia/hoary

« back to all changes in this revision

Viewing changes to arts/modules/synth_midi_test_impl.cc

  • Committer: Bazaar Package Importer
  • Author(s): Martin Schulze
  • Date: 2003-01-22 15:00:51 UTC
  • Revision ID: james.westby@ubuntu.com-20030122150051-uihwkdoxf15mi1tn
Tags: upstream-2.2.2
ImportĀ upstreamĀ versionĀ 2.2.2

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#include "artsmodules.h"
 
2
#include "artsbuilder.h"
 
3
#include "stdsynthmodule.h"
 
4
#include "objectmanager.h"
 
5
#include "connect.h"
 
6
#include "flowsystem.h"
 
7
#include "debug.h"
 
8
#include <fstream>
 
9
#include <math.h>
 
10
 
 
11
using namespace Arts;
 
12
using namespace std;
 
13
 
 
14
/*-------- instrument mapping ---------*/
 
15
 
 
16
class InstrumentMap {
 
17
protected:
 
18
        struct InstrumentData;
 
19
        class Tokenizer;
 
20
        list<InstrumentData> instruments;
 
21
        string directory;
 
22
        void loadLine(const string& line);
 
23
 
 
24
public:
 
25
        void loadFromList(const string& filename, const vector<string>& list);
 
26
        StructureDesc getInstrument(mcopbyte channel, mcopbyte note,
 
27
                                                                                mcopbyte velocity, mcopbyte program);
 
28
};
 
29
 
 
30
struct InstrumentMap::InstrumentData 
 
31
{
 
32
        struct Range
 
33
        {
 
34
                int minValue, maxValue;
 
35
                Range() : minValue(0), maxValue(0)
 
36
                {
 
37
                }
 
38
                Range(int minValue, int maxValue)
 
39
                        : minValue(minValue), maxValue(maxValue)
 
40
                {
 
41
                }
 
42
                bool match(int value)
 
43
                {
 
44
                        return (value >= minValue) && (value <= maxValue);
 
45
                }
 
46
        };
 
47
        Range channel, pitch, program, velocity;
 
48
        StructureDesc instrument;
 
49
};
 
50
 
 
51
class InstrumentMap::Tokenizer {
 
52
protected:
 
53
        bool haveToken, haveNextToken;
 
54
        string token, nextToken, input;
 
55
        string::iterator ii;
 
56
public:
 
57
        Tokenizer(const string& line)
 
58
                : haveToken(false), haveNextToken(false),
 
59
                  input(line+"\n"), ii(input.begin())
 
60
        {
 
61
                /* adding a \n ensures that we will definitely find the last token */
 
62
        }
 
63
        string getToken()
 
64
        {
 
65
                if(!haveMore())
 
66
                        return "";
 
67
 
 
68
                if(haveNextToken)
 
69
                {
 
70
                        string t = token;
 
71
                        haveNextToken = false;
 
72
                        token = nextToken;
 
73
                        return t;
 
74
                }
 
75
                else
 
76
                {
 
77
                        haveToken = false;
 
78
                        return token;
 
79
                }
 
80
        }
 
81
        bool haveMore()
 
82
        {
 
83
                if(haveToken)
 
84
                        return true;
 
85
 
 
86
                token = "";
 
87
                while(ii != input.end() && !haveToken)
 
88
                {
 
89
                        const char& c = *ii++;
 
90
                
 
91
                        if(c == ' ' || c == '\t' || c == '\n')
 
92
                        {
 
93
                                if(token != "") haveToken = true;
 
94
                        }
 
95
                        else if(c == '=') /* || c == '-' || c == '+')*/
 
96
                        {
 
97
                                if(token != "")
 
98
                                {
 
99
                                        haveNextToken = true;
 
100
                                        nextToken = c;
 
101
                                }
 
102
                                else
 
103
                                {
 
104
                                        token = c;
 
105
                                }
 
106
                                haveToken = true;
 
107
                        }
 
108
                        else
 
109
                        {
 
110
                                token += c;
 
111
                        }
 
112
                }
 
113
                return haveToken;
 
114
        }
 
115
};
 
116
 
 
117
void InstrumentMap::loadLine(const string& line)
 
118
{
 
119
        Tokenizer t(line);
 
120
        InstrumentData id;
 
121
        /* default: no filtering */
 
122
        id.channel = InstrumentData::Range(0,15);
 
123
        id.pitch = id.program = id.velocity = InstrumentData::Range(0,127);
 
124
 
 
125
        string s[3];
 
126
        int i = 0;
 
127
        bool seenDo = false;
 
128
        bool loadOk = false;
 
129
 
 
130
        if(t.getToken() != "ON")
 
131
        {
 
132
                arts_warning("error in arts-map: lines must start with ON (did start with %s)\n", t.getToken().c_str());
 
133
                return;
 
134
        }
 
135
 
 
136
        while(t.haveMore())
 
137
        {
 
138
                const string& token = t.getToken();
 
139
 
 
140
                if(token == "DO")
 
141
                        seenDo = true;
 
142
                else
 
143
                {
 
144
                        s[i] = token;
 
145
                        if(i == 2)      /* evaluate */
 
146
                        {
 
147
                                if(s[1] != "=")
 
148
                                {
 
149
                                        arts_warning("error in arts-map: no = operator\n");
 
150
                                        return;
 
151
                                }
 
152
 
 
153
                                if(seenDo)
 
154
                                {
 
155
                                        if(s[0] == "structure")
 
156
                                        {
 
157
                                                string filename = s[2];
 
158
 
 
159
                                                /* if it's no absolute path, its relative to the map */
 
160
                                                if(!filename.empty() && filename[0] != '/')
 
161
                                                        filename = directory + "/" + s[2];
 
162
 
 
163
                                                ifstream infile(filename.c_str());
 
164
                                                string line;
 
165
                                                vector<string> strseq;
 
166
 
 
167
                                                while(getline(infile,line))
 
168
                                                        strseq.push_back(line);
 
169
 
 
170
                                                id.instrument.loadFromList(strseq);
 
171
                                                if(id.instrument.name() != "unknown")
 
172
                                                {
 
173
                                                        loadOk = true;
 
174
                                                }
 
175
                                                else
 
176
                                                {
 
177
                                                        arts_warning("mapped instrument: "
 
178
                                                                        "can't load structure %s",s[2].c_str());
 
179
                                                }
 
180
                                        }
 
181
                                }
 
182
                                else
 
183
                                {
 
184
                                        InstrumentData::Range range;
 
185
                                        range.minValue = atoi(s[2].c_str());
 
186
                                        range.maxValue = range.minValue;
 
187
                                        int i = s[2].find("-",0);
 
188
                                        if(i != 0)
 
189
                                        {
 
190
                                                range.minValue = atoi(s[2].substr(0,i).c_str());
 
191
                                                range.maxValue =
 
192
                                                        atoi(s[2].substr(i+1,s[2].size()-(i+1)).c_str());
 
193
                                        }
 
194
                                        if(s[0] == "pitch")             id.pitch = range;
 
195
                                        if(s[0] == "channel")   id.channel = range;
 
196
                                        if(s[0] == "program")   id.program = range;
 
197
                                        if(s[0] == "velocity")  id.velocity = range;
 
198
                                }
 
199
                                i = 0;
 
200
                        }
 
201
                        else i++;
 
202
                }
 
203
        }
 
204
        if(loadOk) instruments.push_back(id);
 
205
}
 
206
 
 
207
void InstrumentMap::loadFromList(const string& filename,
 
208
                                                                        const vector<string>& list)
 
209
{
 
210
        int r = filename.rfind('/');
 
211
        if(r > 0)
 
212
                directory = filename.substr(0,r);
 
213
        else
 
214
                directory = "";
 
215
 
 
216
        vector<string>::const_iterator i;
 
217
        instruments.clear();
 
218
        for(i = list.begin(); i != list.end(); i++) loadLine(*i);
 
219
}
 
220
 
 
221
StructureDesc InstrumentMap::getInstrument(mcopbyte channel, mcopbyte note,
 
222
                                                                                        mcopbyte velocity, mcopbyte program)
 
223
{
 
224
        list<InstrumentData>::iterator i;
 
225
        for(i = instruments.begin(); i != instruments.end(); i++)
 
226
        {
 
227
                InstrumentData &id = *i;
 
228
 
 
229
                if(id.channel.match(channel) && id.pitch.match(note) &&
 
230
                   id.velocity.match(velocity) && id.program.match(program))
 
231
                {
 
232
                        return id.instrument;
 
233
                }
 
234
        }
 
235
 
 
236
        return StructureDesc::null();
 
237
}
 
238
 
 
239
 
 
240
/*-------instrument mapping end -------*/
 
241
 
 
242
static SynthModule get_AMAN_PLAY(Object structure)
 
243
{
 
244
        Object resultObj = structure._getChild("play");
 
245
        assert(!resultObj.isNull());
 
246
 
 
247
        SynthModule result = DynamicCast(resultObj);
 
248
        assert(!result.isNull());
 
249
 
 
250
        return result;
 
251
}
 
252
 
 
253
struct TSNote {
 
254
        MidiPort port;
 
255
        MidiEvent event;
 
256
        TSNote(MidiPort port, const MidiEvent& event) :
 
257
                port(port), event(event)
 
258
        {
 
259
        }
 
260
};
 
261
 
 
262
class AutoMidiRelease : public TimeNotify {
 
263
public:
 
264
        vector<MidiReleaseHelper> impls;
 
265
        AutoMidiRelease()
 
266
        {
 
267
                Dispatcher::the()->ioManager()->addTimer(10, this);
 
268
        }
 
269
        ~AutoMidiRelease()
 
270
        {
 
271
                Dispatcher::the()->ioManager()->removeTimer(this);
 
272
        }
 
273
        void notifyTime()
 
274
        {
 
275
                vector<MidiReleaseHelper>::iterator i = impls.begin();
 
276
                while(i != impls.end())
 
277
                {
 
278
                        if(i->terminate())
 
279
                        {
 
280
                                MidiReleaseHelper& helper = *i;
 
281
 
 
282
                                arts_debug("one voice terminated");
 
283
                                // put the MidiReleaseHelper and the voice into the ObjectCache
 
284
                                // (instead of simply freeing it)
 
285
                                ObjectCache cache = helper.cache();
 
286
                                SynthModule voice = helper.voice();
 
287
                                get_AMAN_PLAY(voice).stop();
 
288
                                voice.stop();
 
289
                                cache.put(voice,helper.name());
 
290
                                impls.erase(i);
 
291
                                return;
 
292
                        } else i++;
 
293
                }
 
294
        }
 
295
}       *autoMidiRelease;
 
296
 
 
297
// cache startup & shutdown
 
298
static class AutoMidiReleaseStart :public StartupClass
 
299
{
 
300
public:
 
301
        void startup() { autoMidiRelease = new AutoMidiRelease(); }
 
302
        void shutdown() { delete autoMidiRelease; }
 
303
} autoMidiReleaseStart;
 
304
 
 
305
class MidiReleaseHelper_impl : virtual public MidiReleaseHelper_skel,
 
306
                                                           virtual public StdSynthModule
 
307
{
 
308
protected:
 
309
        bool _terminate;
 
310
        SynthModule _voice;
 
311
        ObjectCache _cache;
 
312
        string _name;
 
313
 
 
314
public:
 
315
        MidiReleaseHelper_impl()
 
316
        {
 
317
                autoMidiRelease->impls.push_back(MidiReleaseHelper::_from_base(_copy()));
 
318
        }
 
319
        ~MidiReleaseHelper_impl() {
 
320
                artsdebug("MidiReleaseHelper: one voice is gone now\n");
 
321
        }
 
322
 
 
323
 
 
324
        SynthModule voice() { return _voice; }
 
325
        void voice(SynthModule voice) { _voice = voice; }
 
326
 
 
327
        ObjectCache cache() { return _cache; }
 
328
        void cache(ObjectCache cache) { _cache = cache; }
 
329
 
 
330
        string name() { return _name; }
 
331
        void name(const string& name) { _name = name; }
 
332
 
 
333
        bool terminate() { return _terminate; }
 
334
        void streamStart() { _terminate = false; }
 
335
 
 
336
        void calculateBlock(unsigned long samples)
 
337
        {
 
338
                if(done[0] > 0.5)
 
339
                        _terminate = true;
 
340
        }
 
341
};
 
342
REGISTER_IMPLEMENTATION(MidiReleaseHelper_impl);
 
343
 
 
344
class Synth_MIDI_TEST_impl : virtual public Synth_MIDI_TEST_skel,
 
345
                                                         virtual public StdSynthModule {
 
346
protected:
 
347
        struct ChannelData {
 
348
                SynthModule voice[128];
 
349
                string name[128];
 
350
                float pitchShiftValue;
 
351
                mcopbyte program;
 
352
                ChannelData() {
 
353
                        // initialize all voices with NULL objects (no lazy create)
 
354
                        for(int i = 0; i < 128; i++) voice[i] = SynthModule::null();
 
355
 
 
356
                        pitchShiftValue = 0.0;
 
357
                        program = 0;
 
358
                }
 
359
        }       *channelData; /* data for all 16 midi channels */
 
360
 
 
361
        bool useMap;
 
362
        InstrumentMap map;
 
363
        StructureDesc instrument;
 
364
        StructureBuilder builder;
 
365
        AudioManagerClient amClient;
 
366
        ObjectCache cache;
 
367
        MidiClient client;
 
368
        MidiTimer timer;
 
369
 
 
370
        string _filename;
 
371
        string _title;
 
372
public:
 
373
        Synth_MIDI_TEST self() { return Synth_MIDI_TEST::_from_base(_copy()); }
 
374
 
 
375
        Synth_MIDI_TEST_impl();
 
376
        ~Synth_MIDI_TEST_impl();
 
377
 
 
378
        void filename(const string& newname);
 
379
        string filename()
 
380
        {
 
381
                return _filename;
 
382
        }
 
383
        string title()
 
384
        {
 
385
                return _title;
 
386
        }
 
387
        void noteOn(mcopbyte channel, mcopbyte note, mcopbyte velocity);
 
388
        void noteOff(mcopbyte channel, mcopbyte note);
 
389
        void pitchWheel(mcopbyte channel, mcopbyte lsb, mcopbyte msb);
 
390
 
 
391
        float getFrequency(mcopbyte note,mcopbyte channel);
 
392
 
 
393
        void streamStart();
 
394
        void streamEnd();
 
395
 
 
396
        TimeStamp time()
 
397
        {
 
398
                return timer.time();
 
399
        }
 
400
        void processEvent(const MidiEvent& event)
 
401
        {
 
402
                timer.queueEvent(self(),event);
 
403
        }
 
404
        void processCommand(const MidiCommand& command)
 
405
        {
 
406
                mcopbyte channel = command.status & mcsChannelMask;
 
407
 
 
408
                switch(command.status & mcsCommandMask)
 
409
                {
 
410
                        case mcsNoteOn: noteOn(channel,command.data1,command.data2);
 
411
                                return;
 
412
                        case mcsNoteOff: noteOff(channel,command.data1);
 
413
                                return;
 
414
                        case mcsPitchWheel: pitchWheel(channel,command.data1,command.data2);
 
415
                                return;
 
416
                        case mcsProgram: channelData[channel].program = command.data1;
 
417
                                return;
 
418
                        case mcsParameter:
 
419
                                        if(command.data1 == mcpAllNotesOff && command.data2 == 0)
 
420
                                                for(mcopbyte note=0; note<128; note++)
 
421
                                                        noteOff(channel,note);
 
422
                                return;
 
423
                }
 
424
        }
 
425
};
 
426
REGISTER_IMPLEMENTATION(Synth_MIDI_TEST_impl);
 
427
 
 
428
 
 
429
void Synth_MIDI_TEST_impl::filename(const string& newname)
 
430
{
 
431
        ifstream infile(newname.c_str());
 
432
        string line;
 
433
        vector<string> strseq;
 
434
 
 
435
        while(getline(infile,line))
 
436
                strseq.push_back(line);
 
437
 
 
438
        _filename = newname;
 
439
 
 
440
/* search extension */
 
441
    string::const_reverse_iterator i;
 
442
        string extension;
 
443
        bool extensionok = false;
 
444
 
 
445
    for(i = newname.rbegin(); i != newname.rend() && !extensionok; i++)
 
446
    {
 
447
        if(*i == '.')
 
448
            extensionok = true;
 
449
        else
 
450
            extension.insert(extension.begin(), (char)tolower(*i));
 
451
    }
 
452
 
 
453
        if(extensionok && extension == "arts")
 
454
        {
 
455
                instrument.loadFromList(strseq);
 
456
                _title = "aRts Instrument ("+instrument.name()+")";
 
457
                useMap = false;
 
458
        }
 
459
        else if(extensionok && extension == "arts-map")
 
460
        {
 
461
                map.loadFromList(newname, strseq);
 
462
                _title = "aRts Instrument (mapped)";
 
463
                useMap = true;
 
464
        }
 
465
 
 
466
        if(!client.isNull())
 
467
                client.title(title());
 
468
        amClient.title(title());
 
469
}
 
470
 
 
471
Synth_MIDI_TEST_impl::Synth_MIDI_TEST_impl()
 
472
        : amClient(amPlay, "aRts Instrument","Synth_MIDI_TEST")
 
473
{
 
474
        useMap = false;
 
475
        client = MidiClient::null();
 
476
        timer = SubClass("Arts::AudioMidiTimer");
 
477
        channelData = new ChannelData[16];
 
478
}
 
479
 
 
480
Synth_MIDI_TEST_impl::~Synth_MIDI_TEST_impl()
 
481
{
 
482
        delete[] channelData;
 
483
}
 
484
 
 
485
void Synth_MIDI_TEST_impl::streamStart()
 
486
{
 
487
        // register with the midi manager
 
488
        MidiManager manager = Reference("global:Arts_MidiManager");
 
489
        if(!manager.isNull())
 
490
        {
 
491
                client = manager.addClient(mcdRecord,mctDestination,title(),
 
492
                                                                                "Arts::Synth_MIDI_TEST");
 
493
                client.addInputPort(self());
 
494
        }
 
495
        else
 
496
                arts_warning("Synth_MIDI_TEST: no midi manager found - not registered");
 
497
}
 
498
 
 
499
void Synth_MIDI_TEST_impl::streamEnd()
 
500
{
 
501
        client = MidiClient::null();
 
502
}
 
503
 
 
504
void Synth_MIDI_TEST_impl::noteOn(mcopbyte channel, mcopbyte note,
 
505
                                                                                        mcopbyte velocity)
 
506
{
 
507
        if(velocity == 0)
 
508
        {
 
509
                noteOff(channel,note);
 
510
                return;
 
511
        }
 
512
        if(!channelData[channel].voice[note].isNull())
 
513
        {
 
514
                noteOff(channel,note);
 
515
                arts_info("Synth_MIDI_TEST: duplicate noteOn (mixed channels?)");
 
516
        }
 
517
 
 
518
        if(useMap)
 
519
        {
 
520
                mcopbyte program = channelData[channel].program;
 
521
                StructureDesc sd = map.getInstrument(channel,note,velocity,program);
 
522
                if(sd.isNull()) return;
 
523
                instrument = sd;
 
524
        }
 
525
 
 
526
        Object structureObject = cache.get(instrument.name());
 
527
        if(structureObject.isNull())
 
528
        {
 
529
                arts_debug("creating new structure");
 
530
                structureObject = builder.createObject(instrument);
 
531
 
 
532
                Synth_AMAN_PLAY play(amClient);
 
533
                structureObject._addChild(play,"play");
 
534
                connect(structureObject,"left",play,"left");
 
535
                connect(structureObject,"right",play,"right");
 
536
        }
 
537
        else
 
538
        {
 
539
                arts_debug("used cached structure");
 
540
        }
 
541
 
 
542
        SynthModule structure = DynamicCast(structureObject);
 
543
        assert(!structure.isNull());
 
544
 
 
545
        setValue(structure,"frequency",getFrequency(note,channel));
 
546
        setValue(structure,"velocity",(float)velocity/127.0);
 
547
        setValue(structure,"pressed",1.0);
 
548
 
 
549
        get_AMAN_PLAY(structure).start();
 
550
        structure.start();
 
551
 
 
552
        channelData[channel].voice[note] = structure;
 
553
        channelData[channel].name[note] = instrument.name();
 
554
}
 
555
 
 
556
void Synth_MIDI_TEST_impl::noteOff(mcopbyte channel, mcopbyte note)
 
557
{
 
558
        if(!channelData[channel].voice[note].isNull())
 
559
        {
 
560
                setValue(channelData[channel].voice[note],"pressed",0.0);
 
561
 
 
562
                MidiReleaseHelper h;
 
563
 
 
564
                h.voice(channelData[channel].voice[note]);
 
565
                h.cache(cache);
 
566
                h.name(channelData[channel].name[note]);
 
567
 
 
568
                connect(channelData[channel].voice[note],"done",h,"done");
 
569
                h.start();
 
570
                assert(!h.terminate());
 
571
                channelData[channel].voice[note] = SynthModule::null();
 
572
        }
 
573
}
 
574
 
 
575
float Synth_MIDI_TEST_impl::getFrequency(mcopbyte note, mcopbyte channel)
 
576
{
 
577
        /* 2 semitones pitchshift */
 
578
        return 261.63 * pow(2,((float)(note)+(channelData[channel].pitchShiftValue*2.0))/12.0)/32.0;
 
579
}
 
580
 
 
581
void Synth_MIDI_TEST_impl::pitchWheel(mcopbyte channel,
 
582
                                                                                mcopbyte lsb, mcopbyte msb)
 
583
{
 
584
        mcopbyte note;
 
585
 
 
586
        channelData[channel].pitchShiftValue =
 
587
                (float)((lsb + msb*128) - (0x40*128))/8192.0;
 
588
 
 
589
        for(note = 0; note < 128; note++)
 
590
        {
 
591
                if(!channelData[channel].voice[note].isNull())
 
592
                        setValue(channelData[channel].voice[note],"frequency",getFrequency(note,channel));
 
593
        }
 
594
}