'------------------------------------------------------------------------------------------------------------ ' ' This is a libfluidsynth MIDI player plugin for the OPENAL context. PvE - MIT License 2016. ' ' Updated: added SYN_BUFFER call. Sept 2017 - PvE. ' Updated: improved order of functions, May 2018 - PvE. ' ' Web address: http://fluidsynth.sourceforge.net ' '------------------------------------------------------------------------------------------------------------ ' ' SYN_OPEN(file$, font$) ' => Load the MIDI file and the sound font, song ID is returned ' ' SYN_BUFFER(memory pointer, size, font$) ' => Point to the MIDI data in memory with length and sound font, song ID is returned ' ' SYN_PLAY(songID) ' => Start playing the MIDI file ' ' SYN_PAUSE(songID) ' => Pause playing the MIDI file ' ' SYN_STOP(songID) ' => Stop playing the MIDI file ' ' SYN_UPDATE(songID) ' => Keep the OpenAL buffers filled, to be used in callback function. ' ' SYN_BUSY(songID) ' => Check if we're still playing (TRUE) or not e.g. song is finished (FALSE) ' ' SYN_VOLUME(songID, volume) ' => Set the volume of the source in range 0.0 - 1.0 ' ' SYN_CLOSE(songID) ' => Free resources for the current file ' ' SYN_FREE ' => Free all memory resources for the complete MIDI context ' '------------------------------------------------------------------------------------------------------------ RECORD SYN LOCAL chunk : ' Sample rate is same as chunk size we will be parsing LOCAL data TYPE short* LOCAL buffer[2] TYPE int LOCAL settings, player, synth TYPE void* END RECORD CALL SYN_INIT '------------------------------------------------------------------------------------------------------------ OPTION MEMTYPE short SYN.chunk = 44100 : ' Default sample rate = chunk size SYN.data = MEMORY(SYN.chunk) : ' MIDI working area '------------------------------------------------------------------------------------------------------------ SUB SYN_INIT LOCAL lib$ LOCAL seq = -1 CATCH GOTO lib_import_retry lib$ = "libfluidsynth.so.0" LABEL lib_import_retry INCR seq IF seq > 100 THEN PRINT "ERROR: libfluidsynth not found on this system! Exiting..." END 1 ENDIF lib$ = LEFT$(lib$, INSTRREV(lib$, ".")) & STR$(seq) IMPORT "new_fluid_settings(void)" FROM lib$ TYPE void* CATCH RESET IMPORT "new_fluid_synth(void*)" FROM lib$ TYPE void* IMPORT "new_fluid_player(void*)" FROM lib$ TYPE void* IMPORT "fluid_is_soundfont(char*)" FROM lib$ TYPE int IMPORT "fluid_synth_sfload(void*,char*,int)" FROM lib$ TYPE int IMPORT "fluid_is_midifile(char*)" FROM lib$ TYPE int IMPORT "fluid_player_add(void*,char*)" FROM lib$ TYPE int IMPORT "fluid_player_add_mem(void*,void*,size_t)" FROM lib$ TYPE int IMPORT "fluid_player_play(void*)" FROM lib$ TYPE int IMPORT "fluid_player_get_status(void*)" FROM lib$ TYPE int IMPORT "fluid_settings_setint(void*,char*,int)" FROM lib$ TYPE int IMPORT "fluid_settings_setstr(void*,char*,char*)" FROM lib$ TYPE int IMPORT "fluid_synth_write_s16(void*,int,void*,int,int,void*,int,int)" FROM lib$ TYPE int IMPORT "delete_fluid_player(void*)" FROM lib$ TYPE int IMPORT "delete_fluid_synth(void*)" FROM lib$ TYPE int IMPORT "delete_fluid_settings(void*)" FROM lib$ TYPE void IMPORT "fluid_synth_set_interp_method(void*,int,int)" FROM lib$ TYPE int CONST FLUID_INTERP_NONE = 0 : ' No interpolation, Fastest, but questionable audio quality CONST FLUID_INTERP_LINEAR = 1 : ' Straight-line interpolation, A bit slower, reasonable audio quality CONST FLUID_INTERP_4THORDER = 4 : ' Fourth-order interpolation, good quality, the default CONST FLUID_INTERP_7THORDER = 7 : ' Seventh-order interpolation CONST FLUID_PLAYER_READY = 0 : ' Player is ready CONST FLUID_PLAYER_PLAYING = 1 : ' Player is currently playing CONST FLUID_PLAYER_DONE = 2 : ' Player is finished playing END SUB FUNCTION SYN_OPEN(file$, font$) LOCAL source TYPE int IF NOT(FILEEXISTS(file$)) THEN PRINT "ERROR: cannot find file '", file$, "'! Exiting..." END 1 END IF IF NOT(fluid_is_midifile(file$)) THEN PRINT "ERROR: file '", file$, "' is not a MIDI file! Exiting..." END 1 END IF IF NOT(fluid_is_soundfont(font$)) THEN PRINT "ERROR: file '", font$, "' is not a SF2 soundfont file! Exiting..." END 1 END IF SYN.settings = new_fluid_settings() fluid_settings_setint(SYN.settings, "synth.audio-channels", 1) : ' One stereo pair fluid_settings_setint(SYN.settings, "synth.sample-rate", 44100) SYN.synth = new_fluid_synth(SYN.settings) fluid_settings_setint(SYN.settings, "player.reset-synth ", 1) fluid_settings_setstr(SYN.settings, "player.timing-source", "sample") SYN.player = new_fluid_player(SYN.synth) fluid_synth_set_interp_method(SYN.synth, -1, FLUID_INTERP_7THORDER) fluid_synth_sfload(SYN.synth, font$, 1) fluid_player_add(SYN.player, file$) fluid_player_play(SYN.player) ' Error cleanup alGetError() alGenBuffers(2, SYN.buffer) fluid_synth_write_s16(SYN.synth, SYN.chunk/4, SYN.data, 0, 2, SYN.data, 1, 2) alBufferData(SYN.buffer[0], AL_FORMAT_STEREO16, SYN.data, SYN.chunk, 44100) fluid_synth_write_s16(SYN.synth, SYN.chunk/4, SYN.data, 0, 2, SYN.data, 1, 2) alBufferData(SYN.buffer[1], AL_FORMAT_STEREO16, SYN.data, SYN.chunk, 44100) alGenSources(1, &source) alSourceQueueBuffers(source, 2, SYN.buffer) ' Returned source is used as song ID RETURN source END FUNCTION FUNCTION SYN_BUFFER(void* buffer, size, font$) LOCAL source TYPE int IF NOT(fluid_is_soundfont(font$)) THEN PRINT "ERROR: file '", font$, "' is not a SF2 soundfont file! Exiting..." END 1 END IF fluid_synth_sfload(SYN.synth, font$, 1) fluid_player_add_mem(SYN.player, buffer, size) fluid_player_play(SYN.player) ' Error cleanup alGetError() alGenBuffers(2, SYN.buffer) fluid_synth_write_s16(SYN.synth, SYN.chunk/4, SYN.data, 0, 2, SYN.data, 1, 2) alBufferData(SYN.buffer[0], AL_FORMAT_STEREO16, SYN.data, SYN.chunk, 44100) fluid_synth_write_s16(SYN.synth, SYN.chunk/4, SYN.data, 0, 2, SYN.data, 1, 2) alBufferData(SYN.buffer[1], AL_FORMAT_STEREO16, SYN.data, SYN.chunk, 44100) alGenSources(1, &source) alSourceQueueBuffers(source, 2, SYN.buffer) ' Returned source is used as song ID RETURN source END FUNCTION SUB SYN_SHOW_INFO PRINT "Rate: 44100" PRINT "Stereo" END SUB SUB SYN_PLAY(int Source) alSourcePlay(Source) END SUB SUB SYN_PAUSE(int Source) alSourcePause(Source) END SUB SUB SYN_STOP(int Source) alSourceStop(Source) END SUB SUB SYN_UPDATE(int Source) LOCAL processed, which TYPE int alGetSourcei(Source, AL_BUFFERS_PROCESSED, &processed) IF processed AND fluid_player_get_status(SYN.player) = FLUID_PLAYER_PLAYING THEN alSourceUnqueueBuffers(Source, 1, &which) fluid_synth_write_s16(SYN.synth, SYN.chunk/4, SYN.data, 0, 2, SYN.data, 1, 2) alBufferData(which, AL_FORMAT_STEREO16, SYN.data, SYN.chunk, 44100) alSourceQueueBuffers(Source, 1, &which) ENDIF END SUB FUNCTION SYN_BUSY(int Source) LOCAL state TYPE int alGetSourcei(Source, AL_SOURCE_STATE, &state) IF state = AL_PLAYING THEN RETURN TRUE RETURN FALSE END FUNCTION SUB SYN_VOLUME(int Source, volume#) alSourcef(Source, AL_GAIN, volume#) END SUB SUB SYN_CLOSE(int Source) alDeleteSources(1, &Source) delete_fluid_player(SYN.player) delete_fluid_synth(SYN.synth) delete_fluid_settings(SYN.settings) END SUB SUB SYN_FREE FREE SYN.data alDeleteBuffers(2, SYN.buffer) END SUB