' 70DF9C14493E34A7C18FD26274A6AFBBC64C0E0D521413A9BBAAFF99B4770828E57B4FE95907BB83E68489237B0E9D229FAB39C4F9E39A762369FAAFB63C71BA ' sRipeTech ' 4BBED2287A 2024-07-03 18:13:50 ' ------------------------------------ ' 2024-07-03 ' A script for pwScripter ' Playing sounds using the MIDI interface. ' ' ------------------------------------ ' delete any current user defined script/variables/functions ... newscript ' break$flag = true ' ------------------------------------ ' link to the DLL Winmm ' (open DLLs are automatically closed when the next newscript command executes). dim myDLL = dll.open ("Winmm") IF ( myDLL == 0 ) THEN Message ( "Could not open the Winmm DLL file" ) STOP END IF ' ------------------------------------ ' link to some MIDI functions in that DLL dim midiOutOpen = dll.link(myDLL, "midiOutOpen",5,"unsigned" ) IF ( ! midiOutOpen ) THEN Message ( "Could not find the named procedure" ) STOP END IF ' following is done without error checking ' is really a bit lazy/bad programming dim midiOutShortMsg = dll.link(myDLL, "midiOutShortMsg" ,2,"unsigned" ) dim midiOutSetVolume = dll.link(myDLL, "midiOutSetVolume",2,"unsigned" ) dim midiOutClose = dll.link(myDLL, "midiOutClose" ,1,"unsigned" ) ' ------------------------------------ ' in MIDI middle C = 60 = 0x40 = "C"4 ' there are 12 tones in an octave ' ------------------------------------ ' a helper function to reduce typing when sending a MIDI command FUNCTION midiMsg ( hmo , status , firstByte, secondByte ) dim msg = 0 msg = (msg * 0x0100) + ( secondByte BAND 0x0ff ) msg = (msg * 0x0100) + ( firstByte BAND 0x0ff ) msg = (msg * 0x0100) + ( Status BAND 0x0ff ) midiOutShortMsg ( hmo , msg ) END function ' ------------------------------------ dim HandleMidiOut ' ------------------------------------ ' BEWARNED: once a MIDI device is OPENED it can not be reopened. ' So do not run this script more than once for a given pwScripter instance. ' UNLESS you explicitly close that MIDI device first ' OR terminate pwScripter and redo everything. ' ' HOWEVER: the function onNewScript is predefined. ' It is called when a NEWSCRIPT command is executed as the very first step! ' i.e. before any other NEWSCIPT action! ' It's default action is - no action - ' BUT we can extend it's action to close an open midi device FUNCTION onNewScript () ' ------------------------------------ ' define the extended functionality IF HandleMidiOut then midiOutClose( HandleMidiOut ) END if ' ------------------------------------ ' then call the previous functionality which in this case is do nothing ' But technically we can have multiple instances of onNewScript ' each tiding up a specific bit of coding, then using the INHERIT command ' we can daisy chain them together. INHERIT onNewScript () END Function ' ------------------------------------ ' open and configure a midi device midiOutOpen ( @ HandleMidiOut ,0,0,0,0) ' Note use of the @ to pass address of HandleMidiOut IF HandleMidiOut == 0 then message ("could not initialise Midi player on midi interface 0", "TROUBLE" ) END if midiMsg ( HandleMidiOut , 0xb0 ,0x78 , 0x00 ) ' midi off midiMsg ( HandleMidiOut , 0xb0 ,0x07 , 0x07f BAND 0x0ff ) ' midi volume ' ------------------------------------------------------------------------------------------ break "now play some notes (actually C major )" ' ------------------------------------------------------------------------------------------ midiMsg ( HandleMidiOut , 0x90 , 0x40 , 0x7f ) midiMsg ( HandleMidiOut , 0x90 , 0x44 , 0x7f ) midiMsg ( HandleMidiOut , 0x90 , 0x47 , 0x7f ) ' ------------------------------------ ' FROM HERE ON THERE IS NOTHING NEW IN TERMS OF MIDI FUNCTIONALITY ' ------------------------------------ ' wait 500 ms and play C minor ' use the built-in SLEEP command, it blocks execution! sleep (500) ' stop execution for 500 milliseconds ' ------------------------------------------------------------------------------------------ break " play C minor " ' ------------------------------------------------------------------------------------------ midiMsg ( HandleMidiOut , 0x90 , 0x40 , 0x7f ) midiMsg ( HandleMidiOut , 0x90 , 0x43 , 0x7f ) midiMsg ( HandleMidiOut , 0x90 , 0x47 , 0x7f ) ' ------------------------------------ ' wait 500 ms and play C major sleep (500) midiMsg ( HandleMidiOut , 0x90 , 0x40 , 0x7f ) midiMsg ( HandleMidiOut , 0x90 , 0x44 , 0x7f ) midiMsg ( HandleMidiOut , 0x90 , 0x47 , 0x7f ) sleep (500) ' ------------------------------------ ' Define the TRAID function to "play" 3 notes at a time ' ------------------------------------------------------------------------------------------ break " play a sequence of rising triads" ' ------------------------------------------------------------------------------------------ FUNCTION triad ( p0 , p1 , p2 ) if ( p0 ) then midiMsg ( HandleMidiOut , 0x90 , p0 , 0x7f ) if ( p1 ) then midiMsg ( HandleMidiOut , 0x90 , p1 , 0x7f ) if ( p2 ) then midiMsg ( HandleMidiOut , 0x90 , p2 , 0x7f ) END function ' ------------------------------------ ' use the built-in TIMER command, it does NOT block execution ' when called it will play C minor FUNCTION cbTimer ( timeId ) triad ( 0x40 , 0x43 , 0x47 ) END function ' in 1000 milliseconds call the function cbTimer timer ( 0 , 0 , 1000 , "cbTimer" ) ' ------------------------------------ ' create an array that holds 3 notes and a timer value per entry. ' We will "play" an array entry of 3 notes ' then start another timer that waits for a number of milliseconds ' and then play the next array entry of 3 notes. dim Notes ( 64, 4 ) as byte ' but in this demo we only use 8 of the 64 entries! FUNCTION PopulateNotes ( i , n0, n1, n2 , duration ) report ( istype ( i ) & " "& istype ( duration)) ' we use this to help populate the array report ( istype ( i ) & " "& istype ( duration) & " " & istype (n0) ) Notes ( i , 0 ) = n0 Notes ( i , 1 ) = n1 Notes ( i , 2 ) = n2 Notes ( i , 3 ) = duration END FUNCTION ' ------------------------------- ' define the "tune" as being 7 sets of triads ' the interval between TRIADS is defined in milliseconds dim interval = 300 dim beat dim root = 12 * 3 ' root note FOR beat = 0 to 6 PopulateNotes ( beat , root, root + 4 , root + 7 , 1 ) root = root + 12 ' raise root noe by one octave NEXT beat ' marks the END of the "tune", with a DURATION = 0 (= last parameter in PopulateNotes call). PopulateNotes ( beat , root, root + 4 , root + 7 , 0 ) ' ------------------------------- ' define the timer handling function ' Technically the musical beat is slightly less inaccurate ' if the "next" timer is triggered before the current TRIAD is played. FUNCTION cbTimerBeat ( timeId ) IF ( Notes ( beat, 3 ) ) then timer ( 0 , 0 , interval * Notes ( beat, 3 ) , "cbTimerBeat" ) triad ( Notes ( beat, 0 ) , Notes ( beat, 1 ) , Notes ( beat, 2 ) ) END if ' move to the next triad's entry beat++ END function ' ------------------------------- ' start "playing" the "tune" beat = 0 timer ( 0 , 0 , 1 , "cbTimerBeat" )