/[svn]/linuxsampler/trunk/src/engines/gig/Engine.cpp
ViewVC logotype

Annotation of /linuxsampler/trunk/src/engines/gig/Engine.cpp

Parent Directory Parent Directory | Revision Log Revision Log


Revision 250 - (hide annotations) (download)
Mon Sep 20 00:31:13 2004 UTC (19 years, 6 months ago) by schoenebeck
File size: 46019 byte(s)
* added first two experimental voice stealing algorithms ('oldestkey' -
which just steals the oldest voice on the oldest key and 'keymask' - which
tries to pick the oldest voice on the same key where the new voice should
be spawned, if it fails it behaves like 'oldestkey'), the desired algorithm
can be selected at compile time (see Engine.h) will be configurable via
LSCP soon though

1 schoenebeck 53 /***************************************************************************
2     * *
3     * LinuxSampler - modular, streaming capable sampler *
4     * *
5 schoenebeck 56 * Copyright (C) 2003, 2004 by Benno Senoner and Christian Schoenebeck *
6 schoenebeck 53 * *
7     * This program is free software; you can redistribute it and/or modify *
8     * it under the terms of the GNU General Public License as published by *
9     * the Free Software Foundation; either version 2 of the License, or *
10     * (at your option) any later version. *
11     * *
12     * This program is distributed in the hope that it will be useful, *
13     * but WITHOUT ANY WARRANTY; without even the implied warranty of *
14     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
15     * GNU General Public License for more details. *
16     * *
17     * You should have received a copy of the GNU General Public License *
18     * along with this program; if not, write to the Free Software *
19     * Foundation, Inc., 59 Temple Place, Suite 330, Boston, *
20     * MA 02111-1307 USA *
21     ***************************************************************************/
22    
23     #include <sstream>
24     #include "DiskThread.h"
25     #include "Voice.h"
26    
27     #include "Engine.h"
28    
29     namespace LinuxSampler { namespace gig {
30    
31     InstrumentResourceManager Engine::Instruments;
32    
33     Engine::Engine() {
34     pRIFF = NULL;
35     pGig = NULL;
36     pInstrument = NULL;
37     pAudioOutputDevice = NULL;
38     pDiskThread = NULL;
39     pEventGenerator = NULL;
40 schoenebeck 244 pSysexBuffer = new RingBuffer<uint8_t>(SYSEX_BUFFER_SIZE, 0);
41     pEventQueue = new RingBuffer<Event>(MAX_EVENTS_PER_FRAGMENT, 0);
42 schoenebeck 53 pEventPool = new RTELMemoryPool<Event>(MAX_EVENTS_PER_FRAGMENT);
43     pVoicePool = new RTELMemoryPool<Voice>(MAX_AUDIO_VOICES);
44     pActiveKeys = new RTELMemoryPool<uint>(128);
45 schoenebeck 250 pVoiceStealingQueue = new RTEList<Event>(pEventPool);
46 schoenebeck 53 pEvents = new RTEList<Event>(pEventPool);
47     pCCEvents = new RTEList<Event>(pEventPool);
48     for (uint i = 0; i < Event::destination_count; i++) {
49     pSynthesisEvents[i] = new RTEList<Event>(pEventPool);
50     }
51     for (uint i = 0; i < 128; i++) {
52 schoenebeck 242 pMIDIKeyInfo[i].pActiveVoices = new RTEList<Voice>(pVoicePool);
53     pMIDIKeyInfo[i].KeyPressed = false;
54     pMIDIKeyInfo[i].Active = false;
55     pMIDIKeyInfo[i].ReleaseTrigger = false;
56     pMIDIKeyInfo[i].pSelf = NULL;
57     pMIDIKeyInfo[i].pEvents = new RTEList<Event>(pEventPool);
58 schoenebeck 53 }
59     for (Voice* pVoice = pVoicePool->alloc(); pVoice; pVoice = pVoicePool->alloc()) {
60     pVoice->SetEngine(this);
61     }
62     pVoicePool->clear();
63    
64     pSynthesisParameters[0] = NULL; // we allocate when an audio device is connected
65 schoenebeck 80 pBasicFilterParameters = NULL;
66     pMainFilterParameters = NULL;
67 schoenebeck 123
68 senkov 112 InstrumentIdx = -1;
69 capela 133 InstrumentStat = -1;
70 schoenebeck 53
71 schoenebeck 225 AudioDeviceChannelLeft = -1;
72     AudioDeviceChannelRight = -1;
73    
74 schoenebeck 53 ResetInternal();
75     }
76    
77     Engine::~Engine() {
78     if (pDiskThread) {
79     pDiskThread->StopThread();
80     delete pDiskThread;
81     }
82     if (pGig) delete pGig;
83     if (pRIFF) delete pRIFF;
84     for (uint i = 0; i < 128; i++) {
85     if (pMIDIKeyInfo[i].pActiveVoices) delete pMIDIKeyInfo[i].pActiveVoices;
86     if (pMIDIKeyInfo[i].pEvents) delete pMIDIKeyInfo[i].pEvents;
87     }
88     for (uint i = 0; i < Event::destination_count; i++) {
89     if (pSynthesisEvents[i]) delete pSynthesisEvents[i];
90     }
91     delete[] pSynthesisEvents;
92     if (pEvents) delete pEvents;
93     if (pCCEvents) delete pCCEvents;
94     if (pEventQueue) delete pEventQueue;
95     if (pEventPool) delete pEventPool;
96     if (pVoicePool) delete pVoicePool;
97     if (pActiveKeys) delete pActiveKeys;
98 schoenebeck 244 if (pSysexBuffer) delete pSysexBuffer;
99 schoenebeck 53 if (pEventGenerator) delete pEventGenerator;
100 schoenebeck 80 if (pMainFilterParameters) delete[] pMainFilterParameters;
101     if (pBasicFilterParameters) delete[] pBasicFilterParameters;
102 schoenebeck 53 if (pSynthesisParameters[0]) delete[] pSynthesisParameters[0];
103 schoenebeck 250 if (pVoiceStealingQueue) delete pVoiceStealingQueue;
104 schoenebeck 53 }
105    
106     void Engine::Enable() {
107     dmsg(3,("gig::Engine: enabling\n"));
108     EngineDisabled.PushAndUnlock(false, 2); // set condition object 'EngineDisabled' to false (wait max. 2s)
109 schoenebeck 64 dmsg(3,("gig::Engine: enabled (val=%d)\n", EngineDisabled.GetUnsafe()));
110 schoenebeck 53 }
111    
112     void Engine::Disable() {
113     dmsg(3,("gig::Engine: disabling\n"));
114     bool* pWasDisabled = EngineDisabled.PushAndUnlock(true, 2); // wait max. 2s
115     if (!pWasDisabled) dmsg(3,("gig::Engine warning: Timeout waiting to disable engine.\n"));
116     }
117    
118     void Engine::DisableAndLock() {
119     dmsg(3,("gig::Engine: disabling\n"));
120     bool* pWasDisabled = EngineDisabled.Push(true, 2); // wait max. 2s
121     if (!pWasDisabled) dmsg(3,("gig::Engine warning: Timeout waiting to disable engine.\n"));
122     }
123    
124     /**
125     * Reset all voices and disk thread and clear input event queue and all
126     * control and status variables.
127     */
128     void Engine::Reset() {
129     DisableAndLock();
130    
131     //if (pAudioOutputDevice->IsPlaying()) { // if already running
132     /*
133     // signal audio thread not to enter render part anymore
134     SuspensionRequested = true;
135     // sleep until wakened by audio thread
136     pthread_mutex_lock(&__render_state_mutex);
137     pthread_cond_wait(&__render_exit_condition, &__render_state_mutex);
138     pthread_mutex_unlock(&__render_state_mutex);
139     */
140     //}
141    
142     //if (wasplaying) pAudioOutputDevice->Stop();
143    
144     ResetInternal();
145    
146     // signal audio thread to continue with rendering
147     //SuspensionRequested = false;
148     Enable();
149     }
150    
151     /**
152     * Reset all voices and disk thread and clear input event queue and all
153     * control and status variables. This method is not thread safe!
154     */
155     void Engine::ResetInternal() {
156     Pitch = 0;
157     SustainPedal = false;
158     ActiveVoiceCount = 0;
159     ActiveVoiceCountMax = 0;
160 schoenebeck 225 GlobalVolume = 1.0;
161 schoenebeck 53
162 schoenebeck 250 // reset voice stealing parameters
163     pLastStolenVoice = NULL;
164     puiLastStolenKey = NULL;
165     pVoiceStealingQueue->clear();
166    
167 schoenebeck 244 // reset to normal chromatic scale (means equal temper)
168     memset(&ScaleTuning[0], 0x00, 12);
169    
170 schoenebeck 53 // set all MIDI controller values to zero
171     memset(ControllerTable, 0x00, 128);
172    
173     // reset key info
174     for (uint i = 0; i < 128; i++) {
175     pMIDIKeyInfo[i].pActiveVoices->clear();
176     pMIDIKeyInfo[i].pEvents->clear();
177 schoenebeck 242 pMIDIKeyInfo[i].KeyPressed = false;
178     pMIDIKeyInfo[i].Active = false;
179     pMIDIKeyInfo[i].ReleaseTrigger = false;
180     pMIDIKeyInfo[i].pSelf = NULL;
181 schoenebeck 53 }
182    
183 schoenebeck 239 // reset all key groups
184     map<uint,uint*>::iterator iter = ActiveKeyGroups.begin();
185     for (; iter != ActiveKeyGroups.end(); iter++) iter->second = NULL;
186    
187 schoenebeck 53 // reset all voices
188     for (Voice* pVoice = pVoicePool->alloc(); pVoice; pVoice = pVoicePool->alloc()) {
189     pVoice->Reset();
190     }
191     pVoicePool->clear();
192    
193     // free all active keys
194     pActiveKeys->clear();
195    
196     // reset disk thread
197     if (pDiskThread) pDiskThread->Reset();
198    
199     // delete all input events
200     pEventQueue->init();
201     }
202    
203     /**
204     * Load an instrument from a .gig file.
205     *
206     * @param FileName - file name of the Gigasampler instrument file
207     * @param Instrument - index of the instrument in the .gig file
208     * @throws LinuxSamplerException on error
209     * @returns detailed description of the method call result
210     */
211     void Engine::LoadInstrument(const char* FileName, uint Instrument) {
212    
213     DisableAndLock();
214    
215     ResetInternal(); // reset engine
216    
217     // free old instrument
218     if (pInstrument) {
219     // give old instrument back to instrument manager
220     Instruments.HandBack(pInstrument, this);
221     }
222    
223 capela 133 InstrumentFile = FileName;
224     InstrumentIdx = Instrument;
225     InstrumentStat = 0;
226 senkov 112
227 schoenebeck 239 // delete all key groups
228     ActiveKeyGroups.clear();
229    
230 schoenebeck 53 // request gig instrument from instrument manager
231     try {
232     instrument_id_t instrid;
233     instrid.FileName = FileName;
234     instrid.iInstrument = Instrument;
235     pInstrument = Instruments.Borrow(instrid, this);
236     if (!pInstrument) {
237 capela 133 InstrumentStat = -1;
238 schoenebeck 53 dmsg(1,("no instrument loaded!!!\n"));
239     exit(EXIT_FAILURE);
240     }
241     }
242     catch (RIFF::Exception e) {
243 capela 133 InstrumentStat = -2;
244 schoenebeck 53 String msg = "gig::Engine error: Failed to load instrument, cause: " + e.Message;
245     throw LinuxSamplerException(msg);
246     }
247     catch (InstrumentResourceManagerException e) {
248 capela 133 InstrumentStat = -3;
249 schoenebeck 53 String msg = "gig::Engine error: Failed to load instrument, cause: " + e.Message();
250     throw LinuxSamplerException(msg);
251     }
252     catch (...) {
253 capela 133 InstrumentStat = -4;
254 schoenebeck 53 throw LinuxSamplerException("gig::Engine error: Failed to load instrument, cause: Unknown exception while trying to parse gig file.");
255     }
256    
257 schoenebeck 239 // rebuild ActiveKeyGroups map with key groups of current instrument
258     for (::gig::Region* pRegion = pInstrument->GetFirstRegion(); pRegion; pRegion = pInstrument->GetNextRegion())
259     if (pRegion->KeyGroup) ActiveKeyGroups[pRegion->KeyGroup] = NULL;
260    
261 capela 133 InstrumentStat = 100;
262 senkov 112
263 schoenebeck 53 // inform audio driver for the need of two channels
264     try {
265     if (pAudioOutputDevice) pAudioOutputDevice->AcquireChannels(2); // gig Engine only stereo
266     }
267     catch (AudioOutputException e) {
268     String msg = "Audio output device unable to provide 2 audio channels, cause: " + e.Message();
269     throw LinuxSamplerException(msg);
270     }
271    
272     Enable();
273     }
274    
275     /**
276     * Will be called by the InstrumentResourceManager when the instrument
277     * we are currently using in this engine is going to be updated, so we
278     * can stop playback before that happens.
279     */
280     void Engine::ResourceToBeUpdated(::gig::Instrument* pResource, void*& pUpdateArg) {
281     dmsg(3,("gig::Engine: Received instrument update message.\n"));
282     DisableAndLock();
283     ResetInternal();
284     this->pInstrument = NULL;
285     }
286    
287     /**
288     * Will be called by the InstrumentResourceManager when the instrument
289     * update process was completed, so we can continue with playback.
290     */
291     void Engine::ResourceUpdated(::gig::Instrument* pOldResource, ::gig::Instrument* pNewResource, void* pUpdateArg) {
292 schoenebeck 239 this->pInstrument = pNewResource; //TODO: there are couple of engine parameters we should update here as well if the instrument was updated (see LoadInstrument())
293 schoenebeck 53 Enable();
294     }
295    
296     void Engine::Connect(AudioOutputDevice* pAudioOut) {
297     pAudioOutputDevice = pAudioOut;
298    
299     ResetInternal();
300    
301     // inform audio driver for the need of two channels
302     try {
303     pAudioOutputDevice->AcquireChannels(2); // gig engine only stereo
304     }
305     catch (AudioOutputException e) {
306     String msg = "Audio output device unable to provide 2 audio channels, cause: " + e.Message();
307     throw LinuxSamplerException(msg);
308     }
309    
310 schoenebeck 225 this->AudioDeviceChannelLeft = 0;
311     this->AudioDeviceChannelRight = 1;
312     this->pOutputLeft = pAudioOutputDevice->Channel(0)->Buffer();
313     this->pOutputRight = pAudioOutputDevice->Channel(1)->Buffer();
314     this->MaxSamplesPerCycle = pAudioOutputDevice->MaxSamplesPerCycle();
315     this->SampleRate = pAudioOutputDevice->SampleRate();
316    
317 schoenebeck 53 // (re)create disk thread
318     if (this->pDiskThread) {
319     this->pDiskThread->StopThread();
320     delete this->pDiskThread;
321     }
322     this->pDiskThread = new DiskThread(((pAudioOut->MaxSamplesPerCycle() << MAX_PITCH) << 1) + 6); //FIXME: assuming stereo
323     if (!pDiskThread) {
324     dmsg(0,("gig::Engine new diskthread = NULL\n"));
325     exit(EXIT_FAILURE);
326     }
327    
328     for (Voice* pVoice = pVoicePool->alloc(); pVoice; pVoice = pVoicePool->alloc()) {
329     pVoice->pDiskThread = this->pDiskThread;
330     dmsg(3,("d"));
331     }
332     pVoicePool->clear();
333    
334     // (re)create event generator
335     if (pEventGenerator) delete pEventGenerator;
336     pEventGenerator = new EventGenerator(pAudioOut->SampleRate());
337    
338     // (re)allocate synthesis parameter matrix
339     if (pSynthesisParameters[0]) delete[] pSynthesisParameters[0];
340     pSynthesisParameters[0] = new float[Event::destination_count * pAudioOut->MaxSamplesPerCycle()];
341     for (int dst = 1; dst < Event::destination_count; dst++)
342     pSynthesisParameters[dst] = pSynthesisParameters[dst - 1] + pAudioOut->MaxSamplesPerCycle();
343    
344 schoenebeck 80 // (re)allocate biquad filter parameter sequence
345     if (pBasicFilterParameters) delete[] pBasicFilterParameters;
346     if (pMainFilterParameters) delete[] pMainFilterParameters;
347     pBasicFilterParameters = new biquad_param_t[pAudioOut->MaxSamplesPerCycle()];
348     pMainFilterParameters = new biquad_param_t[pAudioOut->MaxSamplesPerCycle()];
349    
350 schoenebeck 53 dmsg(1,("Starting disk thread..."));
351     pDiskThread->StartThread();
352     dmsg(1,("OK\n"));
353    
354     for (Voice* pVoice = pVoicePool->first(); pVoice; pVoice = pVoicePool->next()) {
355     if (!pVoice->pDiskThread) {
356     dmsg(0,("Engine -> voice::trigger: !pDiskThread\n"));
357     exit(EXIT_FAILURE);
358     }
359     }
360     }
361    
362     void Engine::DisconnectAudioOutputDevice() {
363     if (pAudioOutputDevice) { // if clause to prevent disconnect loops
364     AudioOutputDevice* olddevice = pAudioOutputDevice;
365     pAudioOutputDevice = NULL;
366     olddevice->Disconnect(this);
367 schoenebeck 225 AudioDeviceChannelLeft = -1;
368     AudioDeviceChannelRight = -1;
369 schoenebeck 53 }
370     }
371    
372     /**
373     * Let this engine proceed to render the given amount of sample points. The
374     * calculated audio data of all voices of this engine will be placed into
375     * the engine's audio sum buffer which has to be copied and eventually be
376     * converted to the appropriate value range by the audio output class (e.g.
377     * AlsaIO or JackIO) right after.
378     *
379     * @param Samples - number of sample points to be rendered
380     * @returns 0 on success
381     */
382     int Engine::RenderAudio(uint Samples) {
383     dmsg(5,("RenderAudio(Samples=%d)\n", Samples));
384    
385     // return if no instrument loaded or engine disabled
386     if (EngineDisabled.Pop()) {
387     dmsg(5,("gig::Engine: engine disabled (val=%d)\n",EngineDisabled.GetUnsafe()));
388     return 0;
389     }
390     if (!pInstrument) {
391     dmsg(5,("gig::Engine: no instrument loaded\n"));
392     return 0;
393     }
394    
395    
396     // empty the event lists for the new fragment
397     pEvents->clear();
398     pCCEvents->clear();
399     for (uint i = 0; i < Event::destination_count; i++) {
400     pSynthesisEvents[i]->clear();
401     }
402 schoenebeck 250 for (uint* puiKey = pActiveKeys->first(); puiKey; puiKey = pActiveKeys->next()) {
403     midi_key_info_t* pKey = &pMIDIKeyInfo[*puiKey];
404     pKey->pEvents->clear(); // free all events on the key
405     }
406 schoenebeck 53
407     // read and copy events from input queue
408     Event event = pEventGenerator->CreateEvent();
409     while (true) {
410     if (!pEventQueue->pop(&event)) break;
411     pEvents->alloc_assign(event);
412     }
413    
414    
415     // update time of start and end of this audio fragment (as events' time stamps relate to this)
416     pEventGenerator->UpdateFragmentTime(Samples);
417    
418    
419     // process events
420     Event* pNextEvent = pEvents->first();
421     while (pNextEvent) {
422     Event* pEvent = pNextEvent;
423     pEvents->set_current(pEvent);
424     pNextEvent = pEvents->next();
425     switch (pEvent->Type) {
426     case Event::type_note_on:
427 schoenebeck 244 dmsg(5,("Engine: Note on received\n"));
428 schoenebeck 53 ProcessNoteOn(pEvent);
429     break;
430     case Event::type_note_off:
431 schoenebeck 244 dmsg(5,("Engine: Note off received\n"));
432 schoenebeck 53 ProcessNoteOff(pEvent);
433     break;
434     case Event::type_control_change:
435 schoenebeck 244 dmsg(5,("Engine: MIDI CC received\n"));
436 schoenebeck 53 ProcessControlChange(pEvent);
437     break;
438     case Event::type_pitchbend:
439 schoenebeck 244 dmsg(5,("Engine: Pitchbend received\n"));
440 schoenebeck 53 ProcessPitchbend(pEvent);
441     break;
442 schoenebeck 244 case Event::type_sysex:
443     dmsg(5,("Engine: Sysex received\n"));
444     ProcessSysex(pEvent);
445     break;
446 schoenebeck 53 }
447     }
448    
449    
450     // render audio from all active voices
451     int active_voices = 0;
452     uint* piKey = pActiveKeys->first();
453     while (piKey) { // iterate through all active keys
454     midi_key_info_t* pKey = &pMIDIKeyInfo[*piKey];
455     pActiveKeys->set_current(piKey);
456     piKey = pActiveKeys->next();
457    
458     Voice* pVoiceNext = pKey->pActiveVoices->first();
459     while (pVoiceNext) { // iterate through all voices on this key
460     // already get next voice on key
461     Voice* pVoice = pVoiceNext;
462     pKey->pActiveVoices->set_current(pVoice);
463     pVoiceNext = pKey->pActiveVoices->next();
464    
465     // now render current voice
466     pVoice->Render(Samples);
467     if (pVoice->IsActive()) active_voices++; // still active
468     else { // voice reached end, is now inactive
469 schoenebeck 239 KillVoiceImmediately(pVoice); // remove voice from the list of active voices
470 schoenebeck 53 }
471     }
472     }
473    
474    
475 schoenebeck 250 // now render all postponed voices from voice stealing
476     Event* pVoiceStealEvent = pVoiceStealingQueue->first();
477     while (pVoiceStealEvent) {
478     Voice* pNewVoice = LaunchVoice(pVoiceStealEvent, pVoiceStealEvent->Param.Note.Layer, pVoiceStealEvent->Param.Note.ReleaseTrigger, false);
479     if (pNewVoice) {
480     pNewVoice->Render(Samples);
481     if (pNewVoice->IsActive()) active_voices++; // still active
482     else { // voice reached end, is now inactive
483     KillVoiceImmediately(pNewVoice); // remove voice from the list of active voices
484     }
485     }
486     else dmsg(1,("Ouch, voice stealing didn't work out!\n"));
487     pVoiceStealEvent = pVoiceStealingQueue->next();
488     }
489     // reset voice stealing for the new fragment
490     pVoiceStealingQueue->clear();
491     pLastStolenVoice = NULL;
492     puiLastStolenKey = NULL;
493    
494    
495 schoenebeck 53 // write that to the disk thread class so that it can print it
496     // on the console for debugging purposes
497     ActiveVoiceCount = active_voices;
498     if (ActiveVoiceCount > ActiveVoiceCountMax) ActiveVoiceCountMax = ActiveVoiceCount;
499    
500    
501     return 0;
502     }
503    
504     /**
505     * Will be called by the MIDIIn Thread to let the audio thread trigger a new
506     * voice for the given key.
507     *
508     * @param Key - MIDI key number of the triggered key
509     * @param Velocity - MIDI velocity value of the triggered key
510     */
511     void Engine::SendNoteOn(uint8_t Key, uint8_t Velocity) {
512 schoenebeck 246 Event event = pEventGenerator->CreateEvent();
513     event.Type = Event::type_note_on;
514     event.Param.Note.Key = Key;
515     event.Param.Note.Velocity = Velocity;
516 schoenebeck 53 if (this->pEventQueue->write_space() > 0) this->pEventQueue->push(&event);
517     else dmsg(1,("Engine: Input event queue full!"));
518     }
519    
520     /**
521     * Will be called by the MIDIIn Thread to signal the audio thread to release
522     * voice(s) on the given key.
523     *
524     * @param Key - MIDI key number of the released key
525     * @param Velocity - MIDI release velocity value of the released key
526     */
527     void Engine::SendNoteOff(uint8_t Key, uint8_t Velocity) {
528 schoenebeck 246 Event event = pEventGenerator->CreateEvent();
529     event.Type = Event::type_note_off;
530     event.Param.Note.Key = Key;
531     event.Param.Note.Velocity = Velocity;
532 schoenebeck 53 if (this->pEventQueue->write_space() > 0) this->pEventQueue->push(&event);
533     else dmsg(1,("Engine: Input event queue full!"));
534     }
535    
536     /**
537     * Will be called by the MIDIIn Thread to signal the audio thread to change
538     * the pitch value for all voices.
539     *
540     * @param Pitch - MIDI pitch value (-8192 ... +8191)
541     */
542     void Engine::SendPitchbend(int Pitch) {
543 schoenebeck 246 Event event = pEventGenerator->CreateEvent();
544     event.Type = Event::type_pitchbend;
545     event.Param.Pitch.Pitch = Pitch;
546 schoenebeck 53 if (this->pEventQueue->write_space() > 0) this->pEventQueue->push(&event);
547     else dmsg(1,("Engine: Input event queue full!"));
548     }
549    
550     /**
551     * Will be called by the MIDIIn Thread to signal the audio thread that a
552     * continuous controller value has changed.
553     *
554     * @param Controller - MIDI controller number of the occured control change
555     * @param Value - value of the control change
556     */
557     void Engine::SendControlChange(uint8_t Controller, uint8_t Value) {
558 schoenebeck 246 Event event = pEventGenerator->CreateEvent();
559     event.Type = Event::type_control_change;
560     event.Param.CC.Controller = Controller;
561     event.Param.CC.Value = Value;
562 schoenebeck 53 if (this->pEventQueue->write_space() > 0) this->pEventQueue->push(&event);
563     else dmsg(1,("Engine: Input event queue full!"));
564     }
565    
566     /**
567 schoenebeck 244 * Will be called by the MIDI input device whenever a MIDI system
568     * exclusive message has arrived.
569     *
570     * @param pData - pointer to sysex data
571     * @param Size - lenght of sysex data (in bytes)
572     */
573     void Engine::SendSysex(void* pData, uint Size) {
574 schoenebeck 246 Event event = pEventGenerator->CreateEvent();
575     event.Type = Event::type_sysex;
576     event.Param.Sysex.Size = Size;
577 schoenebeck 244 if (pEventQueue->write_space() > 0) {
578     if (pSysexBuffer->write_space() >= Size) {
579     // copy sysex data to input buffer
580     uint toWrite = Size;
581     uint8_t* pPos = (uint8_t*) pData;
582     while (toWrite) {
583     const uint writeNow = RTMath::Min(toWrite, pSysexBuffer->write_space_to_end());
584     pSysexBuffer->write(pPos, writeNow);
585     toWrite -= writeNow;
586     pPos += writeNow;
587    
588     }
589     // finally place sysex event into input event queue
590     pEventQueue->push(&event);
591     }
592     else dmsg(1,("Engine: Sysex message too large (%d byte) for input buffer (%d byte)!",Size,SYSEX_BUFFER_SIZE));
593     }
594     else dmsg(1,("Engine: Input event queue full!"));
595     }
596    
597     /**
598 schoenebeck 53 * Assigns and triggers a new voice for the respective MIDI key.
599     *
600     * @param pNoteOnEvent - key, velocity and time stamp of the event
601     */
602     void Engine::ProcessNoteOn(Event* pNoteOnEvent) {
603 schoenebeck 246 midi_key_info_t* pKey = &pMIDIKeyInfo[pNoteOnEvent->Param.Note.Key];
604 schoenebeck 53
605     pKey->KeyPressed = true; // the MIDI key was now pressed down
606    
607     // cancel release process of voices on this key if needed
608     if (pKey->Active && !SustainPedal) {
609 schoenebeck 239 Event* pCancelReleaseEvent = pKey->pEvents->alloc();
610     if (pCancelReleaseEvent) {
611     *pCancelReleaseEvent = *pNoteOnEvent;
612     pCancelReleaseEvent->Type = Event::type_cancel_release; // transform event type
613     }
614     else dmsg(1,("Event pool emtpy!\n"));
615 schoenebeck 53 }
616    
617 schoenebeck 233 // allocate and trigger a new voice for the key
618     LaunchVoice(pNoteOnEvent);
619 schoenebeck 239
620     // finally move note on event to the key's own event list
621     pEvents->move(pNoteOnEvent, pKey->pEvents);
622 schoenebeck 53 }
623    
624     /**
625     * Releases the voices on the given key if sustain pedal is not pressed.
626     * If sustain is pressed, the release of the note will be postponed until
627     * sustain pedal will be released or voice turned inactive by itself (e.g.
628     * due to completion of sample playback).
629     *
630     * @param pNoteOffEvent - key, velocity and time stamp of the event
631     */
632     void Engine::ProcessNoteOff(Event* pNoteOffEvent) {
633 schoenebeck 246 midi_key_info_t* pKey = &pMIDIKeyInfo[pNoteOffEvent->Param.Note.Key];
634 schoenebeck 53
635     pKey->KeyPressed = false; // the MIDI key was now released
636    
637     // release voices on this key if needed
638     if (pKey->Active && !SustainPedal) {
639     pNoteOffEvent->Type = Event::type_release; // transform event type
640     }
641 schoenebeck 242
642     // spawn release triggered voice(s) if needed
643     if (pKey->ReleaseTrigger) {
644     LaunchVoice(pNoteOffEvent, 0, true);
645     pKey->ReleaseTrigger = false;
646     }
647    
648     // move event to the key's own event list
649     pEvents->move(pNoteOffEvent, pKey->pEvents);
650 schoenebeck 53 }
651    
652     /**
653     * Moves pitchbend event from the general (input) event list to the pitch
654     * event list.
655     *
656     * @param pPitchbendEvent - absolute pitch value and time stamp of the event
657     */
658     void Engine::ProcessPitchbend(Event* pPitchbendEvent) {
659 schoenebeck 246 this->Pitch = pPitchbendEvent->Param.Pitch.Pitch; // store current pitch value
660 schoenebeck 53 pEvents->move(pPitchbendEvent, pSynthesisEvents[Event::destination_vco]);
661     }
662    
663     /**
664 schoenebeck 233 * Allocates and triggers a new voice. This method will usually be
665     * called by the ProcessNoteOn() method and by the voices itself
666     * (e.g. to spawn further voices on the same key for layered sounds).
667     *
668 schoenebeck 242 * @param pNoteOnEvent - key, velocity and time stamp of the event
669     * @param iLayer - layer index for the new voice (optional - only
670     * in case of layered sounds of course)
671     * @param ReleaseTriggerVoice - if new voice is a release triggered voice
672     * (optional, default = false)
673 schoenebeck 250 * @param VoiceStealing - if voice stealing should be performed
674     * when there is no free voice
675     * (optional, default = true)
676     * @returns pointer to new voice or NULL if there was no free voice or
677     * if an error occured while trying to trigger the new voice
678 schoenebeck 233 */
679 schoenebeck 250 Voice* Engine::LaunchVoice(Event* pNoteOnEvent, int iLayer, bool ReleaseTriggerVoice, bool VoiceStealing) {
680 schoenebeck 246 midi_key_info_t* pKey = &pMIDIKeyInfo[pNoteOnEvent->Param.Note.Key];
681 schoenebeck 233
682     // allocate a new voice for the key
683     Voice* pNewVoice = pKey->pActiveVoices->alloc();
684     if (pNewVoice) {
685     // launch the new voice
686 schoenebeck 242 if (pNewVoice->Trigger(pNoteOnEvent, this->Pitch, this->pInstrument, iLayer, ReleaseTriggerVoice) < 0) {
687 schoenebeck 233 dmsg(1,("Triggering new voice failed!\n"));
688     pKey->pActiveVoices->free(pNewVoice);
689     }
690 schoenebeck 239 else { // on success
691     uint** ppKeyGroup = NULL;
692     if (pNewVoice->KeyGroup) { // if this voice / key belongs to a key group
693     ppKeyGroup = &ActiveKeyGroups[pNewVoice->KeyGroup];
694     if (*ppKeyGroup) { // if there's already an active key in that key group
695     midi_key_info_t* pOtherKey = &pMIDIKeyInfo[**ppKeyGroup];
696     // kill all voices on the (other) key
697     Voice* pVoiceToBeKilled = pOtherKey->pActiveVoices->first();
698 schoenebeck 242 while (pVoiceToBeKilled) {
699     Voice* pVoiceToBeKilledNext = pOtherKey->pActiveVoices->next();
700     if (pVoiceToBeKilled->Type != Voice::type_release_trigger) pVoiceToBeKilled->Kill(pNoteOnEvent);
701     pOtherKey->pActiveVoices->set_current(pVoiceToBeKilled);
702     pVoiceToBeKilled = pVoiceToBeKilledNext;
703     }
704 schoenebeck 239 }
705     }
706     if (!pKey->Active) { // mark as active key
707     pKey->Active = true;
708     pKey->pSelf = pActiveKeys->alloc();
709 schoenebeck 246 *pKey->pSelf = pNoteOnEvent->Param.Note.Key;
710 schoenebeck 239 }
711     if (pNewVoice->KeyGroup) {
712     *ppKeyGroup = pKey->pSelf; // put key as the (new) active key to its key group
713     }
714 schoenebeck 242 if (pNewVoice->Type == Voice::type_release_trigger_required) pKey->ReleaseTrigger = true; // mark key for the need of release triggered voice(s)
715 schoenebeck 250 return pNewVoice; // success
716 schoenebeck 233 }
717     }
718 schoenebeck 250 else if (VoiceStealing) StealVoice(pNoteOnEvent, iLayer, ReleaseTriggerVoice); // no free voice left, so steal one
719    
720     return NULL; // no free voice or error
721 schoenebeck 233 }
722    
723     /**
724 schoenebeck 250 * Will be called by LaunchVoice() method in case there are no free
725     * voices left. This method will select and kill one old voice for
726     * voice stealing and postpone the note-on event until the selected
727     * voice actually died.
728     *
729     * @param pNoteOnEvent - key, velocity and time stamp of the event
730     * @param iLayer - layer index for the new voice
731     * @param ReleaseTriggerVoice - if new voice is a release triggered voice
732     */
733     void Engine::StealVoice(Event* pNoteOnEvent, int iLayer, bool ReleaseTriggerVoice) {
734     if (!pEventPool->pool_is_empty()) {
735    
736     uint* puiOldestKey;
737     Voice* pOldestVoice;
738    
739     // Select one voice for voice stealing
740     switch (VOICE_STEAL_ALGORITHM) {
741    
742     // try to pick the oldest voice on the key where the new
743     // voice should be spawned, if there is no voice on that
744     // key, or no voice left to kill there, then procceed with
745     // 'oldestkey' algorithm
746     case voice_steal_algo_keymask: {
747     midi_key_info_t* pOldestKey = &pMIDIKeyInfo[pNoteOnEvent->Param.Note.Key];
748     if (pLastStolenVoice) {
749     pOldestKey->pActiveVoices->set_current(pLastStolenVoice);
750     pOldestVoice = pOldestKey->pActiveVoices->next();
751     }
752     else { // no voice stolen in this audio fragment cycle yet
753     pOldestVoice = pOldestKey->pActiveVoices->first();
754     }
755     if (pOldestVoice) {
756     puiOldestKey = pOldestKey->pSelf;
757     break; // selection succeeded
758     }
759     } // no break - intentional !
760    
761     // try to pick the oldest voice on the oldest active key
762     // (caution: must stay after 'keymask' algorithm !)
763     case voice_steal_algo_oldestkey: {
764     if (pLastStolenVoice) {
765     midi_key_info_t* pOldestKey = &pMIDIKeyInfo[*puiLastStolenKey];
766     pOldestKey->pActiveVoices->set_current(pLastStolenVoice);
767     pOldestVoice = pOldestKey->pActiveVoices->next();
768     if (!pOldestVoice) {
769     pActiveKeys->set_current(puiLastStolenKey);
770     puiOldestKey = pActiveKeys->next();
771     if (puiOldestKey) {
772     midi_key_info_t* pOldestKey = &pMIDIKeyInfo[*puiOldestKey];
773     pOldestVoice = pOldestKey->pActiveVoices->first();
774     }
775     else { // too less voices, even for voice stealing
776     dmsg(1,("Voice overflow! - You might recompile with higher MAX_AUDIO_VOICES!\n"));
777     return;
778     }
779     }
780     else puiOldestKey = puiLastStolenKey;
781     }
782     else { // no voice stolen in this audio fragment cycle yet
783     puiOldestKey = pActiveKeys->first();
784     midi_key_info_t* pOldestKey = &pMIDIKeyInfo[*puiOldestKey];
785     pOldestVoice = pOldestKey->pActiveVoices->first();
786     }
787     break;
788     }
789    
790     // don't steal anything
791     case voice_steal_algo_none:
792     default: {
793     dmsg(1,("No free voice (voice stealing disabled)!\n"));
794     return;
795     }
796     }
797    
798     // now kill the selected voice
799     pOldestVoice->Kill(pNoteOnEvent);
800     // remember which voice on which key we stole, so we can simply proceed for the next voice stealing
801     this->pLastStolenVoice = pOldestVoice;
802     this->puiLastStolenKey = puiOldestKey;
803     // put note-on event into voice-stealing queue, so it will be reprocessed after killed voice died
804     Event* pStealEvent = pVoiceStealingQueue->alloc();
805     *pStealEvent = *pNoteOnEvent;
806     pStealEvent->Param.Note.Layer = iLayer;
807     pStealEvent->Param.Note.ReleaseTrigger = ReleaseTriggerVoice;
808     }
809     else dmsg(1,("Event pool emtpy!\n"));
810     }
811    
812     /**
813 schoenebeck 53 * Immediately kills the voice given with pVoice (no matter if sustain is
814     * pressed or not) and removes it from the MIDI key's list of active voice.
815     * This method will e.g. be called if a voice went inactive by itself.
816     *
817     * @param pVoice - points to the voice to be killed
818     */
819 schoenebeck 239 void Engine::KillVoiceImmediately(Voice* pVoice) {
820 schoenebeck 53 if (pVoice) {
821 schoenebeck 239 if (pVoice->IsActive()) pVoice->KillImmediately();
822 schoenebeck 53
823     midi_key_info_t* pKey = &pMIDIKeyInfo[pVoice->MIDIKey];
824    
825     // free the voice object
826     pVoicePool->free(pVoice);
827    
828     // check if there are no voices left on the MIDI key and update the key info if so
829     if (pKey->pActiveVoices->is_empty()) {
830 schoenebeck 239 if (pVoice->KeyGroup) { // if voice / key belongs to a key group
831     uint** ppKeyGroup = &ActiveKeyGroups[pVoice->KeyGroup];
832     if (*ppKeyGroup == pKey->pSelf) *ppKeyGroup = NULL; // remove key from key group
833     }
834 schoenebeck 53 pKey->Active = false;
835     pActiveKeys->free(pKey->pSelf); // remove key from list of active keys
836     pKey->pSelf = NULL;
837 schoenebeck 242 pKey->ReleaseTrigger = false;
838 schoenebeck 250 pKey->pEvents->clear();
839 schoenebeck 53 dmsg(3,("Key has no more voices now\n"));
840     }
841     }
842     else std::cerr << "Couldn't release voice! (pVoice == NULL)\n" << std::flush;
843     }
844    
845     /**
846     * Reacts on supported control change commands (e.g. pitch bend wheel,
847     * modulation wheel, aftertouch).
848     *
849     * @param pControlChangeEvent - controller, value and time stamp of the event
850     */
851     void Engine::ProcessControlChange(Event* pControlChangeEvent) {
852 schoenebeck 246 dmsg(4,("Engine::ContinuousController cc=%d v=%d\n", pControlChangeEvent->Param.CC.Controller, pControlChangeEvent->Param.CC.Value));
853 schoenebeck 53
854 schoenebeck 246 switch (pControlChangeEvent->Param.CC.Controller) {
855 schoenebeck 53 case 64: {
856 schoenebeck 246 if (pControlChangeEvent->Param.CC.Value >= 64 && !SustainPedal) {
857 schoenebeck 53 dmsg(4,("PEDAL DOWN\n"));
858     SustainPedal = true;
859    
860     // cancel release process of voices if necessary
861     uint* piKey = pActiveKeys->first();
862     if (piKey) {
863     pControlChangeEvent->Type = Event::type_cancel_release; // transform event type
864     while (piKey) {
865     midi_key_info_t* pKey = &pMIDIKeyInfo[*piKey];
866     pActiveKeys->set_current(piKey);
867     piKey = pActiveKeys->next();
868     if (!pKey->KeyPressed) {
869     Event* pNewEvent = pKey->pEvents->alloc();
870     if (pNewEvent) *pNewEvent = *pControlChangeEvent; // copy event to the key's own event list
871     else dmsg(1,("Event pool emtpy!\n"));
872     }
873     }
874     }
875     }
876 schoenebeck 246 if (pControlChangeEvent->Param.CC.Value < 64 && SustainPedal) {
877 schoenebeck 53 dmsg(4,("PEDAL UP\n"));
878     SustainPedal = false;
879    
880     // release voices if their respective key is not pressed
881     uint* piKey = pActiveKeys->first();
882     if (piKey) {
883     pControlChangeEvent->Type = Event::type_release; // transform event type
884     while (piKey) {
885     midi_key_info_t* pKey = &pMIDIKeyInfo[*piKey];
886     pActiveKeys->set_current(piKey);
887     piKey = pActiveKeys->next();
888     if (!pKey->KeyPressed) {
889     Event* pNewEvent = pKey->pEvents->alloc();
890     if (pNewEvent) *pNewEvent = *pControlChangeEvent; // copy event to the key's own event list
891     else dmsg(1,("Event pool emtpy!\n"));
892     }
893     }
894     }
895     }
896     break;
897     }
898     }
899    
900     // update controller value in the engine's controller table
901 schoenebeck 246 ControllerTable[pControlChangeEvent->Param.CC.Controller] = pControlChangeEvent->Param.CC.Value;
902 schoenebeck 53
903     // move event from the unsorted event list to the control change event list
904     pEvents->move(pControlChangeEvent, pCCEvents);
905     }
906    
907     /**
908 schoenebeck 244 * Reacts on MIDI system exclusive messages.
909     *
910     * @param pSysexEvent - sysex data size and time stamp of the sysex event
911     */
912     void Engine::ProcessSysex(Event* pSysexEvent) {
913     RingBuffer<uint8_t>::NonVolatileReader reader = pSysexBuffer->get_non_volatile_reader();
914    
915     uint8_t exclusive_status, id;
916     if (!reader.pop(&exclusive_status)) goto free_sysex_data;
917     if (!reader.pop(&id)) goto free_sysex_data;
918     if (exclusive_status != 0xF0) goto free_sysex_data;
919    
920     switch (id) {
921     case 0x41: { // Roland
922     uint8_t device_id, model_id, cmd_id;
923     if (!reader.pop(&device_id)) goto free_sysex_data;
924     if (!reader.pop(&model_id)) goto free_sysex_data;
925     if (!reader.pop(&cmd_id)) goto free_sysex_data;
926     if (model_id != 0x42 /*GS*/) goto free_sysex_data;
927     if (cmd_id != 0x12 /*DT1*/) goto free_sysex_data;
928    
929     // command address
930     uint8_t addr[3]; // 2 byte addr MSB, followed by 1 byte addr LSB)
931     const RingBuffer<uint8_t>::NonVolatileReader checksum_reader = reader; // so we can calculate the check sum later
932     if (reader.read(&addr[0], 3) != 3) goto free_sysex_data;
933     if (addr[0] == 0x40 && addr[1] == 0x00) { // System Parameters
934     }
935     else if (addr[0] == 0x40 && addr[1] == 0x01) { // Common Parameters
936     }
937     else if (addr[0] == 0x40 && (addr[1] & 0xf0) == 0x10) { // Part Parameters (1)
938     switch (addr[3]) {
939     case 0x40: { // scale tuning
940     uint8_t scale_tunes[12]; // detuning of all 12 semitones of an octave
941     if (reader.read(&scale_tunes[0], 12) != 12) goto free_sysex_data;
942     uint8_t checksum;
943     if (!reader.pop(&checksum)) goto free_sysex_data;
944     if (GSCheckSum(checksum_reader, 12) != checksum) goto free_sysex_data;
945     for (int i = 0; i < 12; i++) scale_tunes[i] -= 64;
946     AdjustScale((int8_t*) scale_tunes);
947     break;
948     }
949     }
950     }
951     else if (addr[0] == 0x40 && (addr[1] & 0xf0) == 0x20) { // Part Parameters (2)
952     }
953     else if (addr[0] == 0x41) { // Drum Setup Parameters
954     }
955     break;
956     }
957     }
958    
959     free_sysex_data: // finally free sysex data
960 schoenebeck 246 pSysexBuffer->increment_read_ptr(pSysexEvent->Param.Sysex.Size);
961 schoenebeck 244 }
962    
963     /**
964     * Calculates the Roland GS sysex check sum.
965     *
966     * @param AddrReader - reader which currently points to the first GS
967     * command address byte of the GS sysex message in
968     * question
969     * @param DataSize - size of the GS message data (in bytes)
970     */
971     uint8_t Engine::GSCheckSum(const RingBuffer<uint8_t>::NonVolatileReader AddrReader, uint DataSize) {
972     RingBuffer<uint8_t>::NonVolatileReader reader = AddrReader;
973     uint bytes = 3 /*addr*/ + DataSize;
974     uint8_t addr_and_data[bytes];
975     reader.read(&addr_and_data[0], bytes);
976     uint8_t sum = 0;
977     for (uint i = 0; i < bytes; i++) sum += addr_and_data[i];
978     return 128 - sum % 128;
979     }
980    
981     /**
982     * Allows to tune each of the twelve semitones of an octave.
983     *
984     * @param ScaleTunes - detuning of all twelve semitones (in cents)
985     */
986     void Engine::AdjustScale(int8_t ScaleTunes[12]) {
987     memcpy(&this->ScaleTuning[0], &ScaleTunes[0], 12); //TODO: currently not sample accurate
988     }
989    
990     /**
991 schoenebeck 53 * Initialize the parameter sequence for the modulation destination given by
992     * by 'dst' with the constant value given by val.
993     */
994     void Engine::ResetSynthesisParameters(Event::destination_t dst, float val) {
995     int maxsamples = pAudioOutputDevice->MaxSamplesPerCycle();
996 schoenebeck 80 float* m = &pSynthesisParameters[dst][0];
997     for (int i = 0; i < maxsamples; i += 4) {
998     m[i] = val;
999     m[i+1] = val;
1000     m[i+2] = val;
1001     m[i+3] = val;
1002     }
1003 schoenebeck 53 }
1004    
1005     float Engine::Volume() {
1006     return GlobalVolume;
1007     }
1008    
1009     void Engine::Volume(float f) {
1010     GlobalVolume = f;
1011     }
1012    
1013 schoenebeck 225 uint Engine::Channels() {
1014     return 2;
1015     }
1016    
1017     void Engine::SetOutputChannel(uint EngineAudioChannel, uint AudioDeviceChannel) {
1018     AudioChannel* pChannel = pAudioOutputDevice->Channel(AudioDeviceChannel);
1019     if (!pChannel) throw AudioOutputException("Invalid audio output device channel " + ToString(AudioDeviceChannel));
1020     switch (EngineAudioChannel) {
1021     case 0: // left output channel
1022     pOutputLeft = pChannel->Buffer();
1023     AudioDeviceChannelLeft = AudioDeviceChannel;
1024     break;
1025     case 1: // right output channel
1026     pOutputRight = pChannel->Buffer();
1027     AudioDeviceChannelRight = AudioDeviceChannel;
1028     break;
1029     default:
1030     throw AudioOutputException("Invalid engine audio channel " + ToString(EngineAudioChannel));
1031     }
1032     }
1033    
1034     int Engine::OutputChannel(uint EngineAudioChannel) {
1035     switch (EngineAudioChannel) {
1036     case 0: // left channel
1037     return AudioDeviceChannelLeft;
1038     case 1: // right channel
1039     return AudioDeviceChannelRight;
1040     default:
1041     throw AudioOutputException("Invalid engine audio channel " + ToString(EngineAudioChannel));
1042     }
1043     }
1044    
1045 schoenebeck 53 uint Engine::VoiceCount() {
1046     return ActiveVoiceCount;
1047     }
1048    
1049     uint Engine::VoiceCountMax() {
1050     return ActiveVoiceCountMax;
1051     }
1052    
1053     bool Engine::DiskStreamSupported() {
1054     return true;
1055     }
1056    
1057     uint Engine::DiskStreamCount() {
1058     return (pDiskThread) ? pDiskThread->ActiveStreamCount : 0;
1059     }
1060    
1061     uint Engine::DiskStreamCountMax() {
1062     return (pDiskThread) ? pDiskThread->ActiveStreamCountMax : 0;
1063     }
1064    
1065     String Engine::DiskStreamBufferFillBytes() {
1066     return pDiskThread->GetBufferFillBytes();
1067     }
1068    
1069     String Engine::DiskStreamBufferFillPercentage() {
1070     return pDiskThread->GetBufferFillPercentage();
1071     }
1072    
1073 senkov 112 String Engine::EngineName() {
1074     return "GigEngine";
1075     }
1076    
1077     String Engine::InstrumentFileName() {
1078     return InstrumentFile;
1079     }
1080    
1081     int Engine::InstrumentIndex() {
1082     return InstrumentIdx;
1083     }
1084    
1085 capela 133 int Engine::InstrumentStatus() {
1086     return InstrumentStat;
1087     }
1088    
1089 schoenebeck 53 String Engine::Description() {
1090     return "Gigasampler Engine";
1091     }
1092    
1093     String Engine::Version() {
1094 schoenebeck 250 String s = "$Revision: 1.14 $";
1095 schoenebeck 123 return s.substr(11, s.size() - 13); // cut dollar signs, spaces and CVS macro keyword
1096 schoenebeck 53 }
1097    
1098     }} // namespace LinuxSampler::gig

  ViewVC Help
Powered by ViewVC