Backlog/engine/audio/src/sound_engine.zig

182 lines
5.7 KiB
Zig

// Implemented via miniaudio
const std = @import("std");
const core = @import("core");
const assets = @import("assets");
const Name = core.Name;
const AutoHashMapUnmanaged = std.AutoHashMapUnmanaged;
const tracy = core.tracy;
const ma = @import("miniaudio").c;
pub fn sound_log(comptime fmt: []const u8, args: anytype) void {
core.printInner(core.defaultHighlight, "[SOUND ]: " ++ fmt ++ "\n", args);
}
pub fn sound_logs(comptime fmt: []const u8) void {
core.printInner(core.defaultHighlight, "[SOUND ]: " ++ fmt ++ "\n", .{});
}
pub fn sound_err(comptime fmt: []const u8, args: anytype) void {
core.printInner(core.errorHighlight, "[SOUND ]: ERROR!! " ++ fmt ++ "\n", args);
}
pub fn sound_errs(comptime fmt: []const u8) void {
core.printInner(core.errorHighlight, "[SOUND ]: ERROR!!" ++ fmt ++ "\n", .{});
}
pub const SoundLoader = struct {
pub var LoaderInterfaceVTable: assets.AssetLoaderInterface = assets.AssetLoaderInterface.from("Sound", @This());
engine: *SoundEngine,
pub fn init(engine: *SoundEngine) @This() {
return @This(){
.engine = engine,
};
}
pub fn discardAll(self: *@This()) void {
_ = self;
}
pub fn destroy(self: *@This(), allocator: std.mem.Allocator) void {
allocator.destroy(self);
}
// unfortunately this one is blocking
pub fn loadAsset(self: *@This(), assetRef: assets.AssetRef, properties: ?assets.AssetPropertiesBag) assets.AssetLoaderError!void {
core.engine_log("loading sound asset {s}", .{properties.?.path});
var name = assetRef.name;
self.engine.loadSound(&name, properties.?.path, .{
.volume = properties.?.soundVolume,
}) catch {
core.engine_log("unable to load sound {s}", .{name.utf8()});
};
}
};
fn ma_res(value: anytype) !void {
if (value != ma.MA_SUCCESS) {
core.engine_err("miniaudio error value: {d}", .{value});
return error.MA_ERROR;
}
}
// On init, SoundEngine will spawn a
pub const SoundEngine = struct {
pub var NeonObjectTable: core.EngineObjectVTable = core.EngineObjectVTable.from(@This(), "audio.SoundEngine");
engine: *ma.ma_engine,
sounds: AutoHashMapUnmanaged(u32, *ma.ma_sound),
decoders: AutoHashMapUnmanaged(u32, *ma.ma_decoder),
allocator: std.mem.Allocator,
volume: f32 = 1.0,
pub fn init(self: *@This(), allocator: std.mem.Allocator, first: bool) !void {
if (!first)
return;
self.* = @This(){
.engine = allocator.create(ma.ma_engine) catch unreachable,
.sounds = .{},
.decoders = .{},
.allocator = allocator,
};
_ = ma.ma_engine_init(null, self.engine);
}
pub fn shutdown(self: *@This()) void {
_ = self;
}
pub fn loadSound(
self: *@This(),
soundName: *core.Name,
fileName: []const u8,
soundParams: struct {
looping: bool = false,
volume: f32 = 1.0,
},
) !void {
const sound = try self.allocator.create(ma.ma_sound);
errdefer self.allocator.destroy(sound);
const engineSoundBytes = try core.fs().loadFile(fileName);
const bytes = engineSoundBytes.bytes;
// Create a decoder from the loaded bytes
const decoder = try self.allocator.create(ma.ma_decoder);
errdefer self.allocator.destroy(decoder);
const res_decoder = ma.ma_decoder_init_memory(bytes.ptr, bytes.len, null, decoder);
if (res_decoder != ma.MA_SUCCESS) {
sound_err("failed to initialize decoder for sound: {s}", .{soundName.utf8()});
return error.MiniAudioError;
}
const res = ma.ma_sound_init_from_data_source(self.engine, decoder, 0, null, sound);
if (res != ma.MA_SUCCESS) {
sound_err("tried loading sound: {s} failed", .{soundName.utf8()});
return error.MiniAudioError;
}
try self.sounds.put(self.allocator, soundName.handle(), sound);
try self.decoders.put(self.allocator, soundName.handle(), decoder);
ma.ma_sound_set_volume(sound, soundParams.volume);
}
pub fn playSound(self: *@This(), soundName: *core.Name) !void {
const maybeSound = self.sounds.get(soundName.handle());
if (maybeSound == null)
return error.NoSoundError;
const sound = maybeSound.?;
if (ma.ma_sound_start(sound) != ma.MA_SUCCESS)
sound_err("unable to start sound: {s}", .{soundName.utf8()});
}
pub fn setVolume(self: *@This(), volume: f32) void {
self.volume = volume;
_ = ma.ma_engine_set_volume(self.engine, volume);
}
pub fn stopSound(self: *@This(), soundName: *core.Name) void {
const sound = self.sounds.get(soundName.handle()).?;
if (ma.ma_sound_stop(sound) != ma.MA_SUCCESS) {
//
}
}
pub fn deinit(self: *@This()) void {
var iter = self.sounds.iterator();
while (iter.next()) |sound| {
self.allocator.destroy(sound.value_ptr.*);
}
self.sounds.deinit(self.allocator);
var decoder_iter = self.decoders.iterator();
while (decoder_iter.next()) |decoder| {
_ = ma.ma_decoder_uninit(decoder.value_ptr.*);
self.allocator.destroy(decoder.value_ptr.*);
}
self.decoders.deinit(self.allocator);
ma.ma_engine_uninit(self.engine);
self.allocator.destroy(self.engine);
self.allocator.destroy(self);
}
pub fn tick(self: *@This(), deltaTime: f64) void {
var z = tracy.ZoneNC(@src(), "audio engine tick", 0xABBADD);
defer z.End();
_ = self;
_ = deltaTime;
}
};