'--------------------------------------------------------------------------------------------- ' ' Music Macro Language (MML) player. Based on old-school BASIC implementations for PC-Speaker. ' https://en.wikipedia.org/wiki/Music_Macro_Language ' ' ====> Needs TinySoundFont: https://github.com/schellingb/TinySoundFont <===== ' ' TSF_TEMPO : variable to set the tempo, value 0-127. Higher is faster. ' ' TSF_VOICE : variable to set the voice, value 0-127. Use voice index from the standard MIDI table. ' -- See also https://en.wikipedia.org/wiki/General_MIDI -- ' ' TSF_VOLUME : default volume, value 0-127 ' ' TSF_FONT(f$) : the SFX2 sound font to be used for the rendering ' ' TSF_FONT_CLOSE : Release current font ' ' TSF_OPEN(m$) : send MML string to be played. Case insensitive. Returns song ID. ' ' TSF_PLAY(songID) : start playing ' ' TSF_PAUSE(songID) : Pause playing the MML ' ' TSF_STOP(songID) : Stop playing the MML ' ' TSF_BUSY(songID) : Check if playing is still going on ' ' TSF_CLOSE(songID) : Free all song resources ' '--------------------------------------------------------------------------------------------- ' MML Syntax taken from Wikipedia: ' ' cdefgab: the letters a to g correspond to the musical pitches and cause the corresponding note ' to be played. Sharp notes are produced by appending a + or #, and flat notes by appending ' a -. The length of a note is specified by appending a number representing its length as a ' fraction of a whole note — for example, c8 represents a C eighth note, and f+2 an F♯ half note. ' p r: a pause or rest. The length of the rest is specified in the same manner as the length of a ' note. For example, r1 produces a whole rest. ' o: followed by a number, o selects the octave the instrument will play in. ' > < : Used to step up or down one octave. ' l: Followed by a number, specifies the default length used by notes or rests which do not explicitly ' define one. For example, l8 g a b g l16 g a b g produces a series of four eighth notes followed ' by a series of four sixteenth notes. ' v: followed by a number, sets the volume of the instrument (0-127). ' t: followed by a number, sets the tempo in beats per minute (0-127). '--------------------------------------------------------------------------------------------- PRAGMA INCLUDE TinySoundFont-master/tsf.h : ' Include the TinyFonts header file PRAGMA OPTIONS -DTSF_IMPLEMENTATION CONST TSF_Rate = 44100 CONST TSF_Chunk = 44100 : ' Same as sample rate DECLARE All_Notes[] = { 9, 11, 0, 2, 4, 5, 7, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, -1 } OPTION MEMTYPE short RECORD TSF LOCAL tinysf TYPE tsf* LOCAL buffer[1] TYPE int LOCAL data TYPE short* END RECORD TSF.tinysf = NULL PROTO tsf_set_output, tsf_note_off, tsf_note_on, tsf_render_short, tsf_close '------------------------------------------------------------------------------------ SUB TSF_FONT(font$) IF NOT(FILEEXISTS(font$)) THEN EPRINT "ERROR: cannot find file '", font$, "'! Exiting..." END 1 END IF TSF.tinysf = tsf_load_filename(font$) END SUB '------------------------------------------------------------------------------------ SUB TSF_OPEN(tone$) LOCAL offset, octave, ldur, note, duration, source TYPE int LOCAL volume, tmp TYPE double LOCAL x$ IF TSF.tinysf = NULL THEN EPRINT "ERROR: No font loaded! Exiting..." END 1 END IF 'PRINT tsf_get_presetname(TSF.tinysf, TSF_VOICE) FORMAT "Voice is: '%s'\n" ' Calculate necessary memory to be at least available TSF.data = MEMORY(TSF_Chunk*AMOUNT(tone$)*2) tsf_set_output(TSF.tinysf, TSF_MONO, TSF_Rate, 0) ' Init octave octave = 4 ' Volume volume = TSF_VOLUME/127 ' Default duration ldur = TSF_TEMPO FOR x$ IN LCASE$(tone$) SELECT LEFT$(x$, 1) CASE "o" octave = VAL(MID$(x$, 2)) CASE ">" INCR octave IF octave > 10 THEN octave = 10 CASE "<" DECR octave IF octave < 0 THEN octave = 0 CASE "t" TEMPO = VAL(MID$(x$, 2)) CASE "l" ldur = VAL(MID$(x$, 2)) CASE "v" volume = VAL(MID$(x$, 2))/127 CASE "c"; CASE "d"; CASE "e"; CASE "f"; CASE "g"; CASE "a"; CASE "b"; CASE "p"; CASE "r" ' Determine note note = All_Notes[ASC(LEFT$(x$, 1))-97] ' If pause IF note < 0 THEN tmp = volume volume = 0 ENDIF ' Determine sharp / flat note SELECT MID$(x$, 2, 1) CASE "+"; CASE "#" INCR note x$ = MID$(x$, 3) CASE "-" DECR note x$ = MID$(x$, 3) DEFAULT x$ = MID$(x$, 2) ENDSELECT ' Determine duration IF LEN(x$) THEN duration = VAL(MID$(x$, 1)) ELSE duration = ldur ENDIF ' Render the note tsf_note_on(TSF.tinysf, TSF_VOICE, octave*12+note, volume) tsf_render_short(TSF.tinysf, TSF.data+offset, TSF_Chunk, 0) tsf_note_off(TSF.tinysf, TSF_VOICE, octave*12+note) ' Position in memory INCR offset, TSF_Chunk/duration ENDSELECT IF note < 0 THEN volume = tmp NEXT ' Error cleanup alGetError() alGenBuffers(1, TSF.buffer) alBufferData(TSF.buffer[0], AL_FORMAT_MONO16, TSF.data, offset*2+TSF_Chunk, TSF_Rate) alGenSources(1, &source) alSourceQueueBuffers(source, 1, TSF.buffer) RETURN source END FUNCTION '------------------------------------------------------------------------------------ SUB TSF_PLAY(int Source) alSourcePlay(Source) END SUB '------------------------------------------------------------------------------------ SUB TSF_PAUSE(int Source) alSourcePause(Source) END SUB '------------------------------------------------------------------------------------ SUB TSF_STOP(int Source) alSourceStop(Source) END SUB '------------------------------------------------------------------------------------ FUNCTION TSF_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 TSF_CLOSE(int Source) alDeleteSources(1, &Source) alDeleteBuffers(1, TSF.buffer) FREE TSF.data END SUB '------------------------------------------------------------------------------------ SUB TSF_FONT_CLOSE tsf_close(TSF.tinysf) END SUB '------------------------------------------------------------------------------------