From 88badc611b3044455cb9563e44661eb7090e60e0 Mon Sep 17 00:00:00 2001 From: Logickin-Lambda Date: Mon, 25 Aug 2025 15:35:07 +0800 Subject: [PATCH 01/20] - Start The Decoder with DataConverter Previously, I didn't carefully read the original implementation and how zaudio bridges the function, created a bunch of mess. With a bit more studies, I think I have more confidence on porting Decoder Type into zaudio, starting with the unimplemented type for the Decoder so that I don't get lost in the midway of creating the Decoder type. --- src/zaudio.c | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/zaudio.c b/src/zaudio.c index 4e7435c..422dd09 100644 --- a/src/zaudio.c +++ b/src/zaudio.c @@ -675,3 +675,42 @@ void WA_ma_sound_get_velocity(const ma_sound* sound, ma_vec3f* vout) { *vout = ma_sound_get_velocity(sound); } //-------------------------------------------------------------------------------------------------- +// ma_data_converter, required for decoder +void zaudioDataConverterConfigInit( + ma_format formatIn, + ma_format formatOut, + ma_uint32 channelsIn, + ma_uint32 channelsOut, + ma_uint32 sampleRateIn, + ma_uint32 sampleRateOut, + ma_data_converter_config* out_config, +){ + assert(out_config != NULL); + *out_config = ma_data_converter_config_init(formatIn, formatOut, channelsIn, channelsOut, sampleRateIn, sampleRateOut); +} + +// ma_data_converter +ma_result zaudioDataConverterCreate( + ma_data_converter_config* config, + ma_data_converter** out_handle +){ + assert(config != NULL && out_handle != NULL); + *out_handle = s_mem.onMalloc(sizeof(ma_data_converter), s_mem.pUserData); + ma_result res = ma_data_converter_init(config, &s_mem, *out_handle); + if (res != MA_SUCCESS){ + s_mem.onFree(*out_handle, s_mem.pUserData); + *out_handle = NULL; + } + return res; +} + +void zaudioDataConverterDestroy( + ma_data_converter* handle +){ + assert(handle != NULL); + ma_data_converter_uninit(handle, &s_mem); + s_mem.onFree(handle, s_mem.pUserData); +} + + +//-------------------------------------------------------------------------------------------------- From 9df0f3c9ea0802c23b2d052bf41c52f30e27e45d Mon Sep 17 00:00:00 2001 From: Logickin-Lambda Date: Mon, 25 Aug 2025 17:57:14 +0800 Subject: [PATCH 02/20] - Ported DataConverter The coding part of DataConverter is now done, but this will require testing; nonetheless, now we have the DataConverter and Resampler.Config, building the decoder should be less difficult. I will test all the type once I have done decoder. --- src/zaudio.zig | 132 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) diff --git a/src/zaudio.zig b/src/zaudio.zig index 95591c7..49f2b44 100644 --- a/src/zaudio.zig +++ b/src/zaudio.zig @@ -267,6 +267,8 @@ pub const PanMode = enum(u32) { pan, }; +pub const DetherMode = enum(u32) { none, rectangle, triangle }; + pub const AttenuationModel = enum(u32) { none, inverse, @@ -736,6 +738,136 @@ pub const AudioBuffer = opaque { return @as(*DataSource, @ptrCast(audio_buffer)); } }; +//-------------------------------------------------------------------------------------------------- +// +// Data Converter +// +//-------------------------------------------------------------------------------------------------- +pub const DataConverter = opaque { + pub const destroy = zaudioDataConverterDestroy; + extern fn zaudioDataConverterDestroy(handle: DataConverter) void; + + pub fn create(config: Config) Error!*DataConverter { + var handle: ?*DataConverter = null; + try maybeError(zaudioDataConverterCreate(&config, &handle)); + return handle.?; + } + extern fn zaudioDataConverterCreate(config: Config, handle: ?*?*DataConverter) Result; + + pub fn processPcmFrames( + converter: *DataConverter, + frames_in: *anyopaque, + frame_count_in: *u64, + frame_out: *anyopaque, + frame_count_out: *u64, + ) Error!void { + try maybeError(ma_data_converter_process_pcm_frames( + converter, + frames_in, + frame_count_in, + frame_out, + frame_count_out, + )); + } + extern fn ma_data_converter_process_pcm_frames(converter: *DataConverter, frames_in: *anyopaque, frame_count_in: *u64, frame_out: *anyopaque, frame_count_out: *u64) Result; + + pub fn setRate(converter: *DataConverter, sample_rate_in: u32, sample_rate_out: u32) Error!void { + try maybeError(setRate(converter, sample_rate_in, sample_rate_out)); + } + extern fn ma_data_converter_set_rate(converter: *DataConverter, sample_rate_in: u32, sample_rate_out: u32) Result; + + pub fn setRateRatio(converter: *DataConverter, ratio_in_out: f32) Error!void { + try maybeError(ma_data_converter_set_rate_ratio(converter, ratio_in_out)); + } + extern fn ma_data_converter_set_rate_ratio(converter: *DataConverter, ratioInOut: f32) Result; + + pub fn getInputLatency(converter: *DataConverter) u64 { + ma_data_converter_get_input_latency(converter); + } + extern fn ma_data_converter_get_input_latency(converter: *DataConverter) u64; + + pub fn getOutPutLatency(converter: *DataConverter) u64 { + ma_data_converter_get_output_latency(converter); + } + extern fn ma_data_converter_get_output_latency(converter: *DataConverter) u64; + + pub fn getRequiredInputFrameCount(converter: *DataConverter, output_frame_count: u64) Error!u64 { + var input_frame_count: u64 = undefined; + try maybeError(ma_data_converter_get_required_input_frame_count(converter, output_frame_count, &input_frame_count)); + return input_frame_count; + } + extern fn ma_data_converter_get_required_input_frame_count(converter: *DataConverter, output_frame_count: u64, input_frame_count: *u64) Result; + + pub fn getExpectedOutputFrameCount(converter: *DataConverter, input_frame_count: u64) Error!u64 { + var output_frame_count: u64 = undefined; + try maybeError(ma_data_converter_get_expected_output_frame_count(converter, input_frame_count, &output_frame_count)); + return output_frame_count; + } + extern fn ma_data_converter_get_expected_output_frame_count(converter: *DataConverter, input_frame_count: u64, output_frame_count: *u64) Result; + + pub fn getInputChannelMap(converter: *DataConverter, channel_map: *Channel, channel_map_cap: usize) Error!void { + try maybeError(ma_data_converter_get_input_channel_map(converter, channel_map, channel_map_cap)); + } + extern fn ma_data_converter_get_input_channel_map(converter: *DataConverter, channel_map: *Channel, channel_map_cap: usize) Result; + + pub fn getOutputChannelMap(converter: *DataConverter, channel_map: *Channel, channel_map_cap: usize) Error!void { + try maybeError(ma_data_converter_get_output_channel_map(converter, channel_map, channel_map_cap)); + } + extern fn ma_data_converter_get_output_channel_map(converter: *DataConverter, channel_map: *Channel, channel_map_cap: usize) Result; + + pub fn reset(converter: *DataConverter) Error!void { + try maybeError(ma_data_converter_reset(converter)); + } + extern fn ma_data_converter_reset(converter: *DataConverter) Result; + + pub const ExecutionPath = enum(u32) { + passthrough, + format_only, + channels_only, + resample_only, + resample_first, + channels_first, + }; + + pub const Config = extern struct { + format_in: Format, + format_out: Format, + channels_in: u32, + channels_out: u32, + sample_rate_in: u32, + sample_rate_out: u32, + channel_map_in: *Channel, + channel_map_out: *Channel, + dither_mode: DetherMode, + channel_mix_mode: ChannelMixMode, + calculate_LFE_from_spatial_channels: u32, + channel_weighs_io_ptr: [*][*]f32, // [in][out]. Only used when mixingMode is set to ma_channel_mix_mode_custom_weights. + allow_dynamic_sample_rate: u32, + resampling: Resampler.Config, + }; +}; + +//-------------------------------------------------------------------------------------------------- +// +// Resampler (Incomplete, but since many of the function requires the type, especially the config, +// we eventually need an opaque function to reduce code repetition) +// +//-------------------------------------------------------------------------------------------------- +pub const Resampler = opaque { + pub const Config = extern struct { + format: Format, + channels: u32, + sample_rate_in: u32, + sample_rate_out: u32, + algorithm: ResampleAlgorithm, + backend_vtable: ?*anyopaque, // TODO: Should be `*ma_resampling_backend_vtable` (custom resamplers). + backend_user_data: ?*anyopaque, + linear: extern struct { + lpf_order: u32, + }, + }; +}; + //-------------------------------------------------------------------------------------------------- // // Node From cb48c0690301be783d5394f5c53d71a30470ca9e Mon Sep 17 00:00:00 2001 From: Logickin-Lambda Date: Tue, 26 Aug 2025 16:15:21 +0800 Subject: [PATCH 03/20] - Daily Commit There is nothing interesting in this commit because I am only half way through the Decoder type. --- src/zaudio.c | 15 ++++++++- src/zaudio.zig | 91 ++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 103 insertions(+), 3 deletions(-) diff --git a/src/zaudio.c b/src/zaudio.c index 422dd09..094cb35 100644 --- a/src/zaudio.c +++ b/src/zaudio.c @@ -683,7 +683,7 @@ void zaudioDataConverterConfigInit( ma_uint32 channelsOut, ma_uint32 sampleRateIn, ma_uint32 sampleRateOut, - ma_data_converter_config* out_config, + ma_data_converter_config* out_config ){ assert(out_config != NULL); *out_config = ma_data_converter_config_init(formatIn, formatOut, channelsIn, channelsOut, sampleRateIn, sampleRateOut); @@ -712,5 +712,18 @@ void zaudioDataConverterDestroy( s_mem.onFree(handle, s_mem.pUserData); } +//-------------------------------------------------------------------------------------------------- +// ma_decoder +void ma_decoder_config_init( + ma_format outputFormat, + ma_uint32 outputChannels, + ma_uint32 outputSampleRate, + ma_decoder_config* out_config +){ + assert(out_config != NULL); + *out_config = ma_decoder_config_init(outputFormat, outputChannels, outputSampleRate); +} + +// There are more variants in decoder than all other types, //-------------------------------------------------------------------------------------------------- diff --git a/src/zaudio.zig b/src/zaudio.zig index 49f2b44..1781e08 100644 --- a/src/zaudio.zig +++ b/src/zaudio.zig @@ -267,7 +267,11 @@ pub const PanMode = enum(u32) { pan, }; -pub const DetherMode = enum(u32) { none, rectangle, triangle }; +pub const DitherMode = enum(u32) { + none, + rectangle, + triangle, +}; pub const AttenuationModel = enum(u32) { none, @@ -290,6 +294,14 @@ pub const Format = enum(u32) { float32, }; +pub const EncodingFormat = enum(u32) { + unknown, + wav, + flac, + mp3, + vorbis, +}; + pub const PerformanceProfile = enum(u32) { low_latency, conservative, @@ -451,6 +463,12 @@ pub const Vfs = extern struct { on_info: ?*const fn (self: *Vfs, handle: FileHandle, info: *FileInfo) callconv(.C) Result, }; +// these functions were originally located under vfs, but they seems to have no correlation to the type, +// while decoder require such type in order to make it work, so I will temporary locate these functions in here: +pub const readProc = fn (user_data: ?*anyopaque, buffer_out: ?*anyopaque, bytes_to_read: usize, bytes_read: *usize) callconv(.C) Result; +pub const seekProc = fn (user_data: ?*anyopaque, offset: i64, origin: Vfs.SeekOrigin) callconv(.C) Result; +pub const tellProc = fn (user_data: ?*anyopaque, cursor: ?*i64) callconv(.C) Result; + pub const Context = opaque { // TODO: Add methods. }; @@ -738,6 +756,7 @@ pub const AudioBuffer = opaque { return @as(*DataSource, @ptrCast(audio_buffer)); } }; + //-------------------------------------------------------------------------------------------------- // // Data Converter @@ -838,7 +857,7 @@ pub const DataConverter = opaque { sample_rate_out: u32, channel_map_in: *Channel, channel_map_out: *Channel, - dither_mode: DetherMode, + dither_mode: DitherMode, channel_mix_mode: ChannelMixMode, calculate_LFE_from_spatial_channels: u32, channel_weighs_io_ptr: [*][*]f32, // [in][out]. Only used when mixingMode is set to ma_channel_mix_mode_custom_weights. @@ -847,6 +866,74 @@ pub const DataConverter = opaque { }; }; +//-------------------------------------------------------------------------------------------------- +// +// Decoder +// +//-------------------------------------------------------------------------------------------------- +pub const Decoder = opaque { + + // here are the init functions, but after observed other examples + // I will skip the _w variant until there is a solution to handle wchar_t + + pub const VTable = extern struct { + onInit: ?*const fn ( + user_data: *anyopaque, + on_read: readProc, + on_seek: seekProc, + on_tell: tellProc, + read_seek_tell_user_data: *anyopaque, + config: *const BackendConfig, + allocation_callbacks: AllocationCallbacks, + backend: *?*?DataSource, + ) callconv(.C) Result, + + onInitFile: ?*const fn ( + user_data: *anyopaque, + file_path: [:0]const u8, + config: BackendConfig, + allocation_callbacks: AllocationCallbacks, + backend: *?*?DataSource, + ) callconv(.C) Result, + + onInitMemory: ?*const fn ( + user_data: *anyopaque, + data: *const anyopaque, + data_size: usize, + config: BackendConfig, + allocation_callbacks: AllocationCallbacks, + backend: *?*?DataSource, + ) callconv(.C) Result, + + onUninit: ?*const fn ( + user_data: *anyopaque, + backend: *?DataSource, + allocation_callbacks: AllocationCallbacks, + ) callconv(.C) void, + }; + + pub const Config = extern struct { + format: Format, + channels: u32, + sample_rate: u32, + channel_map: *Channel, + channel_mix_mode: ChannelMixMode, + dither_mode: DitherMode, + resampling: Resampler.Config, + allocation_callbacks: AllocationCallbacks, + encoding_format: EncodingFormat, + seek_point_count: u32, + custom_backend_vtable: ?*?*VTable, + custom_backend_count: u32, + custom_backend_user_data: *anyopaque, + }; + + pub const BackendConfig = extern struct { + preferred_format: Format, + seek_point_count: u32, + }; +}; + //-------------------------------------------------------------------------------------------------- // // Resampler (Incomplete, but since many of the function requires the type, especially the config, From f5d18809fd2cc1b3ab4c75e8abcd98006e1c9d29 Mon Sep 17 00:00:00 2001 From: Logickin-Lambda Date: Wed, 27 Aug 2025 11:22:29 +0800 Subject: [PATCH 04/20] - Completed the C part of the zaudio The bridge has been finished for Decoder, but since the zaudio doesn't handle the wchar_t type from the original C implementation, I am not going to port any functions that involve the type unless there are a better one size fits all solutions for all other functions requiring wchar_t as well. --- src/zaudio.c | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/src/zaudio.c b/src/zaudio.c index 094cb35..ff18bc4 100644 --- a/src/zaudio.c +++ b/src/zaudio.c @@ -724,6 +724,71 @@ void ma_decoder_config_init( *out_config = ma_decoder_config_init(outputFormat, outputChannels, outputSampleRate); } -// There are more variants in decoder than all other types, +// There are more variants in decoder than all other types, but I will only handle the non-w type +// since the other type with the similar file access method don't include such type as well +// unless there is a good solution for wchar_t. +ma_result zaudioDecoderCreate( + ma_decoder_read_proc on_read, + ma_decoder_seek_proc on_seek, + void* user_data, + const ma_decoder_config* config, + ma_decoder** out_handle +){ + assert(user_data!= NULL && config != NULL && out_handle != NULL); + *out_handle = s_mem.onMalloc(sizeof(ma_decoder), s_mem.pUserData); + ma_result res = ma_decoder_init(on_read, on_seek, user_data, config, out_handle); + if (res != MA_SUCCESS){ + s_mem.onFree(*out_handle, s_mem.pUserData); + *out_handle = NULL; + } + return res; +} + +ma_result zaudioDecoderCreateFromMemory( + const void* data, + size_t data_size, + const ma_decoder_config* config, + ma_decoder** out_handle +){ + assert(data != NULL && config != NULL && decoder != NULL); + *out_handle = s_mem.onMalloc(sizeof(ma_decoder), s_mem.pUserData); + ma_result res = ma_decoder_init_memory(data, data_size, config, out_handle); + if (res != MA_SUCCESS){ + s_mem.onFree(*out_handle, s_mem.pUserData); + *out_handle = NULL; + } + return res; +} + +ma_result zaudioDecoderCreateVfs( + ma_vfs* vfs, + const char* file_path, + const ma_decoder_config* config, + ma_decoder** out_handle +){ + assert(vfs != NULL && file_path != NULL && config != NULL && out_handle != NULL); + *out_handle = s_mem.onMalloc(sizeof(ma_decoder), s_mem.pUserData); + ma_result res = ma_decoder_init_vfs(vfs, file_path, config, out_handle); + if (res != MA_SUCCESS){ + s_mem.onFree(*out_handle, s_mem.pUserData); + *out_handle = NULL; + } + return res; +} + +ma_result zaudioDecoderFile( + const char* file_path, + const ma_decoder_config* config, + ma_decoder** out_handle +){ + assert(file_path != NULL && config != NULL, out_handle != NULL); + *out_handle = s_mem.onMalloc(sizeof(ma_decoder), s_mem.pUserData); + ma_result res = ma_decoder_init_file(file_path, config, out_handle); + if (res != MA_SUCCESS){ + s_mem.onFree(*out_handle, s_mem.pUserData); + *out_handle = NULL; + } + return res; +} //-------------------------------------------------------------------------------------------------- From 6b1ef442baefc0aa800f7e22935da781e4158fde Mon Sep 17 00:00:00 2001 From: Logickin-Lambda Date: Wed, 27 Aug 2025 17:47:41 +0800 Subject: [PATCH 05/20] - All Required Coding is Done All of the coding related to the decoder is done, but clearly, there will be many errors in the code especially the pointer which I have missed out a bunch of optionals. Thus, I will do a series of testing to ensure everything works fine, and after all, we will finally able to load ourselves some samples at the low level using zaudio. --- src/zaudio.c | 16 ++++++++--- src/zaudio.zig | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 4 deletions(-) diff --git a/src/zaudio.c b/src/zaudio.c index ff18bc4..8ec88d2 100644 --- a/src/zaudio.c +++ b/src/zaudio.c @@ -713,8 +713,8 @@ void zaudioDataConverterDestroy( } //-------------------------------------------------------------------------------------------------- -// ma_decoder -void ma_decoder_config_init( +// ma_decoder zaudioDataConverterConfigInit +void zaudioDecoderConfigInit( ma_format outputFormat, ma_uint32 outputChannels, ma_uint32 outputSampleRate, @@ -760,7 +760,7 @@ ma_result zaudioDecoderCreateFromMemory( return res; } -ma_result zaudioDecoderCreateVfs( +ma_result zaudioDecoderCreateFromVfs( ma_vfs* vfs, const char* file_path, const ma_decoder_config* config, @@ -776,7 +776,7 @@ ma_result zaudioDecoderCreateVfs( return res; } -ma_result zaudioDecoderFile( +ma_result zaudioDecoderCreateFromFile( const char* file_path, const ma_decoder_config* config, ma_decoder** out_handle @@ -791,4 +791,12 @@ ma_result zaudioDecoderFile( return res; } +ma_result zaudioDecoderDestroy( + ma_decoder* handle +){ + assert(handle != NULL); + ma_data_converter_uninit(handle, &s_mem); + s_mem.onFree(handle, s_mem.pUserData); +} + //-------------------------------------------------------------------------------------------------- diff --git a/src/zaudio.zig b/src/zaudio.zig index 1781e08..d4178aa 100644 --- a/src/zaudio.zig +++ b/src/zaudio.zig @@ -872,9 +872,77 @@ pub const DataConverter = opaque { // //-------------------------------------------------------------------------------------------------- pub const Decoder = opaque { + pub const destroy = zaudioDecoderDestroy; + extern fn zaudioDecoderDestroy(handle: *Decoder) void; // here are the init functions, but after observed other examples // I will skip the _w variant until there is a solution to handle wchar_t + pub fn create(decoder_on_read: decoderReadProc, decoder_on_seek: decoderSeekProc, user_data: *anyopaque, config: Config) Error!*Decoder { + var handle: ?*Decoder = null; + try maybeError(zaudioDecoderCreate(decoder_on_read, decoder_on_seek, user_data, &config, &handle)); + return handle.?; + } + extern fn zaudioDecoderCreate(on_read: decoderReadProc, on_seek: decoderSeekProc, user_data: *anyopaque, config: *const Config, out_handle: *?*?Decoder) Result; + + pub fn createFromMemory(data: ?*const anyopaque, data_size: usize, config: Config) Error!*Decoder { + var handle: ?*Decoder = null; + try maybeError(zaudioDecoderCreateFromMemory(data, data_size, &config, &handle)); + return handle.?; + } + extern fn zaudioDecoderCreateFromMemory(data: ?*const anyopaque, data_size: usize, config: *const Config, out_handle: ?*?*Decoder) Result; + + pub fn createFromVfs(vfs: *Vfs, file_path: []const u8, config: Config) Error!*Decoder { + var handle: ?*Decoder = null; + try maybeError(zaudioDecoderCreateFromVfs(vfs, file_path, &config, &handle)); + return handle.?; + } + extern fn zaudioDecoderCreateFromVfs(vfs: *Vfs, file_path: []const u8, config: *const Config, out_handle: ?*?*Decoder) Result; + + pub fn createFromFile(file_path: []const u8, config: Config) Error!*Decoder { + var handle: ?*Decoder = null; + try maybeError(zaudioDecoderCreateFromFile(file_path, &config, &handle)); + return handle.?; + } + extern fn zaudioDecoderCreateFromFile(file_path: []const u8, config: *const Config, out_handle: ?*?*Decoder) Result; + + // The remaing related functions for manipulate the samples: + pub fn readPCMFrame(decoder: *Decoder, frames_count: u64, frames_read: *u64) Error!*anyopaque { + var frame_out: anyopaque = undefined; + try maybeError(ma_decoder_read_pcm_frame(decoder, &frame_out, frames_count, frames_read)); + return frame_out; + } + extern fn ma_decoder_read_pcm_frame(decoder: *Decoder, frame_out: *anyopaque, frames_count: u64, frames_read: ?*u64) Result; + + pub fn seekToPCMFrame(decoder: *Decoder, frame_index: u64) Error!void { + try maybeError(ma_decoder_seek_to_pcm_frame(decoder, frame_index)); + } + extern fn ma_decoder_seek_to_pcm_frame(decoder: *Decoder, frame_index: u64) Result; + + pub fn getDataFormat(decoder: *Decoder, format: *Format, channels: *u32, sample_rate: *u32, channel_map: [*]Channel, channel_map_cap: usize) Error!void { + try maybeError(ma_decoder_get_data_format(decoder, format, channels, sample_rate, channel_map, channel_map_cap)); + } + extern fn ma_decoder_get_data_format(decoder: *Decoder, format: *Format, channels: *u32, sample_rate: *u32, channel_map: [*]Channel, channel_map_cap: usize) Result; + + pub fn getCursorInPCMFrames(decoder: *Decoder) Error!u64 { + var cursor: *u64 = undefined; + try maybeError(ma_decoder_get_cursor_in_pcm_frames(decoder, &cursor)); + return cursor.*; + } + extern fn ma_decoder_get_cursor_in_pcm_frames(decoder: *Decoder, cursor: *u64) Result; + + pub fn getLengthInPCMFrames(decoder: *Decoder) Error!u64 { + var length: *u64 = undefined; + try maybeError(ma_decoder_get_length_in_pcm_frames(decoder, &length)); + return length.*; + } + extern fn ma_decoder_get_length_in_pcm_frames(decoder: *Decoder, length: *u64) Result; + + pub fn getAvailableFrames(decoder: *Decoder) Error!u64 { + var available_frames: *u64 = undefined; + try maybeError(ma_decoder_get_available_frames(decoder, &available_frames)); + return available_frames.*; + } + extern fn ma_decoder_get_available_frames(decoder: *Decoder, available_frames: *u64) Result; pub const VTable = extern struct { onInit: ?*const fn ( @@ -934,6 +1002,10 @@ pub const Decoder = opaque { }; }; +pub const decoderReadProc = fn (decoder: *Decoder, buffer_out: *anyopaque, bytes_to_read: usize, bytes_read: *usize) callconv(.C) Result; +pub const decoderSeekProc = fn (decoder: *Decoder, byte_offset: i64, origin: Vfs.SeekOrigin) Result; +pub const decoderTellProc = fn (decoder: *Decoder, cursor: *i64) Result; + //-------------------------------------------------------------------------------------------------- // // Resampler (Incomplete, but since many of the function requires the type, especially the config, From 1a2ea91e06a14eac7c70881331585ff88f020b66 Mon Sep 17 00:00:00 2001 From: Logickin-Lambda Date: Thu, 28 Aug 2025 10:50:48 +0800 Subject: [PATCH 06/20] - Cleared The Compile Time Errors, but... The library is finally successfully compiled, but clearly this is not enough because we need to test if the library really works in practice; thus, we are going to do some test to ensure the correct of the decoders. --- src/zaudio.c | 14 ++++++------ src/zaudio.zig | 58 ++++++++++++++++++++++++-------------------------- 2 files changed, 35 insertions(+), 37 deletions(-) diff --git a/src/zaudio.c b/src/zaudio.c index 8ec88d2..e4f9e37 100644 --- a/src/zaudio.c +++ b/src/zaudio.c @@ -736,7 +736,7 @@ ma_result zaudioDecoderCreate( ){ assert(user_data!= NULL && config != NULL && out_handle != NULL); *out_handle = s_mem.onMalloc(sizeof(ma_decoder), s_mem.pUserData); - ma_result res = ma_decoder_init(on_read, on_seek, user_data, config, out_handle); + ma_result res = ma_decoder_init(on_read, on_seek, user_data, config, *out_handle); if (res != MA_SUCCESS){ s_mem.onFree(*out_handle, s_mem.pUserData); *out_handle = NULL; @@ -750,9 +750,9 @@ ma_result zaudioDecoderCreateFromMemory( const ma_decoder_config* config, ma_decoder** out_handle ){ - assert(data != NULL && config != NULL && decoder != NULL); + assert(data != NULL && config != NULL && out_handle != NULL); *out_handle = s_mem.onMalloc(sizeof(ma_decoder), s_mem.pUserData); - ma_result res = ma_decoder_init_memory(data, data_size, config, out_handle); + ma_result res = ma_decoder_init_memory(data, data_size, config, *out_handle); if (res != MA_SUCCESS){ s_mem.onFree(*out_handle, s_mem.pUserData); *out_handle = NULL; @@ -768,7 +768,7 @@ ma_result zaudioDecoderCreateFromVfs( ){ assert(vfs != NULL && file_path != NULL && config != NULL && out_handle != NULL); *out_handle = s_mem.onMalloc(sizeof(ma_decoder), s_mem.pUserData); - ma_result res = ma_decoder_init_vfs(vfs, file_path, config, out_handle); + ma_result res = ma_decoder_init_vfs(vfs, file_path, config, *out_handle); if (res != MA_SUCCESS){ s_mem.onFree(*out_handle, s_mem.pUserData); *out_handle = NULL; @@ -781,9 +781,9 @@ ma_result zaudioDecoderCreateFromFile( const ma_decoder_config* config, ma_decoder** out_handle ){ - assert(file_path != NULL && config != NULL, out_handle != NULL); + assert(file_path != NULL && config != NULL && out_handle != NULL); *out_handle = s_mem.onMalloc(sizeof(ma_decoder), s_mem.pUserData); - ma_result res = ma_decoder_init_file(file_path, config, out_handle); + ma_result res = ma_decoder_init_file(file_path, config, *out_handle); if (res != MA_SUCCESS){ s_mem.onFree(*out_handle, s_mem.pUserData); *out_handle = NULL; @@ -795,7 +795,7 @@ ma_result zaudioDecoderDestroy( ma_decoder* handle ){ assert(handle != NULL); - ma_data_converter_uninit(handle, &s_mem); + ma_decoder_uninit(handle); s_mem.onFree(handle, s_mem.pUserData); } diff --git a/src/zaudio.zig b/src/zaudio.zig index d4178aa..dd38209 100644 --- a/src/zaudio.zig +++ b/src/zaudio.zig @@ -465,9 +465,9 @@ pub const Vfs = extern struct { // these functions were originally located under vfs, but they seems to have no correlation to the type, // while decoder require such type in order to make it work, so I will temporary locate these functions in here: -pub const readProc = fn (user_data: ?*anyopaque, buffer_out: ?*anyopaque, bytes_to_read: usize, bytes_read: *usize) callconv(.C) Result; -pub const seekProc = fn (user_data: ?*anyopaque, offset: i64, origin: Vfs.SeekOrigin) callconv(.C) Result; -pub const tellProc = fn (user_data: ?*anyopaque, cursor: ?*i64) callconv(.C) Result; +pub const readProc = *const fn (user_data: ?*anyopaque, buffer_out: ?*anyopaque, bytes_to_read: usize, bytes_read: *usize) callconv(.C) Result; +pub const seekProc = *const fn (user_data: ?*anyopaque, offset: i64, origin: Vfs.SeekOrigin) callconv(.C) Result; +pub const tellProc = *const fn (user_data: ?*anyopaque, cursor: ?*i64) callconv(.C) Result; pub const Context = opaque { // TODO: Add methods. @@ -764,14 +764,14 @@ pub const AudioBuffer = opaque { //-------------------------------------------------------------------------------------------------- pub const DataConverter = opaque { pub const destroy = zaudioDataConverterDestroy; - extern fn zaudioDataConverterDestroy(handle: DataConverter) void; + extern fn zaudioDataConverterDestroy(handle: *DataConverter) void; pub fn create(config: Config) Error!*DataConverter { var handle: ?*DataConverter = null; try maybeError(zaudioDataConverterCreate(&config, &handle)); return handle.?; } - extern fn zaudioDataConverterCreate(config: Config, handle: ?*?*DataConverter) Result; + extern fn zaudioDataConverterCreate(config: *const Config, handle: ?*?*DataConverter) Result; pub fn processPcmFrames( converter: *DataConverter, @@ -791,7 +791,7 @@ pub const DataConverter = opaque { extern fn ma_data_converter_process_pcm_frames(converter: *DataConverter, frames_in: *anyopaque, frame_count_in: *u64, frame_out: *anyopaque, frame_count_out: *u64) Result; pub fn setRate(converter: *DataConverter, sample_rate_in: u32, sample_rate_out: u32) Error!void { - try maybeError(setRate(converter, sample_rate_in, sample_rate_out)); + try maybeError(ma_data_converter_set_rate(converter, sample_rate_in, sample_rate_out)); } extern fn ma_data_converter_set_rate(converter: *DataConverter, sample_rate_in: u32, sample_rate_out: u32) Result; @@ -801,12 +801,12 @@ pub const DataConverter = opaque { extern fn ma_data_converter_set_rate_ratio(converter: *DataConverter, ratioInOut: f32) Result; pub fn getInputLatency(converter: *DataConverter) u64 { - ma_data_converter_get_input_latency(converter); + return ma_data_converter_get_input_latency(converter); } extern fn ma_data_converter_get_input_latency(converter: *DataConverter) u64; pub fn getOutPutLatency(converter: *DataConverter) u64 { - ma_data_converter_get_output_latency(converter); + return ma_data_converter_get_output_latency(converter); } extern fn ma_data_converter_get_output_latency(converter: *DataConverter) u64; @@ -891,27 +891,25 @@ pub const Decoder = opaque { } extern fn zaudioDecoderCreateFromMemory(data: ?*const anyopaque, data_size: usize, config: *const Config, out_handle: ?*?*Decoder) Result; - pub fn createFromVfs(vfs: *Vfs, file_path: []const u8, config: Config) Error!*Decoder { + pub fn createFromVfs(vfs: *Vfs, file_path: [:0]const u8, config: Config) Error!*Decoder { var handle: ?*Decoder = null; - try maybeError(zaudioDecoderCreateFromVfs(vfs, file_path, &config, &handle)); + try maybeError(zaudioDecoderCreateFromVfs(vfs, file_path.ptr, &config, &handle)); return handle.?; } - extern fn zaudioDecoderCreateFromVfs(vfs: *Vfs, file_path: []const u8, config: *const Config, out_handle: ?*?*Decoder) Result; + extern fn zaudioDecoderCreateFromVfs(vfs: *Vfs, file_path: [*:0]const u8, config: *const Config, out_handle: ?*?*Decoder) Result; - pub fn createFromFile(file_path: []const u8, config: Config) Error!*Decoder { + pub fn createFromFile(file_path: [:0]const u8, config: Config) Error!*Decoder { var handle: ?*Decoder = null; - try maybeError(zaudioDecoderCreateFromFile(file_path, &config, &handle)); + try maybeError(zaudioDecoderCreateFromFile(file_path.ptr, &config, &handle)); return handle.?; } - extern fn zaudioDecoderCreateFromFile(file_path: []const u8, config: *const Config, out_handle: ?*?*Decoder) Result; + extern fn zaudioDecoderCreateFromFile(file_path: [*:0]const u8, config: *const Config, out_handle: ?*?*Decoder) Result; // The remaing related functions for manipulate the samples: - pub fn readPCMFrame(decoder: *Decoder, frames_count: u64, frames_read: *u64) Error!*anyopaque { - var frame_out: anyopaque = undefined; - try maybeError(ma_decoder_read_pcm_frame(decoder, &frame_out, frames_count, frames_read)); - return frame_out; + pub fn readPCMFrame(decoder: *Decoder, frame_out: *anyopaque, frames_count: u64, frames_read: *u64) Error!void { + try maybeError(ma_decoder_read_pcm_frames(decoder, frame_out, frames_count, frames_read)); } - extern fn ma_decoder_read_pcm_frame(decoder: *Decoder, frame_out: *anyopaque, frames_count: u64, frames_read: ?*u64) Result; + extern fn ma_decoder_read_pcm_frames(decoder: *Decoder, frame_out: *anyopaque, frames_count: u64, frames_read: ?*u64) Result; pub fn seekToPCMFrame(decoder: *Decoder, frame_index: u64) Error!void { try maybeError(ma_decoder_seek_to_pcm_frame(decoder, frame_index)); @@ -924,23 +922,23 @@ pub const Decoder = opaque { extern fn ma_decoder_get_data_format(decoder: *Decoder, format: *Format, channels: *u32, sample_rate: *u32, channel_map: [*]Channel, channel_map_cap: usize) Result; pub fn getCursorInPCMFrames(decoder: *Decoder) Error!u64 { - var cursor: *u64 = undefined; + var cursor: u64 = undefined; try maybeError(ma_decoder_get_cursor_in_pcm_frames(decoder, &cursor)); - return cursor.*; + return cursor; } extern fn ma_decoder_get_cursor_in_pcm_frames(decoder: *Decoder, cursor: *u64) Result; pub fn getLengthInPCMFrames(decoder: *Decoder) Error!u64 { - var length: *u64 = undefined; + var length: u64 = undefined; try maybeError(ma_decoder_get_length_in_pcm_frames(decoder, &length)); - return length.*; + return length; } extern fn ma_decoder_get_length_in_pcm_frames(decoder: *Decoder, length: *u64) Result; pub fn getAvailableFrames(decoder: *Decoder) Error!u64 { - var available_frames: *u64 = undefined; + var available_frames: u64 = undefined; try maybeError(ma_decoder_get_available_frames(decoder, &available_frames)); - return available_frames.*; + return available_frames; } extern fn ma_decoder_get_available_frames(decoder: *Decoder, available_frames: *u64) Result; @@ -953,15 +951,15 @@ pub const Decoder = opaque { read_seek_tell_user_data: *anyopaque, config: *const BackendConfig, allocation_callbacks: AllocationCallbacks, - backend: *?*?DataSource, + backend: **DataSource, ) callconv(.C) Result, onInitFile: ?*const fn ( user_data: *anyopaque, - file_path: [:0]const u8, + file_path: [*:0]const u8, config: BackendConfig, allocation_callbacks: AllocationCallbacks, - backend: *?*?DataSource, + backend: **DataSource, ) callconv(.C) Result, onInitMemory: ?*const fn ( @@ -970,12 +968,12 @@ pub const Decoder = opaque { data_size: usize, config: BackendConfig, allocation_callbacks: AllocationCallbacks, - backend: *?*?DataSource, + backend: **DataSource, ) callconv(.C) Result, onUninit: ?*const fn ( user_data: *anyopaque, - backend: *?DataSource, + backend: *DataSource, allocation_callbacks: AllocationCallbacks, ) callconv(.C) void, }; From c54c2ec4d3953961f8ffb1450c208d31e71d3f3d Mon Sep 17 00:00:00 2001 From: Logickin-Lambda Date: Thu, 28 Aug 2025 16:41:15 +0800 Subject: [PATCH 07/20] - Successfully created Decoder and its Config, but... There are many more test to do, but I can't do this on the zaudio level where there is no way to place the callback function within the test block. I will open up a separated project to conduct a series of testing such that to ensure the functions working flawlessly and acting as an example code to overcome with the lacking low-level example for zaudio. --- src/zaudio.c | 4 ++++ src/zaudio.zig | 18 ++++++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/zaudio.c b/src/zaudio.c index e4f9e37..0158287 100644 --- a/src/zaudio.c +++ b/src/zaudio.c @@ -724,6 +724,10 @@ void zaudioDecoderConfigInit( *out_config = ma_decoder_config_init(outputFormat, outputChannels, outputSampleRate); } +void zaudioDecoderConfigInitDefault(ma_decoder_config* out_config){ + *out_config = ma_decoder_config_init_default(); +} + // There are more variants in decoder than all other types, but I will only handle the non-w type // since the other type with the similar file access method don't include such type as well // unless there is a good solution for wchar_t. diff --git a/src/zaudio.zig b/src/zaudio.zig index dd38209..169ab86 100644 --- a/src/zaudio.zig +++ b/src/zaudio.zig @@ -906,12 +906,12 @@ pub const Decoder = opaque { extern fn zaudioDecoderCreateFromFile(file_path: [*:0]const u8, config: *const Config, out_handle: ?*?*Decoder) Result; // The remaing related functions for manipulate the samples: - pub fn readPCMFrame(decoder: *Decoder, frame_out: *anyopaque, frames_count: u64, frames_read: *u64) Error!void { + pub fn readPCMFrames(decoder: *Decoder, frame_out: *anyopaque, frames_count: u64, frames_read: ?*u64) Error!void { try maybeError(ma_decoder_read_pcm_frames(decoder, frame_out, frames_count, frames_read)); } extern fn ma_decoder_read_pcm_frames(decoder: *Decoder, frame_out: *anyopaque, frames_count: u64, frames_read: ?*u64) Result; - pub fn seekToPCMFrame(decoder: *Decoder, frame_index: u64) Error!void { + pub fn seekToPCMFrames(decoder: *Decoder, frame_index: u64) Error!void { try maybeError(ma_decoder_seek_to_pcm_frame(decoder, frame_index)); } extern fn ma_decoder_seek_to_pcm_frame(decoder: *Decoder, frame_index: u64) Result; @@ -992,6 +992,20 @@ pub const Decoder = opaque { custom_backend_vtable: ?*?*VTable, custom_backend_count: u32, custom_backend_user_data: *anyopaque, + + pub fn initDefault() Config { + var config: Config = undefined; + zaudioDecoderConfigInitDefault(&config); + return config; + } + extern fn zaudioDecoderConfigInitDefault(out_config: *Config) void; + + pub fn init(output_format: Format, output_channels: u32, output_sample_rate: u32) Config { + var config: Config = undefined; + zaudioDecoderConfigInit(output_format, output_channels, output_sample_rate, &config); + return config; + } + extern fn zaudioDecoderConfigInit(output_format: Format, output_channels: u32, output_sample_rate: u32, out_config: *Config) void; }; pub const BackendConfig = extern struct { From d08ac9b6b971ae07b40e8377fee26e7bca39dcec Mon Sep 17 00:00:00 2001 From: Logickin-Lambda Date: Fri, 29 Aug 2025 15:36:30 +0800 Subject: [PATCH 08/20] - bug fixes This commit addresses the inconsistent convention for channel_map input parameter for the decoder type, and handle a case where some operation requiring to passing the decoder as datasource for other functions. The decoder is now confirmed to fetch and decoder mp3 successfully in a separated project, but more test are required to be conducted, not to mention that the pull request will be done after zig 0.15.1 has properly been merged. --- src/zaudio.zig | 119 ++++++++++++++++++++++++++----------------------- 1 file changed, 63 insertions(+), 56 deletions(-) diff --git a/src/zaudio.zig b/src/zaudio.zig index 169ab86..999d8e8 100644 --- a/src/zaudio.zig +++ b/src/zaudio.zig @@ -17,7 +17,7 @@ pub fn init(allocator: std.mem.Allocator) void { zaudioMemInit(); } -extern fn zaudioMemInit() callconv(.C) void; +extern fn zaudioMemInit() callconv(.c) void; pub fn deinit() void { assert(mem_allocator != null); @@ -406,15 +406,15 @@ pub const MonoExpansionMode = enum(u32) { pub const AllocationCallbacks = extern struct { user_data: ?*anyopaque, - onMalloc: ?*const fn (len: usize, user_data: ?*anyopaque) callconv(.C) ?*anyopaque, + onMalloc: ?*const fn (len: usize, user_data: ?*anyopaque) callconv(.c) ?*anyopaque, onRealloc: ?*const fn ( ptr: ?*anyopaque, len: usize, user_data: ?*anyopaque, - ) callconv(.C) ?*anyopaque, + ) callconv(.c) ?*anyopaque, - onFree: ?*const fn (ptr: ?*anyopaque, user_data: ?*anyopaque) callconv(.C) void, + onFree: ?*const fn (ptr: ?*anyopaque, user_data: ?*anyopaque) callconv(.c) void, }; pub const Bool32 = enum(u32) { @@ -453,21 +453,21 @@ pub const Vfs = extern struct { size_in_bytes: usize, }; - on_open: ?*const fn (self: *Vfs, file_path: [*:0]const u8, mode: OpenMode, handle: *FileHandle) callconv(.C) Result, - on_openw: ?*const fn (self: *Vfs, file_path: [*:0]const u32, mode: OpenMode, handle: *FileHandle) callconv(.C) Result, - on_close: ?*const fn (self: *Vfs, handle: FileHandle) callconv(.C) Result, - on_read: ?*const fn (self: *Vfs, handle: FileHandle, dst: [*]u8, size: usize, bytes_read: *usize) callconv(.C) Result, - on_write: ?*const fn (self: *Vfs, handle: FileHandle, src: [*]const u8, size: usize, bytes_written: *usize) callconv(.C) Result, - on_seek: ?*const fn (self: *Vfs, handle: FileHandle, offset: i64, origin: SeekOrigin) callconv(.C) Result, - on_tell: ?*const fn (self: *Vfs, handle: FileHandle, offset: *i64) callconv(.C) Result, - on_info: ?*const fn (self: *Vfs, handle: FileHandle, info: *FileInfo) callconv(.C) Result, + on_open: ?*const fn (self: *Vfs, file_path: [*:0]const u8, mode: OpenMode, handle: *FileHandle) callconv(.c) Result, + on_openw: ?*const fn (self: *Vfs, file_path: [*:0]const u32, mode: OpenMode, handle: *FileHandle) callconv(.c) Result, + on_close: ?*const fn (self: *Vfs, handle: FileHandle) callconv(.c) Result, + on_read: ?*const fn (self: *Vfs, handle: FileHandle, dst: [*]u8, size: usize, bytes_read: *usize) callconv(.c) Result, + on_write: ?*const fn (self: *Vfs, handle: FileHandle, src: [*]const u8, size: usize, bytes_written: *usize) callconv(.c) Result, + on_seek: ?*const fn (self: *Vfs, handle: FileHandle, offset: i64, origin: SeekOrigin) callconv(.c) Result, + on_tell: ?*const fn (self: *Vfs, handle: FileHandle, offset: *i64) callconv(.c) Result, + on_info: ?*const fn (self: *Vfs, handle: FileHandle, info: *FileInfo) callconv(.c) Result, }; // these functions were originally located under vfs, but they seems to have no correlation to the type, // while decoder require such type in order to make it work, so I will temporary locate these functions in here: -pub const readProc = *const fn (user_data: ?*anyopaque, buffer_out: ?*anyopaque, bytes_to_read: usize, bytes_read: *usize) callconv(.C) Result; -pub const seekProc = *const fn (user_data: ?*anyopaque, offset: i64, origin: Vfs.SeekOrigin) callconv(.C) Result; -pub const tellProc = *const fn (user_data: ?*anyopaque, cursor: ?*i64) callconv(.C) Result; +pub const readProc = *const fn (user_data: ?*anyopaque, buffer_out: ?*anyopaque, bytes_to_read: usize, bytes_read: *usize) callconv(.c) Result; +pub const seekProc = *const fn (user_data: ?*anyopaque, offset: i64, origin: Vfs.SeekOrigin) callconv(.c) Result; +pub const tellProc = *const fn (user_data: ?*anyopaque, cursor: ?*i64) callconv(.c) Result; pub const Context = opaque { // TODO: Add methods. @@ -489,7 +489,7 @@ pub const DataSourceBase = extern struct { loop_end_in_frames: u64, p_current: *DataSource, p_next: *DataSource, - onGetNext: ?*const fn (*DataSource) callconv(.C) void, + onGetNext: ?*const fn (*DataSource) callconv(.c) void, is_looping: Bool32, }; @@ -525,9 +525,9 @@ pub const DataSource = opaque { frames_out: ?*anyopaque, frame_count: u64, frames_read: *u64, - ) callconv(.C) Result, + ) callconv(.c) Result, - onSeek: ?*const fn (ds: *DataSource, frame_index: u64) callconv(.C) Result, + onSeek: ?*const fn (ds: *DataSource, frame_index: u64) callconv(.c) Result, onGetDataFormat: ?*const fn ( ds: *DataSource, @@ -536,13 +536,13 @@ pub const DataSource = opaque { sample_rate: ?*u32, channel_map: ?[*]Channel, channel_map_cap: usize, - ) callconv(.C) Result, + ) callconv(.c) Result, - onGetCursor: ?*const fn (ds: *DataSource, cursor: ?*u64) callconv(.C) Result, + onGetCursor: ?*const fn (ds: *DataSource, cursor: ?*u64) callconv(.c) Result, - onGetLength: ?*const fn (ds: *DataSource, length: ?*u64) callconv(.C) Result, + onGetLength: ?*const fn (ds: *DataSource, length: ?*u64) callconv(.c) Result, - onSetLooping: ?*const fn (ds: *DataSource, is_looping: Bool32) callconv(.C) Result, + onSetLooping: ?*const fn (ds: *DataSource, is_looping: Bool32) callconv(.c) Result, flags: Flags, }; @@ -824,15 +824,15 @@ pub const DataConverter = opaque { } extern fn ma_data_converter_get_expected_output_frame_count(converter: *DataConverter, input_frame_count: u64, output_frame_count: *u64) Result; - pub fn getInputChannelMap(converter: *DataConverter, channel_map: *Channel, channel_map_cap: usize) Error!void { - try maybeError(ma_data_converter_get_input_channel_map(converter, channel_map, channel_map_cap)); + pub fn getInputChannelMap(converter: *DataConverter, channel_map: ?[]Channel, channel_map_cap: usize) Error!void { + try maybeError(ma_data_converter_get_input_channel_map(converter, channel_map.ptr, channel_map_cap)); } - extern fn ma_data_converter_get_input_channel_map(converter: *DataConverter, channel_map: *Channel, channel_map_cap: usize) Result; + extern fn ma_data_converter_get_input_channel_map(converter: *DataConverter, channel_map: ?[*]Channel, channel_map_cap: usize) Result; - pub fn getOutputChannelMap(converter: *DataConverter, channel_map: *Channel, channel_map_cap: usize) Error!void { - try maybeError(ma_data_converter_get_output_channel_map(converter, channel_map, channel_map_cap)); + pub fn getOutputChannelMap(converter: *DataConverter, channel_map: ?[]Channel, channel_map_cap: usize) Error!void { + try maybeError(ma_data_converter_get_output_channel_map(converter, channel_map.ptr, channel_map_cap)); } - extern fn ma_data_converter_get_output_channel_map(converter: *DataConverter, channel_map: *Channel, channel_map_cap: usize) Result; + extern fn ma_data_converter_get_output_channel_map(converter: *DataConverter, channel_map: ?[*]Channel, channel_map_cap: usize) Result; pub fn reset(converter: *DataConverter) Error!void { try maybeError(ma_data_converter_reset(converter)); @@ -855,8 +855,8 @@ pub const DataConverter = opaque { channels_out: u32, sample_rate_in: u32, sample_rate_out: u32, - channel_map_in: *Channel, - channel_map_out: *Channel, + channel_map_in: ?[*]Channel, + channel_map_out: ?[*]Channel, dither_mode: DitherMode, channel_mix_mode: ChannelMixMode, calculate_LFE_from_spatial_channels: u32, @@ -872,6 +872,13 @@ pub const DataConverter = opaque { // //-------------------------------------------------------------------------------------------------- pub const Decoder = opaque { + pub fn asDataSource(handle: *const Decoder) *const DataSource { + return @as(*const DataSource, @ptrCast(handle)); + } + pub fn asDataSourceMut(handle: *Decoder) *DataSource { + return @as(*DataSource, @ptrCast(handle)); + } + pub const destroy = zaudioDecoderDestroy; extern fn zaudioDecoderDestroy(handle: *Decoder) void; @@ -916,10 +923,10 @@ pub const Decoder = opaque { } extern fn ma_decoder_seek_to_pcm_frame(decoder: *Decoder, frame_index: u64) Result; - pub fn getDataFormat(decoder: *Decoder, format: *Format, channels: *u32, sample_rate: *u32, channel_map: [*]Channel, channel_map_cap: usize) Error!void { - try maybeError(ma_decoder_get_data_format(decoder, format, channels, sample_rate, channel_map, channel_map_cap)); + pub fn getDataFormat(decoder: *Decoder, format: *Format, channels: *u32, sample_rate: *u32, channel_map: ?[]Channel, channel_map_cap: usize) Error!void { + try maybeError(ma_decoder_get_data_format(decoder, format, channels, sample_rate, channel_map.ptr, channel_map_cap)); } - extern fn ma_decoder_get_data_format(decoder: *Decoder, format: *Format, channels: *u32, sample_rate: *u32, channel_map: [*]Channel, channel_map_cap: usize) Result; + extern fn ma_decoder_get_data_format(decoder: *Decoder, format: *Format, channels: *u32, sample_rate: *u32, channel_map: ?[*]Channel, channel_map_cap: usize) Result; pub fn getCursorInPCMFrames(decoder: *Decoder) Error!u64 { var cursor: u64 = undefined; @@ -952,7 +959,7 @@ pub const Decoder = opaque { config: *const BackendConfig, allocation_callbacks: AllocationCallbacks, backend: **DataSource, - ) callconv(.C) Result, + ) callconv(.c) Result, onInitFile: ?*const fn ( user_data: *anyopaque, @@ -960,7 +967,7 @@ pub const Decoder = opaque { config: BackendConfig, allocation_callbacks: AllocationCallbacks, backend: **DataSource, - ) callconv(.C) Result, + ) callconv(.c) Result, onInitMemory: ?*const fn ( user_data: *anyopaque, @@ -969,20 +976,20 @@ pub const Decoder = opaque { config: BackendConfig, allocation_callbacks: AllocationCallbacks, backend: **DataSource, - ) callconv(.C) Result, + ) callconv(.c) Result, onUninit: ?*const fn ( user_data: *anyopaque, backend: *DataSource, allocation_callbacks: AllocationCallbacks, - ) callconv(.C) void, + ) callconv(.c) void, }; pub const Config = extern struct { format: Format, channels: u32, sample_rate: u32, - channel_map: *Channel, + channel_map: ?[*]Channel, channel_mix_mode: ChannelMixMode, dither_mode: DitherMode, resampling: Resampler.Config, @@ -991,7 +998,7 @@ pub const Decoder = opaque { seek_point_count: u32, custom_backend_vtable: ?*?*VTable, custom_backend_count: u32, - custom_backend_user_data: *anyopaque, + custom_backend_user_data: ?*anyopaque, pub fn initDefault() Config { var config: Config = undefined; @@ -1014,7 +1021,7 @@ pub const Decoder = opaque { }; }; -pub const decoderReadProc = fn (decoder: *Decoder, buffer_out: *anyopaque, bytes_to_read: usize, bytes_read: *usize) callconv(.C) Result; +pub const decoderReadProc = fn (decoder: *Decoder, buffer_out: *anyopaque, bytes_to_read: usize, bytes_read: *usize) callconv(.c) Result; pub const decoderSeekProc = fn (decoder: *Decoder, byte_offset: i64, origin: Vfs.SeekOrigin) Result; pub const decoderTellProc = fn (decoder: *Decoder, cursor: *i64) Result; @@ -1066,13 +1073,13 @@ pub const Node = opaque { frame_count_in: ?*u32, frames_out: *[*]f32, frame_count_out: *u32, - ) callconv(.C) void, + ) callconv(.c) void, onGetRequiredInputFrameCount: ?*const fn ( node: *Node, output_frame_count: u32, input_frame_count: *u32, - ) callconv(.C) Result, + ) callconv(.c) Result, input_bus_count: u8, output_bus_count: u8, @@ -2024,13 +2031,13 @@ pub const Device = opaque { output: ?*anyopaque, input: ?*const anyopaque, frame_count: u32, - ) callconv(.C) void; + ) callconv(.c) void; pub const NotificationProc = *const fn ( *const anyopaque, // TODO: Should be `*const ma_device_notification`. - ) callconv(.C) void; + ) callconv(.c) void; - pub const StopProc = *const fn (device: *Device) callconv(.C) void; + pub const StopProc = *const fn (device: *Device) callconv(.c) void; pub const Id = extern union { wasapi: [64]i32, @@ -2097,7 +2104,7 @@ pub const Engine = opaque { user_data: ?*anyopaque, frames_out: [*]f32, frame_count: u64, - ) callconv(.C) void; + ) callconv(.c) void; pub fn create(config: ?Config) Error!*Engine { var handle: ?*Engine = null; @@ -2726,7 +2733,7 @@ pub const Sound = opaque { pub const EndProc = *const fn ( user_data: ?*anyopaque, sound: *Sound, - ) callconv(.C) void; + ) callconv(.c) void; }; //-------------------------------------------------------------------------------------------------- // @@ -3013,15 +3020,15 @@ var mem_allocations: ?std.AutoHashMap(usize, usize) = null; var mem_mutex: std.Thread.Mutex = .{}; const mem_alignment = 16; -extern var zaudioMallocPtr: ?*const fn (size: usize, _: ?*anyopaque) callconv(.C) ?*anyopaque; +extern var zaudioMallocPtr: ?*const fn (size: usize, _: ?*anyopaque) callconv(.c) ?*anyopaque; -fn zaudioMalloc(size: usize, _: ?*anyopaque) callconv(.C) ?*anyopaque { +fn zaudioMalloc(size: usize, _: ?*anyopaque) callconv(.c) ?*anyopaque { mem_mutex.lock(); defer mem_mutex.unlock(); const mem = mem_allocator.?.alignedAlloc( u8, - mem_alignment, + .fromByteUnits(mem_alignment), size, ) catch @panic("zaudio: out of memory"); @@ -3030,9 +3037,9 @@ fn zaudioMalloc(size: usize, _: ?*anyopaque) callconv(.C) ?*anyopaque { return mem.ptr; } -extern var zaudioReallocPtr: ?*const fn (ptr: ?*anyopaque, size: usize, _: ?*anyopaque) callconv(.C) ?*anyopaque; +extern var zaudioReallocPtr: ?*const fn (ptr: ?*anyopaque, size: usize, _: ?*anyopaque) callconv(.c) ?*anyopaque; -fn zaudioRealloc(ptr: ?*anyopaque, size: usize, _: ?*anyopaque) callconv(.C) ?*anyopaque { +fn zaudioRealloc(ptr: ?*anyopaque, size: usize, _: ?*anyopaque) callconv(.c) ?*anyopaque { mem_mutex.lock(); defer mem_mutex.unlock(); @@ -3054,9 +3061,9 @@ fn zaudioRealloc(ptr: ?*anyopaque, size: usize, _: ?*anyopaque) callconv(.C) ?*a return new_mem.ptr; } -extern var zaudioFreePtr: ?*const fn (maybe_ptr: ?*anyopaque, _: ?*anyopaque) callconv(.C) void; +extern var zaudioFreePtr: ?*const fn (maybe_ptr: ?*anyopaque, _: ?*anyopaque) callconv(.c) void; -fn zaudioFree(maybe_ptr: ?*anyopaque, _: ?*anyopaque) callconv(.C) void { +fn zaudioFree(maybe_ptr: ?*anyopaque, _: ?*anyopaque) callconv(.c) void { if (maybe_ptr) |ptr| { mem_mutex.lock(); defer mem_mutex.unlock(); @@ -3266,7 +3273,7 @@ test "zaudio.audio_buffer" { defer deinit(); var samples = try std.ArrayList(f32).initCapacity(std.testing.allocator, 1000); - defer samples.deinit(); + defer samples.deinit(std.testing.allocator); var prng = std.Random.DefaultPrng.init(0); const rand = prng.random(); @@ -3295,7 +3302,7 @@ test "zaudio.audio_buffer" { sound.setLooping(true); try sound.start(); - std.time.sleep(1e8); + std.Thread.sleep(1e8); } test { From b102c8aa11f3a89e577599cd5b4eb0409102291c Mon Sep 17 00:00:00 2001 From: Logickin-Lambda Date: Mon, 1 Sep 2025 16:54:47 +0800 Subject: [PATCH 09/20] - Updated decoder function involving channel_map The original function that involving channel_map has an inconsistent input type comparing with other existing function. In this version, I have replaced the two parameters channel_map and channel_map_cap with a slice for my decoder type. All of the implemented functions for decoder are tested and have worked properly; thus, the next task will be testing about the data converter and add the remaining function I missed from the previous commits. --- src/zaudio.zig | 38 +++++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/src/zaudio.zig b/src/zaudio.zig index 999d8e8..8a5142d 100644 --- a/src/zaudio.zig +++ b/src/zaudio.zig @@ -824,13 +824,24 @@ pub const DataConverter = opaque { } extern fn ma_data_converter_get_expected_output_frame_count(converter: *DataConverter, input_frame_count: u64, output_frame_count: *u64) Result; - pub fn getInputChannelMap(converter: *DataConverter, channel_map: ?[]Channel, channel_map_cap: usize) Error!void { - try maybeError(ma_data_converter_get_input_channel_map(converter, channel_map.ptr, channel_map_cap)); + pub fn getInputChannelMap(converter: *DataConverter, channel_map: ?[]Channel) Error!void { + try maybeError(ma_data_converter_get_input_channel_map( + converter, + if (channel_map) |chm| chm.ptr else null, + if (channel_map) |chm| chm.len else 0, + )); } extern fn ma_data_converter_get_input_channel_map(converter: *DataConverter, channel_map: ?[*]Channel, channel_map_cap: usize) Result; - pub fn getOutputChannelMap(converter: *DataConverter, channel_map: ?[]Channel, channel_map_cap: usize) Error!void { - try maybeError(ma_data_converter_get_output_channel_map(converter, channel_map.ptr, channel_map_cap)); + pub fn getOutputChannelMap( + converter: *DataConverter, + channel_map: ?[]Channel, + ) Error!void { + try maybeError(ma_data_converter_get_output_channel_map( + converter, + if (channel_map) |chm| chm.ptr else null, + if (channel_map) |chm| chm.len else 0, + )); } extern fn ma_data_converter_get_output_channel_map(converter: *DataConverter, channel_map: ?[*]Channel, channel_map_cap: usize) Result; @@ -923,10 +934,23 @@ pub const Decoder = opaque { } extern fn ma_decoder_seek_to_pcm_frame(decoder: *Decoder, frame_index: u64) Result; - pub fn getDataFormat(decoder: *Decoder, format: *Format, channels: *u32, sample_rate: *u32, channel_map: ?[]Channel, channel_map_cap: usize) Error!void { - try maybeError(ma_decoder_get_data_format(decoder, format, channels, sample_rate, channel_map.ptr, channel_map_cap)); + pub fn getDataFormat( + decoder: *const Decoder, + format: ?*Format, + channels: ?*u32, + sample_rate: ?*u32, + channel_map: ?[]Channel, + ) Error!void { + try maybeError(ma_decoder_get_data_format( + decoder, + format, + channels, + sample_rate, + if (channel_map) |chm| chm.ptr else null, + if (channel_map) |chm| chm.len else 0, + )); } - extern fn ma_decoder_get_data_format(decoder: *Decoder, format: *Format, channels: *u32, sample_rate: *u32, channel_map: ?[*]Channel, channel_map_cap: usize) Result; + extern fn ma_decoder_get_data_format(decoder: *const Decoder, format: ?*Format, channels: ?*u32, sample_rate: ?*u32, channel_map: ?[*]Channel, channel_map_cap: usize) Result; pub fn getCursorInPCMFrames(decoder: *Decoder) Error!u64 { var cursor: u64 = undefined; From 8cf3ecabc9e740f0ad10aa67f83c5d93f5d74a27 Mon Sep 17 00:00:00 2001 From: Logickin-Lambda Date: Tue, 2 Sep 2025 16:42:40 +0800 Subject: [PATCH 10/20] - Added Missing Functions For DataConverter After the test, it turns out the config init Functions are missing in the type, so I have added it such that we can have a more convenient way to declare the converter. --- src/zaudio.c | 5 +++++ src/zaudio.zig | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/src/zaudio.c b/src/zaudio.c index 0158287..2ae9302 100644 --- a/src/zaudio.c +++ b/src/zaudio.c @@ -689,6 +689,11 @@ void zaudioDataConverterConfigInit( *out_config = ma_data_converter_config_init(formatIn, formatOut, channelsIn, channelsOut, sampleRateIn, sampleRateOut); } +void zaudioDataConverterConfigInitDefault(ma_data_converter_config* out_config){ + assert(out_config != NULL); + *out_config = ma_data_converter_config_init_default(); +} + // ma_data_converter ma_result zaudioDataConverterCreate( ma_data_converter_config* config, diff --git a/src/zaudio.zig b/src/zaudio.zig index 8a5142d..57b62ea 100644 --- a/src/zaudio.zig +++ b/src/zaudio.zig @@ -874,6 +874,43 @@ pub const DataConverter = opaque { channel_weighs_io_ptr: [*][*]f32, // [in][out]. Only used when mixingMode is set to ma_channel_mix_mode_custom_weights. allow_dynamic_sample_rate: u32, resampling: Resampler.Config, + + pub fn initDefault() Config { + var config: Config = undefined; + zaudioDataConverterConfigInitDefault(&config); + return config; + } + extern fn zaudioDataConverterConfigInitDefault(out_config: *Config) void; + + pub fn init( + format_in: Format, + format_out: Format, + channels_in: u32, + channels_out: u32, + sample_rate_in: u32, + sample_rate_out: u32, + ) Config { + var config: Config = undefined; + zaudioDataConverterConfigInit( + format_in, + format_out, + channels_in, + channels_out, + sample_rate_in, + sample_rate_out, + &config, + ); + return config; + } + extern fn zaudioDataConverterConfigInit( + format_in: Format, + format_out: Format, + channels_in: u32, + channels_out: u32, + sample_rate_in: u32, + sample_rate_out: u32, + out_config: *Config, + ) void; }; }; From 0d6d2f74ff7a36e0867df85627dbee86a635f1c9 Mon Sep 17 00:00:00 2001 From: Logickin-Lambda Date: Tue, 2 Sep 2025 16:54:20 +0800 Subject: [PATCH 11/20] - Updated README With The New Type --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 6a90f7e..95ac123 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,8 @@ Provided structs: - [x] `HishelfNode // High Shelf Filter` - [x] `DelayNode` - [x] custom nodes +- [x] `Decoder` (missing methods) +- [x] `DataConverter` ## Getting started From 7652850d40076deee24dbcf1c3ed513e2853fafa Mon Sep 17 00:00:00 2001 From: Logickin-Lambda Date: Mon, 8 Sep 2025 09:48:18 +0800 Subject: [PATCH 12/20] - Updated Naming and Type Inconsistency The GitHub review does reflect the inconsistency of my code, with comparing the existing code from the library. Thus, I have made the change according to the copilot review such that to align the coding standard and format of the existing codebase. --- src/zaudio.c | 4 ++-- src/zaudio.zig | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/zaudio.c b/src/zaudio.c index 2ae9302..9422395 100644 --- a/src/zaudio.c +++ b/src/zaudio.c @@ -718,7 +718,7 @@ void zaudioDataConverterDestroy( } //-------------------------------------------------------------------------------------------------- -// ma_decoder zaudioDataConverterConfigInit +// ma_decoder zaudioDecoderConfigInit void zaudioDecoderConfigInit( ma_format outputFormat, ma_uint32 outputChannels, @@ -800,7 +800,7 @@ ma_result zaudioDecoderCreateFromFile( return res; } -ma_result zaudioDecoderDestroy( +void zaudioDecoderDestroy( ma_decoder* handle ){ assert(handle != NULL); diff --git a/src/zaudio.zig b/src/zaudio.zig index 57b62ea..7784733 100644 --- a/src/zaudio.zig +++ b/src/zaudio.zig @@ -937,7 +937,7 @@ pub const Decoder = opaque { try maybeError(zaudioDecoderCreate(decoder_on_read, decoder_on_seek, user_data, &config, &handle)); return handle.?; } - extern fn zaudioDecoderCreate(on_read: decoderReadProc, on_seek: decoderSeekProc, user_data: *anyopaque, config: *const Config, out_handle: *?*?Decoder) Result; + extern fn zaudioDecoderCreate(on_read: decoderReadProc, on_seek: decoderSeekProc, user_data: *anyopaque, config: *const Config, out_handle: ?*?*Decoder) Result; pub fn createFromMemory(data: ?*const anyopaque, data_size: usize, config: Config) Error!*Decoder { var handle: ?*Decoder = null; @@ -1083,8 +1083,8 @@ pub const Decoder = opaque { }; pub const decoderReadProc = fn (decoder: *Decoder, buffer_out: *anyopaque, bytes_to_read: usize, bytes_read: *usize) callconv(.c) Result; -pub const decoderSeekProc = fn (decoder: *Decoder, byte_offset: i64, origin: Vfs.SeekOrigin) Result; -pub const decoderTellProc = fn (decoder: *Decoder, cursor: *i64) Result; +pub const decoderSeekProc = fn (decoder: *Decoder, byte_offset: i64, origin: Vfs.SeekOrigin) callconv(.c) Result; +pub const decoderTellProc = fn (decoder: *Decoder, cursor: *i64) callconv(.c) Result; //-------------------------------------------------------------------------------------------------- // From 0fe8f17068b6099b79beb393c3dc8fe23719c528 Mon Sep 17 00:00:00 2001 From: Logickin-Lambda Date: Sun, 14 Sep 2025 10:00:34 +0800 Subject: [PATCH 13/20] - Remove bad comments --- src/zaudio.zig | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/zaudio.zig b/src/zaudio.zig index 7784733..f827579 100644 --- a/src/zaudio.zig +++ b/src/zaudio.zig @@ -463,8 +463,6 @@ pub const Vfs = extern struct { on_info: ?*const fn (self: *Vfs, handle: FileHandle, info: *FileInfo) callconv(.c) Result, }; -// these functions were originally located under vfs, but they seems to have no correlation to the type, -// while decoder require such type in order to make it work, so I will temporary locate these functions in here: pub const readProc = *const fn (user_data: ?*anyopaque, buffer_out: ?*anyopaque, bytes_to_read: usize, bytes_read: *usize) callconv(.c) Result; pub const seekProc = *const fn (user_data: ?*anyopaque, offset: i64, origin: Vfs.SeekOrigin) callconv(.c) Result; pub const tellProc = *const fn (user_data: ?*anyopaque, cursor: ?*i64) callconv(.c) Result; @@ -930,8 +928,6 @@ pub const Decoder = opaque { pub const destroy = zaudioDecoderDestroy; extern fn zaudioDecoderDestroy(handle: *Decoder) void; - // here are the init functions, but after observed other examples - // I will skip the _w variant until there is a solution to handle wchar_t pub fn create(decoder_on_read: decoderReadProc, decoder_on_seek: decoderSeekProc, user_data: *anyopaque, config: Config) Error!*Decoder { var handle: ?*Decoder = null; try maybeError(zaudioDecoderCreate(decoder_on_read, decoder_on_seek, user_data, &config, &handle)); @@ -960,7 +956,6 @@ pub const Decoder = opaque { } extern fn zaudioDecoderCreateFromFile(file_path: [*:0]const u8, config: *const Config, out_handle: ?*?*Decoder) Result; - // The remaing related functions for manipulate the samples: pub fn readPCMFrames(decoder: *Decoder, frame_out: *anyopaque, frames_count: u64, frames_read: ?*u64) Error!void { try maybeError(ma_decoder_read_pcm_frames(decoder, frame_out, frames_count, frames_read)); } From 484f599ad70a97226bd6b8bca6aafb33253e9022 Mon Sep 17 00:00:00 2001 From: Logickin-Lambda Date: Sat, 20 Sep 2025 16:58:40 +0800 Subject: [PATCH 14/20] - Encoder! It has begun! Without encoder, it won't be an complete audio application because we need to export our music after all the effort on creating the tracks. This pr will address the missing encoder type, and the missing tests for decoder which is not possible to be tested on its own due to the requirement of external audio files. --- src/zaudio.c | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/zaudio.zig | 22 ++++++++++++++++ 2 files changed, 91 insertions(+) diff --git a/src/zaudio.c b/src/zaudio.c index 9422395..d636ac3 100644 --- a/src/zaudio.c +++ b/src/zaudio.c @@ -809,3 +809,72 @@ void zaudioDecoderDestroy( } //-------------------------------------------------------------------------------------------------- +void zaudioEncoderConfigInit( + ma_encoding_format encodingFormat, + ma_format format, + ma_uint32 channels, + ma_uint32 sampleRate, + ma_encoder_config* out_config +){ + assert(out_config != NULL); + *out_config = ma_encoder_config_init(encodingFormat, format, channels, sampleRate); +} + +ma_result zaudioEncoderCreate( + ma_encoder_write_proc on_write, + ma_encoder_seek_proc on_seek, + void* user_data, + const ma_encoder_config* config, + ma_encoder** out_handle +){ + assert(user_data != NULL && config != NULL && out_handle != NULL); + *out_handle = s_mem.onMalloc(sizeof(ma_encoder), s_mem.pUserData); + ma_result res = ma_encoder_init(on_write, on_seek, user_data,config, *out_handle); + if (res != MA_SUCCESS){ + s_mem.onFree(*out_handle, s_mem.pUserData); + *out_handle = NULL; + } + return res; +} + +ma_result zaudioEncoderCreateFromVfs( + ma_vfs* vfs, + const char* file_path, + const ma_encoder_config* config, + ma_encoder** out_handle +){ + assert(vfs != NULL && file_path != NULL && config != NULL, out_handle != NULL); + *out_handle = s_mem.onMalloc(sizeof(ma_encoder), s_mem.pUserData); + ma_result res = ma_encoder_init_vfs(vfs, file_path, config, *out_handle); + if(res != MA_SUCCESS){ + s_mem.onFree(*out_handle, s_mem.pUserData); + *out_handle = NULL; + } + return res; +} + +ma_result zaudioEncoderCreateFromFile( + const char* file_path, + const ma_encoder_config* config, + ma_encoder* out_handle +){ + assert(file_path != NULL, config != NULL, out_handle != NULL); + *out_handle = s_mem.onMalloc(sizeof(ma_encoder), s_mem.pUserData); + ma_result res = ma_encoder_init_file(file_path, config, out_handle); + if (res != MA_SUCCESS){ + s_mem.onFree(*out_handle, s_mem.pUserData); + *out_handle = NULL; + } + return res; +} + +void zaudioEncoderDestroy( + ma_encoder* handle +){ + assert(handle != NULL); + ma_encoder_uninit(handle); + s_mem.onFree(handle, s_mem.pUserData); +} + +//-------------------------------------------------------------------------------------------------- + diff --git a/src/zaudio.zig b/src/zaudio.zig index f827579..5e2619f 100644 --- a/src/zaudio.zig +++ b/src/zaudio.zig @@ -1081,6 +1081,28 @@ pub const decoderReadProc = fn (decoder: *Decoder, buffer_out: *anyopaque, bytes pub const decoderSeekProc = fn (decoder: *Decoder, byte_offset: i64, origin: Vfs.SeekOrigin) callconv(.c) Result; pub const decoderTellProc = fn (decoder: *Decoder, cursor: *i64) callconv(.c) Result; +//-------------------------------------------------------------------------------------------------- +// +// Encoder +// +//-------------------------------------------------------------------------------------------------- + +pub const Encoder = opaque { + pub const Config = extern struct { + encoding_format: EncodingFormat, + format: Format, + channels: u32, + sample_rate: u32, + allocation_callbacks: AllocationCallbacks, + }; +}; + +pub const encoderWriteProc = fn (encoder: *Encoder, buffer_in: *anyopaque, bytes_to_write: usize, bytes_written: *usize) callconv(.c) Result; +pub const encoderSeekProc = fn (encoder: *Encoder, offset: i64, origin: Vfs.SeekOrigin) callconv(.c) Result; +pub const encoderInitProc = fn (encoder: *Encoder) callconv(.c) Result; +pub const encoderUninitProc = fn (encoder: *Encoder) callconv(.c) void; +pub const encoderWritePcmFrameProc = fn (encoder: *Encoder, frames_in: *anyopaque, frame_count: u64, frames_written: *u64) callconv(.c) Result; + //-------------------------------------------------------------------------------------------------- // // Resampler (Incomplete, but since many of the function requires the type, especially the config, From a875fe53d606b89a2091969cb9ed65750dcc706f Mon Sep 17 00:00:00 2001 From: Logickin-Lambda Date: Mon, 22 Sep 2025 18:27:40 +0800 Subject: [PATCH 15/20] - Completed test for Encoder and Data Converter Encoder is more complicated than I thought because there is no init_from_memory variant for the encoder; as a result, I had to manually write the on_write and on_seek function just to export the wave file into a buffer for validation; otherwise, testing encoder will be impossible without generating files. The upcoming task is to continue the test case for the decoder. --- build.zig | 1 - src/zaudio.c | 20 +++-- src/zaudio.zig | 194 +++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 202 insertions(+), 13 deletions(-) diff --git a/build.zig b/build.zig index fb6bfd6..f9993ee 100644 --- a/build.zig +++ b/build.zig @@ -48,7 +48,6 @@ pub fn build(b: *std.Build) void { .file = b.path("libs/miniaudio/miniaudio.c"), .flags = &.{ "-DMA_NO_WEBAUDIO", - "-DMA_NO_ENCODING", "-DMA_NO_NULL", "-DMA_NO_JACK", "-DMA_NO_DSOUND", diff --git a/src/zaudio.c b/src/zaudio.c index d636ac3..ae3420f 100644 --- a/src/zaudio.c +++ b/src/zaudio.c @@ -810,16 +810,17 @@ void zaudioDecoderDestroy( //-------------------------------------------------------------------------------------------------- void zaudioEncoderConfigInit( - ma_encoding_format encodingFormat, + ma_encoding_format encoding_format, ma_format format, ma_uint32 channels, - ma_uint32 sampleRate, + ma_uint32 sample_rate, ma_encoder_config* out_config ){ assert(out_config != NULL); - *out_config = ma_encoder_config_init(encodingFormat, format, channels, sampleRate); + *out_config = ma_encoder_config_init(encoding_format, format, channels, sample_rate); } + ma_result zaudioEncoderCreate( ma_encoder_write_proc on_write, ma_encoder_seek_proc on_seek, @@ -829,7 +830,7 @@ ma_result zaudioEncoderCreate( ){ assert(user_data != NULL && config != NULL && out_handle != NULL); *out_handle = s_mem.onMalloc(sizeof(ma_encoder), s_mem.pUserData); - ma_result res = ma_encoder_init(on_write, on_seek, user_data,config, *out_handle); + ma_result res = ma_encoder_init(on_write, on_seek, user_data, config, *out_handle); if (res != MA_SUCCESS){ s_mem.onFree(*out_handle, s_mem.pUserData); *out_handle = NULL; @@ -843,7 +844,7 @@ ma_result zaudioEncoderCreateFromVfs( const ma_encoder_config* config, ma_encoder** out_handle ){ - assert(vfs != NULL && file_path != NULL && config != NULL, out_handle != NULL); + assert(vfs != NULL && file_path != NULL && config != NULL && out_handle != NULL); *out_handle = s_mem.onMalloc(sizeof(ma_encoder), s_mem.pUserData); ma_result res = ma_encoder_init_vfs(vfs, file_path, config, *out_handle); if(res != MA_SUCCESS){ @@ -856,9 +857,9 @@ ma_result zaudioEncoderCreateFromVfs( ma_result zaudioEncoderCreateFromFile( const char* file_path, const ma_encoder_config* config, - ma_encoder* out_handle + ma_encoder** out_handle ){ - assert(file_path != NULL, config != NULL, out_handle != NULL); + assert(file_path != NULL && config != NULL && out_handle != NULL); *out_handle = s_mem.onMalloc(sizeof(ma_encoder), s_mem.pUserData); ma_result res = ma_encoder_init_file(file_path, config, out_handle); if (res != MA_SUCCESS){ @@ -876,5 +877,10 @@ void zaudioEncoderDestroy( s_mem.onFree(handle, s_mem.pUserData); } +void* zaudioEncoderGetUserData(ma_encoder* handle) { + assert(handle != NULL); + return handle->pUserData; +} + //-------------------------------------------------------------------------------------------------- diff --git a/src/zaudio.zig b/src/zaudio.zig index 5e2619f..80529af 100644 --- a/src/zaudio.zig +++ b/src/zaudio.zig @@ -771,6 +771,10 @@ pub const DataConverter = opaque { } extern fn zaudioDataConverterCreate(config: *const Config, handle: ?*?*DataConverter) Result; + /// Please don't use the .len of the buffer slice since frame count is different from the actual sample buffer size + /// which your actual buffer size is equal to frame size multiplied by the number of channel, so you should get the + /// frame_count_xxx with using either getRequiredInputFrameCount() or getExpectedOutputFrameCount() as a var, + /// or you will get an incomplete or even a corrupted sample pub fn processPcmFrames( converter: *DataConverter, frames_in: *anyopaque, @@ -1088,20 +1092,61 @@ pub const decoderTellProc = fn (decoder: *Decoder, cursor: *i64) callconv(.c) Re //-------------------------------------------------------------------------------------------------- pub const Encoder = opaque { + pub const destroy = zaudioEncoderDestroy; + extern fn zaudioEncoderDestroy(handle: *Encoder) void; + + pub fn create(encoder_on_write: encoderWriteProc, encoder_on_seek: encoderSeekProc, user_data: *anyopaque, config: Config) Error!*Encoder { + var handle: ?*Encoder = null; + try maybeError(zaudioEncoderCreate(encoder_on_write, encoder_on_seek, user_data, &config, &handle)); + return handle.?; + } + extern fn zaudioEncoderCreate(on_write: encoderWriteProc, on_seek: encoderSeekProc, user_data: *anyopaque, config: *const Config, out_handle: ?*?*Encoder) Result; + + pub fn createFromVfs(vfs: *Vfs, file_path: []const u8, config: Config) Error!*Encoder { + var handle: ?*Encoder = null; + try maybeError(zaudioEncoderCreateFromVfs(vfs, file_path.ptr, &config, &handle)); + return handle.?; + } + extern fn zaudioEncoderCreateFromVfs(vfs: *Vfs, file_path: [*]const u8, config: *const Config, out_handle: ?*?*Encoder) Result; + + pub fn createFromFile(file_path: []const u8, config: Config) Error!*Encoder { + var handle: ?*Encoder = null; + try maybeError(zaudioEncoderCreateFromFile(file_path.ptr, &config, &handle)); + return handle.?; + } + extern fn zaudioEncoderCreateFromFile(file_path: [*]const u8, config: *const Config, handle: ?*?*Encoder) Result; + + pub fn writePcmFrame(encoder: *Encoder, frames_in: *anyopaque, frames_count: u64) Error!u64 { + var frames_written: u64 = undefined; + try maybeError(ma_encoder_write_pcm_frames(encoder, frames_in, frames_count, &frames_written)); + return frames_written; + } + extern fn ma_encoder_write_pcm_frames(encoder: *Encoder, frames_in: *anyopaque, frame_count: u64, frames_written: *u64) Result; + + pub const getUserData = zaudioEncoderGetUserData; + extern fn zaudioEncoderGetUserData(device: *const Encoder) ?*anyopaque; + pub const Config = extern struct { encoding_format: EncodingFormat, format: Format, channels: u32, sample_rate: u32, allocation_callbacks: AllocationCallbacks, + + pub fn init(encoding_format: EncodingFormat, format: Format, channels: u32, sample_rate: u32) Config { + var config: Config = undefined; + zaudioEncoderConfigInit(encoding_format, format, channels, sample_rate, &config); + return config; + } + extern fn zaudioEncoderConfigInit(encoding_format: EncodingFormat, format: Format, channel: u32, sample_rate: u32, out_config: *Config) void; }; }; -pub const encoderWriteProc = fn (encoder: *Encoder, buffer_in: *anyopaque, bytes_to_write: usize, bytes_written: *usize) callconv(.c) Result; -pub const encoderSeekProc = fn (encoder: *Encoder, offset: i64, origin: Vfs.SeekOrigin) callconv(.c) Result; -pub const encoderInitProc = fn (encoder: *Encoder) callconv(.c) Result; -pub const encoderUninitProc = fn (encoder: *Encoder) callconv(.c) void; -pub const encoderWritePcmFrameProc = fn (encoder: *Encoder, frames_in: *anyopaque, frame_count: u64, frames_written: *u64) callconv(.c) Result; +pub const encoderWriteProc = *const fn (encoder: *Encoder, buffer_in: *anyopaque, bytes_to_write: usize, bytes_written: *usize) callconv(.c) Result; +pub const encoderSeekProc = *const fn (encoder: *Encoder, offset: i64, origin: Vfs.SeekOrigin) callconv(.c) Result; +pub const encoderInitProc = *const fn (encoder: *Encoder) callconv(.c) Result; +pub const encoderUninitProc = *const fn (encoder: *Encoder) callconv(.c) void; +pub const encoderWritePcmFrameProc = *const fn (encoder: *Encoder, frames_in: *anyopaque, frame_count: u64, frames_written: *u64) callconv(.c) Result; //-------------------------------------------------------------------------------------------------- // @@ -3383,6 +3428,145 @@ test "zaudio.audio_buffer" { std.Thread.sleep(1e8); } +test "zaudio.data_converter" { + init(std.testing.allocator); + defer deinit(); + + var ctrl_upward_saw_source = std.mem.zeroes([256]u8); + for (0..256) |i| { + ctrl_upward_saw_source[i] = @intCast(i); + } + var ctrl_upward_saw: []u8 = ctrl_upward_saw_source[0..ctrl_upward_saw_source.len]; + + var data_conv_cfg = DataConverter.Config.init(.unsigned8, .float32, 1, 2, 48000, 96000); + try expect(data_conv_cfg.format_in == .unsigned8); + try expect(data_conv_cfg.format_out == .float32); + try expect(data_conv_cfg.channels_in == 1); + try expect(data_conv_cfg.channels_out == 2); + try expect(data_conv_cfg.sample_rate_in == 48000); + try expect(data_conv_cfg.sample_rate_out == 96000); + + // remove load pass filter in the converter + data_conv_cfg.resampling.linear.lpf_order = 0; + + var data_conv = try DataConverter.create(data_conv_cfg); + defer data_conv.destroy(); + + var expected_frame_cnt: u64 = try data_conv.getExpectedOutputFrameCount(ctrl_upward_saw_source.len); + try expect(expected_frame_cnt == ctrl_upward_saw_source.len * 2); // doubled the sample rate, doubled the frames + + const new_upward_saw = try std.testing.allocator.alloc(f32, expected_frame_cnt * data_conv_cfg.channels_out); + defer std.testing.allocator.free(new_upward_saw); + + try data_conv.processPcmFrames(@ptrCast(ctrl_upward_saw.ptr), &ctrl_upward_saw.len, @ptrCast(new_upward_saw.ptr), &expected_frame_cnt); + + // to validate the result samples, since the conversion process has latency, we need to offset the samples by the latency value + const input_latency = data_conv.getInputLatency(); + const output_latency = data_conv.getOutPutLatency(); + var prev_sample = -std.math.floatMax(f32); + + for ((input_latency + output_latency)..new_upward_saw.len) |i| { + if (i % 2 == 1) continue; + try expect(new_upward_saw[i] > prev_sample); + try expect(new_upward_saw[i] == new_upward_saw[i + 1]); + prev_sample = new_upward_saw[i]; + } +} + +const TestingEncodedStorage = struct { + const Self = @This(); + data_allocator: std.mem.Allocator, + buffer: []u8, + writer: std.fs.File.Writer = undefined, + interface: std.Io.Writer = undefined, + + pub fn init(allocator_in: std.mem.Allocator) !Self { + return Self{ + .data_allocator = allocator_in, + .buffer = try allocator_in.alloc(u8, 1), + }; + } + + pub fn deinit(self: Self) void { + self.data_allocator.free(self.buffer); + } + + pub fn updateInterface(self: *Self, offset: i64) void { + self.writer = std.fs.File.stdout().writer(self.buffer); + self.interface = self.writer.interface; + self.interface.advance(@intCast(offset)); + } + + pub fn writeSlice(self: *Self, input: []u8) !usize { + const former_end = self.interface.end; + if (former_end + input.len >= self.buffer.len) { + self.buffer = try self.data_allocator.realloc(self.buffer, former_end + input.len); + self.updateInterface(@intCast(former_end)); + } + return try self.interface.write(input); + } +}; + +fn testing_on_write(encoder: *Encoder, buffer_in: *anyopaque, bytes_to_write: usize, bytes_written: *usize) callconv(.c) Result { + const buffer_in_data: [*]u8 = @ptrCast(@alignCast(buffer_in)); + const result_buffer: *TestingEncodedStorage = @ptrCast(@alignCast(encoder.getUserData())); + _ = result_buffer.writeSlice(buffer_in_data[0..bytes_to_write]) catch return Result.out_of_memory; + bytes_written.* += bytes_to_write; + + return Result.success; +} + +fn testing_on_seek(encoder: *Encoder, offset: i64, _: Vfs.SeekOrigin) callconv(.c) Result { + const result_buffer: *TestingEncodedStorage = @ptrCast(@alignCast(encoder.getUserData())); + result_buffer.updateInterface(offset); + return Result.success; +} + +test "zaudio.encoder_decoder_roundtrip" { + init(std.testing.allocator); + defer deinit(); + + var encoder_result = try TestingEncodedStorage.init(std.testing.allocator); + defer encoder_result.deinit(); + + const encoder_cfg = Encoder.Config.init(.wav, .unsigned8, 1, 44100); + try expect(encoder_cfg.channels == 1); + try expect(encoder_cfg.encoding_format == .wav); + try expect(encoder_cfg.format == .unsigned8); + try expect(encoder_cfg.sample_rate == 44100); + + var ctrl_upward_saw_source = std.mem.zeroes([256]u8); + for (0..256) |i| { + ctrl_upward_saw_source[i] = @intCast(i); + } + + { // the encoder.destroy() includes writing the file size for the wav file, so we need a block for the defer + var encoder = try Encoder.create(testing_on_write, testing_on_seek, @ptrCast(&encoder_result), encoder_cfg); + defer encoder.destroy(); + + _ = try encoder.writePcmFrame(@ptrCast(&ctrl_upward_saw_source), ctrl_upward_saw_source.len); + } + + // this proves .wav has successfully generated + const result_list = encoder_result.buffer; + + try expect(std.mem.eql(u8, "RIFF", result_list[0..4])); + try expect(std.mem.eql(u8, "WAVEfmt ", result_list[8..16])); + // The first 8 bytes are not include in the file_size of the .wav format, so we need to add it back before comparing the slice length + const file_size: usize = @as(usize, @intCast(result_list[4])) + (@as(usize, @intCast(result_list[5])) * 256) + 8; + try expect(file_size == result_list.len); + + // Decoder Tests + const decoder_cfg = Decoder.Config.init(.unsigned8, 1, 44100); + try expect(decoder_cfg.format == .unsigned8); + try expect(decoder_cfg.channels == 1); + try expect(decoder_cfg.sample_rate == 44100); + + const decoder = try Decoder.createFromMemory(@ptrCast(result_list.ptr), result_list.len, decoder_cfg); + defer decoder.destroy(); + std.debug.print("available frames: {d}\n", .{try decoder.getAvailableFrames()}); +} + test { std.testing.refAllDecls(@This()); } From f90774c0839b65f626f31aa364407a164b114835 Mon Sep 17 00:00:00 2001 From: Logickin-Lambda Date: Mon, 22 Sep 2025 21:29:59 +0800 Subject: [PATCH 16/20] - Decoder has also been tested! Finally, thanks to the encoder type, we have some test coverage to the decoder as well, and it should includes most of the commonly used functions. --- README.md | 60 +++++++++++++++++++++++++++++++++++++++++++++++++- src/zaudio.zig | 39 +++++++++++++++++++++++++++----- 2 files changed, 92 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 95ac123..3d5ef8c 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ Provided structs: - [x] `DelayNode` - [x] custom nodes - [x] `Decoder` (missing methods) +- [x] `Encoder` (missing methods) - [x] `DataConverter` ## Getting started @@ -50,7 +51,7 @@ pub fn build(b: *std.Build) void { } ``` -Now in your code you may import and use `zaudio`: +Now in your code you may import and use the high level API of `zaudio`: ```zig const zaudio = @import("zaudio"); @@ -72,3 +73,60 @@ pub fn main() !void { ... } ``` + +Or use the low level API which is similar to the original miniaudio library, but because the callback function is bridged from the original C library, you must explicitly handle the errors at the callback level: + +```zig +const zaudio = @import("zaudio"); + +pub fn main() !void { + ... + zaudio.init(std.heap.smp_allocator); + defer zaudio.deinit(); + + const decoder_config = zaudio.Decoder.Config.initDefault(); + var mp3_decoder = try zaudio.Decoder.createFromFile("testing_media/Accipiter Supersaw Demo.mp3", decoder_config); + defer mp3_decoder.destroy(); + + // device + var device_config = zaudio.Device.Config.init(.playback); + device_config.playback.format = zaudio.Format.float32; + device_config.playback.channels = 2; + device_config.sample_rate = SAMPLE_RATE; + device_config.data_callback = data_callback; // we will fill that with actual signal source + device_config.user_data = mp3_decoder; + + const device = zaudio.Device.create(null, device_config) catch { + @panic("Failed to open playback device"); + }; + defer device.destroy(); + + zaudio.Device.start(device) catch { + zaudio.Device.destroy(device); + @panic("Failed to start playback device"); + }; + ... +} + +fn data_callback(device: *zaudio.Device, pOutput: ?*anyopaque, _: ?*const anyopaque, frame_count: u32) callconv(.c) void { + const decoder_opt: ?*zaudio.Decoder = @ptrCast(device.getUserData()); + + if (decoder_opt) |decoder| { + var frames_read: u64 = 0; + + _ = try decoder.readPCMFrames(pOutput.?, frame_count) catch |err| { + std.debug.print("ERROR: {any}", .{err}); + return; + }; + + if (frames_read < frame_count) { + decoder.seekToPCMFrames(0) catch { + @panic("cannot seek"); + }; + } + } else { + return; + } +} + +``` \ No newline at end of file diff --git a/src/zaudio.zig b/src/zaudio.zig index 80529af..1855ac9 100644 --- a/src/zaudio.zig +++ b/src/zaudio.zig @@ -960,8 +960,10 @@ pub const Decoder = opaque { } extern fn zaudioDecoderCreateFromFile(file_path: [*:0]const u8, config: *const Config, out_handle: ?*?*Decoder) Result; - pub fn readPCMFrames(decoder: *Decoder, frame_out: *anyopaque, frames_count: u64, frames_read: ?*u64) Error!void { - try maybeError(ma_decoder_read_pcm_frames(decoder, frame_out, frames_count, frames_read)); + pub fn readPCMFrames(decoder: *Decoder, frame_out: *anyopaque, frames_count: u64) Error!u64 { + var frames_read: u64 = undefined; + try maybeError(ma_decoder_read_pcm_frames(decoder, frame_out, frames_count, &frames_read)); + return frames_read; } extern fn ma_decoder_read_pcm_frames(decoder: *Decoder, frame_out: *anyopaque, frames_count: u64, frames_read: ?*u64) Result; @@ -3549,14 +3551,12 @@ test "zaudio.encoder_decoder_roundtrip" { // this proves .wav has successfully generated const result_list = encoder_result.buffer; - try expect(std.mem.eql(u8, "RIFF", result_list[0..4])); try expect(std.mem.eql(u8, "WAVEfmt ", result_list[8..16])); - // The first 8 bytes are not include in the file_size of the .wav format, so we need to add it back before comparing the slice length + // The first 8 bytes are not include in the file_size of the .wav format, so we need to +8 for comparison const file_size: usize = @as(usize, @intCast(result_list[4])) + (@as(usize, @intCast(result_list[5])) * 256) + 8; try expect(file_size == result_list.len); - // Decoder Tests const decoder_cfg = Decoder.Config.init(.unsigned8, 1, 44100); try expect(decoder_cfg.format == .unsigned8); try expect(decoder_cfg.channels == 1); @@ -3564,7 +3564,34 @@ test "zaudio.encoder_decoder_roundtrip" { const decoder = try Decoder.createFromMemory(@ptrCast(result_list.ptr), result_list.len, decoder_cfg); defer decoder.destroy(); - std.debug.print("available frames: {d}\n", .{try decoder.getAvailableFrames()}); + try expect(try decoder.getAvailableFrames() == 256); + + var format: Format = .unknown; + var num_channels: u32 = 0; + var sample_rate: u32 = 0; + try decoder.getDataFormat(&format, &num_channels, &sample_rate, null); + try expect(format == .unsigned8); + try expect(num_channels == 1); + try expect(sample_rate == 44100); + + var ctrl_upward_saw_distination = std.mem.zeroes([256]u8); + const frames_read = try decoder.readPCMFrames(@ptrCast(&ctrl_upward_saw_distination), ctrl_upward_saw_distination.len); + try expect(frames_read == ctrl_upward_saw_distination.len); + + // after encoding and decoding, the decoded result should be identical to the original, generated upward saw sample. + for (ctrl_upward_saw_source, ctrl_upward_saw_distination) |src, dst| { + try expect(src == dst); + } + + // this should change the cursor into the middle of the sample + try decoder.seekToPCMFrames(128); + var ctrl_upward_saw_distination_half = std.mem.zeroes([128]u8); + const frames_read_half = try decoder.readPCMFrames(@ptrCast(&ctrl_upward_saw_distination_half), ctrl_upward_saw_distination_half.len); + try expect(frames_read_half == frames_read / 2); + + for (ctrl_upward_saw_distination_half, 0..) |half, i| { + try expect(half == ctrl_upward_saw_source[128 + i]); + } } test { From 80a8738b5e65375e4f346637cb7c15612e586722 Mon Sep 17 00:00:00 2001 From: Logickin-Lambda Date: Mon, 22 Sep 2025 21:58:36 +0800 Subject: [PATCH 17/20] - Added getUserData for Decoder Since Decoder and Encoder is similar, if the custom onWrite and onSeek function requires user_data, the Decoder.create() function might also need the user_data because it also have its own callback for decoding files. --- src/zaudio.c | 5 +++++ src/zaudio.zig | 3 +++ 2 files changed, 8 insertions(+) diff --git a/src/zaudio.c b/src/zaudio.c index ae3420f..558bc66 100644 --- a/src/zaudio.c +++ b/src/zaudio.c @@ -808,6 +808,11 @@ void zaudioDecoderDestroy( s_mem.onFree(handle, s_mem.pUserData); } +void* zaudioDecoderGetUserData(ma_decoder* handle) { + assert(handle != NULL); + return handle->pUserData; +} + //-------------------------------------------------------------------------------------------------- void zaudioEncoderConfigInit( ma_encoding_format encoding_format, diff --git a/src/zaudio.zig b/src/zaudio.zig index 1855ac9..2b7f80b 100644 --- a/src/zaudio.zig +++ b/src/zaudio.zig @@ -1047,6 +1047,9 @@ pub const Decoder = opaque { ) callconv(.c) void, }; + pub const getUserData = zaudioDecoderGetUserData; + extern fn zaudioDecoderGetUserData(device: *const Encoder) ?*anyopaque; + pub const Config = extern struct { format: Format, channels: u32, From bc4e4746301ebd558fc0e02a436a3914914d56de Mon Sep 17 00:00:00 2001 From: Logickin-Lambda Date: Tue, 23 Sep 2025 09:52:56 +0800 Subject: [PATCH 18/20] - Commit for code for testing in linux I just found that it is not possible to retrieve Writer.Interface.end for linux, and it will return an unknown value that break the buffer writer; thus, for the time being, let me replace that with an in-house implementation just to support the seek behavior for miniaudio. --- src/zaudio.zig | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/zaudio.zig b/src/zaudio.zig index 2b7f80b..29ab0d4 100644 --- a/src/zaudio.zig +++ b/src/zaudio.zig @@ -3482,8 +3482,7 @@ const TestingEncodedStorage = struct { const Self = @This(); data_allocator: std.mem.Allocator, buffer: []u8, - writer: std.fs.File.Writer = undefined, - interface: std.Io.Writer = undefined, + cursor: usize = 0, pub fn init(allocator_in: std.mem.Allocator) !Self { return Self{ @@ -3496,19 +3495,20 @@ const TestingEncodedStorage = struct { self.data_allocator.free(self.buffer); } - pub fn updateInterface(self: *Self, offset: i64) void { - self.writer = std.fs.File.stdout().writer(self.buffer); - self.interface = self.writer.interface; - self.interface.advance(@intCast(offset)); + pub fn seek(self: *Self, position: usize) void { + self.cursor = position; } pub fn writeSlice(self: *Self, input: []u8) !usize { - const former_end = self.interface.end; - if (former_end + input.len >= self.buffer.len) { - self.buffer = try self.data_allocator.realloc(self.buffer, former_end + input.len); - self.updateInterface(@intCast(former_end)); + const former_cursor = self.cursor; + if (self.cursor + input.len >= self.buffer.len) { + self.buffer = try self.data_allocator.realloc(self.buffer, self.cursor + input.len); } - return try self.interface.write(input); + for (input) |char| { + self.buffer[self.cursor] = char; + self.cursor += 1; + } + return self.cursor - former_cursor; } }; @@ -3523,7 +3523,7 @@ fn testing_on_write(encoder: *Encoder, buffer_in: *anyopaque, bytes_to_write: us fn testing_on_seek(encoder: *Encoder, offset: i64, _: Vfs.SeekOrigin) callconv(.c) Result { const result_buffer: *TestingEncodedStorage = @ptrCast(@alignCast(encoder.getUserData())); - result_buffer.updateInterface(offset); + result_buffer.seek(@intCast(offset)); return Result.success; } From 1f91bcfea0eb67e6f544c57ed72b6b247346bdd5 Mon Sep 17 00:00:00 2001 From: Logickin-Lambda Date: Tue, 23 Sep 2025 10:36:14 +0800 Subject: [PATCH 19/20] - Cleared the code for the testing type It turns out @memmove can do the string copy as long as I have slice the buffer with the correct index, and this is not java which I don't need to build a setter to set the cursor, so I have removed the seek function and access the cursor directly, to reduce the amount of unnecessary code. --- src/zaudio.zig | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/src/zaudio.zig b/src/zaudio.zig index 29ab0d4..c7ab577 100644 --- a/src/zaudio.zig +++ b/src/zaudio.zig @@ -3495,35 +3495,26 @@ const TestingEncodedStorage = struct { self.data_allocator.free(self.buffer); } - pub fn seek(self: *Self, position: usize) void { - self.cursor = position; - } - pub fn writeSlice(self: *Self, input: []u8) !usize { - const former_cursor = self.cursor; if (self.cursor + input.len >= self.buffer.len) { self.buffer = try self.data_allocator.realloc(self.buffer, self.cursor + input.len); } - for (input) |char| { - self.buffer[self.cursor] = char; - self.cursor += 1; - } - return self.cursor - former_cursor; + @memmove(self.buffer[self.cursor .. self.cursor + input.len], input); + self.cursor += input.len; + return input.len; } }; fn testing_on_write(encoder: *Encoder, buffer_in: *anyopaque, bytes_to_write: usize, bytes_written: *usize) callconv(.c) Result { - const buffer_in_data: [*]u8 = @ptrCast(@alignCast(buffer_in)); const result_buffer: *TestingEncodedStorage = @ptrCast(@alignCast(encoder.getUserData())); - _ = result_buffer.writeSlice(buffer_in_data[0..bytes_to_write]) catch return Result.out_of_memory; - bytes_written.* += bytes_to_write; - + const buffer_in_data: [*]u8 = @ptrCast(@alignCast(buffer_in)); + bytes_written.* += result_buffer.writeSlice(buffer_in_data[0..bytes_to_write]) catch return Result.out_of_memory; return Result.success; } fn testing_on_seek(encoder: *Encoder, offset: i64, _: Vfs.SeekOrigin) callconv(.c) Result { const result_buffer: *TestingEncodedStorage = @ptrCast(@alignCast(encoder.getUserData())); - result_buffer.seek(@intCast(offset)); + result_buffer.cursor = @intCast(offset); return Result.success; } @@ -3581,12 +3572,12 @@ test "zaudio.encoder_decoder_roundtrip" { const frames_read = try decoder.readPCMFrames(@ptrCast(&ctrl_upward_saw_distination), ctrl_upward_saw_distination.len); try expect(frames_read == ctrl_upward_saw_distination.len); - // after encoding and decoding, the decoded result should be identical to the original, generated upward saw sample. + // after encoding and decoding, the decoded result should be identical to the original saw sample. for (ctrl_upward_saw_source, ctrl_upward_saw_distination) |src, dst| { try expect(src == dst); } - // this should change the cursor into the middle of the sample + // this should change the cursor to the middle of the sample try decoder.seekToPCMFrames(128); var ctrl_upward_saw_distination_half = std.mem.zeroes([128]u8); const frames_read_half = try decoder.readPCMFrames(@ptrCast(&ctrl_upward_saw_distination_half), ctrl_upward_saw_distination_half.len); From 1e8cec70d64a52de68a5d0bfa1aa7e0f7d9bcb2e Mon Sep 17 00:00:00 2001 From: Logickin-Lambda Date: Sat, 27 Sep 2025 09:20:32 +0800 Subject: [PATCH 20/20] - Corrected Errors Reported By the Copilot --- src/zaudio.c | 2 +- src/zaudio.zig | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/zaudio.c b/src/zaudio.c index 558bc66..2a59fe5 100644 --- a/src/zaudio.c +++ b/src/zaudio.c @@ -866,7 +866,7 @@ ma_result zaudioEncoderCreateFromFile( ){ assert(file_path != NULL && config != NULL && out_handle != NULL); *out_handle = s_mem.onMalloc(sizeof(ma_encoder), s_mem.pUserData); - ma_result res = ma_encoder_init_file(file_path, config, out_handle); + ma_result res = ma_encoder_init_file(file_path, config, *out_handle); if (res != MA_SUCCESS){ s_mem.onFree(*out_handle, s_mem.pUserData); *out_handle = NULL; diff --git a/src/zaudio.zig b/src/zaudio.zig index c7ab577..1d0c013 100644 --- a/src/zaudio.zig +++ b/src/zaudio.zig @@ -1048,7 +1048,7 @@ pub const Decoder = opaque { }; pub const getUserData = zaudioDecoderGetUserData; - extern fn zaudioDecoderGetUserData(device: *const Encoder) ?*anyopaque; + extern fn zaudioDecoderGetUserData(device: *const Decoder) ?*anyopaque; pub const Config = extern struct { format: Format,