'OHRRPGCE - music_blackbox audio backend
'(C) Copyright 1997-2025 James Paige, Ralph Versteegen, and the OHRRPGCE Developers
'Dual licensed under the GNU GPL v2+ and MIT Licenses. Read LICENSE.txt for terms and disclaimer of liability.

'This is based on music_sdl. The required API set has been refactored and some "common" code pasted in here.
'The actual blackbox backend should provide the needed apis

#include "music.bi"
#include "common_base.bi"

' blackbox APIs
''''''''''''''''''''''''''''''''''''''''''''''''
extern "C"

' specs
Declare Function blackbox_music_supported_formats() As Integer
Declare Function blackbox_sound_supported_formats() As Integer

' commands on music
Declare Function blackbox_music_play(songname As const zstring ptr, fmt As Integer) As Any ptr
Declare Sub blackbox_music_stop()
Declare Sub blackbox_music_setvolume(vol As Single)
'Declare Function blackbox_music_seekable(handle As any ptr) As Boolean
Declare Function blackbox_music_get_time(handle As any ptr) As Double
Declare Function blackbox_music_set_time(handle As any ptr, pos_s As Double) As Boolean
Declare Function blackbox_music_get_length(handle As any ptr) As Double

' commands to manage sfx life cycles
Declare Function blackbox_sound_load(filename As const zstring ptr) As any ptr
Declare Sub blackbox_sound_unload(handle As any Ptr)

' commands on existing sfx
Declare Sub blackbox_sound_play(handle As any ptr, loopcount As Integer, volume As Single)
Declare Sub blackbox_sound_pause(handle As any Ptr)
Declare Sub blackbox_sound_stop(handle As any ptr)
Declare Sub blackbox_sound_set_volume(handle As any ptr, volume As Single)
Declare Function blackbox_sound_playing(handle As any ptr) As Boolean

End extern
''''''''''''''''''''''''''''''''''''''''''''''''

' Types

TYPE SoundEffectSlot EXTENDS SFXCommonData
	used as bool        'whether this slot is free
	playing as bool     'Set to false by a callback when the channel finishes
	buf as any ptr
	vol as single
End TYPE

'variables
dim shared music_vol As Double       '0 to 1 nominally; values above 1 useful when other multipliers exist
dim shared music_running As Boolean = False
dim shared music_song As any ptr = NULL
dim shared sound_inited As Boolean = False

'music_blackbox has an arbitrary limit of 16 sound effects playing at once (copied from music_sdl):
dim shared sfx_slots(15) As SoundEffectSlot

Sub music_init()
	'do nothing if already running
	If music_running Then Exit Sub

	' defaults copied from music_sdl
	music_vol = 0.5
	music_running = True
End Sub

Sub music_close()
	'We don't expect this to get used...
	'Even though it makes no sense to turn off audio on blackbox, we need to clean the state properly (volumes and channels ETC)

	'do nothing if not running
	If Not music_running Then Exit Sub

	music_stop()
End Sub

Function music_get_info() As String
	' it's judged unlikely that the game will need any of this information (hz, etc)
	Return "music_blackbox"
End Function

Function music_supported_formats() as Integer
	Return blackbox_music_supported_formats() And VALID_MUSIC_FORMAT
End function

function sound_supported_formats() as Integer
	Return blackbox_sound_supported_formats() And VALID_SFX_FORMAT
End Function

Function music_settings_menu() As bool
	'There is no settings menu
	Return NO
End Function

Sub music_play(ByVal lump As Lump ptr, ByVal fmt As MusicFormatEnum)
	' not supported?
End Sub

Sub music_play(songname As String, ByVal fmt As MusicFormatEnum)
	If Not music_running Then Exit Sub

	' this is not really so complex
	music_stop()
	music_song = blackbox_music_play(songname, fmt)

End Sub

Sub music_pause()
	'Not implemented
End Sub

Sub music_resume()
	'Not implemented
End Sub

Sub music_stop()
	blackbox_music_stop()
End Sub

Sub music_setvolume(ByVal vol As Single)
	music_vol = large(vol, 0.)
	blackbox_music_setvolume(vol)
End Sub

Function music_getvolume() as Single
	Return music_vol
End Function

'TODO: the following are stubbed out until music seek/tell/length functions are added

Function music_seekable() As bool
	'return YES   'I presume so
	return NO
End Function

Function music_gettime() As Double
	'return blackbox_music_get_time(music_song)
	return -1.0
End Function

Function music_settime(byval pos_s As Double) As bool
	'return blackbox_music_set_time(music_song, pos_s)
	return NO
End Function

Function music_getlength() As Double
	'Return blackbox_music_get_length(music_song)
	return -1.0
End Function

Sub sound_init()
	'if this were called twice, the world would end.
	If sound_inited Then Exit Sub

	'anything that might be initialized here is done in music_init
	'but, I must do it here too
	music_init()
	sound_inited = YES

End Sub

Sub sound_reset()
	'trying to free something that's already freed... bad!
	If sound_inited = NO Then Exit Sub
	For slot As Integer = 0 To ubound(sfx_slots)
		sound_unload(slot)
	Next
End Sub

Sub sound_close()
	sound_reset()
	sound_inited = NO
End Sub

' Returns -1 if too many sounds already playing/loaded
Function next_free_slot() As Integer
	Static retake_slot As Integer = 0
	Dim i As Integer

	'Look for empty slots
	For i = 0 To ubound(sfx_slots)
		If sfx_slots(i).used = NO Then
			Return i
		End If
	Next

	'Look for silent slots
	For i = 0 To ubound(sfx_slots)
		retake_slot = (retake_slot + 1) Mod (ubound(sfx_slots) + 1)
		With sfx_slots(retake_slot)
			If blackbox_sound_playing(.buf) = NO Then
				blackbox_sound_unload(.buf)
				.used = NO
				.buf = 0
				Return retake_slot
			End If
		End With
	Next

	Return -1 ' no slot found
End Function

'Resumes a sfx if it's paused
Sub sound_play(slot As Integer, loopcount As Integer, volume As Single)
	If slot = -1 Then Exit Sub

	' sfx_slots acts like a cache in this backend, since .buf
	' remains loaded after the sound effect has stopped.
	With sfx_slots(slot)
		If .buf = 0 Then
			showbug "sound_play: not loaded"
			Exit Sub
		End If

		If blackbox_sound_playing(.buf) = NO Then
			' Note that the i-th sfx slot is played on the i-th SDL_mixer channel,
			' which is just a simplification.
			.vol = volume
			blackbox_sound_play(.buf, loopcount, volume)
		End If

		' in case it was already playing... ??
		blackbox_sound_set_volume(.buf, volume)

	End With
End Sub

Sub sound_pause(slot As Integer)
	If slot = -1 Then Exit Sub
	With sfx_slots(slot)
		blackbox_sound_pause(.buf)
	End With
End Sub

Sub sound_stop(slot As Integer)
	If slot = -1 Then Exit Sub
	With sfx_slots(slot)
		blackbox_sound_stop(.buf)
	End With
End Sub

Sub sound_free(num As Integer)
	For slot As Integer = 0 To ubound(sfx_slots)
		With sfx_slots(slot)
			If .effectID = num Then sound_unload slot
		End With
	Next
End Sub

Sub sound_setvolume(slot As Integer, volume As Single)
	If slot = -1 Then Exit Sub
	sfx_slots(slot).vol = volume
	blackbox_sound_set_volume(sfx_slots(slot).buf, volume)
End Sub

Function sound_getvolume(slot As Integer) As Single
	If slot = -1 Then Return 0.
	return sfx_slots(slot).vol
End Function

Function sound_slot_with_id(num as integer) as Integer
	For slot As Integer = 0 To ubound(sfx_slots)
		With sfx_slots(slot)
			If .used AndAlso .effectID = num Then Return slot
		End With
	Next

	Return -1
End Function

Function sound_playing(slot As Integer) As bool
	If slot = -1 Then Return NO
	If sfx_slots(slot).used = NO Then Return NO
	Return blackbox_sound_playing(sfx_slots(slot).buf)
End Function

Function sound_slotdata(slot As Integer) As SFXCommonData ptr
	If slot < 0 Or slot > ubound(sfx_slots) Then Return NULL
	If sfx_slots(slot).used = NO Then Return NULL
	Return @sfx_slots(slot)
End Function

Function sound_lastslot() as Integer
	Return ubound(sfx_slots)
End Function

Function sound_load overload(lump As Lump ptr, num As Integer = -1) As Integer
	' not supported?
	Return -1
End Function

Function sound_load overload(filename As String, num As Integer = -1) As Integer
	Dim slot As Integer
	Dim sfx As any ptr

	If filename = "" Then Return -1
	If Not isfile(filename) Then Return -1

	log_openfile filename
	sfx = blackbox_sound_load(filename)
	If sfx = NULL Then
		debug "Couldn't blackbox_sound_load " & filename
		Return -1
	End If

	slot = next_free_slot()
	'debuginfo "sound_load(" & filename & "," & num & ") in slot " & slot

	If slot = -1 Then
		debuginfo "sound_load(""" & filename & """, " & num & ") no more sound slots available"
	Else
		With sfx_slots(slot)
			.used = YES
			.effectID = num
			.buf = sfx
		End With
	End If

	Return slot
End Function

'Unloads a sound loaded in a slot. TAKES A CACHE SLOT, NOT AN SFX ID NUMBER!
Sub sound_unload(slot As Integer)
	With sfx_slots(slot)
		If .used = NO Then Exit Sub
		blackbox_sound_unload(.buf)
		.used = NO
		.effectID = 0
		.buf = 0
	End With
End Sub
