Initial commit.
This commit is contained in:
136
audio.odin
Normal file
136
audio.odin
Normal file
@@ -0,0 +1,136 @@
|
||||
package audio
|
||||
|
||||
import "core:log"
|
||||
|
||||
import "engine"
|
||||
|
||||
Sound :: engine.Sound
|
||||
Sound_Instance :: engine.Sound_Instance
|
||||
Bus :: engine.Bus
|
||||
Listener :: engine.Listener
|
||||
|
||||
Context :: struct {
|
||||
engine: engine.Engine,
|
||||
}
|
||||
|
||||
init :: proc(ctx: ^Context, max_sound_instances: u32) -> bool {
|
||||
assert(ctx != nil)
|
||||
ok := engine.init(&ctx.engine, max_sound_instances)
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
tick :: proc(ctx: ^Context) {
|
||||
assert(ctx != nil)
|
||||
engine.tick(&ctx.engine)
|
||||
}
|
||||
|
||||
shutdown :: proc(ctx: ^Context) {
|
||||
assert(ctx != nil)
|
||||
engine.shutdown(&ctx.engine)
|
||||
}
|
||||
|
||||
|
||||
create_bus :: proc(ctx: ^Context) -> (Bus, bool) {
|
||||
assert(ctx != nil)
|
||||
|
||||
bus, ok := engine.create_bus(&ctx.engine)
|
||||
if !ok {
|
||||
return {}, false
|
||||
}
|
||||
|
||||
return bus, ok
|
||||
}
|
||||
|
||||
destroy_bus :: proc(ctx: ^Context, bus: ^Bus) {
|
||||
assert(ctx != nil)
|
||||
assert(bus != nil)
|
||||
|
||||
engine.destroy_bus(&ctx.engine, bus)
|
||||
}
|
||||
|
||||
create_listener :: proc(ctx: ^Context) -> (^Listener, bool) {
|
||||
assert(ctx != nil)
|
||||
return engine.create_listener(&ctx.engine)
|
||||
}
|
||||
|
||||
destroy_listener :: proc(ctx: ^Context, listener: ^Listener) {
|
||||
assert(ctx != nil)
|
||||
engine.destroy_listener(&ctx.engine, listener)
|
||||
}
|
||||
|
||||
load_sound :: proc {
|
||||
load_sound_from_path,
|
||||
}
|
||||
|
||||
load_sound_from_path :: proc(ctx: ^Context, path: string) -> (Sound, bool) {
|
||||
assert(ctx != nil)
|
||||
|
||||
sound, ok := engine.load_sound_from_path(&ctx.engine, path)
|
||||
if !ok {
|
||||
return {}, false
|
||||
}
|
||||
|
||||
return sound, true
|
||||
}
|
||||
|
||||
unload_sound :: proc(ctx: ^Context, sound: ^Sound) {
|
||||
assert(ctx != nil)
|
||||
assert(sound != nil)
|
||||
|
||||
engine.unload_sound(&ctx.engine, sound)
|
||||
}
|
||||
|
||||
play_sound :: proc {
|
||||
play_sound_at_position,
|
||||
}
|
||||
|
||||
play_sound_at_position :: proc(ctx: ^Context, sound: ^Sound, bus: ^Bus, position: [3]f32) -> (^Sound_Instance, bool) {
|
||||
assert(ctx != nil)
|
||||
|
||||
sound_instance, ok := engine.create_sound_instance(&ctx.engine, sound, bus)
|
||||
if !ok {
|
||||
log.warnf("Failed to play sound! Failed to create a sound instance.")
|
||||
return nil, false
|
||||
}
|
||||
|
||||
sound_instance.spatialized = true // TEMP: SS - Hardcoded. Expose!
|
||||
sound_instance.volume = 1.0 // TEMP: SS - Hardcoded. Expose!
|
||||
sound_instance.position = position
|
||||
sound_instance.min_distance = 1.0 // TEMP: SS - Hardcoded. Expose!
|
||||
sound_instance.max_distance = 50.0 // TEMP: SS - Hardcoded. Expose!
|
||||
|
||||
log.infof("Got instance! Now we should 'start'/play it.")
|
||||
|
||||
if !engine.play_sound_instance(
|
||||
&ctx.engine,
|
||||
sound_instance
|
||||
) {
|
||||
log.warnf("Failed to play sound instance.")
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return sound_instance, true
|
||||
}
|
||||
|
||||
// sound_instance_playing :: proc(sound_instance: ^Sound_Instance) -> bool { // TODO: SS - Implement something like this.
|
||||
// assert(sound_instance != nil)
|
||||
|
||||
// return true // TEMP
|
||||
// }
|
||||
|
||||
update_listener :: proc(ctx: ^Context, listener: ^Listener, position, velocity, direction_forward, world_up: [3]f32) -> bool {
|
||||
assert(ctx != nil)
|
||||
assert(listener != nil)
|
||||
|
||||
if listener.id == engine.INVALID_LISTENER_ID {
|
||||
return false
|
||||
}
|
||||
|
||||
listener.position = position
|
||||
listener.velocity = velocity
|
||||
listener.direction_forward = direction_forward
|
||||
listener.world_up = world_up
|
||||
|
||||
return true
|
||||
}
|
||||
12
bus.odin
Normal file
12
bus.odin
Normal file
@@ -0,0 +1,12 @@
|
||||
package audio
|
||||
|
||||
import "engine"
|
||||
|
||||
// create_bus :: proc(ctx: ^Context) -> (engine.Bus, bool) {
|
||||
// bus, bus_created := engine.create_bus(&ctx.engine)
|
||||
// if !bus_created {
|
||||
// return {}, false
|
||||
// }
|
||||
|
||||
// return bus, true
|
||||
// }
|
||||
49
engine/bus.odin
Normal file
49
engine/bus.odin
Normal file
@@ -0,0 +1,49 @@
|
||||
package engine
|
||||
|
||||
import "core:log"
|
||||
|
||||
import "ljud"
|
||||
|
||||
Bus :: struct {
|
||||
backend: ^Bus_Backend,
|
||||
}
|
||||
|
||||
create_bus :: proc(engine: ^Engine) -> (Bus, bool) {
|
||||
assert(engine != nil)
|
||||
|
||||
bus: Bus
|
||||
|
||||
when AUDIO_ENGINE_LJUD {
|
||||
backend, ok := ljud.create_bus(&engine.backend)
|
||||
if !ok {
|
||||
return {}, false
|
||||
}
|
||||
|
||||
bus.backend = backend
|
||||
}
|
||||
else {
|
||||
#assert("TODO: SS - Implement for Audio Engine backend")
|
||||
}
|
||||
|
||||
if bus.backend == nil {
|
||||
return {}, false
|
||||
}
|
||||
|
||||
return bus, true
|
||||
}
|
||||
|
||||
destroy_bus :: proc(engine: ^Engine, bus: ^Bus) {
|
||||
assert(engine != nil)
|
||||
assert(bus != nil)
|
||||
assert(bus.backend != nil)
|
||||
|
||||
when AUDIO_ENGINE_LJUD {
|
||||
ljud.destroy_bus(&engine.backend, bus.backend)
|
||||
}
|
||||
else {
|
||||
#assert("TODO: SS - Implement for Audio Engine backend")
|
||||
}
|
||||
|
||||
free(bus.backend)
|
||||
bus.backend = nil
|
||||
}
|
||||
132
engine/engine.odin
Normal file
132
engine/engine.odin
Normal file
@@ -0,0 +1,132 @@
|
||||
package engine
|
||||
|
||||
import "core:container/queue"
|
||||
import "core:log"
|
||||
import "core:math/linalg"
|
||||
|
||||
AUDIO_ENGINE_LJUD :: #config(AUDIO_ENGINE_LJUD, false)
|
||||
AUDIO_ENGINE_FMOD :: #config(AUDIO_ENGINE_FMOD, false)
|
||||
AUDIO_ENGINE_WWISE :: #config(AUDIO_ENGINE_WWISE, false)
|
||||
|
||||
import "ljud"
|
||||
|
||||
when AUDIO_ENGINE_LJUD {
|
||||
Engine_Backend :: ljud.Engine
|
||||
Sound_Backend :: ljud.Sound
|
||||
Sound_Instance_Backend :: ljud.Sound_Instance
|
||||
Bus_Backend :: ljud.Bus
|
||||
}
|
||||
else when AUDIO_ENGINE_FMOD {
|
||||
|
||||
}
|
||||
|
||||
Engine :: struct {
|
||||
backend: Engine_Backend,
|
||||
|
||||
sound_instance_ids: queue.Queue(Sound_Instance_ID),
|
||||
sound_instances: []Sound_Instance,
|
||||
|
||||
listeners: [MAX_LISTENERS]Listener,
|
||||
available_listener_ids: queue.Queue(Listener_ID),
|
||||
}
|
||||
|
||||
init :: proc(engine: ^Engine, max_sound_instances: u32) -> bool {
|
||||
assert(engine != nil)
|
||||
assert(max_sound_instances > 0)
|
||||
|
||||
when AUDIO_ENGINE_LJUD {
|
||||
// engine.§ new(ljud.Engine)
|
||||
// if engine.data != nil {
|
||||
// v := transmute(^ljud.Engine)(engine.data)
|
||||
// if !ljud.init(v) {
|
||||
// free(engine.data)
|
||||
// engine.data = nil
|
||||
|
||||
// log.errorf("Failed to initialize audio-engine LJUD.")
|
||||
// return false
|
||||
// }
|
||||
// }
|
||||
|
||||
if !ljud.init(&engine.backend) {
|
||||
// free(engine.data)
|
||||
// engine.data = nil
|
||||
|
||||
log.errorf("Failed to initialize audio-engine LJUD.")
|
||||
return false
|
||||
}
|
||||
}
|
||||
else {
|
||||
#assert("No audio-engine defined. '-define:AUDIO_ENGINE_X=true' where X is the audio-engine; 'LJUD', 'FMOD', 'WWISE'.")
|
||||
}
|
||||
|
||||
{ // Initialize sound-instances.
|
||||
log.infof("Creating %v sound-instances.", max_sound_instances)
|
||||
engine.sound_instances = make([]Sound_Instance, max_sound_instances)
|
||||
|
||||
queue_init_err := queue.init(&engine.sound_instance_ids, int(max_sound_instances))
|
||||
assert(queue_init_err == .None)
|
||||
|
||||
for i in 0..<len(engine.sound_instances) {
|
||||
queue.append(&engine.sound_instance_ids, Sound_Instance_ID(i))
|
||||
}
|
||||
}
|
||||
|
||||
{ // Initialize the array of listeners and the queue of listener-ids.
|
||||
queue.init(&engine.available_listener_ids, len(engine.listeners))
|
||||
for i in 0..<len(engine.listeners) {
|
||||
engine.listeners[i] = Listener {
|
||||
id = INVALID_LISTENER_ID,
|
||||
enabled = false,
|
||||
}
|
||||
queue.push_back(&engine.available_listener_ids, Listener_ID(i))
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
tick :: proc(engine: ^Engine) {
|
||||
when AUDIO_ENGINE_LJUD {
|
||||
ljud.tick(&engine.backend)
|
||||
}
|
||||
else {
|
||||
#assert("TODO: SS - Implement for Audio Engine backend")
|
||||
}
|
||||
|
||||
// Tick listeners.
|
||||
for listener in engine.listeners {
|
||||
if listener.id == INVALID_LISTENER_ID {
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
when AUDIO_ENGINE_LJUD {
|
||||
ljud.tick_listener(
|
||||
&engine.backend,
|
||||
u8(listener.id),
|
||||
listener.enabled,
|
||||
listener.position,
|
||||
listener.velocity,
|
||||
linalg.normalize(listener.direction_forward),
|
||||
linalg.normalize(listener.world_up),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
shutdown :: proc(engine: ^Engine) {
|
||||
assert(engine != nil)
|
||||
|
||||
when AUDIO_ENGINE_LJUD {
|
||||
ljud.shutdown(&engine.backend)
|
||||
}
|
||||
else {
|
||||
#assert("TODO: SS - Implement for Audio Engine backend")
|
||||
}
|
||||
|
||||
delete(engine.sound_instances)
|
||||
engine.sound_instances = nil
|
||||
queue.destroy(&engine.sound_instance_ids)
|
||||
|
||||
queue.destroy(&engine.available_listener_ids)
|
||||
}
|
||||
64
engine/listener.odin
Normal file
64
engine/listener.odin
Normal file
@@ -0,0 +1,64 @@
|
||||
package engine
|
||||
|
||||
import "core:log"
|
||||
import "core:container/queue"
|
||||
|
||||
import "ljud"
|
||||
|
||||
|
||||
MAX_LISTENERS :: 4
|
||||
|
||||
Listener_ID :: distinct u8
|
||||
INVALID_LISTENER_ID :: max(Listener_ID)
|
||||
|
||||
Listener :: struct {
|
||||
id: Listener_ID,
|
||||
|
||||
enabled: bool,
|
||||
|
||||
position, velocity, direction_forward, world_up: [3]f32,
|
||||
}
|
||||
|
||||
create_listener :: proc(
|
||||
engine: ^Engine,
|
||||
position: [3]f32 = {},
|
||||
velocity: [3]f32 = {},
|
||||
direction_forward: [3]f32 = {},
|
||||
world_up: [3]f32 = {}
|
||||
) -> (^Listener, bool)
|
||||
{
|
||||
assert(engine != nil)
|
||||
|
||||
id, ok := queue.pop_front_safe(&engine.available_listener_ids)
|
||||
if !ok {
|
||||
log.warnf("Failed to create listener! No free ID.")
|
||||
return {}, false
|
||||
}
|
||||
|
||||
listener := &engine.listeners[id]
|
||||
assert(listener != nil)
|
||||
|
||||
listener^ = {
|
||||
id = id,
|
||||
|
||||
enabled = true,
|
||||
|
||||
position = position,
|
||||
velocity = velocity,
|
||||
direction_forward = direction_forward,
|
||||
world_up = world_up,
|
||||
}
|
||||
|
||||
return listener, true
|
||||
}
|
||||
|
||||
destroy_listener :: proc(engine: ^Engine, listener: ^Listener) {
|
||||
assert(engine != nil)
|
||||
assert(listener != nil)
|
||||
|
||||
// Return the ID.
|
||||
queue.push_back(&engine.available_listener_ids, listener.id)
|
||||
|
||||
listener^ = {}
|
||||
listener.id = INVALID_LISTENER_ID
|
||||
}
|
||||
39
engine/ljud/bus.odin
Normal file
39
engine/ljud/bus.odin
Normal file
@@ -0,0 +1,39 @@
|
||||
package ljud
|
||||
|
||||
import "core:log"
|
||||
import "core:strings"
|
||||
|
||||
import ma "vendor:miniaudio"
|
||||
|
||||
Bus :: ma.sound_group
|
||||
|
||||
create_bus :: proc(engine: ^Engine) -> (^Bus, bool) {
|
||||
assert(engine != nil)
|
||||
|
||||
b := new(Bus)
|
||||
assert(b != nil)
|
||||
|
||||
result := ma.sound_group_init(
|
||||
pEngine = &engine.ma_engine,
|
||||
flags = {},
|
||||
pParentGroup = nil, // TODO: SS - Support parent busses.
|
||||
pGroup = b,
|
||||
)
|
||||
if result != .SUCCESS {
|
||||
free(b)
|
||||
return nil, false
|
||||
}
|
||||
|
||||
ma.sound_group_set_spatialization_enabled(b, true) // TODO: SS - Needs to be configurable outside.
|
||||
ma.sound_group_set_min_distance(b, 1.0) // TODO: SS - Needs to be configurable outside.
|
||||
ma.sound_group_set_max_distance(b, 50) // TODO: SS - Needs to be configurable outside.
|
||||
|
||||
return b, true
|
||||
}
|
||||
|
||||
destroy_bus :: proc(engine: ^Engine, bus: ^Bus) {
|
||||
assert(engine != nil)
|
||||
assert(bus != nil)
|
||||
|
||||
ma.sound_group_uninit(bus)
|
||||
}
|
||||
94
engine/ljud/ljud.odin
Normal file
94
engine/ljud/ljud.odin
Normal file
@@ -0,0 +1,94 @@
|
||||
package ljud
|
||||
|
||||
import "core:container/queue"
|
||||
import "core:log"
|
||||
import "core:strings"
|
||||
|
||||
import ma "vendor:miniaudio"
|
||||
|
||||
Engine :: struct {
|
||||
ma_engine: ma.engine,
|
||||
}
|
||||
|
||||
init :: proc(engine: ^Engine) -> bool {
|
||||
assert(engine != nil)
|
||||
log.infof("Initializing LJUD Engine")
|
||||
|
||||
engine_config := ma.engine_config_init()
|
||||
engine_config.channels = 0
|
||||
engine_config.sampleRate = 0
|
||||
engine_config.listenerCount = ma.ENGINE_MAX_LISTENERS // HMM: SS - Get this passed in?
|
||||
|
||||
res := ma.engine_init(&engine_config, &engine.ma_engine)
|
||||
if res != .SUCCESS {
|
||||
log.errorf("Failed to initialize miniaudio! Result: %v.", res)
|
||||
return false
|
||||
}
|
||||
|
||||
start_res := ma.engine_start(&engine.ma_engine) // TODO: SS - Move elsewhere?
|
||||
if start_res != .SUCCESS {
|
||||
log.errorf("Failed to start miniaudio! Result: %v.", start_res)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
tick :: proc(engine: ^Engine) {
|
||||
assert(engine != nil)
|
||||
}
|
||||
|
||||
tick_listener :: proc(engine: ^Engine, id: u8, enabled: bool, position, velocity, direction_forward, world_up: [3]f32) {
|
||||
assert(engine != nil)
|
||||
|
||||
// Enabled.
|
||||
ma.engine_listener_set_enabled(
|
||||
&engine.ma_engine,
|
||||
u32(id),
|
||||
b32(enabled),
|
||||
)
|
||||
|
||||
// Position.
|
||||
ma.engine_listener_set_position(
|
||||
&engine.ma_engine,
|
||||
u32(id),
|
||||
expand_values(position)
|
||||
)
|
||||
|
||||
// Velocity.
|
||||
ma.engine_listener_set_velocity(
|
||||
&engine.ma_engine,
|
||||
u32(id),
|
||||
expand_values(velocity)
|
||||
)
|
||||
|
||||
// Direction (forward).
|
||||
ma.engine_listener_set_direction(
|
||||
&engine.ma_engine,
|
||||
u32(id),
|
||||
expand_values(direction_forward)
|
||||
)
|
||||
|
||||
// World (up).
|
||||
ma.engine_listener_set_world_up(
|
||||
&engine.ma_engine,
|
||||
u32(id),
|
||||
expand_values(world_up)
|
||||
)
|
||||
|
||||
// // Cone. // TODO: SS - Support! :)
|
||||
// ma.engine_listener_set_cone(
|
||||
// &engine.ma_engine,
|
||||
// u32(id),
|
||||
// ..
|
||||
// )
|
||||
|
||||
// log.infof("Ticked listener %v. Position: %v, velocity: %v, direction forward: %v, world up: %v", id, position, direction_forward, world_up)
|
||||
}
|
||||
|
||||
shutdown :: proc(engine: ^Engine) {
|
||||
assert(engine != nil)
|
||||
|
||||
log.infof("Shutting down LJUD Engine")
|
||||
ma.engine_uninit(&engine.ma_engine)
|
||||
}
|
||||
157
engine/ljud/sound.odin
Normal file
157
engine/ljud/sound.odin
Normal file
@@ -0,0 +1,157 @@
|
||||
package ljud
|
||||
|
||||
import "core:sys/orca"
|
||||
import "base:runtime"
|
||||
import "core:log"
|
||||
import "core:strings"
|
||||
import "core:c"
|
||||
|
||||
import ma "vendor:miniaudio"
|
||||
|
||||
Sound :: struct {
|
||||
// data_source_base: ma.data_source_base,
|
||||
|
||||
decoder: ma.decoder,
|
||||
}
|
||||
|
||||
Sound_Instance :: ma.sound
|
||||
|
||||
@(private) on_read :: proc "c" (pDataSource: ^ma.data_source, pFramesOut: rawptr, frameCount: u64, pFramesRead: ^u64) -> ma.result {
|
||||
// Read data here. Output in the same format returned by my_data_source_get_data_format().
|
||||
|
||||
context = runtime.default_context()
|
||||
log.infof("%v", #procedure)
|
||||
return .ERROR
|
||||
}
|
||||
|
||||
@(private) on_seek :: proc "c" (pDataSource: ^ma.data_source, frameIndex: u64) -> ma.result {
|
||||
// Seek to a specific PCM frame here. Return MA_NOT_IMPLEMENTED if seeking is not supported.
|
||||
|
||||
context = runtime.default_context()
|
||||
log.infof("%v", #procedure)
|
||||
return .ERROR
|
||||
}
|
||||
|
||||
@(private) on_get_data_format :: proc "c" (pDataSource: ^ma.data_source, pFormat: ^ma.format, pChannels: ^u32, pSampleRate: ^u32, pChannelMap: [^]ma.channel, channelMapCap: c.size_t) -> ma.result {
|
||||
// Return the format of the data here.
|
||||
|
||||
context = runtime.default_context()
|
||||
log.infof("%v", #procedure)
|
||||
return .ERROR
|
||||
}
|
||||
|
||||
@(private) on_get_cursor :: proc "c" (pDataSource: ^ma.data_source, pCursor: ^u64) -> ma.result {
|
||||
// Retrieve the current position of the cursor here. Return MA_NOT_IMPLEMENTED and set *pCursor to 0 if there is no notion of a cursor.
|
||||
|
||||
context = runtime.default_context()
|
||||
log.infof("%v", #procedure)
|
||||
return .ERROR
|
||||
}
|
||||
|
||||
@(private) on_get_length :: proc "c" (pDataSource: ^ma.data_source, pLength: ^u64) -> ma.result {
|
||||
// Retrieve the length in PCM frames here. Return MA_NOT_IMPLEMENTED and set *pLength to 0 if there is no notion of a length or if the length is unknown.
|
||||
|
||||
context = runtime.default_context()
|
||||
log.infof("%v", #procedure)
|
||||
return .ERROR
|
||||
}
|
||||
|
||||
@(private) on_set_looping :: proc "c" (pDataSource: ^ma.data_source, isLooping: b32) -> ma.result {
|
||||
context = runtime.default_context()
|
||||
log.infof("%v", #procedure)
|
||||
return .ERROR
|
||||
}
|
||||
|
||||
load_sound_from_data :: proc(engine: ^Engine, data: []u8, extension: string) -> (^Sound, bool) {
|
||||
assert(engine != nil)
|
||||
|
||||
sound := new(Sound)
|
||||
assert(sound != nil)
|
||||
|
||||
// Configure our decoder
|
||||
decoder_config := ma.decoder_config_init(
|
||||
outputFormat = .f32,
|
||||
outputChannels = 0,
|
||||
outputSampleRate = 0,
|
||||
)
|
||||
|
||||
// NOTE: SS - "When loading a decoder, miniaudio uses a trial and error technique to find the appropriate decoding backend. This can be unnecessarily inefficient if the type is already known. In this case you can use encodingFormat variable in the device config to specify a specific encoding format you want to decode."
|
||||
decoder_config.encodingFormat = .unknown // TODO: SS - Check 'extension'.
|
||||
|
||||
decoder_result := ma.decoder_init_memory(
|
||||
pData = raw_data(data),
|
||||
dataSize = len(data),
|
||||
pConfig = &decoder_config,
|
||||
pDecoder = &sound.decoder,
|
||||
)
|
||||
if decoder_result != .SUCCESS {
|
||||
log.errorf("Failed to initialize decoder! Result: %v.", decoder_result)
|
||||
free(sound)
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return sound, true
|
||||
}
|
||||
|
||||
unload_sound :: proc(engine: ^Engine, sound: ^Sound) {
|
||||
assert(engine != nil)
|
||||
assert(sound != nil)
|
||||
// assert(sound.data_source != nil)
|
||||
|
||||
ma.data_source_uninit(sound.decoder.ds.pCurrent)
|
||||
}
|
||||
|
||||
init_sound_instance :: proc(engine: ^Engine, bus: ^Bus, sound: ^Sound, sound_instance: ^Sound_Instance) -> bool {
|
||||
result := ma.sound_init_from_data_source(
|
||||
pEngine = &engine.ma_engine,
|
||||
pDataSource = sound.decoder.ds.pCurrent,
|
||||
flags = {},
|
||||
pGroup = bus,
|
||||
pSound = sound_instance,
|
||||
)
|
||||
if result != .SUCCESS {
|
||||
log.errorf("Failed to create a sound instance! Result: %v.", result)
|
||||
return false
|
||||
}
|
||||
|
||||
ma.sound_stop(sound_instance)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
Play_Sound_Instance_Spec :: struct {
|
||||
spatialized: bool,
|
||||
volume: f32,
|
||||
|
||||
position: [3]f32,
|
||||
distance: struct { min, max: f32, }
|
||||
}
|
||||
|
||||
play_sound_instance :: proc(engine: ^Engine, sound_instance: ^Sound_Instance, spec: Play_Sound_Instance_Spec) -> bool {
|
||||
assert(engine != nil)
|
||||
assert(sound_instance != nil)
|
||||
|
||||
ma.sound_set_spatialization_enabled(sound_instance, b32(spec.spatialized))
|
||||
ma.sound_set_volume(sound_instance, spec.volume)
|
||||
ma.sound_set_position(sound_instance, expand_values(spec.position))
|
||||
ma.sound_set_min_distance(sound_instance, spec.distance.min)
|
||||
ma.sound_set_max_distance(sound_instance, spec.distance.max)
|
||||
|
||||
// TODO: SS - So many cool thingss available! Expose them.
|
||||
// 'ma.sound_set_looping', 'ma.sound_set_pitch' etc.
|
||||
|
||||
seek_result := ma.sound_seek_to_pcm_frame(sound_instance, 0)
|
||||
if seek_result != .SUCCESS {
|
||||
log.warnf("Failed to play sound instance! Could not seek to start. Result: %v", seek_result)
|
||||
return false
|
||||
}
|
||||
|
||||
start_result := ma.sound_start(sound_instance)
|
||||
if start_result != .SUCCESS {
|
||||
log.warnf("Failed to play sound instance! Result: %v", start_result)
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
return true
|
||||
}
|
||||
161
engine/sound.odin
Normal file
161
engine/sound.odin
Normal file
@@ -0,0 +1,161 @@
|
||||
package engine
|
||||
|
||||
import os "core:os/os2"
|
||||
import "core:container/queue"
|
||||
import "core:log"
|
||||
|
||||
import "ljud"
|
||||
|
||||
Sound :: struct {
|
||||
path: string, // Where I was loaded from.
|
||||
data: []u8,
|
||||
backend: ^Sound_Backend, // Backend-representation.
|
||||
}
|
||||
|
||||
Sound_Instance :: struct {
|
||||
parent: ^Sound, // What I'm an instance of.
|
||||
backend: Sound_Instance_Backend, // Backend-representation.
|
||||
|
||||
spatialized: bool,
|
||||
volume: f32, // 0..1
|
||||
position: [3]f32,
|
||||
min_distance, max_distance: f32,
|
||||
// ..
|
||||
}
|
||||
|
||||
Sound_Instance_ID :: distinct u32
|
||||
|
||||
load_sound_from_path :: proc(engine: ^Engine, path: string) -> (Sound, bool) {
|
||||
assert(engine != nil)
|
||||
|
||||
if len(path) == 0 {
|
||||
return {}, false
|
||||
}
|
||||
|
||||
base, ext := os.split_filename(path)
|
||||
log.info("Loading sound '%v' with extension '%v'.", base, ext)
|
||||
|
||||
// TODO: SS - Check extension.
|
||||
|
||||
data, err := os.read_entire_file(path, context.allocator)
|
||||
if err != nil {
|
||||
log.errorf("Failed to load sound! Could not read entire file. Error: %v", err)
|
||||
return {}, false
|
||||
}
|
||||
|
||||
if len(data) == 0 {
|
||||
log.errorf("Failed to load sound! No data.")
|
||||
return {}, false
|
||||
}
|
||||
|
||||
when AUDIO_ENGINE_LJUD {
|
||||
backend, ok := ljud.load_sound_from_data(&engine.backend, data, ext)
|
||||
if !ok {
|
||||
return {}, false
|
||||
}
|
||||
assert(backend != nil)
|
||||
|
||||
log.info("Loaded sound, returning")
|
||||
|
||||
return Sound {
|
||||
path = path,
|
||||
data = data,
|
||||
backend = backend,
|
||||
}, true
|
||||
}
|
||||
else when AUDIO_ENGINE_FMOD {
|
||||
|
||||
}
|
||||
else when AUDIO_ENGINE_WWISE {
|
||||
|
||||
}
|
||||
|
||||
log.info("No engine defined")
|
||||
|
||||
return {}, false
|
||||
}
|
||||
|
||||
unload_sound :: proc(engine: ^Engine, sound: ^Sound) {
|
||||
assert(engine != nil)
|
||||
assert(sound != nil)
|
||||
assert(sound.data != nil)
|
||||
|
||||
when AUDIO_ENGINE_LJUD {
|
||||
ljud.unload_sound(&engine.backend, sound.backend)
|
||||
}
|
||||
else when AUDIO_ENGINE_FMOD {
|
||||
|
||||
}
|
||||
else when AUDIO_ENGINE_WWISE {
|
||||
|
||||
}
|
||||
|
||||
delete(sound.data)
|
||||
sound.data = nil
|
||||
|
||||
free(sound.backend)
|
||||
sound.backend = nil
|
||||
}
|
||||
|
||||
create_sound_instance :: proc(engine: ^Engine, sound: ^Sound, bus: ^Bus) -> (^Sound_Instance, bool) {
|
||||
assert(engine != nil)
|
||||
assert(sound != nil)
|
||||
|
||||
// TODO: SS - Get an available instance from a pre-allocated buffer.
|
||||
|
||||
sound_instance_id, pop_ok := queue.pop_front_safe(&engine.sound_instance_ids)
|
||||
if !pop_ok {
|
||||
log.warnf("No free sound-instance available.")
|
||||
return {}, false
|
||||
}
|
||||
|
||||
sound_instance := &engine.sound_instances[sound_instance_id]
|
||||
assert(sound_instance != nil)
|
||||
|
||||
sound_instance.parent = sound
|
||||
sound_instance.backend = {}
|
||||
|
||||
instance_ok: bool
|
||||
when AUDIO_ENGINE_LJUD {
|
||||
instance_ok = ljud.init_sound_instance(&engine.backend, bus != nil ? bus.backend : nil, sound.backend, &sound_instance.backend)
|
||||
}
|
||||
else when AUDIO_ENGINE_FMOD {
|
||||
|
||||
}
|
||||
else when AUDIO_ENGINE_WWISE {
|
||||
|
||||
}
|
||||
|
||||
if !instance_ok {
|
||||
log.errorf("Failed to create a Sound-Instance for sound '%v'.", sound.path)
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return sound_instance, true
|
||||
}
|
||||
|
||||
play_sound_instance :: proc(engine: ^Engine, sound_instance: ^Sound_Instance) -> bool {
|
||||
assert(engine != nil)
|
||||
assert(sound_instance != nil)
|
||||
|
||||
when AUDIO_ENGINE_LJUD {
|
||||
spec := ljud.Play_Sound_Instance_Spec {
|
||||
spatialized = sound_instance.spatialized,
|
||||
volume = sound_instance.volume,
|
||||
|
||||
position = sound_instance.position,
|
||||
distance = {
|
||||
min = sound_instance.min_distance,
|
||||
max = sound_instance.max_distance,
|
||||
},
|
||||
}
|
||||
return ljud.play_sound_instance(&engine.backend, &sound_instance.backend, spec)
|
||||
}
|
||||
else when AUDIO_ENGINE_FMOD {
|
||||
}
|
||||
else when AUDIO_ENGINE_WWISE {
|
||||
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user