Simple on the fly audio example

Share your advanced PureBasic knowledge/code with the community.
Booger
Enthusiast
Enthusiast
Posts: 139
Joined: Tue Sep 04, 2007 2:18 pm

Simple on the fly audio example

Post by Booger »

Just wanted to say I'm still lurking around after all these years. Just wanted to share some simple fun stuff example that somebody might find useful on a boring day.

Windows:

Code: Select all

; This program creates a 250ms cyclic audio buffer using the
; low-level Windows Waveform Audio API (winmm.dll).
; It uses a callback function to refill buffers as they finish playing.
;
; This version is modified to play "Super Mario Bros. 1-1"
; using a square wave generator.

; --- Constants and Structures ---

; We need two buffers for a smooth cyclic system (double buffering)
#NUM_BUFFERS = 2


; Windows API Constants
#WAVE_MAPPER = -1
#WAVE_FORMAT_PCM = 1
#CALLBACK_FUNCTION = $30000 ; Tell waveOutOpen() we are using a callback procedure
#WOM_OPEN = $3BB          ; Sent to callback when device is opened
#WOM_DONE = $3BD          ; Sent to callback when a buffer is finished
#WOM_CLOSE = $3BC         ; Sent to callback when device is closed
#WHDR_PREPARED = $2

; --- Structure for Melody ---
Structure Note
  frequency.f
  durationMs.i
EndStructure

; Note frequencies for Mario 1-1

#C3 = 130.81
#D3 = 146.83
#Eb3 = 155.56 
#E3 = 164.81
#F3 = 174.61
#G3 = 196.00
#Ab2 = 103.83 
#Ab3 = 207.65 
#Bb2 = 116.54 
#Bb3 = 233.08 
#C4 = 261.63
#D4 = 293.66  
#E4 = 329.63  
#F4 = 349.23  
#G4 = 392.00  
#A3 = 220.00  
#B3 = 246.94  
#A4 = 440.00  
#REST = 0.0

; Note durations (tempo)
#N_Short = 100
#N_Rest_Short = 100
#N_Rest_Med = 200
#N_Rest_Long = 300
#N_Loop = 130
#N_Rest_Loop = 130


; --- Global variables for audio ---
Global hWaveOut.i                      ; Handle to the audio device
Global wfx.WAVEFORMATEX                ; Our audio format
Global Dim waveHeaders.WAVEHDR(#NUM_BUFFERS - 1) ; Array for our buffer headers
Global Dim *waveBuffers(#NUM_BUFFERS - 1)      ; Pointers to the actual audio data
Global bufferSize.l                    ; Size of one buffer (250ms)
Global waveRunning.i = #True           ; Flag to keep the callback looping

; --- Globals for Melody Playback ---
Global Dim melody.Note(36) ; 37 notes total (0-36)
Global melodyLength.i = 37
Global currentNoteIndex.i = -1 ; Start at -1 to load first note
Global samplesLeftForThisNote.i = 0
Global currentFrequency.f = 0.0
Global currentHalfCycle.d = 0.0
Global g_phase.d = 0.0 ; General phase for square wave

; --- Audio Generation ---

; Fills a buffer with a square wave based on the melody
Procedure FillBuffer(*buffer, length)
  Define *b.Word = *buffer ; Use a 16-bit WORD pointer
  Define samplesToFill.i = length / (wfx\nBlockAlign) ; nBlockAlign is 4 (16-bit stereo)
  Define i, sampleValue.i
  
  For i = 0 To samplesToFill - 1
    
    ; Check if we need to advance to the next note
    If samplesLeftForThisNote = 0
      currentNoteIndex + 1
      If currentNoteIndex >= melodyLength ; Loop song
        currentNoteIndex = 0
      EndIf
      
      ; Get new note data
      currentFrequency = melody(currentNoteIndex)\frequency
      Define durationMs.i = melody(currentNoteIndex)\durationMs
      
      ; Calculate how many samples this note lasts
      samplesLeftForThisNote = (wfx\nSamplesPerSec * durationMs) / 1000
      
      ; Reset phase for the new note
      g_phase = 0.0
      
      ; Calculate half-cycle for square wave
      If currentFrequency > 0
        currentHalfCycle = (wfx\nSamplesPerSec / currentFrequency) / 2.0
      Else
        currentHalfCycle = 0 ; Rest note
      EndIf
    EndIf
    
    ; --- Generate Square Wave Sample ---
    If currentHalfCycle = 0
      sampleValue = 0 ; Rest
    Else
      If g_phase < currentHalfCycle
        sampleValue = 10000 ; High (less than 16384 to be less harsh)
      Else
        sampleValue = -10000 ; Low
      EndIf
    EndIf
    
    ; Advance the phase for the square wave
    g_phase + 1.0
    If g_phase >= currentHalfCycle * 2
      g_phase - (currentHalfCycle * 2)
    EndIf
    
    ; Write the sample to both channels
    *b\w = sampleValue ; Left Channel
    *b + 2
    *b\w = sampleValue ; Right Channel
    *b + 2
    
    ; We've used one sample for this note
    samplesLeftForThisNote - 1
  Next
EndProcedure

; --- The Callback Procedure ---

; This is the heart of the program.
; Windows will call this function from a separate thread.
; We MUST use ProcedureC for the C-style calling convention.
ProcedureC WaveCallback(hWaveOut.i, uMsg.i, dwInstance.i, dwParam1.i, dwParam2.i)
  
  Select uMsg
    Case #WOM_OPEN
      ; Device was opened.
      Debug "Callback: Device Opened."
      
    Case #WOM_DONE
      ; A buffer has finished playing.
      ; dwParam1 is a pointer to the WAVEHDR that just finished.
      
      ; Check our master flag to see if we should continue.
      If waveRunning
        ; 1. Get the pointer to the header that finished
        Define *header.WAVEHDR = dwParam1
        
        ; 2. Refill its associated data buffer
        FillBuffer(*header\lpData, *header\dwBufferLength)
        
        ; 3. Queue the *same buffer* again to play
        ;    This creates the endless cycle.
        waveOutWrite_(hWaveOut, *header, SizeOf(WAVEHDR))
      EndIf
      
    Case #WOM_CLOSE
      ; Device was closed.
      Debug "Callback: Device Closed."
      
  EndSelect
EndProcedure

; --- Main Program Setup ---

; 1. Populate the melody (Super Mario Bros. 1-1)
; Main Riff Part 1
melody(0)\frequency = #E4 : melody(0)\durationMs = #N_Short
melody(1)\frequency = #E4 : melody(1)\durationMs = #N_Short
melody(2)\frequency = #REST : melody(2)\durationMs = #N_Rest_Short
melody(3)\frequency = #E4 : melody(3)\durationMs = #N_Short
melody(4)\frequency = #REST : melody(4)\durationMs = #N_Rest_Short
melody(5)\frequency = #C4 : melody(5)\durationMs = #N_Short
melody(6)\frequency = #E4 : melody(6)\durationMs = #N_Short
melody(7)\frequency = #REST : melody(7)\durationMs = #N_Rest_Short
melody(8)\frequency = #G4 : melody(8)\durationMs = #N_Short
melody(9)\frequency = #REST : melody(9)\durationMs = #N_Rest_Long
melody(10)\frequency = #G3 : melody(10)\durationMs = #N_Short
melody(11)\frequency = #REST : melody(11)\durationMs = #N_Rest_Long

; Main Riff Part 2
melody(12)\frequency = #C4 : melody(12)\durationMs = #N_Short
melody(13)\frequency = #REST : melody(13)\durationMs = #N_Rest_Med
melody(14)\frequency = #G3 : melody(14)\durationMs = #N_Short
melody(15)\frequency = #REST : melody(15)\durationMs = #N_Rest_Med
melody(16)\frequency = #E3 : melody(16)\durationMs = #N_Short
melody(17)\frequency = #REST : melody(17)\durationMs = #N_Rest_Med
melody(18)\frequency = #A3 : melody(18)\durationMs = #N_Short
melody(19)\frequency = #REST : melody(19)\durationMs = #N_Rest_Short
melody(20)\frequency = #B3 : melody(20)\durationMs = #N_Short
melody(21)\frequency = #REST : melody(21)\durationMs = #N_Rest_Short
melody(22)\frequency = #Bb3 : melody(22)\durationMs = #N_Short
melody(23)\frequency = #A3 : melody(23)\durationMs = #N_Short

; Main Riff Part 3 (Loop)
melody(24)\frequency = #G3 : melody(24)\durationMs = #N_Loop
melody(25)\frequency = #E4 : melody(25)\durationMs = #N_Loop
melody(26)\frequency = #G4 : melody(26)\durationMs = #N_Loop
melody(27)\frequency = #A4 : melody(27)\durationMs = #N_Loop
melody(28)\frequency = #REST : melody(28)\durationMs = #N_Rest_Loop
melody(29)\frequency = #F4 : melody(29)\durationMs = #N_Loop
melody(30)\frequency = #G4 : melody(30)\durationMs = #N_Loop
melody(31)\frequency = #REST : melody(31)\durationMs = #N_Rest_Loop
melody(32)\frequency = #E4 : melody(32)\durationMs = #N_Loop
melody(33)\frequency = #REST : melody(33)\durationMs = #N_Rest_Loop
melody(34)\frequency = #C4 : melody(34)\durationMs = #N_Loop
melody(35)\frequency = #D4 : melody(35)\durationMs = #N_Loop
melody(36)\frequency = #B3 : melody(36)\durationMs = #N_Loop


; 2. Define our audio format: 44.1kHz, 16-bit, Stereo PCM
wfx\wFormatTag = #WAVE_FORMAT_PCM
wfx\nChannels = 2
wfx\nSamplesPerSec = 44100
wfx\wBitsPerSample = 16
wfx\nBlockAlign = (wfx\nChannels * wfx\wBitsPerSample) / 8 ; 4 bytes
wfx\nAvgBytesPerSec = wfx\nSamplesPerSec * wfx\nBlockAlign  ; 176400
wfx\cbSize = 0 ; Not needed for PCM

; 3. Calculate our 250ms buffer size
;    BytesPerSecond * 0.250
bufferSize = wfx\nAvgBytesPerSec / 4 ; (176400 / 4 = 44100 bytes)
Debug "Audio Format: 44.1kHz, 16-bit, Stereo"
Debug "Buffer Size (250ms): " + Str(bufferSize) + " bytes"

; 4. Open the audio device
;    We use WAVE_MAPPER (default device) and tell it to use our callback.
Define result.i
result = waveOutOpen_(@hWaveOut, #WAVE_MAPPER, @wfx, @WaveCallback(), 0, #CALLBACK_FUNCTION)

If result <> 0
  MessageRequester("Error", "Could not open audio device. Error: " + Str(result))
  End
EndIf

Debug "Audio device opened successfully (Handle: " + Str(hWaveOut) + ")"

; 5. Allocate and prepare our two buffers
Define i
For i = 0 To #NUM_BUFFERS - 1
  ; Allocate memory for the audio data
  *waveBuffers(i) = AllocateMemory(bufferSize)
  ; Fill it with initial sound
  FillBuffer(*waveBuffers(i), bufferSize)
  
  ; Set up the WAVEHDR structure
  waveHeaders(i)\lpData = *waveBuffers(i)
  waveHeaders(i)\dwBufferLength = bufferSize
  
  ; "Prepare" the header for the audio device
  result = waveOutPrepareHeader_(hWaveOut, @waveHeaders(i), SizeOf(WAVEHDR))
  If result <> 0
    MessageRequester("Error", "Could not prepare buffer " + Str(i))
    waveOutClose_(hWaveOut)
    End
  EndIf
Next

Debug "Prepared " + Str(#NUM_BUFFERS) + " audio buffers."

; 6. "Prime the pump" - queue both buffers to start the playback cycle
For i = 0 To #NUM_BUFFERS - 1
  waveOutWrite_(hWaveOut, @waveHeaders(i), SizeOf(WAVEHDR))
Next
Debug "Playback started. Queued initial buffers."

; 7. Create a window and wait
;    The audio will play in the background via the callback.
;    We just need to keep the main program alive.
OpenWindow(0, 0, 0, 300, 150, "Cyclic Audio Test", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
TextGadget(0, 10, 10, 280, 130, "Playing 'Super Mario Bros. 1-1'..." + #CRLF$ + #CRLF$ + "Close this window to stop.")

Repeat
  Define event.i = WaitWindowEvent()
Until event = #PB_Event_CloseWindow

; --- Cleanup ---
Debug "Window closed. Shutting down audio..."

; 1. Set flag to stop the callback loop
waveRunning = #False

; 2. Reset the device. This stops playback and clears all pending buffers.
waveOutReset_(hWaveOut)

; 3. Unprepare and free all buffers
For i = 0 To #NUM_BUFFERS - 1
  ; Wait until the header is marked as done
  While (waveHeaders(i)\dwFlags & #WHDR_PREPARED)
    waveOutUnprepareHeader_(hWaveOut, @waveHeaders(i), SizeOf(WAVEHDR))
    Delay(10)
  Wend
  
  ; Free the memory for the audio data
  FreeMemory(*waveBuffers(i))
  Debug "Unprepared and freed buffer " + Str(i)
Next

; 4. Close the audio device
waveOutClose_(hWaveOut)
Debug "Audio device closed. Exiting."

End
User avatar
Otrebor
Enthusiast
Enthusiast
Posts: 211
Joined: Mon Mar 17, 2014 1:42 pm
Location: São Paulo, Brasil
Contact:

Re: Simple on the fly audio example

Post by Otrebor »

Thank you!
AZJIO
Addict
Addict
Posts: 2226
Joined: Sun May 14, 2017 1:48 am

Re: Simple on the fly audio example

Post by AZJIO »

Can you combine this with this?

Code: Select all

; This program creates a 250ms cyclic audio buffer using the
; low-level Windows Waveform Audio API (winmm.dll).
; It uses a callback function to refill buffers as they finish playing.
;
; This version is modified to play "Super Mario Bros. 1-1"
; using a square wave generator.

; --- Constants and Structures ---

; We need two buffers for a smooth cyclic system (double buffering)
#NUM_BUFFERS = 2


; Windows API Constants
#WAVE_MAPPER = -1
#WAVE_FORMAT_PCM = 1
#CALLBACK_FUNCTION = $30000 ; Tell waveOutOpen() we are using a callback procedure
#WOM_OPEN = $3BB          ; Sent to callback when device is opened
#WOM_DONE = $3BD          ; Sent to callback when a buffer is finished
#WOM_CLOSE = $3BC         ; Sent to callback when device is closed
#WHDR_PREPARED = $2

; --- Structure for Melody ---
Structure Note
  frequency.f
  durationMs.i
EndStructure

; Note frequencies for Mario 1-1





; --- Global variables for audio ---
Global hWaveOut.i                      ; Handle to the audio device
Global wfx.WAVEFORMATEX                ; Our audio format
Global Dim waveHeaders.WAVEHDR(#NUM_BUFFERS - 1) ; Array for our buffer headers
Global Dim *waveBuffers(#NUM_BUFFERS - 1)      ; Pointers to the actual audio data
Global bufferSize.l                    ; Size of one buffer (250ms)
Global waveRunning.i = #True           ; Flag to keep the callback looping

; --- Globals for Melody Playback ---
Global Dim melody.Note(1) ; 37 notes total (0-36)
Global melodyLength.i = 0
Global currentNoteIndex.i = -1 ; Start at -1 to load first note
Global samplesLeftForThisNote.i = 0
Global currentFrequency.f = 0.0
Global currentHalfCycle.d = 0.0
Global g_phase.d = 0.0 ; General phase for square wave

; --- Audio Generation ---

; Fills a buffer with a square wave based on the melody
Procedure FillBuffer(*buffer, length)
  Define *b.Word = *buffer ; Use a 16-bit WORD pointer
  Define samplesToFill.i = length / (wfx\nBlockAlign) ; nBlockAlign is 4 (16-bit stereo)
  Define i, sampleValue.i
  
  For i = 0 To samplesToFill - 1
    
    ; Check if we need to advance to the next note
    If samplesLeftForThisNote = 0
      currentNoteIndex + 1
      If currentNoteIndex >= melodyLength ; Loop song
        currentNoteIndex = 0
      EndIf
      
      ; Get new note data
      currentFrequency = melody(currentNoteIndex)\frequency
      Define durationMs.i = melody(currentNoteIndex)\durationMs
      
      ; Calculate how many samples this note lasts
      samplesLeftForThisNote = (wfx\nSamplesPerSec * durationMs) / 1000
      
      ; Reset phase for the new note
      g_phase = 0.0
      
      ; Calculate half-cycle for square wave
      If currentFrequency > 0
        currentHalfCycle = (wfx\nSamplesPerSec / currentFrequency) / 2.0
      Else
        currentHalfCycle = 0 ; Rest note
      EndIf
    EndIf
    
    ; --- Generate Square Wave Sample ---
    If currentHalfCycle = 0
      sampleValue = 0 ; Rest
    Else
      If g_phase < currentHalfCycle
        sampleValue = 10000 ; High (less than 16384 to be less harsh)
      Else
        sampleValue = -10000 ; Low
      EndIf
    EndIf
    
    ; Advance the phase for the square wave
    g_phase + 1.0
    If g_phase >= currentHalfCycle * 2
      g_phase - (currentHalfCycle * 2)
    EndIf
    
    ; Write the sample to both channels
    *b\w = sampleValue ; Left Channel
    *b + 2
    *b\w = sampleValue ; Right Channel
    *b + 2
    
    ; We've used one sample for this note
    samplesLeftForThisNote - 1
  Next
EndProcedure

; --- The Callback Procedure ---

; This is the heart of the program.
; Windows will call this function from a separate thread.
; We MUST use ProcedureC for the C-style calling convention.
ProcedureC WaveCallback(hWaveOut.i, uMsg.i, dwInstance.i, dwParam1.i, dwParam2.i)
  
  Select uMsg
    Case #WOM_OPEN
      ; Device was opened.
      Debug "Callback: Device Opened."
      
    Case #WOM_DONE
      ; A buffer has finished playing.
      ; dwParam1 is a pointer to the WAVEHDR that just finished.
      
      ; Check our master flag to see if we should continue.
      If waveRunning
        ; 1. Get the pointer to the header that finished
        Define *header.WAVEHDR = dwParam1
        
        ; 2. Refill its associated data buffer
        FillBuffer(*header\lpData, *header\dwBufferLength)
        
        ; 3. Queue the *same buffer* again to play
        ;    This creates the endless cycle.
        waveOutWrite_(hWaveOut, *header, SizeOf(WAVEHDR))
      EndIf
      
    Case #WOM_CLOSE
      ; Device was closed.
      Debug "Callback: Device Closed."
      
  EndSelect
EndProcedure

; --- Main Program Setup ---

; 1. Populate the melody (Super Mario Bros. 1-1)
; Main Riff Part 1

Global indexArr = 0
Global temp.f=0.6 ; musical tempo coefficient
Global Tone=0

Procedure _Beep(nota.f, octave.f = 4,Duration.f = 200, pause.f = 0)
	melody(indexArr)\frequency = 440.0 * Pow(2, (nota + Tone)/12.0 + octave + 1.0/6.0 - 4.0)
	melody(indexArr)\durationMs = Duration/temp
; 	If pause
; 		Delay(pause/temp)
; 	EndIf
	indexArr + 1
	ReDim melody(indexArr)
EndProcedure

; Procedure Frequency(nota.f,octave.f=4)
; 	ProcedureReturn 440.0*Pow(2, (nota + Tone)/12.0+ octave+1.0/6.0 - 4.0)
; EndProcedure


_Beep(1,4,100)
_Beep(5,4,100)
_Beep(8,4,100)
_Beep(1,5,100)
_Beep(5,5,100)
_Beep(8,4,100)
_Beep(1,5,100)
_Beep(5,5,100)

_Beep(1,4,100)
_Beep(5,4,100)
_Beep(8,4,100)
_Beep(1,5,100)
_Beep(5,5,100)
_Beep(8,4,100)
_Beep(1,5,100)
_Beep(5,5,100)

_Beep(1,4,100)
_Beep(3,4,100)
_Beep(10,4,100)
_Beep(3,5,100)
_Beep(6,5,100)
_Beep(10,4,100)
_Beep(3,5,100)
_Beep(6,5,100)

_Beep(1,4,100)
_Beep(3,4,100)
_Beep(10,4,100)
_Beep(3,5,100)
_Beep(6,5,100)
_Beep(10,4,100)
_Beep(3,5,100)
_Beep(6,5,100)

_Beep(12,3,100)
_Beep(3,4,100)
_Beep(8,4,100)
_Beep(3,5,100)
_Beep(6,5,100)
_Beep(8,4,100)
_Beep(3,5,100)
_Beep(6,5,100)

_Beep(12,3,100)
_Beep(3,4,100)
_Beep(8,4,100)
_Beep(3,5,100)
_Beep(6,5,100)
_Beep(8,4,100)
_Beep(3,5,100)
_Beep(6,5,100)

_Beep(1,4,100)
_Beep(5,4,100)
_Beep(8,4,100)
_Beep(1,5,100)
_Beep(5,5,100)
_Beep(8,4,100)
_Beep(1,5,100)
_Beep(5,5,100)

_Beep(1,4,100)
_Beep(5,4,100)
_Beep(8,4,100)
_Beep(1,5,100)
_Beep(5,5,100)
_Beep(8,4,100)
_Beep(1,5,100)
_Beep(5,5,100)

_Beep(1,4,100)
_Beep(5,4,100)
_Beep(10,4,100)
_Beep(5,5,100)
_Beep(10,5,100)
_Beep(10,4,100)
_Beep(5,5,100)
_Beep(10,5,100)

_Beep(1,4,100)
_Beep(5,4,100)
_Beep(10,4,100)
_Beep(5,5,100)
_Beep(10,5,100)
_Beep(10,4,100)
_Beep(5,5,100)
_Beep(10,5,100)

_Beep(1,4,100)
_Beep(3,4,100)
_Beep(7,4,100)
_Beep(10,4,100)
_Beep(3,5,100)
_Beep(7,4,100)
_Beep(10,4,100)
_Beep(3,5,100)

_Beep(1,4,100)
_Beep(3,4,100)
_Beep(7,4,100)
_Beep(10,4,100)
_Beep(3,5,100)
_Beep(7,4,100)
_Beep(10,4,100)
_Beep(3,5,100)

_Beep(12,3,100)
_Beep(3,4,100)
_Beep(8,4,100)
_Beep(3,5,100)
_Beep(8,5,100)
_Beep(8,4,100)
_Beep(3,5,100)
_Beep(8,5,100)

_Beep(12,3,100)
_Beep(3,4,100)
_Beep(8,4,100)
_Beep(3,5,100)
_Beep(8,5,100)
_Beep(8,4,100)
_Beep(3,5,100)
_Beep(8,5,100)

_Beep(12,3,100)
_Beep(1,4,100)
_Beep(5,4,100)
_Beep(8,4,100)
_Beep(1,5,100)
_Beep(5,4,100)
_Beep(8,4,100)
_Beep(1,5,100)

_Beep(12,3,100)
_Beep(1,4,100)
_Beep(5,4,100)
_Beep(8,4,100)
_Beep(1,5,100)
_Beep(5,4,100)
_Beep(8,4,100)
_Beep(1,5,100)

_Beep(10,3,100)
_Beep(1,4,100)
_Beep(5,4,100)
_Beep(8,4,100)
_Beep(1,5,100)
_Beep(5,4,100)
_Beep(8,4,100)
_Beep(1,5,100)

_Beep(10,3,100)
_Beep(1,4,100)
_Beep(5,4,100)
_Beep(8,4,100)
_Beep(1,5,100)
_Beep(5,4,100)
_Beep(8,4,100)
_Beep(1,5,100)

_Beep(3,3,100)
_Beep(10,3,100)
_Beep(3,4,100)
_Beep(7,4,100)
_Beep(1,5,100)
_Beep(3,4,100)
_Beep(7,4,100)
_Beep(1,5,100)

_Beep(3,3,100)
_Beep(10,3,100)
_Beep(3,4,100)
_Beep(7,4,100)
_Beep(1,5,100)
_Beep(3,4,100)
_Beep(7,4,100)
_Beep(1,5,100)

_Beep(8,3,100)
_Beep(12,3,100)
_Beep(3,4,100)
_Beep(8,4,100)
_Beep(12,4,100)
_Beep(3,4,100)
_Beep(8,4,100)
_Beep(12,4,100)

_Beep(8,3,100)
_Beep(12,3,100)
_Beep(3,4,100)
_Beep(8,4,100)
_Beep(12,4,100)
_Beep(3,4,100)
_Beep(8,4,100)
_Beep(12,4,100)

_Beep(8,3,100)
_Beep(11,3,100)
_Beep(5,4,100)
_Beep(8,4,100)
_Beep(2,5,100)
_Beep(5,4,100)
_Beep(8,4,100)
_Beep(2,5,100)

_Beep(8,3,100)
_Beep(11,3,100)
_Beep(5,4,100)
_Beep(8,4,100)
_Beep(2,5,100)
_Beep(5,4,100)
_Beep(8,4,100)
_Beep(2,5,100)

_Beep(6,3,100)
_Beep(10,3,100)
_Beep(3,4,100)
_Beep(10,4,100)
_Beep(3,5,100)
_Beep(3,4,100)
_Beep(10,4,100)
_Beep(3,5,100)

_Beep(6,3,100)
_Beep(10,3,100)
_Beep(3,4,100)
_Beep(10,4,100)
_Beep(3,5,100)
_Beep(3,4,100)
_Beep(10,4,100)
_Beep(3,5,100)

_Beep(6,3,100)
_Beep(9,3,100)
_Beep(3,4,100)
_Beep(6,4,100)
_Beep(12,4,100)
_Beep(3,4,100)
_Beep(6,4,100)
_Beep(12,4,100)

_Beep(6,3,100)
_Beep(9,3,100)
_Beep(3,4,100)
_Beep(6,4,100)
_Beep(12,4,100)
_Beep(3,4,100)
_Beep(6,4,100)
_Beep(12,4,100)

_Beep(5,3,100)
_Beep(8,3,100)
_Beep(1,4,100)
_Beep(8,4,100)
_Beep(1,5,100)
_Beep(1,4,100)
_Beep(8,4,100)
_Beep(1,5,100)

_Beep(5,3,100)
_Beep(8,3,100)
_Beep(1,4,100)
_Beep(8,4,100)
_Beep(1,5,100)
_Beep(1,4,100)
_Beep(8,4,100)
_Beep(1,5,100)

_Beep(5,3,100)
_Beep(6,3,100)
_Beep(10,3,100)
_Beep(1,4,100)
_Beep(6,4,100)
_Beep(10,3,100)
_Beep(1,4,100)
_Beep(6,4,100)

_Beep(5,3,100)
_Beep(6,3,100)
_Beep(10,3,100)
_Beep(1,4,100)
_Beep(6,4,100)
_Beep(10,3,100)
_Beep(1,4,100)
_Beep(6,4,100)

_Beep(3,3,100)
_Beep(6,3,100)
_Beep(10,3,100)
_Beep(1,4,100)
_Beep(6,4,100)
_Beep(10,3,100)
_Beep(1,4,100)
_Beep(6,4,100)

_Beep(3,3,100)
_Beep(6,3,100)
_Beep(10,3,100)
_Beep(1,4,100)
_Beep(6,4,100)
_Beep(10,3,100)
_Beep(1,4,100)
_Beep(6,4,100)

_Beep(8,2,100)
_Beep(3,3,100)
_Beep(8,3,100)
_Beep(12,3,100)
_Beep(6,4,100)
_Beep(8,3,100)
_Beep(12,3,100)
_Beep(6,4,100)

_Beep(8,2,100)
_Beep(3,3,100)
_Beep(8,3,100)
_Beep(12,3,100)
_Beep(6,4,100)
_Beep(8,3,100)
_Beep(12,3,100)
_Beep(6,4,100)

_Beep(1,3,100)
_Beep(5,3,100)
_Beep(8,3,100)
_Beep(1,4,100)
_Beep(5,4,100)
_Beep(8,3,100)
_Beep(1,4,100)
_Beep(5,4,100)

_Beep(1,3,100)
_Beep(5,3,100)
_Beep(8,3,100)
_Beep(1,4,100)
_Beep(5,4,100)
_Beep(8,3,100)
_Beep(1,4,100)
_Beep(5,4,100)

_Beep(1,3,100)
_Beep(8,3,100)
_Beep(11,3,100)
_Beep(1,4,100)
_Beep(5,4,100)
_Beep(11,3,100)
_Beep(1,4,100)
_Beep(5,4,100)

_Beep(1,3,100)
_Beep(8,3,100)
_Beep(11,3,100)
_Beep(1,4,100)
_Beep(5,4,100)
_Beep(11,3,100)
_Beep(1,4,100)
_Beep(5,4,100)

_Beep(6,2,100)
_Beep(6,3,100)
_Beep(10,3,100)
_Beep(1,4,100)
_Beep(5,4,100)
_Beep(10,3,100)
_Beep(1,4,100)
_Beep(5,4,100)

_Beep(6,2,100)
_Beep(6,3,100)
_Beep(10,3,100)
_Beep(1,4,100)
_Beep(5,4,100)
_Beep(10,3,100)
_Beep(1,4,100)
_Beep(5,4,100)

_Beep(7,2,100)
_Beep(1,3,100)
_Beep(10,3,100)
_Beep(1,4,100)
_Beep(4,4,100)
_Beep(10,3,100)
_Beep(1,4,100)
_Beep(4,4,100)

_Beep(7,2,100)
_Beep(1,3,100)
_Beep(10,3,100)
_Beep(1,4,100)
_Beep(4,4,100)
_Beep(10,3,100)
_Beep(1,4,100)
_Beep(4,4,100)

_Beep(9,2,100)
_Beep(6,3,100)
_Beep(12,3,100)
_Beep(1,4,100)
_Beep(3,4,100)
_Beep(12,3,100)
_Beep(1,4,100)
_Beep(3,4,100)

_Beep(9,2,100)
_Beep(6,3,100)
_Beep(12,3,100)
_Beep(1,4,100)
_Beep(3,4,100)
_Beep(12,3,100)
_Beep(1,4,100)
_Beep(3,4,100)

_Beep(8,2,100)
_Beep(6,3,100)
_Beep(8,3,100)
_Beep(12,3,100)
_Beep(3,4,100)
_Beep(8,3,100)
_Beep(12,3,100)
_Beep(3,4,100)

_Beep(8,2,100)
_Beep(6,3,100)
_Beep(8,3,100)
_Beep(12,3,100)
_Beep(3,4,100)
_Beep(8,3,100)
_Beep(12,3,100)
_Beep(3,4,100)

_Beep(8,2,100)
_Beep(5,3,100)
_Beep(8,3,100)
_Beep(1,4,100)
_Beep(5,4,100)
_Beep(8,3,100)
_Beep(1,4,100)
_Beep(5,4,100)

_Beep(8,2,100)
_Beep(5,3,100)
_Beep(8,3,100)
_Beep(1,4,100)
_Beep(5,4,100)
_Beep(8,3,100)
_Beep(1,4,100)
_Beep(5,4,100)

_Beep(8,2,100)
_Beep(3,3,100)
_Beep(8,3,100)
_Beep(1,4,100)
_Beep(6,4,100)
_Beep(8,3,100)
_Beep(1,4,100)
_Beep(6,4,100)

_Beep(8,2,100)
_Beep(3,3,100)
_Beep(8,3,100)
_Beep(1,4,100)
_Beep(6,4,100)
_Beep(8,3,100)
_Beep(1,4,100)
_Beep(6,4,100)

_Beep(8,2,100)
_Beep(3,3,100)
_Beep(8,3,100)
_Beep(12,3,100)
_Beep(6,4,100)
_Beep(8,3,100)
_Beep(12,3,100)
_Beep(6,4,100)

_Beep(8,2,100)
_Beep(3,3,100)
_Beep(8,3,100)
_Beep(12,3,100)
_Beep(6,4,100)
_Beep(8,3,100)
_Beep(12,3,100)
_Beep(6,4,100)

_Beep(8,2,100)
_Beep(4,3,100)
_Beep(10,3,100)
_Beep(1,4,100)
_Beep(7,4,100)
_Beep(10,3,100)
_Beep(1,4,100)
_Beep(7,4,100)

_Beep(8,2,100)
_Beep(4,3,100)
_Beep(10,3,100)
_Beep(1,4,100)
_Beep(7,4,100)
_Beep(10,3,100)
_Beep(1,4,100)
_Beep(7,4,100)

_Beep(8,2,100)
_Beep(5,3,100)
_Beep(8,3,100)
_Beep(1,4,100)
_Beep(8,4,100)
_Beep(8,3,100)
_Beep(1,4,100)
_Beep(8,4,100)

_Beep(8,2,100)
_Beep(5,3,100)
_Beep(8,3,100)
_Beep(1,4,100)
_Beep(8,4,100)
_Beep(8,3,100)
_Beep(1,4,100)
_Beep(8,4,100)

_Beep(8,2,100)
_Beep(3,3,100)
_Beep(8,3,100)
_Beep(1,4,100)
_Beep(6,4,100)
_Beep(8,3,100)
_Beep(1,4,100)
_Beep(6,4,100)

_Beep(8,2,100)
_Beep(3,3,100)
_Beep(8,3,100)
_Beep(1,4,100)
_Beep(6,4,100)
_Beep(8,3,100)
_Beep(1,4,100)
_Beep(6,4,100)

_Beep(8,2,100)
_Beep(3,3,100)
_Beep(8,3,100)
_Beep(12,3,100)
_Beep(6,4,100)
_Beep(8,3,100)
_Beep(12,3,100)
_Beep(6,4,100)

_Beep(8,2,100)
_Beep(3,3,100)
_Beep(8,3,100)
_Beep(12,3,100)
_Beep(6,4,100)
_Beep(8,3,100)
_Beep(12,3,100)
_Beep(6,4,100)

_Beep(1,2,100)
_Beep(1,3,100)
_Beep(8,3,100)
_Beep(11,3,100)
_Beep(5,4,100)
_Beep(8,3,100)
_Beep(11,3,100)
_Beep(5,4,100)

_Beep(1,2,100)
_Beep(1,3,100)
_Beep(8,3,100)
_Beep(11,3,100)
_Beep(5,4,100)
_Beep(8,3,100)
_Beep(11,3,100)
_Beep(5,4,100)

_Beep(1,2,100)
_Beep(1,3,100)
_Beep(6,3,100)
_Beep(10,3,100)
_Beep(1,4,100)
_Beep(6,4,100)
_Beep(1,4,100)
_Beep(10,3,100)
_Beep(6,3,100)
_Beep(10,3,100)
_Beep(6,3,100)
_Beep(3,3,100)
_Beep(6,3,100)
_Beep(3,3,100)

; _Beep(1,2,100)
; _Beep(1,3,100)
; _Beep(8,4,100)
; _Beep(12,4,100)
; _Beep(3,5,100)
; _Beep(6,5,100)
; _Beep(3,5,100)
; _Beep(12,4,100)
; _Beep(8,4,100)
; _Beep(12,4,100)
; _Beep(8,4,70,30)
; _Beep(8,4,100)
; _Beep(6,4,100)
; _Beep(8,4,100)
; _Beep(6,4,200)
; _Beep(5,4,100)

; _Beep(1,3,900)

_Beep(1,2,100)
_Beep(1,3,100)
_Beep(8,4,100)
_Beep(12,4,100)
_Beep(3,5,100)
_Beep(6,5,100)
_Beep(3,5,100)
_Beep(12,4,100)
_Beep(8,4,100)
_Beep(12,4,100)
_Beep(8,4,100)
_Beep(8,4,100)
_Beep(6,4,100)
_Beep(8,4,100)
_Beep(6,4,100)
_Beep(5,4,100)

_Beep(1,3,900)

melodyLength = indexArr
ReDim melody(indexArr - 1)


; 2. Define our audio format: 44.1kHz, 16-bit, Stereo PCM
wfx\wFormatTag = #WAVE_FORMAT_PCM
wfx\nChannels = 2
wfx\nSamplesPerSec = 44100
wfx\wBitsPerSample = 16
wfx\nBlockAlign = (wfx\nChannels * wfx\wBitsPerSample) / 8 ; 4 bytes
wfx\nAvgBytesPerSec = wfx\nSamplesPerSec * wfx\nBlockAlign  ; 176400
wfx\cbSize = 0 ; Not needed for PCM

; 3. Calculate our 250ms buffer size
;    BytesPerSecond * 0.250
bufferSize = wfx\nAvgBytesPerSec / 4 ; (176400 / 4 = 44100 bytes)
Debug "Audio Format: 44.1kHz, 16-bit, Stereo"
Debug "Buffer Size (250ms): " + Str(bufferSize) + " bytes"

; 4. Open the audio device
;    We use WAVE_MAPPER (default device) and tell it to use our callback.
Define result.i
result = waveOutOpen_(@hWaveOut, #WAVE_MAPPER, @wfx, @WaveCallback(), 0, #CALLBACK_FUNCTION)

If result <> 0
  MessageRequester("Error", "Could not open audio device. Error: " + Str(result))
  End
EndIf

Debug "Audio device opened successfully (Handle: " + Str(hWaveOut) + ")"

; 5. Allocate and prepare our two buffers
Define i
For i = 0 To #NUM_BUFFERS - 1
  ; Allocate memory for the audio data
  *waveBuffers(i) = AllocateMemory(bufferSize)
  ; Fill it with initial sound
  FillBuffer(*waveBuffers(i), bufferSize)
  
  ; Set up the WAVEHDR structure
  waveHeaders(i)\lpData = *waveBuffers(i)
  waveHeaders(i)\dwBufferLength = bufferSize
  
  ; "Prepare" the header for the audio device
  result = waveOutPrepareHeader_(hWaveOut, @waveHeaders(i), SizeOf(WAVEHDR))
  If result <> 0
    MessageRequester("Error", "Could not prepare buffer " + Str(i))
    waveOutClose_(hWaveOut)
    End
  EndIf
Next

Debug "Prepared " + Str(#NUM_BUFFERS) + " audio buffers."

; 6. "Prime the pump" - queue both buffers to start the playback cycle
For i = 0 To #NUM_BUFFERS - 1
  waveOutWrite_(hWaveOut, @waveHeaders(i), SizeOf(WAVEHDR))
Next
Debug "Playback started. Queued initial buffers."

; 7. Create a window and wait
;    The audio will play in the background via the callback.
;    We just need to keep the main program alive.
OpenWindow(0, 0, 0, 300, 150, "Cyclic Audio Test", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
TextGadget(0, 10, 10, 280, 130, "Playing 'Bach - Prelude in C Major'..." + #CRLF$ + #CRLF$ + "Close this window to stop.")

Repeat
  Define event.i = WaitWindowEvent()
Until event = #PB_Event_CloseWindow

; --- Cleanup ---
Debug "Window closed. Shutting down audio..."

; 1. Set flag to stop the callback loop
waveRunning = #False

; 2. Reset the device. This stops playback and clears all pending buffers.
waveOutReset_(hWaveOut)

; 3. Unprepare and free all buffers
For i = 0 To #NUM_BUFFERS - 1
  ; Wait until the header is marked as done
  While (waveHeaders(i)\dwFlags & #WHDR_PREPARED)
    waveOutUnprepareHeader_(hWaveOut, @waveHeaders(i), SizeOf(WAVEHDR))
    Delay(10)
  Wend
  
  ; Free the memory for the audio data
  FreeMemory(*waveBuffers(i))
  Debug "Unprepared and freed buffer " + Str(i)
Next

; 4. Close the audio device
waveOutClose_(hWaveOut)
Debug "Audio device closed. Exiting."

End
Last edited by AZJIO on Sat Oct 25, 2025 2:48 pm, edited 1 time in total.
User avatar
minimy
Enthusiast
Enthusiast
Posts: 687
Joined: Mon Jul 08, 2013 8:43 pm
Location: off world

Re: Simple on the fly audio example

Post by minimy »

Nice code, bip bip! :lol:
Thanks for share!
If translation=Error: reply="Sorry, Im Spanish": Endif
Booger
Enthusiast
Enthusiast
Posts: 139
Joined: Tue Sep 04, 2007 2:18 pm

Re: Simple on the fly audio example

Post by Booger »

Impressive on the Bach. Sounds just like the apple IIC
box_80
Enthusiast
Enthusiast
Posts: 117
Joined: Mon Sep 03, 2012 8:52 pm

Re: Simple on the fly audio example

Post by box_80 »

Thanks for posting. This is a nice example to learn from.
AZJIO
Addict
Addict
Posts: 2226
Joined: Sun May 14, 2017 1:48 am

Re: Simple on the fly audio example

Post by AZJIO »

A list is better than an array of musical notes
Booger
Enthusiast
Enthusiast
Posts: 139
Joined: Tue Sep 04, 2007 2:18 pm

Re: Simple on the fly audio example

Post by Booger »

Is this what you mean with the lists?

Windows:

Code: Select all

; This program creates a 250ms cyclic audio buffer using the
; low-level Windows Waveform Audio API (winmm.dll).
; It uses a callback function To refill buffers As they finish playing.
;
; This version plays an excerpt of "Flight of the Bumblebee" (2 voices)
; using selectable waveforms, includes envelopes, panning,
; And uses Lists populated from DataSections For the melodies.

; --- Waveform Type Constants ---
Enumeration
  #Wave_Square
  #Wave_Sine
  #Wave_Triangle
  #Wave_WhiteNoise
  #Wave_PinkNoise
EndEnumeration

; --- Constants And Structures ---
#NUM_BUFFERS = 2
#NUM_VOICES = 2 ; Treble And Bass

#WAVE_MAPPER = -1
#WAVE_FORMAT_PCM = 1
#CALLBACK_FUNCTION = $30000
#WOM_OPEN = $3BB
#WOM_DONE = $3BD
#WOM_CLOSE = $3BC
#WHDR_PREPARED = $2

Structure Note
  frequency.f
  durationMs.i
  pan.f ; -1.0 (Left) To 1.0 (Right)
EndStructure

; --- Note frequencies ---
; Added more notes needed for Bumblebee (A minor context)
#E2 = 82.41
#A2 = 110.00 : #Bb2 = 116.54 : #B2 = 123.47
#C3 = 130.81 : #Db3 = 138.59 : #D3 = 146.83 : #Eb3 = 155.56 : #E3 = 164.81 : #F3 = 174.61 : #Gb3 = 185.00 : #G3 = 196.00 : #Ab3 = 207.65 : #A3 = 220.00 : #Bb3 = 233.08 : #B3 = 246.94
#C4 = 261.63 : #Db4 = 277.18 : #D4 = 293.66 : #Eb4 = 311.13 : #E4 = 329.63 : #F4 = 349.23 : #Gb4 = 369.99 : #G4 = 392.00 : #Ab4 = 415.30 : #A4 = 440.00 : #Bb4 = 466.16 : #B4 = 493.88
#C5 = 523.25 : #Db5 = 554.37 : #D5 = 587.33 : #Eb5 = 622.25 : #E5 = 659.26 : #F5 = 698.46 : #Gb5 = 739.99 : #G5 = 783.99 : #Ab5 = 830.61 : #A5 = 880.00 : #Bb5 = 932.33 : #B5 = 987.77
#C6 = 1046.50 : #Db6 = 1108.73 : #D6 = 1174.66 : #Eb6 = 1244.51 : #E6 = 1318.51 : #F6 = 1396.91 : #Gb6 = 1479.98 : #G6 = 1567.98 : #Ab6 = 1661.22 : #A6 = 1760.00 : #REST = 0.0

; Sharps (Enharmonic equivalents often used, but explicit sharps added for clarity if needed)
#CSharp4 = #Db4 : #DSharp4 = #Eb4 : #FSharp4 = #Gb4 : #GSharp4 = #Ab4 : #ASharp4 = #Bb4
#CSharp5 = #Db5 : #DSharp5 = #Eb5 : #FSharp5 = #Gb5 : #GSharp5 = #Ab5 : #ASharp5 = #Bb5
#CSharp6 = #Db6 : #DSharp6 = #Eb6

; --- Note durations ---
#BPM = 160 ; Faster tempo For Bumblebee
#BeatMs = (60000 / #BPM) ; Milliseconds per beat
#D_Whole = #BeatMs * 4
#D_Half = #BeatMs * 2
#D_Quarter = #BeatMs
#D_8th = #BeatMs / 2
#D_16th = #BeatMs / 4 ; Very short!
#D_Triplet = #BeatMs / 3
#D_Dot_8th = #D_8th + #D_16th

; --- Globals ---
Global hWaveOut.i
Global wfx.WAVEFORMATEX
Global Dim waveHeaders.WAVEHDR(#NUM_BUFFERS - 1)
Global Dim *waveBuffers(#NUM_BUFFERS - 1)
Global bufferSize.l
Global waveRunning.i = #True

Global g_WaveformType = #Wave_Square ; <<< CHANGE THIS >>>
Global g_amplitude.f = 10000.0 / #NUM_VOICES ; Reduce amplitude per voice

#PINK_OCTAVES = 5
Global Dim pink_Values.f(#PINK_OCTAVES - 1)
Global pink_Index.i = 0

Global envelopeMs.i = 5 ; Keep short envelope
Global envelopeSamples.i = 0

Global NewList melodyTreble.Note() ; Voice 0
Global NewList melodyBass.Note()   ; Voice 1

Global Dim freq.f(#NUM_VOICES - 1)
Global Dim pan.f(#NUM_VOICES - 1)
Global Dim phase.d(#NUM_VOICES - 1)
Global Dim samplesLeft.i(#NUM_VOICES - 1)
Global Dim totalSamples.i(#NUM_VOICES - 1)
Global Dim samplesPlayed.i(#NUM_VOICES - 1)
Global Dim isFirstNote(#NUM_VOICES - 1)

Define initVoiceIndex.i
For initVoiceIndex = 0 To #NUM_VOICES - 1
  isFirstNote(initVoiceIndex) = #True
Next

Global Dim cycleLength.d(#NUM_VOICES - 1)
Global Dim halfCycle.d(#NUM_VOICES - 1)
Global Dim radPhaseStep.d(#NUM_VOICES - 1)

; --- Audio Generation ---
Procedure FillBuffer(*buffer, length)
  Define *b.Word = *buffer
  Define samplesToFill.i = length / (wfx\nBlockAlign)
  Define i, v
  Define sampleValue.f, finalL.f, finalR.f
  Define temp.f, sum.f, j
  Define envelopeMultiplier.f
  Define durationMs.i

  If envelopeSamples = 0
    envelopeSamples = (wfx\nSamplesPerSec * envelopeMs) / 1000
    If envelopeSamples = 0 : envelopeSamples = 1 : EndIf
  EndIf

  For i = 0 To samplesToFill - 1
    finalL = 0.0 : finalR = 0.0

    For v = 0 To #NUM_VOICES - 1
      If samplesLeft(v) <= 0
        Select v
          Case 0 ; Treble
            If isFirstNote(v)
              If Not FirstElement(melodyTreble()) : freq(v)=0.0:totalSamples(v)=1:samplesLeft(v)=1:Debug "Error: Treble empty!": EndIf
              isFirstNote(v)=#False
            Else
              If NextElement(melodyTreble())=0 : FirstElement(melodyTreble()) : EndIf
            EndIf
            If ListSize(melodyTreble())>0
              freq(v)=melodyTreble()\frequency : pan(v)=melodyTreble()\pan : durationMs=melodyTreble()\durationMs
            Else : freq(v)=0.0:pan(v)=0.0:durationMs=1000 : EndIf
          Case 1 ; Bass
            If isFirstNote(v)
              If Not FirstElement(melodyBass()) : freq(v)=0.0:totalSamples(v)=1:samplesLeft(v)=1:Debug "Error: Bass empty!": EndIf
              isFirstNote(v)=#False
            Else
              If NextElement(melodyBass())=0 : FirstElement(melodyBass()) : EndIf
            EndIf
            If ListSize(melodyBass())>0
              freq(v)=melodyBass()\frequency : pan(v)=melodyBass()\pan : durationMs=melodyBass()\durationMs
            Else : freq(v)=0.0:pan(v)=0.0:durationMs=1000 : EndIf
        EndSelect

        totalSamples(v)=(wfx\nSamplesPerSec * durationMs)/1000
        If totalSamples(v)<=0 : totalSamples(v)=1 : EndIf
        samplesLeft(v)=totalSamples(v) : samplesPlayed(v)=0 : phase(v)=0.0

        If freq(v)>0
          cycleLength(v)=wfx\nSamplesPerSec / freq(v) : halfCycle(v)=cycleLength(v)/2.0 : radPhaseStep(v)=(2*#PI)/cycleLength(v)
        Else
          cycleLength(v)=0 : halfCycle(v)=0 : radPhaseStep(v)=0
        EndIf
      EndIf

      sampleValue=0.0
      If freq(v)>0
        Select g_WaveformType
          Case #Wave_Square
            If halfCycle(v)>0
              If phase(v)<halfCycle(v) : sampleValue=g_amplitude : Else : sampleValue=-g_amplitude : EndIf
            EndIf
          Case #Wave_Sine
            If cycleLength(v)>0 : sampleValue=Sin(phase(v)*radPhaseStep(v))*g_amplitude : EndIf
          Case #Wave_Triangle
            If cycleLength(v)>0
              temp=phase(v)/cycleLength(v)
              If temp<0.5 : sampleValue=(temp*4.0-1.0)*g_amplitude
              Else : sampleValue=((1.0-temp)*4.0-1.0)*g_amplitude : EndIf
            EndIf
          Case #Wave_WhiteNoise : sampleValue=Random(g_amplitude*2)-g_amplitude
          Case #Wave_PinkNoise
            pink_Index=(pink_Index+1)%#PINK_OCTAVES : pink_Values(pink_Index)=(Random(10000)/5000.0)-1.0
            sum=0.0 : For j=0 To #PINK_OCTAVES-1 : sum+pink_Values(j) : Next
            sampleValue=(sum/#PINK_OCTAVES)*g_amplitude*1.5
        EndSelect
      EndIf

      envelopeMultiplier=1.0
      If freq(v)>0 And totalSamples(v)>1 And envelopeSamples>0
        Define fadeLength.f=envelopeSamples
        If fadeLength*2.0>totalSamples(v) : fadeLength=totalSamples(v)/2.0 : EndIf
        If fadeLength>0
          If samplesPlayed(v)<fadeLength : envelopeMultiplier=samplesPlayed(v)/fadeLength
          ElseIf(totalSamples(v)-samplesPlayed(v))<=fadeLength
            If fadeLength>0 : envelopeMultiplier=(totalSamples(v)-samplesPlayed(v)-1.0)/fadeLength
            Else : envelopeMultiplier=0.0 : EndIf
          EndIf
          If envelopeMultiplier<0.0 : envelopeMultiplier=0.0 : EndIf
          If envelopeMultiplier>1.0 : envelopeMultiplier=1.0 : EndIf
        EndIf
      EndIf
      sampleValue*envelopeMultiplier

      Define panL.f = (1.0 - pan(v)) / 2.0
      Define panR.f = (1.0 + pan(v)) / 2.0
      Define voiceL.f = sampleValue * panL
      Define voiceR.f = sampleValue * panR

      finalL+voiceL
      finalR+voiceR

      If cycleLength(v)>0
        phase(v)+1.0
        If phase(v)>=cycleLength(v) : phase(v)-cycleLength(v) : EndIf
      EndIf
      samplesLeft(v)-1
      samplesPlayed(v)+1
    Next v

    If finalL>32767.0:finalL=32767.0:EndIf:If finalL<-32767.0:finalL=-32767.0:EndIf
    If finalR>32767.0:finalR=32767.0:EndIf:If finalR<-32767.0:finalR=-32767.0:EndIf

    *b\w=Int(finalL):*b+2
    *b\w=Int(finalR):*b+2
  Next i
EndProcedure

; --- Callback Procedure ---
ProcedureC WaveCallback(hWaveOut.i, uMsg.i, dwInstance.i, dwParam1.i, dwParam2.i)
  Select uMsg
    Case #WOM_OPEN : Debug "Callback: Device Opened."
    Case #WOM_DONE
      If waveRunning
        Define *header.WAVEHDR = dwParam1
        FillBuffer(*header\lpData, *header\dwBufferLength)
        waveOutWrite_(hWaveOut, *header, SizeOf(WAVEHDR))
      EndIf
    Case #WOM_CLOSE : Debug "Callback: Device Closed."
  EndSelect
EndProcedure

; --- Main Program Setup ---

; [UPDATED] Flight of the Bumblebee Excerpt (Measures 1-32) - Treble (Voice 0)
DataSection
  melody_treble_data:
  ; Measure 1
  Data.f #E6  : Data.i #D_16th : Data.f 0.5
  Data.f #Eb6 : Data.i #D_16th : Data.f 0.5
  Data.f #D6  : Data.i #D_16th : Data.f 0.5
  Data.f #Db6 : Data.i #D_16th : Data.f 0.5 ; C#6
  Data.f #D6  : Data.i #D_16th : Data.f 0.5
  Data.f #Db6 : Data.i #D_16th : Data.f 0.5 ; C#6
  Data.f #C6  : Data.i #D_16th : Data.f 0.5
  Data.f #B5  : Data.i #D_16th : Data.f 0.5
  ; Measure 2
  Data.f #C6  : Data.i #D_16th : Data.f 0.5
  Data.f #B5  : Data.i #D_16th : Data.f 0.5
  Data.f #Bb5 : Data.i #D_16th : Data.f 0.5
  Data.f #A5  : Data.i #D_16th : Data.f 0.5
  Data.f #Ab5 : Data.i #D_16th : Data.f 0.5 ; G#5 -> Ab5
  Data.f #G5  : Data.i #D_16th : Data.f 0.5
  Data.f #Gb5 : Data.i #D_16th : Data.f 0.5 ; F#5
  Data.f #F5  : Data.i #D_16th : Data.f 0.5
  ; Measure 3
  Data.f #E5  : Data.i #D_16th : Data.f 0.5
  Data.f #Eb5 : Data.i #D_16th : Data.f 0.5
  Data.f #D5  : Data.i #D_16th : Data.f 0.5
  Data.f #Db5 : Data.i #D_16th : Data.f 0.5 ; C#5
  Data.f #D5  : Data.i #D_16th : Data.f 0.5
  Data.f #Db5 : Data.i #D_16th : Data.f 0.5 ; C#5
  Data.f #C5  : Data.i #D_16th : Data.f 0.5
  Data.f #B4  : Data.i #D_16th : Data.f 0.5
  ; Measure 4
  Data.f #C5  : Data.i #D_16th : Data.f 0.5
  Data.f #B4  : Data.i #D_16th : Data.f 0.5
  Data.f #Bb4 : Data.i #D_16th : Data.f 0.5
  Data.f #A4  : Data.i #D_16th : Data.f 0.5
  Data.f #Ab4 : Data.i #D_16th : Data.f 0.5 ; G#4 -> Ab4
  Data.f #G4  : Data.i #D_16th : Data.f 0.5
  Data.f #Gb4 : Data.i #D_16th : Data.f 0.5 ; F#4
  Data.f #F4  : Data.i #D_16th : Data.f 0.5
  ; Measure 5
  Data.f #E4  : Data.i #D_16th : Data.f 0.5
  Data.f #Eb4 : Data.i #D_16th : Data.f 0.5
  Data.f #D4  : Data.i #D_16th : Data.f 0.5
  Data.f #Db4 : Data.i #D_16th : Data.f 0.5 ; C#4
  Data.f #D4  : Data.i #D_16th : Data.f 0.5
  Data.f #Db4 : Data.i #D_16th : Data.f 0.5 ; C#4
  Data.f #C4  : Data.i #D_16th : Data.f 0.5
  Data.f #B3  : Data.i #D_16th : Data.f 0.5
  ; Measure 6
  Data.f #E4  : Data.i #D_16th : Data.f 0.5
  Data.f #Eb4 : Data.i #D_16th : Data.f 0.5
  Data.f #D4  : Data.i #D_16th : Data.f 0.5
  Data.f #Db4 : Data.i #D_16th : Data.f 0.5 ; C#4
  Data.f #D4  : Data.i #D_16th : Data.f 0.5
  Data.f #Db4 : Data.i #D_16th : Data.f 0.5 ; C#4
  Data.f #C4  : Data.i #D_16th : Data.f 0.5
  Data.f #B3  : Data.i #D_16th : Data.f 0.5
  ; Measure 7
  Data.f #E4  : Data.i #D_16th : Data.f 0.5
  Data.f #Eb4 : Data.i #D_16th : Data.f 0.5
  Data.f #D4  : Data.i #D_16th : Data.f 0.5
  Data.f #Db4 : Data.i #D_16th : Data.f 0.5 ; C#4
  Data.f #C4  : Data.i #D_16th : Data.f 0.5
  Data.f #F4  : Data.i #D_16th : Data.f 0.5
  Data.f #E4  : Data.i #D_16th : Data.f 0.5
  Data.f #Eb4 : Data.i #D_16th : Data.f 0.5 ; D#4
  ; Measure 8
  Data.f #E4  : Data.i #D_16th : Data.f 0.5
  Data.f #Eb4 : Data.i #D_16th : Data.f 0.5
  Data.f #D4  : Data.i #D_16th : Data.f 0.5
  Data.f #Db4 : Data.i #D_16th : Data.f 0.5 ; C#4
  Data.f #C4  : Data.i #D_16th : Data.f 0.5
  Data.f #Db4 : Data.i #D_16th : Data.f 0.5 ; C#4
  Data.f #D4  : Data.i #D_16th : Data.f 0.5
  Data.f #Eb4 : Data.i #D_16th : Data.f 0.5 ; D#4
  ; Measure 9
  Data.f #E4  : Data.i #D_16th : Data.f 0.5
  Data.f #Eb4 : Data.i #D_16th : Data.f 0.5
  Data.f #D4  : Data.i #D_16th : Data.f 0.5
  Data.f #Db4 : Data.i #D_16th : Data.f 0.5 ; XML has Db4
  Data.f #C4  : Data.i #D_16th : Data.f 0.5
  Data.f #F4  : Data.i #D_16th : Data.f 0.5
  Data.f #E4  : Data.i #D_16th : Data.f 0.5
  Data.f #Eb4 : Data.i #D_16th : Data.f 0.5 ; D#4
  ; Measure 10
  Data.f #E4  : Data.i #D_16th : Data.f 0.5
  Data.f #Eb4 : Data.i #D_16th : Data.f 0.5
  Data.f #D4  : Data.i #D_16th : Data.f 0.5
  Data.f #Db4 : Data.i #D_16th : Data.f 0.5 ; XML has Db4
  Data.f #C4  : Data.i #D_16th : Data.f 0.5
  Data.f #Db4 : Data.i #D_16th : Data.f 0.5 ; C#4
  Data.f #D4  : Data.i #D_16th : Data.f 0.5
  Data.f #Eb4 : Data.i #D_16th : Data.f 0.5 ; D#4
  ; Measure 11
  Data.f #E4  : Data.i #D_16th : Data.f 0.5
  Data.f #Eb4 : Data.i #D_16th : Data.f 0.5
  Data.f #D4  : Data.i #D_16th : Data.f 0.5
  Data.f #Db4 : Data.i #D_16th : Data.f 0.5 ; C#4
  Data.f #D4  : Data.i #D_16th : Data.f 0.5
  Data.f #Db4 : Data.i #D_16th : Data.f 0.5 ; C#4
  Data.f #C4  : Data.i #D_16th : Data.f 0.5
  Data.f #B3  : Data.i #D_16th : Data.f 0.5
  ; Measure 12
  Data.f #C4  : Data.i #D_16th : Data.f 0.5
  Data.f #Db4 : Data.i #D_16th : Data.f 0.5 ; C#4
  Data.f #D4  : Data.i #D_16th : Data.f 0.5
  Data.f #Eb4 : Data.i #D_16th : Data.f 0.5 ; D#4
  Data.f #E4  : Data.i #D_16th : Data.f 0.5
  Data.f #F4  : Data.i #D_16th : Data.f 0.5
  Data.f #E4  : Data.i #D_16th : Data.f 0.5
  Data.f #Eb4 : Data.i #D_16th : Data.f 0.5 ; D#4
  ; Measure 13
  Data.f #E4  : Data.i #D_16th : Data.f 0.5
  Data.f #Eb4 : Data.i #D_16th : Data.f 0.5
  Data.f #D4  : Data.i #D_16th : Data.f 0.5
  Data.f #Db4 : Data.i #D_16th : Data.f 0.5 ; C#4
  Data.f #D4  : Data.i #D_16th : Data.f 0.5
  Data.f #Db4 : Data.i #D_16th : Data.f 0.5 ; C#4
  Data.f #C4  : Data.i #D_16th : Data.f 0.5
  Data.f #B3  : Data.i #D_16th : Data.f 0.5
  ; Measure 14
  Data.f #C4  : Data.i #D_16th : Data.f 0.5
  Data.f #Db4 : Data.i #D_16th : Data.f 0.5 ; C#4
  Data.f #D4  : Data.i #D_16th : Data.f 0.5
  Data.f #Eb4 : Data.i #D_16th : Data.f 0.5 ; D#4
  Data.f #E4  : Data.i #D_16th : Data.f 0.5
  Data.f #Gb4 : Data.i #D_16th : Data.f 0.5 ; F#4
  Data.f #G4  : Data.i #D_16th : Data.f 0.5
  Data.f #Ab4 : Data.i #D_16th : Data.f 0.5 ; G#4
  ; Measure 15
  Data.f #A4  : Data.i #D_16th : Data.f 0.5
  Data.f #Ab4 : Data.i #D_16th : Data.f 0.5
  Data.f #G4  : Data.i #D_16th : Data.f 0.5
  Data.f #Gb4 : Data.i #D_16th : Data.f 0.5 ; F#4
  Data.f #F4  : Data.i #D_16th : Data.f 0.5
  Data.f #Bb4 : Data.i #D_16th : Data.f 0.5
  Data.f #A4  : Data.i #D_16th : Data.f 0.5
  Data.f #Ab4 : Data.i #D_16th : Data.f 0.5 ; G#4
  ; Measure 16
  Data.f #A4  : Data.i #D_16th : Data.f 0.5
  Data.f #Ab4 : Data.i #D_16th : Data.f 0.5 ; G#4
  Data.f #G4  : Data.i #D_16th : Data.f 0.5
  Data.f #Gb4 : Data.i #D_16th : Data.f 0.5 ; F#4
  Data.f #F4  : Data.i #D_16th : Data.f 0.5
  Data.f #Gb4 : Data.i #D_16th : Data.f 0.5 ; F#4
  Data.f #G4  : Data.i #D_16th : Data.f 0.5
  Data.f #Ab4 : Data.i #D_16th : Data.f 0.5 ; G#4
  ; --- START NEW MEASURES ---
  ; Measure 17
  Data.f #A4  : Data.i #D_16th : Data.f 0.5
  Data.f #Ab4 : Data.i #D_16th : Data.f 0.5 ; G#4
  Data.f #G4  : Data.i #D_16th : Data.f 0.5
  Data.f #Gb4 : Data.i #D_16th : Data.f 0.5 ; F#4
  Data.f #F4  : Data.i #D_16th : Data.f 0.5
  Data.f #Bb4 : Data.i #D_16th : Data.f 0.5
  Data.f #A4  : Data.i #D_16th : Data.f 0.5
  Data.f #Ab4 : Data.i #D_16th : Data.f 0.5 ; G#4
  ; Measure 18
  Data.f #A4  : Data.i #D_16th : Data.f 0.5
  Data.f #Ab4 : Data.i #D_16th : Data.f 0.5 ; G#4
  Data.f #G4  : Data.i #D_16th : Data.f 0.5
  Data.f #Gb4 : Data.i #D_16th : Data.f 0.5 ; F#4
  Data.f #F4  : Data.i #D_16th : Data.f 0.5
  Data.f #Gb4 : Data.i #D_16th : Data.f 0.5 ; F#4
  Data.f #G4  : Data.i #D_16th : Data.f 0.5
  Data.f #Ab4 : Data.i #D_16th : Data.f 0.5 ; G#4
  ; Measure 19
  Data.f #A4  : Data.i #D_16th : Data.f 0.5
  Data.f #Ab4 : Data.i #D_16th : Data.f 0.5 ; G#4
  Data.f #G4  : Data.i #D_16th : Data.f 0.5
  Data.f #Gb4 : Data.i #D_16th : Data.f 0.5 ; F#4
  Data.f #G4  : Data.i #D_16th : Data.f 0.5
  Data.f #Gb4 : Data.i #D_16th : Data.f 0.5 ; F#4
  Data.f #F4  : Data.i #D_16th : Data.f 0.5
  Data.f #E4  : Data.i #D_16th : Data.f 0.5
  ; Measure 20
  Data.f #F4  : Data.i #D_16th : Data.f 0.5
  Data.f #Gb4 : Data.i #D_16th : Data.f 0.5 ; F#4
  Data.f #G4  : Data.i #D_16th : Data.f 0.5
  Data.f #Ab4 : Data.i #D_16th : Data.f 0.5 ; G#4
  Data.f #A4  : Data.i #D_16th : Data.f 0.5
  Data.f #Bb4 : Data.i #D_16th : Data.f 0.5
  Data.f #A4  : Data.i #D_16th : Data.f 0.5
  Data.f #Ab4 : Data.i #D_16th : Data.f 0.5 ; G#4
  ; Measure 21
  Data.f #A4  : Data.i #D_16th : Data.f 0.5
  Data.f #Ab4 : Data.i #D_16th : Data.f 0.5
  Data.f #G4  : Data.i #D_16th : Data.f 0.5
  Data.f #Gb4 : Data.i #D_16th : Data.f 0.5 ; F#4
  Data.f #G4  : Data.i #D_16th : Data.f 0.5
  Data.f #Gb4 : Data.i #D_16th : Data.f 0.5 ; F#4
  Data.f #F4  : Data.i #D_16th : Data.f 0.5
  Data.f #E4  : Data.i #D_16th : Data.f 0.5
  ; Measure 22
  Data.f #F4  : Data.i #D_16th : Data.f 0.5
  Data.f #Gb4 : Data.i #D_16th : Data.f 0.5 ; F#4
  Data.f #G4  : Data.i #D_16th : Data.f 0.5
  Data.f #Ab4 : Data.i #D_16th : Data.f 0.5 ; G#4
  Data.f #A4  : Data.i #D_16th : Data.f 0.5
  Data.f #Bb4 : Data.i #D_16th : Data.f 0.5
  Data.f #A4  : Data.i #D_16th : Data.f 0.5
  Data.f #Ab4 : Data.i #D_16th : Data.f 0.5 ; G#4
  ; Measure 23
  Data.f #A4  : Data.i #D_8th : Data.f 0.5
  Data.f #REST : Data.i #D_8th : Data.f 0.0
  Data.f #A4  : Data.i #D_Quarter: Data.f 0.5
  ; Measure 24
  Data.f #Bb4 : Data.i #D_Half : Data.f 0.5
  ; Measure 25
  Data.f #A4  : Data.i #D_Quarter: Data.f 0.5
  Data.f #A4  : Data.i #D_Quarter: Data.f 0.5
  ; Measure 26
  Data.f #Bb4 : Data.i #D_Half : Data.f 0.5
  ; Measure 27
  Data.f #A4  : Data.i #D_16th : Data.f 0.5
  Data.f #Bb4 : Data.i #D_16th : Data.f 0.5
  Data.f #A4  : Data.i #D_16th : Data.f 0.5
  Data.f #Ab4 : Data.i #D_16th : Data.f 0.5 ; G#4
  Data.f #A4  : Data.i #D_16th : Data.f 0.5
  Data.f #Bb4 : Data.i #D_16th : Data.f 0.5
  Data.f #A4  : Data.i #D_16th : Data.f 0.5
  Data.f #Ab4 : Data.i #D_16th : Data.f 0.5 ; G#4
  ; Measure 28
  Data.f #A4  : Data.i #D_16th : Data.f 0.5
  Data.f #Bb4 : Data.i #D_16th : Data.f 0.5
  Data.f #A4  : Data.i #D_16th : Data.f 0.5
  Data.f #Ab4 : Data.i #D_16th : Data.f 0.5 ; G#4
  Data.f #A4  : Data.i #D_16th : Data.f 0.5
  Data.f #Bb4 : Data.i #D_16th : Data.f 0.5
  Data.f #A4  : Data.i #D_16th : Data.f 0.5
  Data.f #Ab4 : Data.i #D_16th : Data.f 0.5 ; G#4
  ; Measure 29
  Data.f #A4  : Data.i #D_16th : Data.f 0.5
  Data.f #Bb4 : Data.i #D_16th : Data.f 0.5 ; A#4
  Data.f #B4  : Data.i #D_16th : Data.f 0.5
  Data.f #C5  : Data.i #D_16th : Data.f 0.5
  Data.f #Db5 : Data.i #D_16th : Data.f 0.5 ; C#5 -> Db5 needed
  Data.f #C5  : Data.i #D_16th : Data.f 0.5
  Data.f #B4  : Data.i #D_16th : Data.f 0.5
  Data.f #Bb4 : Data.i #D_16th : Data.f 0.5
  ; Measure 30
  Data.f #A4  : Data.i #D_16th : Data.f 0.5
  Data.f #Bb4 : Data.i #D_16th : Data.f 0.5 ; A#4
  Data.f #B4  : Data.i #D_16th : Data.f 0.5
  Data.f #C5  : Data.i #D_16th : Data.f 0.5
  Data.f #Db5 : Data.i #D_16th : Data.f 0.5 ; C#5 -> Db5 needed
  Data.f #C5  : Data.i #D_16th : Data.f 0.5
  Data.f #B4  : Data.i #D_16th : Data.f 0.5
  Data.f #Bb4 : Data.i #D_16th : Data.f 0.5
  ; Measure 31
  Data.f #A4  : Data.i #D_8th : Data.f 0.5
  Data.f #REST : Data.i #D_8th : Data.f 0.0
  Data.f #D5  : Data.i #D_Quarter: Data.f 0.5
  ; Measure 32
  Data.f #Eb5 : Data.i #D_Half : Data.f 0.5 ; Chord Db5+Eb5 -> taking Eb5 for melody

  Data.f -1.0 : Data.i 0 : Data.f 0.0 ; Sentinel
EndDataSection

; [UPDATED] Flight of the Bumblebee Excerpt (Measures 1-32) - Bass (Voice 1) - More Sustained
DataSection
  melody_bass_data:
  ; Measure 1
  Data.f #E3  : Data.i #D_Half : Data.f -0.5 ; Hold E chord root longer
  ; Measure 2
  Data.f #REST : Data.i #D_Half : Data.f 0.0
  ; Measure 3
  Data.f #E2  : Data.i #D_Half : Data.f -0.5 ; Hold E chord root longer (lower octave)
  ; Measure 4
  Data.f #REST : Data.i #D_Half : Data.f 0.0
  ; Measure 5
  Data.f #REST : Data.i #D_Half : Data.f 0.0
  ; Measure 6
  Data.f #REST : Data.i #D_Half : Data.f 0.0
  ; Measure 7
  Data.f #A2  : Data.i #D_Half : Data.f -0.5 ; Hold A chord root longer
  ; Measure 8
  Data.f #A2  : Data.i #D_Half : Data.f -0.5 ; Continue holding A
  ; Measure 9
  Data.f #A2  : Data.i #D_Half : Data.f -0.5 ; Hold A chord root longer
  ; Measure 10
  Data.f #A2  : Data.i #D_Half : Data.f -0.5 ; Continue holding A
  ; Measure 11
  Data.f #A2  : Data.i #D_8th : Data.f -0.5 ; Original A chord
  Data.f #REST : Data.i #D_8th : Data.f 0.0 ; Rest
  Data.f #F3  : Data.i #D_8th : Data.f -0.5 ; Original F chord
  Data.f #REST : Data.i #D_8th : Data.f 0.0 ; Rest
  ; Measure 12
  Data.f #E3  : Data.i #D_8th : Data.f -0.5 ; Original E chord
  Data.f #REST : Data.i #D_8th : Data.f 0.0 ; Rest
  Data.f #D3  : Data.i #D_8th : Data.f -0.5 ; Original D chord (lowest note)
  Data.f #REST : Data.i #D_8th : Data.f 0.0 ; Rest
   ; Measure 13
  Data.f #C3  : Data.i #D_8th : Data.f -0.5 ; Original C chord (lowest note)
  Data.f #REST : Data.i #D_8th : Data.f 0.0 ; Rest
  Data.f #F3  : Data.i #D_8th : Data.f -0.5 ; Original F chord
  Data.f #REST : Data.i #D_8th : Data.f 0.0 ; Rest
  ; Measure 14
  Data.f #E3  : Data.i #D_8th : Data.f -0.5 ; Original E chord
  Data.f #REST : Data.i #D_8th : Data.f 0.0 ; Rest
  Data.f #D3  : Data.i #D_8th : Data.f -0.5 ; Original D chord
  Data.f #REST : Data.i #D_8th : Data.f 0.0 ; Rest
  ; Measure 15
  Data.f #Db3 : Data.i #D_8th : Data.f -0.5 ; Original C# chord (lowest note Db)
  Data.f #REST : Data.i #D_8th : Data.f 0.0 ; Rest
  Data.f #D3  : Data.i #D_8th : Data.f -0.5 ; Original D chord
  Data.f #REST : Data.i #D_8th : Data.f 0.0 ; Rest
  ; Measure 16
  Data.f #F3  : Data.i #D_8th : Data.f -0.5 ; Original F chord
  Data.f #REST : Data.i #D_8th : Data.f 0.0 ; Rest
  Data.f #D3  : Data.i #D_8th : Data.f -0.5 ; Original D chord
  Data.f #REST : Data.i #D_8th : Data.f 0.0 ; Rest
  ; --- START NEW MEASURES ---
  ; Measure 17
  Data.f #F3  : Data.i #D_8th : Data.f -0.5 ; Chord F3+A3+D4
  Data.f #REST : Data.i #D_8th : Data.f 0.0
  Data.f #REST : Data.i #D_Quarter: Data.f 0.0
  ; Measure 18
  Data.f #F3  : Data.i #D_8th : Data.f -0.5 ; Chord F3+A3+D4
  Data.f #REST : Data.i #D_8th : Data.f 0.0
  Data.f #D3  : Data.i #D_8th : Data.f -0.5 ; Chord D3+G3+B3
  Data.f #REST : Data.i #D_8th : Data.f 0.0
  ; Measure 19
  Data.f #F3  : Data.i #D_8th : Data.f -0.5 ; Chord F3+A3+D4
  Data.f #REST : Data.i #D_8th : Data.f 0.0
  Data.f #G3  : Data.i #D_8th : Data.f -0.5 ; Chord G3+Bb3+D4
  Data.f #REST : Data.i #D_8th : Data.f 0.0
  ; Measure 20
  Data.f #F3  : Data.i #D_8th : Data.f -0.5 ; Chord F3+A3+D4
  Data.f #REST : Data.i #D_8th : Data.f 0.0
  Data.f #E3  : Data.i #D_8th : Data.f -0.5 ; Chord E3+G3+A3+C#4 -> E3 lowest
  Data.f #REST : Data.i #D_8th : Data.f 0.0
  ; Measure 21
  Data.f #F3  : Data.i #D_8th : Data.f -0.5 ; Chord F3+A3+D4
  Data.f #REST : Data.i #D_8th : Data.f 0.0
  Data.f #D3  : Data.i #D_8th : Data.f -0.5 ; Chord D3+G3+Bb3
  Data.f #REST : Data.i #D_8th : Data.f 0.0
  ; Measure 22
  Data.f #D3  : Data.i #D_8th : Data.f -0.5 ; Chord D3+F3+A3
  Data.f #REST : Data.i #D_8th : Data.f 0.0
  Data.f #Db3 : Data.i #D_8th : Data.f -0.5 ; Chord C#3+E3+G3+A3 -> C#3 lowest (Db3)
  Data.f #REST : Data.i #D_8th : Data.f 0.0
  ; Measure 23 - Bass has 16ths now
  Data.f #REST : Data.i #D_16th: Data.f 0.0
  Data.f #A3  : Data.i #D_16th: Data.f -0.5
  Data.f #B3  : Data.i #D_16th: Data.f -0.5
  Data.f #A3  : Data.i #D_16th: Data.f -0.5
  Data.f #B3  : Data.i #D_16th: Data.f -0.5
  Data.f #A3  : Data.i #D_16th: Data.f -0.5
  Data.f #B3  : Data.i #D_16th: Data.f -0.5
  Data.f #A3  : Data.i #D_16th: Data.f -0.5
  ; Measure 24 - Bass continues 16ths
  Data.f #Bb3 : Data.i #D_16th: Data.f -0.5
  Data.f #Ab3 : Data.i #D_16th: Data.f -0.5
  Data.f #Bb3 : Data.i #D_16th: Data.f -0.5
  Data.f #Ab3 : Data.i #D_16th: Data.f -0.5
  Data.f #Bb3 : Data.i #D_16th: Data.f -0.5
  Data.f #Ab3 : Data.i #D_16th: Data.f -0.5
  Data.f #Bb3 : Data.i #D_16th: Data.f -0.5
  Data.f #Ab3 : Data.i #D_16th: Data.f -0.5
  ; Measure 25
  Data.f #REST : Data.i #D_16th: Data.f 0.0
  Data.f #A3  : Data.i #D_16th: Data.f -0.5
  Data.f #B3  : Data.i #D_16th: Data.f -0.5
  Data.f #A3  : Data.i #D_16th: Data.f -0.5
  Data.f #B3  : Data.i #D_16th: Data.f -0.5
  Data.f #A3  : Data.i #D_16th: Data.f -0.5
  Data.f #B3  : Data.i #D_16th: Data.f -0.5
  Data.f #A3  : Data.i #D_16th: Data.f -0.5
  ; Measure 26
  Data.f #Bb3 : Data.i #D_16th: Data.f -0.5
  Data.f #Ab3 : Data.i #D_16th: Data.f -0.5
  Data.f #Bb3 : Data.i #D_16th: Data.f -0.5
  Data.f #Ab3 : Data.i #D_16th: Data.f -0.5
  Data.f #Bb3 : Data.i #D_16th: Data.f -0.5
  Data.f #Ab3 : Data.i #D_16th: Data.f -0.5
  Data.f #Bb3 : Data.i #D_16th: Data.f -0.5
  Data.f #Ab3 : Data.i #D_16th: Data.f -0.5
  ; Measure 27
  Data.f #A3  : Data.i #D_8th : Data.f -0.5
  Data.f #REST : Data.i #D_8th : Data.f 0.0
  Data.f #Ab3 : Data.i #D_8th : Data.f -0.5 ; Chord Ab3+Bb3
  Data.f #REST : Data.i #D_8th : Data.f 0.0
  ; Measure 28
  Data.f #A3  : Data.i #D_8th : Data.f -0.5
  Data.f #Ab3 : Data.i #D_8th : Data.f -0.5 ; Chord Ab3+Bb3
  Data.f #G3  : Data.i #D_8th : Data.f -0.5 ; Chord G3+B3
  Data.f #Gb3 : Data.i #D_8th : Data.f -0.5 ; Chord F#3+C4 -> F#3 (Gb3) lowest
  ; Measure 29
  Data.f #F3  : Data.i #D_Half : Data.f -0.5 ; Chord F3+C#4 -> Held F3
  ; Measure 30
  Data.f #F3  : Data.i #D_Half : Data.f -0.5 ; Chord F3+C#4 -> Held F3
  ; Measure 31
  Data.f #REST : Data.i #D_16th: Data.f 0.0
  Data.f #D4  : Data.i #D_16th: Data.f -0.5
  Data.f #E4  : Data.i #D_16th: Data.f -0.5
  Data.f #D4  : Data.i #D_16th: Data.f -0.5
  Data.f #E4  : Data.i #D_16th: Data.f -0.5
  Data.f #D4  : Data.i #D_16th: Data.f -0.5
  Data.f #E4  : Data.i #D_16th: Data.f -0.5
  Data.f #D4  : Data.i #D_16th: Data.f -0.5
  ; Measure 32
  Data.f #Eb4 : Data.i #D_16th: Data.f -0.5
  Data.f #Db4 : Data.i #D_16th: Data.f -0.5
  Data.f #Eb4 : Data.i #D_16th: Data.f -0.5
  Data.f #Db4 : Data.i #D_16th: Data.f -0.5
  Data.f #Eb4 : Data.i #D_16th: Data.f -0.5
  Data.f #Db4 : Data.i #D_16th: Data.f -0.5
  Data.f #Eb4 : Data.i #D_16th: Data.f -0.5
  Data.f #Db4 : Data.i #D_16th: Data.f -0.5

  Data.f -1.0 : Data.i 0 : Data.f 0.0 ; Sentinel
EndDataSection

Define tempFreq.f, tempDur.i, tempPan.f
Restore melody_treble_data
Repeat
  Read.f tempFreq : If tempFreq = -1.0 : Break : EndIf : Read.i tempDur : Read.f tempPan
  AddElement(melodyTreble()) : melodyTreble()\frequency = tempFreq : melodyTreble()\durationMs = tempDur : melodyTreble()\pan = tempPan
ForEver
Restore melody_bass_data
Repeat
  Read.f tempFreq : If tempFreq = -1.0 : Break : EndIf : Read.i tempDur : Read.f tempPan
  AddElement(melodyBass()) : melodyBass()\frequency = tempFreq : melodyBass()\durationMs = tempDur : melodyBass()\pan = tempPan
ForEver

wfx\wFormatTag = #WAVE_FORMAT_PCM : wfx\nChannels = 2 : wfx\nSamplesPerSec = 44100
wfx\wBitsPerSample = 16 : wfx\nBlockAlign = (wfx\nChannels * wfx\wBitsPerSample) / 8
wfx\nAvgBytesPerSec = wfx\nSamplesPerSec * wfx\nBlockAlign : wfx\cbSize = 0

bufferSize = wfx\nAvgBytesPerSec / 4
Debug "Audio Format: 44.1kHz, 16-bit, Stereo"
Debug "Buffer Size (250ms): " + Str(bufferSize) + " bytes"
Define waveName.s = ""
Select g_WaveformType
    Case #Wave_Square: waveName = "Square" : Case #Wave_Sine: waveName = "Sine"
    Case #Wave_Triangle: waveName = "Triangle" : Case #Wave_WhiteNoise: waveName = "White Noise"
    Case #Wave_PinkNoise: waveName = "Pink Noise"
EndSelect
Debug "Waveform: " + waveName

Define result.i
result = waveOutOpen_(@hWaveOut, #WAVE_MAPPER, @wfx, @WaveCallback(), 0, #CALLBACK_FUNCTION)
If result <> 0 : MessageRequester("Error", "Could not open audio device. Error: " + Str(result)) : End : EndIf
Debug "Audio device opened successfully (Handle: " + Str(hWaveOut) + ")"

Define i
For i = 0 To #NUM_BUFFERS - 1
  *waveBuffers(i) = AllocateMemory(bufferSize)
  FillBuffer(*waveBuffers(i), bufferSize)
  waveHeaders(i)\lpData = *waveBuffers(i)
  waveHeaders(i)\dwBufferLength = bufferSize
  result = waveOutPrepareHeader_(hWaveOut, @waveHeaders(i), SizeOf(WAVEHDR))
  If result <> 0 : MessageRequester("Error", "Could not prepare buffer " + Str(i)) : waveOutClose_(hWaveOut) : End : EndIf
Next
Debug "Prepared " + Str(#NUM_BUFFERS) + " audio buffers."

For i = 0 To #NUM_BUFFERS - 1 : waveOutWrite_(hWaveOut, @waveHeaders(i), SizeOf(WAVEHDR)) : Next
Debug "Playback started. Queued initial buffers."

OpenWindow(0, 0, 0, 300, 150, "Bumblebee Test", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
TextGadget(0, 10, 10, 280, 130, "Playing 'Flight of the Bumblebee' Excerpt..." + #CRLF$ + "Current Waveform: " + waveName + #CRLF$ + #CRLF$ + "Close this window To stop.")
Repeat : Define event.i = WaitWindowEvent() : Until event = #PB_Event_CloseWindow

Debug "Window closed. Shutting down audio..."
waveRunning = #False
waveOutReset_(hWaveOut)

For i = 0 To #NUM_BUFFERS - 1
  Repeat
    result = waveOutUnprepareHeader_(hWaveOut, @waveHeaders(i), SizeOf(WAVEHDR))
    Delay(10)
  Until result = 0 Or Not (waveHeaders(i)\dwFlags & #WHDR_PREPARED)
  FreeMemory(*waveBuffers(i))
  Debug "Unprepared And freed buffer " + Str(i)
Next

waveOutClose_(hWaveOut)
Debug "Audio device closed. Exiting."

End
User avatar
Kwai chang caine
Always Here
Always Here
Posts: 5502
Joined: Sun Nov 05, 2006 11:42 pm
Location: Lyon - France

Re: Simple on the fly audio example

Post by Kwai chang caine »

Very nice..nearly like the true :mrgreen:

Image

Thanks for sharing your concerts 8)
ImageThe happiness is a road...
Not a destination
Booger
Enthusiast
Enthusiast
Posts: 139
Joined: Tue Sep 04, 2007 2:18 pm

Re: Simple on the fly audio example

Post by Booger »

This is no longer simple but is near my goal. I converted to lists by suggestion. This is a concert of Mario 1-1.
Enjoy and feel free to make it great.
If you improve please share to the community. The shutdown isn't always perfect.
This is my last concert for a while, going back into lurk mode to emulate some ancient hardwares.

Code: Select all

; This program creates a 250ms cyclic audio buffer using the
; low-level Windows Waveform Audio API (winmm.dll).
; It uses a callback function To refill buffers As they finish playing.
;
; This version plays the Super Mario Bros. theme (3 voices)
; using selectable waveforms FOR EACH VOICE.
;
; *** This version uses COMPRESSED data sections (byte indices) ***
; *** to reduce total file size. ***

; --- Waveform Type Constants ---
Enumeration
  #Wave_Square
  #Wave_Sine
  #Wave_Triangle
  #Wave_WhiteNoise
  #Wave_PinkNoise
EndEnumeration

; --- Constants And Structures ---
#NUM_BUFFERS = 2
#NUM_VOICES = 3 ; Treble, Bass, Percussion

#WAVE_MAPPER = -1
#WAVE_FORMAT_PCM = 1
#CALLBACK_FUNCTION = $30000
#WOM_OPEN = $3BB
#WOM_DONE = $3BD
#WOM_CLOSE = $3BC
#WHDR_PREPARED = $2
#WHDR_DONE = $1 ; Flag indicating buffer is finished playing

Structure Note
  frequency.f
  durationMs.i
  pan.f ; -1.0 (Left) To 1.0 (Right)
EndStructure

; --- Note frequencies ---
#E2 = 82.41 : #G2 = 98.00
#A2 = 110.00 : #Bb2 = 116.54 : #B2 = 123.47
#C3 = 130.81 : #Db3 = 138.59 : #D3 = 146.83 : #Eb3 = 155.56 : #E3 = 164.81 : #F3 = 174.61 : #Gb3 = 185.00 : #G3 = 196.00 : #Ab3 = 207.65 : #A3 = 220.00 : #Bb3 = 233.08 : #B3 = 246.94
#C4 = 261.63 : #Db4 = 277.18 : #D4 = 293.66 : #Eb4 = 311.13 : #E4 = 329.63 : #F4 = 349.23 : #Gb4 = 369.99 : #G4 = 392.00 : #Ab4 = 415.30 : #A4 = 440.00 : #Bb4 = 466.16 : #B4 = 493.88
#C5 = 523.25 : #Db5 = 554.37 : #D5 = 587.33 : #Eb5 = 622.25 : #E5 = 659.26 : #F5 = 698.46 : #Gb5 = 739.99 : #G5 = 783.99 : #Ab5 = 830.61 : #A5 = 880.00 : #Bb5 = 932.33 : #B5 = 987.77
#C6 = 1046.50 : #Db6 = 1108.73 : #D6 = 1174.66 : #Eb6 = 1244.51 : #E6 = 1318.51 : #F6 = 1396.91 : #Gb6 = 1479.98 : #G6 = 1567.98 : #Ab6 = 1661.22 : #A6 = 1760.00 : #C7 = 2093.00 : #REST = 0.0

; Sharps
#FSharp3 = #Gb3
#CSharp4 = #Db4 : #DSharp4 = #Eb4 : #FSharp4 = #Gb4 : #GSharp4 = #Ab4 : #ASharp4 = #Bb4
#CSharp5 = #Db5 : #DSharp5 = #Eb5 : #FSharp5 = #Gb5 : #GSharp5 = #Ab5 : #ASharp5 = #Bb5
#CSharp6 = #Db6 : #DSharp6 = #Eb6

; --- Note durations ---
#BPM = 105 ; Tempo from Mario XML
#BeatMs = (60000 / #BPM) ; Milliseconds per beat (Quarter note)
#D_Whole = #BeatMs * 4
#D_Half = #BeatMs * 2
#D_Quarter = #BeatMs
#D_8th = #BeatMs / 2
#D_16th = #BeatMs / 4
#D_Triplet = #BeatMs / 3
#D_Dot_8th = #D_8th + #D_16th

; --- [NEW] Data Lookup Arrays ---
Global Dim g_Frequencies.f(40) ; Map for note frequencies
Global Dim g_Durations.i(6)    ; Map for note durations
Global Dim g_Pans.f(2)         ; Map for pan values

; Populate Frequency Map (Index -> Frequency)
g_Frequencies(0) = #REST
g_Frequencies(1) = #E2 : g_Frequencies(2) = #G2 : g_Frequencies(3) = #A2
g_Frequencies(4) = #C3 : g_Frequencies(5) = #D3 : g_Frequencies(6) = #E3 : g_Frequencies(7) = #F3 : g_Frequencies(8) = #FSharp3 : g_Frequencies(9) = #G3 : g_Frequencies(10) = #A3 : g_Frequencies(11) = #B3
g_Frequencies(12) = #C4
g_Frequencies(13) = #D4   ; [FIX] Added missing D4
g_Frequencies(14) = #E4   ; (was 13)
g_Frequencies(15) = #F4   ; (was 14)
g_Frequencies(16) = #Gb4  ; (was 15)
g_Frequencies(17) = #G4   ; (was 16)
g_Frequencies(18) = #A4   ; (was 17)
g_Frequencies(19) = #B4   ; (was 18)
g_Frequencies(20) = #C5   ; (was 19)
g_Frequencies(21) = #D5   ; (was 20)
g_Frequencies(22) = #Eb5  ; (was 21)
g_Frequencies(23) = #E5   ; (was 22)
g_Frequencies(24) = #F5   ; (was 23)
g_Frequencies(25) = #Gb5  ; (was 24)
g_Frequencies(26) = #G5   ; (was 25)
g_Frequencies(27) = #Ab5  ; (was 26)
g_Frequencies(28) = #A5   ; (was 27)
g_Frequencies(29) = #Bb5  ; (was 28)
g_Frequencies(30) = #B5   ; (was 29)
g_Frequencies(31) = #C6   ; (was 30)
g_Frequencies(32) = #D6   ; (was 31)
g_Frequencies(33) = #Eb6  ; (was 32)
g_Frequencies(34) = #E6   ; (was 33)
g_Frequencies(35) = #F6   ; (was 34)
g_Frequencies(36) = #Gb6  ; (was 35)
g_Frequencies(37) = #G6   ; (was 36)
g_Frequencies(38) = #A6   ; (was 37)
g_Frequencies(39) = #C7   ; (was 38)
g_Frequencies(40) = #A4   ; Percussion (was 39)


; Populate Duration Map (Index -> Duration in MS)
g_Durations(0) = #D_16th
g_Durations(1) = #D_8th
g_Durations(2) = #D_Dot_8th
g_Durations(3) = #D_Quarter
g_Durations(4) = #D_Half
g_Durations(5) = #D_Whole
g_Durations(6) = #D_Triplet

; Populate Pan Map (Index -> Pan value)
g_Pans(0) = 0.0  ; Center
g_Pans(1) = 0.2  ; Treble Pan
g_Pans(2) = -0.2 ; Bass Pan

; --- Globals ---
Global hWaveOut.i
Global wfx.WAVEFORMATEX
Global Dim waveHeaders.WAVEHDR(#NUM_BUFFERS - 1)
Global Dim *waveBuffers(#NUM_BUFFERS - 1)
Global bufferSize.l
Global waveRunning.i = #True ; Flag to control buffer submission in callback

; [CHANGED] Waveform selection is now per-voice
Global g_MelodyWaveformType = #Wave_Square ; <<< CHANGE THIS (Melody wave)
Global g_BassWaveformType = #Wave_Triangle ; <<< CHANGE THIS (Bass wave)
Global g_PercussionWaveformType = #Wave_PinkNoise ; <<< [CHANGED] Set to Pink Noise >>>
Global g_amplitude.f = 10000.0 / #NUM_VOICES ; Reduce amplitude per voice

#PINK_OCTAVES = 5
Global Dim pink_Values.f(#PINK_OCTAVES - 1)
Global pink_Index.i = 0

Global envelopeMs.i = 10 ; Envelope duration in milliseconds
Global envelopeSamples.i = 0

Global NewList melodyTreble.Note() ; Voice 0
Global NewList melodyBass.Note()   ; Voice 1
Global NewList melodyPercussion.Note() ; Voice 2

Global Dim g_NoteCounter.i(#NUM_VOICES - 1) ; Note counters for debugging

Global Dim freq.f(#NUM_VOICES - 1)
Global Dim pan.f(#NUM_VOICES - 1)
Global Dim phase.d(#NUM_VOICES - 1)
Global Dim samplesLeft.i(#NUM_VOICES - 1)
Global Dim totalSamples.i(#NUM_VOICES - 1)
Global Dim samplesPlayed.i(#NUM_VOICES - 1)
Global Dim isFirstNote(#NUM_VOICES - 1)

Define initVoiceIndex.i
For initVoiceIndex = 0 To #NUM_VOICES - 1
  isFirstNote(initVoiceIndex) = #True
Next

Global Dim cycleLength.d(#NUM_VOICES - 1)
Global Dim halfCycle.d(#NUM_VOICES - 1)
Global Dim radPhaseStep.d(#NUM_VOICES - 1)

; --- Audio Generation ---
Procedure FillBuffer(*buffer, length)
  Define *b.Word = *buffer
  Define samplesToFill.i = length / (wfx\nBlockAlign)
  Define i, v
  Define sampleValue.f, finalL.f, finalR.f
  Define temp.f, sum.f, j
  Define envelopeMultiplier.f
  Define durationMs.i
  Define piOverTwo.f = #PI / 2.0 ; Precalculate for envelope
  Define currentWaveform.i

  ; Calculate envelope length in samples only once if needed
  If envelopeSamples = 0
    envelopeSamples = (wfx\nSamplesPerSec * envelopeMs) / 1000
    If envelopeSamples = 0 : envelopeSamples = 1 : EndIf ; Ensure at least 1 sample
  EndIf

  For i = 0 To samplesToFill - 1
    finalL = 0.0 : finalR = 0.0

    For v = 0 To #NUM_VOICES - 1
      ; Check if the current note for this voice has finished playing
      If samplesLeft(v) <= 0
        ; Get the next note from the appropriate melody list
        Select v
          Case 0 ; Treble
            If isFirstNote(v) ; Handle the very first note
              If Not FirstElement(melodyTreble()) : freq(v)=0.0:totalSamples(v)=1:samplesLeft(v)=1:Debug "Error: Treble empty!": EndIf
              isFirstNote(v)=#False
            Else ; Get the next note, loop back to start if end is reached
              If NextElement(melodyTreble())=0 : FirstElement(melodyTreble()) : EndIf
            EndIf
            ; Read note properties from the list element
            If ListSize(melodyTreble())>0
              freq(v)=melodyTreble()\frequency : pan(v)=melodyTreble()\pan : durationMs=melodyTreble()\durationMs
            Else : freq(v)=0.0:pan(v)=0.0:durationMs=1000 : EndIf ; Default if list empty
          Case 1 ; Bass
            If isFirstNote(v) ; Handle the very first note
              If Not FirstElement(melodyBass()) : freq(v)=0.0:totalSamples(v)=1:samplesLeft(v)=1:Debug "Error: Bass empty!": EndIf
              isFirstNote(v)=#False
            Else ; Get the next note, loop back to start if end is reached
              If NextElement(melodyBass())=0 : FirstElement(melodyBass()) : EndIf
            EndIf
             ; Read note properties from the list element
            If ListSize(melodyBass())>0
              freq(v)=melodyBass()\frequency : pan(v)=melodyBass()\pan : durationMs=melodyBass()\durationMs
            Else : freq(v)=0.0:pan(v)=0.0:durationMs=1000 : EndIf ; Default if list empty
          Case 2 ; Percussion
            If isFirstNote(v) ; Handle the very first note
              If Not FirstElement(melodyPercussion()) : freq(v)=0.0:totalSamples(v)=1:samplesLeft(v)=1:Debug "Error: Percussion empty!": EndIf
              isFirstNote(v)=#False
            Else ; Get the next note, loop back to start if end is reached
              If NextElement(melodyPercussion())=0 : FirstElement(melodyPercussion()) : EndIf
            EndIf
             ; Read note properties from the list element
            If ListSize(melodyPercussion())>0
              freq(v)=melodyPercussion()\frequency : pan(v)=melodyPercussion()\pan : durationMs=melodyPercussion()\durationMs
            Else : freq(v)=0.0:pan(v)=0.0:durationMs=1000 : EndIf ; Default if list empty
        EndSelect
        
        ; Increment and debug note counter for melody voice
        g_NoteCounter(v) + 1
        If v = 0 ; Only print for the melody voice (voice 0)
          ;Debug "Voice 0 (Melody) playing note: " + Str(g_NoteCounter(v))
        EndIf

        ; Calculate total samples for the new note's duration
        totalSamples(v)=(wfx\nSamplesPerSec * durationMs)/1000
        If totalSamples(v)<=0 : totalSamples(v)=1 : EndIf ; Ensure at least 1 sample
        samplesLeft(v)=totalSamples(v) : samplesPlayed(v)=0 : phase(v)=0.0 ; Reset counters for the new note

        ; Pre-calculate phase step for sine/triangle waves if frequency is valid
        If freq(v)>0
          cycleLength(v)=wfx\nSamplesPerSec / freq(v) : halfCycle(v)=cycleLength(v)/2.0 : radPhaseStep(v)=(2*#PI)/cycleLength(v)
        Else ; Reset for rests
          cycleLength(v)=0 : halfCycle(v)=0 : radPhaseStep(v)=0
        EndIf
      EndIf

      ; [CHANGED] Set waveform type based on voice
      Select v
        Case 0 ; Melody
          currentWaveform = g_MelodyWaveformType
        Case 1 ; Bass
          currentWaveform = g_BassWaveformType
        Case 2 ; Percussion
          currentWaveform = g_PercussionWaveformType
      EndSelect

      sampleValue=0.0 ; Default to silence
      If freq(v)>0 ; Only generate sound if frequency is not REST (#REST = 0.0)
        Select currentWaveform ; Use the waveform specific to this voice
          Case #Wave_Square
            If halfCycle(v)>0 ; Avoid division by zero
              If phase(v)<halfCycle(v) : sampleValue=g_amplitude : Else : sampleValue=-g_amplitude : EndIf
            EndIf
          Case #Wave_Sine
            If cycleLength(v)>0 : sampleValue=Sin(phase(v)*radPhaseStep(v))*g_amplitude : EndIf
          Case #Wave_Triangle
            If cycleLength(v)>0
              temp=phase(v)/cycleLength(v)
              If temp<0.5 : sampleValue=(temp*4.0-1.0)*g_amplitude ; Rising slope
              Else : sampleValue=((1.0-temp)*4.0-1.0)*g_amplitude : EndIf ; Falling slope
            EndIf
          Case #Wave_WhiteNoise : sampleValue=Random(g_amplitude*2)-g_amplitude ; Simple white noise
          Case #Wave_PinkNoise ; Approximation using averaging random sources
            pink_Index=(pink_Index+1)%#PINK_OCTAVES : pink_Values(pink_Index)=(Random(10000)/5000.0)-1.0
            sum=0.0 : For j=0 To #PINK_OCTAVES-1 : sum+pink_Values(j) : Next
            sampleValue=(sum/#PINK_OCTAVES)*g_amplitude*1.5 ; Scale factor is empirical
        EndSelect
      EndIf

      ; Apply smoother Attack/Decay envelope using Sine curve
      envelopeMultiplier=1.0
      If freq(v)>0 And totalSamples(v)>1 And envelopeSamples>0
        Define fadeLength.f=envelopeSamples
        
        ; [MODIFIED] Percussion uses a slightly longer custom fade now, not the full note length
        If v = 2
          fadeLength = (wfx\nSamplesPerSec * 40) / 1000 ; Force 40ms fade for percussion
          If fadeLength <= 0 : fadeLength = 1 : EndIf
        Else
          ; Prevent envelope overlap if note is very short for non-percussion
          If fadeLength*2.0>totalSamples(v) : fadeLength=totalSamples(v)/2.0 : EndIf
        EndIf

        If fadeLength > 0 ; Proceed only if fade length is valid
          If samplesPlayed(v) < fadeLength ; Attack phase (0 to pi/2)
            envelopeMultiplier = Sin( (samplesPlayed(v) / fadeLength) * piOverTwo )
          ElseIf (totalSamples(v) - samplesPlayed(v)) <= fadeLength And v <> 2 ; Decay phase for non-percussion
            ; Calculate remaining samples in decay phase (fadeLength down to 1)
            Define samplesRemainingInFade.f = (totalSamples(v) - samplesPlayed(v) - 1.0)
            If samplesRemainingInFade < 0 : samplesRemainingInFade = 0 : EndIf
            envelopeMultiplier = Sin( (samplesRemainingInFade / fadeLength) * piOverTwo )
          ElseIf v = 2 And samplesPlayed(v) >= fadeLength ; [NEW] Cut off percussion after its fade-in
            envelopeMultiplier = 0.0 ; Cut off sharply after attack
          Else
            envelopeMultiplier = 1.0 ; Sustain phase
          EndIf

          ; Clamp envelope multiplier (Sin should naturally be 0 to 1, but for safety)
          If envelopeMultiplier<0.0 : envelopeMultiplier=0.0 : EndIf
          If envelopeMultiplier>1.0 : envelopeMultiplier=1.0 : EndIf
        Else
          envelopeMultiplier = 1.0 ; No fade if fadeLength is zero
        EndIf
      EndIf
      
      sampleValue*envelopeMultiplier ; Apply envelope fade

      ; Calculate left/right channel volume based on pan value (-1=Left, 0=Center, 1=Right)
      Define panL.f = (1.0 - pan(v)) / 2.0
      Define panR.f = (1.0 + pan(v)) / 2.0
      Define voiceL.f = sampleValue * panL
      Define voiceR.f = sampleValue * panR

      ; Add this voice's contribution to the final mix for this sample frame
      finalL+voiceL
      finalR+voiceR

      ; Advance phase for the next sample calculation
      If cycleLength(v)>0
        phase(v)+1.0
        If phase(v)>=cycleLength(v) : phase(v)-cycleLength(v) : EndIf ; Wrap phase back to 0 if cycle completes
      EndIf
      samplesLeft(v)-1 ; Decrement samples remaining for this note
      samplesPlayed(v)+1 ; Increment samples played for envelope calculation
    Next v ; End voice loop (v=0 to #NUM_VOICES-1)

    ; Clamp final mixed sample values to 16-bit integer range before writing
    If finalL>32767.0:finalL=32767.0:EndIf:If finalL<-32767.0:finalL=-32767.0:EndIf
    If finalR>32767.0:finalR=32767.0:EndIf:If finalR<-32767.0:finalR=-32767.0:EndIf

    ; Write interleaved stereo samples (Left, Right) to the buffer memory
    *b\w=Int(finalL):*b+2 ; Write Left sample, advance pointer by 2 bytes (size of Word)
    *b\w=Int(finalR):*b+2 ; Write Right sample, advance pointer by 2 bytes
  Next i ; End sample loop (i=0 to samplesToFill-1)
EndProcedure

; --- Callback Procedure ---
; This runs in a separate thread managed by the audio driver.
ProcedureC WaveCallback(hWaveOut.i, uMsg.i, dwInstance.i, dwParam1.i, dwParam2.i)
  Select uMsg
    Case #WOM_OPEN : Debug "Callback: Device Opened."
    Case #WOM_DONE ; This message is sent when a buffer finishes playing
      If waveRunning ; Check if we should continue playing (not shutting down)
        Define *header.WAVEHDR = dwParam1 ; dwParam1 points to the WAVEHDR of the finished buffer

        ; [Shutdown Safety Check] Ensure the header pointer is valid and the buffer
        ; is still marked as prepared before trying to refill and requeue it.
        ; This prevents trying to access a buffer that the main thread might be
        ; unpreparing during shutdown.
        If *header And (*header\dwFlags & #WHDR_PREPARED)
           FillBuffer(*header\lpData, *header\dwBufferLength) ; Refill the buffer with new audio data
           waveOutWrite_(hWaveOut, *header, SizeOf(WAVEHDR)) ; Send the refilled buffer back to the queue
        Else
           ; Don't log here during normal operation, only potentially during shutdown
           If Not waveRunning
               Debug "Callback: Received WOM_DONE for a header not marked as prepared (dwFlags=" + Str(*header\dwFlags) +"). Possibly shutting down."
           EndIf
        EndIf
      Else
        ; If waveRunning is false, it means the main thread is shutting down.
        ; Do not refill or requeue the buffer.
        Debug "Callback: Received WOM_DONE but waveRunning is false. Ignoring."
      EndIf
    Case #WOM_CLOSE : Debug "Callback: Device Closed." ; Sent when waveOutClose completes
  EndSelect
EndProcedure

; --- Main Program Setup ---

;Data Compression Explanation:
;
; The large DataSections below are compressed to save space. Instead of writing
; "Data.f #C4, Data.i #D_Quarter, Data.f 0.2", we use byte-sized indices.
; Each note is represented by three bytes:
;   Data.b <freq_index>, <duration_index>, <pan_index>
;
; 1. <freq_index>: A byte (0-255) that corresponds to an index in the
;    'g_Frequencies()' array defined above. g_Frequencies(12) holds #C4.
; 2. <duration_index>: A byte (0-255) for the 'g_Durations()' array.
;    g_Durations(3) holds #D_Quarter.
; 3. <pan_index>: A byte (0-255) for the 'g_Pans()' array.
;    g_Pans(1) holds 0.2 (treble pan).
;
; So, the note C4 for a quarter beat panned right becomes:
;   Data.b 12, 3, 1
;
; This saves a significant amount of space compared to the original text data.
; The 'Sentinel' or end-of-data marker is now:
;   Data.b -1, -1, -1

; [COMPRESSED] Super Mario Bros. Theme (Measures 1-37) - Treble (Voice 0 - P1)
; Data format: Data.b FrequencyIndex, DurationIndex, PanIndex
DataSection
  melody_treble_data:
  ; M1
  Data.b 34,0,1 : Data.b 34,1,1 : Data.b 34,0,1 : Data.b 0,0,0 : Data.b 31,0,1 : Data.b 34,1,1 : Data.b 37,3,1 : Data.b 0,3,0
  ; M2
  Data.b 31,2,1 : Data.b 26,0,1 : Data.b 0,1,0 : Data.b 23,1,1 : Data.b 0,0,0 : Data.b 28,1,1 : Data.b 30,0,1 : Data.b 0,0,0 : Data.b 29,0,1 : Data.b 28,1,1
  ; M3
  Data.b 26,0,1 : Data.b 34,1,1 : Data.b 37,0,1 : Data.b 38,1,1 : Data.b 35,0,1 : Data.b 37,0,1 : Data.b 0,0,0 : Data.b 34,1,1 : Data.b 31,0,1 : Data.b 32,0,1 : Data.b 30,2,1
  ; M4
  Data.b 31,2,1 : Data.b 26,0,1 : Data.b 0,1,0 : Data.b 23,1,1 : Data.b 0,0,0 : Data.b 28,1,1 : Data.b 30,0,1 : Data.b 0,0,0 : Data.b 29,0,1 : Data.b 28,1,1
  ; M5
  Data.b 26,0,1 : Data.b 34,1,1 : Data.b 37,0,1 : Data.b 38,1,1 : Data.b 35,0,1 : Data.b 37,0,1 : Data.b 0,0,0 : Data.b 34,1,1 : Data.b 31,0,1 : Data.b 32,0,1 : Data.b 30,2,1
  ; M6
  Data.b 0,1,0 : Data.b 37,0,1 : Data.b 36,0,1 : Data.b 35,0,1 : Data.b 33,1,1 : Data.b 34,0,1 : Data.b 0,0,0 : Data.b 26,0,1 : Data.b 28,0,1 : Data.b 31,0,1 : Data.b 0,0,0 : Data.b 28,0,1 : Data.b 31,0,1 : Data.b 32,0,1
  ; M7
  Data.b 0,1,0 : Data.b 37,0,1 : Data.b 36,0,1 : Data.b 35,0,1 : Data.b 33,1,1 : Data.b 34,0,1 : Data.b 0,0,0 : Data.b 39,1,1 : Data.b 39,0,1 : Data.b 39,3,1
  ; M8
  Data.b 0,1,0 : Data.b 37,0,1 : Data.b 36,0,1 : Data.b 35,0,1 : Data.b 33,1,1 : Data.b 34,0,1 : Data.b 0,0,0 : Data.b 26,0,1 : Data.b 28,0,1 : Data.b 31,0,1 : Data.b 0,0,0 : Data.b 28,0,1 : Data.b 31,0,1 : Data.b 32,0,1
  ; M9
  Data.b 0,1,0 : Data.b 33,1,1 : Data.b 0,0,0 : Data.b 32,2,1 : Data.b 31,3,1 : Data.b 0,3,0
  ; M10
  Data.b 0,1,0 : Data.b 37,0,1 : Data.b 36,0,1 : Data.b 35,0,1 : Data.b 33,1,1 : Data.b 34,0,1 : Data.b 0,0,0 : Data.b 27,0,1 : Data.b 28,0,1 : Data.b 31,0,1 : Data.b 0,0,0 : Data.b 28,0,1 : Data.b 31,0,1 : Data.b 32,0,1
  ; M11
  Data.b 0,1,0 : Data.b 37,0,1 : Data.b 36,0,1 : Data.b 35,0,1 : Data.b 33,1,1 : Data.b 34,0,1 : Data.b 0,0,0 : Data.b 39,1,1 : Data.b 39,0,1 : Data.b 39,3,1
  ; M12
  Data.b 0,1,0 : Data.b 37,0,1 : Data.b 36,0,1 : Data.b 35,0,1 : Data.b 33,1,1 : Data.b 34,0,1 : Data.b 0,0,0 : Data.b 26,0,1 : Data.b 28,0,1 : Data.b 31,0,1 : Data.b 0,0,0 : Data.b 28,0,1 : Data.b 31,0,1 : Data.b 32,0,1
  ; M13
  Data.b 0,1,0 : Data.b 33,1,1 : Data.b 0,0,0 : Data.b 32,2,1 : Data.b 31,3,1 : Data.b 0,3,0
  ; M14
  Data.b 31,0,1 : Data.b 31,1,1 : Data.b 31,0,1 : Data.b 0,0,0 : Data.b 31,0,1 : Data.b 32,1,1 : Data.b 34,0,1 : Data.b 31,1,1 : Data.b 28,0,1 : Data.b 26,3,1
  ; M15
  Data.b 31,0,1 : Data.b 31,1,1 : Data.b 31,0,1 : Data.b 0,0,0 : Data.b 31,0,1 : Data.b 32,1,1 : Data.b 34,0,1 : Data.b 0,4,0
  ; M16
  Data.b 31,0,1 : Data.b 31,1,1 : Data.b 31,0,1 : Data.b 0,0,0 : Data.b 31,0,1 : Data.b 32,1,1 : Data.b 34,0,1 : Data.b 31,1,1 : Data.b 28,0,1 : Data.b 26,3,1
  ; M17
  Data.b 34,0,1 : Data.b 34,1,1 : Data.b 34,0,1 : Data.b 0,0,0 : Data.b 31,0,1 : Data.b 34,1,1 : Data.b 37,3,1 : Data.b 0,3,0
  ; M18
  Data.b 31,2,1 : Data.b 26,0,1 : Data.b 0,1,0 : Data.b 23,1,1 : Data.b 0,0,0 : Data.b 28,1,1 : Data.b 30,0,1 : Data.b 0,0,0 : Data.b 29,0,1 : Data.b 28,1,1
  ; M19
  Data.b 26,0,1 : Data.b 34,1,1 : Data.b 37,0,1 : Data.b 38,1,1 : Data.b 35,0,1 : Data.b 37,0,1 : Data.b 0,0,0 : Data.b 34,1,1 : Data.b 31,0,1 : Data.b 32,0,1 : Data.b 30,2,1
  ; M20
  Data.b 31,2,1 : Data.b 26,0,1 : Data.b 0,1,0 : Data.b 23,1,1 : Data.b 0,0,0 : Data.b 28,1,1 : Data.b 30,0,1 : Data.b 0,0,0 : Data.b 29,0,1 : Data.b 28,1,1
  ; M21
  Data.b 26,0,1 : Data.b 34,1,1 : Data.b 37,0,1 : Data.b 38,1,1 : Data.b 35,0,1 : Data.b 37,0,1 : Data.b 0,0,0 : Data.b 34,1,1 : Data.b 31,0,1 : Data.b 32,0,1 : Data.b 30,2,1
  ; M22
  Data.b 34,0,1 : Data.b 31,1,1 : Data.b 26,0,1 : Data.b 0,1,0 : Data.b 25,1,1 : Data.b 28,0,1 : Data.b 35,1,1 : Data.b 35,0,1 : Data.b 28,3,1
  ; M23
  Data.b 30,0,1 : Data.b 38,1,1 : Data.b 38,0,1 : Data.b 38,0,1 : Data.b 37,1,1 : Data.b 35,0,1 : Data.b 34,0,1 : Data.b 31,1,1 : Data.b 28,0,1 : Data.b 26,3,1
  ; M24
  Data.b 34,0,1 : Data.b 31,1,1 : Data.b 26,0,1 : Data.b 0,1,0 : Data.b 25,1,1 : Data.b 28,0,1 : Data.b 35,1,1 : Data.b 35,0,1 : Data.b 28,3,1
  ; M25
  Data.b 30,0,1 : Data.b 35,1,1 : Data.b 35,0,1 : Data.b 35,0,1 : Data.b 34,1,1 : Data.b 32,0,1 : Data.b 31,3,1 : Data.b 0,3,0
  ; M26
  Data.b 31,0,1 : Data.b 31,1,1 : Data.b 31,0,1 : Data.b 0,0,0 : Data.b 31,0,1 : Data.b 32,1,1 : Data.b 34,0,1 : Data.b 31,1,1 : Data.b 28,0,1 : Data.b 26,3,1
  ; M27
  Data.b 31,0,1 : Data.b 31,1,1 : Data.b 31,0,1 : Data.b 0,0,0 : Data.b 31,0,1 : Data.b 32,1,1 : Data.b 34,0,1 : Data.b 0,4,0
  ; M28
  Data.b 31,0,1 : Data.b 31,1,1 : Data.b 31,0,1 : Data.b 0,0,0 : Data.b 31,0,1 : Data.b 32,1,1 : Data.b 34,0,1 : Data.b 31,1,1 : Data.b 28,0,1 : Data.b 26,3,1
  ; M29
  Data.b 34,0,1 : Data.b 34,1,1 : Data.b 34,0,1 : Data.b 0,0,0 : Data.b 31,0,1 : Data.b 34,1,1 : Data.b 37,3,1 : Data.b 0,3,0
  ; M30
  Data.b 31,2,1 : Data.b 26,0,1 : Data.b 0,1,0 : Data.b 23,1,1 : Data.b 0,0,0 : Data.b 28,1,1 : Data.b 30,0,1 : Data.b 0,0,0 : Data.b 29,0,1 : Data.b 28,1,1
  ; M31
  Data.b 26,0,1 : Data.b 34,1,1 : Data.b 37,0,1 : Data.b 38,1,1 : Data.b 35,0,1 : Data.b 37,0,1 : Data.b 0,0,0 : Data.b 34,1,1 : Data.b 31,0,1 : Data.b 32,0,1 : Data.b 30,2,1
  ; M32
  Data.b 31,2,1 : Data.b 26,0,1 : Data.b 0,1,0 : Data.b 23,1,1 : Data.b 0,0,0 : Data.b 28,1,1 : Data.b 30,0,1 : Data.b 0,0,0 : Data.b 29,0,1 : Data.b 28,1,1
  ; M33
  Data.b 26,0,1 : Data.b 34,1,1 : Data.b 37,0,1 : Data.b 38,1,1 : Data.b 35,0,1 : Data.b 37,0,1 : Data.b 0,0,0 : Data.b 34,1,1 : Data.b 31,0,1 : Data.b 32,0,1 : Data.b 30,2,1
  ; M34
  Data.b 34,0,1 : Data.b 31,1,1 : Data.b 26,0,1 : Data.b 0,1,0 : Data.b 25,1,1 : Data.b 28,0,1 : Data.b 35,1,1 : Data.b 35,0,1 : Data.b 28,3,1
  ; M35
  Data.b 30,0,1 : Data.b 38,1,1 : Data.b 38,0,1 : Data.b 38,0,1 : Data.b 37,1,1 : Data.b 35,0,1 : Data.b 34,0,1 : Data.b 31,1,1 : Data.b 28,0,1 : Data.b 26,3,1
  ; M36
  Data.b 34,0,1 : Data.b 31,1,1 : Data.b 26,0,1 : Data.b 0,1,0 : Data.b 25,1,1 : Data.b 28,0,1 : Data.b 35,1,1 : Data.b 35,0,1 : Data.b 28,3,1
  ; M37
  Data.b 30,0,1 : Data.b 35,1,1 : Data.b 35,0,1 : Data.b 35,0,1 : Data.b 34,1,1 : Data.b 32,0,1 : Data.b 31,3,1 : Data.b 0,3,0
  ; End
  Data.b -1, -1, -1
EndDataSection

; [COMPRESSED] Super Mario Bros. Theme (Measures 1-37) - Bass (Voice 1 - P3)
DataSection
  melody_bass_data:
  ; M1
  Data.b 5,0,2 : Data.b 5,1,2 : Data.b 5,0,2 : Data.b 0,0,0 : Data.b 5,0,2 : Data.b 5,1,2 : Data.b 17,3,2 : Data.b 9,3,2
  ; M2
  Data.b 9,2,2 : Data.b 6,0,2 : Data.b 0,1,0 : Data.b 4,1,2 : Data.b 0,0,0 : Data.b 7,1,2 : Data.b 9,0,2 : Data.b 0,0,0 : Data.b 8,0,2 : Data.b 7,1,2
  ; M3
  Data.b 6,0,2 : Data.b 12,1,2 : Data.b 14,0,2 : Data.b 15,1,2 : Data.b 13,0,2 : Data.b 14,0,2 : Data.b 0,0,0 : Data.b 12,1,2 : Data.b 10,0,2 : Data.b 11,0,2 : Data.b 9,2,2
  ; M4
  Data.b 9,2,2 : Data.b 6,0,2 : Data.b 0,1,0 : Data.b 4,1,2 : Data.b 0,0,0 : Data.b 7,1,2 : Data.b 9,0,2 : Data.b 0,0,0 : Data.b 8,0,2 : Data.b 7,1,2
  ; M5
  Data.b 6,0,2 : Data.b 12,1,2 : Data.b 14,0,2 : Data.b 15,1,2 : Data.b 13,0,2 : Data.b 14,0,2 : Data.b 0,0,0 : Data.b 12,1,2 : Data.b 10,0,2 : Data.b 11,0,2 : Data.b 9,2,2
  ; M6
  Data.b 0,1,0 : Data.b 17,0,2 : Data.b 16,0,2 : Data.b 15,0,2 : Data.b 22,1,2 : Data.b 14,0,2 : Data.b 0,0,0 : Data.b 9,0,2 : Data.b 10,0,2 : Data.b 12,0,2 : Data.b 0,0,0 : Data.b 10,0,2 : Data.b 12,0,2 : Data.b 13,0,2
  ; M7
  Data.b 0,1,0 : Data.b 17,0,2 : Data.b 16,0,2 : Data.b 15,0,2 : Data.b 22,1,2 : Data.b 14,0,2 : Data.b 0,0,0 : Data.b 20,1,2 : Data.b 20,0,2 : Data.b 20,3,2
  ; M8
  Data.b 0,1,0 : Data.b 17,0,2 : Data.b 16,0,2 : Data.b 15,0,2 : Data.b 22,1,2 : Data.b 14,0,2 : Data.b 0,0,0 : Data.b 9,0,2 : Data.b 10,0,2 : Data.b 12,0,2 : Data.b 0,0,0 : Data.b 10,0,2 : Data.b 12,0,2 : Data.b 13,0,2
  ; M9
  Data.b 0,1,0 : Data.b 9,1,2 : Data.b 0,0,0 : Data.b 7,2,2 : Data.b 6,3,2 : Data.b 0,3,0
  ; M10
  Data.b 0,1,0 : Data.b 17,0,2 : Data.b 16,0,2 : Data.b 15,0,2 : Data.b 22,1,2 : Data.b 14,0,2 : Data.b 0,0,0 : Data.b 9,0,2 : Data.b 10,0,2 : Data.b 12,0,2 : Data.b 0,0,0 : Data.b 10,0,2 : Data.b 12,0,2 : Data.b 13,0,2
  ; M11
  Data.b 0,1,0 : Data.b 17,0,2 : Data.b 16,0,2 : Data.b 15,0,2 : Data.b 22,1,2 : Data.b 14,0,2 : Data.b 0,0,0 : Data.b 20,1,2 : Data.b 20,0,2 : Data.b 20,3,2
  ; M12
  Data.b 0,1,0 : Data.b 17,0,2 : Data.b 16,0,2 : Data.b 15,0,2 : Data.b 22,1,2 : Data.b 14,0,2 : Data.b 0,0,0 : Data.b 9,0,2 : Data.b 10,0,2 : Data.b 12,0,2 : Data.b 0,0,0 : Data.b 10,0,2 : Data.b 12,0,2 : Data.b 13,0,2
  ; M13
  Data.b 0,1,0 : Data.b 9,1,2 : Data.b 0,0,0 : Data.b 7,2,2 : Data.b 6,3,2 : Data.b 0,3,0
  ; M14
  Data.b 4,0,2 : Data.b 4,1,2 : Data.b 4,0,2 : Data.b 0,0,0 : Data.b 4,0,2 : Data.b 5,1,2 : Data.b 6,0,2 : Data.b 4,1,2 : Data.b 3,0,2 : Data.b 2,3,2
  ; M15
  Data.b 4,0,2 : Data.b 4,1,2 : Data.b 4,0,2 : Data.b 0,0,0 : Data.b 4,0,2 : Data.b 5,1,2 : Data.b 6,0,2 : Data.b 0,4,0
  ; M16
  Data.b 4,0,2 : Data.b 4,1,2 : Data.b 4,0,2 : Data.b 0,0,0 : Data.b 4,0,2 : Data.b 5,1,2 : Data.b 6,0,2 : Data.b 4,1,2 : Data.b 3,0,2 : Data.b 2,3,2
  ; M17
  Data.b 5,0,2 : Data.b 5,1,2 : Data.b 5,0,2 : Data.b 0,0,0 : Data.b 5,0,2 : Data.b 5,1,2 : Data.b 17,3,2 : Data.b 9,3,2
  ; M18
  Data.b 9,2,2 : Data.b 6,0,2 : Data.b 0,1,0 : Data.b 4,1,2 : Data.b 0,0,0 : Data.b 7,1,2 : Data.b 9,0,2 : Data.b 0,0,0 : Data.b 8,0,2 : Data.b 7,1,2
  ; M19
  Data.b 6,0,2 : Data.b 12,1,2 : Data.b 14,0,2 : Data.b 15,1,2 : Data.b 13,0,2 : Data.b 14,0,2 : Data.b 0,0,0 : Data.b 12,1,2 : Data.b 10,0,2 : Data.b 11,0,2 : Data.b 9,2,2
  ; M20
  Data.b 9,2,2 : Data.b 6,0,2 : Data.b 0,1,0 : Data.b 4,1,2 : Data.b 0,0,0 : Data.b 7,1,2 : Data.b 9,0,2 : Data.b 0,0,0 : Data.b 8,0,2 : Data.b 7,1,2
  ; M21
  Data.b 6,0,2 : Data.b 12,1,2 : Data.b 14,0,2 : Data.b 15,1,2 : Data.b 13,0,2 : Data.b 14,0,2 : Data.b 0,0,0 : Data.b 12,1,2 : Data.b 10,0,2 : Data.b 11,0,2 : Data.b 9,2,2
  ; M22
  Data.b 4,0,2 : Data.b 3,1,2 : Data.b 1,0,2 : Data.b 0,1,0 : Data.b 1,1,2 : Data.b 7,0,2 : Data.b 12,1,2 : Data.b 12,0,2 : Data.b 7,3,2
  ; M23
  Data.b 9,0,2 : Data.b 15,1,2 : Data.b 15,0,2 : Data.b 15,0,2 : Data.b 14,1,2 : Data.b 13,0,2 : Data.b 12,1,2 : Data.b 10,0,2 : Data.b 7,0,2 : Data.b 6,3,2
  ; M24
  Data.b 4,0,2 : Data.b 3,1,2 : Data.b 1,0,2 : Data.b 0,1,0 : Data.b 1,1,2 : Data.b 7,0,2 : Data.b 12,1,2 : Data.b 12,0,2 : Data.b 7,3,2
  ; M25
  Data.b 9,0,2 : Data.b 13,1,2 : Data.b 13,0,2 : Data.b 13,0,2 : Data.b 12,1,2 : Data.b 11,0,2 : Data.b 9,1,2 : Data.b 6,0,2 : Data.b 6,0,2 : Data.b 4,3,2
  ; M26
  Data.b 4,0,2 : Data.b 4,1,2 : Data.b 4,0,2 : Data.b 0,0,0 : Data.b 4,0,2 : Data.b 5,1,2 : Data.b 6,0,2 : Data.b 4,1,2 : Data.b 3,0,2 : Data.b 2,3,2
  ; M27
  Data.b 4,0,2 : Data.b 4,1,2 : Data.b 4,0,2 : Data.b 0,0,0 : Data.b 4,0,2 : Data.b 5,1,2 : Data.b 6,0,2 : Data.b 0,4,0
  ; M28
  Data.b 4,0,2 : Data.b 4,1,2 : Data.b 4,0,2 : Data.b 0,0,0 : Data.b 4,0,2 : Data.b 5,1,2 : Data.b 6,0,2 : Data.b 4,1,2 : Data.b 3,0,2 : Data.b 2,3,2
  ; M29
  Data.b 5,0,2 : Data.b 5,1,2 : Data.b 5,0,2 : Data.b 0,0,0 : Data.b 5,0,2 : Data.b 5,1,2 : Data.b 17,3,2 : Data.b 9,3,2
  ; M30
  Data.b 9,2,2 : Data.b 6,0,2 : Data.b 0,1,0 : Data.b 4,1,2 : Data.b 0,0,0 : Data.b 7,1,2 : Data.b 9,0,2 : Data.b 0,0,0 : Data.b 8,0,2 : Data.b 7,1,2
  ; M31
  Data.b 6,0,2 : Data.b 12,1,2 : Data.b 14,0,2 : Data.b 15,1,2 : Data.b 13,0,2 : Data.b 14,0,2 : Data.b 0,0,0 : Data.b 12,1,2 : Data.b 10,0,2 : Data.b 11,0,2 : Data.b 9,2,2
  ; M32
  Data.b 9,2,2 : Data.b 6,0,2 : Data.b 0,1,0 : Data.b 4,1,2 : Data.b 0,0,0 : Data.b 7,1,2 : Data.b 9,0,2 : Data.b 0,0,0 : Data.b 8,0,2 : Data.b 7,1,2
  ; M33
  Data.b 6,0,2 : Data.b 12,1,2 : Data.b 14,0,2 : Data.b 15,1,2 : Data.b 13,0,2 : Data.b 14,0,2 : Data.b 0,0,0 : Data.b 12,1,2 : Data.b 10,0,2 : Data.b 11,0,2 : Data.b 9,2,2
  ; M34
  Data.b 4,0,2 : Data.b 3,1,2 : Data.b 1,0,2 : Data.b 0,1,0 : Data.b 1,1,2 : Data.b 7,0,2 : Data.b 12,1,2 : Data.b 12,0,2 : Data.b 7,3,2
  ; M35
  Data.b 9,0,2 : Data.b 15,1,2 : Data.b 15,0,2 : Data.b 15,0,2 : Data.b 14,1,2 : Data.b 13,0,2 : Data.b 12,1,2 : Data.b 10,0,2 : Data.b 7,0,2 : Data.b 6,3,2
  ; M36
  Data.b 4,0,2 : Data.b 3,1,2 : Data.b 1,0,2 : Data.b 0,1,0 : Data.b 1,1,2 : Data.b 7,0,2 : Data.b 12,1,2 : Data.b 12,0,2 : Data.b 7,3,2
  ; M37
  Data.b 9,0,2 : Data.b 13,1,2 : Data.b 13,0,2 : Data.b 13,0,2 : Data.b 12,1,2 : Data.b 11,0,2 : Data.b 9,1,2 : Data.b 6,0,2 : Data.b 6,0,2 : Data.b 4,3,2
  ; End
  Data.b -1, -1, -1
EndDataSection

; [COMPRESSED] Super Mario Bros. Theme (Measures 1-37) - Percussion (Voice 2 - P4)
DataSection
  melody_percussion_data:
  ; M1
  Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 0,1,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0
  ; M2
  Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0
  ; M3
  Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0
  ; M4
  Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0
  ; M5
  Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0
  ; M6
  Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0
  ; M7
  Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0
  ; M8
  Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0
  ; M9
  Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0
  ; M10
  Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0
  ; M11
  Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0
  ; M12
  Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0
  ; M13
  Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0
  ; M14
  Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 0,1,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0
  ; M15
  Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 0,1,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0
  ; M16
  Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 0,1,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0
  ; M17
  Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 0,1,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0
  ; M18
  Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0
  ; M19
  Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0
  ; M20
  Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0
  ; M21
  Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0
  ; M22
  Data.b 40,0,0 : Data.b 0,1,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 0,1,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 0,0,0
  ; M23
  Data.b 40,0,0 : Data.b 0,1,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 0,1,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 0,0,0
  ; M24
  Data.b 40,0,0 : Data.b 0,1,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 0,1,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 0,0,0
  ; M25
  Data.b 40,0,0 : Data.b 0,1,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 0,1,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 0,0,0
  ; M26
  Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 0,1,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0
  ; M27
  Data.b 40,0,0 : Data.b 0,1,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 0,1,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 0,0,0
  ; M28
  Data.b 40,0,0 : Data.b 0,1,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 0,1,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 0,0,0
  ; M29
  Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0
  ; M30
  Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 0,1,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0
  ; M31
  Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 0,1,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0
  ; M32
  Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 0,1,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0
  ; M33
  Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 0,1,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 40,0,0
  ; M34
  Data.b 40,0,0 : Data.b 0,1,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 0,1,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 0,0,0
  ; M35
  Data.b 40,0,0 : Data.b 0,1,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 0,1,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 0,0,0
  ; M36
  Data.b 40,0,0 : Data.b 0,1,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 0,1,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 0,0,0
  ; M37
  Data.b 40,0,0 : Data.b 0,1,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 0,1,0 : Data.b 40,0,0 : Data.b 40,0,0 : Data.b 0,0,0 : Data.b 40,0,0 : Data.b 0,0,0
  ; End
  Data.b -1, -1, -1
EndDataSection

Define tempFreqIndex.b, tempDurIndex.b, tempPanIndex.b
; Load Treble Melody
Restore melody_treble_data
Repeat
  Read.b tempFreqIndex
  Read.b tempDurIndex
  Read.b tempPanIndex
  If tempFreqIndex = -1 : Break : EndIf
  If tempFreqIndex < 0 Or tempFreqIndex > 40 : Debug "Treble Freq Index Out of Range: " + Str(tempFreqIndex) : Break : EndIf
  If tempDurIndex < 0 Or tempDurIndex > 6 : Debug "Treble Dur Index Out of Range: " + Str(tempDurIndex) : Break : EndIf
  If tempPanIndex < 0 Or tempPanIndex > 2 : Debug "Treble Pan Index Out of Range: " + Str(tempPanIndex) : Break : EndIf
  AddElement(melodyTreble())
  melodyTreble()\frequency = g_Frequencies(tempFreqIndex)
  melodyTreble()\durationMs = g_Durations(tempDurIndex)
  melodyTreble()\pan = g_Pans(tempPanIndex)
ForEver

; Load Bass Melody
Restore melody_bass_data
Repeat
  Read.b tempFreqIndex
  Read.b tempDurIndex
  Read.b tempPanIndex
  If tempFreqIndex = -1 : Break : EndIf
  If tempFreqIndex < 0 Or tempFreqIndex > 40 : Debug "Bass Freq Index Out of Range: " + Str(tempFreqIndex) : Break : EndIf
  If tempDurIndex < 0 Or tempDurIndex > 6 : Debug "Bass Dur Index Out of Range: " + Str(tempDurIndex) : Break : EndIf
  If tempPanIndex < 0 Or tempPanIndex > 2 : Debug "Bass Pan Index Out of Range: " + Str(tempPanIndex) : Break : EndIf
  AddElement(melodyBass())
  melodyBass()\frequency = g_Frequencies(tempFreqIndex)
  melodyBass()\durationMs = g_Durations(tempDurIndex)
  melodyBass()\pan = g_Pans(tempPanIndex)
ForEver

; Load Percussion Melody
Restore melody_percussion_data
Repeat
  Read.b tempFreqIndex
  Read.b tempDurIndex
  Read.b tempPanIndex
  If tempFreqIndex = -1 : Break : EndIf
  If tempFreqIndex < 0 Or tempFreqIndex > 40 : Debug "Perc Freq Index Out of Range: " + Str(tempFreqIndex) : Break : EndIf
  If tempDurIndex < 0 Or tempDurIndex > 6 : Debug "Perc Dur Index Out of Range: " + Str(tempDurIndex) : Break : EndIf
  If tempPanIndex < 0 Or tempPanIndex > 2 : Debug "Perc Pan Index Out of Range: " + Str(tempPanIndex) : Break : EndIf
  AddElement(melodyPercussion())
  melodyPercussion()\frequency = g_Frequencies(tempFreqIndex)
  melodyPercussion()\durationMs = g_Durations(tempDurIndex)
  melodyPercussion()\pan = g_Pans(tempPanIndex)
ForEver

; Set up audio format structure
wfx\wFormatTag = #WAVE_FORMAT_PCM : wfx\nChannels = 2 : wfx\nSamplesPerSec = 44100
wfx\wBitsPerSample = 16 : wfx\nBlockAlign = (wfx\nChannels * wfx\wBitsPerSample) / 8
wfx\nAvgBytesPerSec = wfx\nSamplesPerSec * wfx\nBlockAlign : wfx\cbSize = 0

; Calculate buffer size for ~250ms of audio
bufferSize = wfx\nAvgBytesPerSec / 4 ; (Bytes per second) / 4 = Bytes per 250ms

Debug "Audio Format: 44.1kHz, 16-bit, Stereo"
Debug "Buffer Size (250ms): " + Str(bufferSize) + " bytes"
Define melodyWaveName.s = ""
Select g_MelodyWaveformType
    Case #Wave_Square: melodyWaveName = "Square" : Case #Wave_Sine: melodyWaveName = "Sine"
    Case #Wave_Triangle: melodyWaveName = "Triangle" : Case #Wave_WhiteNoise: melodyWaveName = "White Noise"
    Case #Wave_PinkNoise: melodyWaveName = "Pink Noise"
EndSelect
Debug "Melody Waveform: " + melodyWaveName

Define bassWaveName.s = ""
Select g_BassWaveformType
    Case #Wave_Square: bassWaveName = "Square" : Case #Wave_Sine: bassWaveName = "Sine"
    Case #Wave_Triangle: bassWaveName = "Triangle" : Case #Wave_WhiteNoise: bassWaveName = "White Noise"
    Case #Wave_PinkNoise: bassWaveName = "Pink Noise"
EndSelect
Debug "Bass Waveform: " + bassWaveName

Define percWaveName.s = ""
Select g_PercussionWaveformType
  Case #Wave_WhiteNoise: percWaveName = "White Noise"
  Case #Wave_PinkNoise: percWaveName = "Pink Noise"
  Default: percWaveName = "Other"
EndSelect
Debug "Percussion Waveform: " + percWaveName

; Open the default audio output device
Define result.i
result = waveOutOpen_(@hWaveOut, #WAVE_MAPPER, @wfx, @WaveCallback(), 0, #CALLBACK_FUNCTION)
If result <> 0 : MessageRequester("Error", "Could not open audio device. Error: " + Str(result)) : End : EndIf
Debug "Audio device opened successfully (Handle: " + Str(hWaveOut) + ")"

; Allocate, prepare, and fill initial buffers
Define i
For i = 0 To #NUM_BUFFERS - 1
  *waveBuffers(i) = AllocateMemory(bufferSize) ; Allocate memory for audio samples
  If *waveBuffers(i) = 0 : MessageRequester("Error", "Failed To allocate buffer " + Str(i)) : waveOutClose_(hWaveOut): End : EndIf ; Check allocation success
  FillBuffer(*waveBuffers(i), bufferSize) ; Fill with initial audio data
  waveHeaders(i)\lpData = *waveBuffers(i) ; Point header to the buffer
  waveHeaders(i)\dwBufferLength = bufferSize ; Set buffer size in header
  waveHeaders(i)\dwFlags = 0 ; Ensure flags are reset before preparing
  waveHeaders(i)\dwUser = i ; Optional: Store buffer index For debugging in callback
  ; Prepare the header (locks buffer memory, makes it ready for driver)
  result = waveOutPrepareHeader_(hWaveOut, @waveHeaders(i), SizeOf(WAVEHDR))
  If result <> 0 : MessageRequester("Error", "Could not prepare buffer " + Str(i) + ". Error: " + Str(result)) : waveOutClose_(hWaveOut) : End : EndIf
Next
Debug "Prepared " + Str(#NUM_BUFFERS) + " audio buffers."

; Write the initial buffers to the output device to start playback
For i = 0 To #NUM_BUFFERS - 1 : waveOutWrite_(hWaveOut, @waveHeaders(i), SizeOf(WAVEHDR)) : Next
Debug "Playback started. Queued initial buffers."

; Simple window to keep program running and allow user to stop
OpenWindow(0, 0, 0, 300, 150, "Mario Test (3 Voice)", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
TextGadget(0, 10, 10, 280, 130, "Playing 'Super Mario Bros. Theme' (3 Voice)..." + #CRLF$ + "Melody: " + melodyWaveName + #CRLF$ + "Bass: " + bassWaveName + #CRLF$ + "Perc: " + percWaveName + #CRLF$ + #CRLF$ + "Close this window To stop.")
; Main event loop - waits until the window is closed
Repeat
  Define event.i = WaitWindowEvent()
Until event = #PB_Event_CloseWindow

; --- [REVISED v3 - No waveOutReset_] Shutdown Sequence ---
Debug "Window closed. Shutting down audio..."

; 1. Signal the callback thread to stop processing WOM_DONE messages by queueing new buffers.
waveRunning = #False
Debug "Callback flag 'waveRunning' set to False."

; 2. Wait for all buffers to be returned by the callback.
;    Check the WHDR_DONE flag. Buffers are marked DONE when they finish playing
;    or when waveOutReset is called (which we are now avoiding).
;    We need both buffers to be done before proceeding.
Define allBuffersDone.i = #False
Define waitTimeout.i = 2000 ; Max wait time in ms (e.g., 2 seconds)
Define startTime.i = ElapsedMilliseconds()
Debug "Waiting for buffers to complete (Max " + Str(waitTimeout) + "ms)..."
Repeat
  allBuffersDone = #True ; Assume done until proven otherwise
  For i = 0 To #NUM_BUFFERS - 1
    ; Check the DONE flag is set AND the PREPARED flag is still set (it shouldn't be cleared yet)
    If Not (waveHeaders(i)\dwFlags & #WHDR_DONE) And (waveHeaders(i)\dwFlags & #WHDR_PREPARED)
      allBuffersDone = #False
      Break ; No need to check others if one isn't done
    EndIf
  Next
  If allBuffersDone
    Debug "All buffers marked as DONE."
    Break
  EndIf
  If ElapsedMilliseconds() - startTime > waitTimeout
    Debug "Timeout waiting for buffers to complete. Proceeding cautiously..."
    Break ; Exit loop after timeout
  EndIf
  Delay(20) ; Wait a bit before checking again
Until allBuffersDone

; 3. Now that playback should be stopped and callbacks quiescent, unprepare headers.
Debug "Starting unprepare loop..."
For i = 0 To #NUM_BUFFERS - 1
  ; Safety check: Make sure the buffer pointer is actually valid (was allocated successfully)
  If *waveBuffers(i)
    Debug "Attempting to unprepare buffer " + Str(i) + "..."
    ; Check if the buffer is still marked as prepared before trying to unprepare it
    ; It *should* be prepared at this point if it was ever used.
    If waveHeaders(i)\dwFlags & #WHDR_PREPARED
      ; 5. Retry loop for waveOutUnprepareHeader_. This function unlocks the buffer memory.
      ;    Even without reset, retries might be needed if timing is tight.
      Define retries.i = 0
      Define maxRetries.i = 50 ; Wait up to 50 * 10ms = 500ms
      Repeat
        result = waveOutUnprepareHeader_(hWaveOut, @waveHeaders(i), SizeOf(WAVEHDR))

        If result = 0 ; MMSYSERR_NOERROR = Success
          ; Successfully unprepared
          Debug "Unprepared buffer " + Str(i) + " successfully."
          Break ; Exit repeat loop

        ; 6. [Hang Prevention] If unprepare failed, check if the prepared flag got cleared anyway
        ElseIf Not (waveHeaders(i)\dwFlags & #WHDR_PREPARED)
          Debug "Buffer " + Str(i) + " flag cleared during unprepare attempt. Assuming success."
          result = 0 ; Treat as success for the purpose of breaking the loop
          Break ; Exit repeat loop
        EndIf

        ; Wait briefly before retrying
        Delay(10)
        retries + 1
      Until retries >= maxRetries ; Stop trying after a reasonable timeout

      ; 7. [Hang Prevention] If still unprepared after retries, log a warning but DO NOT HANG.
      If result <> 0 ; Check if the loop exited due to failure after retries
         Debug "Warning: Failed to unprepare header " + Str(i) + " after retries. Error: " + Str(result) + ". Flag: " + Str(waveHeaders(i)\dwFlags)
      EndIf
    Else
       ; Buffer wasn't marked as prepared initially (or flag cleared before loop), no need to unprepare
       Debug "Buffer " + Str(i) + " was not marked as prepared before loop (dwFlags=" + Str(waveHeaders(i)\dwFlags) + ")."
    EndIf

    ; 8. Free the memory allocated for the audio samples.
    FreeMemory(*waveBuffers(i))
    *waveBuffers(i) = 0 ; Nullify the pointer after freeing to prevent dangling pointers.
    Debug "Freed buffer memory " + Str(i)
  Else
    Debug "Pointer for buffer " + Str(i) + " was null, skipping free."
  EndIf
Next ; End buffer loop

Debug "Attempting to close audio device..."
; 9. Close the waveOut device handle, releasing associated system resources.
result = waveOutClose_(hWaveOut)
If result <> 0
  Debug "Warning: waveOutClose_ failed. Error: " + Str(result)
Else
  Debug "Audio device closed successfully." ; Callback should receive WOM_CLOSE now
EndIf

Debug "Exiting."

End

dige
Addict
Addict
Posts: 1417
Joined: Wed Apr 30, 2003 8:15 am
Location: Germany
Contact:

Re: Simple on the fly audio example

Post by dige »

Nice retro feeling! :D
"Daddy, I'll run faster, then it is not so far..."
Post Reply