note description: "chocolate doom i_sdlsound.c" license: "[ Copyright (C) 1993-1996 by id Software, Inc. Copyright (C) 2005-2014 Simon Howard Copyright (C) 2008 David Flater Copyright (C) 2021 Ilgiz Mustafin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ]" class SOUND_SDL_MODULE inherit SOUND_MODULE_T create make feature {NONE} i_main: I_MAIN make (a_i_main: like i_main) do i_main := a_i_main create channels_playing.make_filled (Void, 0, Num_channels - 1) end feature sound_initialized: BOOLEAN Use_libsamplerate: BOOLEAN = False use_sfx_prefix: BOOLEAN mixer_freq: INTEGER_32 mixer_format: INTEGER_16 mixer_channels: INTEGER_32 Num_channels: INTEGER_32 = 16 channels_playing: ARRAY [detachable ALLOCATED_SOUND_T] feature allocated_sounds_head: detachable ALLOCATED_SOUND_T allocated_sounds_tail: detachable ALLOCATED_SOUND_T allocated_sounds_size: INTEGER_32 feature release_sound_on_channel (channel: INTEGER_32) local snd: ALLOCATED_SOUND_T do snd := channels_playing [channel] {SDL_MIXER_FUNCTIONS_API}.mix_halt_channel (channel).do_nothing if attached snd then channels_playing [channel] := Void unlock_allocated_sound (snd) if snd.pitch /= {I_SOUND}.norm_pitch and snd.use_count <= 0 then free_allocated_sound (snd) end end end free_allocated_sound (snd: ALLOCATED_SOUND_T) do allocated_sound_unlink (snd) allocated_sounds_size := allocated_sounds_size - snd.chunk.alen.to_integer_32 end lock_sound (sfxinfo: SFXINFO_T): BOOLEAN do Result := True if get_allocated_sound_by_sfx_info_and_pitch (sfxinfo, {I_SOUND}.norm_pitch) = Void then if not cache_sfx (sfxinfo) then Result := False end end if Result then check attached get_allocated_sound_by_sfx_info_and_pitch (sfxinfo, {I_SOUND}.norm_pitch) as snd then lock_allocated_sound (snd) end end end get_allocated_sound_by_sfx_info_and_pitch (sfxinfo: SFXINFO_T; pitch: INTEGER_32): detachable ALLOCATED_SOUND_T do from Result := allocated_sounds_head until Result = Void or else (Result.sfxinfo = sfxinfo and Result.pitch = pitch) loop Result := Result.next end end pitch_shift (insnd: ALLOCATED_SOUND_T; pitch: INTEGER_32): ALLOCATED_SOUND_T local inp: INTEGER_32 outp: INTEGER_32 srcbuf: MANAGED_POINTER dstbuf: MANAGED_POINTER srclen, dstlen: NATURAL_32 do srclen := insnd.chunk.alen srcbuf := insnd.chunk.abuf_managed dstlen := ((1.to_double + (1.to_double - pitch / {I_SOUND}.norm_pitch)) * srclen.to_real_64).floor.to_natural_32 if dstlen \\ 2 = 0 then dstlen := dstlen + 1 end check attached insnd.sfxinfo as insnd_sfxinfo then Result := allocate_sound (insnd_sfxinfo, dstlen) end if attached Result then Result.pitch := pitch dstbuf := Result.chunk.abuf_managed check attached dstbuf and then attached srcbuf then from outp := 0 until outp >= dstlen.to_integer_32 // 2 loop inp := (outp.to_real / dstlen.to_real_32 * srclen.to_real_32).floor.to_integer_32 dstbuf.put_integer_16 (srcbuf.read_integer_16 (inp * 2), outp * 2) outp := outp + 1 end end end end lock_allocated_sound (snd: ALLOCATED_SOUND_T) -- Lock a sound, to indicate that it may not be freed. do snd.use_count := snd.use_count + 1 allocated_sound_unlink (snd) allocated_sound_link (snd) end allocated_sound_unlink (snd: ALLOCATED_SOUND_T) -- Unlink a sound from the linked list do if snd.prev = Void then allocated_sounds_head := snd.next else check attached snd.prev as prev then prev.next := snd.next end end if snd.next = Void then allocated_sounds_tail := snd.prev else check attached snd.next as next then next.prev := snd.prev end end end unlock_allocated_sound (snd: ALLOCATED_SOUND_T) -- Unlock a sound to indicate that it may now be freed. do if snd.use_count <= 0 then {I_MAIN}.i_error ("Sound effect released more times than it was locked...") end snd.use_count := snd.use_count - 1 end feature init (a_use_sfx_prefix: BOOLEAN): BOOLEAN -- Initialise sound module -- Returns True if successfully initialised local i: INTEGER_32 do use_sfx_prefix := a_use_sfx_prefix from i := 0 until i >= Num_channels loop channels_playing [i] := Void i := i + 1 end if {SDL_FUNCTIONS_API}.sdl_init ({SDL_CONSTANT_API}.sdl_init_audio.to_natural_32) < 0 then print ("Unable to set up sound" + {SDL_ERROR}.sdl_get_error) Result := False else if {SDL_MIXER_FUNCTIONS_API}.mix_open_audio_device ({I_SOUND}.snd_samplerate, {SDL_AUDIO}.audio_s16sys.to_natural_32, 2, get_slice_size, default_pointer, {SDL_CONSTANT_API}.sdl_audio_allow_frequency_change) < 0 then print ("Error initialising SDL_mixer: " + {MIX_ERROR}.get_error) Result := False else expand_sound_data_sdl_mode := True if {SDL_MIXER_FUNCTIONS_API}.mix_query_spec ($mixer_freq, $mixer_format.to_pointer, $mixer_channels) = 0 then {I_MAIN}.i_error ("Error Mix_QuerySpec" + {MIX_ERROR}.get_error) end if Use_libsamplerate then print ("use_libsamplerate=" + Use_libsamplerate.out + " but libsamplerate not supported") end if {SDL_MIXER_FUNCTIONS_API}.mix_allocate_channels (Num_channels) /= Num_channels then {I_MAIN}.i_error ("Should not happen! Mix_AllocateChannels(" + Num_channels.out + ") returned other value") end {SDL_AUDIO_FUNCTIONS_API}.sdl_pause_audio (0) sound_initialized := True Result := True end end end expand_sound_data_sdl_mode: BOOLEAN -- Use ExpandSoudData_SDL? If not, then ExpandSoundData_SRC expand_sound_data (sfxinfo: SFXINFO_T; data: POINTER; samplerate, length: INTEGER_32): BOOLEAN do check expand_sound_data_sdl_mode then Result := expand_sound_data_sdl (sfxinfo, data, samplerate, length) end end expand_sound_data_sdl (sfxinfo: SFXINFO_T; data: POINTER; samplerate: INTEGER_32; length: INTEGER_32): BOOLEAN -- Generic sound expansion function for any sample rate. -- Returns number of clipped samples (always 0). local convertor: SDL_AUDIO_CVT snd_detachable: ALLOCATED_SOUND_T chunk: MIX_CHUNK expanded_length: NATURAL_32 do create convertor.make expanded_length := ((length.to_natural_64) * mixer_freq.to_natural_64 // samplerate.to_natural_64).to_natural_32 expanded_length := expanded_length * 4 snd_detachable := allocate_sound (sfxinfo, expanded_length) if attached snd_detachable as snd then chunk := snd.chunk if samplerate <= mixer_freq and then convertible_ratio (samplerate, mixer_freq) and then {SDL_AUDIO_FUNCTIONS_API}.sdl_build_audio_cvt (convertor, {SDL_AUDIO}.audio_u8.to_natural_32, (1).to_character_8, samplerate, mixer_format.to_natural_32, mixer_channels.to_character_8, mixer_freq) /= 0 then convertor.set_len (length) convertor.allocate_buf (convertor.len * convertor.len_mult) check attached convertor.buf_managed as buf then buf.item.memory_copy (data.item, length) if {SDL_AUDIO_FUNCTIONS_API}.sdl_convert_audio (convertor) < 0 then {I_MAIN}.i_error ("Couldn't convert audio " + {SDL_ERROR}.sdl_get_error) end check attached chunk.abuf_managed as abuf then abuf.item.memory_copy (buf.item, chunk.alen.to_integer_32) end Result := True end else {I_MAIN}.i_error ("Couldn't convert with SDL") end else Result := False end end convertible_ratio (freq1, freq2: INTEGER_32): BOOLEAN local ratio: INTEGER_32 do if freq1 > freq2 then Result := convertible_ratio (freq2, freq1) elseif freq2 \\ freq1 /= 0 then Result := False else ratio := freq2 // freq1 from until ratio.bit_and (1) /= 0 loop ratio := ratio |>> 1 end Result := ratio = 1 end end reserve_cache_space (len: NATURAL_32) -- Enforce SFX cache size limit. We are just about to allocate "len" -- bytes on the heap for a new sound effect, so free up some space -- so that we keep allocated_sounds_size < snd_cachesize do {NOT_IMPLEMENTED}.not_implemented ("SOUND_SDL reserve_cache_space", False) end allocate_sound (sfxinfo: SFXINFO_T; len: NATURAL_32): ALLOCATED_SOUND_T local snd: ALLOCATED_SOUND_T do reserve_cache_space (len) create snd.make snd.chunk.allocate_abuf (len.to_integer_32) snd.chunk.set_alen (len) snd.chunk.set_allocated (1) snd.chunk.set_volume ({MIX}.mix_max_volume.to_character_8) snd.pitch := {I_SOUND}.norm_pitch snd.use_count := 0 snd.sfxinfo := sfxinfo allocated_sounds_size := allocated_sounds_size + len.to_integer_32 allocated_sound_link (snd) Result := snd end allocated_sound_link (snd: ALLOCATED_SOUND_T) -- Hook a sound into the linked list at the head do snd.prev := Void snd.next := allocated_sounds_head allocated_sounds_head := snd if allocated_sounds_tail = Void then allocated_sounds_tail := snd else check attached snd.next as next then next.prev := snd end end end get_slice_size: INTEGER_32 local limit: INTEGER_32 do limit := ({I_SOUND}.snd_samplerate * {I_SOUND}.snd_maxslicetime_ms) // 1000 from Result := 0 until (1 |<< (Result + 1)) > limit loop Result := Result + 1 end Result := 1 |<< Result ensure Result <= ({I_SOUND}.snd_samplerate * {I_SOUND}.snd_maxslicetime_ms) // 1000 ({I_SOUND}.snd_samplerate * {I_SOUND}.snd_maxslicetime_ms) // 1000 < (Result * 2) end Sound_devices: ARRAY [INTEGER_32] -- List of sound devices that this sound module is used for. once Result := <<{I_SOUND}.snddevice_sb, {I_SOUND}.snddevice_pas, {I_SOUND}.snddevice_gus, {I_SOUND}.snddevice_waveblaster, {I_SOUND}.snddevice_soundcanvas, {I_SOUND}.snddevice_awe32>> end feature shutdown -- Shutdown sound module do if sound_initialized then {SDL_MIXER_FUNCTIONS_API}.mix_close_audio {SDL_FUNCTIONS_API}.sdl_quit_sub_system ({SDL_CONSTANT_API}.sdl_init_audio.to_natural_32) sound_initialized := False end end get_sfx_lump_name (sfxinfo: SFXINFO_T): STRING_8 local sfx: SFXINFO_T do if attached sfxinfo.link as link then sfx := link else sfx := sfxinfo end check attached sfx.name as name then if use_sfx_prefix then Result := "ds" + name else Result := name end end end get_sfx_lump_num (sfxinfo: SFXINFO_T): INTEGER_32 -- Returns the lump index of the given sound. do Result := i_main.W_wad.w_getnumforname (get_sfx_lump_name (sfxinfo)) end update -- Called periodically to update the subsystem. local i: INTEGER_32 do from i := channels_playing.lower until i > channels_playing.upper loop if attached channels_playing [i] and not sound_is_playing (i) then release_sound_on_channel (i) end i := i + 1 end end sound_is_playing (handle: INTEGER_32): BOOLEAN -- Query if a sound is playing on the given channel do if sound_initialized and handle >= 0 and handle < Num_channels then Result := {SDL_MIXER_FUNCTIONS_API}.mix_playing (handle) /= 0 end end update_sound_params (handle, vol, sep: INTEGER_32) -- Update the sound settings on the given channel. local left: INTEGER_32 right: INTEGER_32 do if sound_initialized and handle >= 0 and handle < Num_channels then left := ((254 - sep) * vol) // 127 right := (sep * vol) // 127 left := left.max (0).min (255) right := right.max (0).min (255) {SDL_MIXER_FUNCTIONS_API}.mix_set_panning (handle, left.to_character_8, right.to_character_8).do_nothing end end start_sound (sfxinfo: SFXINFO_T; channel, vol, sep, pitch: INTEGER_32): INTEGER_32 -- Start a sound on a given channel. Returns the channel id -- or -1 on failure. local snd: ALLOCATED_SOUND_T newsnd: ALLOCATED_SOUND_T do if not sound_initialized or channel < 0 or channel >= Num_channels then Result := -1 else release_sound_on_channel (channel) if not lock_sound (sfxinfo) then Result := -1 else snd := get_allocated_sound_by_sfx_info_and_pitch (sfxinfo, pitch) if not attached snd then snd := get_allocated_sound_by_sfx_info_and_pitch (sfxinfo, {I_SOUND}.norm_pitch) if not attached snd then Result := -1 else if {I_SOUND}.snd_pitchshift /= 0 then newsnd := pitch_shift (snd, pitch) if newsnd /= Void then lock_allocated_sound (newsnd) unlock_allocated_sound (snd) snd := newsnd end end end else lock_allocated_sound (snd) end if Result /= -1 then check attached snd as attached_snd then {MIX}.mix_play_channel (channel, attached_snd.chunk, 0).do_nothing channels_playing [channel] := attached_snd end update_sound_params (channel, vol, sep) Result := channel end end end end stop_sound (handle: INTEGER_32) -- Stop the sound playing on the given channel do if sound_initialized and handle >= 0 and handle < Num_channels then release_sound_on_channel (handle) end end cache_sounds (sounds: ARRAY [SFXINFO_T]) -- Called on startup to precache sound effects (if necessary) do if not Use_libsamplerate then cache_sounds_inner (sounds) end end cache_sounds_inner (sounds: ARRAY [SFXINFO_T]) local i: INTEGER_32 name: STRING_8 do print ("Precaching all sound effects...") from i := sounds.lower until i > sounds.upper loop if i \\ 6 = 0 then print (".") end name := get_sfx_lump_name (sounds [i]) sounds [i].lumpnum := i_main.W_wad.w_checknumforname (name) if sounds [i].lumpnum /= -1 then if not cache_sfx (sounds [i]) then print ("Could not cache sound " + i.out + "%N") end end i := i + 1 end end cache_sfx (sfxinfo: SFXINFO_T): BOOLEAN local lumpnum: INTEGER_32 lumplen: NATURAL_32 samplerate: INTEGER_32 length: NATURAL_32 data: MANAGED_POINTER do lumpnum := sfxinfo.lumpnum data := i_main.W_wad.w_cachelumpnum (lumpnum) lumplen := i_main.W_wad.w_lumplength (lumpnum).to_natural_32 if lumplen < 8 or data.read_natural_8_le (0) /= 3 or data.read_natural_8_le (1) /= 0 then Result := False else samplerate := data.read_integer_16_le (2).to_integer_32 length := data.read_natural_32_le (4) if length > lumplen - 8 or length <= 48 then Result := False else length := length - 32 create data.share_from_pointer (data.item + 16, length.to_integer_32) if not expand_sound_data (sfxinfo, data.item + 8, samplerate, length.to_integer_32) then Result := False else i_main.W_wad.w_releaselumpnum (lumpnum) Result := True end end end end feature Sound_sdl_module (a_i_main: like i_main): SOUND_SDL_MODULE once create Result.make (a_i_main) ensure instance_free: class end end -- class SOUND_SDL_MODULE
Generated by ISE EiffelStudio