diff options
author | Chris Robinson <[email protected]> | 2014-02-02 02:39:56 -0800 |
---|---|---|
committer | Chris Robinson <[email protected]> | 2014-02-02 02:39:56 -0800 |
commit | 7c4339c195e29b17c8f76e6fbe9c8ee1b4f7b9b8 (patch) | |
tree | 728e3298bff7d7612ee6d7fcd88a2049d343d763 /Alc | |
parent | 755f161fc58aec0b541c7783ed716bd5c861a615 (diff) |
Rework MIDI clock timing
It's best to avoid using doubles in the mixer since the FPU's set to single-
precision mode. The new clock timing is similar to the device clock timing, and
should hopefully be less prone to drift caused by fp rounding errors.
Diffstat (limited to 'Alc')
-rw-r--r-- | Alc/midi/base.c | 69 | ||||
-rw-r--r-- | Alc/midi/base.h | 17 | ||||
-rw-r--r-- | Alc/midi/dummy.c | 27 | ||||
-rw-r--r-- | Alc/midi/fluidsynth.c | 72 |
4 files changed, 62 insertions, 123 deletions
diff --git a/Alc/midi/base.c b/Alc/midi/base.c index 249bc2a8..1850a6c6 100644 --- a/Alc/midi/base.c +++ b/Alc/midi/base.c @@ -16,9 +16,6 @@ #include "alu.h" -/* Nanosecond resolution */ -#define TICKS_PER_SECOND (1000000000) - /* MIDI events */ #define SYSEX_EVENT (0xF0) @@ -129,12 +126,9 @@ void MidiSynth_Construct(MidiSynth *self, ALCdevice *device) self->Gain = 1.0f; self->State = AL_INITIAL; - self->LastEvtTime = 0; - self->NextEvtTime = UINT64_MAX; - self->SamplesSinceLast = 0.0; - self->SamplesToNext = 0.0; - - self->SamplesPerTick = (ALdouble)device->Frequency / TICKS_PER_SECOND; + self->ClockBase = 0; + self->SamplesDone = 0; + self->SampleRate = device->Frequency; } void MidiSynth_Destruct(MidiSynth *self) @@ -195,29 +189,22 @@ void MidiSynth_stop(MidiSynth *self) { ResetEvtQueue(&self->EventQueue); - self->LastEvtTime = 0; - self->NextEvtTime = UINT64_MAX; - self->SamplesSinceLast = 0.0; - self->SamplesToNext = 0.0; + self->ClockBase = 0; + self->SamplesDone = 0; } extern inline void MidiSynth_reset(MidiSynth *self); - -ALuint64 MidiSynth_getTime(const MidiSynth *self) -{ - ALuint64 time = self->LastEvtTime + (self->SamplesSinceLast/self->SamplesPerTick); - return clampu64(time, self->LastEvtTime, self->NextEvtTime); -} - +extern inline ALuint64 MidiSynth_getTime(const MidiSynth *self); extern inline ALuint64 MidiSynth_getNextEvtTime(const MidiSynth *self); -void MidiSynth_setSampleRate(MidiSynth *self, ALdouble srate) +void MidiSynth_setSampleRate(MidiSynth *self, ALuint srate) { - ALdouble sampletickrate = srate / TICKS_PER_SECOND; - - self->SamplesSinceLast = self->SamplesSinceLast * sampletickrate / self->SamplesPerTick; - self->SamplesToNext = self->SamplesToNext * sampletickrate / self->SamplesPerTick; - self->SamplesPerTick = sampletickrate; + if(self->SampleRate != srate) + { + self->ClockBase += self->SamplesDone * MIDI_CLOCK_RES / self->SampleRate; + self->SamplesDone = 0; + self->SampleRate = srate; + } } extern inline void MidiSynth_update(MidiSynth *self, ALCdevice *device); @@ -225,25 +212,11 @@ extern inline void MidiSynth_update(MidiSynth *self, ALCdevice *device); ALenum MidiSynth_insertEvent(MidiSynth *self, ALuint64 time, ALuint event, ALsizei param1, ALsizei param2) { MidiEvent entry; - ALenum err; - entry.time = time; entry.event = event; entry.param.val[0] = param1; entry.param.val[1] = param2; - - err = InsertEvtQueue(&self->EventQueue, &entry); - if(err != AL_NO_ERROR) return err; - - if(entry.time < self->NextEvtTime) - { - self->NextEvtTime = entry.time; - - self->SamplesToNext = (self->NextEvtTime - self->LastEvtTime) * self->SamplesPerTick; - self->SamplesToNext -= self->SamplesSinceLast; - } - - return AL_NO_ERROR; + return InsertEvtQueue(&self->EventQueue, &entry); } ALenum MidiSynth_insertSysExEvent(MidiSynth *self, ALuint64 time, const ALbyte *data, ALsizei size) @@ -261,18 +234,6 @@ ALenum MidiSynth_insertSysExEvent(MidiSynth *self, ALuint64 time, const ALbyte * err = InsertEvtQueue(&self->EventQueue, &entry); if(err != AL_NO_ERROR) - { free(entry.param.sysex.data); - return err; - } - - if(entry.time < self->NextEvtTime) - { - self->NextEvtTime = entry.time; - - self->SamplesToNext = (self->NextEvtTime - self->LastEvtTime) * self->SamplesPerTick; - self->SamplesToNext -= self->SamplesSinceLast; - } - - return AL_NO_ERROR; + return err; } diff --git a/Alc/midi/base.h b/Alc/midi/base.h index f900c941..4d13a054 100644 --- a/Alc/midi/base.h +++ b/Alc/midi/base.h @@ -23,17 +23,17 @@ typedef struct Reader { ALboolean loadSf2(Reader *stream, struct ALsoundfont *sfont, ALCcontext *context); +#define MIDI_CLOCK_RES U64(1000000000) + + struct MidiSynthVtable; typedef struct MidiSynth { EvtQueue EventQueue; - ALuint64 LastEvtTime; - ALuint64 NextEvtTime; - ALdouble SamplesSinceLast; - ALdouble SamplesToNext; - - ALdouble SamplesPerTick; + ALuint64 ClockBase; + ALuint SamplesDone; + ALuint SampleRate; /* NOTE: This rwlock is for the state and soundfont. The EventQueue and * related must instead use the device lock as they're used in the mixer @@ -59,14 +59,15 @@ inline void MidiSynth_setState(MidiSynth *self, ALenum state) { ExchangeInt(&sel inline ALenum MidiSynth_getState(const MidiSynth *self) { return self->State; } void MidiSynth_stop(MidiSynth *self); inline void MidiSynth_reset(MidiSynth *self) { MidiSynth_stop(self); } -ALuint64 MidiSynth_getTime(const MidiSynth *self); +inline ALuint64 MidiSynth_getTime(const MidiSynth *self) +{ return self->ClockBase + (self->SamplesDone*MIDI_CLOCK_RES/self->SampleRate); } inline ALuint64 MidiSynth_getNextEvtTime(const MidiSynth *self) { if(self->EventQueue.pos == self->EventQueue.size) return UINT64_MAX; return self->EventQueue.events[self->EventQueue.pos].time; } -void MidiSynth_setSampleRate(MidiSynth *self, ALdouble srate); +void MidiSynth_setSampleRate(MidiSynth *self, ALuint srate); inline void MidiSynth_update(MidiSynth *self, ALCdevice *device) { MidiSynth_setSampleRate(self, device->Frequency); } ALenum MidiSynth_insertEvent(MidiSynth *self, ALuint64 time, ALuint event, ALsizei param1, ALsizei param2); diff --git a/Alc/midi/dummy.c b/Alc/midi/dummy.c index 71c03efb..79f82b87 100644 --- a/Alc/midi/dummy.c +++ b/Alc/midi/dummy.c @@ -49,30 +49,17 @@ static void DSynth_processQueue(DSynth *self, ALuint64 time) static void DSynth_process(DSynth *self, ALuint SamplesToDo, ALfloatBUFFERSIZE*restrict UNUSED(DryBuffer)) { MidiSynth *synth = STATIC_CAST(MidiSynth, self); + ALuint64 curtime; if(synth->State != AL_PLAYING) return; - synth->SamplesSinceLast += SamplesToDo; - synth->SamplesToNext -= SamplesToDo; - while(synth->SamplesToNext < 1.0f) - { - ALuint64 time = synth->NextEvtTime; - if(time == UINT64_MAX) - { - synth->SamplesToNext = 0.0; - break; - } - - synth->SamplesSinceLast -= (time - synth->LastEvtTime) * synth->SamplesPerTick; - synth->SamplesSinceLast = maxd(synth->SamplesSinceLast, 0.0); - synth->LastEvtTime = time; - DSynth_processQueue(self, time); - - synth->NextEvtTime = MidiSynth_getNextEvtTime(synth); - if(synth->NextEvtTime != UINT64_MAX) - synth->SamplesToNext += (synth->NextEvtTime - synth->LastEvtTime) * synth->SamplesPerTick; - } + synth->SamplesDone += SamplesToDo; + synth->ClockBase += (synth->SamplesDone/synth->SampleRate) * MIDI_CLOCK_RES; + synth->SamplesDone %= synth->SampleRate; + + curtime = MidiSynth_getTime(synth); + DSynth_processQueue(self, maxi64(curtime-1, 0)); } diff --git a/Alc/midi/fluidsynth.c b/Alc/midi/fluidsynth.c index 9d58f87b..d2db71e3 100644 --- a/Alc/midi/fluidsynth.c +++ b/Alc/midi/fluidsynth.c @@ -640,24 +640,12 @@ static void FSynth_setState(FSynth *self, ALenum state) static void FSynth_stop(FSynth *self) { MidiSynth *synth = STATIC_CAST(MidiSynth, self); + ALuint64 curtime; ALsizei chan; /* Make sure all pending events are processed. */ - while(!(synth->SamplesToNext >= 1.0)) - { - ALuint64 time = synth->NextEvtTime; - if(time == UINT64_MAX) - break; - - synth->SamplesSinceLast -= (time - synth->LastEvtTime) * synth->SamplesPerTick; - synth->SamplesSinceLast = maxd(synth->SamplesSinceLast, 0.0); - synth->LastEvtTime = time; - FSynth_processQueue(self, time); - - synth->NextEvtTime = MidiSynth_getNextEvtTime(synth); - if(synth->NextEvtTime != UINT64_MAX) - synth->SamplesToNext += (synth->NextEvtTime - synth->LastEvtTime) * synth->SamplesPerTick; - } + curtime = MidiSynth_getTime(synth); + FSynth_processQueue(self, curtime); /* All notes off */ for(chan = 0;chan < 16;chan++) @@ -759,6 +747,7 @@ static void FSynth_process(FSynth *self, ALuint SamplesToDo, ALfloat (*restrict { MidiSynth *synth = STATIC_CAST(MidiSynth, self); ALenum state = synth->State; + ALuint64 curtime; ALuint total = 0; if(state == AL_INITIAL) @@ -770,41 +759,42 @@ static void FSynth_process(FSynth *self, ALuint SamplesToDo, ALfloat (*restrict return; } + curtime = MidiSynth_getTime(synth); while(total < SamplesToDo) { - if(synth->SamplesToNext >= 1.0) - { - ALuint todo = minu(SamplesToDo - total, fastf2u(synth->SamplesToNext)); + ALuint64 time, diff; + ALint tonext; - fluid_synth_write_float(self->Synth, todo, - &DryBuffer[FrontLeft][total], 0, 1, - &DryBuffer[FrontRight][total], 0, 1); - total += todo; - synth->SamplesSinceLast += todo; - synth->SamplesToNext -= todo; + time = MidiSynth_getNextEvtTime(synth); + diff = maxu64(time, curtime) - curtime; + if(diff >= MIDI_CLOCK_RES || time == UINT64_MAX) + { + /* If there's no pending event, or if it's more than 1 second + * away, do as many samples as we can. */ + tonext = INT_MAX; } else { - ALuint64 time = synth->NextEvtTime; - if(time == UINT64_MAX) - { - synth->SamplesSinceLast += SamplesToDo-total; - fluid_synth_write_float(self->Synth, SamplesToDo-total, - &DryBuffer[FrontLeft][total], 0, 1, - &DryBuffer[FrontRight][total], 0, 1); - break; - } - - synth->SamplesSinceLast -= (time - synth->LastEvtTime) * synth->SamplesPerTick; - synth->SamplesSinceLast = maxd(synth->SamplesSinceLast, 0.0); - synth->LastEvtTime = time; - FSynth_processQueue(self, time); + /* Figure out how many samples until the next event. */ + tonext = (ALint)(((diff * synth->SampleRate)+(MIDI_CLOCK_RES-1)) / MIDI_CLOCK_RES); + tonext -= total; + } - synth->NextEvtTime = MidiSynth_getNextEvtTime(synth); - if(synth->NextEvtTime != UINT64_MAX) - synth->SamplesToNext += (synth->NextEvtTime - synth->LastEvtTime) * synth->SamplesPerTick; + if(tonext > 0) + { + ALuint todo = mini(tonext, SamplesToDo-total); + fluid_synth_write_float(self->Synth, todo, DryBuffer[FrontLeft], total, 1, + DryBuffer[FrontRight], total, 1); + total += todo; + tonext -= todo; } + if(total < SamplesToDo && tonext == 0) + FSynth_processQueue(self, time); } + + synth->SamplesDone += SamplesToDo; + synth->ClockBase += (synth->SamplesDone/synth->SampleRate) * MIDI_CLOCK_RES; + synth->SamplesDone %= synth->SampleRate; } |