#import "Sound_Player";
Plays sounds: it mixes streams of WAV (PCM or ADPCM) and Ogg Vorbis audio and sends the result to the system's audio output.
Load a sound with load_audio_file or load_audio_data, start the player with sound_player_init,
then make a stream with make_stream, set it up and start_playing it. Streams with a position
are panned and softened by distance from the listener (update_listener). Call update once per
frame; sound_player_shutdown ends it all.
#import "Sound_Player";
sound := load_audio_file("jump.wav");
assert(sound.loaded);
assert(sound_player_init(.{}));
stream := make_stream(*sound, .GENERAL_SFX);
start_playing(stream);
while true {
update();
// ... run a frame ...
}This one is only checked to compile, not run.
Module parameters#
- MAX_SOUND_CATEGORIES := DEFAULT_MAX_SOUND_CATEGORIES
- How many categories streams can be sorted into.
- VERBOSE := false
- Logs when the audio output cannot be opened or started.
Procedures#
Samples#
Mixer#
Panning#
Tells the player where the listener is, so sounds with a position can be panned and softened by distance.
update_listener(.{0, 1.7, 0}, .{0, 0, 0, 1});This one is only checked to compile, not run.
The loudness a stream should have for the listener's position, from the master volume, the stream's own scales and distance.
Sets the volume of everything the player plays, as a factor of the original: 1 is unchanged, 0 is silent.
Cache#
ADPCM#
Decode queue#
Vorbis#
Loading#
Reads a sound file. The result has loaded set when it worked.
sound := load_audio_file("music.ogg");
if !sound.loaded return;This one is only checked to compile, not run.
Reads a sound from memory. The result has loaded set when it worked.
sound := load_audio_data("junk", "this is not audio");
assert(!sound.loaded);Doesn't run in the browser (skipped on wasm).
Stops every stream that plays data, frees the bytes it was loaded from and clears it, so that nothing points into it any more.
samples: [64] s16;
data := Sound_Data.{ loaded = true, type = .LINEAR_SAMPLE_ARRAY, samples = samples.data, nsamples_times_nchannels = samples.count };
data.buffer = copy_string("stands in for the file bytes");
release_sound_data(*data);
assert(!data.loaded && data.buffer.count == 0);Doesn't run in the browser (skipped on wasm).
Swaps in a new sound for data without moving it: the streams playing the old one are stopped, its bytes are freed and
replacement takes its place. Anything that holds the *Sound_Data keeps a valid pointer and gets the new sound from the next
make_stream on. This is how to hot-reload a sound.
first := load_audio_data("first", copy_string("not audio"));
assert(!first.loaded); // too short to be a sound; its bytes are still ours
free(first.buffer.data);
samples: [64] s16;
data := Sound_Data.{ loaded = true, type = .LINEAR_SAMPLE_ARRAY, samples = samples.data, nsamples_times_nchannels = samples.count };
data.buffer = copy_string("old bytes");
louder: [64] s16;
louder[0] = 100;
replacement := Sound_Data.{ loaded = true, type = .LINEAR_SAMPLE_ARRAY, samples = louder.data, nsamples_times_nchannels = louder.count };
replacement.buffer = copy_string("new bytes");
assert(replace_sound_data(*data, replacement));
assert(data.samples == louder.data && data.buffer == "new bytes");
release_sound_data(*data);Doesn't run in the browser (skipped on wasm).
Backend#
History#
Player#
The audio outputs the system offers.
for get_devices() print("%\n", it.name);This one is only checked to compile, not run.
The sampling rate of the output in hertz, or 0 before sound_player_init.
assert(get_audio_sampling_rate() == 0);Doesn't run in the browser (skipped on wasm).
Opens the audio output and starts mixing. Returns false if there is no usable device. Calling it again after it worked does nothing.
if !sound_player_init(.{}) print("no audio\n");This one is only checked to compile, not run.
Stops every stream, closes the output and frees what the player holds. sound_player_init can be called again after it.
Makes a stream to play data. Returns null when the sound is not loaded or has more channels than a stream can take.
samples: [64] s16;
sound := Sound_Data.{ loaded = true, type = .LINEAR_SAMPLE_ARRAY, nchannels = 1, samples = samples.data, nsamples_times_nchannels = samples.count };
stream := make_stream(*sound, .UI);
assert(stream != null);
not_loaded: Sound_Data;
assert(make_stream(*not_loaded) == null);Doesn't run in the browser (skipped on wasm).
Gives stream to the player, which plays it from the start and frees it once it ends. Do nothing before
sound_player_init.
The playing stream with this entity_id and manager_id, or null. Set those on a stream to find it later.
sound := load_audio_file("jump.wav");
stream := make_stream(*sound);
stream.entity_id = 7;
start_playing(stream);
assert(find(7, 0) == stream);This one is only checked to compile, not run.
Stops every stream whose entity_id is id, with no fade.
This one is only checked to compile, not run.
Stops every stream that has this manager_id, with no fade.
Works out how fast a stream plays from its sound's sampling rate, the output rate and rate_scale.
Makes a stream loop, or stops it from looping.
sound := load_audio_file("jump.wav");
stream := make_stream(*sound, .MUSIC);
set_repeating(stream, true);
start_playing(stream);This one is only checked to compile, not run.
The first half of update: marks streams that have reached their end. Call it and post_entity_update
instead of update when you need to touch streams between the two.
The second half of update: ends streams that were not kept alive, and refreshes the pitch and panning of the rest.
dt is the seconds since the last frame.
Advances the player by one frame. Call it once per frame. dt is the seconds since the last
call; leave it out to have the player measure it.
while true {
update();
// ... draw and handle input ...
}This one is only checked to compile, not run.
Mixes the next stretch of every stream into the output buffer. The audio thread calls it.
Fills the output with the audio thread's mixing step, or with the main thread's when update_from_a_thread is off.
The body of the audio thread.
Windows ABI#
Types#
A speaker of the output, as laid out by the player.
- FRONT_LEFT
- FRONT_RIGHT
- CENTER
- SUBWOOFER
- REAR_LEFT
- REAR_RIGHT
- EXTEND_LEFT
- EXTEND_RIGHT
Where a channel of a sound belongs in the room. load_audio_file sets these from the channel count; change
Sound_Data.source_directions before starting a stream to override them.
- MONO
- LEFT
- RIGHT
- FRONT_LEFT
- FRONT_RIGHT
- REAR_LEFT
- REAR_RIGHT
- CENTER_FORWARD
- SUBWOOFER_CENTER_FORWARD
- EXTEND_LEFT
- EXTEND_RIGHT
A group of sounds. A stream's category sets its default flags (a .MUSIC stream is music).
- GENERAL_SFX
- AMBIENCES
- FOOTSTEPS
- UI
- MUSIC
- channels: [MAX_CHANNELS_PER_SOUND] float
- base: Audio_Sample_Base
- base: Audio_Sample_Base
- source_scale_for_this_output_index: [OUTPUT_CHANNELS_MAX] float
- interpolated_source_scale_for_this_output_index: [OUTPUT_CHANNELS_MAX] float
- dvolume_dsample: [OUTPUT_CHANNELS_MAX] float
A loaded sound. loaded says whether loading worked. volume_scale scales every stream of it, and
buffer holds the bytes the sound was read from. Make a Sound_Stream from it to play it.
- resource: struct { name: string; full_path: string; loaded := false; skip_load := false; }
- type: := Kind.UNINITIALIZED
- nsamples_times_nchannels: s64
- nchannels: u16
- sampling_rate: u32
- buffer: string
- samples: *s16
- volume_scale: float
- silence_before_repeat: float
- filename_to_stream_from: string
- source_directions: [MAX_CHANNELS_PER_SOUND] Source_Direction
- nBlockAlign: s32
- wSamplesPerBlock: s32
samples: [8] s16;
sound := Sound_Data.{ loaded = true, type = .LINEAR_SAMPLE_ARRAY, nchannels = 1, samples = samples.data, nsamples_times_nchannels = samples.count };
assert(sound.sampling_rate == 44100);Doesn't run in the browser (skipped on wasm).
One playing instance of a sound. make_stream creates it and start_playing hands it to the player,
which owns it from then on. Set user_volume_scale, rate_scale, position and the user_flags before
starting it, or any time while it plays.
- user_data_u64: u64
- user_data_pointer: *void
- manager_id: s64
- entity_id: s64
- sound_name: string
- user_flags: enum u32 { REPEATING :: 0x1; SPATIALIZED :: 0x2; MUSIC :: 0x10; VOICEOVER :: 0x20; IS_FOOTSTEP :: 0x40; DO_NOT_KILL :: 0x80; PAUSED_DUE_TO_MENU :: 0x100; WAS_PAUSED_DUE_TO_MENU :: 0x200; AMBIENT :: 0x400; }
- internal_flags: enum u32 { FIRST_VOLUME_UPDATE :: 0x2; WAITING_FOR_INITIAL_DECODER_PAGES :: 0x4; FADING_OUT :: 0x8; }
- num_channels: s32
- sound_data: *Sound_Data
- decoder: *Cached_Decoder
- category: Sound_Category
- repeat_start_position: s64
- repeat_end_position: s64
- universe_volume_scale: := 1.0
- user_volume_scale: := 1.0
- play_cursor: float64
- silent_samples_at_start_of_play: s64
- marked: := false
- inaudible: := false
- position: Vector3
- orientation: Quaternion
- inner_radius: := 0.5
- outer_radius: := 10.0
- rate_scale: := 1.0
- samples_streamed_since_entity_update: s64
- max_gain: float
- last_plane_dir: Vector2
- debug_display_volume: float
- silent_this_frame: := false
- debug_watch: := false
- current_rate: float
- desired_rate: float
- input_scale_mappings: [MAX_CHANNELS_PER_SOUND] Scale_Mapping
- initial_samples_fetched: s64
- samples_needed_to_start_playing: s64
sound := load_audio_file("jump.wav");
stream := make_stream(*sound);
stream.user_volume_scale = 0.5;
stream.rate_scale = 1.2; // a little higher in pitch
start_playing(stream);This one is only checked to compile, not run.
- OGG
- ADPCM
- type: Decoder_Type
- owner: *Cached_Decoder
- start_address: s64
- end_address: s64
- page_index: s64
- data: *void
- buffer_length_in_bytes: s64
- num_samples_contained: s64
- marked: := false
- type: Decoder_Type
- pages: [..] *Cached_Decoder_Page
- issued_start_addresses: [..] s64
- page_size_in_samples: s64
- uncompressed_length_in_samples: s64
- num_channels: s64
- sampling_rate: s64
- sound_data: *Sound_Data
- stream: *Sound_Stream
- do_not_queue: := true
- deinit_proc: (decoder: *Cached_Decoder) -> ()
- add_decode_queue_item: (page: *Cached_Decoder_Page) -> ()
- recompute_page: (decoder: *Cached_Decoder, page: *Cached_Decoder_Page) -> ()
- make_page: (decoder: *Cached_Decoder) -> *Cached_Decoder_Page
- base: Cached_Decoder
- vorbis_file: *stb_vorbis
- seek_base: s64
- opus_pre_skip: s64
- bytes_per_mono_sample: s64
- uncompressed_length_in_bytes: s64
- bitrate_low: s64
- bitrate_nominal: s64
- bitrate_high: s64
- base: Cached_Decoder
- start: s64
- end: s64
- memory: *void
- buffer0: string
- buffer1: string
What the audio backend settled on: the channel count and the output sampling rate.
- initted: := false
- num_channels: s32
- channel_names: [] string
- output_sampling_rate: s32
An audio output that get_devices found.
- name: string
- hints: [] string
- device_index: s64
Settings for sound_player_init. The default works: it mixes on its own thread and keeps sounds
playing when the window loses focus only if audible_when_window_is_not_in_focus is set.
release_asset is called when a stream ends, so the program can free what the stream used.
- update_from_a_thread: := true
- audible_when_window_is_not_in_focus: := false
- keep_sounds_alive_by_default: := true
- seconds_to_fill_ahead: := 2.4 / 60.0
- set_async_thread_priority: () -> ()
- release_asset: (stream: *Sound_Stream, data: *Sound_Data)
config: Sound_Player_Config;
config.update_from_a_thread = false; // mix inside update() instead
config.audible_when_window_is_not_in_focus = true;
assert(sound_player_init(config));This one is only checked to compile, not run.
- speaker1: Audio_Channel
- speaker2: Audio_Channel
- l_inverse: Matrix2
- HEADPHONE
- MONO
- QUAD
- STEREO
- SURROUND
- _5POINT1
- _5POINT1_BACK
- _5POINT1_SURROUND
- _7POINT1
- _7POINT1_SURROUND
- _7POINT1_WIDE
- dwSize: s32
- dwFlags: s32
- dwBufferBytes: s32
- dwReserved: s32
- lpwfxFormat: *Waveformatex
- guid3DAlgorithm: GUID
- Format: Waveformatex
- wValidBitsPerSample: s16
- dwChannelMask: s32
- SubFormat: GUID
- outer_context: #Context
- devices: [..] Output_Device
- unknown: IUnknown
- vtable: *IDirectSound8Vtable
- unknown: IUnknown
- vtable: *IDirectSoundBuffer8Vtable
- base: IUnknown_VTable
- CreateSoundBuffer: (dsound: *IDirectSound8, desc: *DSBUFFERDESC, buffer: **IDirectSoundBuffer8, outer: **void = null) -> s32 #c_call
- GetCaps: *void
- DuplicateSoundBuffer: *void
- SetCooperativeLevel: (dsound: *IDirectSound8, hwnd: HWND, level: s32) -> s32 #c_call
- Compact: *void
- GetSpeakerConfig: (dsound: *IDirectSound8, config: *s32) -> s32 #c_call
- SetSpeakerConfig: *void
- Initialize: *void
- base: IUnknown_VTable
- GetCaps: (buffer: *IDirectSoundBuffer8, caps: *DSBUFFERCAPS) #c_call
- GetCurrentPosition: (buffer: *IDirectSoundBuffer8, pdwCurrentPlayCursor: *s32, pdwCurrentWriteCursor: *s32) -> s32 #c_call
- GetFormat: *void
- GetVolume: *void
- GetPan: *void
- GetFrequency: *void
- GetStatus: (buffer: *IDirectSoundBuffer8, pdwStatus: *s32) -> s32 #c_call
- Initialize: *void
- Lock: (buffer: *IDirectSoundBuffer8, dwOffset: s32, dwBytes: s32, pvAudioPtr1: **void, dwAudioBytes1: *s32, pvAudioPtr2: **void, dwAudioBytes2: *s32, dwFlags: s32) -> s32 #c_call
- Play: (buffer: *IDirectSoundBuffer8, dwReserved1: s32, dwPriority: s32, dwFlags: s32) -> s32 #c_call
- SetCurrentPosition: *void
- SetFormat: (buffer: *IDirectSoundBuffer8, format: *Waveformatex) -> s32 #c_call
- SetVolume: *void
- SetPan: *void
- SetFrequency: *void
- Stop: (buffer: *IDirectSoundBuffer8) -> s32 #c_call
- Unlock: (buffer: *IDirectSoundBuffer8, pvAudioPtr1: *void, dwAudioBytes1: s32, pvAudioPtr2: *void, dwAudioBytes2: s32) -> s32 #c_call
- Restore: (buffer: *IDirectSoundBuffer8) -> s32 #c_call
- SetFX: *void
- AcquireResources: *void
- GetObjectInPath: *void