Remove the DYNLOAD option. It's buggy and causes problems.
[alure.git] / src / codec_fluidsynth.cpp
blob48a49db8fea3150186917c3e6283e545722b1d60
1 /*
2 * ALURE OpenAL utility library
3 * Copyright (c) 2009-2010 by Chris Robinson.
5 * Permission is hereby granted, free of charge, to any person obtaining a copy
6 * of this software and associated documentation files (the "Software"), to
7 * deal in the Software without restriction, including without limitation the
8 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9 * sell copies of the Software, and to permit persons to whom the Software is
10 * furnished to do so, subject to the following conditions:
12 * The above copyright notice and this permission notice shall be included in
13 * all copies or substantial portions of the Software.
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21 * IN THE SOFTWARE.
24 #include "config.h"
26 #include "main.h"
28 #include <string.h>
29 #include <assert.h>
30 #ifdef _WIN32
31 #include <io.h>
32 #else
33 #include <unistd.h>
34 #endif
36 #include <istream>
38 #include <fluidsynth.h>
41 struct fluidStream : public alureStream {
42 private:
43 static const ALubyte MIDI_CHANNEL_MASK = 0x0F;
44 static const ALubyte MIDI_EVENT_MASK = 0xF0;
46 static const ALubyte MIDI_NOTEOFF = 0x80; // + note + velocity
47 static const ALubyte MIDI_NOTEON = 0x90; // + note + velocity
48 static const ALubyte MIDI_POLYPRESS = 0xA0; // + pressure (2 bytes)
49 static const ALubyte MIDI_CTRLCHANGE = 0xB0; // + ctrl + value
50 static const ALubyte MIDI_PRGMCHANGE = 0xC0; // + new patch
51 static const ALubyte MIDI_CHANPRESS = 0xD0; // + pressure (1 byte)
52 static const ALubyte MIDI_PITCHBEND = 0xE0; // + pitch bend (2 bytes)
53 static const ALubyte MIDI_SPECIAL = 0xF0; // Special event
55 static const ALubyte MIDI_SYSEX = 0xF0; // SysEx begin
56 static const ALubyte MIDI_SYSEXEND = 0xF7; // SysEx end
57 static const ALubyte MIDI_SONGPOS = 0xF2; // Song position
58 static const ALubyte MIDI_SONGSEL = 0xF3; // Song select
59 static const ALubyte MIDI_META = 0xFF; // Meta event begin
61 static const ALubyte MIDI_META_EOT = 0x2F; // End-of-track
62 static const ALubyte MIDI_META_TEMPO = 0x51; // Tempo change
64 struct MidiTrack {
65 std::vector<ALubyte> data;
66 size_t Offset;
67 ALubyte LastEvent;
68 ALdouble SamplesLeft;
70 MidiTrack() : Offset(0), LastEvent(0), SamplesLeft(0.)
71 { }
72 MidiTrack(const MidiTrack &rhs)
73 : data(rhs.data), Offset(rhs.Offset), LastEvent(rhs.LastEvent),
74 SamplesLeft(rhs.SamplesLeft)
75 { }
77 void Reset()
79 Offset = 0;
80 LastEvent = 0;
81 SamplesLeft = 0.;
84 const MidiTrack& operator=(const MidiTrack &rhs)
86 data = rhs.data;
87 Offset = rhs.Offset;
88 LastEvent = rhs.LastEvent;
89 SamplesLeft = rhs.SamplesLeft;
90 return *this;
93 unsigned long ReadVarLen()
95 if(Offset >= data.size())
96 return 0;
98 unsigned long len = data[Offset]&0x7F;
99 while((data[Offset]&0x80))
101 if(++Offset >= data.size())
102 return 0;
103 len = (len<<7) | (data[Offset]&0x7F);
105 Offset++;
107 return len;
111 ALuint Divisions;
112 std::vector<MidiTrack> Tracks;
114 ALenum format;
115 ALsizei sampleRate;
116 ALdouble samplesPerTick;
118 fluid_settings_t *fluidSettings;
119 fluid_synth_t *fluidSynth;
120 int fontID;
121 bool doFontLoad;
123 public:
124 static void Init() { }
125 static void Deinit() { }
127 virtual bool IsValid()
128 { return fluidSynth != NULL; }
130 virtual bool GetFormat(ALenum *fmt, ALuint *frequency, ALuint *blockalign)
132 if(format == AL_NONE)
134 format = GetSampleFormat(2, 32, true);
135 if(format == AL_NONE)
136 format = AL_FORMAT_STEREO16;
138 *fmt = format;
139 *frequency = sampleRate;
140 *blockalign = 2 * ((format==AL_FORMAT_STEREO16) ? sizeof(ALshort) :
141 sizeof(ALfloat));
142 return true;
145 virtual ALuint GetData(ALubyte *data, ALuint bytes)
147 ALuint ret;
149 if(doFontLoad)
151 doFontLoad = false;
152 const char *soundfont = getenv("FLUID_SOUNDFONT");
153 if(soundfont && soundfont[0])
154 fontID = fluid_synth_sfload(fluidSynth, soundfont, true);
157 if(format == AL_FORMAT_STEREO16)
159 ALshort *ptr = reinterpret_cast<ALshort*>(data);
160 ret = FillBuffer(ptr, bytes/2/sizeof(ALshort));
161 ret *= 2 * sizeof(ALshort);
163 else
165 ALfloat *ptr = reinterpret_cast<ALfloat*>(data);
166 ret = FillBuffer(ptr, bytes/2/sizeof(ALfloat));
167 ret *= 2 * sizeof(ALfloat);
170 return ret;
173 virtual bool Rewind()
175 for(std::vector<MidiTrack>::iterator i = Tracks.begin(), end = Tracks.end();i != end;i++)
177 i->Reset();
178 unsigned long val = i->ReadVarLen();
179 i->SamplesLeft += val * samplesPerTick;
181 fluid_synth_program_reset(fluidSynth);
182 UpdateTempo(500000);
183 return true;
186 virtual bool SetPatchset(const char *sfont)
188 if(UsingSTDIO)
190 int newid = fluid_synth_sfload(fluidSynth, sfont, true);
191 if(newid == FLUID_FAILED)
193 SetError("Failed to load soundfont");
194 return false;
197 if(fontID != FLUID_FAILED)
198 fluid_synth_sfunload(fluidSynth, fontID, true);
199 fontID = newid;
200 doFontLoad = false;
201 return true;
204 /* FluidSynth has no way to load a soundfont using IO callbacks. So we
205 * have to copy the specified file using the callbacks to a regular
206 * file that FluidSynth can open. */
207 int newid = FLUID_FAILED;
208 InStream istream(sfont);
209 if(istream.fail())
211 SetError("Failed to open file");
212 return false;
215 /* First, get a temp filename */
216 const char *str = getenv("TEMP");
217 if(!str || !str[0]) str = getenv("TMP");
218 #ifdef _WIN32
219 if(!str || !str[0]) str = ".";
220 #else
221 if(!str || !str[0]) str = "/tmp";
222 #endif
223 std::string fname = str;
224 fname += "/alure-sfont-XXXXXX";
226 for(size_t i = 0;i < fname.size();i++)
228 if(fname[i] == '\\')
229 fname[i] = '/';
232 std::vector<char> tmpfname(fname.begin(), fname.end());
233 tmpfname.push_back(0);
235 /* Open a temp file */
236 int fd = -1;
237 FILE *file;
238 #ifdef _WIN32
239 if(mktemp(&tmpfname[0]) == NULL || (file=fopen(&tmpfname[0], "wb")) == NULL)
240 #else
241 if((fd=mkstemp(&tmpfname[0])) == -1 || (file=fdopen(fd, "wb")) == NULL)
242 #endif
244 if(fd >= 0)
246 close(fd);
247 remove(&tmpfname[0]);
249 SetError("Failed to create temp file");
250 return false;
253 bool copyok = false;
254 char buf[4096];
255 size_t got;
256 do {
257 istream.read(buf, sizeof(buf));
258 if((got=istream.gcount()) == 0)
260 copyok = true;
261 break;
263 } while(fwrite(buf, 1, got, file) == got);
265 if(copyok)
267 fflush(file);
268 newid = fluid_synth_sfload(fluidSynth, &tmpfname[0], true);
271 fclose(file);
272 remove(&tmpfname[0]);
274 if(!copyok)
276 SetError("Failed to copy file");
277 return false;
280 if(newid == FLUID_FAILED)
282 SetError("Failed to load soundfont");
283 return false;
286 if(fontID != FLUID_FAILED)
287 fluid_synth_sfunload(fluidSynth, fontID, true);
288 fontID = newid;
289 doFontLoad = false;
291 return true;
294 fluidStream(std::istream *_fstream)
295 : alureStream(_fstream), Divisions(100),
296 format(AL_NONE), sampleRate(48000), samplesPerTick(1.),
297 fluidSettings(NULL), fluidSynth(NULL), fontID(FLUID_FAILED),
298 doFontLoad(true)
300 ALCdevice *device = alcGetContextsDevice(alcGetCurrentContext());
301 if(device) alcGetIntegerv(device, ALC_FREQUENCY, 1, &sampleRate);
303 char hdr[4];
304 if(!fstream->read(hdr, 4))
305 return;
307 if(memcmp(hdr, "MThd", 4) == 0)
309 ALuint len = read_be32(fstream);
310 if(len != 6)
311 return;
313 int type = read_be16(fstream);
314 if(type != 0 && type != 1)
315 return;
317 ALuint numtracks = read_be16(fstream);
319 Divisions = read_be16(fstream);
320 UpdateTempo(500000);
322 Tracks.resize(numtracks);
323 for(std::vector<MidiTrack>::iterator i = Tracks.begin(), end = Tracks.end();i != end;i++)
325 if(!fstream->read(hdr, 4) || memcmp(hdr, "MTrk", 4) != 0)
326 return;
328 ALint len = read_be32(fstream);
329 i->data.resize(len);
330 if(!fstream->read(reinterpret_cast<char*>(&i->data[0]), len) ||
331 fstream->gcount() != len)
332 return;
334 unsigned long val = i->ReadVarLen();
335 i->SamplesLeft += val * samplesPerTick;
337 SetupSynth();
341 virtual ~fluidStream()
343 if(fontID != FLUID_FAILED)
344 fluid_synth_sfunload(fluidSynth, fontID, true);
345 fontID = FLUID_FAILED;
347 if(fluidSynth != NULL)
348 delete_fluid_synth(fluidSynth);
349 fluidSynth = NULL;
351 if(fluidSettings != NULL)
352 delete_fluid_settings(fluidSettings);
353 fluidSettings = NULL;
356 private:
357 template<typename T>
358 ALuint FillBuffer(T *Buffer, ALuint BufferSamples)
360 ALuint SamplesInBuffer = 0;
361 while(SamplesInBuffer < BufferSamples)
363 // Check if any tracks are still playing and how many samples are waiting to render
364 size_t TracksPlaying = 0;
365 ALuint SamplesToDo = BufferSamples - SamplesInBuffer;
366 for(std::vector<MidiTrack>::iterator i = Tracks.begin(),
367 end = Tracks.end();i != end;i++)
369 if(i->Offset < i->data.size())
371 SamplesToDo = std::min<ALuint>(SamplesToDo, i->SamplesLeft);
372 TracksPlaying++;
375 if(TracksPlaying == 0)
376 break;
378 if(SamplesToDo == 0)
380 ProcessMidi();
381 continue;
384 // Render samples
385 WriteSamples(SamplesToDo, Buffer);
386 Buffer += SamplesToDo*2;
387 SamplesInBuffer += SamplesToDo;
389 for(std::vector<MidiTrack>::iterator i = Tracks.begin(),
390 end = Tracks.end();i != end;i++)
392 if(i->Offset < i->data.size())
393 i->SamplesLeft -= SamplesToDo;
397 return SamplesInBuffer;
400 void WriteSamples(ALuint count, short *buffer)
401 { fluid_synth_write_s16(fluidSynth, count, buffer, 0, 2, buffer, 1, 2); }
402 void WriteSamples(ALuint count, float *buffer)
403 { fluid_synth_write_float(fluidSynth, count, buffer, 0, 2, buffer, 1, 2); }
405 void ProcessMidi()
407 ALuint newtempo = 0;
409 // Process more events
410 std::vector<MidiTrack>::iterator i=Tracks.begin(), end=Tracks.end();
411 while(i != end)
413 if(i->Offset >= i->data.size() || i->SamplesLeft >= 1.)
415 i++;
416 continue;
419 if(i->data.size() - i->Offset < 3)
421 i->Offset = i->data.size();
422 i++;
423 continue;
426 ALubyte event = i->data[i->Offset++];
427 ALubyte parm1, parm2;
428 if(!(event&0x80))
430 event = i->LastEvent;
431 i->Offset--;
433 if((event&MIDI_EVENT_MASK) != MIDI_SPECIAL)
434 i->LastEvent = event;
435 parm1 = i->data[i->Offset];
436 parm2 = i->data[i->Offset+1];
438 int channel = event&MIDI_CHANNEL_MASK;
439 switch(event&MIDI_EVENT_MASK)
441 case MIDI_NOTEOFF:
442 fluid_synth_noteoff(fluidSynth, channel, parm1);
443 i->Offset += 2;
444 break;
445 case MIDI_NOTEON:
446 fluid_synth_noteon(fluidSynth, channel, parm1, parm2);
447 i->Offset += 2;
448 break;
449 case MIDI_POLYPRESS:
450 i->Offset += 2;
451 break;
453 case MIDI_CTRLCHANGE:
454 fluid_synth_cc(fluidSynth, channel, parm1, parm2);
455 i->Offset += 2;
456 break;
457 case MIDI_PRGMCHANGE:
458 fluid_synth_program_change(fluidSynth, channel, parm1);
459 i->Offset += 1;
460 break;
462 case MIDI_CHANPRESS:
463 fluid_synth_channel_pressure(fluidSynth, channel, parm1);
464 i->Offset += 1;
465 break;
467 case MIDI_PITCHBEND:
468 fluid_synth_pitch_bend(fluidSynth, channel, (parm1&0x7F) | ((parm2&0x7F)<<7));
469 i->Offset += 2;
470 break;
472 case MIDI_SPECIAL:
473 switch(event)
475 case MIDI_SYSEX:
477 unsigned long len = i->ReadVarLen();
478 if(i->data.size() - i->Offset < len)
480 i->Offset = i->data.size();
481 break;
484 if(i->data[i->Offset+len-1] == MIDI_SYSEXEND)
486 char *data = reinterpret_cast<char*>(&i->data[i->Offset]);
487 fluid_synth_sysex(fluidSynth, data, len-1, NULL, NULL, NULL, false);
489 i->Offset += len;
490 break;
492 case MIDI_SYSEXEND:
494 unsigned long len = i->ReadVarLen();
495 if(i->data.size() - i->Offset < len)
497 i->Offset = i->data.size();
498 break;
500 i->Offset += len;
501 break;
504 case MIDI_SONGPOS:
505 i->Offset += 2;
506 break;
508 case MIDI_SONGSEL:
509 i->Offset += 1;
510 break;
512 case MIDI_META:
514 ALubyte metatype = i->data[i->Offset++];
515 unsigned long val = i->ReadVarLen();
517 if(i->data.size() - i->Offset < val)
519 i->Offset = i->data.size();
520 break;
523 if(metatype == MIDI_META_EOT)
525 i->Offset = i->data.size();
526 break;
529 if(metatype == MIDI_META_TEMPO && val >= 3)
531 newtempo = (i->data[i->Offset] << 16) |
532 (i->data[i->Offset+1] << 8) |
533 (i->data[i->Offset+2]);
536 i->Offset += val;
537 break;
540 default:
541 /* The rest of the special events don't have any
542 * data bytes */
543 break;
545 break;
547 default:
548 /* Shouldn't ever get to here */
549 break;
552 unsigned long val = i->ReadVarLen();
553 i->SamplesLeft += val * samplesPerTick;
555 if(newtempo)
556 UpdateTempo(newtempo);
559 void UpdateTempo(ALuint tempo)
561 ALdouble sampletickrate = sampleRate / (1000000. / tempo) / Divisions;
563 for(std::vector<MidiTrack>::iterator i = Tracks.begin(),
564 end = Tracks.end();i != end;i++)
566 if(i->Offset >= i->data.size())
567 continue;
568 i->SamplesLeft = i->SamplesLeft / samplesPerTick * sampletickrate;
570 samplesPerTick = sampletickrate;
573 void SetupSynth()
575 fluidSettings = new_fluid_settings();
576 if(fluidSettings)
578 fluid_settings_setnum(fluidSettings, "synth.gain", 0.5);
579 fluid_settings_setstr(fluidSettings, "synth.reverb.active", "yes");
580 fluid_settings_setstr(fluidSettings, "synth.chorus.active", "yes");
581 fluid_settings_setint(fluidSettings, "synth.polyphony", 256);
582 fluid_settings_setnum(fluidSettings, "synth.sample-rate", (double)sampleRate);
584 fluidSynth = new_fluid_synth(fluidSettings);
588 static DecoderDecl<fluidStream,0> fluidStream_decoder;
589 Decoder &alure_init_fluidsynth(void)
590 { return fluidStream_decoder; }