From a223340c4498f49633d4108bb1ea5024071b1d53 Mon Sep 17 00:00:00 2001 From: bkrypt <4868093+bkrypt@users.noreply.github.com> Date: Fri, 15 Apr 2022 13:38:58 +0200 Subject: [PATCH 1/7] Update `vendor/miniaudio` to v0.11.9 --- vendor/miniaudio/common.odin | 108 +- vendor/miniaudio/data_conversion.odin | 291 +- vendor/miniaudio/decoding.odin | 102 +- vendor/miniaudio/device_io_procs.odin | 786 +- vendor/miniaudio/device_io_types.odin | 365 +- vendor/miniaudio/doc.odin | 2955 +- vendor/miniaudio/effects.odin | 300 + vendor/miniaudio/encoding.odin | 23 +- vendor/miniaudio/engine.odin | 341 + vendor/miniaudio/filtering.odin | 129 +- vendor/miniaudio/generation.odin | 28 +- vendor/miniaudio/job_queue.odin | 239 + vendor/miniaudio/lib/miniaudio.lib | Bin 1873426 -> 2767136 bytes vendor/miniaudio/logging.odin | 30 + vendor/miniaudio/node_graph.odin | 469 + vendor/miniaudio/resource_manager.odin | 288 + vendor/miniaudio/src/miniaudio.h | 34632 ++++++++++++++++++----- vendor/miniaudio/synchronization.odin | 152 + vendor/miniaudio/utilities.odin | 175 +- vendor/miniaudio/vfs.odin | 10 +- 20 files changed, 32998 insertions(+), 8425 deletions(-) create mode 100644 vendor/miniaudio/effects.odin create mode 100644 vendor/miniaudio/engine.odin create mode 100644 vendor/miniaudio/job_queue.odin create mode 100644 vendor/miniaudio/node_graph.odin create mode 100644 vendor/miniaudio/resource_manager.odin create mode 100644 vendor/miniaudio/synchronization.odin diff --git a/vendor/miniaudio/common.odin b/vendor/miniaudio/common.odin index 89e3d6bd2..d7b901714 100644 --- a/vendor/miniaudio/common.odin +++ b/vendor/miniaudio/common.odin @@ -13,13 +13,15 @@ when ODIN_OS == .Windows { handle :: distinct rawptr -/* SIMD alignment in bytes. Currently set to 64 bytes in preparation for future AVX-512 optimizations. */ -SIMD_ALIGNMENT :: 64 +/* SIMD alignment in bytes. Currently set to 32 bytes in preparation for future AVX optimizations. */ +SIMD_ALIGNMENT :: 32 -LOG_LEVEL_DEBUG :: 4 -LOG_LEVEL_INFO :: 3 -LOG_LEVEL_WARNING :: 2 -LOG_LEVEL_ERROR :: 1 +log_level :: enum c.int { + LOG_LEVEL_DEBUG = 4, + LOG_LEVEL_INFO = 3, + LOG_LEVEL_WARNING = 2, + LOG_LEVEL_ERROR = 1, +} channel :: enum u8 { @@ -158,13 +160,13 @@ result :: enum c.int { FAILED_TO_STOP_BACKEND_DEVICE = -303, } + MIN_CHANNELS :: 1 -MAX_CHANNELS :: 32 +MAX_CHANNELS :: 254 MAX_FILTER_ORDER :: 8 - stream_format :: enum c.int { pcm = 0, } @@ -175,9 +177,9 @@ stream_layout :: enum c.int { } dither_mode :: enum c.int { - none = 0, - rectangle, - triangle, + none = 0, + rectangle, + triangle, } format :: enum c.int { @@ -191,6 +193,7 @@ format :: enum c.int { s24 = 3, /* Tightly packed. 3 bytes per sample. */ s32 = 4, f32 = 5, + count, } standard_sample_rate :: enum u32 { @@ -224,7 +227,6 @@ channel_mix_mode :: enum c.int { rectangular = 0, /* Simple averaging based on the plane(s) the channel is sitting on. */ simple, /* Drop excess channels; zeroed out extra channels. */ custom_weights, /* Use custom weights specified in ma_channel_router_config. */ - planar_blend = rectangular, default = rectangular, } @@ -257,6 +259,10 @@ lcg :: struct { state: i32, } + +/* Spinlocks are 32-bit for compatibility reasons. */ +spinlock :: distinct u32 + NO_THREADING :: false when !NO_THREADING { @@ -272,8 +278,6 @@ thread_priority :: enum c.int { default = 0, } -/* Spinlocks are 32-bit for compatibility reasons. */ -spinlock :: distinct u32 when ODIN_OS == .Windows { thread :: distinct rawptr @@ -297,69 +301,6 @@ when ODIN_OS == .Windows { } } - -@(default_calling_convention="c", link_prefix="ma_") -foreign lib { - /* - Locks a spinlock. - */ - spinlock_lock :: proc(/*volatile*/ pSpinlock: ^spinlock) -> result --- - - /* - Locks a spinlock, but does not yield() when looping. - */ - spinlock_lock_noyield :: proc(/*volatile*/ pSpinlock: ^spinlock) -> result --- - - /* - Unlocks a spinlock. - */ - spinlock_unlock :: proc(/*volatile*/ pSpinlock: ^spinlock) -> result --- - - - /* - Creates a mutex. - - A mutex must be created from a valid context. A mutex is initially unlocked. - */ - mutex_init :: proc(pMutex: ^mutex) -> result --- - - /* - Deletes a mutex. - */ - mutex_uninit :: proc(pMutex: ^mutex) --- - - /* - Locks a mutex with an infinite timeout. - */ - mutex_lock :: proc(pMutex: ^mutex) --- - - /* - Unlocks a mutex. - */ - mutex_unlock :: proc(pMutex: ^mutex) --- - - - /* - Initializes an auto-reset event. - */ - event_init :: proc(pEvent: ^event) -> result --- - - /* - Uninitializes an auto-reset event. - */ - event_uninit :: proc(pEvent: ^event) --- - - /* - Waits for the specified auto-reset event to become signalled. - */ - event_wait :: proc(pEvent: ^event) -> result --- - - /* - Signals the specified auto-reset event. - */ - event_signal :: proc(pEvent: ^event) -> result --- -} - } /* NO_THREADING */ @@ -385,17 +326,22 @@ foreign lib { result_description :: proc(result: result) -> cstring --- /* - malloc(). Calls MA_MALLOC(). + malloc() */ malloc :: proc(sz: c.size_t, pAllocationCallbacks: ^allocation_callbacks) -> rawptr --- /* - realloc(). Calls MA_REALLOC(). + calloc() + */ + calloc :: proc(sz: c.size_t, pAllocationCallbacks: ^allocation_callbacks) -> rawptr --- + + /* + realloc() */ realloc :: proc(p: rawptr, sz: c.size_t, pAllocationCallbacks: ^allocation_callbacks) -> rawptr --- /* - free(). Calls MA_FREE(). + free() */ free :: proc(p: rawptr, pAllocationCallbacks: ^allocation_callbacks) --- @@ -417,7 +363,7 @@ foreign lib { /* Blends two frames in floating point format. */ - blend_f32 :: proc(pOut, pInA, pInB: ^f32, factor: f32, channels: u32) --- + blend_f32 :: proc(pOut, pInA, pInB: [^]f32, factor: f32, channels: u32) --- /* Retrieves the size of a sample in bytes for the given format. diff --git a/vendor/miniaudio/data_conversion.odin b/vendor/miniaudio/data_conversion.odin index 7167270a1..ffcf2fcb3 100644 --- a/vendor/miniaudio/data_conversion.odin +++ b/vendor/miniaudio/data_conversion.odin @@ -36,77 +36,106 @@ linear_resampler_config :: struct { } linear_resampler :: struct { - config: linear_resampler_config, + config: linear_resampler_config, inAdvanceInt: u32, inAdvanceFrac: u32, inTimeInt: u32, inTimeFrac: u32, x0: struct #raw_union { - f32: [MAX_CHANNELS]f32, - s16: [MAX_CHANNELS]i16, + f32: [^]f32, + s16: [^]i16, }, /* The previous input frame. */ x1: struct #raw_union { - f32: [MAX_CHANNELS]f32, - s16: [MAX_CHANNELS]i16, + f32: [^]f32, + s16: [^]i16, }, /* The next input frame. */ lpf: lpf, + + /* Memory management. */ + _pHeap: rawptr, + _ownsHeap: b32, +} + +resampling_backend :: struct {} +resampling_backend_vtable :: struct { + onGetHeapSize: proc "c" (pUserData: rawptr, pConfig: ^resampler_config, pHeapSizeInBytes: ^c.size_t) -> result, + onInit: proc "c" (pUserData: rawptr, pConfig: ^resampler_config, pHeap: rawptr, ppBackend: ^^resampling_backend) -> result, + onUninit: proc "c" (pUserData: rawptr, pBackend: ^resampling_backend, pAllocationCallbacks: ^allocation_callbacks), + onProcess: proc "c" (pUserData: rawptr, pBackend: ^resampling_backend, pFramesIn: rawptr, pFrameCountIn: ^u64, pFramesOut: rawptr, pFrameCountOut: ^u64) -> result, + onSetRate: proc "c" (pUserData: rawptr, pBackend: ^resampling_backend, sampleRateIn: u32, sampleRateOut: u32) -> result, /* Optional. Rate changes will be disabled. */ + onGetInputLatency: proc "c" (pUserData: rawptr, pBackend: ^resampling_backend) -> u64, /* Optional. Latency will be reported as 0. */ + onGetOutputLatency: proc "c" (pUserData: rawptr, pBackend: ^resampling_backend) -> u64, /* Optional. Latency will be reported as 0. */ + onGetRequiredInputFrameCount: proc "c" (pUserData: rawptr, pBackend: ^resampling_backend, outputFrameCount: u64, pInputFrameCount: ^u64) -> result, /* Optional. Latency mitigation will be disabled. */ + onGetExpectedOutputFrameCount: proc "c" (pUserData: rawptr, pBackend: ^resampling_backend, inputFrameCount: u64, pOutputFrameCount: ^u64) -> result, /* Optional. Latency mitigation will be disabled. */ + onReset: proc "c" (pUserData: rawptr, pBackend: ^resampling_backend) -> result, } resample_algorithm :: enum { linear = 0, /* Fastest, lowest quality. Optional low-pass filtering. Default. */ - speex, + custom, } resampler_config :: struct { - format: format, /* Must be either ma_format_f32 or ma_format_s16. */ - channels: u32, - sampleRateIn: u32, - sampleRateOut: u32, - algorithm: resample_algorithm, + format: format, /* Must be either ma_format_f32 or ma_format_s16. */ + channels: u32, + sampleRateIn: u32, + sampleRateOut: u32, + algorithm: resample_algorithm, /* When set to ma_resample_algorithm_custom, pBackendVTable will be used. */ + pBackendVTable: ^resampling_backend_vtable, + pBackendUserData: rawptr, linear: struct { lpfOrder: u32, - lpfNyquistFactor: f64, - }, - speex: struct { - quality: c.int, /* 0 to 10. Defaults to 3. */ }, } resampler :: struct { - config: resampler_config, + pBackend: ^resampling_backend, + pBackendVTable: ^resampling_backend_vtable, + pBackendUserData: rawptr, + format: format, + channels: u32, + sampleRateIn: u32, + sampleRateOut: u32, state: struct #raw_union { linear: linear_resampler, - speex: struct { - pSpeexResamplerState: rawptr, /* SpeexResamplerState* */ - }, - }, + }, /* State for stock resamplers so we can avoid a malloc. For stock resamplers, pBackend will point here. */ + + /* Memory management. */ + _pHeap: rawptr, + _ownsHeap: b32, } @(default_calling_convention="c", link_prefix="ma_") foreign lib { linear_resampler_config_init :: proc(format: format, channels: u32, sampleRateIn, sampleRateOut: u32) -> linear_resampler_config --- - linear_resampler_init :: proc(pConfig: ^linear_resampler_config, pResampler: ^linear_resampler) -> result --- - linear_resampler_uninit :: proc(pResampler: ^linear_resampler) --- + linear_resampler_get_heap_size :: proc(pConfig: ^linear_resampler_config, pHeapSizeInBytes: ^c.size_t) -> result --- + linear_resampler_init_preallocated :: proc(pConfig: ^linear_resampler_config, pHeap: rawptr, pResampler: ^linear_resampler) -> result --- + linear_resampler_init :: proc(pConfig: ^linear_resampler_config, pAllocationCallbacks: ^allocation_callbacks, pResampler: ^linear_resampler) -> result --- + linear_resampler_uninit :: proc(pResampler: ^linear_resampler, pAllocationCallbacks: ^allocation_callbacks) --- linear_resampler_process_pcm_frames :: proc(pResampler: ^linear_resampler, pFramesIn: rawptr, pFrameCountIn: ^u64, pFramesOut: rawptr, pFrameCountOut: ^u64) -> result --- linear_resampler_set_rate :: proc(pResampler: ^linear_resampler, sampleRateIn, sampleRateOut: u32) -> result --- linear_resampler_set_rate_ratio :: proc(pResampler: ^linear_resampler, ratioInOut: f32) -> result --- - linear_resampler_get_required_input_frame_count :: proc(pResampler: ^linear_resampler, outputFrameCount: u64) -> u64 --- - linear_resampler_get_expected_output_frame_count :: proc(pResampler: ^linear_resampler, inputFrameCount: u64) -> u64 --- linear_resampler_get_input_latency :: proc(pResampler: ^linear_resampler) -> u64 --- linear_resampler_get_output_latency :: proc(pResampler: ^linear_resampler) -> u64 --- + linear_resampler_get_required_input_frame_count :: proc(pResampler: ^linear_resampler, outputFrameCount: u64, pInputFrameCount: ^u64) -> result --- + linear_resampler_get_expected_output_frame_count :: proc(pResampler: ^linear_resampler, inputFrameCount: u64, pOutputFrameCount: ^u64) -> result --- + linear_resampler_reset :: proc(pResampler: ^linear_resampler) -> result --- resampler_config_init :: proc(format: format, channels: u32, sampleRateIn, sampleRateOut: u32, algorithm: resample_algorithm) -> resampler_config --- + resampler_get_heap_size :: proc(pConfig: ^resampler_config, pHeapSizeInBytes: ^c.size_t) -> result --- + resampler_init_preallocated :: proc(pConfig: ^resampler_config, pHeap: rawptr, pResampler: ^resampler) -> result --- + /* Initializes a new resampler object from a config. */ - resampler_init :: proc(pConfig: ^resampler_config, pResampler: ^resampler) -> result --- + resampler_init :: proc(pConfig: ^resampler_config, pAllocationCallbacks: ^allocation_callbacks, pResampler: ^resampler) -> result --- /* Uninitializes a resampler. */ - resampler_uninit :: proc(pResampler: ^resampler) --- + resampler_uninit :: proc(pResampler: ^resampler, pAllocationCallbacks: ^allocation_callbacks) --- /* Converts the given input data. @@ -145,23 +174,6 @@ foreign lib { */ resampler_set_rate_ratio :: proc(pResampler: ^resampler, ratio: f32) -> result --- - - /* - Calculates the number of whole input frames that would need to be read from the client in order to output the specified - number of output frames. - - The returned value does not include cached input frames. It only returns the number of extra frames that would need to be - read from the input buffer in order to output the specified number of output frames. - */ - resampler_get_required_input_frame_count :: proc(pResampler: ^resampler, outputFrameCount: u64) -> u64 --- - - /* - Calculates the number of whole output frames that would be output after fully reading and consuming the specified number of - input frames. - */ - resampler_get_expected_output_frame_count :: proc(pResampler: ^resampler, inputFrameCount: u64) -> u64 --- - - /* Retrieves the latency introduced by the resampler in input frames. */ @@ -171,6 +183,26 @@ foreign lib { Retrieves the latency introduced by the resampler in output frames. */ resampler_get_output_latency :: proc(pResampler: ^resampler) -> u64 --- + + /* + Calculates the number of whole input frames that would need to be read from the client in order to output the specified + number of output frames. + + The returned value does not include cached input frames. It only returns the number of extra frames that would need to be + read from the input buffer in order to output the specified number of output frames. + */ + resampler_get_required_input_frame_count :: proc(pResampler: ^resampler, outputFrameCount: u64, pInputFrameCount: ^u64) -> result --- + + /* + Calculates the number of whole output frames that would be output after fully reading and consuming the specified number of + input frames. + */ + resampler_get_expected_output_frame_count :: proc(pResampler: ^resampler, inputFrameCount: u64, pOutputFrameCount: ^u64) -> result --- + + /* + Resets the resampler's timer and clears it's internal cache. + */ + resampler_reset :: proc(pResampler: ^resampler) -> result --- } @@ -179,42 +211,63 @@ foreign lib { Channel Conversion **************************************************************************************************************************************************************/ +channel_conversion_path :: enum c.int { + unknown, + passthrough, + mono_out, /* Converting to mono. */ + mono_in, /* Converting from mono. */ + shuffle, /* Simple shuffle. Will use this when all channels are present in both input and output channel maps, but just in a different order. */ + weights, /* Blended based on weights. */ +} + +mono_expansion_mode :: enum c.int { + duplicate = 0, /* The default. */ + average, /* Average the mono channel across all channels. */ + stereo_only, /* Duplicate to the left and right channels only and ignore the others. */ + default = duplicate, +} + channel_converter_config :: struct { - format: format, - channelsIn: u32, - channelsOut: u32, - channelMapIn: [MAX_CHANNELS]channel, - channelMapOut: [MAX_CHANNELS]channel, - mixingMode: channel_mix_mode, - weights: [MAX_CHANNELS][MAX_CHANNELS]f32, /* [in][out]. Only used when mixingMode is set to ma_channel_mix_mode_custom_weights. */ + format: format, + channelsIn: u32, + channelsOut: u32, + pChannelMapIn: [^]channel, + pChannelMapOut: [^]channel, + mixingMode: channel_mix_mode, + ppWeights: ^[^]f32, /* [in][out]. Only used when mixingMode is set to ma_channel_mix_mode_custom_weights. */ } channel_converter :: struct { - format: format, - channelsIn: u32, - channelsOut: u32, - channelMapIn: [MAX_CHANNELS]channel, - channelMapOut: [MAX_CHANNELS]channel, - mixingMode: channel_mix_mode, - weights: struct #raw_union { - f32: [MAX_CHANNELS][MAX_CHANNELS]f32, - s16: [MAX_CHANNELS][MAX_CHANNELS]i32, + format: format, + channelsIn: u32, + channelsOut: u32, + mixingMode: channel_mix_mode, + conversionPath: channel_conversion_path, + pChannelMapIn: [^]channel, + pChannelMapOut: [^]channel, + pShuffleTable: [^]u8, + weights: struct #raw_union { /* [in][out] */ + f32: ^[^]f32, + s16: ^[^]i32, }, - isPassthrough: b8, - isSimpleShuffle: b8, - isSimpleMonoExpansion: b8, - isStereoToMono: b8, - shuffleTable: [MAX_CHANNELS]u8, + + /* Memory management. */ + _pHeap: rawptr, + _ownsHeap: b32, } @(default_calling_convention="c", link_prefix="ma_") foreign lib { - channel_converter_config_init :: proc(format: format, channelsIn: u32, pChannelMapIn: ^channel, channelsOut: u32, pChannelMapOut: ^channel, mixingMode: channel_mix_mode) -> channel_converter_config --- + channel_converter_config_init :: proc(format: format, channelsIn: u32, pChannelMapIn: [^]channel, channelsOut: u32, pChannelMapOut: [^]channel, mixingMode: channel_mix_mode) -> channel_converter_config --- - channel_converter_init :: proc(pConfig: ^channel_converter_config, pConverter: ^channel_converter) -> result --- - channel_converter_uninit :: proc(pConverter: ^channel_converter) --- - channel_converter_process_pcm_frames :: proc(pConverter: ^channel_converter, pFramesOut: rawptr, pFramesIn: rawptr, frameCount: u64) -> result --- + channel_converter_get_heap_size :: proc(pConfig: ^channel_converter_config, pHeapSizeInBytes: ^c.size_t) -> result --- + channel_converter_init_preallocated :: proc(pConfig: ^channel_converter_config, pHeap: rawptr, pConverter: ^channel_converter) -> result --- + channel_converter_init :: proc(pConfig: ^channel_converter_config, pAllocationCallbacks: ^allocation_callbacks, pConverter: ^channel_converter) -> result --- + channel_converter_uninit :: proc(pConverter: ^channel_converter, pAllocationCallbacks: ^allocation_callbacks) --- + channel_converter_process_pcm_frames :: proc(pConverter: ^channel_converter, pFramesOut, pFramesIn: rawptr, frameCount: u64) -> result --- + channel_converter_get_input_channel_map :: proc(pConverter: ^channel_converter, pChannelMap: [^]channel, channelMapCap: c.size_t) -> result --- + channel_converter_get_output_channel_map :: proc(pConverter: ^channel_converter, pChannelMap: [^]channel, channelMapCap: c.size_t) -> result --- } @@ -224,32 +277,39 @@ Data Conversion **************************************************************************************************************************************************************/ data_converter_config :: struct { - formatIn: format, - formatOut: format, - channelsIn: u32, - channelsOut: u32, - sampleRateIn: u32, - sampleRateOut: u32, - channelMapIn: [MAX_CHANNELS]channel, - channelMapOut: [MAX_CHANNELS]channel, - ditherMode: dither_mode, - channelMixMode: channel_mix_mode, - channelWeights: [MAX_CHANNELS][MAX_CHANNELS]f32, /* [in][out]. Only used when channelMixMode is set to ma_channel_mix_mode_custom_weights. */ - resampling: struct { - algorithm: resample_algorithm, - allowDynamicSampleRate: b32, - linear: struct { - lpfOrderL: u32, - lpfNyquistFactor: f64, - }, - speex: struct { - quality: c.int, - }, - }, + formatIn: format, + formatOut: format, + channelsIn: u32, + channelsOut: u32, + sampleRateIn: u32, + sampleRateOut: u32, + pChannelMapIn: [^]channel, + pChannelMapOut: [^]channel, + ditherMode: dither_mode, + channelMixMode: channel_mix_mode, + ppChannelWeights: ^[^]f32, /* [in][out]. Only used when channelMixMode is set to ma_channel_mix_mode_custom_weights. */ + allowDynamicSampleRate: b32, + resampling: resampler_config, +} + +data_converter_execution_path :: enum c.int { + passthrough, /* No conversion. */ + format_only, /* Only format conversion. */ + channels_only, /* Only channel conversion. */ + resample_only, /* Only resampling. */ + resample_first, /* All conversions, but resample as the first step. */ + channels_first, /* All conversions, but channels as the first step. */ } data_converter :: struct { - config: data_converter_config, + formatIn: format, + formatOut: format, + channelsIn: u32, + channelsOut: u32, + sampleRateIn: u32, + sampleRateOut: u32, + ditherMode: dither_mode, + executionPath: data_converter_execution_path, /* The execution path the data converter will follow when processing. */ channelConverter: channel_converter, resampler: resampler, hasPreFormatConversion: b8, @@ -257,6 +317,10 @@ data_converter :: struct { hasChannelConverter: b8, hasResampler: b8, isPassthrough: b8, + + /* Memory management. */ + _ownsHeap: b8, + _pHeap: rawptr, } @@ -265,15 +329,20 @@ foreign lib { data_converter_config_init_default :: proc() -> data_converter_config --- data_converter_config_init :: proc(formatIn, formatOut: format, channelsIn, channelsOut: u32, sampleRateIn, sampleRateOut: u32) -> data_converter_config --- - data_converter_init :: proc(pConfig: ^data_converter_config, pConverter: ^data_converter) -> result --- - data_converter_uninit :: proc(pConverter: ^data_converter) --- + data_converter_get_heap_size :: proc(pConfig: ^data_converter_config, pHeapSizeInBytes: ^c.size_t) -> result --- + data_converter_init_preallocated :: proc(pConfig: ^data_converter_config, pHeap: rawptr, pConverter: ^data_converter) -> result --- + data_converter_init :: proc(pConfig: ^data_converter_config, pAllocationCallbacks: ^allocation_callbacks, pConverter: ^data_converter) -> result --- + data_converter_uninit :: proc(pConverter: ^data_converter, pAllocationCallbacks: ^allocation_callbacks) --- data_converter_process_pcm_frames :: proc(pConverter: ^data_converter, pFramesIn: rawptr, pFrameCountIn: ^u64, pFramesOut: rawptr, pFrameCountOut: ^u64) -> result --- data_converter_set_rate :: proc(pConverter: ^data_converter, sampleRateIn, sampleRateOut: u32) -> result --- data_converter_set_rate_ratio :: proc(pConverter: ^data_converter, ratioInOut: f32) -> result --- - data_converter_get_required_input_frame_count :: proc(pConverter: ^data_converter, outputFrameCount: u64) -> u64 --- - data_converter_get_expected_output_frame_count :: proc(pConverter: ^data_converter, inputFrameCount: u64) -> u64 --- data_converter_get_input_latency :: proc(pConverter: ^data_converter) -> u64 --- data_converter_get_output_latency :: proc(pConverter: ^data_converter) -> u64 --- + data_converter_get_required_input_frame_count :: proc(pConverter: ^data_converter, outputFrameCount: u64, pInputFrameCount: ^u64) -> result --- + data_converter_get_expected_output_frame_count :: proc(pConverter: ^data_converter, inputFrameCount: u64, pOutputFrameCount: ^u64) -> result --- + data_converter_get_input_channel_map :: proc(pConverter: ^data_converter, pChannelMap: [^]channel, channelMapCap: c.size_t) -> result --- + data_converter_get_output_channel_map :: proc(pConverter: ^data_converter, pChannelMap: [^]channel, channelMapCap: c.size_t) -> result --- + data_converter_reset :: proc(pConverter: ^data_converter) -> result --- } /************************************************************************************************************************************************************ @@ -332,43 +401,40 @@ CHANNEL_INDEX_NULL :: 255 @(default_calling_convention="c", link_prefix="ma_") foreign lib { - /* Retrieves the channel position of the specified channel based on miniaudio's default channel map. */ - channel_map_get_default_channel :: proc(channelCount: u32, channelIndex: u32) -> channel --- - /* Retrieves the channel position of the specified channel in the given channel map. The pChannelMap parameter can be null, in which case miniaudio's default channel map will be assumed. */ - channel_map_get_channel :: proc(pChannelMap: ^channel, channelCount: u32, channelIndex: u32) -> channel --- + channel_map_get_channel :: proc(pChannelMap: [^]channel, channelCount: u32, channelIndex: u32) -> channel --- /* Initializes a blank channel map. When a blank channel map is specified anywhere it indicates that the native channel map should be used. */ - channel_map_init_blank :: proc(channels: u32, pChannelMap: ^channel) --- + channel_map_init_blank :: proc(pChannelMap: [^]channel, channels: u32) --- /* Helper for retrieving a standard channel map. - The output channel map buffer must have a capacity of at least `channels`. + The output channel map buffer must have a capacity of at least `channelMapCap`. */ - get_standard_channel_map :: proc(standardChannelMap: standard_channel_map, channels: u32, pChannelMap: ^channel) --- + channel_map_init_standard :: proc(standardChannelMap: standard_channel_map, pChannelMap: [^]channel, channelMapCap: c.size_t, channels: u32) --- /* Copies a channel map. Both input and output channel map buffers must have a capacity of at at least `channels`. */ - channel_map_copy :: proc(pOut: ^channel, pIn: ^channel, channels: u32) --- + channel_map_copy :: proc(pOut: [^]channel, pIn: [^]channel, channels: u32) --- /* Copies a channel map if one is specified, otherwise copies the default channel map. The output buffer must have a capacity of at least `channels`. If not NULL, the input channel map must also have a capacity of at least `channels`. */ - channel_map_copy_or_default :: proc(pOut: ^channel, pIn: ^channel, channels: u32) --- + channel_map_copy_or_default :: proc(pOut: [^]channel, channelMapCapOut: c.size_t, pIn: [^]channel, channels: u32) --- /* @@ -378,12 +444,12 @@ foreign lib { is usually treated as a passthrough. Invalid channel maps: - - A channel map with no channels - - A channel map with more than one channel and a mono channel + - A channel map with no channels + - A channel map with more than one channel and a mono channel The channel map buffer must have a capacity of at least `channels`. */ - channel_map_valid :: proc(channels: u32, pChannelMap: ^channel) -> b32 --- + channel_map_is_valid :: proc(pChannelMap: [^]channel, channels: u32) -> b32 --- /* Helper for comparing two channel maps for equality. @@ -392,23 +458,24 @@ foreign lib { Both channels map buffers must have a capacity of at least `channels`. */ - channel_map_equal :: proc(channels: u32, pChannelMapA, pChannelMapB: ^channel) -> b32 --- + channel_map_is_equal :: proc(pChannelMapA, pChannelMapB: [^]channel, channels: u32) -> b32 --- /* Helper for determining if a channel map is blank (all channels set to MA_CHANNEL_NONE). The channel map buffer must have a capacity of at least `channels`. */ - channel_map_blank :: proc(channels: u32, pChannelMap: ^channel) -> b32 --- + channel_map_is_blank :: proc(pChannelMap: [^]channel, channels: u32) -> b32 --- /* Helper for determining whether or not a channel is present in the given channel map. The channel map buffer must have a capacity of at least `channels`. */ - channel_map_contains_channel_position :: proc(channels: u32, pChannelMap: ^channel, channelPosition: channel) -> b32 --- + channel_map_contains_channel_position :: proc(channels: u32, pChannelMap: [^]channel, channelPosition: channel) -> b32 --- } + /************************************************************************************************************************************************************ Conversion Helpers @@ -461,9 +528,9 @@ foreign lib { rb_uninit :: proc(pRB: ^rb) --- rb_reset :: proc(pRB: ^rb) --- rb_acquire_read :: proc(pRB: ^rb, pSizeInBytes: ^c.size_t, ppBufferOut: ^rawptr) -> result --- - rb_commit_read :: proc(pRB: ^rb, sizeInBytes: c.size_t, pBufferOut: rawptr) -> result --- + rb_commit_read :: proc(pRB: ^rb, sizeInBytes: c.size_t) -> result --- rb_acquire_write :: proc(pRB: ^rb, pSizeInBytes: ^c.size_t, ppBufferOut: ^rawptr) -> result --- - rb_commit_write :: proc(pRB: ^rb, sizeInBytes: c.size_t, pBufferOut: rawptr) -> result --- + rb_commit_write :: proc(pRB: ^rb, sizeInBytes: c.size_t) -> result --- rb_seek_read :: proc(pRB: ^rb, offsetInBytes: c.size_t) -> result --- rb_seek_write :: proc(pRB: ^rb, offsetInBytes: c.size_t) -> result --- rb_pointer_distance :: proc(pRB: ^rb) -> i32 --- /* Returns the distance between the write pointer and the read pointer. Should never be negative for a correct program. Will return the number of bytes that can be read before the read pointer hits the write pointer. */ diff --git a/vendor/miniaudio/decoding.odin b/vendor/miniaudio/decoding.odin index dcf3b7a1a..003f6f950 100644 --- a/vendor/miniaudio/decoding.odin +++ b/vendor/miniaudio/decoding.odin @@ -22,68 +22,63 @@ you do your own synchronization. decoding_backend_config :: struct { preferredFormat: format, + seekPointCount: u32, /* Set to > 0 to generate a seektable if the decoding backend supports it. */ } @(default_calling_convention="c", link_prefix="ma_") foreign lib { - decoding_backend_config_init :: proc(preferredFormat: format) -> decoding_backend_config --- + decoding_backend_config_init :: proc(preferredFormat: format, seekPointCount: u32) -> decoding_backend_config --- } decoding_backend_vtable :: struct { onInit: proc "c" (pUserData: rawptr, onRead: decoder_read_proc, onSeek: decoder_seek_proc, onTell: decoder_tell_proc, pReadSeekTellUserData: rawptr, pConfig: ^decoding_backend_config, pAllocationCallbacks: ^allocation_callbacks, ppBackend: ^^data_source) -> result, - onInitFile: proc "c" (pUserData: rawptr, pFilePath: cstring, pConfig: ^decoding_backend_config, pAllocationCallbacks: ^allocation_callbacks, ppBackend: ^^data_source) -> result, /* Optional. */ + onInitFile: proc "c" (pUserData: rawptr, pFilePath: cstring, pConfig: ^decoding_backend_config, pAllocationCallbacks: ^allocation_callbacks, ppBackend: ^^data_source) -> result, /* Optional. */ onInitFileW: proc "c" (pUserData: rawptr, pFilePath: [^]c.wchar_t, pConfig: ^decoding_backend_config, pAllocationCallbacks: ^allocation_callbacks, ppBackend: ^^data_source) -> result, /* Optional. */ onInitMemory: proc "c" (pUserData: rawptr, pData: rawptr, dataSize: c.size_t, pConfig: ^decoding_backend_config, pAllocationCallbacks: ^allocation_callbacks, ppBackend: ^^data_source) -> result, /* Optional. */ onUninit: proc "c" (pUserData: rawptr, pBackend: ^data_source, pAllocationCallbacks: ^allocation_callbacks), - onGetChannelMap: proc "c" (pUserData: rawptr, pBackend: ^data_source, pChannelMap: ^channel, channelMapCap: c.size_t) -> result, } -/* TODO: Convert read and seek to be consistent with the VFS API (ma_result return value, bytes read moved to an output parameter). */ -decoder_read_proc :: proc "c" (pDecoder: ^decoder, pBufferOut: rawptr, bytesToRead: c.size_t) -> c.size_t /* Returns the number of bytes read. */ -decoder_seek_proc :: proc "c" (pDecoder: ^decoder, byteOffset: i64, origin: seek_origin) -> b32 +decoder_read_proc :: proc "c" (pDecoder: ^decoder, pBufferOut: rawptr, bytesToRead: c.size_t, pBytesRead: ^c.size_t) -> result /* Returns the number of bytes read. */ +decoder_seek_proc :: proc "c" (pDecoder: ^decoder, byteOffset: i64, origin: seek_origin) -> result decoder_tell_proc :: proc "c" (pDecoder: ^decoder, pCursor: ^i64) -> result decoder_config :: struct { - format: format, /* Set to 0 or ma_format_unknown to use the stream's internal format. */ - channels: u32, /* Set to 0 to use the stream's internal channels. */ - sampleRate: u32, /* Set to 0 to use the stream's internal sample rate. */ - channelMap: [MAX_CHANNELS]channel, - channelMixMode: channel_mix_mode, - ditherMode: dither_mode, - resampling: struct { - algorithm: resample_algorithm, - linear: struct { - lpfOrder: u32, - }, - speex: struct { - quality: c.int, - }, - }, + format: format, /* Set to 0 or ma_format_unknown to use the stream's internal format. */ + channels: u32, /* Set to 0 to use the stream's internal channels. */ + sampleRate: u32, /* Set to 0 to use the stream's internal sample rate. */ + channelMap: [^]channel, + channelMixMode: channel_mix_mode, + ditherMode: dither_mode, + resampling: resampler_config, allocationCallbacks: allocation_callbacks, encodingFormat: encoding_format, - ppCustomBackendVTables: ^^decoding_backend_vtable, + seekPointCount: u32, /* When set to > 0, specifies the number of seek points to use for the generation of a seek table. Not all decoding backends support this. */ + ppCustomBackendVTables: ^[^]decoding_backend_vtable, customBackendCount: u32, pCustomBackendUserData: rawptr, } decoder :: struct { - ds: data_source_base, - pBackend: ^data_source, /* The decoding backend we'll be pulling data from. */ - pBackendVTable: ^^decoding_backend_vtable, /* The vtable for the decoding backend. This needs to be stored so we can access the onUninit() callback. */ - pBackendUserData: rawptr, - onRead: decoder_read_proc, - onSeek: decoder_seek_proc, - onTell: decoder_tell_proc, - pUserData: rawptr, + ds: data_source_base, + pBackend: ^data_source, /* The decoding backend we'll be pulling data from. */ + pBackendVTable: ^decoding_backend_vtable, /* The vtable for the decoding backend. This needs to be stored so we can access the onUninit() callback. */ + pBackendUserData: rawptr, + onRead: decoder_read_proc, + onSeek: decoder_seek_proc, + onTell: decoder_tell_proc, + pUserData: rawptr, readPointerInPCMFrames: u64, /* In output sample rate. Used for keeping track of how many frames are available for decoding. */ - outputFormat: format, - outputChannels: u32, - outputSampleRate: u32, - outputChannelMap: [MAX_CHANNELS]channel, - converter: data_converter, /* <-- Data conversion is achieved by running frames through this. */ - allocationCallbacks: allocation_callbacks, + outputFormat: format, + outputChannels: u32, + outputSampleRate: u32, + converter: data_converter, /* <-- Data conversion is achieved by running frames through this. */ + pInputCache: rawptr, /* In input format. Can be null if it's not needed. */ + inputCacheCap: u64, /* The capacity of the input cache. */ + inputCacheConsumed: u64, /* The number of frames that have been consumed in the cache. Used for determining the next valid frame. */ + inputCacheRemaining: u64, /* The number of valid frames remaining in the cahce. */ + allocationCallbacks: allocation_callbacks, data: struct #raw_union { vfs: struct { pVFS: ^vfs, @@ -114,6 +109,25 @@ foreign lib { */ decoder_uninit :: proc(pDecoder: ^decoder) -> result --- + /* + Reads PCM frames from the given decoder. + + This is not thread safe without your own synchronization. + */ + decoder_read_pcm_frames :: proc(pDecoder: ^decoder, pFramesOut: rawptr, frameCount: u64, pFramesRead: ^u64) -> result --- + + /* + Seeks to a PCM frame based on it's absolute index. + + This is not thread safe without your own synchronization. + */ + decoder_seek_to_pcm_frame :: proc(pDecoder: ^decoder, frameIndex: u64) -> result --- + + /* + Retrieves the decoder's output data format. + */ + decoder_get_data_format :: proc(pDecoder: ^decoder, pFormat: ^format, pChannels, pSampleRate: ^u32, pChannelMap: ^channel, channelMapCap: c.size_t) -> result --- + /* Retrieves the current position of the read cursor in PCM frames. */ @@ -133,21 +147,7 @@ foreign lib { This function is not thread safe without your own synchronization. */ - decoder_get_length_in_pcm_frames :: proc(pDecoder: ^decoder) -> u64 --- - - /* - Reads PCM frames from the given decoder. - - This is not thread safe without your own synchronization. - */ - decoder_read_pcm_frames :: proc(pDecoder: ^decoder, pFramesOut: rawptr, frameCount: u64) -> u64 --- - - /* - Seeks to a PCM frame based on it's absolute index. - - This is not thread safe without your own synchronization. - */ - decoder_seek_to_pcm_frame :: proc(pDecoder: ^decoder, frameIndex: u64) -> result --- + decoder_get_length_in_pcm_frames :: proc(pDecoder: ^decoder, pLength: ^u64) -> result --- /* Retrieves the number of frames that can be read before reaching the end. diff --git a/vendor/miniaudio/device_io_procs.odin b/vendor/miniaudio/device_io_procs.odin index 7cff3f621..de60645e4 100644 --- a/vendor/miniaudio/device_io_procs.odin +++ b/vendor/miniaudio/device_io_procs.odin @@ -12,6 +12,12 @@ import "core:c" @(default_calling_convention="c", link_prefix="ma_") foreign lib { + device_job_thread_config_init :: proc() -> device_job_thread_config --- + + device_job_thread_init :: proc(pConfig: ^device_job_thread_config, pAllocationCallbacks: ^allocation_callbacks, pJobThread: ^device_job_thread) -> result --- + device_job_thread_uninit :: proc(pJobThread: ^device_job_thread, pAllocationCallbacks: ^allocation_callbacks) --- + device_job_thread_post :: proc(pJobThread: ^device_job_thread, pJob: ^job) -> result --- + device_job_thread_next :: proc(pJobThread: ^device_job_thread, pJob: ^job) -> result --- /* Initializes a `ma_context_config` object. @@ -46,16 +52,16 @@ foreign lib { Parameters ---------- backends (in, optional) - A list of backends to try initializing, in priority order. Can be NULL, in which case it uses default priority order. + A list of backends to try initializing, in priority order. Can be NULL, in which case it uses default priority order. backendCount (in, optional) - The number of items in `backend`. Ignored if `backend` is NULL. + The number of items in `backend`. Ignored if `backend` is NULL. pConfig (in, optional) - The context configuration. + The context configuration. pContext (in) - A pointer to the context object being initialized. + A pointer to the context object being initialized. Return Value @@ -72,107 +78,116 @@ foreign lib { ------- When `backends` is NULL, the default priority order will be used. Below is a list of backends in priority order: - |-------------|-----------------------|--------------------------------------------------------| - | Name | Enum Name | Supported Operating Systems | - |-------------|-----------------------|--------------------------------------------------------| - | WASAPI | ma_backend_wasapi | Windows Vista+ | - | DirectSound | ma_backend_dsound | Windows XP+ | - | WinMM | ma_backend_winmm | Windows XP+ (may work on older versions, but untested) | - | Core Audio | ma_backend_coreaudio | macOS, iOS | - | ALSA | ma_backend_alsa | Linux | - | PulseAudio | ma_backend_pulseaudio | Cross Platform (disabled on Windows, BSD and Android) | - | JACK | ma_backend_jack | Cross Platform (disabled on BSD and Android) | - | sndio | ma_backend_sndio | OpenBSD | - | audio(4) | ma_backend_audio4 | NetBSD, OpenBSD | - | OSS | ma_backend_oss | FreeBSD | - | AAudio | ma_backend_aaudio | Android 8+ | - | OpenSL|ES | ma_backend_opensl | Android (API level 16+) | - | Web Audio | ma_backend_webaudio | Web (via Emscripten) | - | Null | ma_backend_null | Cross Platform (not used on Web) | - |-------------|-----------------------|--------------------------------------------------------| + |-------------|-----------------------|--------------------------------------------------------| + | Name | Enum Name | Supported Operating Systems | + |-------------|-----------------------|--------------------------------------------------------| + | WASAPI | ma_backend_wasapi | Windows Vista+ | + | DirectSound | ma_backend_dsound | Windows XP+ | + | WinMM | ma_backend_winmm | Windows XP+ (may work on older versions, but untested) | + | Core Audio | ma_backend_coreaudio | macOS, iOS | + | ALSA | ma_backend_alsa | Linux | + | PulseAudio | ma_backend_pulseaudio | Cross Platform (disabled on Windows, BSD and Android) | + | JACK | ma_backend_jack | Cross Platform (disabled on BSD and Android) | + | sndio | ma_backend_sndio | OpenBSD | + | audio(4) | ma_backend_audio4 | NetBSD, OpenBSD | + | OSS | ma_backend_oss | FreeBSD | + | AAudio | ma_backend_aaudio | Android 8+ | + | OpenSL|ES | ma_backend_opensl | Android (API level 16+) | + | Web Audio | ma_backend_webaudio | Web (via Emscripten) | + | Null | ma_backend_null | Cross Platform (not used on Web) | + |-------------|-----------------------|--------------------------------------------------------| The context can be configured via the `pConfig` argument. The config object is initialized with `ma_context_config_init()`. Individual configuration settings can then be set directly on the structure. Below are the members of the `ma_context_config` object. - pLog - A pointer to the `ma_log` to post log messages to. Can be NULL if the application does not - require logging. See the `ma_log` API for details on how to use the logging system. + pLog + A pointer to the `ma_log` to post log messages to. Can be NULL if the application does not + require logging. See the `ma_log` API for details on how to use the logging system. - threadPriority - The desired priority to use for the audio thread. Allowable values include the following: + threadPriority + The desired priority to use for the audio thread. Allowable values include the following: - |--------------------------------------| - | Thread Priority | - |--------------------------------------| - | ma_thread_priority_idle | - | ma_thread_priority_lowest | - | ma_thread_priority_low | - | ma_thread_priority_normal | - | ma_thread_priority_high | - | ma_thread_priority_highest (default) | - | ma_thread_priority_realtime | - | ma_thread_priority_default | - |--------------------------------------| + |--------------------------------------| + | Thread Priority | + |--------------------------------------| + | ma_thread_priority_idle | + | ma_thread_priority_lowest | + | ma_thread_priority_low | + | ma_thread_priority_normal | + | ma_thread_priority_high | + | ma_thread_priority_highest (default) | + | ma_thread_priority_realtime | + | ma_thread_priority_default | + |--------------------------------------| - pUserData - A pointer to application-defined data. This can be accessed from the context object directly such as `context.pUserData`. + threadStackSize + The desired size of the stack for the audio thread. Defaults to the operating system's default. - allocationCallbacks - Structure containing custom allocation callbacks. Leaving this at defaults will cause it to use MA_MALLOC, MA_REALLOC and MA_FREE. These allocation - callbacks will be used for anything tied to the context, including devices. + pUserData + A pointer to application-defined data. This can be accessed from the context object directly such as `context.pUserData`. - alsa.useVerboseDeviceEnumeration - ALSA will typically enumerate many different devices which can be intrusive and not user-friendly. To combat this, miniaudio will enumerate only unique - card/device pairs by default. The problem with this is that you lose a bit of flexibility and control. Setting alsa.useVerboseDeviceEnumeration makes - it so the ALSA backend includes all devices. Defaults to false. + allocationCallbacks + Structure containing custom allocation callbacks. Leaving this at defaults will cause it to use MA_MALLOC, MA_REALLOC and MA_FREE. These allocation + callbacks will be used for anything tied to the context, including devices. - pulse.pApplicationName - PulseAudio only. The application name to use when initializing the PulseAudio context with `pa_context_new()`. + alsa.useVerboseDeviceEnumeration + ALSA will typically enumerate many different devices which can be intrusive and not user-friendly. To combat this, miniaudio will enumerate only unique + card/device pairs by default. The problem with this is that you lose a bit of flexibility and control. Setting alsa.useVerboseDeviceEnumeration makes + it so the ALSA backend includes all devices. Defaults to false. - pulse.pServerName - PulseAudio only. The name of the server to connect to with `pa_context_connect()`. + pulse.pApplicationName + PulseAudio only. The application name to use when initializing the PulseAudio context with `pa_context_new()`. - pulse.tryAutoSpawn - PulseAudio only. Whether or not to try automatically starting the PulseAudio daemon. Defaults to false. If you set this to true, keep in mind that - miniaudio uses a trial and error method to find the most appropriate backend, and this will result in the PulseAudio daemon starting which may be - intrusive for the end user. + pulse.pServerName + PulseAudio only. The name of the server to connect to with `pa_context_connect()`. - coreaudio.sessionCategory - iOS only. The session category to use for the shared AudioSession instance. Below is a list of allowable values and their Core Audio equivalents. + pulse.tryAutoSpawn + PulseAudio only. Whether or not to try automatically starting the PulseAudio daemon. Defaults to false. If you set this to true, keep in mind that + miniaudio uses a trial and error method to find the most appropriate backend, and this will result in the PulseAudio daemon starting which may be + intrusive for the end user. - |-----------------------------------------|-------------------------------------| - | miniaudio Token | Core Audio Token | - |-----------------------------------------|-------------------------------------| - | ma_ios_session_category_ambient | AVAudioSessionCategoryAmbient | - | ma_ios_session_category_solo_ambient | AVAudioSessionCategorySoloAmbient | - | ma_ios_session_category_playback | AVAudioSessionCategoryPlayback | - | ma_ios_session_category_record | AVAudioSessionCategoryRecord | - | ma_ios_session_category_play_and_record | AVAudioSessionCategoryPlayAndRecord | - | ma_ios_session_category_multi_route | AVAudioSessionCategoryMultiRoute | - | ma_ios_session_category_none | AVAudioSessionCategoryAmbient | - | ma_ios_session_category_default | AVAudioSessionCategoryAmbient | - |-----------------------------------------|-------------------------------------| + coreaudio.sessionCategory + iOS only. The session category to use for the shared AudioSession instance. Below is a list of allowable values and their Core Audio equivalents. - coreaudio.sessionCategoryOptions - iOS only. Session category options to use with the shared AudioSession instance. Below is a list of allowable values and their Core Audio equivalents. + |-----------------------------------------|-------------------------------------| + | miniaudio Token | Core Audio Token | + |-----------------------------------------|-------------------------------------| + | ma_ios_session_category_ambient | AVAudioSessionCategoryAmbient | + | ma_ios_session_category_solo_ambient | AVAudioSessionCategorySoloAmbient | + | ma_ios_session_category_playback | AVAudioSessionCategoryPlayback | + | ma_ios_session_category_record | AVAudioSessionCategoryRecord | + | ma_ios_session_category_play_and_record | AVAudioSessionCategoryPlayAndRecord | + | ma_ios_session_category_multi_route | AVAudioSessionCategoryMultiRoute | + | ma_ios_session_category_none | AVAudioSessionCategoryAmbient | + | ma_ios_session_category_default | AVAudioSessionCategoryAmbient | + |-----------------------------------------|-------------------------------------| - |---------------------------------------------------------------------------|------------------------------------------------------------------| - | miniaudio Token | Core Audio Token | - |---------------------------------------------------------------------------|------------------------------------------------------------------| - | ma_ios_session_category_option_mix_with_others | AVAudioSessionCategoryOptionMixWithOthers | - | ma_ios_session_category_option_duck_others | AVAudioSessionCategoryOptionDuckOthers | - | ma_ios_session_category_option_allow_bluetooth | AVAudioSessionCategoryOptionAllowBluetooth | - | ma_ios_session_category_option_default_to_speaker | AVAudioSessionCategoryOptionDefaultToSpeaker | - | ma_ios_session_category_option_interrupt_spoken_audio_and_mix_with_others | AVAudioSessionCategoryOptionInterruptSpokenAudioAndMixWithOthers | - | ma_ios_session_category_option_allow_bluetooth_a2dp | AVAudioSessionCategoryOptionAllowBluetoothA2DP | - | ma_ios_session_category_option_allow_air_play | AVAudioSessionCategoryOptionAllowAirPlay | - |---------------------------------------------------------------------------|------------------------------------------------------------------| + coreaudio.sessionCategoryOptions + iOS only. Session category options to use with the shared AudioSession instance. Below is a list of allowable values and their Core Audio equivalents. - jack.pClientName - The name of the client to pass to `jack_client_open()`. + |---------------------------------------------------------------------------|------------------------------------------------------------------| + | miniaudio Token | Core Audio Token | + |---------------------------------------------------------------------------|------------------------------------------------------------------| + | ma_ios_session_category_option_mix_with_others | AVAudioSessionCategoryOptionMixWithOthers | + | ma_ios_session_category_option_duck_others | AVAudioSessionCategoryOptionDuckOthers | + | ma_ios_session_category_option_allow_bluetooth | AVAudioSessionCategoryOptionAllowBluetooth | + | ma_ios_session_category_option_default_to_speaker | AVAudioSessionCategoryOptionDefaultToSpeaker | + | ma_ios_session_category_option_interrupt_spoken_audio_and_mix_with_others | AVAudioSessionCategoryOptionInterruptSpokenAudioAndMixWithOthers | + | ma_ios_session_category_option_allow_bluetooth_a2dp | AVAudioSessionCategoryOptionAllowBluetoothA2DP | + | ma_ios_session_category_option_allow_air_play | AVAudioSessionCategoryOptionAllowAirPlay | + |---------------------------------------------------------------------------|------------------------------------------------------------------| - jack.tryStartServer - Whether or not to try auto-starting the JACK server. Defaults to false. + coreaudio.noAudioSessionActivate + iOS only. When set to true, does not perform an explicit [[AVAudioSession sharedInstace] setActive:true] on initialization. + + coreaudio.noAudioSessionDeactivate + iOS only. When set to true, does not perform an explicit [[AVAudioSession sharedInstace] setActive:false] on uninitialization. + + jack.pClientName + The name of the client to pass to `jack_client_open()`. + + jack.tryStartServer + Whether or not to try auto-starting the JACK server. Defaults to false. It is recommended that only a single context is active at any given time because it's a bulky data structure which performs run-time linking for the @@ -190,7 +205,7 @@ foreign lib { ma_context context; ma_result result = ma_context_init(NULL, 0, NULL, &context); if (result != MA_SUCCESS) { - // Error. + // Error. } ``` @@ -205,24 +220,30 @@ foreign lib { ```c ma_backend backends[] = { - ma_backend_alsa, - ma_backend_pulseaudio, - ma_backend_wasapi, - ma_backend_dsound + ma_backend_alsa, + ma_backend_pulseaudio, + ma_backend_wasapi, + ma_backend_dsound }; + ma_log log; + ma_log_init(&log); + ma_log_register_callback(&log, ma_log_callback_init(my_log_callbac, pMyLogUserData)); + ma_context_config config = ma_context_config_init(); - config.logCallback = my_log_callback; - config.pUserData = pMyUserData; + config.pLog = &log; // Specify a custom log object in the config so any logs that are posted from ma_context_init() are captured. ma_context context; ma_result result = ma_context_init(backends, sizeof(backends)/sizeof(backends[0]), &config, &context); if (result != MA_SUCCESS) { - // Error. - if (result == MA_NO_BACKEND) { - // Couldn't find an appropriate backend. - } + // Error. + if (result == MA_NO_BACKEND) { + // Couldn't find an appropriate backend. + } } + + // You could also attach a log callback post-initialization: + ma_log_register_callback(ma_context_get_log(&context), ma_log_callback_init(my_log_callback, pMyLogUserData)); ``` @@ -298,13 +319,13 @@ foreign lib { Parameters ---------- pContext (in) - A pointer to the context performing the enumeration. + A pointer to the context performing the enumeration. callback (in) - The callback to fire for each enumerated device. + The callback to fire for each enumerated device. pUserData (in) - A pointer to application-defined data passed to the callback. + A pointer to application-defined data passed to the callback. Return Value @@ -331,15 +352,15 @@ foreign lib { Example 1 - Simple Enumeration ------------------------------ - ma_bool32 ma_device_enum_callback(pContext: ^context_type, ma_device_type deviceType, const ma_device_info* pInfo, void* pUserData) + ma_bool32 ma_device_enum_callback(ma_context* pContext, ma_device_type deviceType, const ma_device_info* pInfo, void* pUserData) { - printf("Device Name: %s\n", pInfo->name); - return MA_TRUE; + printf("Device Name: %s\n", pInfo->name); + return MA_TRUE; } ma_result result = ma_context_enumerate_devices(&context, my_device_enum_callback, pMyUserData); if (result != MA_SUCCESS) { - // Error. + // Error. } @@ -359,19 +380,19 @@ foreign lib { Parameters ---------- pContext (in) - A pointer to the context performing the enumeration. + A pointer to the context performing the enumeration. ppPlaybackDeviceInfos (out) - A pointer to a pointer that will receive the address of a buffer containing the list of `ma_device_info` structures for playback devices. + A pointer to a pointer that will receive the address of a buffer containing the list of `ma_device_info` structures for playback devices. pPlaybackDeviceCount (out) - A pointer to an unsigned integer that will receive the number of playback devices. + A pointer to an unsigned integer that will receive the number of playback devices. ppCaptureDeviceInfos (out) - A pointer to a pointer that will receive the address of a buffer containing the list of `ma_device_info` structures for capture devices. + A pointer to a pointer that will receive the address of a buffer containing the list of `ma_device_info` structures for capture devices. pCaptureDeviceCount (out) - A pointer to an unsigned integer that will receive the number of capture devices. + A pointer to an unsigned integer that will receive the number of capture devices. Return Value @@ -407,20 +428,16 @@ foreign lib { Parameters ---------- pContext (in) - A pointer to the context performing the query. + A pointer to the context performing the query. deviceType (in) - The type of the device being queried. Must be either `ma_device_type_playback` or `ma_device_type_capture`. + The type of the device being queried. Must be either `ma_device_type_playback` or `ma_device_type_capture`. pDeviceID (in) - The ID of the device being queried. - - shareMode (in) - The share mode to query for device capabilities. This should be set to whatever you're intending on using when initializing the device. If you're unsure, - set this to `ma_share_mode_shared`. + The ID of the device being queried. pDeviceInfo (out) - A pointer to the `ma_device_info` structure that will receive the device information. + A pointer to the `ma_device_info` structure that will receive the device information. Return Value @@ -444,7 +461,7 @@ foreign lib { This leaves pDeviceInfo unmodified in the result of an error. */ - context_get_device_info :: proc(pContext: ^context_type, deviceType: device_type, pDeviceID: ^device_id, shareMode: share_mode, pDeviceInfo: ^device_info) -> result --- + context_get_device_info :: proc(pContext: ^context_type, deviceType: device_type, pDeviceID: ^device_id, pDeviceInfo: ^device_info) -> result --- /* Determines if the given context supports loopback mode. @@ -471,16 +488,16 @@ foreign lib { Parameters ---------- deviceType (in) - The type of the device this config is being initialized for. This must set to one of the following: + The type of the device this config is being initialized for. This must set to one of the following: - |-------------------------| - | Device Type | - |-------------------------| - | ma_device_type_playback | - | ma_device_type_capture | - | ma_device_type_duplex | - | ma_device_type_loopback | - |-------------------------| + |-------------------------| + | Device Type | + |-------------------------| + | ma_device_type_playback | + | ma_device_type_capture | + | ma_device_type_duplex | + | ma_device_type_loopback | + |-------------------------| Return Value @@ -554,13 +571,13 @@ foreign lib { Parameters ---------- pContext (in, optional) - A pointer to the context that owns the device. This can be null, in which case it creates a default context internally. + A pointer to the context that owns the device. This can be null, in which case it creates a default context internally. pConfig (in) - A pointer to the device configuration. Cannot be null. See remarks for details. + A pointer to the device configuration. Cannot be null. See remarks for details. pDevice (out) - A pointer to the device object being initialized. + A pointer to the device object being initialized. Return Value @@ -583,9 +600,9 @@ foreign lib { ------- Setting `pContext` to NULL will result in miniaudio creating a default context internally and is equivalent to passing in a context initialized like so: - ```c - ma_context_init(NULL, 0, NULL, &context); - ``` + ```c + ma_context_init(NULL, 0, NULL, &context); + ``` Do not set `pContext` to NULL if you are needing to open multiple devices. You can, however, use NULL when initializing the first device, and then use device.pContext for the initialization of other devices. @@ -593,136 +610,173 @@ foreign lib { The device can be configured via the `pConfig` argument. The config object is initialized with `ma_device_config_init()`. Individual configuration settings can then be set directly on the structure. Below are the members of the `ma_device_config` object. - deviceType - Must be `ma_device_type_playback`, `ma_device_type_capture`, `ma_device_type_duplex` of `ma_device_type_loopback`. + deviceType + Must be `ma_device_type_playback`, `ma_device_type_capture`, `ma_device_type_duplex` of `ma_device_type_loopback`. - sampleRate - The sample rate, in hertz. The most common sample rates are 48000 and 44100. Setting this to 0 will use the device's native sample rate. + sampleRate + The sample rate, in hertz. The most common sample rates are 48000 and 44100. Setting this to 0 will use the device's native sample rate. - periodSizeInFrames - The desired size of a period in PCM frames. If this is 0, `periodSizeInMilliseconds` will be used instead. If both are 0 the default buffer size will - be used depending on the selected performance profile. This value affects latency. See below for details. + periodSizeInFrames + The desired size of a period in PCM frames. If this is 0, `periodSizeInMilliseconds` will be used instead. If both are 0 the default buffer size will + be used depending on the selected performance profile. This value affects latency. See below for details. - periodSizeInMilliseconds - The desired size of a period in milliseconds. If this is 0, `periodSizeInFrames` will be used instead. If both are 0 the default buffer size will be - used depending on the selected performance profile. The value affects latency. See below for details. + periodSizeInMilliseconds + The desired size of a period in milliseconds. If this is 0, `periodSizeInFrames` will be used instead. If both are 0 the default buffer size will be + used depending on the selected performance profile. The value affects latency. See below for details. - periods - The number of periods making up the device's entire buffer. The total buffer size is `periodSizeInFrames` or `periodSizeInMilliseconds` multiplied by - this value. This is just a hint as backends will be the ones who ultimately decide how your periods will be configured. + periods + The number of periods making up the device's entire buffer. The total buffer size is `periodSizeInFrames` or `periodSizeInMilliseconds` multiplied by + this value. This is just a hint as backends will be the ones who ultimately decide how your periods will be configured. - performanceProfile - A hint to miniaudio as to the performance requirements of your program. Can be either `ma_performance_profile_low_latency` (default) or - `ma_performance_profile_conservative`. This mainly affects the size of default buffers and can usually be left at it's default value. + performanceProfile + A hint to miniaudio as to the performance requirements of your program. Can be either `ma_performance_profile_low_latency` (default) or + `ma_performance_profile_conservative`. This mainly affects the size of default buffers and can usually be left at it's default value. - noPreZeroedOutputBuffer - When set to true, the contents of the output buffer passed into the data callback will be left undefined. When set to false (default), the contents of - the output buffer will be cleared the zero. You can use this to avoid the overhead of zeroing out the buffer if you can guarantee that your data - callback will write to every sample in the output buffer, or if you are doing your own clearing. + noPreSilencedOutputBuffer + When set to true, the contents of the output buffer passed into the data callback will be left undefined. When set to false (default), the contents of + the output buffer will be cleared the zero. You can use this to avoid the overhead of zeroing out the buffer if you can guarantee that your data + callback will write to every sample in the output buffer, or if you are doing your own clearing. - noClip - When set to true, the contents of the output buffer passed into the data callback will be clipped after returning. When set to false (default), the - contents of the output buffer are left alone after returning and it will be left up to the backend itself to decide whether or not the clip. This only - applies when the playback sample format is f32. + noClip + When set to true, the contents of the output buffer passed into the data callback will be clipped after returning. When set to false (default), the + contents of the output buffer are left alone after returning and it will be left up to the backend itself to decide whether or not the clip. This only + applies when the playback sample format is f32. - dataCallback - The callback to fire whenever data is ready to be delivered to or from the device. + noDisableDenormals + By default, miniaudio will disable denormals when the data callback is called. Setting this to true will prevent the disabling of denormals. - stopCallback - The callback to fire whenever the device has stopped, either explicitly via `ma_device_stop()`, or implicitly due to things like the device being - disconnected. + noFixedSizedCallback + Allows miniaudio to fire the data callback with any frame count. When this is set to true, the data callback will be fired with a consistent frame + count as specified by `periodSizeInFrames` or `periodSizeInMilliseconds`. When set to false, miniaudio will fire the callback with whatever the + backend requests, which could be anything. - pUserData - The user data pointer to use with the device. You can access this directly from the device object like `device.pUserData`. + dataCallback + The callback to fire whenever data is ready to be delivered to or from the device. - resampling.algorithm - The resampling algorithm to use when miniaudio needs to perform resampling between the rate specified by `sampleRate` and the device's native rate. The - default value is `ma_resample_algorithm_linear`, and the quality can be configured with `resampling.linear.lpfOrder`. + notificationCallback + The callback to fire when something has changed with the device, such as whether or not it has been started or stopped. - resampling.linear.lpfOrder - The linear resampler applies a low-pass filter as part of it's procesing for anti-aliasing. This setting controls the order of the filter. The higher - the value, the better the quality, in general. Setting this to 0 will disable low-pass filtering altogether. The maximum value is - `MA_MAX_FILTER_ORDER`. The default value is `min(4, MA_MAX_FILTER_ORDER)`. + pUserData + The user data pointer to use with the device. You can access this directly from the device object like `device.pUserData`. - playback.pDeviceID - A pointer to a `ma_device_id` structure containing the ID of the playback device to initialize. Setting this NULL (default) will use the system's - default playback device. Retrieve the device ID from the `ma_device_info` structure, which can be retrieved using device enumeration. + resampling.algorithm + The resampling algorithm to use when miniaudio needs to perform resampling between the rate specified by `sampleRate` and the device's native rate. The + default value is `ma_resample_algorithm_linear`, and the quality can be configured with `resampling.linear.lpfOrder`. - playback.format - The sample format to use for playback. When set to `ma_format_unknown` the device's native format will be used. This can be retrieved after - initialization from the device object directly with `device.playback.format`. + resampling.pBackendVTable + A pointer to an optional vtable that can be used for plugging in a custom resampler. - playback.channels - The number of channels to use for playback. When set to 0 the device's native channel count will be used. This can be retrieved after initialization - from the device object directly with `device.playback.channels`. + resampling.pBackendUserData + A pointer that will passed to callbacks in pBackendVTable. - playback.channelMap - The channel map to use for playback. When left empty, the device's native channel map will be used. This can be retrieved after initialization from the - device object direct with `device.playback.channelMap`. + resampling.linear.lpfOrder + The linear resampler applies a low-pass filter as part of it's procesing for anti-aliasing. This setting controls the order of the filter. The higher + the value, the better the quality, in general. Setting this to 0 will disable low-pass filtering altogether. The maximum value is + `MA_MAX_FILTER_ORDER`. The default value is `min(4, MA_MAX_FILTER_ORDER)`. - playback.shareMode - The preferred share mode to use for playback. Can be either `ma_share_mode_shared` (default) or `ma_share_mode_exclusive`. Note that if you specify - exclusive mode, but it's not supported by the backend, initialization will fail. You can then fall back to shared mode if desired by changing this to - ma_share_mode_shared and reinitializing. + playback.pDeviceID + A pointer to a `ma_device_id` structure containing the ID of the playback device to initialize. Setting this NULL (default) will use the system's + default playback device. Retrieve the device ID from the `ma_device_info` structure, which can be retrieved using device enumeration. - capture.pDeviceID - A pointer to a `ma_device_id` structure containing the ID of the capture device to initialize. Setting this NULL (default) will use the system's - default capture device. Retrieve the device ID from the `ma_device_info` structure, which can be retrieved using device enumeration. + playback.format + The sample format to use for playback. When set to `ma_format_unknown` the device's native format will be used. This can be retrieved after + initialization from the device object directly with `device.playback.format`. - capture.format - The sample format to use for capture. When set to `ma_format_unknown` the device's native format will be used. This can be retrieved after - initialization from the device object directly with `device.capture.format`. + playback.channels + The number of channels to use for playback. When set to 0 the device's native channel count will be used. This can be retrieved after initialization + from the device object directly with `device.playback.channels`. - capture.channels - The number of channels to use for capture. When set to 0 the device's native channel count will be used. This can be retrieved after initialization - from the device object directly with `device.capture.channels`. + playback.pChannelMap + The channel map to use for playback. When left empty, the device's native channel map will be used. This can be retrieved after initialization from the + device object direct with `device.playback.pChannelMap`. When set, the buffer should contain `channels` items. - capture.channelMap - The channel map to use for capture. When left empty, the device's native channel map will be used. This can be retrieved after initialization from the - device object direct with `device.capture.channelMap`. + playback.shareMode + The preferred share mode to use for playback. Can be either `ma_share_mode_shared` (default) or `ma_share_mode_exclusive`. Note that if you specify + exclusive mode, but it's not supported by the backend, initialization will fail. You can then fall back to shared mode if desired by changing this to + ma_share_mode_shared and reinitializing. - capture.shareMode - The preferred share mode to use for capture. Can be either `ma_share_mode_shared` (default) or `ma_share_mode_exclusive`. Note that if you specify - exclusive mode, but it's not supported by the backend, initialization will fail. You can then fall back to shared mode if desired by changing this to - ma_share_mode_shared and reinitializing. + capture.pDeviceID + A pointer to a `ma_device_id` structure containing the ID of the capture device to initialize. Setting this NULL (default) will use the system's + default capture device. Retrieve the device ID from the `ma_device_info` structure, which can be retrieved using device enumeration. - wasapi.noAutoConvertSRC - WASAPI only. When set to true, disables WASAPI's automatic resampling and forces the use of miniaudio's resampler. Defaults to false. + capture.format + The sample format to use for capture. When set to `ma_format_unknown` the device's native format will be used. This can be retrieved after + initialization from the device object directly with `device.capture.format`. - wasapi.noDefaultQualitySRC - WASAPI only. Only used when `wasapi.noAutoConvertSRC` is set to false. When set to true, disables the use of `AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY`. - You should usually leave this set to false, which is the default. + capture.channels + The number of channels to use for capture. When set to 0 the device's native channel count will be used. This can be retrieved after initialization + from the device object directly with `device.capture.channels`. - wasapi.noAutoStreamRouting - WASAPI only. When set to true, disables automatic stream routing on the WASAPI backend. Defaults to false. + capture.pChannelMap + The channel map to use for capture. When left empty, the device's native channel map will be used. This can be retrieved after initialization from the + device object direct with `device.capture.pChannelMap`. When set, the buffer should contain `channels` items. - wasapi.noHardwareOffloading - WASAPI only. When set to true, disables the use of WASAPI's hardware offloading feature. Defaults to false. + capture.shareMode + The preferred share mode to use for capture. Can be either `ma_share_mode_shared` (default) or `ma_share_mode_exclusive`. Note that if you specify + exclusive mode, but it's not supported by the backend, initialization will fail. You can then fall back to shared mode if desired by changing this to + ma_share_mode_shared and reinitializing. - alsa.noMMap - ALSA only. When set to true, disables MMap mode. Defaults to false. + wasapi.noAutoConvertSRC + WASAPI only. When set to true, disables WASAPI's automatic resampling and forces the use of miniaudio's resampler. Defaults to false. - alsa.noAutoFormat - ALSA only. When set to true, disables ALSA's automatic format conversion by including the SND_PCM_NO_AUTO_FORMAT flag. Defaults to false. + wasapi.noDefaultQualitySRC + WASAPI only. Only used when `wasapi.noAutoConvertSRC` is set to false. When set to true, disables the use of `AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY`. + You should usually leave this set to false, which is the default. - alsa.noAutoChannels - ALSA only. When set to true, disables ALSA's automatic channel conversion by including the SND_PCM_NO_AUTO_CHANNELS flag. Defaults to false. + wasapi.noAutoStreamRouting + WASAPI only. When set to true, disables automatic stream routing on the WASAPI backend. Defaults to false. - alsa.noAutoResample - ALSA only. When set to true, disables ALSA's automatic resampling by including the SND_PCM_NO_AUTO_RESAMPLE flag. Defaults to false. + wasapi.noHardwareOffloading + WASAPI only. When set to true, disables the use of WASAPI's hardware offloading feature. Defaults to false. - pulse.pStreamNamePlayback - PulseAudio only. Sets the stream name for playback. + alsa.noMMap + ALSA only. When set to true, disables MMap mode. Defaults to false. - pulse.pStreamNameCapture - PulseAudio only. Sets the stream name for capture. + alsa.noAutoFormat + ALSA only. When set to true, disables ALSA's automatic format conversion by including the SND_PCM_NO_AUTO_FORMAT flag. Defaults to false. - coreaudio.allowNominalSampleRateChange - Core Audio only. Desktop only. When enabled, allows the sample rate of the device to be changed at the operating system level. This - is disabled by default in order to prevent intrusive changes to the user's system. This is useful if you want to use a sample rate - that is known to be natively supported by the hardware thereby avoiding the cost of resampling. When set to true, miniaudio will - find the closest match between the sample rate requested in the device config and the sample rates natively supported by the - hardware. When set to false, the sample rate currently set by the operating system will always be used. + alsa.noAutoChannels + ALSA only. When set to true, disables ALSA's automatic channel conversion by including the SND_PCM_NO_AUTO_CHANNELS flag. Defaults to false. + + alsa.noAutoResample + ALSA only. When set to true, disables ALSA's automatic resampling by including the SND_PCM_NO_AUTO_RESAMPLE flag. Defaults to false. + + pulse.pStreamNamePlayback + PulseAudio only. Sets the stream name for playback. + + pulse.pStreamNameCapture + PulseAudio only. Sets the stream name for capture. + + coreaudio.allowNominalSampleRateChange + Core Audio only. Desktop only. When enabled, allows the sample rate of the device to be changed at the operating system level. This + is disabled by default in order to prevent intrusive changes to the user's system. This is useful if you want to use a sample rate + that is known to be natively supported by the hardware thereby avoiding the cost of resampling. When set to true, miniaudio will + find the closest match between the sample rate requested in the device config and the sample rates natively supported by the + hardware. When set to false, the sample rate currently set by the operating system will always be used. + + opensl.streamType + OpenSL only. Explicitly sets the stream type. If left unset (`ma_opensl_stream_type_default`), the + stream type will be left unset. Think of this as the type of audio you're playing. + + opensl.recordingPreset + OpenSL only. Explicitly sets the type of recording your program will be doing. When left + unset, the recording preset will be left unchanged. + + aaudio.usage + AAudio only. Explicitly sets the nature of the audio the program will be consuming. When + left unset, the usage will be left unchanged. + + aaudio.contentType + AAudio only. Sets the content type. When left unset, the content type will be left unchanged. + + aaudio.inputPreset + AAudio only. Explicitly sets the type of recording your program will be doing. When left + unset, the input preset will be left unchanged. + + aaudio.noAutoStartAfterReroute + AAudio only. Controls whether or not the device should be automatically restarted after a + stream reroute. When set to false (default) the device will be restarted automatically; + otherwise the device will be stopped. Once initialized, the device's config is immutable. If you need to change the config you will need to initialize a new device. @@ -767,7 +821,7 @@ foreign lib { ma_device device; ma_result result = ma_device_init(NULL, &config, &device); if (result != MA_SUCCESS) { - // Error + // Error } ``` @@ -782,14 +836,14 @@ foreign lib { ma_context context; ma_result result = ma_context_init(NULL, 0, NULL, &context); if (result != MA_SUCCESS) { - // Error + // Error } ma_device_info* pPlaybackDeviceInfos; ma_uint32 playbackDeviceCount; result = ma_context_get_devices(&context, &pPlaybackDeviceInfos, &playbackDeviceCount, NULL, NULL); if (result != MA_SUCCESS) { - // Error + // Error } // ... choose a device from pPlaybackDeviceInfos ... @@ -807,7 +861,7 @@ foreign lib { ma_device device; result = ma_device_init(&context, &config, &device); if (result != MA_SUCCESS) { - // Error + // Error } ``` @@ -833,19 +887,19 @@ foreign lib { Parameters ---------- backends (in, optional) - A list of backends to try initializing, in priority order. Can be NULL, in which case it uses default priority order. + A list of backends to try initializing, in priority order. Can be NULL, in which case it uses default priority order. backendCount (in, optional) - The number of items in `backend`. Ignored if `backend` is NULL. + The number of items in `backend`. Ignored if `backend` is NULL. pContextConfig (in, optional) - The context configuration. + The context configuration. pConfig (in) - A pointer to the device configuration. Cannot be null. See remarks for details. + A pointer to the device configuration. Cannot be null. See remarks for details. pDevice (out) - A pointer to the device object being initialized. + A pointer to the device object being initialized. Return Value @@ -890,7 +944,7 @@ foreign lib { Parameters ---------- pDevice (in) - A pointer to the device to stop. + A pointer to the device to stop. Return Value @@ -927,6 +981,95 @@ foreign lib { device_get_log :: proc(pDevice: ^device) -> ^log --- + /* + Retrieves information about the device. + + + Parameters + ---------- + pDevice (in) + A pointer to the device whose information is being retrieved. + + type (in) + The device type. This parameter is required for duplex devices. When retrieving device + information, you are doing so for an individual playback or capture device. + + pDeviceInfo (out) + A pointer to the `ma_device_info` that will receive the device information. + + + Return Value + ------------ + MA_SUCCESS if successful; any other error code otherwise. + + + Thread Safety + ------------- + Unsafe. This should be considered unsafe because it may be calling into the backend which may or + may not be safe. + + + Callback Safety + --------------- + Unsafe. You should avoid calling this in the data callback because it may call into the backend + which may or may not be safe. + */ + device_get_info :: proc(pDevice: ^device, type: device_type, pDeviceInfo: ^device_info) -> result --- + + + /* + Retrieves the name of the device. + + + Parameters + ---------- + pDevice (in) + A pointer to the device whose information is being retrieved. + + type (in) + The device type. This parameter is required for duplex devices. When retrieving device + information, you are doing so for an individual playback or capture device. + + pName (out) + A pointer to the buffer that will receive the name. + + nameCap (in) + The capacity of the output buffer, including space for the null terminator. + + pLengthNotIncludingNullTerminator (out, optional) + A pointer to the variable that will receive the length of the name, not including the null + terminator. + + + Return Value + ------------ + MA_SUCCESS if successful; any other error code otherwise. + + + Thread Safety + ------------- + Unsafe. This should be considered unsafe because it may be calling into the backend which may or + may not be safe. + + + Callback Safety + --------------- + Unsafe. You should avoid calling this in the data callback because it may call into the backend + which may or may not be safe. + + + Remarks + ------- + If the name does not fully fit into the output buffer, it'll be truncated. You can pass in NULL to + `pName` if you want to first get the length of the name for the purpose of memory allocation of the + output buffer. Allocating a buffer of size `MA_MAX_DEVICE_NAME_LENGTH + 1` should be enough for + most cases and will avoid the need for the inefficiency of calling this function twice. + + This is implemented in terms of `ma_device_get_info()`. + */ + device_get_name :: proc(pDevice: ^device, type: device_type, pName: [^]c.char, nameCap: c.size_t, pLengthNotIncludingNullTerminator: ^c.size_t) -> result --- + + /* Starts the device. For playback devices this begins playback. For capture devices it begins recording. @@ -936,7 +1079,7 @@ foreign lib { Parameters ---------- pDevice (in) - A pointer to the device to start. + A pointer to the device to start. Return Value @@ -979,7 +1122,7 @@ foreign lib { Parameters ---------- pDevice (in) - A pointer to the device to stop. + A pointer to the device to stop. Return Value @@ -1025,7 +1168,7 @@ foreign lib { Parameters ---------- pDevice (in) - A pointer to the device whose start state is being retrieved. + A pointer to the device whose start state is being retrieved. Return Value @@ -1059,24 +1202,24 @@ foreign lib { Parameters ---------- pDevice (in) - A pointer to the device whose state is being retrieved. + A pointer to the device whose state is being retrieved. Return Value ------------ The current state of the device. The return value will be one of the following: - +------------------------+------------------------------------------------------------------------------+ - | MA_STATE_UNINITIALIZED | Will only be returned if the device is in the middle of initialization. | - +------------------------+------------------------------------------------------------------------------+ - | MA_STATE_STOPPED | The device is stopped. The initial state of the device after initialization. | - +------------------------+------------------------------------------------------------------------------+ - | MA_STATE_STARTED | The device started and requesting and/or delivering audio data. | - +------------------------+------------------------------------------------------------------------------+ - | MA_STATE_STARTING | The device is in the process of starting. | - +------------------------+------------------------------------------------------------------------------+ - | MA_STATE_STOPPING | The device is in the process of stopping. | - +------------------------+------------------------------------------------------------------------------+ + +-------------------------------+------------------------------------------------------------------------------+ + | ma_device_state_uninitialized | Will only be returned if the device is in the middle of initialization. | + +-------------------------------+------------------------------------------------------------------------------+ + | ma_device_state_stopped | The device is stopped. The initial state of the device after initialization. | + +-------------------------------+------------------------------------------------------------------------------+ + | ma_device_state_started | The device started and requesting and/or delivering audio data. | + +-------------------------------+------------------------------------------------------------------------------+ + | ma_device_state_starting | The device is in the process of starting. | + +-------------------------------+------------------------------------------------------------------------------+ + | ma_device_state_stopping | The device is in the process of stopping. | + +-------------------------------+------------------------------------------------------------------------------+ Thread Safety @@ -1094,40 +1237,89 @@ foreign lib { ------- The general flow of a devices state goes like this: - ``` - ma_device_init() -> MA_STATE_UNINITIALIZED -> MA_STATE_STOPPED - ma_device_start() -> MA_STATE_STARTING -> MA_STATE_STARTED - ma_device_stop() -> MA_STATE_STOPPING -> MA_STATE_STOPPED - ``` + ``` + ma_device_init() -> ma_device_state_uninitialized -> ma_device_state_stopped + ma_device_start() -> ma_device_state_starting -> ma_device_state_started + ma_device_stop() -> ma_device_state_stopping -> ma_device_state_stopped + ``` When the state of the device is changed with `ma_device_start()` or `ma_device_stop()` at this same time as this function is called, the value returned by this function could potentially be out of sync. If this is significant to your program you need to implement your own synchronization. */ - device_get_state :: proc(pDevice: ^device) -> u32 --- + device_get_state :: proc(pDevice: ^device) -> device_state --- + + + /* + Performs post backend initialization routines for setting up internal data conversion. + + This should be called whenever the backend is initialized. The only time this should be called from + outside of miniaudio is if you're implementing a custom backend, and you would only do it if you + are reinitializing the backend due to rerouting or reinitializing for some reason. + + + Parameters + ---------- + pDevice [in] + A pointer to the device. + + deviceType [in] + The type of the device that was just reinitialized. + + pPlaybackDescriptor [in] + The descriptor of the playback device containing the internal data format and buffer sizes. + + pPlaybackDescriptor [in] + The descriptor of the capture device containing the internal data format and buffer sizes. + + + Return Value + ------------ + MA_SUCCESS if successful; any other error otherwise. + + + Thread Safety + ------------- + Unsafe. This will be reinitializing internal data converters which may be in use by another thread. + + + Callback Safety + --------------- + Unsafe. This will be reinitializing internal data converters which may be in use by the callback. + + + Remarks + ------- + For a duplex device, you can call this for only one side of the system. This is why the deviceType + is specified as a parameter rather than deriving it from the device. + + You do not need to call this manually unless you are doing a custom backend, in which case you need + only do it if you're manually performing rerouting or reinitialization. + */ + device_post_init :: proc(pDevice: ^device, deviceType: device_type, pPlaybackDescriptor, pCaptureDescriptor: ^device_descriptor) -> result --- /* Sets the master volume factor for the device. - The volume factor must be between 0 (silence) and 1 (full volume). Use `ma_device_set_master_gain_db()` to use decibel notation, where 0 is full volume and + The volume factor must be between 0 (silence) and 1 (full volume). Use `ma_device_set_master_volume_db()` to use decibel notation, where 0 is full volume and values less than 0 decreases the volume. Parameters ---------- pDevice (in) - A pointer to the device whose volume is being set. + A pointer to the device whose volume is being set. volume (in) - The new volume factor. Must be within the range of [0, 1]. + The new volume factor. Must be >= 0. Return Value ------------ MA_SUCCESS if the volume was set successfully. MA_INVALID_ARGS if pDevice is NULL. - MA_INVALID_ARGS if the volume factor is not within the range of [0, 1]. + MA_INVALID_ARGS if volume is negative. Thread Safety @@ -1150,8 +1342,8 @@ foreign lib { See Also -------- ma_device_get_master_volume() - ma_device_set_master_volume_gain_db() - ma_device_get_master_volume_gain_db() + ma_device_set_master_volume_db() + ma_device_get_master_volume_db() */ device_set_master_volume :: proc(pDevice: ^device, volume: f32) -> result --- @@ -1162,10 +1354,10 @@ foreign lib { Parameters ---------- pDevice (in) - A pointer to the device whose volume factor is being retrieved. + A pointer to the device whose volume factor is being retrieved. pVolume (in) - A pointer to the variable that will receive the volume factor. The returned value will be in the range of [0, 1]. + A pointer to the variable that will receive the volume factor. The returned value will be in the range of [0, 1]. Return Value @@ -1207,10 +1399,10 @@ foreign lib { Parameters ---------- pDevice (in) - A pointer to the device whose gain is being set. + A pointer to the device whose gain is being set. gainDB (in) - The new volume as gain in decibels. Must be less than or equal to 0, where 0 is full volume and anything less than 0 decreases the volume. + The new volume as gain in decibels. Must be less than or equal to 0, where 0 is full volume and anything less than 0 decreases the volume. Return Value @@ -1243,7 +1435,7 @@ foreign lib { ma_device_set_master_volume() ma_device_get_master_volume() */ - device_set_master_gain_db :: proc(pDevice: ^device, gainDB: f32) -> result --- + device_set_master_volume_db :: proc(pDevice: ^device, gainDB: f32) -> result --- /* Retrieves the master gain in decibels. @@ -1252,10 +1444,10 @@ foreign lib { Parameters ---------- pDevice (in) - A pointer to the device whose gain is being retrieved. + A pointer to the device whose gain is being retrieved. pGainDB (in) - A pointer to the variable that will receive the gain in decibels. The returned value will be <= 0. + A pointer to the variable that will receive the gain in decibels. The returned value will be <= 0. Return Value @@ -1282,11 +1474,11 @@ foreign lib { See Also -------- - ma_device_set_master_volume_gain_db() + ma_device_set_master_volume_db() ma_device_set_master_volume() ma_device_get_master_volume() */ - device_get_master_gain_db :: proc(pDevice: ^device, pGainDB: ^f32) -> result --- + device_get_master_volume_db :: proc(pDevice: ^device, pGainDB: ^f32) -> result --- /* @@ -1296,18 +1488,18 @@ foreign lib { Parameters ---------- pDevice (in) - A pointer to device whose processing the data callback. + A pointer to device whose processing the data callback. pOutput (out) - A pointer to the buffer that will receive the output PCM frame data. On a playback device this must not be NULL. On a duplex device - this can be NULL, in which case pInput must not be NULL. + A pointer to the buffer that will receive the output PCM frame data. On a playback device this must not be NULL. On a duplex device + this can be NULL, in which case pInput must not be NULL. pInput (in) - A pointer to the buffer containing input PCM frame data. On a capture device this must not be NULL. On a duplex device this can be - NULL, in which case `pOutput` must not be NULL. + A pointer to the buffer containing input PCM frame data. On a capture device this must not be NULL. On a duplex device this can be + NULL, in which case `pOutput` must not be NULL. frameCount (in) - The number of frames being processed. + The number of frames being processed. Return Value @@ -1334,7 +1526,7 @@ foreign lib { If you are implementing a custom backend, and that backend uses a callback for data delivery, you'll need to call this from inside that callback. */ - device_handle_backend_data_callback :: proc(pDevice: ^device, pOutput: rawptr, pInput: rawptr, frameCount: u32) -> result --- + device_handle_backend_data_callback :: proc(pDevice: ^device, pOutput, pInput: rawptr, frameCount: u32) -> result --- /* @@ -1351,18 +1543,18 @@ foreign lib { Parameters ---------- pDescriptor (in) - A pointer to device descriptor whose `periodSizeInFrames` and `periodSizeInMilliseconds` members - will be used for the calculation of the buffer size. + A pointer to device descriptor whose `periodSizeInFrames` and `periodSizeInMilliseconds` members + will be used for the calculation of the buffer size. nativeSampleRate (in) - The device's native sample rate. This is only ever used when the `periodSizeInFrames` member of - `pDescriptor` is zero. In this case, `periodSizeInMilliseconds` will be used instead, in which - case a sample rate is required to convert to a size in frames. + The device's native sample rate. This is only ever used when the `periodSizeInFrames` member of + `pDescriptor` is zero. In this case, `periodSizeInMilliseconds` will be used instead, in which + case a sample rate is required to convert to a size in frames. performanceProfile (in) - When both the `periodSizeInFrames` and `periodSizeInMilliseconds` members of `pDescriptor` are - zero, miniaudio will fall back to a buffer size based on the performance profile. The profile - to use for this calculation is determine by this parameter. + When both the `periodSizeInFrames` and `periodSizeInMilliseconds` members of `pDescriptor` are + zero, miniaudio will fall back to a buffer size based on the performance profile. The profile + to use for this calculation is determine by this parameter. Return Value @@ -1408,14 +1600,14 @@ foreign lib { Parameters ---------- pBackends (out, optional) - A pointer to the buffer that will receive the enabled backends. Set to NULL to retrieve the backend count. Setting - the capacity of the buffer to `MA_BUFFER_COUNT` will guarantee it's large enough for all backends. + A pointer to the buffer that will receive the enabled backends. Set to NULL to retrieve the backend count. Setting + the capacity of the buffer to `MA_BUFFER_COUNT` will guarantee it's large enough for all backends. backendCap (in) - The capacity of the `pBackends` buffer. + The capacity of the `pBackends` buffer. pBackendCount (out) - A pointer to the variable that will receive the enabled backend count. + A pointer to the variable that will receive the enabled backend count. Return Value @@ -1463,7 +1655,7 @@ foreign lib { result = ma_get_enabled_backends(enabledBackends, MA_BACKEND_COUNT, &enabledBackendCount); if (result != MA_SUCCESS) { - // Failed to retrieve enabled backends. Should never happen in this example since all inputs are valid. + // Failed to retrieve enabled backends. Should never happen in this example since all inputs are valid. } ``` diff --git a/vendor/miniaudio/device_io_types.odin b/vendor/miniaudio/device_io_types.odin index e05f94665..b3ecb2301 100644 --- a/vendor/miniaudio/device_io_types.odin +++ b/vendor/miniaudio/device_io_types.odin @@ -18,12 +18,13 @@ SUPPORT_WEBAUDIO :: false // ODIN_OS == .Emscripten SUPPORT_CUSTOM :: true SUPPORT_NULL :: true // ODIN_OS != .Emscripten -STATE_UNINITIALIZED :: 0 -STATE_STOPPED :: 1 /* The device's default state after initialization. */ -STATE_STARTED :: 2 /* The device is started and is requesting and/or delivering audio data. */ -STATE_STARTING :: 3 /* Transitioning from a stopped state to started. */ -STATE_STOPPING :: 4 /* Transitioning from a started state to stopped. */ - +device_state :: enum c.int { + uninitialized = 0, + stopped = 1, /* The device's default state after initialization. */ + started = 2, /* The device is started and is requesting and/or delivering audio data. */ + starting = 3, /* Transitioning from a stopped state to started. */ + stopping = 4, /* Transitioning from a started state to stopped. */ +} when SUPPORT_WASAPI { @@ -56,6 +57,96 @@ backend :: enum c.int { BACKEND_COUNT :: len(backend) +/* +Device job thread. This is used by backends that require asynchronous processing of certain +operations. It is not used by all backends. + +The device job thread is made up of a thread and a job queue. You can post a job to the thread with +ma_device_job_thread_post(). The thread will do the processing of the job. +*/ +device_job_thread_config :: struct { + noThread: b32, /* Set this to true if you want to process jobs yourself. */ + jobQueueCapacity: u32, + jobQueueFlags: u32, +} + +device_job_thread :: struct { + thread: thread, + jobQueue: job_queue, + _hasThread: b32, +} + + +/* Device notification types. */ +device_notification_type :: enum c.int { + started, + stopped, + rerouted, + interruption_began, + interruption_ended, +} + +device_notification :: struct { + pDevice: ^device, + type: device_notification_type, + data: struct #raw_union { + started: struct { + _unused: c.int, + }, + stopped: struct { + _unused: c.int, + }, + rerouted: struct { + _unused: c.int, + }, + interruption: struct { + _unused: c.int, + }, + }, +} + +/* +The notification callback for when the application should be notified of a change to the device. + +This callback is used for notifying the application of changes such as when the device has started, +stopped, rerouted or an interruption has occurred. Note that not all backends will post all +notification types. For example, some backends will perform automatic stream routing without any +kind of notification to the host program which means miniaudio will never know about it and will +never be able to fire the rerouted notification. You should keep this in mind when designing your +program. + +The stopped notification will *not* get fired when a device is rerouted. + + +Parameters +---------- +pNotification (in) + A pointer to a structure containing information about the event. Use the `pDevice` member of + this object to retrieve the relevant device. The `type` member can be used to discriminate + against each of the notification types. + + +Remarks +------- +Do not restart or uninitialize the device from the callback. + +Not all notifications will be triggered by all backends, however the started and stopped events +should be reliable for all backends. Some backends do not have a good way to detect device +stoppages due to unplugging the device which may result in the stopped callback not getting +fired. This has been observed with at least one BSD variant. + +The rerouted notification is fired *after* the reroute has occurred. The stopped notification will +*not* get fired when a device is rerouted. The following backends are known to do automatic stream +rerouting, but do not have a way to be notified of the change: + + * DirectSound + +The interruption notifications are used on mobile platforms for detecting when audio is interrupted +due to things like an incoming phone call. Currently this is only implemented on iOS. None of the +Android backends will report this notification. +*/ +device_notification_proc :: proc "c" (pNotification: ^device_notification) + /* The callback for processing audio data from the device. @@ -96,9 +187,11 @@ callback. The following APIs cannot be called from inside the callback: The proper way to stop the device is to call `ma_device_stop()` from a different thread, normally the main application thread. */ -device_callback_proc :: proc "c" (pDevice: ^device, pOutput: rawptr, pInput: rawptr, frameCount: u32) +device_data_proc :: proc "c" (pDevice: ^device, pOutput, pInput: rawptr, frameCount: u32) /* +DEPRECATED. Use ma_device_notification_proc instead. + The callback for when the device has been stopped. This will be called when the device is stopped explicitly with `ma_device_stop()` and also called implicitly when the device is stopped through external forces @@ -108,48 +201,15 @@ such as being unplugged or an internal error occuring. Parameters ---------- pDevice (in) - A pointer to the device that has just stopped. + A pointer to the device that has just stopped. Remarks ------- Do not restart or uninitialize the device from the callback. */ -stop_proc :: proc "c" (pDevice: ^device) +stop_proc :: proc "c" (pDevice: ^device) /* DEPRECATED. Use ma_device_notification_proc instead. */ -/* -The callback for handling log messages. - - -Parameters ----------- -pContext (in) - A pointer to the context the log message originated from. - -pDevice (in) - A pointer to the device the log message originate from, if any. This can be null, in which case the message came from the context. - -logLevel (in) - The log level. This can be one of the following: - - +----------------------+ - | Log Level | - +----------------------+ - | MA_LOG_LEVEL_DEBUG | - | MA_LOG_LEVEL_INFO | - | MA_LOG_LEVEL_WARNING | - | MA_LOG_LEVEL_ERROR | - +----------------------+ - -message (in) - The log message. - - -Remarks -------- -Do not modify the state of the device from inside the callback. -*/ -log_proc :: proc "c" (pContext: context_type, pDevice: ^device, logLevel: u32, message: cstring) device_type :: enum c.int { playback = 1, @@ -279,29 +339,14 @@ device_id :: struct #raw_union { DATA_FORMAT_FLAG_EXCLUSIVE_MODE :: 1 << 1 /* If set, this is supported in exclusive mode. Otherwise not natively supported by exclusive mode. */ +MAX_DEVICE_NAME_LENGTH :: 255 + device_info :: struct { /* Basic info. This is the only information guaranteed to be filled in during device enumeration. */ id: device_id, - name: [256]byte, + name: [MAX_DEVICE_NAME_LENGTH + 1]c.char, /* +1 for null terminator. */ isDefault: b32, - /* - Detailed info. As much of this is filled as possible with ma_context_get_device_info(). Note that you are allowed to initialize - a device with settings outside of this range, but it just means the data will be converted using miniaudio's data conversion - pipeline before sending the data to/from the device. Most programs will need to not worry about these values, but it's provided - here mainly for informational purposes or in the rare case that someone might find it useful. - - These will be set to 0 when returned by ma_context_enumerate_devices() or ma_context_get_devices(). - */ - formatCount: u32, - formats: [format]format, - minChannels: u32, - maxChannels: u32, - minSampleRate: u32, - maxSampleRate: u32, - - - /* Experimental. Don't use these right now. */ nativeDataFormatCount: u32, nativeDataFormats: [/*len(format_count) * standard_sample_rate.rate_count * MAX_CHANNELS*/ 64]struct { /* Not sure how big to make this. There can be *many* permutations for virtual devices which can support anything. */ format: format, /* Sample format. If set to ma_format_unknown, all sample formats are supported. */ @@ -312,31 +357,26 @@ device_info :: struct { } device_config :: struct { - deviceType: device_type, - sampleRate: u32, - periodSizeInFrames: u32, - periodSizeInMilliseconds: u32, - periods: u32, - performanceProfile: performance_profile, - noPreZeroedOutputBuffer: b8, /* When set to true, the contents of the output buffer passed into the data callback will be left undefined rather than initialized to zero. */ - noClip: b8, /* When set to true, the contents of the output buffer passed into the data callback will be clipped after returning. Only applies when the playback sample format is f32. */ - dataCallback: device_callback_proc, - stopCallback: stop_proc, - pUserData: rawptr, - resampling: struct { - algorithm: resample_algorithm, - linear: struct { - lpfOrder: u32, - }, - speex: struct { - quality: c.int, - }, - }, + deviceType: device_type, + sampleRate: u32, + periodSizeInFrames: u32, + periodSizeInMilliseconds: u32, + periods: u32, + performanceProfile: performance_profile, + noPreSilencedOutputBuffer: b8, /* When set to true, the contents of the output buffer passed into the data callback will be left undefined rather than initialized to zero. */ + noClip: b8, /* When set to true, the contents of the output buffer passed into the data callback will be clipped after returning. Only applies when the playback sample format is f32. */ + noDisableDenormals: b8, /* Do not disable denormals when firing the data callback. */ + noFixedSizedCallback: b8, /* Disables strict fixed-sized data callbacks. Setting this to true will result in the period size being treated only as a hint to the backend. This is an optimization for those who don't need fixed sized callbacks. */ + dataCallback: device_data_proc, + notificationCallback: device_notification_proc, + stopCallback: stop_proc, + pUserData: rawptr, + resampling: resampler_config, playback: struct { pDeviceID: ^device_id, format: format, channels: u32, - channelMap: [MAX_CHANNELS]channel, + channelMap: [^]channel, channelMixMode: channel_mix_mode, shareMode: share_mode, }, @@ -344,7 +384,7 @@ device_config :: struct { pDeviceID: ^device_id, format: format, channels: u32, - channelMap: [MAX_CHANNELS]channel, + channelMap: [^]channel, channelMixMode: channel_mix_mode, shareMode: share_mode, }, @@ -373,9 +413,10 @@ device_config :: struct { recordingPreset: opensl_recording_preset, }, aaudio: struct { - usage: aaudio_usage, - contentType: aaudio_content_type, - inputPreset: aaudio_input_preset, + usage: aaudio_usage, + contentType: aaudio_content_type, + inputPreset: aaudio_input_preset, + noAutoStartAfterReroute: b32, }, } @@ -425,14 +466,14 @@ to many devices. A device is created from a context. The general flow goes like this: 1) A context is created with `onContextInit()` - 1a) Available devices can be enumerated with `onContextEnumerateDevices()` if required. - 1b) Detailed information about a device can be queried with `onContextGetDeviceInfo()` if required. + 1a) Available devices can be enumerated with `onContextEnumerateDevices()` if required. + 1b) Detailed information about a device can be queried with `onContextGetDeviceInfo()` if required. 2) A device is created from the context that was created in the first step using `onDeviceInit()`, and optionally a device ID that was - selected from device enumeration via `onContextEnumerateDevices()`. + selected from device enumeration via `onContextEnumerateDevices()`. 3) A device is started or stopped with `onDeviceStart()` / `onDeviceStop()` 4) Data is delivered to and from the device by the backend. This is always done based on the native format returned by the prior call - to `onDeviceInit()`. Conversion between the device's native format and the format requested by the application will be handled by - miniaudio internally. + to `onDeviceInit()`. Conversion between the device's native format and the format requested by the application will be handled by + miniaudio internally. Initialization of the context is quite simple. You need to do any necessary initialization of internal objects and then output the callbacks defined in this structure. @@ -440,7 +481,7 @@ callbacks defined in this structure. Once the context has been initialized you can initialize a device. Before doing so, however, the application may want to know which physical devices are available. This is where `onContextEnumerateDevices()` comes in. This is fairly simple. For each device, fire the given callback with, at a minimum, the basic information filled out in `ma_device_info`. When the callback returns `MA_FALSE`, enumeration -needs to stop and the `onContextEnumerateDevices()` function return with a success code. +needs to stop and the `onContextEnumerateDevices()` function returns with a success code. Detailed device information can be retrieved from a device ID using `onContextGetDeviceInfo()`. This takes as input the device type and ID, and on output returns detailed information about the device in `ma_device_info`. The `onContextGetDeviceInfo()` callback must handle the @@ -455,7 +496,7 @@ internally by miniaudio. On input, if the sample format is set to `ma_format_unknown`, the backend is free to use whatever sample format it desires, so long as it's supported by miniaudio. When the channel count is set to 0, the backend should use the device's native channel count. The same applies for -sample rate. For the channel map, the default should be used when `ma_channel_map_blank()` returns true (all channels set to +sample rate. For the channel map, the default should be used when `ma_channel_map_is_blank()` returns true (all channels set to `MA_CHANNEL_NONE`). On input, the `periodSizeInFrames` or `periodSizeInMilliseconds` option should always be set. The backend should inspect both of these variables. If `periodSizeInFrames` is set, it should take priority, otherwise it needs to be derived from the period size in milliseconds (`periodSizeInMilliseconds`) and the sample rate, keeping in mind that the sample rate may be 0, in which case the @@ -474,14 +515,17 @@ This allows miniaudio to then process any necessary data conversion and then pas If the backend requires absolute flexibility with it's data delivery, it can optionally implement the `onDeviceDataLoop()` callback which will allow it to implement the logic that will run on the audio thread. This is much more advanced and is completely optional. -The audio thread should run data delivery logic in a loop while `ma_device_get_state() == MA_STATE_STARTED` and no errors have been +The audio thread should run data delivery logic in a loop while `ma_device_get_state() == ma_device_state_started` and no errors have been encounted. Do not start or stop the device here. That will be handled from outside the `onDeviceDataLoop()` callback. The invocation of the `onDeviceDataLoop()` callback will be handled by miniaudio. When you start the device, miniaudio will fire this -callback. When the device is stopped, the `ma_device_get_state() == MA_STATE_STARTED` condition will fail and the loop will be terminated +callback. When the device is stopped, the `ma_device_get_state() == ma_device_state_started` condition will fail and the loop will be terminated which will then fall through to the part that stops the device. For an example on how to implement the `onDeviceDataLoop()` callback, look at `ma_device_audio_thread__default_read_write()`. Implement the `onDeviceDataLoopWakeup()` callback if you need a mechanism to wake up the audio thread. + +If the backend supports an optimized retrieval of device information from an initialized `ma_device` object, it should implement the +`onDeviceGetInfo()` callback. This is optional, in which case it will fall back to `onContextGetDeviceInfo()` which is less efficient. */ backend_callbacks :: struct { onContextInit: proc "c" (pContext: ^context_type, pConfig: ^context_config, pCallbacks: ^backend_callbacks) -> result, @@ -496,10 +540,10 @@ backend_callbacks :: struct { onDeviceWrite: proc "c" (pDevice: ^device, pFrames: rawptr, frameCount: u32, pFramesWritten: ^u32) -> result, onDeviceDataLoop: proc "c" (pDevice: ^device) -> result, onDeviceDataLoopWakeup: proc "c" (pDevice: ^device) -> result, + onDeviceGetInfo: proc "c" (pDevice: ^device, type: device_type, pDeviceInfo: ^device_info) -> result, } context_config :: struct { - logCallback: log_proc, /* Legacy logging callback. Will be removed in version 0.11. */ pLog: ^log, threadPriority: thread_priority, threadStackSize: c.size_t, @@ -538,7 +582,7 @@ context_command__wasapi :: struct { deviceType: device_type, pAudioClient: rawptr, ppAudioClientService: ^rawptr, - pResult: ^rawptr, /* The result from creating the audio client service. */ + pResult: ^result, /* The result from creating the audio client service. */ }, releaseAudioClient: struct { pDevice: ^device, @@ -548,21 +592,20 @@ context_command__wasapi :: struct { } context_type :: struct { - callbacks: backend_callbacks, - backend: backend, /* DirectSound, ALSA, etc. */ - pLog: ^log, - log: log, /* Only used if the log is owned by the context. The pLog member will be set to &log in this case. */ - logCallback: log_proc, /* Legacy callback. Will be removed in version 0.11. */ - threadPriority: thread_priority, - threadStackSize: c.size_t, - pUserData: rawptr, - allocationCallbacks: allocation_callbacks, - deviceEnumLock: mutex, /* Used to make ma_context_get_devices() thread safe. */ - deviceInfoLock: mutex, /* Used to make ma_context_get_device_info() thread safe. */ - deviceInfoCapacity: u32, /* Total capacity of pDeviceInfos. */ + callbacks: backend_callbacks, + backend: backend, /* DirectSound, ALSA, etc. */ + pLog: ^log, + log: log, /* Only used if the log is owned by the context. The pLog member will be set to &log in this case. */ + threadPriority: thread_priority, + threadStackSize: c.size_t, + pUserData: rawptr, + allocationCallbacks: allocation_callbacks, + deviceEnumLock: mutex, /* Used to make ma_context_get_devices() thread safe. */ + deviceInfoLock: mutex, /* Used to make ma_context_get_device_info() thread safe. */ + deviceInfoCapacity: u32, /* Total capacity of pDeviceInfos. */ playbackDeviceInfoCount: u32, - captureDeviceInfoCount: u32, - pDeviceInfos: [^]device_info, /* Playback devices first, then capture. */ + captureDeviceInfoCount: u32, + pDeviceInfos: [^]device_info, /* Playback devices first, then capture. */ using _: struct #raw_union { wasapi: (struct { @@ -575,7 +618,7 @@ context_type :: struct { } when SUPPORT_WASAPI else struct {}), dsound: (struct { - DSoundDLL: handle, + hDSoundDLL: handle, DirectSoundCreate: proc "system" (), DirectSoundEnumerateA: proc "system" (), DirectSoundCaptureCreate: proc "system" (), @@ -741,6 +784,8 @@ context_type :: struct { /*pa_mainloop**/ pMainLoop: rawptr, /*pa_context**/ pPulseContext: rawptr, + pApplicationName: cstring, /* Set when the context is initialized. Used by devices for their local pa_context objects. */ + pServerName: cstring, /* Set when the context is initialized. Used by devices for their local pa_context objects. */ } when SUPPORT_PULSEAUDIO else struct {}), jack: (struct { @@ -762,7 +807,7 @@ context_type :: struct { jack_port_get_buffer: proc "system" (), jack_free: proc "system" (), - pClientName: [^]c.char, + pClientName: cstring, tryStartServer: b32, } when SUPPORT_JACK else struct {}), @@ -817,7 +862,7 @@ context_type :: struct { } when SUPPORT_SNDIO else struct {}), audio4: (struct { - _unused: cint, + _unused: c.int, } when SUPPORT_AUDIO4 else struct {}), oss: (struct { @@ -855,6 +900,7 @@ context_type :: struct { AAudioStream_getFramesPerBurst: proc "system" (), AAudioStream_requestStart: proc "system" (), AAudioStream_requestStop: proc "system" (), + jobThread: device_job_thread, /* For processing operations outside of the error callback, specifically device disconnections and rerouting. */ } when SUPPORT_AAUDIO else struct {}), opensl: (struct { @@ -921,37 +967,40 @@ context_type :: struct { } device :: struct { - pContext: ^context_type, - type: device_type, - sampleRate: u32, - state: u32, /*atomic*/ /* The state of the device is variable and can change at any time on any thread. Must be used atomically. */ - onData: device_callback_proc, /* Set once at initialization time and should not be changed after. */ - onStop: stop_proc, /* Set once at initialization time and should not be changed after. */ - pUserData: rawptr, /* Application defined data. */ - startStopLock: mutex, - wakeupEvent: event, - startEvent: event, - stopEvent: event, - device_thread: thread, - workResult: result, /* This is set by the worker thread after it's finished doing a job. */ - isOwnerOfContext: b8, /* When set to true, uninitializing the device will also uninitialize the context. Set to true when NULL is passed into ma_device_init(). */ - noPreZeroedOutputBuffer: b8, - noClip: b8, - masterVolumeFactor: f32, /*atomic*/ /* Linear 0..1. Can be read and written simultaneously by different threads. Must be used atomically. */ - duplexRB: duplex_rb, /* Intermediary buffer for duplex device on asynchronous backends. */ + pContext: ^context_type, + type: device_type, + sampleRate: u32, + state: u32, /*atomic*/ /* The state of the device is variable and can change at any time on any thread. Must be used atomically. */ + onData: device_data_proc, /* Set once at initialization time and should not be changed after. */ + onNotification: device_notification_proc, /* Set once at initialization time and should not be changed after. */ + onStop: stop_proc, /* DEPRECATED. Use the notification callback instead. Set once at initialization time and should not be changed after. */ + pUserData: rawptr, /* Application defined data. */ + startStopLock: mutex, + wakeupEvent: event, + startEvent: event, + stopEvent: event, + device_thread: thread, + workResult: result, /* This is set by the worker thread after it's finished doing a job. */ + isOwnerOfContext: b8, /* When set to true, uninitializing the device will also uninitialize the context. Set to true when NULL is passed into ma_device_init(). */ + noPreSilencedOutputBuffer: b8, + noClip: b8, + noDisableDenormals: b8, + noFixedSizedCallback: b8, + masterVolumeFactor: f32, /*atomic*/ /* Linear 0..1. Can be read and written simultaneously by different threads. Must be used atomically. */ + duplexRB: duplex_rb, /* Intermediary buffer for duplex device on asynchronous backends. */ resampling: struct { - algorithm: resample_algorithm, + algorithm: resample_algorithm, + pBackendVTable: ^resampling_backend_vtable, + pBackendUserData: rawptr, linear: struct { lpfOrder: u32, }, - speex: struct { - quality: c.int, - }, }, playback: struct { - id: device_id, /* If using an explicit device, will be set to a copy of the ID used for initialization. Otherwise cleared to 0. */ - name: [256]byte, /* Maybe temporary. Likely to be replaced with a query API. */ - shareMode: share_mode, /* Set to whatever was passed in when the device was initialized. */ + pID: ^device_id, /* Set to NULL if using default ID, otherwise set to the address of "id". */ + id: device_id, /* If using an explicit device, will be set to a copy of the ID used for initialization. Otherwise cleared to 0. */ + name: [MAX_DEVICE_NAME_LENGTH + 1]c.char, /* Maybe temporary. Likely to be replaced with a query API. */ + shareMode: share_mode, /* Set to whatever was passed in when the device was initialized. */ playback_format: format, channels: u32, channelMap: [MAX_CHANNELS]channel, @@ -963,11 +1012,19 @@ device :: struct { internalPeriods: u32, channelMixMode: channel_mix_mode, converter: data_converter, + pIntermediaryBuffer: rawptr, /* For implementing fixed sized buffer callbacks. Will be null if using variable sized callbacks. */ + intermediaryBufferCap: u32, + intermediaryBufferLen: u32, /* How many valid frames are sitting in the intermediary buffer. */ + pInputCache: rawptr, /* In external format. Can be null. */ + inputCacheCap: u64, + inputCacheConsumed: u64, + inputCacheRemaining: u64, }, capture: struct { - id: device_id, /* If using an explicit device, will be set to a copy of the ID used for initialization. Otherwise cleared to 0. */ - name: [256]byte, /* Maybe temporary. Likely to be replaced with a query API. */ - shareMode: share_mode, /* Set to whatever was passed in when the device was initialized. */ + pID: ^device_id, /* Set to NULL if using default ID, otherwise set to the address of "id". */ + id: device_id, /* If using an explicit device, will be set to a copy of the ID used for initialization. Otherwise cleared to 0. */ + name: [MAX_DEVICE_NAME_LENGTH + 1]c.char, /* Maybe temporary. Likely to be replaced with a query API. */ + shareMode: share_mode, /* Set to whatever was passed in when the device was initialized. */ capture_format: format, channels: u32, channelMap: [MAX_CHANNELS]channel, @@ -979,6 +1036,9 @@ device :: struct { internalPeriods: u32, channelMixMode: channel_mix_mode, converter: data_converter, + pIntermediaryBuffer: rawptr, /* For implementing fixed sized buffer callbacks. Will be null if using variable sized callbacks. */ + intermediaryBufferCap: u32, + intermediaryBufferLen: u32, /* How many valid frames are sitting in the intermediary buffer. */ }, using _: struct #raw_union { @@ -991,7 +1051,7 @@ device :: struct { notificationClient: IMMNotificationClient, /*HANDLE*/ hEventPlayback: handle, /* Auto reset. Initialized to signaled. */ /*HANDLE*/ hEventCapture: handle, /* Auto reset. Initialized to unsignaled. */ - actualPeriodSizeInFramesPlayback: u32, /* Value from GetBufferSize(). internalPeriodSizeInFrames is not set to the _actual_ buffer size when low-latency shared mode is being used due to the way the IAudioClient3 API works. */ + actualPeriodSizeInFramesPlayback: u32, /* Value from GetBufferSize(). internalPeriodSizeInFrames is not set to the _actual_ buffer size when low-latency shared mode is being used due to the way the IAudioClient3 API works. */ actualPeriodSizeInFramesCapture: u32, originalPeriodSizeInFrames: u32, originalPeriodSizeInMilliseconds: u32, @@ -999,8 +1059,14 @@ device :: struct { originalPerformanceProfile: performance_profile, periodSizeInFramesPlayback: u32, periodSizeInFramesCapture: u32, - isStartedCapture: b32, /*atomic*/ /* Can be read and written simultaneously across different threads. Must be used atomically, and must be 32-bit. */ - isStartedPlayback: b32, /*atomic*/ /* Can be read and written simultaneously across different threads. Must be used atomically, and must be 32-bit. */ + pMappedBufferCapture: rawptr, + mappedBufferCaptureCap: u32, + mappedBufferCaptureLen: u32, + pMappedBufferPlayback: rawptr, + mappedBufferPlaybackCap: u32, + mappedBufferPlaybackLen: u32, + isStartedCapture: b32, /*atomic*/ /* Can be read and written simultaneously across different threads. Must be used atomically, and must be 32-bit. */ + isStartedPlayback: b32, /*atomic*/ /* Can be read and written simultaneously across different threads. Must be used atomically, and must be 32-bit. */ noAutoConvertSRC: b8, /* When set to true, disables the use of AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM. */ noDefaultQualitySRC: b8, /* When set to true, disables the use of AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY. */ noHardwareOffloading: b8, @@ -1049,14 +1115,16 @@ device :: struct { } when SUPPORT_ALSA else struct {}), pulse: (struct { + /*pa_mainloop**/ pMainLoop: rawptr, + /*pa_context**/ pPulseContext: rawptr, /*pa_stream**/ pStreamPlayback: rawptr, /*pa_stream**/ pStreamCapture: rawptr, } when SUPPORT_PULSEAUDIO else struct {}), jack: (struct { /*jack_client_t**/ pClient: rawptr, - /*jack_port_t**/ pPortsPlayback: [MAX_CHANNELS]rawptr, - /*jack_port_t**/ pPortsCapture: [MAX_CHANNELS]rawptr, + /*jack_port_t**/ pPortsPlayback: [^]rawptr, + /*jack_port_t**/ pPortsCapture: [^]rawptr, pIntermediaryBufferPlayback: [^]f32, /* Typed as a float because JACK is always floating point. */ pIntermediaryBufferCapture: [^]f32, } when SUPPORT_JACK else struct {}), @@ -1079,6 +1147,7 @@ device :: struct { isSwitchingCaptureDevice: b32, /* <-- Set to true when the default device has changed and miniaudio is in the process of switching. */ pRouteChangeHandler: rawptr, /* Only used on mobile platforms. Obj-C object for handling route changes. */ } when SUPPORT_COREAUDIO else struct {}), + sndio: (struct { handlePlayback: rawptr, handleCapture: rawptr, @@ -1099,6 +1168,10 @@ device :: struct { aaudio: (struct { /*AAudioStream**/ pStreamPlayback: rawptr, /*AAudioStream**/ pStreamCapture: rawptr, + usage: aaudio_usage, + contentType: aaudio_content_type, + inputPreset: aaudio_input_preset, + noAutoStartAfterReroute: b32, } when SUPPORT_AAUDIO else struct {}), opensl: (struct { diff --git a/vendor/miniaudio/doc.odin b/vendor/miniaudio/doc.odin index 887e5d149..c6de0ec61 100644 --- a/vendor/miniaudio/doc.odin +++ b/vendor/miniaudio/doc.odin @@ -2,7 +2,7 @@ package miniaudio /* Audio playback and capture library. Choice of public domain or MIT-0. See license statements at the end of this file. -miniaudio - v0.10.42 - 2021-08-22 +miniaudio - v0.11.9 - 2022-04-20 David Reid - mackron@gmail.com @@ -14,7 +14,8 @@ GitHub: https://github.com/mackron/miniaudio /* 1. Introduction =============== -miniaudio is a single file library for audio playback and capture. To use it, do the following in one .c file: +miniaudio is a single file library for audio playback and capture. To use it, do the following in +one .c file: ```c #define MINIAUDIO_IMPLEMENTATION @@ -23,16 +24,44 @@ miniaudio is a single file library for audio playback and capture. To use it, do You can do `#include "miniaudio.h"` in other parts of the program just like any other header. -miniaudio uses the concept of a "device" as the abstraction for physical devices. The idea is that you choose a physical device to emit or capture audio from, -and then move data to/from the device when miniaudio tells you to. Data is delivered to and from devices asynchronously via a callback which you specify when -initializing the device. +miniaudio includes both low level and high level APIs. The low level API is good for those who want +to do all of their mixing themselves and only require a light weight interface to the underlying +audio device. The high level API is good for those who have complex mixing and effect requirements. -When initializing the device you first need to configure it. The device configuration allows you to specify things like the format of the data delivered via -the callback, the size of the internal buffer and the ID of the device you want to emit or capture audio from. +In miniaudio, objects are transparent structures. Unlike many other libraries, there are no handles +to opaque objects which means you need to allocate memory for objects yourself. In the examples +presented in this documentation you will often see objects declared on the stack. You need to be +careful when translating these examples to your own code so that you don't accidentally declare +your objects on the stack and then cause them to become invalid once the function returns. In +addition, you must ensure the memory address of your objects remain the same throughout their +lifetime. You therefore cannot be making copies of your objects. -Once you have the device configuration set up you can initialize the device. When initializing a device you need to allocate memory for the device object -beforehand. This gives the application complete control over how the memory is allocated. In the example below we initialize a playback device on the stack, -but you could allocate it on the heap if that suits your situation better. +A config/init pattern is used throughout the entire library. The idea is that you set up a config +object and pass that into the initialization routine. The advantage to this system is that the +config object can be initialized with logical defaults and new properties added to it without +breaking the API. The config object can be allocated on the stack and does not need to be +maintained after initialization of the corresponding object. + + +1.1. Low Level API +------------------ +The low level API gives you access to the raw audio data of an audio device. It supports playback, +capture, full-duplex and loopback (WASAPI only). You can enumerate over devices to determine which +physical device(s) you want to connect to. + +The low level API uses the concept of a "device" as the abstraction for physical devices. The idea +is that you choose a physical device to emit or capture audio from, and then move data to/from the +device when miniaudio tells you to. Data is delivered to and from devices asynchronously via a +callback which you specify when initializing the device. + +When initializing the device you first need to configure it. The device configuration allows you to +specify things like the format of the data delivered via the callback, the size of the internal +buffer and the ID of the device you want to emit or capture audio from. + +Once you have the device configuration set up you can initialize the device. When initializing a +device you need to allocate memory for the device object beforehand. This gives the application +complete control over how the memory is allocated. In the example below we initialize a playback +device on the stack, but you could allocate it on the heap if that suits your situation better. ```c void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount) @@ -65,20 +94,27 @@ but you could allocate it on the heap if that suits your situation better. } ``` -In the example above, `data_callback()` is where audio data is written and read from the device. The idea is in playback mode you cause sound to be emitted -from the speakers by writing audio data to the output buffer (`pOutput` in the example). In capture mode you read data from the input buffer (`pInput`) to -extract sound captured by the microphone. The `frameCount` parameter tells you how many frames can be written to the output buffer and read from the input -buffer. A "frame" is one sample for each channel. For example, in a stereo stream (2 channels), one frame is 2 samples: one for the left, one for the right. -The channel count is defined by the device config. The size in bytes of an individual sample is defined by the sample format which is also specified in the -device config. Multi-channel audio data is always interleaved, which means the samples for each frame are stored next to each other in memory. For example, in -a stereo stream the first pair of samples will be the left and right samples for the first frame, the second pair of samples will be the left and right samples -for the second frame, etc. +In the example above, `data_callback()` is where audio data is written and read from the device. +The idea is in playback mode you cause sound to be emitted from the speakers by writing audio data +to the output buffer (`pOutput` in the example). In capture mode you read data from the input +buffer (`pInput`) to extract sound captured by the microphone. The `frameCount` parameter tells you +how many frames can be written to the output buffer and read from the input buffer. A "frame" is +one sample for each channel. For example, in a stereo stream (2 channels), one frame is 2 +samples: one for the left, one for the right. The channel count is defined by the device config. +The size in bytes of an individual sample is defined by the sample format which is also specified +in the device config. Multi-channel audio data is always interleaved, which means the samples for +each frame are stored next to each other in memory. For example, in a stereo stream the first pair +of samples will be the left and right samples for the first frame, the second pair of samples will +be the left and right samples for the second frame, etc. -The configuration of the device is defined by the `ma_device_config` structure. The config object is always initialized with `ma_device_config_init()`. It's -important to always initialize the config with this function as it initializes it with logical defaults and ensures your program doesn't break when new members -are added to the `ma_device_config` structure. The example above uses a fairly simple and standard device configuration. The call to `ma_device_config_init()` -takes a single parameter, which is whether or not the device is a playback, capture, duplex or loopback device (loopback devices are not supported on all -backends). The `config.playback.format` member sets the sample format which can be one of the following (all formats are native-endian): +The configuration of the device is defined by the `ma_device_config` structure. The config object +is always initialized with `ma_device_config_init()`. It's important to always initialize the +config with this function as it initializes it with logical defaults and ensures your program +doesn't break when new members are added to the `ma_device_config` structure. The example above +uses a fairly simple and standard device configuration. The call to `ma_device_config_init()` takes +a single parameter, which is whether or not the device is a playback, capture, duplex or loopback +device (loopback devices are not supported on all backends). The `config.playback.format` member +sets the sample format which can be one of the following (all formats are native-endian): +---------------+----------------------------------------+---------------------------+ | Symbol | Description | Range | @@ -90,22 +126,30 @@ backends). The `config.playback.format` member sets the sample format which can | ma_format_u8 | 8-bit unsigned integer | [0, 255] | +---------------+----------------------------------------+---------------------------+ -The `config.playback.channels` member sets the number of channels to use with the device. The channel count cannot exceed MA_MAX_CHANNELS. The -`config.sampleRate` member sets the sample rate (which must be the same for both playback and capture in full-duplex configurations). This is usually set to -44100 or 48000, but can be set to anything. It's recommended to keep this between 8000 and 384000, however. +The `config.playback.channels` member sets the number of channels to use with the device. The +channel count cannot exceed MA_MAX_CHANNELS. The `config.sampleRate` member sets the sample rate +(which must be the same for both playback and capture in full-duplex configurations). This is +usually set to 44100 or 48000, but can be set to anything. It's recommended to keep this between +8000 and 384000, however. -Note that leaving the format, channel count and/or sample rate at their default values will result in the internal device's native configuration being used -which is useful if you want to avoid the overhead of miniaudio's automatic data conversion. +Note that leaving the format, channel count and/or sample rate at their default values will result +in the internal device's native configuration being used which is useful if you want to avoid the +overhead of miniaudio's automatic data conversion. -In addition to the sample format, channel count and sample rate, the data callback and user data pointer are also set via the config. The user data pointer is -not passed into the callback as a parameter, but is instead set to the `pUserData` member of `ma_device` which you can access directly since all miniaudio -structures are transparent. +In addition to the sample format, channel count and sample rate, the data callback and user data +pointer are also set via the config. The user data pointer is not passed into the callback as a +parameter, but is instead set to the `pUserData` member of `ma_device` which you can access +directly since all miniaudio structures are transparent. -Initializing the device is done with `ma_device_init()`. This will return a result code telling you what went wrong, if anything. On success it will return -`MA_SUCCESS`. After initialization is complete the device will be in a stopped state. To start it, use `ma_device_start()`. Uninitializing the device will stop -it, which is what the example above does, but you can also stop the device with `ma_device_stop()`. To resume the device simply call `ma_device_start()` again. -Note that it's important to never stop or start the device from inside the callback. This will result in a deadlock. Instead you set a variable or signal an -event indicating that the device needs to stop and handle it in a different thread. The following APIs must never be called inside the callback: +Initializing the device is done with `ma_device_init()`. This will return a result code telling you +what went wrong, if anything. On success it will return `MA_SUCCESS`. After initialization is +complete the device will be in a stopped state. To start it, use `ma_device_start()`. +Uninitializing the device will stop it, which is what the example above does, but you can also stop +the device with `ma_device_stop()`. To resume the device simply call `ma_device_start()` again. +Note that it's important to never stop or start the device from inside the callback. This will +result in a deadlock. Instead you set a variable or signal an event indicating that the device +needs to stop and handle it in a different thread. The following APIs must never be called inside +the callback: ```c ma_device_init() @@ -115,12 +159,14 @@ event indicating that the device needs to stop and handle it in a different thre ma_device_stop() ``` -You must never try uninitializing and reinitializing a device inside the callback. You must also never try to stop and start it from inside the callback. There -are a few other things you shouldn't do in the callback depending on your requirements, however this isn't so much a thread-safety thing, but rather a -real-time processing thing which is beyond the scope of this introduction. +You must never try uninitializing and reinitializing a device inside the callback. You must also +never try to stop and start it from inside the callback. There are a few other things you shouldn't +do in the callback depending on your requirements, however this isn't so much a thread-safety +thing, but rather a real-time processing thing which is beyond the scope of this introduction. -The example above demonstrates the initialization of a playback device, but it works exactly the same for capture. All you need to do is change the device type -from `ma_device_type_playback` to `ma_device_type_capture` when setting up the config, like so: +The example above demonstrates the initialization of a playback device, but it works exactly the +same for capture. All you need to do is change the device type from `ma_device_type_playback` to +`ma_device_type_capture` when setting up the config, like so: ```c ma_device_config config = ma_device_config_init(ma_device_type_capture); @@ -128,8 +174,9 @@ from `ma_device_type_playback` to `ma_device_type_capture` when setting up the c config.capture.channels = MY_CHANNEL_COUNT; ``` -In the data callback you just read from the input buffer (`pInput` in the example above) and leave the output buffer alone (it will be set to NULL when the -device type is set to `ma_device_type_capture`). +In the data callback you just read from the input buffer (`pInput` in the example above) and leave +the output buffer alone (it will be set to NULL when the device type is set to +`ma_device_type_capture`). These are the available device types and how you should handle the buffers in the callback: @@ -142,23 +189,29 @@ These are the available device types and how you should handle the buffers in th | ma_device_type_loopback | Read from input buffer, leave output buffer untouched. | +-------------------------+--------------------------------------------------------+ -You will notice in the example above that the sample format and channel count is specified separately for playback and capture. This is to support different -data formats between the playback and capture devices in a full-duplex system. An example may be that you want to capture audio data as a monaural stream (one -channel), but output sound to a stereo speaker system. Note that if you use different formats between playback and capture in a full-duplex configuration you -will need to convert the data yourself. There are functions available to help you do this which will be explained later. +You will notice in the example above that the sample format and channel count is specified +separately for playback and capture. This is to support different data formats between the playback +and capture devices in a full-duplex system. An example may be that you want to capture audio data +as a monaural stream (one channel), but output sound to a stereo speaker system. Note that if you +use different formats between playback and capture in a full-duplex configuration you will need to +convert the data yourself. There are functions available to help you do this which will be +explained later. -The example above did not specify a physical device to connect to which means it will use the operating system's default device. If you have multiple physical -devices connected and you want to use a specific one you will need to specify the device ID in the configuration, like so: +The example above did not specify a physical device to connect to which means it will use the +operating system's default device. If you have multiple physical devices connected and you want to +use a specific one you will need to specify the device ID in the configuration, like so: ```c config.playback.pDeviceID = pMyPlaybackDeviceID; // Only if requesting a playback or duplex device. config.capture.pDeviceID = pMyCaptureDeviceID; // Only if requesting a capture, duplex or loopback device. ``` -To retrieve the device ID you will need to perform device enumeration, however this requires the use of a new concept called the "context". Conceptually -speaking the context sits above the device. There is one context to many devices. The purpose of the context is to represent the backend at a more global level -and to perform operations outside the scope of an individual device. Mainly it is used for performing run-time linking against backend libraries, initializing -backends and enumerating devices. The example below shows how to enumerate devices. +To retrieve the device ID you will need to perform device enumeration, however this requires the +use of a new concept called the "context". Conceptually speaking the context sits above the device. +There is one context to many devices. The purpose of the context is to represent the backend at a +more global level and to perform operations outside the scope of an individual device. Mainly it is +used for performing run-time linking against backend libraries, initializing backends and +enumerating devices. The example below shows how to enumerate devices. ```c ma_context context; @@ -199,44 +252,236 @@ backends and enumerating devices. The example below shows how to enumerate devic ma_context_uninit(&context); ``` -The first thing we do in this example is initialize a `ma_context` object with `ma_context_init()`. The first parameter is a pointer to a list of `ma_backend` -values which are used to override the default backend priorities. When this is NULL, as in this example, miniaudio's default priorities are used. The second -parameter is the number of backends listed in the array pointed to by the first parameter. The third parameter is a pointer to a `ma_context_config` object -which can be NULL, in which case defaults are used. The context configuration is used for setting the logging callback, custom memory allocation callbacks, -user-defined data and some backend-specific configurations. +The first thing we do in this example is initialize a `ma_context` object with `ma_context_init()`. +The first parameter is a pointer to a list of `ma_backend` values which are used to override the +default backend priorities. When this is NULL, as in this example, miniaudio's default priorities +are used. The second parameter is the number of backends listed in the array pointed to by the +first parameter. The third parameter is a pointer to a `ma_context_config` object which can be +NULL, in which case defaults are used. The context configuration is used for setting the logging +callback, custom memory allocation callbacks, user-defined data and some backend-specific +configurations. -Once the context has been initialized you can enumerate devices. In the example above we use the simpler `ma_context_get_devices()`, however you can also use a -callback for handling devices by using `ma_context_enumerate_devices()`. When using `ma_context_get_devices()` you provide a pointer to a pointer that will, -upon output, be set to a pointer to a buffer containing a list of `ma_device_info` structures. You also provide a pointer to an unsigned integer that will -receive the number of items in the returned buffer. Do not free the returned buffers as their memory is managed internally by miniaudio. +Once the context has been initialized you can enumerate devices. In the example above we use the +simpler `ma_context_get_devices()`, however you can also use a callback for handling devices by +using `ma_context_enumerate_devices()`. When using `ma_context_get_devices()` you provide a pointer +to a pointer that will, upon output, be set to a pointer to a buffer containing a list of +`ma_device_info` structures. You also provide a pointer to an unsigned integer that will receive +the number of items in the returned buffer. Do not free the returned buffers as their memory is +managed internally by miniaudio. -The `ma_device_info` structure contains an `id` member which is the ID you pass to the device config. It also contains the name of the device which is useful -for presenting a list of devices to the user via the UI. +The `ma_device_info` structure contains an `id` member which is the ID you pass to the device +config. It also contains the name of the device which is useful for presenting a list of devices +to the user via the UI. -When creating your own context you will want to pass it to `ma_device_init()` when initializing the device. Passing in NULL, like we do in the first example, -will result in miniaudio creating the context for you, which you don't want to do since you've already created a context. Note that internally the context is -only tracked by it's pointer which means you must not change the location of the `ma_context` object. If this is an issue, consider using `malloc()` to -allocate memory for the context. +When creating your own context you will want to pass it to `ma_device_init()` when initializing the +device. Passing in NULL, like we do in the first example, will result in miniaudio creating the +context for you, which you don't want to do since you've already created a context. Note that +internally the context is only tracked by it's pointer which means you must not change the location +of the `ma_context` object. If this is an issue, consider using `malloc()` to allocate memory for +the context. + + +1.2. High Level API +------------------- +The high level API consists of three main parts: + + * Resource management for loading and streaming sounds. + * A node graph for advanced mixing and effect processing. + * A high level "engine" that wraps around the resource manager and node graph. + +The resource manager (`ma_resource_manager`) is used for loading sounds. It supports loading sounds +fully into memory and also streaming. It will also deal with reference counting for you which +avoids the same sound being loaded multiple times. + +The node graph is used for mixing and effect processing. The idea is that you connect a number of +nodes into the graph by connecting each node's outputs to another node's inputs. Each node can +implement it's own effect. By chaining nodes together, advanced mixing and effect processing can +be achieved. + +The engine encapsulates both the resource manager and the node graph to create a simple, easy to +use high level API. The resource manager and node graph APIs are covered in more later sections of +this manual. + +The code below shows how you can initialize an engine using it's default configuration. + + ```c + ma_result result; + ma_engine engine; + + result = ma_engine_init(NULL, &engine); + if (result != MA_SUCCESS) { + return result; // Failed to initialize the engine. + } + ``` + +This creates an engine instance which will initialize a device internally which you can access with +`ma_engine_get_device()`. It will also initialize a resource manager for you which can be accessed +with `ma_engine_get_resource_manager()`. The engine itself is a node graph (`ma_node_graph`) which +means you can pass a pointer to the engine object into any of the `ma_node_graph` APIs (with a +cast). Alternatively, you can use `ma_engine_get_node_graph()` instead of a cast. + +Note that all objects in miniaudio, including the `ma_engine` object in the example above, are +transparent structures. There are no handles to opaque structures in miniaudio which means you need +to be mindful of how you declare them. In the example above we are declaring it on the stack, but +this will result in the struct being invalidated once the function encapsulating it returns. If +allocating the engine on the heap is more appropriate, you can easily do so with a standard call +to `malloc()` or whatever heap allocation routine you like: + + ```c + ma_engine* pEngine = malloc(sizeof(*pEngine)); + ``` + +The `ma_engine` API uses the same config/init pattern used all throughout miniaudio. To configure +an engine, you can fill out a `ma_engine_config` object and pass it into the first parameter of +`ma_engine_init()`: + + ```c + ma_result result; + ma_engine engine; + ma_engine_config engineConfig; + + engineConfig = ma_engine_config_init(); + engineConfig.pResourceManager = &myCustomResourceManager; // <-- Initialized as some earlier stage. + + result = ma_engine_init(&engineConfig, &engine); + if (result != MA_SUCCESS) { + return result; + } + ``` + +This creates an engine instance using a custom config. In this particular example it's showing how +you can specify a custom resource manager rather than having the engine initialize one internally. +This is particularly useful if you want to have multiple engine's share the same resource manager. + +The engine must be uninitialized with `ma_engine_uninit()` when it's no longer needed. + +By default the engine will be started, but nothing will be playing because no sounds have been +initialized. The easiest but least flexible way of playing a sound is like so: + + ```c + ma_engine_play_sound(&engine, "my_sound.wav", NULL); + ``` + +This plays what miniaudio calls an "inline" sound. It plays the sound once, and then puts the +internal sound up for recycling. The last parameter is used to specify which sound group the sound +should be associated with which will be explained later. This particular way of playing a sound is +simple, but lacks flexibility and features. A more flexible way of playing a sound is to first +initialize a sound: + + ```c + ma_result result; + ma_sound sound; + + result = ma_sound_init_from_file(&engine, "my_sound.wav", 0, NULL, NULL, &sound); + if (result != MA_SUCCESS) { + return result; + } + + ma_sound_start(&sound); + ``` + +This returns a `ma_sound` object which represents a single instance of the specified sound file. If +you want to play the same file multiple times simultaneously, you need to create one sound for each +instance. + +Sounds should be uninitialized with `ma_sound_uninit()`. + +Sounds are not started by default. Start a sound with `ma_sound_start()` and stop it with +`ma_sound_stop()`. When a sound is stopped, it is not rewound to the start. Use +`ma_sound_seek_to_pcm_frames(&sound, 0)` to seek back to the start of a sound. By default, starting +and stopping sounds happens immediately, but sometimes it might be convenient to schedule the sound +the be started and/or stopped at a specific time. This can be done with the following functions: + + ```c + ma_sound_set_start_time_in_pcm_frames() + ma_sound_set_start_time_in_milliseconds() + ma_sound_set_stop_time_in_pcm_frames() + ma_sound_set_stop_time_in_milliseconds() + ``` + +The start/stop time needs to be specified based on the absolute timer which is controlled by the +engine. The current global time time in PCM frames can be retrieved with `ma_engine_get_time()`. +The engine's global time can be changed with `ma_engine_set_time()` for synchronization purposes if +required. Note that scheduling a start time still requires an explicit call to `ma_sound_start()` +before anything will play: + + ```c + ma_sound_set_start_time_in_pcm_frames(&sound, ma_engine_get_time(&engine) + (ma_engine_get_sample_rate(&engine) * 2); + ma_sound_start(&sound); + ``` + +The third parameter of `ma_sound_init_from_file()` is a set of flags that control how the sound be +loaded and a few options on which features should be enabled for that sound. By default, the sound +is synchronously loaded fully into memory straight from the file system without any kind of +decoding. If you want to decode the sound before storing it in memory, you need to specify the +`MA_SOUND_FLAG_DECODE` flag. This is useful if you want to incur the cost of decoding at an earlier +stage, such as a loading stage. Without this option, decoding will happen dynamically at mixing +time which might be too expensive on the audio thread. + +If you want to load the sound asynchronously, you can specify the `MA_SOUND_FLAG_ASYNC` flag. This +will result in `ma_sound_init_from_file()` returning quickly, but the sound will not start playing +until the sound has had some audio decoded. + +The fourth parameter is a pointer to sound group. A sound group is used as a mechanism to organise +sounds into groups which have their own effect processing and volume control. An example is a game +which might have separate groups for sfx, voice and music. Each of these groups have their own +independent volume control. Use `ma_sound_group_init()` or `ma_sound_group_init_ex()` to initialize +a sound group. + +Sounds and sound groups are nodes in the engine's node graph and can be plugged into any `ma_node` +API. This makes it possible to connect sounds and sound groups to effect nodes to produce complex +effect chains. + +A sound can have it's volume changed with `ma_sound_set_volume()`. If you prefer decibel volume +control you can use `ma_volume_db_to_linear()` to convert from decibel representation to linear. + +Panning and pitching is supported with `ma_sound_set_pan()` and `ma_sound_set_pitch()`. If you know +a sound will never have it's pitch changed with `ma_sound_set_pitch()` or via the doppler effect, +you can specify the `MA_SOUND_FLAG_NO_PITCH` flag when initializing the sound for an optimization. + +By default, sounds and sound groups have spatialization enabled. If you don't ever want to +spatialize your sounds, initialize the sound with the `MA_SOUND_FLAG_NO_SPATIALIZATION` flag. The +spatialization model is fairly simple and is roughly on feature parity with OpenAL. HRTF and +environmental occlusion are not currently supported, but planned for the future. The supported +features include: + + * Sound and listener positioning and orientation with cones + * Attenuation models: none, inverse, linear and exponential + * Doppler effect + +Sounds can be faded in and out with `ma_sound_set_fade_in_pcm_frames()`. + +To check if a sound is currently playing, you can use `ma_sound_is_playing()`. To check if a sound +is at the end, use `ma_sound_at_end()`. Looping of a sound can be controlled with +`ma_sound_set_looping()`. Use `ma_sound_is_looping()` to check whether or not the sound is looping. 2. Building =========== -miniaudio should work cleanly out of the box without the need to download or install any dependencies. See below for platform-specific details. +miniaudio should work cleanly out of the box without the need to download or install any +dependencies. See below for platform-specific details. 2.1. Windows ------------ -The Windows build should compile cleanly on all popular compilers without the need to configure any include paths nor link to any libraries. +The Windows build should compile cleanly on all popular compilers without the need to configure any +include paths nor link to any libraries. + +The UWP build may require linking to mmdevapi.lib if you get errors about an unresolved external +symbol for `ActivateAudioInterfaceAsync()`. + 2.2. macOS and iOS ------------------ -The macOS build should compile cleanly without the need to download any dependencies nor link to any libraries or frameworks. The iOS build needs to be -compiled as Objective-C and will need to link the relevant frameworks but should compile cleanly out of the box with Xcode. Compiling through the command line -requires linking to `-lpthread` and `-lm`. +The macOS build should compile cleanly without the need to download any dependencies nor link to +any libraries or frameworks. The iOS build needs to be compiled as Objective-C and will need to +link the relevant frameworks but should compile cleanly out of the box with Xcode. Compiling +through the command line requires linking to `-lpthread` and `-lm`. -Due to the way miniaudio links to frameworks at runtime, your application may not pass Apple's notarization process. To fix this there are two options. The -first is to use the `MA_NO_RUNTIME_LINKING` option, like so: +Due to the way miniaudio links to frameworks at runtime, your application may not pass Apple's +notarization process. To fix this there are two options. The first is to use the +`MA_NO_RUNTIME_LINKING` option, like so: ```c #ifdef __APPLE__ @@ -246,8 +491,9 @@ first is to use the `MA_NO_RUNTIME_LINKING` option, like so: #include "miniaudio.h" ``` -This will require linking with `-framework CoreFoundation -framework CoreAudio -framework AudioUnit`. Alternatively, if you would rather keep using runtime -linking you can add the following to your entitlements.xcent file: +This will require linking with `-framework CoreFoundation -framework CoreAudio -framework AudioUnit`. +Alternatively, if you would rather keep using runtime linking you can add the following to your +entitlements.xcent file: ``` com.apple.security.cs.allow-dyld-environment-variables @@ -256,26 +502,37 @@ linking you can add the following to your entitlements.xcent file: ``` +See this discussion for more info: https://github.com/mackron/miniaudio/issues/203. + 2.3. Linux ---------- -The Linux build only requires linking to `-ldl`, `-lpthread` and `-lm`. You do not need any development packages. +The Linux build only requires linking to `-ldl`, `-lpthread` and `-lm`. You do not need any +development packages. You may need to link with `-latomic` if you're compiling for 32-bit ARM. + 2.4. BSD -------- -The BSD build only requires linking to `-lpthread` and `-lm`. NetBSD uses audio(4), OpenBSD uses sndio and FreeBSD uses OSS. +The BSD build only requires linking to `-lpthread` and `-lm`. NetBSD uses audio(4), OpenBSD uses +sndio and FreeBSD uses OSS. You may need to link with `-latomic` if you're compiling for 32-bit +ARM. + 2.5. Android ------------ -AAudio is the highest priority backend on Android. This should work out of the box without needing any kind of compiler configuration. Support for AAudio -starts with Android 8 which means older versions will fall back to OpenSL|ES which requires API level 16+. +AAudio is the highest priority backend on Android. This should work out of the box without needing +any kind of compiler configuration. Support for AAudio starts with Android 8 which means older +versions will fall back to OpenSL|ES which requires API level 16+. + +There have been reports that the OpenSL|ES backend fails to initialize on some Android based +devices due to `dlopen()` failing to open "libOpenSLES.so". If this happens on your platform +you'll need to disable run-time linking with `MA_NO_RUNTIME_LINKING` and link with -lOpenSLES. -There have been reports that the OpenSL|ES backend fails to initialize on some Android based devices due to `dlopen()` failing to open "libOpenSLES.so". If -this happens on your platform you'll need to disable run-time linking with `MA_NO_RUNTIME_LINKING` and link with -lOpenSLES. 2.6. Emscripten --------------- -The Emscripten build emits Web Audio JavaScript directly and should compile cleanly out of the box. You cannot use -std=c* compiler flags, nor -ansi. +The Emscripten build emits Web Audio JavaScript directly and should compile cleanly out of the box. +You cannot use `-std=c*` compiler flags, nor `-ansi`. 2.7. Build Options @@ -368,28 +625,26 @@ The Emscripten build emits Web Audio JavaScript directly and should compile clea +----------------------------------+--------------------------------------------------------------------+ | MA_NO_MP3 | Disables the built-in MP3 decoder. | +----------------------------------+--------------------------------------------------------------------+ - | MA_NO_DEVICE_IO | Disables playback and recording. This will disable ma_context and | - | | ma_device APIs. This is useful if you only want to use miniaudio's | - | | data conversion and/or decoding APIs. | + | MA_NO_DEVICE_IO | Disables playback and recording. This will disable `ma_context` | + | | and `ma_device` APIs. This is useful if you only want to use | + | | miniaudio's data conversion and/or decoding APIs. | +----------------------------------+--------------------------------------------------------------------+ - | MA_NO_THREADING | Disables the ma_thread, ma_mutex, ma_semaphore and ma_event APIs. | - | | This option is useful if you only need to use miniaudio for data | - | | conversion, decoding and/or encoding. Some families of APIs | - | | require threading which means the following options must also be | - | | set: | + | MA_NO_THREADING | Disables the `ma_thread`, `ma_mutex`, `ma_semaphore` and | + | | `ma_event` APIs. This option is useful if you only need to use | + | | miniaudio for data conversion, decoding and/or encoding. Some | + | | families of APIsrequire threading which means the following | + | | options must also be set: | | | | | | ``` | | | MA_NO_DEVICE_IO | | | ``` | +----------------------------------+--------------------------------------------------------------------+ - | MA_NO_GENERATION | Disables generation APIs such a ma_waveform and ma_noise. | + | MA_NO_GENERATION | Disables generation APIs such a `ma_waveform` and `ma_noise`. | +----------------------------------+--------------------------------------------------------------------+ | MA_NO_SSE2 | Disables SSE2 optimizations. | +----------------------------------+--------------------------------------------------------------------+ | MA_NO_AVX2 | Disables AVX2 optimizations. | +----------------------------------+--------------------------------------------------------------------+ - | MA_NO_AVX512 | Disables AVX-512 optimizations. | - +----------------------------------+--------------------------------------------------------------------+ | MA_NO_NEON | Disables NEON optimizations. | +----------------------------------+--------------------------------------------------------------------+ | MA_NO_RUNTIME_LINKING | Disables runtime linking. This is useful for passing Apple's | @@ -401,47 +656,47 @@ The Emscripten build emits Web Audio JavaScript directly and should compile clea | | You may need to enable this if your target platform does not allow | | | runtime linking via `dlopen()`. | +----------------------------------+--------------------------------------------------------------------+ - | MA_DEBUG_OUTPUT | Enable processing of MA_LOG_LEVEL_DEBUG messages and `printf()` | - | | output. | + | MA_DEBUG_OUTPUT | Enable `printf()` output of debug logs (`MA_LOG_LEVEL_DEBUG`). | +----------------------------------+--------------------------------------------------------------------+ | MA_COINIT_VALUE | Windows only. The value to pass to internal calls to | | | `CoInitializeEx()`. Defaults to `COINIT_MULTITHREADED`. | +----------------------------------+--------------------------------------------------------------------+ | MA_API | Controls how public APIs should be decorated. Default is `extern`. | +----------------------------------+--------------------------------------------------------------------+ - | MA_DLL | If set, configures MA_API to either import or export APIs | - | | depending on whether or not the implementation is being defined. | - | | If defining the implementation, MA_API will be configured to | - | | export. Otherwise it will be configured to import. This has no | - | | effect if MA_API is defined externally. | - +----------------------------------+--------------------------------------------------------------------+ 3. Definitions ============== -This section defines common terms used throughout miniaudio. Unfortunately there is often ambiguity in the use of terms throughout the audio space, so this -section is intended to clarify how miniaudio uses each term. +This section defines common terms used throughout miniaudio. Unfortunately there is often ambiguity +in the use of terms throughout the audio space, so this section is intended to clarify how miniaudio +uses each term. 3.1. Sample ----------- -A sample is a single unit of audio data. If the sample format is f32, then one sample is one 32-bit floating point number. +A sample is a single unit of audio data. If the sample format is f32, then one sample is one 32-bit +floating point number. 3.2. Frame / PCM Frame ---------------------- -A frame is a group of samples equal to the number of channels. For a stereo stream a frame is 2 samples, a mono frame is 1 sample, a 5.1 surround sound frame -is 6 samples, etc. The terms "frame" and "PCM frame" are the same thing in miniaudio. Note that this is different to a compressed frame. If ever miniaudio -needs to refer to a compressed frame, such as a FLAC frame, it will always clarify what it's referring to with something like "FLAC frame". +A frame is a group of samples equal to the number of channels. For a stereo stream a frame is 2 +samples, a mono frame is 1 sample, a 5.1 surround sound frame is 6 samples, etc. The terms "frame" +and "PCM frame" are the same thing in miniaudio. Note that this is different to a compressed frame. +If ever miniaudio needs to refer to a compressed frame, such as a FLAC frame, it will always +clarify what it's referring to with something like "FLAC frame". 3.3. Channel ------------ -A stream of monaural audio that is emitted from an individual speaker in a speaker system, or received from an individual microphone in a microphone system. A -stereo stream has two channels (a left channel, and a right channel), a 5.1 surround sound system has 6 channels, etc. Some audio systems refer to a channel as -a complex audio stream that's mixed with other channels to produce the final mix - this is completely different to miniaudio's use of the term "channel" and -should not be confused. +A stream of monaural audio that is emitted from an individual speaker in a speaker system, or +received from an individual microphone in a microphone system. A stereo stream has two channels (a +left channel, and a right channel), a 5.1 surround sound system has 6 channels, etc. Some audio +systems refer to a channel as a complex audio stream that's mixed with other channels to produce +the final mix - this is completely different to miniaudio's use of the term "channel" and should +not be confused. 3.4. Sample Rate ---------------- -The sample rate in miniaudio is always expressed in Hz, such as 44100, 48000, etc. It's the number of PCM frames that are processed per second. +The sample rate in miniaudio is always expressed in Hz, such as 44100, 48000, etc. It's the number +of PCM frames that are processed per second. 3.5. Formats ------------ @@ -461,10 +716,1685 @@ All formats are native-endian. -4. Decoding +4. Data Sources +=============== +The data source abstraction in miniaudio is used for retrieving audio data from some source. A few +examples include `ma_decoder`, `ma_noise` and `ma_waveform`. You will need to be familiar with data +sources in order to make sense of some of the higher level concepts in miniaudio. + +The `ma_data_source` API is a generic interface for reading from a data source. Any object that +implements the data source interface can be plugged into any `ma_data_source` function. + +To read data from a data source: + + ```c + ma_result result; + ma_uint64 framesRead; + + result = ma_data_source_read_pcm_frames(pDataSource, pFramesOut, frameCount, &framesRead, loop); + if (result != MA_SUCCESS) { + return result; // Failed to read data from the data source. + } + ``` + +If you don't need the number of frames that were successfully read you can pass in `NULL` to the +`pFramesRead` parameter. If this returns a value less than the number of frames requested it means +the end of the file has been reached. `MA_AT_END` will be returned only when the number of frames +read is 0. + +When calling any data source function, with the exception of `ma_data_source_init()` and +`ma_data_source_uninit()`, you can pass in any object that implements a data source. For example, +you could plug in a decoder like so: + + ```c + ma_result result; + ma_uint64 framesRead; + ma_decoder decoder; // <-- This would be initialized with `ma_decoder_init_*()`. + + result = ma_data_source_read_pcm_frames(&decoder, pFramesOut, frameCount, &framesRead, loop); + if (result != MA_SUCCESS) { + return result; // Failed to read data from the decoder. + } + ``` + +If you want to seek forward you can pass in `NULL` to the `pFramesOut` parameter. Alternatively you +can use `ma_data_source_seek_pcm_frames()`. + +To seek to a specific PCM frame: + + ```c + result = ma_data_source_seek_to_pcm_frame(pDataSource, frameIndex); + if (result != MA_SUCCESS) { + return result; // Failed to seek to PCM frame. + } + ``` + +You can retrieve the total length of a data source in PCM frames, but note that some data sources +may not have the notion of a length, such as noise and waveforms, and others may just not have a +way of determining the length such as some decoders. To retrieve the length: + + ```c + ma_uint64 length; + + result = ma_data_source_get_length_in_pcm_frames(pDataSource, &length); + if (result != MA_SUCCESS) { + return result; // Failed to retrieve the length. + } + ``` + +Care should be taken when retrieving the length of a data source where the underlying decoder is +pulling data from a data stream with an undefined length, such as internet radio or some kind of +broadcast. If you do this, `ma_data_source_get_length_in_pcm_frames()` may never return. + +The current position of the cursor in PCM frames can also be retrieved: + + ```c + ma_uint64 cursor; + + result = ma_data_source_get_cursor_in_pcm_frames(pDataSource, &cursor); + if (result != MA_SUCCESS) { + return result; // Failed to retrieve the cursor. + } + ``` + +You will often need to know the data format that will be returned after reading. This can be +retrieved like so: + + ```c + ma_format format; + ma_uint32 channels; + ma_uint32 sampleRate; + ma_channel channelMap[MA_MAX_CHANNELS]; + + result = ma_data_source_get_data_format(pDataSource, &format, &channels, &sampleRate, channelMap, MA_MAX_CHANNELS); + if (result != MA_SUCCESS) { + return result; // Failed to retrieve data format. + } + ``` + +If you do not need a specific data format property, just pass in NULL to the respective parameter. + +There may be cases where you want to implement something like a sound bank where you only want to +read data within a certain range of the underlying data. To do this you can use a range: + + ```c + result = ma_data_source_set_range_in_pcm_frames(pDataSource, rangeBegInFrames, rangeEndInFrames); + if (result != MA_SUCCESS) { + return result; // Failed to set the range. + } + ``` + +This is useful if you have a sound bank where many sounds are stored in the same file and you want +the data source to only play one of those sub-sounds. + +Custom loop points can also be used with data sources. By default, data sources will loop after +they reach the end of the data source, but if you need to loop at a specific location, you can do +the following: + + ```c + result = ma_data_set_loop_point_in_pcm_frames(pDataSource, loopBegInFrames, loopEndInFrames); + if (result != MA_SUCCESS) { + return result; // Failed to set the loop point. + } + ``` + +The loop point is relative to the current range. + +It's sometimes useful to chain data sources together so that a seamless transition can be achieved. +To do this, you can use chaining: + + ```c + ma_decoder decoder1; + ma_decoder decoder2; + + // ... initialize decoders with ma_decoder_init_*() ... + + result = ma_data_source_set_next(&decoder1, &decoder2); + if (result != MA_SUCCESS) { + return result; // Failed to set the next data source. + } + + result = ma_data_source_read_pcm_frames(&decoder1, pFramesOut, frameCount, pFramesRead, MA_FALSE); + if (result != MA_SUCCESS) { + return result; // Failed to read from the decoder. + } + ``` + +In the example above we're using decoders. When reading from a chain, you always want to read from +the top level data source in the chain. In the example above, `decoder1` is the top level data +source in the chain. When `decoder1` reaches the end, `decoder2` will start seamlessly without any +gaps. + +Note that the `loop` parameter is set to false in the example above. When this is set to true, only +the current data source will be looped. You can loop the entire chain by linking in a loop like so: + + ```c + ma_data_source_set_next(&decoder1, &decoder2); // decoder1 -> decoder2 + ma_data_source_set_next(&decoder2, &decoder1); // decoder2 -> decoder1 (loop back to the start). + ``` + +Note that setting up chaining is not thread safe, so care needs to be taken if you're dynamically +changing links while the audio thread is in the middle of reading. + +Do not use `ma_decoder_seek_to_pcm_frame()` as a means to reuse a data source to play multiple +instances of the same sound simultaneously. Instead, initialize multiple data sources for each +instance. This can be extremely inefficient depending on the data source and can result in +glitching due to subtle changes to the state of internal filters. + + +4.1. Custom Data Sources +------------------------ +You can implement a custom data source by implementing the functions in `ma_data_source_vtable`. +Your custom object must have `ma_data_source_base` as it's first member: + + ```c + struct my_data_source + { + ma_data_source_base base; + ... + }; + ``` + +In your initialization routine, you need to call `ma_data_source_init()` in order to set up the +base object (`ma_data_source_base`): + + ```c + static ma_result my_data_source_read(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) + { + // Read data here. Output in the same format returned by my_data_source_get_data_format(). + } + + static ma_result my_data_source_seek(ma_data_source* pDataSource, ma_uint64 frameIndex) + { + // Seek to a specific PCM frame here. Return MA_NOT_IMPLEMENTED if seeking is not supported. + } + + static ma_result my_data_source_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) + { + // Return the format of the data here. + } + + static ma_result my_data_source_get_cursor(ma_data_source* pDataSource, ma_uint64* pCursor) + { + // Retrieve the current position of the cursor here. Return MA_NOT_IMPLEMENTED and set *pCursor to 0 if there is no notion of a cursor. + } + + static ma_result my_data_source_get_length(ma_data_source* pDataSource, ma_uint64* pLength) + { + // Retrieve the length in PCM frames here. Return MA_NOT_IMPLEMENTED and set *pLength to 0 if there is no notion of a length or if the length is unknown. + } + + static g_my_data_source_vtable = + { + my_data_source_read, + my_data_source_seek, + my_data_source_get_data_format, + my_data_source_get_cursor, + my_data_source_get_length + }; + + ma_result my_data_source_init(my_data_source* pMyDataSource) + { + ma_result result; + ma_data_source_config baseConfig; + + baseConfig = ma_data_source_config_init(); + baseConfig.vtable = &g_my_data_source_vtable; + + result = ma_data_source_init(&baseConfig, &pMyDataSource->base); + if (result != MA_SUCCESS) { + return result; + } + + // ... do the initialization of your custom data source here ... + + return MA_SUCCESS; + } + + void my_data_source_uninit(my_data_source* pMyDataSource) + { + // ... do the uninitialization of your custom data source here ... + + // You must uninitialize the base data source. + ma_data_source_uninit(&pMyDataSource->base); + } + ``` + +Note that `ma_data_source_init()` and `ma_data_source_uninit()` are never called directly outside +of the custom data source. It's up to the custom data source itself to call these within their own +init/uninit functions. + + + +5. Engine +========= +The `ma_engine` API is a high level API for managing and mixing sounds and effect processing. The +`ma_engine` object encapsulates a resource manager and a node graph, both of which will be +explained in more detail later. + +Sounds are called `ma_sound` and are created from an engine. Sounds can be associated with a mixing +group called `ma_sound_group` which are also created from the engine. Both `ma_sound` and +`ma_sound_group` objects are nodes within the engine's node graph. + +When the engine is initialized, it will normally create a device internally. If you would rather +manage the device yourself, you can do so and just pass a pointer to it via the engine config when +you initialize the engine. You can also just use the engine without a device, which again can be +configured via the engine config. + +The most basic way to initialize the engine is with a default config, like so: + + ```c + ma_result result; + ma_engine engine; + + result = ma_engine_init(NULL, &engine); + if (result != MA_SUCCESS) { + return result; // Failed to initialize the engine. + } + ``` + +This will result in the engine initializing a playback device using the operating system's default +device. This will be sufficient for many use cases, but if you need more flexibility you'll want to +configure the engine with an engine config: + + ```c + ma_result result; + ma_engine engine; + ma_engine_config engineConfig; + + engineConfig = ma_engine_config_init(); + engineConfig.pPlaybackDevice = &myDevice; + + result = ma_engine_init(&engineConfig, &engine); + if (result != MA_SUCCESS) { + return result; // Failed to initialize the engine. + } + ``` + +In the example above we're passing in a pre-initialized device. Since the caller is the one in +control of the device's data callback, it's their responsibility to manually call +`ma_engine_read_pcm_frames()` from inside their data callback: + + ```c + void playback_data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount) + { + ma_engine_read_pcm_frames(&g_Engine, pOutput, frameCount, NULL); + } + ``` + +You can also use the engine independent of a device entirely: + + ```c + ma_result result; + ma_engine engine; + ma_engine_config engineConfig; + + engineConfig = ma_engine_config_init(); + engineConfig.noDevice = MA_TRUE; + engineConfig.channels = 2; // Must be set when not using a device. + engineConfig.sampleRate = 48000; // Must be set when not using a device. + + result = ma_engine_init(&engineConfig, &engine); + if (result != MA_SUCCESS) { + return result; // Failed to initialize the engine. + } + ``` + +Note that when you're not using a device, you must set the channel count and sample rate in the +config or else miniaudio won't know what to use (miniaudio will use the device to determine this +normally). When not using a device, you need to use `ma_engine_read_pcm_frames()` to process audio +data from the engine. This kind of setup is useful if you want to do something like offline +processing. + +When a sound is loaded it goes through a resource manager. By default the engine will initialize a +resource manager internally, but you can also specify a pre-initialized resource manager: + + ```c + ma_result result; + ma_engine engine1; + ma_engine engine2; + ma_engine_config engineConfig; + + engineConfig = ma_engine_config_init(); + engineConfig.pResourceManager = &myResourceManager; + + ma_engine_init(&engineConfig, &engine1); + ma_engine_init(&engineConfig, &engine2); + ``` + +In this example we are initializing two engines, both of which are sharing the same resource +manager. This is especially useful for saving memory when loading the same file across multiple +engines. If you were not to use a shared resource manager, each engine instance would use their own +which would result in any sounds that are used between both engine's being loaded twice. By using +a shared resource manager, it would only be loaded once. Using multiple engine's is useful when you +need to output to multiple playback devices, such as in a local multiplayer game where each player +is using their own set of headphones. + +By default an engine will be in a started state. To make it so the engine is not automatically +started you can configure it as such: + + ```c + engineConfig.noAutoStart = MA_TRUE; + + // The engine will need to be started manually. + ma_engine_start(&engine); + + // Later on the engine can be stopped with ma_engine_stop(). + ma_engine_stop(&engine); + ``` + +The concept of starting or stopping an engine is only relevant when using the engine with a +device. Attempting to start or stop an engine that is not associated with a device will result in +`MA_INVALID_OPERATION`. + +The master volume of the engine can be controlled with `ma_engine_set_volume()` which takes a +linear scale, with 0 resulting in silence and anything above 1 resulting in amplification. If you +prefer decibel based volume control, use `ma_volume_db_to_linear()` to convert from dB to linear. + +When a sound is spatialized, it is done so relative to a listener. An engine can be configured to +have multiple listeners which can be configured via the config: + + ```c + engineConfig.listenerCount = 2; + ``` + +The maximum number of listeners is restricted to `MA_ENGINE_MAX_LISTENERS`. By default, when a +sound is spatialized, it will be done so relative to the closest listener. You can also pin a sound +to a specific listener which will be explained later. Listener's have a position, direction, cone, +and velocity (for doppler effect). A listener is referenced by an index, the meaning of which is up +to the caller (the index is 0 based and cannot go beyond the listener count, minus 1). The +position, direction and velocity are all specified in absolute terms: + + ```c + ma_engine_listener_set_position(&engine, listenerIndex, worldPosX, worldPosY, worldPosZ); + ``` + +The direction of the listener represents it's forward vector. The listener's up vector can also be +specified and defaults to +1 on the Y axis. + + ```c + ma_engine_listener_set_direction(&engine, listenerIndex, forwardX, forwardY, forwardZ); + ma_engine_listener_set_world_up(&engine, listenerIndex, 0, 1, 0); + ``` + +The engine supports directional attenuation. The listener can have a cone the controls how sound is +attenuated based on the listener's direction. When a sound is between the inner and outer cones, it +will be attenuated between 1 and the cone's outer gain: + + ```c + ma_engine_listener_set_cone(&engine, listenerIndex, innerAngleInRadians, outerAngleInRadians, outerGain); + ``` + +When a sound is inside the inner code, no directional attenuation is applied. When the sound is +outside of the outer cone, the attenuation will be set to `outerGain` in the example above. When +the sound is in between the inner and outer cones, the attenuation will be interpolated between 1 +and the outer gain. + +The engine's coordinate system follows the OpenGL coordinate system where positive X points right, +positive Y points up and negative Z points forward. + +The simplest and least flexible way to play a sound is like so: + + ```c + ma_engine_play_sound(&engine, "my_sound.wav", pGroup); + ``` + +This is a "fire and forget" style of function. The engine will manage the `ma_sound` object +internally. When the sound finishes playing, it'll be put up for recycling. For more flexibility +you'll want to initialize a sound object: + + ```c + ma_sound sound; + + result = ma_sound_init_from_file(&engine, "my_sound.wav", flags, pGroup, NULL, &sound); + if (result != MA_SUCCESS) { + return result; // Failed to load sound. + } + ``` + +Sounds need to be uninitialized with `ma_sound_uninit()`. + +The example above loads a sound from a file. If the resource manager has been disabled you will not +be able to use this function and instead you'll need to initialize a sound directly from a data +source: + + ```c + ma_sound sound; + + result = ma_sound_init_from_data_source(&engine, &dataSource, flags, pGroup, &sound); + if (result != MA_SUCCESS) { + return result; + } + ``` + +Each `ma_sound` object represents a single instance of the sound. If you want to play the same +sound multiple times at the same time, you need to initialize a separate `ma_sound` object. + +For the most flexibility when initializing sounds, use `ma_sound_init_ex()`. This uses miniaudio's +standard config/init pattern: + + ```c + ma_sound sound; + ma_sound_config soundConfig; + + soundConfig = ma_sound_config_init(); + soundConfig.pFilePath = NULL; // Set this to load from a file path. + soundConfig.pDataSource = NULL; // Set this to initialize from an existing data source. + soundConfig.pInitialAttachment = &someNodeInTheNodeGraph; + soundConfig.initialAttachmentInputBusIndex = 0; + soundConfig.channelsIn = 1; + soundConfig.channelsOut = 0; // Set to 0 to use the engine's native channel count. + + result = ma_sound_init_ex(&soundConfig, &sound); + if (result != MA_SUCCESS) { + return result; + } + ``` + +In the example above, the sound is being initialized without a file nor a data source. This is +valid, in which case the sound acts as a node in the middle of the node graph. This means you can +connect other sounds to this sound and allow it to act like a sound group. Indeed, this is exactly +what a `ma_sound_group` is. + +When loading a sound, you specify a set of flags that control how the sound is loaded and what +features are enabled for that sound. When no flags are set, the sound will be fully loaded into +memory in exactly the same format as how it's stored on the file system. The resource manager will +allocate a block of memory and then load the file directly into it. When reading audio data, it +will be decoded dynamically on the fly. In order to save processing time on the audio thread, it +might be beneficial to pre-decode the sound. You can do this with the `MA_SOUND_FLAG_DECODE` flag: + + ```c + ma_sound_init_from_file(&engine, "my_sound.wav", MA_SOUND_FLAG_DECODE, pGroup, NULL, &sound); + ``` + +By default, sounds will be loaded synchronously, meaning `ma_sound_init_*()` will not return until +the sound has been fully loaded. If this is prohibitive you can instead load sounds asynchronously +by specificying the `MA_SOUND_FLAG_ASYNC` flag: + + ```c + ma_sound_init_from_file(&engine, "my_sound.wav", MA_SOUND_FLAG_DECODE | MA_SOUND_FLAG_ASYNC, pGroup, NULL, &sound); + ``` + +This will result in `ma_sound_init_*()` returning quickly, but the sound won't yet have been fully +loaded. When you start the sound, it won't output anything until some sound is available. The sound +will start outputting audio before the sound has been fully decoded when the `MA_SOUND_FLAG_DECODE` +is specified. + +If you need to wait for an asynchronously loaded sound to be fully loaded, you can use a fence. A +fence in miniaudio is a simple synchronization mechanism which simply blocks until it's internal +counter hit's zero. You can specify a fence like so: + + ```c + ma_result result; + ma_fence fence; + ma_sound sounds[4]; + + result = ma_fence_init(&fence); + if (result != MA_SUCCES) { + return result; + } + + // Load some sounds asynchronously. + for (int iSound = 0; iSound < 4; iSound += 1) { + ma_sound_init_from_file(&engine, mySoundFilesPaths[iSound], MA_SOUND_FLAG_DECODE | MA_SOUND_FLAG_ASYNC, pGroup, &fence, &sounds[iSound]); + } + + // ... do some other stuff here in the mean time ... + + // Wait for all sounds to finish loading. + ma_fence_wait(&fence); + ``` + +If loading the entire sound into memory is prohibitive, you can also configure the engine to stream +the audio data: + + ```c + ma_sound_init_from_file(&engine, "my_sound.wav", MA_SOUND_FLAG_STREAM, pGroup, NULL, &sound); + ``` + +When streaming sounds, 2 seconds worth of audio data is stored in memory. Although it should work +fine, it's inefficient to use streaming for short sounds. Streaming is useful for things like music +tracks in games. + +When you initialize a sound, if you specify a sound group the sound will be attached to that group +automatically. If you set it to NULL, it will be automatically attached to the engine's endpoint. +If you would instead rather leave the sound unattached by default, you can can specify the +`MA_SOUND_FLAG_NO_DEFAULT_ATTACHMENT` flag. This is useful if you want to set up a complex node +graph. + +Sounds are not started by default. To start a sound, use `ma_sound_start()`. Stop a sound with +`ma_sound_stop()`. + +Sounds can have their volume controlled with `ma_sound_set_volume()` in the same way as the +engine's master volume. + +Sounds support stereo panning and pitching. Set the pan with `ma_sound_set_pan()`. Setting the pan +to 0 will result in an unpanned sound. Setting it to -1 will shift everything to the left, whereas ++1 will shift it to the right. The pitch can be controlled with `ma_sound_set_pitch()`. A larger +value will result in a higher pitch. The pitch must be greater than 0. + +The engine supports 3D spatialization of sounds. By default sounds will have spatialization +enabled, but if a sound does not need to be spatialized it's best to disable it. There are two ways +to disable spatialization of a sound: + + ```c + // Disable spatialization at initialization time via a flag: + ma_sound_init_from_file(&engine, "my_sound.wav", MA_SOUND_FLAG_NO_SPATIALIZATION, NULL, NULL, &sound); + + // Dynamically disable or enable spatialization post-initialization: + ma_sound_set_spatialization_enabled(&sound, isSpatializationEnabled); + ``` + +By default sounds will be spatialized based on the closest listener. If a sound should always be +spatialized relative to a specific listener it can be pinned to one: + + ```c + ma_sound_set_pinned_listener_index(&sound, listenerIndex); + ``` + +Like listeners, sounds have a position. By default, the position of a sound is in absolute space, +but it can be changed to be relative to a listener: + + ```c + ma_sound_set_positioning(&sound, ma_positioning_relative); + ``` + +Note that relative positioning of a sound only makes sense if there is either only one listener, or +the sound is pinned to a specific listener. To set the position of a sound: + + ```c + ma_sound_set_position(&sound, posX, posY, posZ); + ``` + +The direction works the same way as a listener and represents the sound's forward direction: + + ```c + ma_sound_set_direction(&sound, forwardX, forwardY, forwardZ); + ``` + +Sound's also have a cone for controlling directional attenuation. This works exactly the same as +listeners: + + ```c + ma_sound_set_cone(&sound, innerAngleInRadians, outerAngleInRadians, outerGain); + ``` + +The velocity of a sound is used for doppler effect and can be set as such: + + ```c + ma_sound_set_velocity(&sound, velocityX, velocityY, velocityZ); + ``` + +The engine supports different attenuation models which can be configured on a per-sound basis. By +default the attenuation model is set to `ma_attenuation_model_inverse` which is the equivalent to +OpenAL's `AL_INVERSE_DISTANCE_CLAMPED`. Configure the attenuation model like so: + + ```c + ma_sound_set_attenuation_model(&sound, ma_attenuation_model_inverse); + ``` + +The supported attenuation models include the following: + + +----------------------------------+----------------------------------------------+ + | ma_attenuation_model_none | No distance attenuation. | + +----------------------------------+----------------------------------------------+ + | ma_attenuation_model_inverse | Equivalent to `AL_INVERSE_DISTANCE_CLAMPED`. | + +----------------------------------+----------------------------------------------+ + | ma_attenuation_model_linear | Linear attenuation. | + +----------------------------------+----------------------------------------------+ + | ma_attenuation_model_exponential | Exponential attenuation. | + +----------------------------------+----------------------------------------------+ + +To control how quickly a sound rolls off as it moves away from the listener, you need to configure +the rolloff: + + ```c + ma_sound_set_rolloff(&sound, rolloff); + ``` + +You can control the minimum and maximum gain to apply from spatialization: + + ```c + ma_sound_set_min_gain(&sound, minGain); + ma_sound_set_max_gain(&sound, maxGain); + ``` + +Likewise, in the calculation of attenuation, you can control the minimum and maximum distances for +the attenuation calculation. This is useful if you want to ensure sounds don't drop below a certain +volume after the listener moves further away and to have sounds play a maximum volume when the +listener is within a certain distance: + + ```c + ma_sound_set_min_distance(&sound, minDistance); + ma_sound_set_max_distance(&sound, maxDistance); + ``` + +The engine's spatialization system supports doppler effect. The doppler factor can be configure on +a per-sound basis like so: + + ```c + ma_sound_set_doppler_factor(&sound, dopplerFactor); + ``` + +You can fade sounds in and out with `ma_sound_set_fade_in_pcm_frames()` and +`ma_sound_set_fade_in_milliseconds()`. Set the volume to -1 to use the current volume as the +starting volume: + + ```c + // Fade in over 1 second. + ma_sound_set_fade_in_milliseconds(&sound, 0, 1, 1000); + + // ... sometime later ... + + // Fade out over 1 second, starting from the current volume. + ma_sound_set_fade_in_milliseconds(&sound, -1, 0, 1000); + ``` + +By default sounds will start immediately, but sometimes for timing and synchronization purposes it +can be useful to schedule a sound to start or stop: + + ```c + // Start the sound in 1 second from now. + ma_sound_set_start_time_in_pcm_frames(&sound, ma_engine_get_time(&engine) + (ma_engine_get_sample_rate(&engine) * 1)); + + // Stop the sound in 2 seconds from now. + ma_sound_set_stop_time_in_pcm_frames(&sound, ma_engine_get_time(&engine) + (ma_engine_get_sample_rate(&engine) * 2)); + ``` + +Note that scheduling a start time still requires an explicit call to `ma_sound_start()` before +anything will play. + +The time is specified in global time which is controlled by the engine. You can get the engine's +current time with `ma_engine_get_time()`. The engine's global time is incremented automatically as +audio data is read, but it can be reset with `ma_engine_set_time()` in case it needs to be +resynchronized for some reason. + +To determine whether or not a sound is currently playing, use `ma_sound_is_playing()`. This will +take the scheduled start and stop times into account. + +Whether or not a sound should loop can be controlled with `ma_sound_set_looping()`. Sounds will not +be looping by default. Use `ma_sound_is_looping()` to determine whether or not a sound is looping. + +Use `ma_sound_at_end()` to determine whether or not a sound is currently at the end. For a looping +sound this should never return true. + +Internally a sound wraps around a data source. Some APIs exist to control the underlying data +source, mainly for convenience: + + ```c + ma_sound_seek_to_pcm_frame(&sound, frameIndex); + ma_sound_get_data_format(&sound, &format, &channels, &sampleRate, pChannelMap, channelMapCapacity); + ma_sound_get_cursor_in_pcm_frames(&sound, &cursor); + ma_sound_get_length_in_pcm_frames(&sound, &length); + ``` + +Sound groups have the same API as sounds, only they are called `ma_sound_group`, and since they do +not have any notion of a data source, anything relating to a data source is unavailable. + +Internally, sound data is loaded via the `ma_decoder` API which means by default in only supports +file formats that have built-in support in miniaudio. You can extend this to support any kind of +file format through the use of custom decoders. To do this you'll need to use a self-managed +resource manager and configure it appropriately. See the "Resource Management" section below for +details on how to set this up. + + +6. Resource Management +====================== +Many programs will want to manage sound resources for things such as reference counting and +streaming. This is supported by miniaudio via the `ma_resource_manager` API. + +The resource manager is mainly responsible for the following: + + * Loading of sound files into memory with reference counting. + * Streaming of sound data + +When loading a sound file, the resource manager will give you back a `ma_data_source` compatible +object called `ma_resource_manager_data_source`. This object can be passed into any +`ma_data_source` API which is how you can read and seek audio data. When loading a sound file, you +specify whether or not you want the sound to be fully loaded into memory (and optionally +pre-decoded) or streamed. When loading into memory, you can also specify whether or not you want +the data to be loaded asynchronously. + +The example below is how you can initialize a resource manager using it's default configuration: + + ```c + ma_resource_manager_config config; + ma_resource_manager resourceManager; + + config = ma_resource_manager_config_init(); + result = ma_resource_manager_init(&config, &resourceManager); + if (result != MA_SUCCESS) { + ma_device_uninit(&device); + printf("Failed to initialize the resource manager."); + return -1; + } + ``` + +You can configure the format, channels and sample rate of the decoded audio data. By default it +will use the file's native data format, but you can configure it to use a consistent format. This +is useful for offloading the cost of data conversion to load time rather than dynamically +converting at mixing time. To do this, you configure the decoded format, channels and sample rate +like the code below: + + ```c + config = ma_resource_manager_config_init(); + config.decodedFormat = device.playback.format; + config.decodedChannels = device.playback.channels; + config.decodedSampleRate = device.sampleRate; + ``` + +In the code above, the resource manager will be configured so that any decoded audio data will be +pre-converted at load time to the device's native data format. If instead you used defaults and +the data format of the file did not match the device's data format, you would need to convert the +data at mixing time which may be prohibitive in high-performance and large scale scenarios like +games. + +Internally the resource manager uses the `ma_decoder` API to load sounds. This means by default it +only supports decoders that are built into miniaudio. It's possible to support additional encoding +formats through the use of custom decoders. To do so, pass in your `ma_decoding_backend_vtable` +vtables into the resource manager config: + + ```c + ma_decoding_backend_vtable* pCustomBackendVTables[] = + { + &g_ma_decoding_backend_vtable_libvorbis, + &g_ma_decoding_backend_vtable_libopus + }; + + ... + + resourceManagerConfig.ppCustomDecodingBackendVTables = pCustomBackendVTables; + resourceManagerConfig.customDecodingBackendCount = sizeof(pCustomBackendVTables) / sizeof(pCustomBackendVTables[0]); + resourceManagerConfig.pCustomDecodingBackendUserData = NULL; + ``` + +This system can allow you to support any kind of file format. See the "Decoding" section for +details on how to implement custom decoders. The miniaudio repository includes examples for Opus +via libopus and libopusfile and Vorbis via libvorbis and libvorbisfile. + +Asynchronicity is achieved via a job system. When an operation needs to be performed, such as the +decoding of a page, a job will be posted to a queue which will then be processed by a job thread. +By default there will be only one job thread running, but this can be configured, like so: + + ```c + config = ma_resource_manager_config_init(); + config.jobThreadCount = MY_JOB_THREAD_COUNT; + ``` + +By default job threads are managed internally by the resource manager, however you can also self +manage your job threads if, for example, you want to integrate the job processing into your +existing job infrastructure, or if you simply don't like the way the resource manager does it. To +do this, just set the job thread count to 0 and process jobs manually. To process jobs, you first +need to retrieve a job using `ma_resource_manager_next_job()` and then process it using +`ma_job_process()`: + + ```c + config = ma_resource_manager_config_init(); + config.jobThreadCount = 0; // Don't manage any job threads internally. + config.flags = MA_RESOURCE_MANAGER_FLAG_NON_BLOCKING; // Optional. Makes `ma_resource_manager_next_job()` non-blocking. + + // ... Initialize your custom job threads ... + + void my_custom_job_thread(...) + { + for (;;) { + ma_job job; + ma_result result = ma_resource_manager_next_job(pMyResourceManager, &job); + if (result != MA_SUCCESS) { + if (result == MA_NOT_DATA_AVAILABLE) { + // No jobs are available. Keep going. Will only get this if the resource manager was initialized + // with MA_RESOURCE_MANAGER_FLAG_NON_BLOCKING. + continue; + } else if (result == MA_CANCELLED) { + // MA_JOB_TYPE_QUIT was posted. Exit. + break; + } else { + // Some other error occurred. + break; + } + } + + ma_job_process(&job); + } + } + ``` + +In the example above, the `MA_JOB_TYPE_QUIT` event is the used as the termination +indicator, but you can use whatever you would like to terminate the thread. The call to +`ma_resource_manager_next_job()` is blocking by default, but can be configured to be non-blocking +by initializing the resource manager with the `MA_RESOURCE_MANAGER_FLAG_NON_BLOCKING` configuration +flag. Note that the `MA_JOB_TYPE_QUIT` will never be removed from the job queue. This +is to give every thread the opportunity to catch the event and terminate naturally. + +When loading a file, it's sometimes convenient to be able to customize how files are opened and +read instead of using standard `fopen()`, `fclose()`, etc. which is what miniaudio will use by +default. This can be done by setting `pVFS` member of the resource manager's config: + + ```c + // Initialize your custom VFS object. See documentation for VFS for information on how to do this. + my_custom_vfs vfs = my_custom_vfs_init(); + + config = ma_resource_manager_config_init(); + config.pVFS = &vfs; + ``` + +This is particularly useful in programs like games where you want to read straight from an archive +rather than the normal file system. If you do not specify a custom VFS, the resource manager will +use the operating system's normal file operations. This is default. + +To load a sound file and create a data source, call `ma_resource_manager_data_source_init()`. When +loading a sound you need to specify the file path and options for how the sounds should be loaded. +By default a sound will be loaded synchronously. The returned data source is owned by the caller +which means the caller is responsible for the allocation and freeing of the data source. Below is +an example for initializing a data source: + + ```c + ma_resource_manager_data_source dataSource; + ma_result result = ma_resource_manager_data_source_init(pResourceManager, pFilePath, flags, &dataSource); + if (result != MA_SUCCESS) { + // Error. + } + + // ... + + // A ma_resource_manager_data_source object is compatible with the `ma_data_source` API. To read data, just call + // the `ma_data_source_read_pcm_frames()` like you would with any normal data source. + result = ma_data_source_read_pcm_frames(&dataSource, pDecodedData, frameCount, &framesRead); + if (result != MA_SUCCESS) { + // Failed to read PCM frames. + } + + // ... + + ma_resource_manager_data_source_uninit(pResourceManager, &dataSource); + ``` + +The `flags` parameter specifies how you want to perform loading of the sound file. It can be a +combination of the following flags: + + ``` + MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM + MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE + MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC + MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT + ``` + +When no flags are specified (set to 0), the sound will be fully loaded into memory, but not +decoded, meaning the raw file data will be stored in memory, and then dynamically decoded when +`ma_data_source_read_pcm_frames()` is called. To instead decode the audio data before storing it in +memory, use the `MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE` flag. By default, the sound file will +be loaded synchronously, meaning `ma_resource_manager_data_source_init()` will only return after +the entire file has been loaded. This is good for simplicity, but can be prohibitively slow. You +can instead load the sound asynchronously using the `MA_RESOURCE_MANAGER_DATA_SOURCE_ASYNC` flag. +This will result in `ma_resource_manager_data_source_init()` returning quickly, but no data will be +returned by `ma_data_source_read_pcm_frames()` until some data is available. When no data is +available because the asynchronous decoding hasn't caught up, `MA_BUSY` will be returned by +`ma_data_source_read_pcm_frames()`. + +For large sounds, it's often prohibitive to store the entire file in memory. To mitigate this, you +can instead stream audio data which you can do by specifying the +`MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM` flag. When streaming, data will be decoded in 1 +second pages. When a new page needs to be decoded, a job will be posted to the job queue and then +subsequently processed in a job thread. + +For in-memory sounds, reference counting is used to ensure the data is loaded only once. This means +multiple calls to `ma_resource_manager_data_source_init()` with the same file path will result in +the file data only being loaded once. Each call to `ma_resource_manager_data_source_init()` must be +matched up with a call to `ma_resource_manager_data_source_uninit()`. Sometimes it can be useful +for a program to register self-managed raw audio data and associate it with a file path. Use the +`ma_resource_manager_register_*()` and `ma_resource_manager_unregister_*()` APIs to do this. +`ma_resource_manager_register_decoded_data()` is used to associate a pointer to raw, self-managed +decoded audio data in the specified data format with the specified name. Likewise, +`ma_resource_manager_register_encoded_data()` is used to associate a pointer to raw self-managed +encoded audio data (the raw file data) with the specified name. Note that these names need not be +actual file paths. When `ma_resource_manager_data_source_init()` is called (without the +`MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM` flag), the resource manager will look for these +explicitly registered data buffers and, if found, will use it as the backing data for the data +source. Note that the resource manager does *not* make a copy of this data so it is up to the +caller to ensure the pointer stays valid for it's lifetime. Use +`ma_resource_manager_unregister_data()` to unregister the self-managed data. You can also use +`ma_resource_manager_register_file()` and `ma_resource_manager_unregister_file()` to register and +unregister a file. It does not make sense to use the `MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM` +flag with a self-managed data pointer. + + +6.1. Asynchronous Loading and Synchronization +--------------------------------------------- +When loading asynchronously, it can be useful to poll whether or not loading has finished. Use +`ma_resource_manager_data_source_result()` to determine this. For in-memory sounds, this will +return `MA_SUCCESS` when the file has been *entirely* decoded. If the sound is still being decoded, +`MA_BUSY` will be returned. Otherwise, some other error code will be returned if the sound failed +to load. For streaming data sources, `MA_SUCCESS` will be returned when the first page has been +decoded and the sound is ready to be played. If the first page is still being decoded, `MA_BUSY` +will be returned. Otherwise, some other error code will be returned if the sound failed to load. + +In addition to polling, you can also use a simple synchronization object called a "fence" to wait +for asynchronously loaded sounds to finish. This is called `ma_fence`. The advantage to using a +fence is that it can be used to wait for a group of sounds to finish loading rather than waiting +for sounds on an individual basis. There are two stages to loading a sound: + + * Initialization of the internal decoder; and + * Completion of decoding of the file (the file is fully decoded) + +You can specify separate fences for each of the different stages. Waiting for the initialization +of the internal decoder is important for when you need to know the sample format, channels and +sample rate of the file. + +The example below shows how you could use a fence when loading a number of sounds: + + ```c + // This fence will be released when all sounds are finished loading entirely. + ma_fence fence; + ma_fence_init(&fence); + + // This will be passed into the initialization routine for each sound. + ma_resource_manager_pipeline_notifications notifications = ma_resource_manager_pipeline_notifications_init(); + notifications.done.pFence = &fence; + + // Now load a bunch of sounds: + for (iSound = 0; iSound < soundCount; iSound += 1) { + ma_resource_manager_data_source_init(pResourceManager, pSoundFilePaths[iSound], flags, ¬ifications, &pSoundSources[iSound]); + } + + // ... DO SOMETHING ELSE WHILE SOUNDS ARE LOADING ... + + // Wait for loading of sounds to finish. + ma_fence_wait(&fence); + ``` + +In the example above we used a fence for waiting until the entire file has been fully decoded. If +you only need to wait for the initialization of the internal decoder to complete, you can use the +`init` member of the `ma_resource_manager_pipeline_notifications` object: + + ```c + notifications.init.pFence = &fence; + ``` + +If a fence is not appropriate for your situation, you can instead use a callback that is fired on +an individual sound basis. This is done in a very similar way to fences: + + ```c + typedef struct + { + ma_async_notification_callbacks cb; + void* pMyData; + } my_notification; + + void my_notification_callback(ma_async_notification* pNotification) + { + my_notification* pMyNotification = (my_notification*)pNotification; + + // Do something in response to the sound finishing loading. + } + + ... + + my_notification myCallback; + myCallback.cb.onSignal = my_notification_callback; + myCallback.pMyData = pMyData; + + ma_resource_manager_pipeline_notifications notifications = ma_resource_manager_pipeline_notifications_init(); + notifications.done.pNotification = &myCallback; + + ma_resource_manager_data_source_init(pResourceManager, "my_sound.wav", flags, ¬ifications, &mySound); + ``` + +In the example above we just extend the `ma_async_notification_callbacks` object and pass an +instantiation into the `ma_resource_manager_pipeline_notifications` in the same way as we did with +the fence, only we set `pNotification` instead of `pFence`. You can set both of these at the same +time and they should both work as expected. If using the `pNotification` system, you need to ensure +your `ma_async_notification_callbacks` object stays valid. + + + +6.2. Resource Manager Implementation Details +-------------------------------------------- +Resources are managed in two main ways: + + * By storing the entire sound inside an in-memory buffer (referred to as a data buffer) + * By streaming audio data on the fly (referred to as a data stream) + +A resource managed data source (`ma_resource_manager_data_source`) encapsulates a data buffer or +data stream, depending on whether or not the data source was initialized with the +`MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM` flag. If so, it will make use of a +`ma_resource_manager_data_stream` object. Otherwise it will use a `ma_resource_manager_data_buffer` +object. Both of these objects are data sources which means they can be used with any +`ma_data_source_*()` API. + +Another major feature of the resource manager is the ability to asynchronously decode audio files. +This relieves the audio thread of time-consuming decoding which can negatively affect scalability +due to the audio thread needing to complete it's work extremely quickly to avoid glitching. +Asynchronous decoding is achieved through a job system. There is a central multi-producer, +multi-consumer, fixed-capacity job queue. When some asynchronous work needs to be done, a job is +posted to the queue which is then read by a job thread. The number of job threads can be +configured for improved scalability, and job threads can all run in parallel without needing to +worry about the order of execution (how this is achieved is explained below). + +When a sound is being loaded asynchronously, playback can begin before the sound has been fully +decoded. This enables the application to start playback of the sound quickly, while at the same +time allowing to resource manager to keep loading in the background. Since there may be less +threads than the number of sounds being loaded at a given time, a simple scheduling system is used +to keep decoding time balanced and fair. The resource manager solves this by splitting decoding +into chunks called pages. By default, each page is 1 second long. When a page has been decoded, a +new job will be posted to start decoding the next page. By dividing up decoding into pages, an +individual sound shouldn't ever delay every other sound from having their first page decoded. Of +course, when loading many sounds at the same time, there will always be an amount of time required +to process jobs in the queue so in heavy load situations there will still be some delay. To +determine if a data source is ready to have some frames read, use +`ma_resource_manager_data_source_get_available_frames()`. This will return the number of frames +available starting from the current position. + + +6.2.1. Job Queue +---------------- +The resource manager uses a job queue which is multi-producer, multi-consumer, and fixed-capacity. +This job queue is not currently lock-free, and instead uses a spinlock to achieve thread-safety. +Only a fixed number of jobs can be allocated and inserted into the queue which is done through a +lock-free data structure for allocating an index into a fixed sized array, with reference counting +for mitigation of the ABA problem. The reference count is 32-bit. + +For many types of jobs it's important that they execute in a specific order. In these cases, jobs +are executed serially. For the resource manager, serial execution of jobs is only required on a +per-object basis (per data buffer or per data stream). Each of these objects stores an execution +counter. When a job is posted it is associated with an execution counter. When the job is +processed, it checks if the execution counter of the job equals the execution counter of the +owning object and if so, processes the job. If the counters are not equal, the job will be posted +back onto the job queue for later processing. When the job finishes processing the execution order +of the main object is incremented. This system means the no matter how many job threads are +executing, decoding of an individual sound will always get processed serially. The advantage to +having multiple threads comes into play when loading multiple sounds at the same time. + +The resource manager's job queue is not 100% lock-free and will use a spinlock to achieve +thread-safety for a very small section of code. This is only relevant when the resource manager +uses more than one job thread. If only using a single job thread, which is the default, the +lock should never actually wait in practice. The amount of time spent locking should be quite +short, but it's something to be aware of for those who have pedantic lock-free requirements and +need to use more than one job thread. There are plans to remove this lock in a future version. + +In addition, posting a job will release a semaphore, which on Win32 is implemented with +`ReleaseSemaphore` and on POSIX platforms via a condition variable: + + ```c + pthread_mutex_lock(&pSemaphore->lock); + { + pSemaphore->value += 1; + pthread_cond_signal(&pSemaphore->cond); + } + pthread_mutex_unlock(&pSemaphore->lock); + ``` + +Again, this is relevant for those with strict lock-free requirements in the audio thread. To avoid +this, you can use non-blocking mode (via the `MA_JOB_QUEUE_FLAG_NON_BLOCKING` +flag) and implement your own job processing routine (see the "Resource Manager" section above for +details on how to do this). + + + +6.2.2. Data Buffers +------------------- +When the `MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM` flag is excluded at initialization time, the +resource manager will try to load the data into an in-memory data buffer. Before doing so, however, +it will first check if the specified file is already loaded. If so, it will increment a reference +counter and just use the already loaded data. This saves both time and memory. When the data buffer +is uninitialized, the reference counter will be decremented. If the counter hits zero, the file +will be unloaded. This is a detail to keep in mind because it could result in excessive loading and +unloading of a sound. For example, the following sequence will result in a file be loaded twice, +once after the other: + + ```c + ma_resource_manager_data_source_init(pResourceManager, "my_file", ..., &myDataBuffer0); // Refcount = 1. Initial load. + ma_resource_manager_data_source_uninit(pResourceManager, &myDataBuffer0); // Refcount = 0. Unloaded. + + ma_resource_manager_data_source_init(pResourceManager, "my_file", ..., &myDataBuffer1); // Refcount = 1. Reloaded because previous uninit() unloaded it. + ma_resource_manager_data_source_uninit(pResourceManager, &myDataBuffer1); // Refcount = 0. Unloaded. + ``` + +A binary search tree (BST) is used for storing data buffers as it has good balance between +efficiency and simplicity. The key of the BST is a 64-bit hash of the file path that was passed +into `ma_resource_manager_data_source_init()`. The advantage of using a hash is that it saves +memory over storing the entire path, has faster comparisons, and results in a mostly balanced BST +due to the random nature of the hash. The disadvantage is that file names are case-sensitive. If +this is an issue, you should normalize your file names to upper- or lower-case before initializing +your data sources. + +When a sound file has not already been loaded and the `MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC` +flag is excluded, the file will be decoded synchronously by the calling thread. There are two +options for controlling how the audio is stored in the data buffer - encoded or decoded. When the +`MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE` option is excluded, the raw file data will be stored +in memory. Otherwise the sound will be decoded before storing it in memory. Synchronous loading is +a very simple and standard process of simply adding an item to the BST, allocating a block of +memory and then decoding (if `MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE` is specified). + +When the `MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC` flag is specified, loading of the data buffer +is done asynchronously. In this case, a job is posted to the queue to start loading and then the +function immediately returns, setting an internal result code to `MA_BUSY`. This result code is +returned when the program calls `ma_resource_manager_data_source_result()`. When decoding has fully +completed `MA_SUCCESS` will be returned. This can be used to know if loading has fully completed. + +When loading asynchronously, a single job is posted to the queue of the type +`MA_JOB_TYPE_RESOURCE_MANAGER_LOAD_DATA_BUFFER_NODE`. This involves making a copy of the file path and +associating it with job. When the job is processed by the job thread, it will first load the file +using the VFS associated with the resource manager. When using a custom VFS, it's important that it +be completely thread-safe because it will be used from one or more job threads at the same time. +Individual files should only ever be accessed by one thread at a time, however. After opening the +file via the VFS, the job will determine whether or not the file is being decoded. If not, it +simply allocates a block of memory and loads the raw file contents into it and returns. On the +other hand, when the file is being decoded, it will first allocate a decoder on the heap and +initialize it. Then it will check if the length of the file is known. If so it will allocate a +block of memory to store the decoded output and initialize it to silence. If the size is unknown, +it will allocate room for one page. After memory has been allocated, the first page will be +decoded. If the sound is shorter than a page, the result code will be set to `MA_SUCCESS` and the +completion event will be signalled and loading is now complete. If, however, there is more to +decode, a job with the code `MA_JOB_TYPE_RESOURCE_MANAGER_PAGE_DATA_BUFFER_NODE` is posted. This job +will decode the next page and perform the same process if it reaches the end. If there is more to +decode, the job will post another `MA_JOB_TYPE_RESOURCE_MANAGER_PAGE_DATA_BUFFER_NODE` job which will +keep on happening until the sound has been fully decoded. For sounds of an unknown length, each +page will be linked together as a linked list. Internally this is implemented via the +`ma_paged_audio_buffer` object. + + +6.2.3. Data Streams +------------------- +Data streams only ever store two pages worth of data for each instance. They are most useful for +large sounds like music tracks in games that would consume too much memory if fully decoded in +memory. After every frame from a page has been read, a job will be posted to load the next page +which is done from the VFS. + +For data streams, the `MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC` flag will determine whether or +not initialization of the data source waits until the two pages have been decoded. When unset, +`ma_resource_manager_data_source_init()` will wait until the two pages have been loaded, otherwise +it will return immediately. + +When frames are read from a data stream using `ma_resource_manager_data_source_read_pcm_frames()`, +`MA_BUSY` will be returned if there are no frames available. If there are some frames available, +but less than the number requested, `MA_SUCCESS` will be returned, but the actual number of frames +read will be less than the number requested. Due to the asynchronous nature of data streams, +seeking is also asynchronous. If the data stream is in the middle of a seek, `MA_BUSY` will be +returned when trying to read frames. + +When `ma_resource_manager_data_source_read_pcm_frames()` results in a page getting fully consumed +a job is posted to load the next page. This will be posted from the same thread that called +`ma_resource_manager_data_source_read_pcm_frames()`. + +Data streams are uninitialized by posting a job to the queue, but the function won't return until +that job has been processed. The reason for this is that the caller owns the data stream object and +therefore miniaudio needs to ensure everything completes before handing back control to the caller. +Also, if the data stream is uninitialized while pages are in the middle of decoding, they must +complete before destroying any underlying object and the job system handles this cleanly. + +Note that when a new page needs to be loaded, a job will be posted to the resource manager's job +thread from the audio thread. You must keep in mind the details mentioned in the "Job Queue" +section above regarding locking when posting an event if you require a strictly lock-free audio +thread. + + + +7. Node Graph +============= +miniaudio's routing infrastructure follows a node graph paradigm. The idea is that you create a +node whose outputs are attached to inputs of another node, thereby creating a graph. There are +different types of nodes, with each node in the graph processing input data to produce output, +which is then fed through the chain. Each node in the graph can apply their own custom effects. At +the start of the graph will usually be one or more data source nodes which have no inputs, but +instead pull their data from a data source. At the end of the graph is an endpoint which represents +the end of the chain and is where the final output is ultimately extracted from. + +Each node has a number of input buses and a number of output buses. An output bus from a node is +attached to an input bus of another. Multiple nodes can connect their output buses to another +node's input bus, in which case their outputs will be mixed before processing by the node. Below is +a diagram that illustrates a hypothetical node graph setup: + + ``` + >>>>>>>>>>>>>>>>>>>>>>>>>>>>>> Data flows left to right >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + +---------------+ +-----------------+ + | Data Source 1 =----+ +----------+ +----= Low Pass Filter =----+ + +---------------+ | | =----+ +-----------------+ | +----------+ + +----= Splitter | +----= ENDPOINT | + +---------------+ | | =----+ +-----------------+ | +----------+ + | Data Source 2 =----+ +----------+ +----= Echo / Delay =----+ + +---------------+ +-----------------+ + ``` + +In the above graph, it starts with two data sources whose outputs are attached to the input of a +splitter node. It's at this point that the two data sources are mixed. After mixing, the splitter +performs it's processing routine and produces two outputs which is simply a duplication of the +input stream. One output is attached to a low pass filter, whereas the other output is attached to +a echo/delay. The outputs of the the low pass filter and the echo are attached to the endpoint, and +since they're both connected to the same input but, they'll be mixed. + +Each input bus must be configured to accept the same number of channels, but the number of channels +used by input buses can be different to the number of channels for output buses in which case +miniaudio will automatically convert the input data to the output channel count before processing. +The number of channels of an output bus of one node must match the channel count of the input bus +it's attached to. The channel counts cannot be changed after the node has been initialized. If you +attempt to attach an output bus to an input bus with a different channel count, attachment will +fail. + +To use a node graph, you first need to initialize a `ma_node_graph` object. This is essentially a +container around the entire graph. The `ma_node_graph` object is required for some thread-safety +issues which will be explained later. A `ma_node_graph` object is initialized using miniaudio's +standard config/init system: + + ```c + ma_node_graph_config nodeGraphConfig = ma_node_graph_config_init(myChannelCount); + + result = ma_node_graph_init(&nodeGraphConfig, NULL, &nodeGraph); // Second parameter is a pointer to allocation callbacks. + if (result != MA_SUCCESS) { + // Failed to initialize node graph. + } + ``` + +When you initialize the node graph, you're specifying the channel count of the endpoint. The +endpoint is a special node which has one input bus and one output bus, both of which have the +same channel count, which is specified in the config. Any nodes that connect directly to the +endpoint must be configured such that their output buses have the same channel count. When you read +audio data from the node graph, it'll have the channel count you specified in the config. To read +data from the graph: + + ```c + ma_uint32 framesRead; + result = ma_node_graph_read_pcm_frames(&nodeGraph, pFramesOut, frameCount, &framesRead); + if (result != MA_SUCCESS) { + // Failed to read data from the node graph. + } + ``` + +When you read audio data, miniaudio starts at the node graph's endpoint node which then pulls in +data from it's input attachments, which in turn recusively pull in data from their inputs, and so +on. At the start of the graph there will be some kind of data source node which will have zero +inputs and will instead read directly from a data source. The base nodes don't literally need to +read from a `ma_data_source` object, but they will always have some kind of underlying object that +sources some kind of audio. The `ma_data_source_node` node can be used to read from a +`ma_data_source`. Data is always in floating-point format and in the number of channels you +specified when the graph was initialized. The sample rate is defined by the underlying data sources. +It's up to you to ensure they use a consistent and appropraite sample rate. + +The `ma_node` API is designed to allow custom nodes to be implemented with relative ease, but +miniaudio includes a few stock nodes for common functionality. This is how you would initialize a +node which reads directly from a data source (`ma_data_source_node`) which is an example of one +of the stock nodes that comes with miniaudio: + + ```c + ma_data_source_node_config config = ma_data_source_node_config_init(pMyDataSource); + + ma_data_source_node dataSourceNode; + result = ma_data_source_node_init(&nodeGraph, &config, NULL, &dataSourceNode); + if (result != MA_SUCCESS) { + // Failed to create data source node. + } + ``` + +The data source node will use the output channel count to determine the channel count of the output +bus. There will be 1 output bus and 0 input buses (data will be drawn directly from the data +source). The data source must output to floating-point (`ma_format_f32`) or else an error will be +returned from `ma_data_source_node_init()`. + +By default the node will not be attached to the graph. To do so, use `ma_node_attach_output_bus()`: + + ```c + result = ma_node_attach_output_bus(&dataSourceNode, 0, ma_node_graph_get_endpoint(&nodeGraph), 0); + if (result != MA_SUCCESS) { + // Failed to attach node. + } + ``` + +The code above connects the data source node directly to the endpoint. Since the data source node +has only a single output bus, the index will always be 0. Likewise, the endpoint only has a single +input bus which means the input bus index will also always be 0. + +To detach a specific output bus, use `ma_node_detach_output_bus()`. To detach all output buses, use +`ma_node_detach_all_output_buses()`. If you want to just move the output bus from one attachment to +another, you do not need to detach first. You can just call `ma_node_attach_output_bus()` and it'll +deal with it for you. + +Less frequently you may want to create a specialized node. This will be a node where you implement +your own processing callback to apply a custom effect of some kind. This is similar to initalizing +one of the stock node types, only this time you need to specify a pointer to a vtable containing a +pointer to the processing function and the number of input and output buses. Example: + + ```c + static void my_custom_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) + { + // Do some processing of ppFramesIn (one stream of audio data per input bus) + const float* pFramesIn_0 = ppFramesIn[0]; // Input bus @ index 0. + const float* pFramesIn_1 = ppFramesIn[1]; // Input bus @ index 1. + float* pFramesOut_0 = ppFramesOut[0]; // Output bus @ index 0. + + // Do some processing. On input, `pFrameCountIn` will be the number of input frames in each + // buffer in `ppFramesIn` and `pFrameCountOut` will be the capacity of each of the buffers + // in `ppFramesOut`. On output, `pFrameCountIn` should be set to the number of input frames + // your node consumed and `pFrameCountOut` should be set the number of output frames that + // were produced. + // + // You should process as many frames as you can. If your effect consumes input frames at the + // same rate as output frames (always the case, unless you're doing resampling), you need + // only look at `ppFramesOut` and process that exact number of frames. If you're doing + // resampling, you'll need to be sure to set both `pFrameCountIn` and `pFrameCountOut` + // properly. + } + + static ma_node_vtable my_custom_node_vtable = + { + my_custom_node_process_pcm_frames, // The function that will be called process your custom node. This is where you'd implement your effect processing. + NULL, // Optional. A callback for calculating the number of input frames that are required to process a specified number of output frames. + 2, // 2 input buses. + 1, // 1 output bus. + 0 // Default flags. + }; + + ... + + // Each bus needs to have a channel count specified. To do this you need to specify the channel + // counts in an array and then pass that into the node config. + ma_uint32 inputChannels[2]; // Equal in size to the number of input channels specified in the vtable. + ma_uint32 outputChannels[1]; // Equal in size to the number of output channels specicied in the vtable. + + inputChannels[0] = channelsIn; + inputChannels[1] = channelsIn; + outputChannels[0] = channelsOut; + + ma_node_config nodeConfig = ma_node_config_init(); + nodeConfig.vtable = &my_custom_node_vtable; + nodeConfig.pInputChannels = inputChannels; + nodeConfig.pOutputChannels = outputChannels; + + ma_node_base node; + result = ma_node_init(&nodeGraph, &nodeConfig, NULL, &node); + if (result != MA_SUCCESS) { + // Failed to initialize node. + } + ``` + +When initializing a custom node, as in the code above, you'll normally just place your vtable in +static space. The number of input and output buses are specified as part of the vtable. If you need +a variable number of buses on a per-node bases, the vtable should have the relevant bus count set +to `MA_NODE_BUS_COUNT_UNKNOWN`. In this case, the bus count should be set in the node config: + + ```c + static ma_node_vtable my_custom_node_vtable = + { + my_custom_node_process_pcm_frames, // The function that will be called process your custom node. This is where you'd implement your effect processing. + NULL, // Optional. A callback for calculating the number of input frames that are required to process a specified number of output frames. + MA_NODE_BUS_COUNT_UNKNOWN, // The number of input buses is determined on a per-node basis. + 1, // 1 output bus. + 0 // Default flags. + }; + + ... + + ma_node_config nodeConfig = ma_node_config_init(); + nodeConfig.vtable = &my_custom_node_vtable; + nodeConfig.inputBusCount = myBusCount; // <-- Since the vtable specifies MA_NODE_BUS_COUNT_UNKNOWN, the input bus count should be set here. + nodeConfig.pInputChannels = inputChannels; // <-- Make sure there are nodeConfig.inputBusCount elements in this array. + nodeConfig.pOutputChannels = outputChannels; // <-- The vtable specifies 1 output bus, so there must be 1 element in this array. + ``` + +In the above example it's important to never set the `inputBusCount` and `outputBusCount` members +to anything other than their defaults if the vtable specifies an explicit count. They can only be +set if the vtable specifies MA_NODE_BUS_COUNT_UNKNOWN in the relevant bus count. + +Most often you'll want to create a structure to encapsulate your node with some extra data. You +need to make sure the `ma_node_base` object is your first member of the structure: + + ```c + typedef struct + { + ma_node_base base; // <-- Make sure this is always the first member. + float someCustomData; + } my_custom_node; + ``` + +By doing this, your object will be compatible with all `ma_node` APIs and you can attach it to the +graph just like any other node. + +In the custom processing callback (`my_custom_node_process_pcm_frames()` in the example above), the +number of channels for each bus is what was specified by the config when the node was initialized +with `ma_node_init()`. In addition, all attachments to each of the input buses will have been +pre-mixed by miniaudio. The config allows you to specify different channel counts for each +individual input and output bus. It's up to the effect to handle it appropriate, and if it can't, +return an error in it's initialization routine. + +Custom nodes can be assigned some flags to describe their behaviour. These are set via the vtable +and include the following: + + +-----------------------------------------+---------------------------------------------------+ + | Flag Name | Description | + +-----------------------------------------+---------------------------------------------------+ + | MA_NODE_FLAG_PASSTHROUGH | Useful for nodes that do not do any kind of audio | + | | processing, but are instead used for tracking | + | | time, handling events, etc. Also used by the | + | | internal endpoint node. It reads directly from | + | | the input bus to the output bus. Nodes with this | + | | flag must have exactly 1 input bus and 1 output | + | | bus, and both buses must have the same channel | + | | counts. | + +-----------------------------------------+---------------------------------------------------+ + | MA_NODE_FLAG_CONTINUOUS_PROCESSING | Causes the processing callback to be called even | + | | when no data is available to be read from input | + | | attachments. This is useful for effects like | + | | echos where there will be a tail of audio data | + | | that still needs to be processed even when the | + | | original data sources have reached their ends. | + +-----------------------------------------+---------------------------------------------------+ + | MA_NODE_FLAG_ALLOW_NULL_INPUT | Used in conjunction with | + | | `MA_NODE_FLAG_CONTINUOUS_PROCESSING`. When this | + | | is set, the `ppFramesIn` parameter of the | + | | processing callback will be set to NULL when | + | | there are no input frames are available. When | + | | this is unset, silence will be posted to the | + | | processing callback. | + +-----------------------------------------+---------------------------------------------------+ + | MA_NODE_FLAG_DIFFERENT_PROCESSING_RATES | Used to tell miniaudio that input and output | + | | frames are processed at different rates. You | + | | should set this for any nodes that perform | + | | resampling. | + +-----------------------------------------+---------------------------------------------------+ + | MA_NODE_FLAG_SILENT_OUTPUT | Used to tell miniaudio that a node produces only | + | | silent output. This is useful for nodes where you | + | | don't want the output to contribute to the final | + | | mix. An example might be if you want split your | + | | stream and have one branch be output to a file. | + | | When using this flag, you should avoid writing to | + | | the output buffer of the node's processing | + | | callback because miniaudio will ignore it anyway. | + +-----------------------------------------+---------------------------------------------------+ + + +If you need to make a copy of an audio stream for effect processing you can use a splitter node +called `ma_splitter_node`. This takes has 1 input bus and splits the stream into 2 output buses. +You can use it like this: + + ```c + ma_splitter_node_config splitterNodeConfig = ma_splitter_node_config_init(channelsIn, channelsOut); + + ma_splitter_node splitterNode; + result = ma_splitter_node_init(&nodeGraph, &splitterNodeConfig, NULL, &splitterNode); + if (result != MA_SUCCESS) { + // Failed to create node. + } + + // Attach your output buses to two different input buses (can be on two different nodes). + ma_node_attach_output_bus(&splitterNode, 0, ma_node_graph_get_endpoint(&nodeGraph), 0); // Attach directly to the endpoint. + ma_node_attach_output_bus(&splitterNode, 1, &myEffectNode, 0); // Attach to input bus 0 of some effect node. + ``` + +The volume of an output bus can be configured on a per-bus basis: + + ```c + ma_node_set_output_bus_volume(&splitterNode, 0, 0.5f); + ma_node_set_output_bus_volume(&splitterNode, 1, 0.5f); + ``` + +In the code above we're using the splitter node from before and changing the volume of each of the +copied streams. + +You can start and stop a node with the following: + + ```c + ma_node_set_state(&splitterNode, ma_node_state_started); // The default state. + ma_node_set_state(&splitterNode, ma_node_state_stopped); + ``` + +By default the node is in a started state, but since it won't be connected to anything won't +actually be invoked by the node graph until it's connected. When you stop a node, data will not be +read from any of it's input connections. You can use this property to stop a group of sounds +atomically. + +You can configure the initial state of a node in it's config: + + ```c + nodeConfig.initialState = ma_node_state_stopped; + ``` + +Note that for the stock specialized nodes, all of their configs will have a `nodeConfig` member +which is the config to use with the base node. This is where the initial state can be configured +for specialized nodes: + + ```c + dataSourceNodeConfig.nodeConfig.initialState = ma_node_state_stopped; + ``` + +When using a specialized node like `ma_data_source_node` or `ma_splitter_node`, be sure to not +modify the `vtable` member of the `nodeConfig` object. + + +7.1. Timing +----------- +The node graph supports starting and stopping nodes at scheduled times. This is especially useful +for data source nodes where you want to get the node set up, but only start playback at a specific +time. There are two clocks: local and global. + +A local clock is per-node, whereas the global clock is per graph. Scheduling starts and stops can +only be done based on the global clock because the local clock will not be running while the node +is stopped. The global clocks advances whenever `ma_node_graph_read_pcm_frames()` is called. On the +other hand, the local clock only advances when the node's processing callback is fired, and is +advanced based on the output frame count. + +To retrieve the global time, use `ma_node_graph_get_time()`. The global time can be set with +`ma_node_graph_set_time()` which might be useful if you want to do seeking on a global timeline. +Getting and setting the local time is similar. Use `ma_node_get_time()` to retrieve the local time, +and `ma_node_set_time()` to set the local time. The global and local times will be advanced by the +audio thread, so care should be taken to avoid data races. Ideally you should avoid calling these +outside of the node processing callbacks which are always run on the audio thread. + +There is basic support for scheduling the starting and stopping of nodes. You can only schedule one +start and one stop at a time. This is mainly intended for putting nodes into a started or stopped +state in a frame-exact manner. Without this mechanism, starting and stopping of a node is limited +to the resolution of a call to `ma_node_graph_read_pcm_frames()` which would typically be in blocks +of several milliseconds. The following APIs can be used for scheduling node states: + + ```c + ma_node_set_state_time() + ma_node_get_state_time() + ``` + +The time is absolute and must be based on the global clock. An example is below: + + ```c + ma_node_set_state_time(&myNode, ma_node_state_started, sampleRate*1); // Delay starting to 1 second. + ma_node_set_state_time(&myNode, ma_node_state_stopped, sampleRate*5); // Delay stopping to 5 seconds. + ``` + +An example for changing the state using a relative time. + + ```c + ma_node_set_state_time(&myNode, ma_node_state_started, sampleRate*1 + ma_node_graph_get_time(&myNodeGraph)); + ma_node_set_state_time(&myNode, ma_node_state_stopped, sampleRate*5 + ma_node_graph_get_time(&myNodeGraph)); + ``` + +Note that due to the nature of multi-threading the times may not be 100% exact. If this is an +issue, consider scheduling state changes from within a processing callback. An idea might be to +have some kind of passthrough trigger node that is used specifically for tracking time and handling +events. + + + +7.2. Thread Safety and Locking +------------------------------ +When processing audio, it's ideal not to have any kind of locking in the audio thread. Since it's +expected that `ma_node_graph_read_pcm_frames()` would be run on the audio thread, it does so +without the use of any locks. This section discusses the implementation used by miniaudio and goes +over some of the compromises employed by miniaudio to achieve this goal. Note that the current +implementation may not be ideal - feedback and critiques are most welcome. + +The node graph API is not *entirely* lock-free. Only `ma_node_graph_read_pcm_frames()` is expected +to be lock-free. Attachment, detachment and uninitialization of nodes use locks to simplify the +implementation, but are crafted in a way such that such locking is not required when reading audio +data from the graph. Locking in these areas are achieved by means of spinlocks. + +The main complication with keeping `ma_node_graph_read_pcm_frames()` lock-free stems from the fact +that a node can be uninitialized, and it's memory potentially freed, while in the middle of being +processed on the audio thread. There are times when the audio thread will be referencing a node, +which means the uninitialization process of a node needs to make sure it delays returning until the +audio thread is finished so that control is not handed back to the caller thereby giving them a +chance to free the node's memory. + +When the audio thread is processing a node, it does so by reading from each of the output buses of +the node. In order for a node to process data for one of it's output buses, it needs to read from +each of it's input buses, and so on an so forth. It follows that once all output buses of a node +are detached, the node as a whole will be disconnected and no further processing will occur unless +it's output buses are reattached, which won't be happening when the node is being uninitialized. +By having `ma_node_detach_output_bus()` wait until the audio thread is finished with it, we can +simplify a few things, at the expense of making `ma_node_detach_output_bus()` a bit slower. By +doing this, the implementation of `ma_node_uninit()` becomes trivial - just detach all output +nodes, followed by each of the attachments to each of it's input nodes, and then do any final clean +up. + +With the above design, the worst-case scenario is `ma_node_detach_output_bus()` taking as long as +it takes to process the output bus being detached. This will happen if it's called at just the +wrong moment where the audio thread has just iterated it and has just started processing. The +caller of `ma_node_detach_output_bus()` will stall until the audio thread is finished, which +includes the cost of recursively processing it's inputs. This is the biggest compromise made with +the approach taken by miniaudio for it's lock-free processing system. The cost of detaching nodes +earlier in the pipeline (data sources, for example) will be cheaper than the cost of detaching +higher level nodes, such as some kind of final post-processing endpoint. If you need to do mass +detachments, detach starting from the lowest level nodes and work your way towards the final +endpoint node (but don't try detaching the node graph's endpoint). If the audio thread is not +running, detachment will be fast and detachment in any order will be the same. The reason nodes +need to wait for their input attachments to complete is due to the potential for desyncs between +data sources. If the node was to terminate processing mid way through processing it's inputs, +there's a chance that some of the underlying data sources will have been read, but then others not. +That will then result in a potential desynchronization when detaching and reattaching higher-level +nodes. A possible solution to this is to have an option when detaching to terminate processing +before processing all input attachments which should be fairly simple. + +Another compromise, albeit less significant, is locking when attaching and detaching nodes. This +locking is achieved by means of a spinlock in order to reduce memory overhead. A lock is present +for each input bus and output bus. When an output bus is connected to an input bus, both the output +bus and input bus is locked. This locking is specifically for attaching and detaching across +different threads and does not affect `ma_node_graph_read_pcm_frames()` in any way. The locking and +unlocking is mostly self-explanatory, but a slightly less intuitive aspect comes into it when +considering that iterating over attachments must not break as a result of attaching or detaching a +node while iteration is occuring. + +Attaching and detaching are both quite simple. When an output bus of a node is attached to an input +bus of another node, it's added to a linked list. Basically, an input bus is a linked list, where +each item in the list is and output bus. We have some intentional (and convenient) restrictions on +what can done with the linked list in order to simplify the implementation. First of all, whenever +something needs to iterate over the list, it must do so in a forward direction. Backwards iteration +is not supported. Also, items can only be added to the start of the list. + +The linked list is a doubly-linked list where each item in the list (an output bus) holds a pointer +to the next item in the list, and another to the previous item. A pointer to the previous item is +only required for fast detachment of the node - it is never used in iteration. This is an +important property because it means from the perspective of iteration, attaching and detaching of +an item can be done with a single atomic assignment. This is exploited by both the attachment and +detachment process. When attaching the node, the first thing that is done is the setting of the +local "next" and "previous" pointers of the node. After that, the item is "attached" to the list +by simply performing an atomic exchange with the head pointer. After that, the node is "attached" +to the list from the perspective of iteration. Even though the "previous" pointer of the next item +hasn't yet been set, from the perspective of iteration it's been attached because iteration will +only be happening in a forward direction which means the "previous" pointer won't actually ever get +used. The same general process applies to detachment. See `ma_node_attach_output_bus()` and +`ma_node_detach_output_bus()` for the implementation of this mechanism. + + + +8. Decoding =========== -The `ma_decoder` API is used for reading audio files. Decoders are completely decoupled from devices and can be used independently. The following formats are -supported: +The `ma_decoder` API is used for reading audio files. Decoders are completely decoupled from +devices and can be used independently. The following formats are supported: +---------+------------------+----------+ | Format | Decoding Backend | Built-In | @@ -475,7 +2405,8 @@ supported: | Vorbis | stb_vorbis | No | +---------+------------------+----------+ -Vorbis is supported via stb_vorbis which can be enabled by including the header section before the implementation of miniaudio, like the following: +Vorbis is supported via stb_vorbis which can be enabled by including the header section before the +implementation of miniaudio, like the following: ```c #define STB_VORBIS_HEADER_ONLY @@ -491,8 +2422,9 @@ Vorbis is supported via stb_vorbis which can be enabled by including the header A copy of stb_vorbis is included in the "extras" folder in the miniaudio repository (https://github.com/mackron/miniaudio). -Built-in decoders are amalgamated into the implementation section of miniaudio. You can disable the built-in decoders by specifying one or more of the -following options before the miniaudio implementation: +Built-in decoders are amalgamated into the implementation section of miniaudio. You can disable the +built-in decoders by specifying one or more of the following options before the miniaudio +implementation: ```c #define MA_NO_WAV @@ -500,10 +2432,12 @@ following options before the miniaudio implementation: #define MA_NO_FLAC ``` -Disabling built-in decoding libraries is useful if you use these libraries independantly of the `ma_decoder` API. +Disabling built-in decoding libraries is useful if you use these libraries independantly of the +`ma_decoder` API. -A decoder can be initialized from a file with `ma_decoder_init_file()`, a block of memory with `ma_decoder_init_memory()`, or from data delivered via callbacks -with `ma_decoder_init()`. Here is an example for loading a decoder from a file: +A decoder can be initialized from a file with `ma_decoder_init_file()`, a block of memory with +`ma_decoder_init_memory()`, or from data delivered via callbacks with `ma_decoder_init()`. Here is +an example for loading a decoder from a file: ```c ma_decoder decoder; @@ -517,20 +2451,23 @@ with `ma_decoder_init()`. Here is an example for loading a decoder from a file: ma_decoder_uninit(&decoder); ``` -When initializing a decoder, you can optionally pass in a pointer to a `ma_decoder_config` object (the `NULL` argument in the example above) which allows you -to configure the output format, channel count, sample rate and channel map: +When initializing a decoder, you can optionally pass in a pointer to a `ma_decoder_config` object +(the `NULL` argument in the example above) which allows you to configure the output format, channel +count, sample rate and channel map: ```c ma_decoder_config config = ma_decoder_config_init(ma_format_f32, 2, 48000); ``` -When passing in `NULL` for decoder config in `ma_decoder_init*()`, the output format will be the same as that defined by the decoding backend. +When passing in `NULL` for decoder config in `ma_decoder_init*()`, the output format will be the +same as that defined by the decoding backend. -Data is read from the decoder as PCM frames. This will return the number of PCM frames actually read. If the return value is less than the requested number of -PCM frames it means you've reached the end: +Data is read from the decoder as PCM frames. This will output the number of PCM frames actually +read. If this is less than the requested number of PCM frames it means you've reached the end. The +return value will be `MA_AT_END` if no samples have been read and the end has been reached. ```c - ma_uint64 framesRead = ma_decoder_read_pcm_frames(pDecoder, pFrames, framesToRead); + ma_result result = ma_decoder_read_pcm_frames(pDecoder, pFrames, framesToRead, &framesRead); if (framesRead < framesToRead) { // Reached the end. } @@ -551,8 +2488,10 @@ If you want to loop back to the start, you can simply seek back to the first PCM ma_decoder_seek_to_pcm_frame(pDecoder, 0); ``` -When loading a decoder, miniaudio uses a trial and error technique to find the appropriate decoding backend. This can be unnecessarily inefficient if the type -is already known. In this case you can use `encodingFormat` variable in the device config to specify a specific encoding format you want to decode: +When loading a decoder, miniaudio uses a trial and error technique to find the appropriate decoding +backend. This can be unnecessarily inefficient if the type is already known. In this case you can +use `encodingFormat` variable in the device config to specify a specific encoding format you want +to decode: ```c decoderConfig.encodingFormat = ma_encoding_format_wav; @@ -560,24 +2499,95 @@ is already known. In this case you can use `encodingFormat` variable in the devi See the `ma_encoding_format` enum for possible encoding formats. -The `ma_decoder_init_file()` API will try using the file extension to determine which decoding backend to prefer. +The `ma_decoder_init_file()` API will try using the file extension to determine which decoding +backend to prefer. + + +8.1. Custom Decoders +-------------------- +It's possible to implement a custom decoder and plug it into miniaudio. This is extremely useful +when you want to use the `ma_decoder` API, but need to support an encoding format that's not one of +the stock formats supported by miniaudio. This can be put to particularly good use when using the +`ma_engine` and/or `ma_resource_manager` APIs because they use `ma_decoder` internally. If, for +example, you wanted to support Opus, you can do so with a custom decoder (there if a reference +Opus decoder in the "extras" folder of the miniaudio repository which uses libopus + libopusfile). + +A custom decoder must implement a data source. A vtable called `ma_decoding_backend_vtable` needs +to be implemented which is then passed into the decoder config: + + ```c + ma_decoding_backend_vtable* pCustomBackendVTables[] = + { + &g_ma_decoding_backend_vtable_libvorbis, + &g_ma_decoding_backend_vtable_libopus + }; + + ... + + decoderConfig = ma_decoder_config_init_default(); + decoderConfig.pCustomBackendUserData = NULL; + decoderConfig.ppCustomBackendVTables = pCustomBackendVTables; + decoderConfig.customBackendCount = sizeof(pCustomBackendVTables) / sizeof(pCustomBackendVTables[0]); + ``` + +The `ma_decoding_backend_vtable` vtable has the following functions: + + ``` + onInit + onInitFile + onInitFileW + onInitMemory + onUninit + ``` + +There are only two functions that must be implemented - `onInit` and `onUninit`. The other +functions can be implemented for a small optimization for loading from a file path or memory. If +these are not specified, miniaudio will deal with it for you via a generic implementation. + +When you initialize a custom data source (by implementing the `onInit` function in the vtable) you +will need to output a pointer to a `ma_data_source` which implements your custom decoder. See the +section about data sources for details on how to implemen this. Alternatively, see the +"custom_decoders" example in the miniaudio repository. + +The `onInit` function takes a pointer to some callbacks for the purpose of reading raw audio data +from some abitrary source. You'll use these functions to read from the raw data and perform the +decoding. When you call them, you will pass in the `pReadSeekTellUserData` pointer to the relevant +parameter. + +The `pConfig` parameter in `onInit` can be used to configure the backend if appropriate. It's only +used as a hint and can be ignored. However, if any of the properties are relevant to your decoder, +an optimal implementation will handle the relevant properties appropriately. + +If memory allocation is required, it should be done so via the specified allocation callbacks if +possible (the `pAllocationCallbacks` parameter). + +If an error occurs when initializing the decoder, you should leave `ppBackend` unset, or set to +NULL, and make sure everything is cleaned up appropriately and an appropriate result code returned. +When multiple custom backends are specified, miniaudio will cycle through the vtables in the order +they're listed in the array that's passed into the decoder config so it's important that your +initialization routine is clean. + +When a decoder is uninitialized, the `onUninit` callback will be fired which will give you an +opportunity to clean up and internal data. -5. Encoding +9. Encoding =========== -The `ma_encoding` API is used for writing audio files. The only supported output format is WAV which is achieved via dr_wav which is amalgamated into the -implementation section of miniaudio. This can be disabled by specifying the following option before the implementation of miniaudio: +The `ma_encoding` API is used for writing audio files. The only supported output format is WAV +which is achieved via dr_wav which is amalgamated into the implementation section of miniaudio. +This can be disabled by specifying the following option before the implementation of miniaudio: ```c #define MA_NO_WAV ``` -An encoder can be initialized to write to a file with `ma_encoder_init_file()` or from data delivered via callbacks with `ma_encoder_init()`. Below is an -example for initializing an encoder to output to a file. +An encoder can be initialized to write to a file with `ma_encoder_init_file()` or from data +delivered via callbacks with `ma_encoder_init()`. Below is an example for initializing an encoder +to output to a file. ```c - ma_encoder_config config = ma_encoder_config_init(ma_resource_format_wav, FORMAT, CHANNELS, SAMPLE_RATE); + ma_encoder_config config = ma_encoder_config_init(ma_encoding_format_wav, FORMAT, CHANNELS, SAMPLE_RATE); ma_encoder encoder; ma_result result = ma_encoder_init_file("my_file.wav", &config, &encoder); if (result != MA_SUCCESS) { @@ -589,17 +2599,20 @@ example for initializing an encoder to output to a file. ma_encoder_uninit(&encoder); ``` -When initializing an encoder you must specify a config which is initialized with `ma_encoder_config_init()`. Here you must specify the file type, the output -sample format, output channel count and output sample rate. The following file types are supported: +When initializing an encoder you must specify a config which is initialized with +`ma_encoder_config_init()`. Here you must specify the file type, the output sample format, output +channel count and output sample rate. The following file types are supported: +------------------------+-------------+ | Enum | Description | +------------------------+-------------+ - | ma_resource_format_wav | WAV | + | ma_encoding_format_wav | WAV | +------------------------+-------------+ -If the format, channel count or sample rate is not supported by the output file type an error will be returned. The encoder will not perform data conversion so -you will need to convert it before outputting any audio data. To output audio data, use `ma_encoder_write_pcm_frames()`, like in the example below: +If the format, channel count or sample rate is not supported by the output file type an error will +be returned. The encoder will not perform data conversion so you will need to convert it before +outputting any audio data. To output audio data, use `ma_encoder_write_pcm_frames()`, like in the +example below: ```c framesWritten = ma_encoder_write_pcm_frames(&encoder, pPCMFramesToWrite, framesToWrite); @@ -608,21 +2621,25 @@ you will need to convert it before outputting any audio data. To output audio da Encoders must be uninitialized with `ma_encoder_uninit()`. -6. Data Conversion -================== -A data conversion API is included with miniaudio which supports the majority of data conversion requirements. This supports conversion between sample formats, -channel counts (with channel mapping) and sample rates. + +10. Data Conversion +=================== +A data conversion API is included with miniaudio which supports the majority of data conversion +requirements. This supports conversion between sample formats, channel counts (with channel +mapping) and sample rates. -6.1. Sample Format Conversion ------------------------------ -Conversion between sample formats is achieved with the `ma_pcm_*_to_*()`, `ma_pcm_convert()` and `ma_convert_pcm_frames_format()` APIs. Use `ma_pcm_*_to_*()` -to convert between two specific formats. Use `ma_pcm_convert()` to convert based on a `ma_format` variable. Use `ma_convert_pcm_frames_format()` to convert -PCM frames where you want to specify the frame count and channel count as a variable instead of the total sample count. +10.1. Sample Format Conversion +------------------------------ +Conversion between sample formats is achieved with the `ma_pcm_*_to_*()`, `ma_pcm_convert()` and +`ma_convert_pcm_frames_format()` APIs. Use `ma_pcm_*_to_*()` to convert between two specific +formats. Use `ma_pcm_convert()` to convert based on a `ma_format` variable. Use +`ma_convert_pcm_frames_format()` to convert PCM frames where you want to specify the frame count +and channel count as a variable instead of the total sample count. -6.1.1. Dithering ----------------- +10.1.1. Dithering +----------------- Dithering can be set using the ditherMode parameter. The different dithering modes include the following, in order of efficiency: @@ -635,8 +2652,9 @@ The different dithering modes include the following, in order of efficiency: | Triangle | ma_dither_mode_triangle | +-----------+--------------------------+ -Note that even if the dither mode is set to something other than `ma_dither_mode_none`, it will be ignored for conversions where dithering is not needed. -Dithering is available for the following conversions: +Note that even if the dither mode is set to something other than `ma_dither_mode_none`, it will be +ignored for conversions where dithering is not needed. Dithering is available for the following +conversions: ``` s16 -> u8 @@ -648,14 +2666,16 @@ Dithering is available for the following conversions: f32 -> s16 ``` -Note that it is not an error to pass something other than ma_dither_mode_none for conversions where dither is not used. It will just be ignored. +Note that it is not an error to pass something other than ma_dither_mode_none for conversions where +dither is not used. It will just be ignored. -6.2. Channel Conversion ------------------------ -Channel conversion is used for channel rearrangement and conversion from one channel count to another. The `ma_channel_converter` API is used for channel -conversion. Below is an example of initializing a simple channel converter which converts from mono to stereo. +10.2. Channel Conversion +------------------------ +Channel conversion is used for channel rearrangement and conversion from one channel count to +another. The `ma_channel_converter` API is used for channel conversion. Below is an example of +initializing a simple channel converter which converts from mono to stereo. ```c ma_channel_converter_config config = ma_channel_converter_config_init( @@ -666,7 +2686,7 @@ conversion. Below is an example of initializing a simple channel converter which NULL, // Output channel map ma_channel_mix_mode_default); // The mixing algorithm to use when combining channels. - result = ma_channel_converter_init(&config, &converter); + result = ma_channel_converter_init(&config, NULL, &converter); if (result != MA_SUCCESS) { // Error. } @@ -681,34 +2701,43 @@ To perform the conversion simply call `ma_channel_converter_process_pcm_frames() } ``` -It is up to the caller to ensure the output buffer is large enough to accomodate the new PCM frames. +It is up to the caller to ensure the output buffer is large enough to accomodate the new PCM +frames. Input and output PCM frames are always interleaved. Deinterleaved layouts are not supported. -6.2.1. Channel Mapping ----------------------- -In addition to converting from one channel count to another, like the example above, the channel converter can also be used to rearrange channels. When -initializing the channel converter, you can optionally pass in channel maps for both the input and output frames. If the channel counts are the same, and each -channel map contains the same channel positions with the exception that they're in a different order, a simple shuffling of the channels will be performed. If, -however, there is not a 1:1 mapping of channel positions, or the channel counts differ, the input channels will be mixed based on a mixing mode which is -specified when initializing the `ma_channel_converter_config` object. +10.2.1. Channel Mapping +----------------------- +In addition to converting from one channel count to another, like the example above, the channel +converter can also be used to rearrange channels. When initializing the channel converter, you can +optionally pass in channel maps for both the input and output frames. If the channel counts are the +same, and each channel map contains the same channel positions with the exception that they're in +a different order, a simple shuffling of the channels will be performed. If, however, there is not +a 1:1 mapping of channel positions, or the channel counts differ, the input channels will be mixed +based on a mixing mode which is specified when initializing the `ma_channel_converter_config` +object. -When converting from mono to multi-channel, the mono channel is simply copied to each output channel. When going the other way around, the audio of each output -channel is simply averaged and copied to the mono channel. +When converting from mono to multi-channel, the mono channel is simply copied to each output +channel. When going the other way around, the audio of each output channel is simply averaged and +copied to the mono channel. -In more complicated cases blending is used. The `ma_channel_mix_mode_simple` mode will drop excess channels and silence extra channels. For example, converting -from 4 to 2 channels, the 3rd and 4th channels will be dropped, whereas converting from 2 to 4 channels will put silence into the 3rd and 4th channels. +In more complicated cases blending is used. The `ma_channel_mix_mode_simple` mode will drop excess +channels and silence extra channels. For example, converting from 4 to 2 channels, the 3rd and 4th +channels will be dropped, whereas converting from 2 to 4 channels will put silence into the 3rd and +4th channels. -The `ma_channel_mix_mode_rectangle` mode uses spacial locality based on a rectangle to compute a simple distribution between input and output. Imagine sitting -in the middle of a room, with speakers on the walls representing channel positions. The MA_CHANNEL_FRONT_LEFT position can be thought of as being in the corner -of the front and left walls. +The `ma_channel_mix_mode_rectangle` mode uses spacial locality based on a rectangle to compute a +simple distribution between input and output. Imagine sitting in the middle of a room, with +speakers on the walls representing channel positions. The `MA_CHANNEL_FRONT_LEFT` position can be +thought of as being in the corner of the front and left walls. -Finally, the `ma_channel_mix_mode_custom_weights` mode can be used to use custom user-defined weights. Custom weights can be passed in as the last parameter of +Finally, the `ma_channel_mix_mode_custom_weights` mode can be used to use custom user-defined +weights. Custom weights can be passed in as the last parameter of `ma_channel_converter_config_init()`. -Predefined channel maps can be retrieved with `ma_get_standard_channel_map()`. This takes a `ma_standard_channel_map` enum as it's first parameter, which can -be one of the following: +Predefined channel maps can be retrieved with `ma_channel_map_init_standard()`. This takes a +`ma_standard_channel_map` enum as it's first parameter, which can be one of the following: +-----------------------------------+-----------------------------------------------------------+ | Name | Description | @@ -780,9 +2809,10 @@ Below are the channel maps used by default in miniaudio (`ma_standard_channel_ma -6.3. Resampling ---------------- -Resampling is achieved with the `ma_resampler` object. To create a resampler object, do something like the following: +10.3. Resampling +---------------- +Resampling is achieved with the `ma_resampler` object. To create a resampler object, do something +like the following: ```c ma_resampler_config config = ma_resampler_config_init( @@ -819,104 +2849,128 @@ The following example shows how data can be processed // number of output frames written. ``` -To initialize the resampler you first need to set up a config (`ma_resampler_config`) with `ma_resampler_config_init()`. You need to specify the sample format -you want to use, the number of channels, the input and output sample rate, and the algorithm. +To initialize the resampler you first need to set up a config (`ma_resampler_config`) with +`ma_resampler_config_init()`. You need to specify the sample format you want to use, the number of +channels, the input and output sample rate, and the algorithm. -The sample format can be either `ma_format_s16` or `ma_format_f32`. If you need a different format you will need to perform pre- and post-conversions yourself -where necessary. Note that the format is the same for both input and output. The format cannot be changed after initialization. +The sample format can be either `ma_format_s16` or `ma_format_f32`. If you need a different format +you will need to perform pre- and post-conversions yourself where necessary. Note that the format +is the same for both input and output. The format cannot be changed after initialization. -The resampler supports multiple channels and is always interleaved (both input and output). The channel count cannot be changed after initialization. +The resampler supports multiple channels and is always interleaved (both input and output). The +channel count cannot be changed after initialization. -The sample rates can be anything other than zero, and are always specified in hertz. They should be set to something like 44100, etc. The sample rate is the -only configuration property that can be changed after initialization. +The sample rates can be anything other than zero, and are always specified in hertz. They should be +set to something like 44100, etc. The sample rate is the only configuration property that can be +changed after initialization. -The miniaudio resampler supports multiple algorithms: +The miniaudio resampler has built-in support for the following algorithms: +-----------+------------------------------+ | Algorithm | Enum Token | +-----------+------------------------------+ | Linear | ma_resample_algorithm_linear | - | Speex | ma_resample_algorithm_speex | + | Custom | ma_resample_algorithm_custom | +-----------+------------------------------+ -Because Speex is not public domain it is strictly opt-in and the code is stored in separate files. if you opt-in to the Speex backend you will need to consider -it's license, the text of which can be found in it's source files in "extras/speex_resampler". Details on how to opt-in to the Speex resampler is explained in -the Speex Resampler section below. - The algorithm cannot be changed after initialization. -Processing always happens on a per PCM frame basis and always assumes interleaved input and output. De-interleaved processing is not supported. To process -frames, use `ma_resampler_process_pcm_frames()`. On input, this function takes the number of output frames you can fit in the output buffer and the number of -input frames contained in the input buffer. On output these variables contain the number of output frames that were written to the output buffer and the -number of input frames that were consumed in the process. You can pass in NULL for the input buffer in which case it will be treated as an infinitely large -buffer of zeros. The output buffer can also be NULL, in which case the processing will be treated as seek. +Processing always happens on a per PCM frame basis and always assumes interleaved input and output. +De-interleaved processing is not supported. To process frames, use +`ma_resampler_process_pcm_frames()`. On input, this function takes the number of output frames you +can fit in the output buffer and the number of input frames contained in the input buffer. On +output these variables contain the number of output frames that were written to the output buffer +and the number of input frames that were consumed in the process. You can pass in NULL for the +input buffer in which case it will be treated as an infinitely large buffer of zeros. The output +buffer can also be NULL, in which case the processing will be treated as seek. -The sample rate can be changed dynamically on the fly. You can change this with explicit sample rates with `ma_resampler_set_rate()` and also with a decimal -ratio with `ma_resampler_set_rate_ratio()`. The ratio is in/out. +The sample rate can be changed dynamically on the fly. You can change this with explicit sample +rates with `ma_resampler_set_rate()` and also with a decimal ratio with +`ma_resampler_set_rate_ratio()`. The ratio is in/out. -Sometimes it's useful to know exactly how many input frames will be required to output a specific number of frames. You can calculate this with -`ma_resampler_get_required_input_frame_count()`. Likewise, it's sometimes useful to know exactly how many frames would be output given a certain number of -input frames. You can do this with `ma_resampler_get_expected_output_frame_count()`. +Sometimes it's useful to know exactly how many input frames will be required to output a specific +number of frames. You can calculate this with `ma_resampler_get_required_input_frame_count()`. +Likewise, it's sometimes useful to know exactly how many frames would be output given a certain +number of input frames. You can do this with `ma_resampler_get_expected_output_frame_count()`. -Due to the nature of how resampling works, the resampler introduces some latency. This can be retrieved in terms of both the input rate and the output rate -with `ma_resampler_get_input_latency()` and `ma_resampler_get_output_latency()`. +Due to the nature of how resampling works, the resampler introduces some latency. This can be +retrieved in terms of both the input rate and the output rate with +`ma_resampler_get_input_latency()` and `ma_resampler_get_output_latency()`. -6.3.1. Resampling Algorithms ----------------------------- -The choice of resampling algorithm depends on your situation and requirements. The linear resampler is the most efficient and has the least amount of latency, -but at the expense of poorer quality. The Speex resampler is higher quality, but slower with more latency. It also performs several heap allocations internally -for memory management. +10.3.1. Resampling Algorithms +----------------------------- +The choice of resampling algorithm depends on your situation and requirements. -6.3.1.1. Linear Resampling --------------------------- -The linear resampler is the fastest, but comes at the expense of poorer quality. There is, however, some control over the quality of the linear resampler which -may make it a suitable option depending on your requirements. +10.3.1.1. Linear Resampling +--------------------------- +The linear resampler is the fastest, but comes at the expense of poorer quality. There is, however, +some control over the quality of the linear resampler which may make it a suitable option depending +on your requirements. -The linear resampler performs low-pass filtering before or after downsampling or upsampling, depending on the sample rates you're converting between. When -decreasing the sample rate, the low-pass filter will be applied before downsampling. When increasing the rate it will be performed after upsampling. By default -a fourth order low-pass filter will be applied. This can be configured via the `lpfOrder` configuration variable. Setting this to 0 will disable filtering. +The linear resampler performs low-pass filtering before or after downsampling or upsampling, +depending on the sample rates you're converting between. When decreasing the sample rate, the +low-pass filter will be applied before downsampling. When increasing the rate it will be performed +after upsampling. By default a fourth order low-pass filter will be applied. This can be configured +via the `lpfOrder` configuration variable. Setting this to 0 will disable filtering. -The low-pass filter has a cutoff frequency which defaults to half the sample rate of the lowest of the input and output sample rates (Nyquist Frequency). This -can be controlled with the `lpfNyquistFactor` config variable. This defaults to 1, and should be in the range of 0..1, although a value of 0 does not make -sense and should be avoided. A value of 1 will use the Nyquist Frequency as the cutoff. A value of 0.5 will use half the Nyquist Frequency as the cutoff, etc. -Values less than 1 will result in more washed out sound due to more of the higher frequencies being removed. This config variable has no impact on performance -and is a purely perceptual configuration. +The low-pass filter has a cutoff frequency which defaults to half the sample rate of the lowest of +the input and output sample rates (Nyquist Frequency). -The API for the linear resampler is the same as the main resampler API, only it's called `ma_linear_resampler`. +The API for the linear resampler is the same as the main resampler API, only it's called +`ma_linear_resampler`. -6.3.1.2. Speex Resampling +10.3.2. Custom Resamplers ------------------------- -The Speex resampler is made up of third party code which is released under the BSD license. Because it is licensed differently to miniaudio, which is public -domain, it is strictly opt-in and all of it's code is stored in separate files. If you opt-in to the Speex resampler you must consider the license text in it's -source files. To opt-in, you must first `#include` the following file before the implementation of miniaudio.h: +You can implement a custom resampler by using the `ma_resample_algorithm_custom` resampling +algorithm and setting a vtable in the resampler config: ```c - #include "extras/speex_resampler/ma_speex_resampler.h" + ma_resampler_config config = ma_resampler_config_init(..., ma_resample_algorithm_custom); + config.pBackendVTable = &g_customResamplerVTable; ``` -Both the header and implementation is contained within the same file. The implementation can be included in your program like so: +Custom resamplers are useful if the stock algorithms are not appropriate for your use case. You +need to implement the required functions in `ma_resampling_backend_vtable`. Note that not all +functions in the vtable need to be implemented, but if it's possible to implement, they should be. - ```c - #define MINIAUDIO_SPEEX_RESAMPLER_IMPLEMENTATION - #include "extras/speex_resampler/ma_speex_resampler.h" - ``` +You can use the `ma_linear_resampler` object for an example on how to implement the vtable. The +`onGetHeapSize` callback is used to calculate the size of any internal heap allocation the custom +resampler will need to make given the supplied config. When you initialize the resampler via the +`onInit` callback, you'll be given a pointer to a heap allocation which is where you should store +the heap allocated data. You should not free this data in `onUninit` because miniaudio will manage +it for you. -Note that even if you opt-in to the Speex backend, miniaudio won't use it unless you explicitly ask for it in the respective config of the object you are -initializing. If you try to use the Speex resampler without opting in, initialization of the `ma_resampler` object will fail with `MA_NO_BACKEND`. +The `onProcess` callback is where the actual resampling takes place. On input, `pFrameCountIn` +points to a variable containing the number of frames in the `pFramesIn` buffer and +`pFrameCountOut` points to a variable containing the capacity in frames of the `pFramesOut` buffer. +On output, `pFrameCountIn` should be set to the number of input frames that were fully consumed, +whereas `pFrameCountOut` should be set to the number of frames that were written to `pFramesOut`. -The only configuration option to consider with the Speex resampler is the `speex.quality` config variable. This is a value between 0 and 10, with 0 being -the fastest with the poorest quality and 10 being the slowest with the highest quality. The default value is 3. +The `onSetRate` callback is optional and is used for dynamically changing the sample rate. If +dynamic rate changes are not supported, you can set this callback to NULL. + +The `onGetInputLatency` and `onGetOutputLatency` functions are used for retrieving the latency in +input and output rates respectively. These can be NULL in which case latency calculations will be +assumed to be NULL. + +The `onGetRequiredInputFrameCount` callback is used to give miniaudio a hint as to how many input +frames are required to be available to produce the given number of output frames. Likewise, the +`onGetExpectedOutputFrameCount` callback is used to determine how many output frames will be +produced given the specified number of input frames. miniaudio will use these as a hint, but they +are optional and can be set to NULL if you're unable to implement them. -6.4. General Data Conversion ----------------------------- -The `ma_data_converter` API can be used to wrap sample format conversion, channel conversion and resampling into one operation. This is what miniaudio uses -internally to convert between the format requested when the device was initialized and the format of the backend's native device. The API for general data -conversion is very similar to the resampling API. Create a `ma_data_converter` object like this: +10.4. General Data Conversion +----------------------------- +The `ma_data_converter` API can be used to wrap sample format conversion, channel conversion and +resampling into one operation. This is what miniaudio uses internally to convert between the format +requested when the device was initialized and the format of the backend's native device. The API +for general data conversion is very similar to the resampling API. Create a `ma_data_converter` +object like this: ```c ma_data_converter_config config = ma_data_converter_config_init( @@ -929,14 +2983,15 @@ conversion is very similar to the resampling API. Create a `ma_data_converter` o ); ma_data_converter converter; - ma_result result = ma_data_converter_init(&config, &converter); + ma_result result = ma_data_converter_init(&config, NULL, &converter); if (result != MA_SUCCESS) { // An error occurred... } ``` -In the example above we use `ma_data_converter_config_init()` to initialize the config, however there's many more properties that can be configured, such as -channel maps and resampling quality. Something like the following may be more suitable depending on your requirements: +In the example above we use `ma_data_converter_config_init()` to initialize the config, however +there's many more properties that can be configured, such as channel maps and resampling quality. +Something like the following may be more suitable depending on your requirements: ```c ma_data_converter_config config = ma_data_converter_config_init_default(); @@ -946,14 +3001,14 @@ channel maps and resampling quality. Something like the following may be more su config.channelsOut = outputChannels; config.sampleRateIn = inputSampleRate; config.sampleRateOut = outputSampleRate; - ma_get_standard_channel_map(ma_standard_channel_map_flac, config.channelCountIn, config.channelMapIn); + ma_channel_map_init_standard(ma_standard_channel_map_flac, config.channelMapIn, sizeof(config.channelMapIn)/sizeof(config.channelMapIn[0]), config.channelCountIn); config.resampling.linear.lpfOrder = MA_MAX_FILTER_ORDER; ``` Do the following to uninitialize the data converter: ```c - ma_data_converter_uninit(&converter); + ma_data_converter_uninit(&converter, NULL); ``` The following example shows how data can be processed @@ -970,33 +3025,42 @@ The following example shows how data can be processed // of output frames written. ``` -The data converter supports multiple channels and is always interleaved (both input and output). The channel count cannot be changed after initialization. +The data converter supports multiple channels and is always interleaved (both input and output). +The channel count cannot be changed after initialization. -Sample rates can be anything other than zero, and are always specified in hertz. They should be set to something like 44100, etc. The sample rate is the only -configuration property that can be changed after initialization, but only if the `resampling.allowDynamicSampleRate` member of `ma_data_converter_config` is -set to `MA_TRUE`. To change the sample rate, use `ma_data_converter_set_rate()` or `ma_data_converter_set_rate_ratio()`. The ratio must be in/out. The -resampling algorithm cannot be changed after initialization. +Sample rates can be anything other than zero, and are always specified in hertz. They should be set +to something like 44100, etc. The sample rate is the only configuration property that can be +changed after initialization, but only if the `resampling.allowDynamicSampleRate` member of +`ma_data_converter_config` is set to `MA_TRUE`. To change the sample rate, use +`ma_data_converter_set_rate()` or `ma_data_converter_set_rate_ratio()`. The ratio must be in/out. +The resampling algorithm cannot be changed after initialization. -Processing always happens on a per PCM frame basis and always assumes interleaved input and output. De-interleaved processing is not supported. To process -frames, use `ma_data_converter_process_pcm_frames()`. On input, this function takes the number of output frames you can fit in the output buffer and the number -of input frames contained in the input buffer. On output these variables contain the number of output frames that were written to the output buffer and the -number of input frames that were consumed in the process. You can pass in NULL for the input buffer in which case it will be treated as an infinitely large -buffer of zeros. The output buffer can also be NULL, in which case the processing will be treated as seek. +Processing always happens on a per PCM frame basis and always assumes interleaved input and output. +De-interleaved processing is not supported. To process frames, use +`ma_data_converter_process_pcm_frames()`. On input, this function takes the number of output frames +you can fit in the output buffer and the number of input frames contained in the input buffer. On +output these variables contain the number of output frames that were written to the output buffer +and the number of input frames that were consumed in the process. You can pass in NULL for the +input buffer in which case it will be treated as an infinitely large +buffer of zeros. The output buffer can also be NULL, in which case the processing will be treated +as seek. -Sometimes it's useful to know exactly how many input frames will be required to output a specific number of frames. You can calculate this with -`ma_data_converter_get_required_input_frame_count()`. Likewise, it's sometimes useful to know exactly how many frames would be output given a certain number of -input frames. You can do this with `ma_data_converter_get_expected_output_frame_count()`. +Sometimes it's useful to know exactly how many input frames will be required to output a specific +number of frames. You can calculate this with `ma_data_converter_get_required_input_frame_count()`. +Likewise, it's sometimes useful to know exactly how many frames would be output given a certain +number of input frames. You can do this with `ma_data_converter_get_expected_output_frame_count()`. -Due to the nature of how resampling works, the data converter introduces some latency if resampling is required. This can be retrieved in terms of both the -input rate and the output rate with `ma_data_converter_get_input_latency()` and `ma_data_converter_get_output_latency()`. +Due to the nature of how resampling works, the data converter introduces some latency if resampling +is required. This can be retrieved in terms of both the input rate and the output rate with +`ma_data_converter_get_input_latency()` and `ma_data_converter_get_output_latency()`. -7. Filtering -============ +11. Filtering +============= -7.1. Biquad Filtering ---------------------- +11.1. Biquad Filtering +---------------------- Biquad filtering is achieved with the `ma_biquad` API. Example: ```c @@ -1011,28 +3075,33 @@ Biquad filtering is achieved with the `ma_biquad` API. Example: ma_biquad_process_pcm_frames(&biquad, pFramesOut, pFramesIn, frameCount); ``` -Biquad filtering is implemented using transposed direct form 2. The numerator coefficients are b0, b1 and b2, and the denominator coefficients are a0, a1 and -a2. The a0 coefficient is required and coefficients must not be pre-normalized. +Biquad filtering is implemented using transposed direct form 2. The numerator coefficients are b0, +b1 and b2, and the denominator coefficients are a0, a1 and a2. The a0 coefficient is required and +coefficients must not be pre-normalized. -Supported formats are `ma_format_s16` and `ma_format_f32`. If you need to use a different format you need to convert it yourself beforehand. When using -`ma_format_s16` the biquad filter will use fixed point arithmetic. When using `ma_format_f32`, floating point arithmetic will be used. +Supported formats are `ma_format_s16` and `ma_format_f32`. If you need to use a different format +you need to convert it yourself beforehand. When using `ma_format_s16` the biquad filter will use +fixed point arithmetic. When using `ma_format_f32`, floating point arithmetic will be used. Input and output frames are always interleaved. -Filtering can be applied in-place by passing in the same pointer for both the input and output buffers, like so: +Filtering can be applied in-place by passing in the same pointer for both the input and output +buffers, like so: ```c ma_biquad_process_pcm_frames(&biquad, pMyData, pMyData, frameCount); ``` -If you need to change the values of the coefficients, but maintain the values in the registers you can do so with `ma_biquad_reinit()`. This is useful if you -need to change the properties of the filter while keeping the values of registers valid to avoid glitching. Do not use `ma_biquad_init()` for this as it will -do a full initialization which involves clearing the registers to 0. Note that changing the format or channel count after initialization is invalid and will -result in an error. +If you need to change the values of the coefficients, but maintain the values in the registers you +can do so with `ma_biquad_reinit()`. This is useful if you need to change the properties of the +filter while keeping the values of registers valid to avoid glitching. Do not use +`ma_biquad_init()` for this as it will do a full initialization which involves clearing the +registers to 0. Note that changing the format or channel count after initialization is invalid and +will result in an error. -7.2. Low-Pass Filtering ------------------------ +11.2. Low-Pass Filtering +------------------------ Low-pass filtering is achieved with the following APIs: +---------+------------------------------------------+ @@ -1057,16 +3126,18 @@ Low-pass filter example: ma_lpf_process_pcm_frames(&lpf, pFramesOut, pFramesIn, frameCount); ``` -Supported formats are `ma_format_s16` and` ma_format_f32`. If you need to use a different format you need to convert it yourself beforehand. Input and output -frames are always interleaved. +Supported formats are `ma_format_s16` and` ma_format_f32`. If you need to use a different format +you need to convert it yourself beforehand. Input and output frames are always interleaved. -Filtering can be applied in-place by passing in the same pointer for both the input and output buffers, like so: +Filtering can be applied in-place by passing in the same pointer for both the input and output +buffers, like so: ```c ma_lpf_process_pcm_frames(&lpf, pMyData, pMyData, frameCount); ``` -The maximum filter order is limited to `MA_MAX_FILTER_ORDER` which is set to 8. If you need more, you can chain first and second order filters together. +The maximum filter order is limited to `MA_MAX_FILTER_ORDER` which is set to 8. If you need more, +you can chain first and second order filters together. ```c for (iFilter = 0; iFilter < filterCount; iFilter += 1) { @@ -1074,19 +3145,22 @@ The maximum filter order is limited to `MA_MAX_FILTER_ORDER` which is set to 8. } ``` -If you need to change the configuration of the filter, but need to maintain the state of internal registers you can do so with `ma_lpf_reinit()`. This may be -useful if you need to change the sample rate and/or cutoff frequency dynamically while maintaing smooth transitions. Note that changing the format or channel -count after initialization is invalid and will result in an error. +If you need to change the configuration of the filter, but need to maintain the state of internal +registers you can do so with `ma_lpf_reinit()`. This may be useful if you need to change the sample +rate and/or cutoff frequency dynamically while maintaing smooth transitions. Note that changing the +format or channel count after initialization is invalid and will result in an error. -The `ma_lpf` object supports a configurable order, but if you only need a first order filter you may want to consider using `ma_lpf1`. Likewise, if you only -need a second order filter you can use `ma_lpf2`. The advantage of this is that they're lighter weight and a bit more efficient. +The `ma_lpf` object supports a configurable order, but if you only need a first order filter you +may want to consider using `ma_lpf1`. Likewise, if you only need a second order filter you can use +`ma_lpf2`. The advantage of this is that they're lighter weight and a bit more efficient. -If an even filter order is specified, a series of second order filters will be processed in a chain. If an odd filter order is specified, a first order filter -will be applied, followed by a series of second order filters in a chain. +If an even filter order is specified, a series of second order filters will be processed in a +chain. If an odd filter order is specified, a first order filter will be applied, followed by a +series of second order filters in a chain. -7.3. High-Pass Filtering ------------------------- +11.3. High-Pass Filtering +------------------------- High-pass filtering is achieved with the following APIs: +---------+-------------------------------------------+ @@ -1097,12 +3171,12 @@ High-pass filtering is achieved with the following APIs: | ma_hpf | High order high-pass filter (Butterworth) | +---------+-------------------------------------------+ -High-pass filters work exactly the same as low-pass filters, only the APIs are called `ma_hpf1`, `ma_hpf2` and `ma_hpf`. See example code for low-pass filters -for example usage. +High-pass filters work exactly the same as low-pass filters, only the APIs are called `ma_hpf1`, +`ma_hpf2` and `ma_hpf`. See example code for low-pass filters for example usage. -7.4. Band-Pass Filtering ------------------------- +11.4. Band-Pass Filtering +------------------------- Band-pass filtering is achieved with the following APIs: +---------+-------------------------------+ @@ -1112,13 +3186,14 @@ Band-pass filtering is achieved with the following APIs: | ma_bpf | High order band-pass filter | +---------+-------------------------------+ -Band-pass filters work exactly the same as low-pass filters, only the APIs are called `ma_bpf2` and `ma_hpf`. See example code for low-pass filters for example -usage. Note that the order for band-pass filters must be an even number which means there is no first order band-pass filter, unlike low-pass and high-pass -filters. +Band-pass filters work exactly the same as low-pass filters, only the APIs are called `ma_bpf2` and +`ma_hpf`. See example code for low-pass filters for example usage. Note that the order for +band-pass filters must be an even number which means there is no first order band-pass filter, +unlike low-pass and high-pass filters. -7.5. Notch Filtering --------------------- +11.5. Notch Filtering +--------------------- Notch filtering is achieved with the following APIs: +-----------+------------------------------------------+ @@ -1128,7 +3203,7 @@ Notch filtering is achieved with the following APIs: +-----------+------------------------------------------+ -7.6. Peaking EQ Filtering +11.6. Peaking EQ Filtering ------------------------- Peaking filtering is achieved with the following APIs: @@ -1139,8 +3214,8 @@ Peaking filtering is achieved with the following APIs: +----------+------------------------------------------+ -7.7. Low Shelf Filtering ------------------------- +11.7. Low Shelf Filtering +------------------------- Low shelf filtering is achieved with the following APIs: +-------------+------------------------------------------+ @@ -1149,11 +3224,12 @@ Low shelf filtering is achieved with the following APIs: | ma_loshelf2 | Second order low shelf filter | +-------------+------------------------------------------+ -Where a high-pass filter is used to eliminate lower frequencies, a low shelf filter can be used to just turn them down rather than eliminate them entirely. +Where a high-pass filter is used to eliminate lower frequencies, a low shelf filter can be used to +just turn them down rather than eliminate them entirely. -7.8. High Shelf Filtering -------------------------- +11.8. High Shelf Filtering +-------------------------- High shelf filtering is achieved with the following APIs: +-------------+------------------------------------------+ @@ -1162,18 +3238,20 @@ High shelf filtering is achieved with the following APIs: | ma_hishelf2 | Second order high shelf filter | +-------------+------------------------------------------+ -The high shelf filter has the same API as the low shelf filter, only you would use `ma_hishelf` instead of `ma_loshelf`. Where a low shelf filter is used to -adjust the volume of low frequencies, the high shelf filter does the same thing for high frequencies. +The high shelf filter has the same API as the low shelf filter, only you would use `ma_hishelf` +instead of `ma_loshelf`. Where a low shelf filter is used to adjust the volume of low frequencies, +the high shelf filter does the same thing for high frequencies. -8. Waveform and Noise Generation -================================ +12. Waveform and Noise Generation +================================= -8.1. Waveforms --------------- -miniaudio supports generation of sine, square, triangle and sawtooth waveforms. This is achieved with the `ma_waveform` API. Example: +12.1. Waveforms +--------------- +miniaudio supports generation of sine, square, triangle and sawtooth waveforms. This is achieved +with the `ma_waveform` API. Example: ```c ma_waveform_config config = ma_waveform_config_init( @@ -1195,11 +3273,12 @@ miniaudio supports generation of sine, square, triangle and sawtooth waveforms. ma_waveform_read_pcm_frames(&waveform, pOutput, frameCount); ``` -The amplitude, frequency, type, and sample rate can be changed dynamically with `ma_waveform_set_amplitude()`, `ma_waveform_set_frequency()`, -`ma_waveform_set_type()`, and `ma_waveform_set_sample_rate()` respectively. +The amplitude, frequency, type, and sample rate can be changed dynamically with +`ma_waveform_set_amplitude()`, `ma_waveform_set_frequency()`, `ma_waveform_set_type()`, and +`ma_waveform_set_sample_rate()` respectively. -You can invert the waveform by setting the amplitude to a negative value. You can use this to control whether or not a sawtooth has a positive or negative -ramp, for example. +You can invert the waveform by setting the amplitude to a negative value. You can use this to +control whether or not a sawtooth has a positive or negative ramp, for example. Below are the supported waveform types: @@ -1214,8 +3293,8 @@ Below are the supported waveform types: -8.2. Noise ----------- +12.2. Noise +----------- miniaudio supports generation of white, pink and Brownian noise via the `ma_noise` API. Example: ```c @@ -1237,13 +3316,16 @@ miniaudio supports generation of white, pink and Brownian noise via the `ma_nois ma_noise_read_pcm_frames(&noise, pOutput, frameCount); ``` -The noise API uses simple LCG random number generation. It supports a custom seed which is useful for things like automated testing requiring reproducibility. -Setting the seed to zero will default to `MA_DEFAULT_LCG_SEED`. +The noise API uses simple LCG random number generation. It supports a custom seed which is useful +for things like automated testing requiring reproducibility. Setting the seed to zero will default +to `MA_DEFAULT_LCG_SEED`. -The amplitude, seed, and type can be changed dynamically with `ma_noise_set_amplitude()`, `ma_noise_set_seed()`, and `ma_noise_set_type()` respectively. +The amplitude, seed, and type can be changed dynamically with `ma_noise_set_amplitude()`, +`ma_noise_set_seed()`, and `ma_noise_set_type()` respectively. -By default, the noise API will use different values for different channels. So, for example, the left side in a stereo stream will be different to the right -side. To instead have each channel use the same random value, set the `duplicateChannels` member of the noise config to true, like so: +By default, the noise API will use different values for different channels. So, for example, the +left side in a stereo stream will be different to the right side. To instead have each channel use +the same random value, set the `duplicateChannels` member of the noise config to true, like so: ```c config.duplicateChannels = MA_TRUE; @@ -1261,10 +3343,11 @@ Below are the supported noise types. -9. Audio Buffers -================ -miniaudio supports reading from a buffer of raw audio data via the `ma_audio_buffer` API. This can read from memory that's managed by the application, but -can also handle the memory management for you internally. Memory management is flexible and should support most use cases. +13. Audio Buffers +================= +miniaudio supports reading from a buffer of raw audio data via the `ma_audio_buffer` API. This can +read from memory that's managed by the application, but can also handle the memory management for +you internally. Memory management is flexible and should support most use cases. Audio buffers are initialised using the standard configuration system used everywhere in miniaudio: @@ -1287,11 +3370,14 @@ Audio buffers are initialised using the standard configuration system used every ma_audio_buffer_uninit(&buffer); ``` -In the example above, the memory pointed to by `pExistingData` will *not* be copied and is how an application can do self-managed memory allocation. If you -would rather make a copy of the data, use `ma_audio_buffer_init_copy()`. To uninitialize the buffer, use `ma_audio_buffer_uninit()`. +In the example above, the memory pointed to by `pExistingData` will *not* be copied and is how an +application can do self-managed memory allocation. If you would rather make a copy of the data, use +`ma_audio_buffer_init_copy()`. To uninitialize the buffer, use `ma_audio_buffer_uninit()`. -Sometimes it can be convenient to allocate the memory for the `ma_audio_buffer` structure and the raw audio data in a contiguous block of memory. That is, -the raw audio data will be located immediately after the `ma_audio_buffer` structure. To do this, use `ma_audio_buffer_alloc_and_init()`: +Sometimes it can be convenient to allocate the memory for the `ma_audio_buffer` structure and the +raw audio data in a contiguous block of memory. That is, the raw audio data will be located +immediately after the `ma_audio_buffer` structure. To do this, use +`ma_audio_buffer_alloc_and_init()`: ```c ma_audio_buffer_config config = ma_audio_buffer_config_init( @@ -1312,13 +3398,18 @@ the raw audio data will be located immediately after the `ma_audio_buffer` struc ma_audio_buffer_uninit_and_free(&buffer); ``` -If you initialize the buffer with `ma_audio_buffer_alloc_and_init()` you should uninitialize it with `ma_audio_buffer_uninit_and_free()`. In the example above, -the memory pointed to by `pExistingData` will be copied into the buffer, which is contrary to the behavior of `ma_audio_buffer_init()`. +If you initialize the buffer with `ma_audio_buffer_alloc_and_init()` you should uninitialize it +with `ma_audio_buffer_uninit_and_free()`. In the example above, the memory pointed to by +`pExistingData` will be copied into the buffer, which is contrary to the behavior of +`ma_audio_buffer_init()`. -An audio buffer has a playback cursor just like a decoder. As you read frames from the buffer, the cursor moves forward. The last parameter (`loop`) can be -used to determine if the buffer should loop. The return value is the number of frames actually read. If this is less than the number of frames requested it -means the end has been reached. This should never happen if the `loop` parameter is set to true. If you want to manually loop back to the start, you can do so -with with `ma_audio_buffer_seek_to_pcm_frame(pAudioBuffer, 0)`. Below is an example for reading data from an audio buffer. +An audio buffer has a playback cursor just like a decoder. As you read frames from the buffer, the +cursor moves forward. The last parameter (`loop`) can be used to determine if the buffer should +loop. The return value is the number of frames actually read. If this is less than the number of +frames requested it means the end has been reached. This should never happen if the `loop` +parameter is set to true. If you want to manually loop back to the start, you can do so with with +`ma_audio_buffer_seek_to_pcm_frame(pAudioBuffer, 0)`. Below is an example for reading data from an +audio buffer. ```c ma_uint64 framesRead = ma_audio_buffer_read_pcm_frames(pAudioBuffer, pFramesOut, desiredFrameCount, isLooping); @@ -1327,8 +3418,8 @@ with with `ma_audio_buffer_seek_to_pcm_frame(pAudioBuffer, 0)`. Below is an exam } ``` -Sometimes you may want to avoid the cost of data movement between the internal buffer and the output buffer. Instead you can use memory mapping to retrieve a -pointer to a segment of data: +Sometimes you may want to avoid the cost of data movement between the internal buffer and the +output buffer. Instead you can use memory mapping to retrieve a pointer to a segment of data: ```c void* pMappedFrames; @@ -1344,23 +3435,30 @@ pointer to a segment of data: } ``` -When you use memory mapping, the read cursor is increment by the frame count passed in to `ma_audio_buffer_unmap()`. If you decide not to process every frame -you can pass in a value smaller than the value returned by `ma_audio_buffer_map()`. The disadvantage to using memory mapping is that it does not handle looping -for you. You can determine if the buffer is at the end for the purpose of looping with `ma_audio_buffer_at_end()` or by inspecting the return value of -`ma_audio_buffer_unmap()` and checking if it equals `MA_AT_END`. You should not treat `MA_AT_END` as an error when returned by `ma_audio_buffer_unmap()`. +When you use memory mapping, the read cursor is increment by the frame count passed in to +`ma_audio_buffer_unmap()`. If you decide not to process every frame you can pass in a value smaller +than the value returned by `ma_audio_buffer_map()`. The disadvantage to using memory mapping is +that it does not handle looping for you. You can determine if the buffer is at the end for the +purpose of looping with `ma_audio_buffer_at_end()` or by inspecting the return value of +`ma_audio_buffer_unmap()` and checking if it equals `MA_AT_END`. You should not treat `MA_AT_END` +as an error when returned by `ma_audio_buffer_unmap()`. -10. Ring Buffers +14. Ring Buffers ================ -miniaudio supports lock free (single producer, single consumer) ring buffers which are exposed via the `ma_rb` and `ma_pcm_rb` APIs. The `ma_rb` API operates -on bytes, whereas the `ma_pcm_rb` operates on PCM frames. They are otherwise identical as `ma_pcm_rb` is just a wrapper around `ma_rb`. +miniaudio supports lock free (single producer, single consumer) ring buffers which are exposed via +the `ma_rb` and `ma_pcm_rb` APIs. The `ma_rb` API operates on bytes, whereas the `ma_pcm_rb` +operates on PCM frames. They are otherwise identical as `ma_pcm_rb` is just a wrapper around +`ma_rb`. -Unlike most other APIs in miniaudio, ring buffers support both interleaved and deinterleaved streams. The caller can also allocate their own backing memory for -the ring buffer to use internally for added flexibility. Otherwise the ring buffer will manage it's internal memory for you. +Unlike most other APIs in miniaudio, ring buffers support both interleaved and deinterleaved +streams. The caller can also allocate their own backing memory for the ring buffer to use +internally for added flexibility. Otherwise the ring buffer will manage it's internal memory for +you. -The examples below use the PCM frame variant of the ring buffer since that's most likely the one you will want to use. To initialize a ring buffer, do -something like the following: +The examples below use the PCM frame variant of the ring buffer since that's most likely the one +you will want to use. To initialize a ring buffer, do something like the following: ```c ma_pcm_rb rb; @@ -1370,39 +3468,53 @@ something like the following: } ``` -The `ma_pcm_rb_init()` function takes the sample format and channel count as parameters because it's the PCM varient of the ring buffer API. For the regular -ring buffer that operates on bytes you would call `ma_rb_init()` which leaves these out and just takes the size of the buffer in bytes instead of frames. The -fourth parameter is an optional pre-allocated buffer and the fifth parameter is a pointer to a `ma_allocation_callbacks` structure for custom memory allocation -routines. Passing in `NULL` for this results in `MA_MALLOC()` and `MA_FREE()` being used. +The `ma_pcm_rb_init()` function takes the sample format and channel count as parameters because +it's the PCM varient of the ring buffer API. For the regular ring buffer that operates on bytes you +would call `ma_rb_init()` which leaves these out and just takes the size of the buffer in bytes +instead of frames. The fourth parameter is an optional pre-allocated buffer and the fifth parameter +is a pointer to a `ma_allocation_callbacks` structure for custom memory allocation routines. +Passing in `NULL` for this results in `MA_MALLOC()` and `MA_FREE()` being used. -Use `ma_pcm_rb_init_ex()` if you need a deinterleaved buffer. The data for each sub-buffer is offset from each other based on the stride. To manage your -sub-buffers you can use `ma_pcm_rb_get_subbuffer_stride()`, `ma_pcm_rb_get_subbuffer_offset()` and `ma_pcm_rb_get_subbuffer_ptr()`. +Use `ma_pcm_rb_init_ex()` if you need a deinterleaved buffer. The data for each sub-buffer is +offset from each other based on the stride. To manage your sub-buffers you can use +`ma_pcm_rb_get_subbuffer_stride()`, `ma_pcm_rb_get_subbuffer_offset()` and +`ma_pcm_rb_get_subbuffer_ptr()`. -Use `ma_pcm_rb_acquire_read()` and `ma_pcm_rb_acquire_write()` to retrieve a pointer to a section of the ring buffer. You specify the number of frames you -need, and on output it will set to what was actually acquired. If the read or write pointer is positioned such that the number of frames requested will require -a loop, it will be clamped to the end of the buffer. Therefore, the number of frames you're given may be less than the number you requested. +Use `ma_pcm_rb_acquire_read()` and `ma_pcm_rb_acquire_write()` to retrieve a pointer to a section +of the ring buffer. You specify the number of frames you need, and on output it will set to what +was actually acquired. If the read or write pointer is positioned such that the number of frames +requested will require a loop, it will be clamped to the end of the buffer. Therefore, the number +of frames you're given may be less than the number you requested. -After calling `ma_pcm_rb_acquire_read()` or `ma_pcm_rb_acquire_write()`, you do your work on the buffer and then "commit" it with `ma_pcm_rb_commit_read()` or -`ma_pcm_rb_commit_write()`. This is where the read/write pointers are updated. When you commit you need to pass in the buffer that was returned by the earlier -call to `ma_pcm_rb_acquire_read()` or `ma_pcm_rb_acquire_write()` and is only used for validation. The number of frames passed to `ma_pcm_rb_commit_read()` and -`ma_pcm_rb_commit_write()` is what's used to increment the pointers, and can be less that what was originally requested. +After calling `ma_pcm_rb_acquire_read()` or `ma_pcm_rb_acquire_write()`, you do your work on the +buffer and then "commit" it with `ma_pcm_rb_commit_read()` or `ma_pcm_rb_commit_write()`. This is +where the read/write pointers are updated. When you commit you need to pass in the buffer that was +returned by the earlier call to `ma_pcm_rb_acquire_read()` or `ma_pcm_rb_acquire_write()` and is +only used for validation. The number of frames passed to `ma_pcm_rb_commit_read()` and +`ma_pcm_rb_commit_write()` is what's used to increment the pointers, and can be less that what was +originally requested. -If you want to correct for drift between the write pointer and the read pointer you can use a combination of `ma_pcm_rb_pointer_distance()`, -`ma_pcm_rb_seek_read()` and `ma_pcm_rb_seek_write()`. Note that you can only move the pointers forward, and you should only move the read pointer forward via -the consumer thread, and the write pointer forward by the producer thread. If there is too much space between the pointers, move the read pointer forward. If +If you want to correct for drift between the write pointer and the read pointer you can use a +combination of `ma_pcm_rb_pointer_distance()`, `ma_pcm_rb_seek_read()` and +`ma_pcm_rb_seek_write()`. Note that you can only move the pointers forward, and you should only +move the read pointer forward via the consumer thread, and the write pointer forward by the +producer thread. If there is too much space between the pointers, move the read pointer forward. If there is too little space between the pointers, move the write pointer forward. -You can use a ring buffer at the byte level instead of the PCM frame level by using the `ma_rb` API. This is exactly the same, only you will use the `ma_rb` -functions instead of `ma_pcm_rb` and instead of frame counts you will pass around byte counts. +You can use a ring buffer at the byte level instead of the PCM frame level by using the `ma_rb` +API. This is exactly the same, only you will use the `ma_rb` functions instead of `ma_pcm_rb` and +instead of frame counts you will pass around byte counts. -The maximum size of the buffer in bytes is `0x7FFFFFFF-(MA_SIMD_ALIGNMENT-1)` due to the most significant bit being used to encode a loop flag and the internally -managed buffers always being aligned to MA_SIMD_ALIGNMENT. +The maximum size of the buffer in bytes is `0x7FFFFFFF-(MA_SIMD_ALIGNMENT-1)` due to the most +significant bit being used to encode a loop flag and the internally managed buffers always being +aligned to `MA_SIMD_ALIGNMENT`. -Note that the ring buffer is only thread safe when used by a single consumer thread and single producer thread. +Note that the ring buffer is only thread safe when used by a single consumer thread and single +producer thread. -11. Backends +15. Backends ============ The following backends are supported by miniaudio. @@ -1428,28 +3540,36 @@ The following backends are supported by miniaudio. Some backends have some nuance details you may want to be aware of. -11.1. WASAPI +15.1. WASAPI ------------ -- Low-latency shared mode will be disabled when using an application-defined sample rate which is different to the device's native sample rate. To work around - this, set `wasapi.noAutoConvertSRC` to true in the device config. This is due to IAudioClient3_InitializeSharedAudioStream() failing when the - `AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM` flag is specified. Setting wasapi.noAutoConvertSRC will result in miniaudio's internal resampler being used instead - which will in turn enable the use of low-latency shared mode. +- Low-latency shared mode will be disabled when using an application-defined sample rate which is + different to the device's native sample rate. To work around this, set `wasapi.noAutoConvertSRC` + to true in the device config. This is due to IAudioClient3_InitializeSharedAudioStream() failing + when the `AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM` flag is specified. Setting wasapi.noAutoConvertSRC + will result in miniaudio's internal resampler being used instead which will in turn enable the + use of low-latency shared mode. -11.2. PulseAudio +15.2. PulseAudio ---------------- - If you experience bad glitching/noise on Arch Linux, consider this fix from the Arch wiki: - https://wiki.archlinux.org/index.php/PulseAudio/Troubleshooting#Glitches,_skips_or_crackling. Alternatively, consider using a different backend such as ALSA. + https://wiki.archlinux.org/index.php/PulseAudio/Troubleshooting#Glitches,_skips_or_crackling. + Alternatively, consider using a different backend such as ALSA. -11.3. Android +15.3. Android ------------- -- To capture audio on Android, remember to add the RECORD_AUDIO permission to your manifest: `` -- With OpenSL|ES, only a single ma_context can be active at any given time. This is due to a limitation with OpenSL|ES. -- With AAudio, only default devices are enumerated. This is due to AAudio not having an enumeration API (devices are enumerated through Java). You can however - perform your own device enumeration through Java and then set the ID in the ma_device_id structure (ma_device_id.aaudio) and pass it to ma_device_init(). -- The backend API will perform resampling where possible. The reason for this as opposed to using miniaudio's built-in resampler is to take advantage of any - potential device-specific optimizations the driver may implement. +- To capture audio on Android, remember to add the RECORD_AUDIO permission to your manifest: + `` +- With OpenSL|ES, only a single ma_context can be active at any given time. This is due to a + limitation with OpenSL|ES. +- With AAudio, only default devices are enumerated. This is due to AAudio not having an enumeration + API (devices are enumerated through Java). You can however perform your own device enumeration + through Java and then set the ID in the ma_device_id structure (ma_device_id.aaudio) and pass it + to ma_device_init(). +- The backend API will perform resampling where possible. The reason for this as opposed to using + miniaudio's built-in resampler is to take advantage of any potential device-specific + optimizations the driver may implement. -11.4. UWP +15.4. UWP --------- - UWP only supports default playback and capture devices. - UWP requires the Microphone capability to be enabled in the application's manifest (Package.appxmanifest): @@ -1463,28 +3583,49 @@ Some backends have some nuance details you may want to be aware of. ``` -11.5. Web Audio / Emscripten +15.5. Web Audio / Emscripten ---------------------------- - You cannot use `-std=c*` compiler flags, nor `-ansi`. This only applies to the Emscripten build. -- The first time a context is initialized it will create a global object called "miniaudio" whose primary purpose is to act as a factory for device objects. -- Currently the Web Audio backend uses ScriptProcessorNode's, but this may need to change later as they've been deprecated. -- Google has implemented a policy in their browsers that prevent automatic media output without first receiving some kind of user input. The following web page - has additional details: https://developers.google.com/web/updates/2017/09/autoplay-policy-changes. Starting the device may fail if you try to start playback - without first handling some kind of user input. +- The first time a context is initialized it will create a global object called "miniaudio" whose + primary purpose is to act as a factory for device objects. +- Currently the Web Audio backend uses ScriptProcessorNode's, but this may need to change later as + they've been deprecated. +- Google has implemented a policy in their browsers that prevent automatic media output without + first receiving some kind of user input. The following web page has additional details: + https://developers.google.com/web/updates/2017/09/autoplay-policy-changes. Starting the device + may fail if you try to start playback without first handling some kind of user input. -12. Miscellaneous Notes +16. Optimization Tips +===================== + +16.1. High Level API +-------------------- +- If a sound does not require doppler or pitch shifting, consider disabling pitching by + initializing the sound with the `MA_SOUND_FLAG_NO_PITCH` flag. +- If a sound does not require spatialization, disable it by initialzing the sound with the + `MA_SOUND_FLAG_NO_SPATIALIZATION` flag. It can be renabled again post-initialization with + `ma_sound_set_spatialization_enabled()`. + + + +17. Miscellaneous Notes ======================= -- Automatic stream routing is enabled on a per-backend basis. Support is explicitly enabled for WASAPI and Core Audio, however other backends such as - PulseAudio may naturally support it, though not all have been tested. -- The contents of the output buffer passed into the data callback will always be pre-initialized to silence unless the `noPreZeroedOutputBuffer` config variable - in `ma_device_config` is set to true, in which case it'll be undefined which will require you to write something to the entire buffer. -- By default miniaudio will automatically clip samples. This only applies when the playback sample format is configured as `ma_format_f32`. If you are doing - clipping yourself, you can disable this overhead by setting `noClip` to true in the device config. -- The sndio backend is currently only enabled on OpenBSD builds. -- The audio(4) backend is supported on OpenBSD, but you may need to disable sndiod before you can use it. +- Automatic stream routing is enabled on a per-backend basis. Support is explicitly enabled for + WASAPI and Core Audio, however other backends such as PulseAudio may naturally support it, though + not all have been tested. +- The contents of the output buffer passed into the data callback will always be pre-initialized to + silence unless the `noPreSilencedOutputBuffer` config variable in `ma_device_config` is set to + true, in which case it'll be undefined which will require you to write something to the entire + buffer. +- By default miniaudio will automatically clip samples. This only applies when the playback sample + format is configured as `ma_format_f32`. If you are doing clipping yourself, you can disable this + overhead by setting `noClip` to true in the device config. - Note that GCC and Clang requires `-msse2`, `-mavx2`, etc. for SIMD optimizations. -- When compiling with VC6 and earlier, decoding is restricted to files less than 2GB in size. This is due to 64-bit file APIs not being available. +- The sndio backend is currently only enabled on OpenBSD builds. +- The audio(4) backend is supported on OpenBSD, but you may need to disable sndiod before you can + use it. +- When compiling with VC6 and earlier, decoding is restricted to files less than 2GB in size. This + is due to 64-bit file APIs not being available. */ - diff --git a/vendor/miniaudio/effects.odin b/vendor/miniaudio/effects.odin new file mode 100644 index 000000000..e86d670d9 --- /dev/null +++ b/vendor/miniaudio/effects.odin @@ -0,0 +1,300 @@ +package miniaudio + +import c "core:c/libc" + +when ODIN_OS == .Windows { + foreign import lib "lib/miniaudio.lib" +} else when ODIN_OS == .Linux { + foreign import lib "lib/miniaudio.a" +} else { + foreign import lib "system:miniaudio" +} + +/* +Delay +*/ +delay_config :: struct { + channels: u32, + sampleRate: u32, + delayInFrames: u32, + delayStart: b32, /* Set to true to delay the start of the output; false otherwise. */ + wet: f32, /* 0..1. Default = 1. */ + dry: f32, /* 0..1. Default = 1. */ + decay: f32, /* 0..1. Default = 0 (no feedback). Feedback decay. Use this for echo. */ +} + +delay :: struct { + config: delay_config, + cursor: u32, /* Feedback is written to this cursor. Always equal or in front of the read cursor. */ + bufferSizeInFrames: u32, /* The maximum of config.startDelayInFrames and config.feedbackDelayInFrames. */ + pBuffer: [^]f32, +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + delay_config_init :: proc(channels, sampleRate, delayInFrames: u32, decay: f32) -> delay_config --- + + delay_init :: proc(pConfig: ^delay_config, pAllocationCallbacks: ^allocation_callbacks, pDelay: ^delay) -> result --- + delay_uninit :: proc(pDelay: ^delay, pAllocationCallbacks: ^allocation_callbacks) --- + delay_process_pcm_frames :: proc(pDelay: ^delay, pFramesOut, pFramesIn: rawptr, frameCount: u32) -> result --- + delay_set_wet :: proc(pDelay: ^delay, value: f32) --- + delay_get_wet :: proc(pDelay: ^delay) -> f32 --- + delay_set_dry :: proc(pDelay: ^delay, value: f32) --- + delay_get_dry :: proc(pDelay: ^delay) -> f32 --- + delay_set_decay :: proc(pDelay: ^delay, value: f32) --- + delay_get_decay :: proc(pDelay: ^delay) -> f32 --- +} + + +/* Gainer for smooth volume changes. */ +gainer_config :: struct { + channels: u32, + smoothTimeInFrames: u32, +} + +gainer :: struct { + config: gainer_config, + t: u32, + pOldGains: [^]f32, + pNewGains: [^]f32, + + /* Memory management. */ + _pHeap: rawptr, + _ownsHeap: b32, +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + gainer_config_init :: proc(channels, smoothTimeInFrames: u32) -> gainer_config --- + + gainer_get_heap_size :: proc(pConfig: ^gainer_config, pHeapSizeInBytes: ^c.size_t) -> result --- + gainer_init_preallocated :: proc(pConfig: ^gainer_config, pHeap: rawptr, pGainer: ^gainer) -> result --- + gainer_init :: proc(pConfig: ^gainer_config, pAllocationCallbacks: ^allocation_callbacks, pGainer: ^gainer) -> result --- + gainer_uninit :: proc(pGainer: ^gainer, pAllocationCallbacks: ^allocation_callbacks) --- + gainer_process_pcm_frames :: proc(pGainer: ^gainer, pFramesOut: rawptr, pFramesIn: rawptr, frameCount: u64) -> result --- + gainer_set_gain :: proc(pGainer: ^gainer, newGain: f32) -> result --- + gainer_set_gains :: proc(pGainer: ^gainer, pNewGains: [^]f32) -> result --- +} + + +/* Stereo panner. */ +pan_mode :: enum c.int { + balance = 0, /* Does not blend one side with the other. Technically just a balance. Compatible with other popular audio engines and therefore the default. */ + pan, /* A true pan. The sound from one side will "move" to the other side and blend with it. */ +} + +panner_config :: struct { + format: format, + channels: u32, + mode: pan_mode, + pan: f32, +} + +panner :: struct { + format: format, + channels: u32, + mode: pan_mode, + pan: f32, /* -1..1 where 0 is no pan, -1 is left side, +1 is right side. Defaults to 0. */ +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + panner_config_init :: proc(format: format, channels: u32) -> panner_config --- + + panner_init :: proc(pConfig: ^panner_config, pPanner: ^panner) -> result --- + panner_process_pcm_frames :: proc(pPanner: ^panner, pFramesOut, pFramesIn: rawptr, frameCount: u64) -> result --- + panner_set_mode :: proc(pPanner: ^panner, mode: pan_mode) --- + panner_get_mode :: proc(pPanner: ^panner) -> pan_mode --- + panner_set_pan :: proc(pPanner: ^panner, pan: f32) --- + panner_get_pan :: proc(pPanner: ^panner) -> f32 --- +} + + +/* Fader. */ +fader_config :: struct { + format: format, + channels: u32, + sampleRate: u32, +} + +fader :: struct { + config: fader_config, + volumeBeg: f32, /* If volumeBeg and volumeEnd is equal to 1, no fading happens (ma_fader_process_pcm_frames() will run as a passthrough). */ + volumeEnd: f32, + lengthInFrames: u64, /* The total length of the fade. */ + cursorInFrames: u64, /* The current time in frames. Incremented by ma_fader_process_pcm_frames(). */ +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + fader_config_init :: proc(format: format, channels, sampleRate: u32) -> fader_config --- + + fader_init :: proc(pConfig: ^fader_config, pFader: ^fader) -> result --- + fader_process_pcm_frames :: proc(pFader: ^fader, pFramesOut, pFramesIn: rawptr, frameCount: u64) -> result --- + fader_get_data_format :: proc(pFader: ^fader, pFormat: ^format, pChannels, pSampleRate: ^u32) --- + fader_set_fade :: proc(pFader: ^fader, volumeBeg, volumeEnd: f32, lengthInFrames: u64) --- + fader_get_current_volume :: proc(pFader: ^fader) -> f32 --- +} + + +/* Spatializer. */ +vec3f :: struct { + x: f32, + y: f32, + z: f32, +} + +attenuation_model :: enum c.int { + none, /* No distance attenuation and no spatialization. */ + inverse, /* Equivalent to OpenAL's AL_INVERSE_DISTANCE_CLAMPED. */ + linear, /* Linear attenuation. Equivalent to OpenAL's AL_LINEAR_DISTANCE_CLAMPED. */ + exponential, /* Exponential attenuation. Equivalent to OpenAL's AL_EXPONENT_DISTANCE_CLAMPED. */ +} + +positioning :: enum c.int { + absolute, + relative, +} + +handedness :: enum c.int { + right, + left, +} + +spatializer_listener_config :: struct { + channelsOut: u32, + pChannelMapOut: [^]channel, + handedness: handedness, /* Defaults to right. Forward is -1 on the Z axis. In a left handed system, forward is +1 on the Z axis. */ + coneInnerAngleInRadians: f32, + coneOuterAngleInRadians: f32, + coneOuterGain: f32, + speedOfSound: f32, + worldUp: vec3f, +} + +spatializer_listener :: struct { + config: spatializer_listener_config, + position: vec3f, /* The absolute position of the listener. */ + direction: vec3f, /* The direction the listener is facing. The world up vector is config.worldUp. */ + velocity: vec3f, + isEnabled: b32, + + /* Memory management. */ + _ownsHeap: b32, + _pHeap: rawptr, +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + spatializer_listener_config_init :: proc(channelsOut: u32) -> spatializer_listener_config --- + + spatializer_listener_get_heap_size :: proc(pConfig: ^spatializer_listener_config, pHeapSizeInBytes: ^c.size_t) -> result --- + spatializer_listener_init_preallocated :: proc(pConfig: ^spatializer_listener_config, pHeap: rawptr, pListener: ^spatializer_listener) -> result --- + spatializer_listener_init :: proc(pConfig: ^spatializer_listener_config, pAllocationCallbacks: ^allocation_callbacks, pListener: ^spatializer_listener) -> result --- + spatializer_listener_uninit :: proc(pListener: ^spatializer_listener, pAllocationCallbacks: ^allocation_callbacks) --- + spatializer_listener_get_channel_map :: proc(pListener: ^spatializer_listener) -> ^channel --- + spatializer_listener_set_cone :: proc(pListener: ^spatializer_listener, innerAngleInRadians, outerAngleInRadians, outerGain: f32) --- + spatializer_listener_get_cone :: proc(pListener: ^spatializer_listener, pInnerAngleInRadians, pOuterAngleInRadians, pOuterGain: ^f32) --- + spatializer_listener_set_position :: proc(pListener: ^spatializer_listener, x, y, z: f32) --- + spatializer_listener_get_position :: proc(pListener: ^spatializer_listener) -> vec3f --- + spatializer_listener_set_direction :: proc(pListener: ^spatializer_listener, x, y, z: f32) --- + spatializer_listener_get_direction :: proc(pListener: ^spatializer_listener) -> vec3f --- + spatializer_listener_set_velocity :: proc(pListener: ^spatializer_listener, x, y, z: f32) --- + spatializer_listener_get_velocity :: proc(pListener: ^spatializer_listener) -> vec3f --- + spatializer_listener_set_speed_of_sound :: proc(pListener: ^spatializer_listener, speedOfSound: f32) --- + spatializer_listener_get_speed_of_sound :: proc(pListener: ^spatializer_listener) -> f32 --- + spatializer_listener_set_world_up :: proc(pListener: ^spatializer_listener, x, y, z: f32) --- + spatializer_listener_get_world_up :: proc(pListener: ^spatializer_listener) -> vec3f --- + spatializer_listener_set_enabled :: proc(pListener: ^spatializer_listener, isEnabled: b32) --- + spatializer_listener_is_enabled :: proc(pListener: ^spatializer_listener) -> b32 --- +} + +spatializer_config :: struct { + channelsIn: u32, + channelsOut: u32, + pChannelMapIn: [^]channel, + attenuationModel: attenuation_model, + positioning: positioning, + handedness: handedness, /* Defaults to right. Forward is -1 on the Z axis. In a left handed system, forward is +1 on the Z axis. */ + minGain: f32, + maxGain: f32, + minDistance: f32, + maxDistance: f32, + rolloff: f32, + coneInnerAngleInRadians: f32, + coneOuterAngleInRadians: f32, + coneOuterGain: f32, + dopplerFactor: f32, /* Set to 0 to disable doppler effect. */ + directionalAttenuationFactor: f32, /* Set to 0 to disable directional attenuation. */ + gainSmoothTimeInFrames: u32, /* When the gain of a channel changes during spatialization, the transition will be linearly interpolated over this number of frames. */ +} + +spatializer :: struct { + channelsIn: u32, + channelsOut: u32, + pChannelMapIn: [^]channel, + attenuationModel: attenuation_model, + positioning: positioning, + handedness: handedness, /* Defaults to right. Forward is -1 on the Z axis. In a left handed system, forward is +1 on the Z axis. */ + minGain: f32, + maxGain: f32, + minDistance: f32, + maxDistance: f32, + rolloff: f32, + coneInnerAngleInRadians: f32, + coneOuterAngleInRadians: f32, + coneOuterGain: f32, + dopplerFactor: f32, /* Set to 0 to disable doppler effect. */ + directionalAttenuationFactor: f32, /* Set to 0 to disable directional attenuation. */ + gainSmoothTimeInFrames: u32, /* When the gain of a channel changes during spatialization, the transition will be linearly interpolated over this number of frames. */ + position: vec3f, + direction: vec3f, + velocity: vec3f, /* For doppler effect. */ + dopplerPitch: f32, /* Will be updated by ma_spatializer_process_pcm_frames() and can be used by higher level functions to apply a pitch shift for doppler effect. */ + gainer: gainer, /* For smooth gain transitions. */ + pNewChannelGainsOut: [^]f32, /* An offset of _pHeap. Used by ma_spatializer_process_pcm_frames() to store new channel gains. The number of elements in this array is equal to config.channelsOut. */ + + /* Memory management. */ + _pHeap: rawptr, + _ownsHeap: b32, +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + spatializer_config_init :: proc(channelsIn, channelsOut: u32) -> spatializer_config --- + + spatializer_get_heap_size :: proc(pConfig: ^spatializer_config, pHeapSizeInBytes: ^c.size_t) -> result --- + spatializer_init_preallocated :: proc(pConfig: ^spatializer_config, pHeap: rawptr, pSpatializer: ^spatializer) -> result --- + spatializer_init :: proc(pConfig: ^spatializer_config, pAllocationCallbacks: ^allocation_callbacks, pSpatializer: ^spatializer) -> result --- + spatializer_uninit :: proc(pSpatializer: ^spatializer, pAllocationCallbacks: ^allocation_callbacks) --- + spatializer_process_pcm_frames :: proc(pSpatializer: ^spatializer, pListener: ^spatializer_listener, pFramesOut, pFramesIn: rawptr, frameCount: u64) -> result --- + spatializer_get_input_channels :: proc(pSpatializer: ^spatializer) -> u32 --- + spatializer_get_output_channels :: proc(pSpatializer: ^spatializer) -> u32 --- + spatializer_set_attenuation_model :: proc(pSpatializer: ^spatializer, attenuationModel: attenuation_model) --- + spatializer_get_attenuation_model :: proc(pSpatializer: ^spatializer) -> attenuation_model --- + spatializer_set_positioning :: proc(pSpatializer: ^spatializer, positioning: positioning) --- + spatializer_get_positioning :: proc(pSpatializer: ^spatializer) -> positioning --- + spatializer_set_rolloff :: proc(pSpatializer: ^spatializer, rolloff: f32) --- + spatializer_get_rolloff :: proc(pSpatializer: ^spatializer) -> f32 --- + spatializer_set_min_gain :: proc(pSpatializer: ^spatializer, minGain: f32) --- + spatializer_get_min_gain :: proc(pSpatializer: ^spatializer) -> f32 --- + spatializer_set_max_gain :: proc(pSpatializer: ^spatializer, maxGain: f32) --- + spatializer_get_max_gain :: proc(pSpatializer: ^spatializer) -> f32 --- + spatializer_set_min_distance :: proc(pSpatializer: ^spatializer, minDistance: f32) --- + spatializer_get_min_distance :: proc(pSpatializer: ^spatializer) -> f32 --- + spatializer_set_max_distance :: proc(pSpatializer: ^spatializer, maxDistance: f32) --- + spatializer_get_max_distance :: proc(pSpatializer: ^spatializer) -> f32 --- + spatializer_set_cone :: proc(pSpatializer: ^spatializer, innerAngleInRadians, outerAngleInRadians, outerGain: f32) --- + spatializer_get_cone :: proc(pSpatializer: ^spatializer, pInnerAngleInRadians, pOuterAngleInRadians, pOuterGain: ^f32) --- + spatializer_set_doppler_factor :: proc(pSpatializer: ^spatializer, dopplerFactor: f32) --- + spatializer_get_doppler_factor :: proc(pSpatializer: ^spatializer) -> f32 --- + spatializer_set_directional_attenuation_factor :: proc(pSpatializer: ^spatializer, directionalAttenuationFactor: f32) --- + spatializer_get_directional_attenuation_factor :: proc(pSpatializer: ^spatializer) -> f32 --- + spatializer_set_position :: proc(pSpatializer: ^spatializer, x, y, z: f32) --- + spatializer_get_position :: proc(pSpatializer: ^spatializer) -> vec3f --- + spatializer_set_direction :: proc(pSpatializer: ^spatializer, x, y, z: f32) --- + spatializer_get_direction :: proc(pSpatializer: ^spatializer) -> vec3f --- + spatializer_set_velocity :: proc(pSpatializer: ^spatializer, x, y, z: f32) --- + spatializer_get_velocity :: proc(pSpatializer: ^spatializer) -> vec3f --- + spatializer_get_relative_position_and_direction :: proc(pSpatializer: ^spatializer, pListener: ^spatializer_listener, pRelativePos, pRelativeDir: ^vec3f) --- +} diff --git a/vendor/miniaudio/encoding.odin b/vendor/miniaudio/encoding.odin index 9b84108dc..ee396466a 100644 --- a/vendor/miniaudio/encoding.odin +++ b/vendor/miniaudio/encoding.odin @@ -19,14 +19,14 @@ Encoders do not perform any format conversion for you. If your target format doe ************************************************************************************************************************************************************/ -encoder_write_proc :: proc "c" (pEncoder: ^encoder, pBufferIn: rawptr, bytesToWrite: c.size_t) -> c.size_t /* Returns the number of bytes written. */ -encoder_seek_proc :: proc "c" (pEncoder: ^encoder, byteOffset: c.int, origin: seek_origin) -> b32 +encoder_write_proc :: proc "c" (pEncoder: ^encoder, pBufferIn: rawptr, bytesToWrite: c.size_t, pBytesWritten: ^c.size_t) -> result +encoder_seek_proc :: proc "c" (pEncoder: ^encoder, offset: i64, origin: seek_origin) -> result encoder_init_proc :: proc "c" (pEncoder: ^encoder) -> result -encoder_uninit_proc :: proc "c" (pEncoder: ^encoder) -encoder_write_pcm_frames_proc :: proc "c" (pEncoder: ^encoder, pFramesIn: rawptr, frameCount: u64) -> u64 +encoder_uninit_proc :: proc "c" (pEncoder: ^encoder) +encoder_write_pcm_frames_proc :: proc "c" (pEncoder: ^encoder, pFramesIn: rawptr, frameCount: u64, pFramesWritten: ^u64) -> result encoder_config :: struct { - resourceFormat: resource_format, + encodingFormat: encoding_format, format: format, channels: u32, sampleRate: u32, @@ -42,16 +42,23 @@ encoder :: struct { onWritePCMFrames: encoder_write_pcm_frames_proc, pUserData: rawptr, pInternalEncoder: rawptr, /* <-- The drwav/drflac/stb_vorbis/etc. objects. */ - pFile: rawptr, /* FILE*. Only used when initialized with ma_encoder_init_file(). */ + data: struct #raw_union { + vfs: struct { + pVFS: ^vfs, + file: vfs_file, + }, + }, } @(default_calling_convention="c", link_prefix="ma_") foreign lib { - encoder_config_init :: proc(resourceFormat: resource_format, format: format, channels: u32, sampleRate: u32) -> encoder_config --- + encoder_config_init :: proc(encodingFormat: encoding_format, format: format, channels: u32, sampleRate: u32) -> encoder_config --- encoder_init :: proc(onWrite: encoder_write_proc, onSeek: encoder_seek_proc, pUserData: rawptr, pConfig: ^encoder_config, pEncoder: ^encoder) -> result --- + encoder_init_vfs :: proc(pVFS: ^vfs, pFilePath: cstring, pConfig: ^encoder_config, pEncoder: ^encoder) -> result --- + encoder_init_vfs_w :: proc(pVFS: ^vfs, pFilePath: [^]c.wchar_t, pConfig: ^encoder_config, pEncoder: ^encoder) -> result --- encoder_init_file :: proc(pFilePath: cstring, pConfig: ^encoder_config, pEncoder: ^encoder) -> result --- encoder_init_file_w :: proc(pFilePath: [^]c.wchar_t, pConfig: ^encoder_config, pEncoder: ^encoder) -> result --- encoder_uninit :: proc(pEncoder: ^encoder) --- - encoder_write_pcm_frames :: proc(pEncoder: ^encoder, FramesIn: rawptr, frameCount: u64) -> u64 --- + encoder_write_pcm_frames :: proc(pEncoder: ^encoder, FramesIn: rawptr, frameCount: u64, pFramesWritten: ^u64) -> result --- } diff --git a/vendor/miniaudio/engine.odin b/vendor/miniaudio/engine.odin new file mode 100644 index 000000000..935d54744 --- /dev/null +++ b/vendor/miniaudio/engine.odin @@ -0,0 +1,341 @@ +package miniaudio + +import "core:c" + +when ODIN_OS == .Windows { + foreign import lib "lib/miniaudio.lib" +} else when ODIN_OS == .Linux { + foreign import lib "lib/miniaudio.a" +} else { + foreign import lib "system:miniaudio" +} + +/************************************************************************************************************************************************************ + +Engine + +************************************************************************************************************************************************************/ + +/* Sound flags. */ +sound_flags :: enum c.int { + STREAM = 0x00000001, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM */ + DECODE = 0x00000002, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE */ + ASYNC = 0x00000004, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC */ + WAIT_INIT = 0x00000008, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT */ + NO_DEFAULT_ATTACHMENT = 0x00000010, /* Do not attach to the endpoint by default. Useful for when setting up nodes in a complex graph system. */ + NO_PITCH = 0x00000020, /* Disable pitch shifting with ma_sound_set_pitch() and ma_sound_group_set_pitch(). This is an optimization. */ + NO_SPATIALIZATION = 0x00000040, /* Disable spatialization. */ +} + +ENGINE_MAX_LISTENERS :: 4 + +LISTENER_INDEX_CLOSEST :: 255 + +engine_node_type :: enum c.int { + sound, + group, +} + +engine_node_config :: struct { + pEngine: ^engine, + type: engine_node_type, + channelsIn: u32, + channelsOut: u32, + sampleRate: u32, /* Only used when the type is set to ma_engine_node_type_sound. */ + isPitchDisabled: b8, /* Pitching can be explicitly disable with MA_SOUND_FLAG_NO_PITCH to optimize processing. */ + isSpatializationDisabled: b8, /* Spatialization can be explicitly disabled with MA_SOUND_FLAG_NO_SPATIALIZATION. */ + pinnedListenerIndex: u8, /* The index of the listener this node should always use for spatialization. If set to MA_LISTENER_INDEX_CLOSEST the engine will use the closest listener. */ +} + +/* Base node object for both ma_sound and ma_sound_group. */ +engine_node :: struct { + baseNode: node_base, /* Must be the first member for compatiblity with the ma_node API. */ + pEngine: ^engine, /* A pointer to the engine. Set based on the value from the config. */ + sampleRate: u32, /* The sample rate of the input data. For sounds backed by a data source, this will be the data source's sample rate. Otherwise it'll be the engine's sample rate. */ + fader: fader, + resampler: linear_resampler, /* For pitch shift. */ + spatializer: spatializer, + panner: panner, + pitch: f32, /*atomic*/ + oldPitch: f32, /* For determining whether or not the resampler needs to be updated to reflect the new pitch. The resampler will be updated on the mixing thread. */ + oldDopplerPitch: f32, /* For determining whether or not the resampler needs to be updated to take a new doppler pitch into account. */ + isPitchDisabled: b32, /*atomic*/ /* When set to true, pitching will be disabled which will allow the resampler to be bypassed to save some computation. */ + isSpatializationDisabled: b32, /*atomic*/ /* Set to false by default. When set to false, will not have spatialisation applied. */ + pinnedListenerIndex: u32, /*atomic*/ /* The index of the listener this node should always use for spatialization. If set to MA_LISTENER_INDEX_CLOSEST the engine will use the closest listener. */ + + /* Memory management. */ + _ownsHeap: b8, + _pHeap: rawptr, +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + engine_node_config_init :: proc(pEngine: ^engine, type: engine_node_type, flags: u32) -> engine_node_config --- + + engine_node_get_heap_size :: proc(pConfig: ^engine_node_config, pHeapSizeInBytes: ^c.size_t) -> result --- + engine_node_init_preallocated :: proc(pConfig: ^engine_node_config, pHeap: rawptr, pEngineNode: ^engine_node) -> result --- + engine_node_init :: proc(pConfig: ^engine_node_config, pAllocationCallbacks: ^allocation_callbacks, pEngineNode: ^engine_node) -> result --- + engine_node_uninit :: proc(pEngineNode: ^engine_node, pAllocationCallbacks: ^allocation_callbacks) --- +} + + +SOUND_SOURCE_CHANNEL_COUNT :: 0xFFFFFFFF + +sound_config :: struct { + pFilePath: cstring, /* Set this to load from the resource manager. */ + pFilePathW: [^]c.wchar_t, /* Set this to load from the resource manager. */ + pDataSource: ^data_source, /* Set this to load from an existing data source. */ + pInitialAttachment: ^node, /* If set, the sound will be attached to an input of this node. This can be set to a ma_sound. If set to NULL, the sound will be attached directly to the endpoint unless MA_SOUND_FLAG_NO_DEFAULT_ATTACHMENT is set in `flags`. */ + initialAttachmentInputBusIndex: u32, /* The index of the input bus of pInitialAttachment to attach the sound to. */ + channelsIn: u32, /* Ignored if using a data source as input (the data source's channel count will be used always). Otherwise, setting to 0 will cause the engine's channel count to be used. */ + channelsOut: u32, /* Set this to 0 (default) to use the engine's channel count. Set to MA_SOUND_SOURCE_CHANNEL_COUNT to use the data source's channel count (only used if using a data source as input). */ + flags: u32, /* A combination of MA_SOUND_FLAG_* flags. */ + initialSeekPointInPCMFrames: u64, /* Initializes the sound such that it's seeked to this location by default. */ + rangeBegInPCMFrames: u64, + rangeEndInPCMFrames: u64, + loopPointBegInPCMFrames: u64, + loopPointEndInPCMFrames: u64, + isLooping: b32, + pDoneFence: ^fence, /* Released when the resource manager has finished decoding the entire sound. Not used with streams. */ +} + +sound :: struct { + engineNode: engine_node, /* Must be the first member for compatibility with the ma_node API. */ + pDataSource: ^data_source, + seekTarget: u64, /*atomic*/ /* The PCM frame index to seek to in the mixing thread. Set to (~(ma_uint64)0) to not perform any seeking. */ + atEnd: b32, /*atomic*/ + ownsDataSource: b8, + + /* + We're declaring a resource manager data source object here to save us a malloc when loading a + sound via the resource manager, which I *think* will be the most common scenario. + */ + pResourceManagerDataSource: ^resource_manager_data_source, +} + +/* Structure specifically for sounds played with ma_engine_play_sound(). Making this a separate structure to reduce overhead. */ +sound_inlined :: struct { + sound: sound, + pNext: ^sound_inlined, + pPrev: ^sound_inlined, +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + sound_config_init :: proc() -> sound_config --- + + sound_init_from_file :: proc(pEngine: ^engine, pFilePath: cstring, flags: u32, pGroup: ^sound_group, pDoneFence: ^fence, pSound: ^sound) -> result --- + sound_init_from_file_w :: proc(pEngine: ^engine, pFilePath: [^]c.wchar_t, flags: u32, pGroup: ^sound_group, pDoneFence: ^fence, pSound: ^sound) -> result --- + sound_init_copy :: proc(pEngine: ^engine, pExistingSound: ^sound, flags: u32, pGroup: ^sound_group, pSound: ^sound) -> result --- + sound_init_from_data_source :: proc(pEngine: ^engine, pDataSource: ^data_source, flags: u32, pGroup: ^sound_group, pSound: ^sound) -> result --- + sound_init_ex :: proc(pEngine: ^engine, pConfig: ^sound_config, pSound: ^sound) -> result --- + sound_uninit :: proc(pSound: ^sound) --- + sound_get_engine :: proc(pSound: ^sound) -> ^engine --- + sound_get_data_source :: proc(pSound: ^sound) -> ^data_source --- + sound_start :: proc(pSound: ^sound) -> result --- + sound_stop :: proc(pSound: ^sound) -> result --- + sound_set_volume :: proc(pSound: ^sound, volume: f32) --- + sound_get_volume :: proc(pSound: ^sound) -> f32 --- + sound_set_pan :: proc(pSound: ^sound, pan: f32) --- + sound_get_pan :: proc(pSound: ^sound) -> f32 --- + sound_set_pan_mode :: proc(pSound: ^sound, panMode: pan_mode) --- + sound_get_pan_mode :: proc(pSound: ^sound) -> pan_mode --- + sound_set_pitch :: proc(pSound: ^sound, pitch: f32) --- + sound_get_pitch :: proc(pSound: ^sound) -> f32 --- + sound_set_spatialization_enabled :: proc(pSound: ^sound, enabled: b32) --- + sound_is_spatialization_enabled :: proc(pSound: ^sound) -> b32 --- + sound_set_pinned_listener_index :: proc(pSound: ^sound, listenerIndex: u32) --- + sound_get_pinned_listener_index :: proc(pSound: ^sound) -> u32 --- + sound_get_listener_index :: proc(pSound: ^sound) -> u32 --- + sound_get_direction_to_listener :: proc(pSound: ^sound) -> vec3f --- + sound_set_position :: proc(pSound: ^sound, x, y, z: f32) --- + sound_get_position :: proc(pSound: ^sound) -> vec3f --- + sound_set_direction :: proc(pSound: ^sound, x, y, z: f32) --- + sound_get_direction :: proc(pSound: ^sound) -> vec3f --- + sound_set_velocity :: proc(pSound: ^sound, x, y, z: f32) --- + sound_get_velocity :: proc(pSound: ^sound) -> vec3f --- + sound_set_attenuation_model :: proc(pSound: ^sound, attenuationModel: attenuation_model) --- + sound_get_attenuation_model :: proc(pSound: ^sound) -> attenuation_model --- + sound_set_positioning :: proc(pSound: ^sound, positioning: positioning) --- + sound_get_positioning :: proc(pSound: ^sound) -> positioning --- + sound_set_rolloff :: proc(pSound: ^sound, rolloff: f32) --- + sound_get_rolloff :: proc(pSound: ^sound) -> f32 --- + sound_set_min_gain :: proc(pSound: ^sound, minGain: f32) --- + sound_get_min_gain :: proc(pSound: ^sound) -> f32 --- + sound_set_max_gain :: proc(pSound: ^sound, maxGain: f32) --- + sound_get_max_gain :: proc(pSound: ^sound) -> f32 --- + sound_set_min_distance :: proc(pSound: ^sound, minDistance: f32) --- + sound_get_min_distance :: proc(pSound: ^sound) -> f32 --- + sound_set_max_distance :: proc(pSound: ^sound, maxDistance: f32) --- + sound_get_max_distance :: proc(pSound: ^sound) -> f32 --- + sound_set_cone :: proc(pSound: ^sound, innerAngleInRadians, outerAngleInRadians, outerGain: f32) --- + sound_get_cone :: proc(pSound: ^sound, pInnerAngleInRadians, pOuterAngleInRadians, pOuterGain: ^f32) --- + sound_set_doppler_factor :: proc(pSound: ^sound, dopplerFactor: f32) --- + sound_get_doppler_factor :: proc(pSound: ^sound) -> f32 --- + sound_set_directional_attenuation_factor :: proc(pSound: ^sound, directionalAttenuationFactor: f32) --- + sound_get_directional_attenuation_factor :: proc(pSound: ^sound) -> f32 --- + sound_set_fade_in_pcm_frames :: proc(pSound: ^sound, volumeBeg, volumeEnd: f32, fadeLengthInFrames: u64) --- + sound_set_fade_in_milliseconds :: proc(pSound: ^sound, volumeBeg, volumeEnd: f32, fadeLengthInMilliseconds: u64) --- + sound_get_current_fade_volume :: proc(pSound: ^sound) -> f32 --- + sound_set_start_time_in_pcm_frames :: proc(pSound: ^sound, absoluteGlobalTimeInFrames: u64) --- + sound_set_start_time_in_milliseconds :: proc(pSound: ^sound, absoluteGlobalTimeInMilliseconds: u64) --- + sound_set_stop_time_in_pcm_frames :: proc(pSound: ^sound, absoluteGlobalTimeInFrames: u64) --- + sound_set_stop_time_in_milliseconds :: proc(pSound: ^sound, absoluteGlobalTimeInMilliseconds: u64) --- + sound_is_playing :: proc(pSound: ^sound) -> b32 --- + sound_get_time_in_pcm_frames :: proc(pSound: ^sound) -> u64 --- + sound_set_looping :: proc(pSound: ^sound, isLooping: b32) --- + sound_is_looping :: proc(pSound: ^sound) -> b32 --- + sound_at_end :: proc(pSound: ^sound) -> b32 --- + sound_seek_to_pcm_frame :: proc(pSound: ^sound, frameIndex: u64) -> result --- /* Just a wrapper around ma_data_source_seek_to_pcm_frame(). */ + sound_get_data_format :: proc(pSound: ^sound, pFormat: ^format, pChannels, pSampleRate: ^u32, pChannelMap: ^channel, channelMapCap: c.size_t) -> result --- + sound_get_cursor_in_pcm_frames :: proc(pSound: ^sound, pCursor: ^u64) -> result --- + sound_get_length_in_pcm_frames :: proc(pSound: ^sound, pLength: ^u64) -> result --- + sound_get_cursor_in_seconds :: proc(pSound: ^sound, pCursor: ^f32) -> result --- + sound_get_length_in_seconds :: proc(pSound: ^sound, pLength: ^f32) -> result --- +} + + +/* A sound group is just a sound. */ +sound_group_config :: distinct sound_config +sound_group :: distinct sound + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + sound_group_config_init :: proc() -> sound_group_config --- + + sound_group_init :: proc(pEngine: ^engine, flags: u32, pParentGroup, pGroup: ^sound_group) -> result --- + sound_group_init_ex :: proc(pEngine: ^engine, pConfig: ^sound_group_config, pGroup: ^sound_group) -> result --- + sound_group_uninit :: proc(pGroup: ^sound_group) --- + sound_group_get_engine :: proc(pGroup: ^sound_group) -> ^engine --- + sound_group_start :: proc(pGroup: ^sound_group) -> result --- + sound_group_stop :: proc(pGroup: ^sound_group) -> result --- + sound_group_set_volume :: proc(pGroup: ^sound_group, volume: f32) --- + sound_group_get_volume :: proc(pGroup: ^sound_group) -> f32 --- + sound_group_set_pan :: proc(pGroup: ^sound_group, pan: f32) --- + sound_group_get_pan :: proc(pGroup: ^sound_group) -> f32 --- + sound_group_set_pan_mode :: proc(pGroup: ^sound_group, panMode: pan_mode) --- + sound_group_get_pan_mode :: proc(pGroup: ^sound_group) -> pan_mode --- + sound_group_set_pitch :: proc(pGroup: ^sound_group, pitch: f32) --- + sound_group_get_pitch :: proc(pGroup: ^sound_group) -> f32 --- + sound_group_set_spatialization_enabled :: proc(pGroup: ^sound_group, enabled: b32) --- + sound_group_is_spatialization_enabled :: proc(pGroup: ^sound_group) -> b32 --- + sound_group_set_pinned_listener_index :: proc(pGroup: ^sound_group, listenerIndex: u32) --- + sound_group_get_pinned_listener_index :: proc(pGroup: ^sound_group) -> u32 --- + sound_group_get_listener_index :: proc(pGroup: ^sound_group) -> u32 --- + sound_group_get_direction_to_listener :: proc(pGroup: ^sound_group) -> vec3f --- + sound_group_set_position :: proc(pGroup: ^sound_group, x, y, z: f32) --- + sound_group_get_position :: proc(pGroup: ^sound_group) -> vec3f --- + sound_group_set_direction :: proc(pGroup: ^sound_group, x, y, z: f32) --- + sound_group_get_direction :: proc(pGroup: ^sound_group) -> vec3f --- + sound_group_set_velocity :: proc(pGroup: ^sound_group, x, y, z: f32) --- + sound_group_get_velocity :: proc(pGroup: ^sound_group) -> vec3f --- + sound_group_set_attenuation_model :: proc(pGroup: ^sound_group, attenuationModel: attenuation_model) --- + sound_group_get_attenuation_model :: proc(pGroup: ^sound_group) -> attenuation_model --- + sound_group_set_positioning :: proc(pGroup: ^sound_group, positioning: positioning) --- + sound_group_get_positioning :: proc(pGroup: ^sound_group) -> positioning --- + sound_group_set_rolloff :: proc(pGroup: ^sound_group, rolloff: f32) --- + sound_group_get_rolloff :: proc(pGroup: ^sound_group) -> f32 --- + sound_group_set_min_gain :: proc(pGroup: ^sound_group, minGain: f32) --- + sound_group_get_min_gain :: proc(pGroup: ^sound_group) -> f32 --- + sound_group_set_max_gain :: proc(pGroup: ^sound_group, maxGain: f32) --- + sound_group_get_max_gain :: proc(pGroup: ^sound_group) -> f32 --- + sound_group_set_min_distance :: proc(pGroup: ^sound_group, minDistance: f32) --- + sound_group_get_min_distance :: proc(pGroup: ^sound_group) -> f32 --- + sound_group_set_max_distance :: proc(pGroup: ^sound_group, maxDistance: f32) --- + sound_group_get_max_distance :: proc(pGroup: ^sound_group) -> f32 --- + sound_group_set_cone :: proc(pGroup: ^sound_group, innerAngleInRadians, outerAngleInRadians, outerGain: f32) --- + sound_group_get_cone :: proc(pGroup: ^sound_group, pInnerAngleInRadians, pOuterAngleInRadians, pOuterGain: ^f32) --- + sound_group_set_doppler_factor :: proc(pGroup: ^sound_group, dopplerFactor: f32) --- + sound_group_get_doppler_factor :: proc(pGroup: ^sound_group) -> f32 --- + sound_group_set_directional_attenuation_factor :: proc(pGroup: ^sound_group, directionalAttenuationFactor: f32) --- + sound_group_get_directional_attenuation_factor :: proc(pGroup: ^sound_group) -> f32 --- + sound_group_set_fade_in_pcm_frames :: proc(pGroup: ^sound_group, volumeBeg, volumeEnd: f32, fadeLengthInFrames: u64) --- + sound_group_set_fade_in_milliseconds :: proc(pGroup: ^sound_group, volumeBeg, volumeEnd: f32, fadeLengthInMilliseconds: u64) --- + sound_group_get_current_fade_volume :: proc(pGroup: ^sound_group) -> f32 --- + sound_group_set_start_time_in_pcm_frames :: proc(pGroup: ^sound_group, absoluteGlobalTimeInFrames: u64) --- + sound_group_set_start_time_in_milliseconds :: proc(pGroup: ^sound_group, absoluteGlobalTimeInMilliseconds: u64) --- + sound_group_set_stop_time_in_pcm_frames :: proc(pGroup: ^sound_group, absoluteGlobalTimeInFrames: u64) --- + sound_group_set_stop_time_in_milliseconds :: proc(pGroup: ^sound_group, absoluteGlobalTimeInMilliseconds: u64) --- + sound_group_is_playing :: proc(pGroup: ^sound_group) -> b32 --- + sound_group_get_time_in_pcm_frames :: proc(pGroup: ^sound_group) -> u64 --- +} + + +engine_config :: struct { + pResourceManager: ^resource_manager, /* Can be null in which case a resource manager will be created for you. */ + pContext: ^context_type, + pDevice: ^device, /* If set, the caller is responsible for calling ma_engine_data_callback() in the device's data callback. */ + pPlaybackDeviceID: ^device_id, /* The ID of the playback device to use with the default listener. */ + pLog: ^log, /* When set to NULL, will use the context's log. */ + listenerCount: u32, /* Must be between 1 and MA_ENGINE_MAX_LISTENERS. */ + channels: u32, /* The number of channels to use when mixing and spatializing. When set to 0, will use the native channel count of the device. */ + sampleRate: u32, /* The sample rate. When set to 0 will use the native channel count of the device. */ + periodSizeInFrames: u32, /* If set to something other than 0, updates will always be exactly this size. The underlying device may be a different size, but from the perspective of the mixer that won't matter.*/ + periodSizeInMilliseconds: u32, /* Used if periodSizeInFrames is unset. */ + gainSmoothTimeInFrames: u32, /* The number of frames to interpolate the gain of spatialized sounds across. If set to 0, will use gainSmoothTimeInMilliseconds. */ + gainSmoothTimeInMilliseconds: u32, /* When set to 0, gainSmoothTimeInFrames will be used. If both are set to 0, a default value will be used. */ + allocationCallbacks: allocation_callbacks, + noAutoStart: b32, /* When set to true, requires an explicit call to ma_engine_start(). This is false by default, meaning the engine will be started automatically in ma_engine_init(). */ + noDevice: b32, /* When set to true, don't create a default device. ma_engine_read_pcm_frames() can be called manually to read data. */ + monoExpansionMode: mono_expansion_mode, /* Controls how the mono channel should be expanded to other channels when spatialization is disabled on a sound. */ + pResourceManagerVFS: ^vfs, /* A pointer to a pre-allocated VFS object to use with the resource manager. This is ignored if pResourceManager is not NULL. */ +} + +engine :: struct { + nodeGraph: node_graph, /* An engine is a node graph. It should be able to be plugged into any ma_node_graph API (with a cast) which means this must be the first member of this struct. */ + pResourceManager: ^resource_manager, + pDevice: ^device, /* Optionally set via the config, otherwise allocated by the engine in ma_engine_init(). */ + pLog: ^log, + sampleRate: u32, + listenerCount: u32, + listeners: [ENGINE_MAX_LISTENERS]spatializer_listener, + allocationCallbacks: allocation_callbacks, + ownsResourceManager: b8, + ownsDevice: b8, + inlinedSoundLock: spinlock, /* For synchronizing access so the inlined sound list. */ + pInlinedSoundHead: ^sound_inlined, /* The first inlined sound. Inlined sounds are tracked in a linked list. */ + inlinedSoundCount: u32, /*atomic*/ /* The total number of allocated inlined sound objects. Used for debugging. */ + gainSmoothTimeInFrames: u32, /* The number of frames to interpolate the gain of spatialized sounds across. */ + monoExpansionMode: mono_expansion_mode, +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + engine_config_init :: proc() -> engine_config --- + + engine_init :: proc(pConfig: ^engine_config, pEngine: ^engine) -> result --- + engine_uninit :: proc(pEngine: ^engine) --- + engine_read_pcm_frames :: proc(pEngine: ^engine, pFramesOut: rawptr, frameCount: u64, pFramesRead: ^u64) -> result --- + engine_get_node_graph :: proc(pEngine: ^engine) -> ^node_graph --- + engine_get_resource_manager :: proc(pEngine: ^engine) -> ^resource_manager --- + engine_get_device :: proc(pEngine: ^engine) -> ^device --- + engine_get_log :: proc(pEngine: ^engine) -> ^log --- + engine_get_endpoint :: proc(pEngine: ^engine) -> ^node --- + engine_get_time :: proc(pEngine: ^engine) -> u64 --- + engine_set_time :: proc(pEngine: ^engine, globalTime: u64) -> result --- + engine_get_channels :: proc(pEngine: ^engine) -> u32 --- + engine_get_sample_rate :: proc(pEngine: ^engine) -> u32 --- + + engine_start :: proc(pEngine: ^engine) -> result --- + engine_stop :: proc(pEngine: ^engine) -> result --- + engine_set_volume :: proc(pEngine: ^engine, volume: f32) -> result --- + engine_set_gain_db :: proc(pEngine: ^engine, gainDB: f32) -> result --- + + engine_get_listener_count :: proc(pEngine: ^engine) -> u32 --- + engine_find_closest_listener :: proc(pEngine: ^engine, absolutePosX, absolutePosY, absolutePosZ: f32) -> u32 --- + engine_listener_set_position :: proc(pEngine: ^engine, listenerIndex: u32, x, y, z: f32) --- + engine_listener_get_position :: proc(pEngine: ^engine, listenerIndex: u32) -> vec3f --- + engine_listener_set_direction :: proc(pEngine: ^engine, listenerIndex: u32, x, y, z: f32) --- + engine_listener_get_direction :: proc(pEngine: ^engine, listenerIndex: u32) -> vec3f --- + engine_listener_set_velocity :: proc(pEngine: ^engine, listenerIndex: u32, x, y, z: f32) --- + engine_listener_get_velocity :: proc(pEngine: ^engine, listenerIndex: u32) -> vec3f --- + engine_listener_set_cone :: proc(pEngine: ^engine, listenerIndex: u32, innerAngleInRadians, outerAngleInRadians, outerGain: f32) --- + engine_listener_get_cone :: proc(pEngine: ^engine, listenerIndex: u32, pInnerAngleInRadians, pOuterAngleInRadians, pOuterGain: ^f32) --- + engine_listener_set_world_up :: proc(pEngine: ^engine, listenerIndex: u32, x, y, z: f32) --- + engine_listener_get_world_up :: proc(pEngine: ^engine, listenerIndex: u32) -> vec3f --- + engine_listener_set_enabled :: proc(pEngine: ^engine, listenerIndex: u32, isEnabled: b32) --- + engine_listener_is_enabled :: proc(pEngine: ^engine, listenerIndex: u32) -> b32 --- + + engine_play_sound_ex :: proc(pEngine: ^engine, pFilePath: cstring, pNode: ^node, nodeInputBusIndex: u32) -> result --- + engine_play_sound :: proc(pEngine: ^engine, pFilePath: cstring, pGroup: ^sound_group) -> result --- /* Fire and forget. */ +} diff --git a/vendor/miniaudio/filtering.odin b/vendor/miniaudio/filtering.odin index 9949f6338..b8175c372 100644 --- a/vendor/miniaudio/filtering.odin +++ b/vendor/miniaudio/filtering.odin @@ -1,5 +1,7 @@ package miniaudio +import c "core:c/libc" + when ODIN_OS == .Windows { foreign import lib "lib/miniaudio.lib" } else when ODIN_OS == .Linux { @@ -19,14 +21,14 @@ biquad_coefficient :: struct #raw_union { } biquad_config :: struct { - format: format, + format: format, channels: u32, - b0: f64, - b1: f64, - b2: f64, - a0: f64, - a1: f64, - a2: f64, + b0: f64, + b1: f64, + b2: f64, + a0: f64, + a1: f64, + a2: f64, } biquad :: struct { @@ -37,17 +39,25 @@ biquad :: struct { b2: biquad_coefficient, a1: biquad_coefficient, a2: biquad_coefficient, - r1: [MAX_CHANNELS]biquad_coefficient, - r2: [MAX_CHANNELS]biquad_coefficient, + pR1: ^biquad_coefficient, + pR2: ^biquad_coefficient, + + /* Memory management. */ + _pHeap: rawptr, + _ownsHeap: b32, } @(default_calling_convention="c", link_prefix="ma_") foreign lib { biquad_config_init :: proc(format: format, channels: u32, b0, b1, b2, a0, a1, a2: f64) -> biquad_config --- - biquad_init :: proc(pConfig: ^biquad_config, pBQ: ^biquad) -> result --- + biquad_get_heap_size :: proc(pConfig: ^biquad_config, pHeapSizeInBytes: ^c.size_t) -> result --- + biquad_init_preallocated :: proc(pConfig: ^biquad_config, pHeap: rawptr, pBQ: ^biquad) -> result --- + biquad_init :: proc(pConfig: ^biquad_config, pAllocationCallbacks: ^allocation_callbacks, pBQ: ^biquad) -> result --- + biquad_uninit :: proc(pBQ: ^biquad, pAllocationCallbacks: ^allocation_callbacks) --- biquad_reinit :: proc(pConfig: ^biquad_config, pBQ: ^biquad) -> result --- - biquad_process_pcm_frames :: proc(pBQ: ^biquad, pFramesOut: rawptr, pFramesIn: rawptr, frameCount: u64) -> result --- + biquad_clear_cache :: proc(pBQ: ^biquad) -> result --- + biquad_process_pcm_frames :: proc(pBQ: ^biquad, pFramesOut, pFramesIn: rawptr, frameCount: u64) -> result --- biquad_get_latency :: proc(pBQ: ^biquad) -> u32 --- } @@ -70,7 +80,11 @@ lpf1 :: struct { format: format, channels: u32, a: biquad_coefficient, - r1: [MAX_CHANNELS]biquad_coefficient, + pR1: ^biquad_coefficient, + + /* Memory management. */ + _pHeap: rawptr, + _ownsHeap: b32, } lpf2 :: struct { @@ -91,8 +105,12 @@ lpf :: struct { sampleRate: u32, lpf1Count: u32, lpf2Count: u32, - lpf1: [1]lpf1, - lpf2: [MAX_FILTER_ORDER/2]lpf2, + pLPF1: ^lpf1, + pLPF2: ^lpf2, + + /* Memory management. */ + _pHeap: rawptr, + _ownsHeap: b32, } @@ -101,20 +119,32 @@ foreign lib { lpf1_config_init :: proc(format: format, channels: u32, sampleRate: u32, cutoffFrequency: f64) -> lpf1_config --- lpf2_config_init :: proc(format: format, channels: u32, sampleRate: u32, cutoffFrequency, q: f64) -> lpf2_config --- - lpf1_init :: proc(pConfig: ^lpf1_config, pLPF: ^lpf1) -> result --- + lpf1_get_heap_size :: proc(pConfig: ^lpf1_config, pHeapSizeInBytes: ^c.size_t) -> result --- + lpf1_init_preallocated :: proc(pConfig: ^lpf1_config, pHeap: rawptr, pLPF: ^lpf1) -> result --- + lpf1_init :: proc(pConfig: ^lpf1_config, pAllocationCallbacks: ^allocation_callbacks, pLPF: ^lpf1) -> result --- + lpf1_uninit :: proc(pLPF: ^lpf1, pAllocationCallbacks: ^allocation_callbacks) --- lpf1_reinit :: proc(pConfig: ^lpf1_config, pLPF: ^lpf1) -> result --- + lpf1_clear_cache :: proc(pLPF: ^lpf1) -> result --- lpf1_process_pcm_frames :: proc(pLPF: ^lpf1, pFramesOut: rawptr, pFramesIn: rawptr, frameCount: u64) -> result --- lpf1_get_latency :: proc(pLPF: ^lpf1) -> u32 --- - lpf2_init :: proc(pConfig: ^lpf2_config, pLPF: ^lpf2) -> result --- + lpf2_get_heap_size :: proc(pConfig: ^lpf2_config, pHeapSizeInBytes: ^c.size_t) -> result --- + lpf2_init_preallocated :: proc(pConfig: ^lpf2_config, pHeap: rawptr, pHPF: ^lpf2) -> result --- + lpf2_init :: proc(pConfig: ^lpf2_config, pAllocationCallbacks: ^allocation_callbacks, pLPF: ^lpf2) -> result --- + lpf2_uninit :: proc(pLPF: ^lpf2, pAllocationCallbacks: ^allocation_callbacks) --- lpf2_reinit :: proc(pConfig: ^lpf2_config, pLPF: ^lpf2) -> result --- + lpf2_clear_cache :: proc(pLPF: ^lpf2) -> result --- lpf2_process_pcm_frames :: proc(pLPF: ^lpf2, pFramesOut: rawptr, pFramesIn: rawptr, frameCount: u64) -> result --- lpf2_get_latency :: proc(pLPF: ^lpf2) -> u32 --- lpf_config_init :: proc(format: format, channels: u32, sampleRate: u32, cutoffFrequency: f64, order: u32) -> lpf_config --- - lpf_init :: proc(pConfig: ^lpf_config, pLPF: ^lpf) -> result --- + lpf_get_heap_size :: proc(pConfig: ^lpf_config, pHeapSizeInBytes: ^c.size_t) -> result --- + lpf_init_preallocated :: proc(pConfig: ^lpf_config, pHeap: rawptr, pLPF: ^lpf) -> result --- + lpf_init :: proc(pConfig: ^lpf_config, pAllocationCallbacks: ^allocation_callbacks, pLPF: ^lpf) -> result --- + lpf_uninit :: proc(pLPF: ^lpf, pAllocationCallbacks: ^allocation_callbacks) --- lpf_reinit :: proc(pConfig: ^lpf_config, pLPF: ^lpf) -> result --- + lpf_clear_cache :: proc(pLPF: ^lpf) -> result --- lpf_process_pcm_frames :: proc(pLPF: ^lpf, pFramesOut: rawptr, pFramesIn: rawptr, frameCount: u64) -> result --- lpf_get_latency :: proc(pLPF: ^lpf) -> u32 --- } @@ -138,7 +168,11 @@ hpf1 :: struct { format: format, channels: u32, a: biquad_coefficient, - r1: [MAX_CHANNELS]biquad_coefficient, + pR1: ^biquad_coefficient, + + /* Memory management. */ + _pHeap: rawptr, + _ownsHeap: b32, } hpf2 :: struct { @@ -159,8 +193,12 @@ hpf :: struct { sampleRate: u32, hpf1Count: u32, hpf2Count: u32, - hpf1: [1]hpf1, - hpf2: [MAX_FILTER_ORDER/2]hpf2, + pHPF1: ^hpf1, + pHPF2: ^hpf2, + + /* Memory management. */ + _pHeap: rawptr, + _ownsHeap: b32, } @@ -169,19 +207,28 @@ foreign lib { hpf1_config_init :: proc(format: format, channels: u32, sampleRate: u32, cutoffFrequency: f64) -> hpf1_config --- hpf2_config_init :: proc(format: format, channels: u32, sampleRate: u32, cutoffFrequency, q: f64) -> hpf2_config --- - hpf1_init :: proc(pConfig: ^hpf1_config, pHPF: ^hpf1) -> result --- + hpf1_get_heap_size :: proc(pConfig: ^hpf1_config, pHeapSizeInBytes: ^c.size_t) -> result --- + hpf1_init_preallocated :: proc(pConfig: ^hpf1_config, pHeap: rawptr, pLPF: ^hpf1) -> result --- + hpf1_init :: proc(pConfig: ^hpf1_config, pAllocationCallbacks: ^allocation_callbacks, pHPF: ^hpf1) -> result --- + hpf1_uninit :: proc(pHPF: ^hpf1, pAllocationCallbacks: ^allocation_callbacks) --- hpf1_reinit :: proc(pConfig: ^hpf1_config, pHPF: ^hpf1) -> result --- hpf1_process_pcm_frames :: proc(pHPF: ^hpf1, pFramesOut: rawptr, pFramesIn: rawptr, frameCount: u64) -> result --- hpf1_get_latency :: proc(pHPF: ^hpf1) -> u32 --- - hpf2_init :: proc(pConfig: ^hpf2_config, pHPF: ^hpf2) -> result --- + hpf2_get_heap_size :: proc(pConfig: ^hpf2_config, pHeapSizeInBytes: ^c.size_t) -> result --- + hpf2_init_preallocated :: proc(pConfig: ^hpf2_config, pHeap: rawptr, pHPF: ^hpf2) -> result --- + hpf2_init :: proc(pConfig: ^hpf2_config, pAllocationCallbacks: ^allocation_callbacks, pHPF: ^hpf2) -> result --- + hpf2_uninit :: proc(pHPF: ^hpf2, pAllocationCallbacks: ^allocation_callbacks) --- hpf2_reinit :: proc(pConfig: ^hpf2_config, pHPF: ^hpf2) -> result --- hpf2_process_pcm_frames :: proc(pHPF: ^hpf2, pFramesOut: rawptr, pFramesIn: rawptr, frameCount: u64) -> result --- hpf2_get_latency :: proc(pHPF: ^hpf2) -> u32 --- hpf_config_init :: proc(format: format, channels: u32, sampleRate: u32, cutoffFrequency: f64, order: u32) -> hpf_config --- - hpf_init :: proc(pConfig: ^hpf_config, pHPF: ^hpf) -> result --- + hpf_get_heap_size :: proc(pConfig: ^hpf_config, pHeapSizeInBytes: ^c.size_t) -> result --- + hpf_init_preallocated :: proc(pConfig: ^hpf_config, pHeap: rawptr, pLPF: ^hpf) -> result --- + hpf_init :: proc(pConfig: ^hpf_config, pAllocationCallbacks: ^allocation_callbacks, pHPF: ^hpf) -> result --- + hpf_uninit :: proc(pHPF: ^hpf, pAllocationCallbacks: ^allocation_callbacks) --- hpf_reinit :: proc(pConfig: ^hpf_config, pHPF: ^hpf) -> result --- hpf_process_pcm_frames :: proc(pHPF: ^hpf, pFramesOut: rawptr, pFramesIn: rawptr, frameCount: u64) -> result --- hpf_get_latency :: proc(pHPF: ^hpf) -> u32 --- @@ -217,21 +264,31 @@ bpf :: struct { format: format, channels: u32, bpf2Count: u32, - bpf2: [MAX_FILTER_ORDER/2]bpf2, + pBPF2: ^bpf2, + + /* Memory management. */ + _pHeap: rawptr, + _ownsHeap: b32, } @(default_calling_convention="c", link_prefix="ma_") foreign lib { bpf2_config_init :: proc(format: format, channels: u32, sampleRate: u32, cutoffFrequency: f64, q: f64) -> bpf2_config --- - bpf2_init :: proc(pConfig: ^bpf2_config, pBPF: ^bpf2) -> result --- + bpf2_get_heap_size :: proc(pConfig: ^bpf2_config, pHeapSizeInBytes: ^c.size_t) -> result --- + bpf2_init_preallocated :: proc(pConfig: ^bpf2_config, pHeap: rawptr, pBPF: ^bpf2) -> result --- + bpf2_init :: proc(pConfig: ^bpf2_config, pAllocationCallbacks: ^allocation_callbacks, pBPF: ^bpf2) -> result --- + bpf2_uninit :: proc(pBPF: ^bpf2, pAllocationCallbacks: ^allocation_callbacks) --- bpf2_reinit :: proc(pConfig: ^bpf2_config, pBPF: ^bpf2) -> result --- bpf2_process_pcm_frames :: proc(pBPF: ^bpf2, pFramesOut: rawptr, pFramesIn: rawptr, frameCount: u64) -> result --- bpf2_get_latency :: proc(pBPF: ^bpf2) -> u32 --- bpf_config_init :: proc(format: format, channels: u32, sampleRate: u32, cutoffFrequency: f64, order: u32) -> bpf_config --- - bpf_init :: proc(pConfig: ^bpf_config, pBPF: ^bpf) -> result --- + bpf_get_heap_size :: proc(pConfig: ^bpf_config, pHeapSizeInBytes: ^c.size_t) -> result --- + bpf_init_preallocated :: proc(pConfig: ^bpf_config, pHeap: rawptr, pBPF: ^bpf) -> result --- + bpf_init :: proc(pConfig: ^bpf_config, pAllocationCallbacks: ^allocation_callbacks, pBPF: ^bpf) -> result --- + bpf_uninit :: proc(pBPF: ^bpf, pAllocationCallbacks: ^allocation_callbacks) --- bpf_reinit :: proc(pConfig: ^bpf_config, pBPF: ^bpf) -> result --- bpf_process_pcm_frames :: proc(pBPF: ^bpf, pFramesOut: rawptr, pFramesIn: rawptr, frameCount: u64) -> result --- bpf_get_latency :: proc(pBPF: ^bpf) -> u32 --- @@ -260,7 +317,10 @@ notch2 :: struct { foreign lib { notch2_config_init :: proc(format: format, channels: u32, sampleRate: u32, q: f64, frequency: f64) -> notch2_config --- - notch2_init :: proc(pConfig: ^notch2_config, pFilter: ^notch2) -> result --- + notch2_get_heap_size :: proc(pConfig: ^notch2_config, pHeapSizeInBytes: ^c.size_t) -> result --- + notch2_init_preallocated :: proc(pConfig: ^notch2_config, pHeap: rawptr, pFilter: ^notch2) -> result --- + notch2_init :: proc(pConfig: ^notch2_config, pAllocationCallbacks: ^allocation_callbacks, pFilter: ^notch2) -> result --- + notch2_uninit :: proc(pFilter: ^notch2, pAllocationCallbacks: ^allocation_callbacks) --- notch2_reinit :: proc(pConfig: ^notch2_config, pFilter: ^notch2) -> result --- notch2_process_pcm_frames :: proc(pFilter: ^notch2, pFramesOut: rawptr, pFramesIn: rawptr, frameCount: u64) -> result --- notch2_get_latency :: proc(pFilter: ^notch2) -> u32 --- @@ -290,7 +350,10 @@ peak2 :: struct { foreign lib { peak2_config_init :: proc(format: format, channels: u32, sampleRate: u32, gainDB, q, frequency: f64) -> peak2_config --- - peak2_init :: proc(pConfig: ^peak2_config, pFilter: ^peak2) -> result --- + peak2_get_heap_size :: proc(pConfig: ^peak2_config, pHeapSizeInBytes: ^c.size_t) -> result --- + peak2_init_preallocated :: proc(pConfig: ^peak2_config, pHeap: rawptr, pFilter: ^peak2) -> result --- + peak2_init :: proc(pConfig: ^peak2_config, pAllocationCallbacks: ^allocation_callbacks, pFilter: ^peak2) -> result --- + peak2_uninit :: proc(pFilter: ^peak2, pAllocationCallbacks: ^allocation_callbacks) --- peak2_reinit :: proc(pConfig: ^peak2_config, pFilter: ^peak2) -> result --- peak2_process_pcm_frames :: proc(pFilter: ^peak2, pFramesOut: rawptr, pFramesIn: rawptr, frameCount: u64) -> result --- peak2_get_latency :: proc(pFilter: ^peak2) -> u32 --- @@ -320,7 +383,10 @@ loshelf2 :: struct { foreign lib { loshelf2_config_init :: proc(format: format, channels: u32, sampleRate: u32, gainDB, shelfSlope, frequency: f64) -> loshelf2_config --- - loshelf2_init :: proc(pConfig: ^loshelf2_config, pFilter: ^loshelf2) -> result --- + loshelf2_get_heap_size :: proc(pConfig: ^loshelf2_config, pHeapSizeInBytes: ^c.size_t) -> result --- + loshelf2_init_preallocated :: proc(pConfig: ^loshelf2_config, pHeap: rawptr, pFilter: ^loshelf2) -> result --- + loshelf2_init :: proc(pConfig: ^loshelf2_config, pAllocationCallbacks: ^allocation_callbacks, pFilter: ^loshelf2) -> result --- + loshelf2_uninit :: proc(pFilter: ^loshelf2, pAllocationCallbacks: ^allocation_callbacks) --- loshelf2_reinit :: proc(pConfig: ^loshelf2_config, pFilter: ^loshelf2) -> result --- loshelf2_process_pcm_frames :: proc(pFilter: ^loshelf2, pFramesOut: rawptr, pFramesIn: rawptr, frameCount: u64) -> result --- loshelf2_get_latency :: proc(pFilter: ^loshelf2) -> u32 --- @@ -350,7 +416,10 @@ hishelf2 :: struct { foreign lib { hishelf2_config_init :: proc(format: format, channels: u32, sampleRate: u32, gainDB, shelfSlope, frequency: f64) -> hishelf2_config --- - hishelf2_init :: proc(pConfig: ^hishelf2_config, pFilter: ^hishelf2) -> result --- + hishelf2_get_heap_size :: proc(pConfig: ^hishelf2_config, pHeapSizeInBytes: ^c.size_t) -> result --- + hishelf2_init_preallocated :: proc(pConfig: ^hishelf2_config, pHeap: rawptr, pFilter: ^hishelf2) -> result --- + hishelf2_init :: proc(pConfig: ^hishelf2_config, pAllocationCallbacks: ^allocation_callbacks, pFilter: ^hishelf2) -> result --- + hishelf2_uninit :: proc(pFilter: ^hishelf2, pAllocationCallbacks: ^allocation_callbacks) --- hishelf2_reinit :: proc(pConfig: ^hishelf2_config, pFilter: ^hishelf2) -> result --- hishelf2_process_pcm_frames :: proc(pFilter: ^hishelf2, pFramesOut: rawptr, pFramesIn: rawptr, frameCount: u64) -> result --- hishelf2_get_latency :: proc(pFilter: ^hishelf2) -> u32 --- diff --git a/vendor/miniaudio/generation.odin b/vendor/miniaudio/generation.odin index 97b7d453c..305090c7d 100644 --- a/vendor/miniaudio/generation.odin +++ b/vendor/miniaudio/generation.odin @@ -56,14 +56,18 @@ noise :: struct { lcg: lcg, state: struct #raw_union { pink: struct { - bin: [MAX_CHANNELS][16]f64, - accumulation: [MAX_CHANNELS]f64, - counter: [MAX_CHANNELS]u32, + bin: ^[^]f64, + accumulation: [^]f64, + counter: [^]u32, }, brownian: struct { - accumulation: [MAX_CHANNELS]f64, + accumulation: [^]f64, }, }, + + /* Memory management. */ + _pHeap: rawptr, + _ownsHeap: b32, } @(default_calling_convention="c", link_prefix="ma_") @@ -72,7 +76,7 @@ foreign lib { waveform_init :: proc(pConfig: ^waveform_config, pWaveform: ^waveform) -> result --- waveform_uninit :: proc(pWaveform: ^waveform) --- - waveform_read_pcm_frames :: proc(pWaveform: ^waveform, pFramesOut: rawptr, frameCount: u64) -> u64 --- + waveform_read_pcm_frames :: proc(pWaveform: ^waveform, pFramesOut: rawptr, frameCount: u64, pFramesRead: ^u64) -> result --- waveform_seek_to_pcm_frame :: proc(pWaveform: ^waveform, frameIndex: u64) -> result --- waveform_set_amplitude :: proc(pWaveform: ^waveform, amplitude: f64) -> result --- waveform_set_frequency :: proc(pWaveform: ^waveform, frequency: f64) -> result --- @@ -81,10 +85,12 @@ foreign lib { noise_config_init :: proc(format: format, channels: u32, type: noise_type, seed: i32, amplitude: f64) -> noise_config --- - noise_init :: proc(pConfig: ^noise_config, pNoise: ^noise) -> result --- - noise_uninit :: proc(pNoise: ^noise) --- - noise_read_pcm_frames :: proc(pNoise: ^noise, pFramesOut: rawptr, frameCount: u64) -> u64 --- - noise_set_amplitude :: proc(pNoise: ^noise, amplitude: f64) -> result --- - noise_set_seed :: proc(pNoise: ^noise, seed: i32) -> result --- - noise_set_type :: proc(pNoise: ^noise, type: noise_type) -> result --- + noise_get_heap_size :: proc(pConfig: ^noise_config, pHeapSizeInBytes: ^c.size_t) -> result --- + noise_init_preallocated :: proc(pConfig: ^noise_config, pHeap: rawptr, pNoise: ^noise) -> result --- + noise_init :: proc(pConfig: ^noise_config, pAllocationCallbacks: ^allocation_callbacks, pNoise: ^noise) -> result --- + noise_uninit :: proc(pNoise: ^noise, pAllocationCallbacks: ^allocation_callbacks) --- + noise_read_pcm_frames :: proc(pNoise: ^noise, pFramesOut: rawptr, frameCount: u64, pFramesRead: ^u64) -> result --- + noise_set_amplitude :: proc(pNoise: ^noise, amplitude: f64) -> result --- + noise_set_seed :: proc(pNoise: ^noise, seed: i32) -> result --- + noise_set_type :: proc(pNoise: ^noise, type: noise_type) -> result --- } diff --git a/vendor/miniaudio/job_queue.odin b/vendor/miniaudio/job_queue.odin new file mode 100644 index 000000000..99899fdbd --- /dev/null +++ b/vendor/miniaudio/job_queue.odin @@ -0,0 +1,239 @@ +package miniaudio + +import c "core:c/libc" + +when ODIN_OS == .Windows { + foreign import lib "lib/miniaudio.lib" +} else when ODIN_OS == .Linux { + foreign import lib "lib/miniaudio.a" +} else { + foreign import lib "system:miniaudio" +} + +/* +Slot Allocator +-------------- +The idea of the slot allocator is for it to be used in conjunction with a fixed sized buffer. You use the slot allocator to allocator an index that can be used +as the insertion point for an object. + +Slots are reference counted to help mitigate the ABA problem in the lock-free queue we use for tracking jobs. + +The slot index is stored in the low 32 bits. The reference counter is stored in the high 32 bits: + + +-----------------+-----------------+ + | 32 Bits | 32 Bits | + +-----------------+-----------------+ + | Reference Count | Slot Index | + +-----------------+-----------------+ +*/ +slot_allocator_config :: struct { + capacity: u32, /* The number of slots to make available. */ +} + +slot_allocator_group :: struct { + bitfield: u32, /*atomic*/ /* Must be used atomically because the allocation and freeing routines need to make copies of this which must never be optimized away by the compiler. */ +} + +slot_allocator :: struct { + pGroups: [^]slot_allocator_group, /* Slots are grouped in chunks of 32. */ + pSlots: [^]u32, /* 32 bits for reference counting for ABA mitigation. */ + count: u32, /* Allocation count. */ + capacity: u32, + + /* Memory management. */ + _ownsHeap: b32, + _pHeap: rawptr, +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + slot_allocator_config_init :: proc(capacity: u32) -> slot_allocator_config --- + + slot_allocator_get_heap_size :: proc(pConfig: ^slot_allocator_config, pHeapSizeInBytes: ^c.size_t) -> result --- + slot_allocator_init_preallocated :: proc(pConfig: ^slot_allocator_config, pHeap: rawptr, pAllocator: ^slot_allocator) -> result --- + slot_allocator_init :: proc(pConfig: ^slot_allocator_config, pAllocationCallbacks: ^allocation_callbacks, pAllocator: ^slot_allocator) -> result --- + slot_allocator_uninit :: proc(pAllocator: ^slot_allocator, pAllocationCallbacks: ^allocation_callbacks) --- + slot_allocator_alloc :: proc(pAllocator: ^slot_allocator, pSlot: ^u64) -> result --- + slot_allocator_free :: proc(pAllocator: ^slot_allocator, slot: u64) -> result --- +} + +/* +Callback for processing a job. Each job type will have their own processing callback which will be +called by ma_job_process(). +*/ +job_proc :: proc "c" (pJob: ^job) + +/* When a job type is added here an callback needs to be added go "g_jobVTable" in the implementation section. */ +job_type :: enum c.int { + /* Miscellaneous. */ + QUIT = 0, + CUSTOM, + + /* Resource Manager. */ + RESOURCE_MANAGER_LOAD_DATA_BUFFER_NODE, + RESOURCE_MANAGER_FREE_DATA_BUFFER_NODE, + RESOURCE_MANAGER_PAGE_DATA_BUFFER_NODE, + RESOURCE_MANAGER_LOAD_DATA_BUFFER, + RESOURCE_MANAGER_FREE_DATA_BUFFER, + RESOURCE_MANAGER_LOAD_DATA_STREAM, + RESOURCE_MANAGER_FREE_DATA_STREAM, + RESOURCE_MANAGER_PAGE_DATA_STREAM, + RESOURCE_MANAGER_SEEK_DATA_STREAM, + + /* Device. */ + DEVICE_AAUDIO_REROUTE, + + /* Count. Must always be last. */ + COUNT, +} + +job :: struct { + toc: struct #raw_union { /* 8 bytes. We encode the job code into the slot allocation data to save space. */ + breakup: struct { + code: u16, /* Job type. */ + slot: u16, /* Index into a ma_slot_allocator. */ + refcount: u32, + }, + allocation: u64, + }, + next: u64, /*atomic*/ /* refcount + slot for the next item. Does not include the job code. */ + order: u32, /* Execution order. Used to create a data dependency and ensure a job is executed in order. Usage is contextual depending on the job type. */ + + data: struct #raw_union { + /* Miscellaneous. */ + custom: struct { + proc_: job_proc, + data0: uintptr, + data1: uintptr, + }, + + /* Resource Manager */ + resourceManager: struct #raw_union { + loadDataBufferNode: struct { + pResourceManager: rawptr /*ma_resource_manager**/, + pDataBufferNode: rawptr /*ma_resource_manager_data_buffer_node**/, + pFilePath: cstring, + pFilePathW: [^]c.wchar_t, + flags: u32, /* Resource manager data source flags that were used when initializing the data buffer. */ + pInitNotification: ^async_notification, /* Signalled when the data buffer has been initialized and the format/channels/rate can be retrieved. */ + pDoneNotification: ^async_notification, /* Signalled when the data buffer has been fully decoded. Will be passed through to MA_JOB_TYPE_RESOURCE_MANAGER_PAGE_DATA_BUFFER_NODE when decoding. */ + pInitFence: ^fence, /* Released when initialization of the decoder is complete. */ + pDoneFence: ^fence, /* Released if initialization of the decoder fails. Passed through to PAGE_DATA_BUFFER_NODE untouched if init is successful. */ + }, + freeDataBufferNode: struct { + pResourceManager: rawptr /*ma_resource_manager**/, + pDataBufferNode: rawptr /*ma_resource_manager_data_buffer_node**/, + pDoneNotification: ^async_notification, + pDoneFence: ^fence, + }, + pageDataBufferNode: struct { + pResourceManager: rawptr /*ma_resource_manager**/, + pDataBufferNode: rawptr /*ma_resource_manager_data_buffer_node**/, + pDecoder: rawptr /*ma_decoder**/, + pDoneNotification: ^async_notification, /* Signalled when the data buffer has been fully decoded. */ + pDoneFence: ^fence, /* Passed through from LOAD_DATA_BUFFER_NODE and released when the data buffer completes decoding or an error occurs. */ + }, + + loadDataBuffer: struct { + pDataBuffer: rawptr /*ma_resource_manager_data_buffer**/, + pInitNotification: ^async_notification, /* Signalled when the data buffer has been initialized and the format/channels/rate can be retrieved. */ + pDoneNotification: ^async_notification, /* Signalled when the data buffer has been fully decoded. */ + pInitFence: ^fence, /* Released when the data buffer has been initialized and the format/channels/rate can be retrieved. */ + pDoneFence: ^fence, /* Released when the data buffer has been fully decoded. */ + rangeBegInPCMFrames: u64, + rangeEndInPCMFrames: u64, + loopPointBegInPCMFrames: u64, + loopPointEndInPCMFrames: u64, + isLooping: u32, + }, + freeDataBuffer: struct { + pDataBuffer: rawptr /*ma_resource_manager_data_buffer**/, + pDoneNotification: ^async_notification, + pDoneFence: ^fence, + }, + + loadDataStream: struct { + pDataStream: rawptr /*ma_resource_manager_data_stream**/, + pFilePath: cstring, /* Allocated when the job is posted, freed by the job thread after loading. */ + pFilePathW: [^]c.wchar_t, /* ^ As above ^. Only used if pFilePath is NULL. */ + initialSeekPoint: u64, + pInitNotification: ^async_notification, /* Signalled after the first two pages have been decoded and frames can be read from the stream. */ + pInitFence: ^fence, + }, + freeDataStream: struct { + pDataStream: rawptr /*ma_resource_manager_data_stream**/, + pDoneNotification: ^async_notification, + pDoneFence: ^fence, + }, + pageDataStream: struct { + pDataStream: rawptr /*ma_resource_manager_data_stream**/, + pageIndex: u32, /* The index of the page to decode into. */ + }, + seekDataStream: struct { + pDataStream: rawptr /*ma_resource_manager_data_stream**/, + frameIndex: u64, + }, + }, + + /* Device. */ + device: struct #raw_union { + aaudio: struct #raw_union { + reroute: struct { + pDevice: rawptr /*ma_device**/, + deviceType: u32 /*ma_device_type*/, + }, + }, + }, + }, +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + job_init :: proc(code: u16) -> job --- + job_process :: proc(pJob: ^job) -> result --- +} + + +/* +When set, ma_job_queue_next() will not wait and no semaphore will be signaled in +ma_job_queue_post(). ma_job_queue_next() will return MA_NO_DATA_AVAILABLE if nothing is available. + +This flag should always be used for platforms that do not support multithreading. +*/ +job_queue_flags :: enum c.int { + NON_BLOCKING = 0x00000001, +} + +job_queue_config :: struct { + flags: u32, + capacity: u32, /* The maximum number of jobs that can fit in the queue at a time. */ +} + +USE_EXPERIMENTAL_LOCK_FREE_JOB_QUEUE :: false + +job_queue :: struct { + flags: u32, /* Flags passed in at initialization time. */ + capacity: u32, /* The maximum number of jobs that can fit in the queue at a time. Set by the config. */ + head: u64, /*atomic*/ /* The first item in the list. Required for removing from the top of the list. */ + tail: u64, /*atomic*/ /* The last item in the list. Required for appending to the end of the list. */ + sem: (struct {} when NO_THREADING else semaphore), /* Only used when MA_JOB_QUEUE_FLAG_NON_BLOCKING is unset. */ + allocator: slot_allocator, + pJobs: [^]job, + lock: (struct {} when USE_EXPERIMENTAL_LOCK_FREE_JOB_QUEUE else spinlock), + + /* Memory management. */ + _pHeap: rawptr, + _ownsHeap: b32, +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + job_queue_config_init :: proc(flags, capacity: u32) -> job_queue_config --- + + job_queue_get_heap_size :: proc(pConfig: ^job_queue_config, pHeapSizeInBytes: ^c.size_t) -> result --- + job_queue_init_preallocated :: proc(pConfig: ^job_queue_config, pHeap: rawptr, pQueue: ^job_queue) -> result --- + job_queue_init :: proc(pConfig: ^job_queue_config, pAllocationCallbacks: ^allocation_callbacks, pQueue: ^job_queue) -> result --- + job_queue_uninit :: proc(pQueue: ^job_queue, pAllocationCallbacks: ^allocation_callbacks) --- + job_queue_post :: proc(pQueue: ^job_queue, pJob: ^job) -> result --- + job_queue_next :: proc(pQueue: ^job_queue, pJob: ^job) -> result --- /* Returns MA_CANCELLED if the next job is a quit job. */ +} diff --git a/vendor/miniaudio/lib/miniaudio.lib b/vendor/miniaudio/lib/miniaudio.lib index 3d7c8327f6b3432b3c9fc8054061d0b9dc5c07c8..400cb9608cb9090e57467eb2fc0beb292eb2f139 100644 GIT binary patch literal 2767136 zcmY$iNi0gvu;bEKKm~?oriO;*rshV5sNx1tu7#Pou_>6Hz{SA8K97NE+M7{48U~|j zU^ESkrh(BkFq#HN)4*sN7)=ACX<#%BjHZFnG%%V5M$^D(8W>FjqiJ9?4UDFN(KIlc z21e7sXc`zz1EXnRG!2ZVfzdQTZW^$+k9T&6H!yJa3-EJt^l)}?V1No426($WdN}+0 zIM}N=d%=Z`T)dsVJw5$gK*Dat2yu5`A2%l_KZo)pgqW90fV)Exl7OF^kDr^rpM#%2 zLQ#OLi=)4nqnCq^D_qdn$1{^++gD5@8j&_=I8F<=^W$&H^am! zz|}p#$H&#dGuSNzA?)nv72xU=5a5uOTVijDB<|wj?d#+2hJ94?Ct2_ z>FghX5O(qK_w?`zaC6AZFG)oRySg}fySn&#J9s+#BH8I0;Oy?@?HJ$?lm?dss;8H^Oc0bc$-elG4V4k?KxiAbvbJ-pll9DN-;9fJ_6O`ZJ$ zJpFuqT^+(R^L!C$&D7i9$Iac<#nHjV)hW~+Zj7m~lZ&gnkF&RfYfz9sBCJe(-Q8T> zd>uU;>{Z-+>{XoIitSb0{OrvTIs;t2{T%~5eI1JPQZn=5;$}_(ejbiqE*=gB_9ljg z_9m7HDHmr~cRxQ57qFDEy$QlJGglukUsrEeur4!u6Jtb%HFI+c@bd8Y^>+w&40a6g zL>TAp8sP63;O*t$=va#41Wz}2Pk#>=R|n_P;*$Jagbq+pdN?_II5?Kro2KR=0^i)l z%h%K0)5G7vGp{T$2Ps<2UELkMygl3;oD&O5N{bMg$=uu7-^0_*Kfoc}G04x;4-u&5 zJ{~?Eo^DPKe);yM8Tm-**xc9K(Z|=x#mgZiGdDHA6p?1l{hXZL9bLQ}9YXW6^YY8{ z;Ep!;cXRZ1aSHGOhnkVSiLnJjF2K*pJ;2l1#UUBZbr!Bp0lpp{er^r{If<1?iOJb; zO%|>JE}q^VUak%7Pb+bC9>yQYF2zwQ0PZN~N!P3de&)Lz@E5O0OAT=-8$6m$5*A-FvS~@#Ozo)a0qm#d{LwIVEy(wzyvJ7x^ z^Y?Z1^LEJ1%*zCO7#>DWjt`fExO;a+9Qj<&aiz<;-xi~xddVn$s zEOzWoT~Hn9=xO;OPqScuHzwa!F=cB2t~;f{>W;^N{ETvC*om+n@S zpX(m#>0*RznWw*(hlh`guY+HHiM?rNZb43JZfaghDkAwiIXZZ`I668x2e^VN)>O|t z$CMPO(zLWxMEpBBI(WPJxq7?$xxzAuy=iW0ad9G2$T>MVxOzCcJ2`v0I=H8nxTF?m zm*f|OXXd5kmm{3yz+9=`7G z&aO`G2!|&YrI&&N0ohTWZjN5APA>kC@J`OjOwB8a&o4+t3+ z>tJsRfzJ7PWvNBQAl1f5MJ-6Fvx}#9fH%Zt`FZih8KosD$fbmnvx8HBr=O31fDgix z`30#(i6F}nzI1kQb8~U`@$qv*c)%sK%sH{37%9Y@9o#*AJzTted>kBeic%9(D(y{^ zAzndFHO>z1-k$C*POcsfF5pxioL`!k0*O#$D?JDPfzCnPe*r1LiH~#!4?C~4&E+qZoY0Fj*z^c zSdwUOnpl>Y32K8NS?c28=@j7V>*wVKaauusQAvDJYI+r!5pEVU>ztuhP} z?VfpQ`QeC=b8+wsaPbWYa0@_7NNJ_T$bsVG;P33^;p*@02#!=ppkT(Ti-W(5pOe3P zfSUuPu(3DI1CbUIa>g=w*$ogQvHrvzNQGw}W4( zkB_}6v>wMSa9tfd{hYjfz1`g*bwX}pUZuS$DF54=re)@&BIR&b2OmFwS8rcWPY0j; z#FWgubbHg{%G{*<9D8GXQ%G|O(YSGPb@20YbaeCcgOwTqMX3deMX4UCi7CkG%+0~c z%g4ph+r`ZxD784hvWk@#%Pd{fDZ+|B@h(eG-@d(w3tl{S1i>Q3z5~ zoCvDg;)_tKNOuQk9~Vy#KYu^uw49k|Z(2~4pI($&j1;Zz4lcf){@(r`u);AkuK;^Y zxjVRed$_s!`Fc7Sn%P?>WtP~R24|+{rKZ@Mdghg+rXxqIyMvpbo4bdTuP>r#hBQaO zrD|GcI>KM>4j%4)zMhT&E)K>PbbaQ#>48oIJezUEMukNf2k~dN{aw_`3M{c?F;rRGE31 zC7FpinN`Rx@Nn=5@bGr=bp$mNO7fHQbD-%EOY!32;N|D!?c?L!c^IQgXI zvVf8x&do(}%Lp3Yt_E*{v-2IZ?#TD&Ed79*AFUJg#4PM)s*&R!0|8Hq)y_NKY{DXCCjVu>R!2X}u^&&|u*!CuA1 z4~|WpJskbqJ^kG6O+9v`4Qu`vr)63t*8}3JFy#uOqp-pIreZ{EO7b6F( zkAqu4fUBdMFH*ZZBQY;8HK!QtdWb7MIPP` zjwvai3e(;+EipGU2i7pdk}drlJlvc;T|Iq4V+_S5nR(z|h`nhd%z&cQ!ctHmAu2vU z2TylLXD2UbZ!FmmTYci^;Opw)7vSrO8aRkL!rl~=N03K(octX8{9Juqyj`5Y<64jb zCGfx&qFLyclb8-k2`bKRIVgG3-@(br)y2~-z!fDvic9hf;Ff{nA7!M$$=|`*)z8b_ z%@L7h3c&e4II}7hRtET&mS8Og{2g4KoSmI~oRMoRLc#Cv;O^!Q3LH0h^rt{-Fd|9> ze+SP1XCEJLA72ztLfXO55XI(6FV6rkH!l}=Nb3SrTp_jauoPMT4t{Rl-p&C&{@}O( z4Ff}B0=ZCt*a~T?*dqp;o%|jAy`4OK9eqLNzMBtnM1~fFvyvxx)W#XyiAQM%1%Mja z-T|Kekg-khI4Q*9^2D-K|I(81qRf(1&=?kI2n&?f!DSSZ(+0WO@4NojVhX(9TLCy7Iq<8{p_je0$@^f@U@gugN%E1#>&Q1=V-tJDGE>7-v zoB`{&L)`)QyEACe!`sWt#ShgL$VCMtvM@>vXJ-cwCl7Z}3mY;-10e%IEfZ+vV~R3r z1FAh#oDmZ-&dv^=j{c60z5$5b4>BVlvADP-qbR>LJp-xM0qI7@XeRl(`Z~D=AeAF& zrFkidpkb`U9PBw8IShQgJ)E7r-2=cOh?eY;2K>-cKC%gZo&jzFUhYotQU&A_XGrB~ zZyJDX(AF|&;0ob! z4+jq~Hy>wL_W%bY6KF>bt>X?ZtUW_A(=$pyZ5(91{%(%GZtlKbU~gl$x)3x5ZEsqT zQCXasoQRn9bM|y_c6Rsj^np4YDFY(A91>_4Gc3-Y4le#4UY?FVkOB~gZq&L1;c`z0 ze=je0Uq4@e2gi~U&`266o0x){-N;dlC~Dl_9|u=Q zSAQ30XeveM_Rj-_EwohvYfco~n%CEcbJ8 z_i=Oe^7H|%lR(X%!6ikhiMgOoa1o-&EXvP8bQYXJ4QOvKKPOMj_6@S*!9@#rtiawh z87Zy#IRtom1UP!ZQVV$E6w=5+)(&%eYFR2$x5L@r!8^d!)!7-mmIKx4MCcB1@OE?Z z@(l2U_7DRZtI68PbdIWem`$7^L(qslC&^(buz#S=gJi9nLc)NJJ`-8@zK~v>O z3JGd;a&UL`^zid@gXU=*u>|Q}WagEm7Udxp0Ju0gczQVdy1PR|3N+mhu@O2Q4Qf^( zM;B=N3E^xf2QPOIcQ1bz)K)&hkaBYH_i=Lbb8&&>DIDbxqC5aO8)2)ngR8f{v$MN1 zB%E=j4IDmp1`Xx=x%#;HI0Q!qhq(IM8#$L26{Y5ZrX5O(@^gYyOYDuD^YcoI5bF_K zoEk@OSj~aB*?OpPZp3FUY$^i1|VnXNLekH$NvYCul6;h+g?&pp(2Sa-spxz3y zKHq=`y z++4lg{k%L;2ecBC!DAd4VTEwChl7u^i;J7L52Pi8x9k89NFua)Iykww`S}HSKvFen zTKxn3eI30B)N43`%G<%+&C%Nxw5kbI-r>r^IBF&r9|tE_Pj4q*M~J(TwFjlbtpvAD zU`=L(r+pnfeEnU0{2d`l3sIpv=GjB0V(m@ylaoPZLW;dFDC@0!d)Vp^a=aqPWh@tU%W7*tBTlK^e97J)_~5QCSlt`4rQ zZq82bPLSRahO?0bz-b#%gt)po1h{)U`}zAgLc$A2sflR1C6*$h+11U##ofcr%L6no zi8Q{9@FXN#!vkXJH>}Y2DrutxdtPawz_&a zc)PiKd3(C!Pa8Na^>lD`a&>p}^oB$cQe;9BC35ltx6sp&mXf)8J2-oKdHaEu+Ttrj zAo;1dG_wSG>8PuZLx8)Zqm!oxYSl!@!@dr#0X{wfPHrwJRSRKl-rjy5PJT|PqkV+5 z`8oJGx_Wtdf<`Go6%zJ5kJ181>La`QJGeVJIr@9MLaKg@@I}e=poJHR)a38rnaUq-}}U&!{=b&BY+4qjfqu3j!KkhF|kN`@3w zg6krb(d2l<8dNt|2R{!dXLoUaFg$gRf_R zOMoA2P6B&~Czg~XCTG~2W`frrA=Z|=d4eY31H2tEBOBGt?x`i<8C8V0y&YWLT-^Qr zoiY2Es9HUt)9whZz79^FZZ3Xqo;Ye!yut13;Opn%>g(nLDeW+V3?*rUDq@7apt%W8 zM?ZH*NR32-R(D4SH#aXgf6%gP^diRvygV!=vp6|FuOu_CG_#}%_UQg(KCa`5$cboO%uO#@>~Cdj&T6G5{{rD~Ez7o@F*p*uJ=IUm%CPlSv|*_$RJ+79lX4qlG_{=Uwndf4B=$Im;!Bftxi zK9F-V!Q#g~z`@zi)7jA%R~&(|vP)?}PHKg{A$)xV@)j5mF9&aLH$P8z&%;_;N}#To0qeTn+GI?kfz())xp)-%iG7r8KN5_jzJ-Y98#bm3GnP1 z-1*+F4u0B6`#5+ydAT@w<7lda z{DId19|vdW0CyKRXBY5t0MN`qDpIZLIn)69mt=gXX=EXWV@p zTz&lfyu7>|Qi?$<8c|oZ`#89H`8YZHcm_BGl;#wtf~Ps*;pyYx?i=9a?Ck03P+FW? zguWymR}WUJ%N9XkQ06UvGbRA8**& zyI|0&RJhxG9o+mqeB40GjzH_{ii%1Lke96bI(RsHJGp!LK|JPPT7u#;Uk48#e~$n^ zH%HJiac9uVWA9YNvLjyyA4e}=H$QiOl?9ndOFMiW zeEgh!J$>B0pgsdzfUMo$)x*cx%f-P7w8RCv2m&te=iuxS5a8w&fV8e2G>eR+z{S-y zAi&el8En3PX$fl3`8l|`ySaP0KxW>d!y`z#J^dWry?p)r91>H?5(_fX6NR6HSAe&l zr<0SngJ(`oYIod$BaD3j^sptCr>Y54;L2)&^ie4Dw?9iymUn3@^kR__j7h~^YK7hLW#T| z%HP4+-P6_4*TW0E<;~u-1iY;yKM%3C(%-?w+27sU%iSH62tet>JGBydqkzAItGBnW zx4XX!cw*5tFC{ZE4^(DB=0y-|#{3=Jd|W*I9o?NFK@RJP!ad~g;Nj%uhA|O2Q&o<&iu$`c{_Rfxcm9KIyeS+!UhsR`5wtMZ&yEWuK<6@6moE|s}Ua4+`WC> zy}bM(c@&x$kk(83JNS6GcsqN#`@^!SnF(q}^>^^~bPDhW?H+W_4@oS}_D#)oL*8}c z@8IX?U_<%WnZ(2xbv78id9KPOMmfPjDihk(SA4D8Y2=NaJc3`*6|Ela&>gf^1SFjVB1%C7SW7&Jl?)<4ZF5IQ$Z9Dh+7u)Q z-}eME&d=4~$<4zT)HngDb9Mr$fEZy3VuIHvsyI3pgE(Msft>^nAFvp_{4sR$bZ~KT z_w;cF&!{;%f=$T>=>xkk55y_~5pc)(f_dOIsg8~iQDcx0k_$Z@TmpRj+&mmXy(mXV zunRmuYEnT2*ezf&A2_!X#6nmLZ^(h%=i}z#SYB=j7_=A)qJpDmJ@JI(6j7Z>4o(=(?K3)_(#>g?m?3`u4rhclLx4BsJZ z=;Z6*;u+xX=jTItEP_J~DS!AoID0z!x;eUny11D60~YS!Vh7dt;E)DKB{!PPUs+usjs_=7zS&i7yf?0B$W!08{JfRMa_83`UP zKA`OjpaC#PM{0Nh(+Y2YPgj2zHzY5B!y25_!5J4^2nK_^4$iYxAQspka4&#;ikb?L zQmMa#j}vImjR$DBmpZ-xm*yx@?c?X==H%oDDR#jDMXE0#RVb=2kctR@2hiSjM;8}J zh9Jud7c2pVlIQ&WJUkrzq4gX%)qu-SaE1UkP{831&Qjp`A+31!cL?zHb$4+GuU;WO zBEZ!)IO@?N9Gu}0K?GV5>*46)=?0$KqR4#QdBWMz!PCvv$!ATcS zv5RcJhqsfji-#j5D}zl0*PY<}3@#R^>m4t5p8!{v0MH6nXp#pf1+a_3o}#XG-oBu7 zQv4ttYH;v@Qv{d*+X*f&z!rmZ9oXAob>LzQ928(#aP)#@5j{O;PX`}QCue70@Kh(% zJKzE|0FpwpOKpxt_K6oED_f{IN@O9X5sxJ(B}2{`J&kqB-S4-QZS7i=Gx3wAo#BCvPB&I0?S0^$^e1)wdho-XcApkY!K=ztEj!_3>k#m6PU z%L_bn3AF>95x`jj9B^RA!Gj3w5z+z<*+;GcuCCrr-jGB`iy(7z@^$rc_ki^J!4`lU zIA8*t#K7TCE$_Iw`?&f#LmL+0C`ZIBILU(30@ZBu^Y!p`2QQ6-mPg>?6&zJyi@+>u z+2-Tx=;G!a0O=@!{RS>X5mhL~6$o;|@bwGuaB+iQ!G>cK$?Gn2%&G_q}; zE*@@9Zmytta7RaQrw{B6u-CwLfVp5#Qo%MaH)n4TcSyqmJY)hh6FNE#js$QDM$HD` z6bcRjuuH&!40agUiQpW7)N}N8@OAO@_V)oV4}gvhg98nm9KiYz{)4%If-v-V3-I%F z1n-T5PPTyU06Q8SW8mBgb~F`i3vl&wcXW4zRQ6y)!JYvJ3@Pal(icV<7C=ge{tiwa z9={GDaqEi}E{1ueT$xLq`t>9Dzjsvh8;VZxmo&6o$JpJ800zfN3RFEr4aDah(aNv3wY&lqr zG9S75xcGZOYh?81~ z6<9UcN8sQChdpIJ@^y3db8`jnsDs+(3JO1P#Dhx+aL|FdRIts@$=BV<$rHV=15O{{ zfB_dY;Pe3wG0OZF;NCY$NAQq8 z%sgnT0j2l^=Vg*AF60t0z{|zo&(#H~nF|g*aH+*# z_X_|on}rrp;CP~9sfg?=KTk(L=Kz05yAw970#0&hjdXC7fCpN^J_bh)*t1{)9LV4> zhlelx+z*ha0zADvy&!wOpq@gMyWo@o4om8!J7))HCpUNZ0B@vP5?eFp~L#|W6EdVfqu#%EC8?tR4jvfJS&aRO5JDRt!*#QnourtUB zNl#}-Hy3wk$qTj(XS|VFPb2%yGr--|)dSiS!kNLqO#^UJ0Vf)8@&RWxaNa_&z{Mi` zsCz>fX9sU@FF!BX{2ACc;7G#NRs*L*uti`3T&sZ9f-@q*BE(?3i?f4|qrblsbRv}o zUhsAC_73m^Z_$Pp65t31dmZc}a8m|c-hi_wm<#p@>Cx}%;Ns@x=;#d{9|0SW7Rz8U zupQ)BiHLj9=1)I&Cm(;v3^dgP&eg%q#oNc#*$=6miLJZ?&rcvqC9wCv6*8gFL-vZB ztE;oClQU9h6l@hZn7~OE>^*Rr0$YU^rX*SA=I-nQx>y8M@q=B9R=tCZ1cX)KSVC|~ zvC7@i$Jf&lI!gey3QT}=5SRcLGGMd8mV)yKxZVPbkz$pHi>JH0qcc)l7M$(CUISZ& zvk)RGr6b3chr4Tlo4*fIaf&Ejz=nZi4cznshaClR<>}<@>*fRA-VDvU_`N}aRo)%} z9$t>%t)x(^(0m0hG{I>R9Eo86fujkWAW4ZU9~b`scOTFhW3X`wM7aZQxPb|T*%bK7 z$KAu*)dxCh4t6(~2nD5ma9km50jEWW|8D8KVMf@7jL8i3-EvlI2nRt4LsBf zPLSZr0Gvv|g)rDYlC1Lg_i=XzpKkz-EAZkVgjL{SXR27`>*(d;>*B5LRS^!edSN<*m?v75#zQS20g3DoWgdv*Iq{NlKm!q?DfD5D}3icJ+ zTr+q`23%o)GZHvKf)gLuKH{x%cW`zMaB}nWKq{EP=?R@C7FaJ>R9cfqL; zoF~BP2<#59i{O{27`lK?FL!tIaCL_C;=%b8tOjg6xNrdL!{m<84g-y{Vx z-`zF9)!oAfDG?xgG~m`5*sb7f1Gb5j=s^_X?hc-wp6;$L;7y#+ECinOK==b3Rp6il z+XD_5A`2p9d%Rt|Je_>OyZ)f|fa3)0afD@HD{uxHt~7vbkB_sjzo(ZoQn+C&&%k~J z2S2#<0lNa6D8LRN${zm!PfyU@37`@b97f>s0IVPEAFvoWVca3Ukd9$ya+ z@R3RmaC>n22W%$TD+pJBlOj>}csjcHIf4&vg2oTn97rs{sh|r z4t}s5;E)FgA2`b(dgxzTQ5Tx5!J~lSTn~-|a1sLB04{dGX#^|=wgGG_IGFKUK=Tjswert%lj<0zaw7(8bfi)6K`z*$aI10yM;kj2z-pICA9pdiwf%_<;^i zhplY_y9?nbuv@{|2V6^nLlT@K!Ro+Va8^dBLrk5ycsls{xCXd6f)7W7dIcQEV13}A z0T*mw&0qt;v4Y^Drg*SL;N}VZU_elC`8m4#c{(G{_<-#ID+Gr;*dlOBMXMmd$q&gk z@C-ci%05I;c{@0{`#T1B`5>+Bz**dY?Fai2VL#Ysh$0&n^UhAlb~rnF2e`O_kCSn9 zMCb)O5$p?a5P(faxE;&_7eC-!1GWepW8io~8c+9j09_8_3^_ajI>HSOb+qUv!ujBG z2kb1w&PNw-2NyTL0Cy+k;XQDk0ILQ&4s0D*AJ`{gw}P`H*iQ%_gZ+d(j66L2{oK64 zM>s%}6r$k;HV!)_1`bdhb_94j`TDy;3KOsw5Mct=Msc$BcJTD_ z@pbm{MjB@UH!s2S7+{Bk;}GFyu)W}RCpb6}J_56_`@_q{F~G?QvR4S~4e;~3&)flWjB6znx{MGTezD*-D; zoN{6aI;hpt-QUe0e7+nspM!k>b}87AVCRB$;|v|JlfWScwhGk(Z!ce8A7>9p?f}b! zg96L~TL7-gaasy?GJ1`R976v7{$Ac*{t)xQ(Mz+)2=Mpv_4fze@d6vh0tX4W3Iuxv zoM6BNIKaR@12-zbvS7#I2`hgGS6?q@PhaFsE#OicY#5jTSFhl71`b5D?0|@TaKR4N z0Y4+a(8b@uHNe%!+sPei#0py>2TnQQ=me)QFab6ToG1|00K^hRT=+Y9xVd>bBabM8 zeG4`KOn@U3Y!o>9!4U=a2$%qe4>%!!0}dQ!h$ZhX{tlj?HjcMDWXKn6AlOA*yNb?d1fS%m8aaEtbG82Ad2v6zpQK zq2PE0H><(Az)HYkU|IN;tDpqs8xY{@k;Fa;Zp$W34_I0C_*2m20e7T99k857xjZx45OHz$ly25_{2jUg$J5ZMwX zCcHg7y&aJXKZG)HP=f;*><_T(!8U>e22q5ABOB}V8?@_2%NdW zVGH&%SO-$A?ds^@>*nw3;|M;+6}q|^Y$8}UT6`nY6F3yH=4nv6L$x5l&BNc@)d@0m z4Nik#hk^qIcai~_19k%<^}z!VoN_@cO%UnI*}>V}-Pg_E2R+iko&kpkm;mQ;Fbf>5 z2p@qR1}4BE2zDZ35W&^i!P(Q@$=$~tse1t~3c#wd6(Hcw2iPXC6A(TH8x8h7q5yPt zc5rcV0-e)^C_C^Qk0m95%>+joIN-q6A{Jr0Iy-oJ`MY|%BQL@RYXOG@*dK_n2B%iA z7`Xq08T6p|frx@PHy}s3vx8@VkB^TJbchPR_# zMK}SRY7r~GT%8@f{M=mpoRM=lNwpDb;DLh-oTgCh@Nsr`cXx4vG&jM{0%svG3+!I7 z%W;;0VBetk`;Zf?kE^4fvmbPe8rUpwc!Sd**b+$ifL#Z+4;*}8HQ?|8v%m=q98!?T z0ByfVw#3iV*Tu~rd@?k&2mwb2*gQm%#F>-PLKSX@AF>_(E}rflkQ)x59Sd+V3U0jO z8fQhS+(0P_Yik!_hpU5&yO*!0C-~%cJpKTOIoMvXFTwEzPCVeS0?UF4Fc*I2x1p=6 zgO{I&zq_+9qzMHM7;uV(ge*ufMyuC-h((u+#9S zC~$m%Lj=qMM>{wgz^+5eMlQ%nD!|3f*U1NYlL*)*{GmpmR7aTa4m#u5-O(F)S_o`3 zI9-8V2X-+y>cIgB&Jtj^gG+8iA^_)3aI_R4oA2u58<6-~a#{3Qk|(&;lnAEM+#>6<|qlr2>g)_`%19uAUCg9$o>U zBN@T9D%kH}qro8qPAg!SgHs{c`H)Zmg$HW22QCLeEe?(p0PxKa z&>|4*BXB@~y#TfrY#GrJjtDDH2X`+wUq5$3#W*-9$SD3i9lX3<-Q5D9+b&=O_F$Ky z20PdWu-Cvr2=*0N4D2;Xz}TyJfO9LH24{Bo#af_f@dHgXIzo>d0|z`f+QEqj99-b2 z0<*x509%HT1uF(?gG4#lK}dPq+ri1r+1bqldSW41IoPRS6T#Iem<2Hc>^ykfqBP0D z$pNer>>x<_h=><&2bTbUCwEumsd@bE0YpjzCk=3@p*J3p&3AJRaQ5zZ6gv}e^ zFaUc49NJ)SAh@U=0_SzGhY%fnS8oS@U!MR!Pv{nTupQu32+j^zS|Q+63$_PrBiJ&q zq2OWx90K6t1*wkpb#QWX^Ye9uF8v2<2FC=L1=b8sAz&-PCV*XlKScx}qQcj~)z#I_ z&DjlU5CoB6zzzmG6YOBHonV{5PC%q*umoy|Ar)1=4z6zg0q&j{r%8ZKp-z(Wb@29Z z^7Qxhz&b8ZEr0m<_<9AnK)aRT_y%Vwa5R9!0L&t{`tx=0_j7Y`@`Ii#2zE3$J%FXpWqQG8o>g45bQUw zFX1X0J{<_ z0j@{DT(I-OV&EtM>pqtv2Br`#DmO<5KX(r&SEm3-uNdr5 zNYX|%4s03N0jNn7>?v@b0ZW3N0}fk6G3(~&;O`U=;P3B`R1AWH09?3&oeVYx)nstK z1e*tG6M#(x#{`o55a9(nZ!W;u#R+*Vh4=I;+#aSBeQ zVCRFg5;*R_Q36h&U_-$Y*zz4%9oQL&F*P@52S0BwPZv*T+O<2}Tpc_-TmyU@kyqw{ z`-tG?7{te*gaeKYFc<7Eupe-TCn6r)9sHbpT^)VBkm^!!u>lS!urc6}0f!OJ`Fr$O zLYVLA;OXw{>Ji|AR7YTQ7dRauLKz&ph{S}m8|mih;O80O3R=7jt{*6gV?^j7m95?m zPR=gwo*v*!*O2Q`aHxQrm0*1cx1y#=aKZ-XbZ~%zt8Q>nf*AL3^LB7{@^tj{gB}G= zi60Olh?Gvf9o*ghUA-KU+e_g30qh5G$^knL9LHc5LJZY6=qeEL?Cs#?;t#q_4^juA z`5_CGM8R$Y`yHIkz?OqkG1xY6jDsx)2PIexaWIgZuLJ0Cd}n9yZBWp7pt2v_9UXlA zoSof3_ZYxt<*99jtAnebm!GpA^0G&;r@=u2&br`W0tYlWguoJrdGZ;2=XUbU+ed z^TGbYS!B4oJGg=_G;v1Wg+=Xnc6V?OaQ1ZdfvyY$`yK3PaCCwLfY^8jt3#`L+}#~O z^-h2*^pHE)hze*QG$IXY$ceB zYBE?7VJ#xZAdQl{dph{~`ucjgAa4|+X43U`aCUcbb#^7XB@Rv)U@w7L;J5~R4qQ<{ zTAGkBLxdE_5?^OO=v)OjgMlL$tPf0p4Fo4LaDX6!5fLij1}|7KIKFa`jSq10bn$Zy zfXqaKodh-?Y!)~-BDjb=2o8Tl%N6Wsusgs-JaQ!XI{0`w`8&HnPap-G2zCy*L<1*N zuttQl!8U`7Az0Eytb+k1cep6neo!Vrc*oxXe4wx!^pps2FoTT;v%tOs2Lm|3!L1Om zZD295I!O3{twGsHftcR$aCC6;3h;Dyfu1-7HW{o9oZAsW0WNyMz68e_*m$rKM7)44 z2a6#ZvL22O?oM7FPX5rXQ?xMO-__a02YHPyxC{VCG}!%MSA#|k)*gX0t&QeX#z-2x6RaOnbOLDDWf!+AJ6_<$~x zboWI%>jSJ0Yy#NJV13|Z1!f_{P-6{U1+pCh&H+v?F3?svIK06|fx{cD4{RH`8P3(g z!_CRj#TRu3_-@C0|(5$g>+K?l^k__{!DWQVrez^ezq#()C~ zHT1zIgF_L_0{aVWFM3G{_k*XagTJ@0pPwW6!gxnVaKqCcGX4bKe~WGFG$cU4(%=zJ zu#I4kgS`PM^$|BLc!IX5czZZ|As^%qwg~J%aIyq@25b)4y+Iv<=>)05!6gy61r7EnI6;A3jmRiqJ5fUhED82CH~_&cd&JEtUak(#P63Y2 zj^0Qs1*senUak)Aj{ZLGzTQaZ-BI5TA0Jm&Cud)zz@oMt?hejwE}*kSk;-*y+u`fr z>+b6A>I^+T1uap5GX`3ugHs!%_y#3pa5RI(z}XM11JSkg@^|nL@bq#1y%6wS!? zfF{}8ygVWHfX%0JLFnxa8cA?<^bUa70gh@a7lhuf4!(Z=9s!eSCaQesW??d~r!iW`2A@QD$CAT6{i4or8m= zgC&EnW4wE)r%Swdu#01eqnm$_uVYAjaHvyAWPoeDr>m=Lyqk}|V+ekA0nWY*@$s3( ziSdbLiJ3WxNja(UDXD3Rr8y-G_?*BHA77N3nB!n<00D5m2^!x53*XSd0IojK!r0Kr zEDg?2hNy>;$l?|T7KTWCLjxlN6F5H=q63`|5k;~u)xb2-BGCe&&e$-;AO*=BljP*& z!iJz2Wo{Ypd zGO4FRBixl{l4NLUjIb}wG$qM6#T=pDEZM@?G!?-&H?T-cGD6}f8z!43 zBk@y`(@c@v3-KpHJ`n_v+>>N#V3KBxkVlO;gm_9)a*}zHp{bEss;L2zd!P{p2^K_p zNJ}+KG_XufNlLRwvPAYjGypNwrx_TUm?x$}3530AxXiIMGE7S{H8C+UN-;#FgS0fT zxEY%H(BupiMWiDW0|T&nbaP2F5tVO>l`@8lExIB*XAPExPAfv$;rXFe>+&mZ`tO&{9$p+Z-W3mDE z{E%z_O23FS0k_W#u57W(ZY=L8B^z)v@3NZD3#kQx5VZ z!ZdSeOkmO|ra^+&0F=hSwj=m(WgsEk7_JNy1Gq6vS+YTjL8?KTfrWvkL83vDfr){s zftdj)6@uz-Q2hb+0!Rr+DM$%ODM$%OsgVK5_n>MYqzqC97{JR$bUxHlW030&3=DA5 zFlEW0V6ZR*WqnZF0-c5_L&Q2b#K8F*%z_YbWhP09X^CmZsn}__GDyR~I2Ah$Q58+6c~-B%ov*U(P=bgumT3BvP5&^6mwJKH0(6oG-z&ty3H7rkfCf4 z1yyEZU}R!omYjx*hATs@Y;l=}RtK7(@nNPJq9!;KG(Jq3abi-UnHj91fh%}TKm}B) zfdMWWW*R6*8WVo_>*YDIEJVqSV`d~r!peqMTfYDGa#W^!gp zyrCH>s*R0EQEg^IifRjnl%lkp#N_zooc!Wc7$>bL70%Ag%PfgcE=?`YNKGw?FDXh) z&W_J4Ni9k&$uEMb2dgQ|FG|WR26;X=HLnCiOz1?(uBFPEVZaOGasIipu*6nU@j|F#$BZ1QktA%tM5v8Oi$ETzw=clAXibjZ1kZ78*5h!KCZ3KxyjV@0ti%-l+EC>aE&NF18IW^ zz%`|rnV>onDu8ezOaP%Jwj3X{%B%}X!Iz%E^ynSx?6QsjV`@u?M1E5STa(njZFQ4*h80nVQw z2f+1V6G5v)(9|Q#V$)reSpupj(R6`CK~Jdfcl?h%C$P9+F~aVtT!2$W;+6!D-`05%GrI(Rz5uM*91I87`z zG$YnTBK=ZqY=mkeG_m5YEaCpfqY}+O_*BB(iB{D?Q#qPwd=jkKK$k!y0W323bb%Y} zAWxy|!zqFA9lDlcLo=LK6&sr2HW61S#1|VI;Wp9O2-U=*L|E|zN+>1y&~6UMb5Ma| zxJAWAXjL3k0O2{90746-MSuutP~s`ck4Msir8x_-y(BR^H9o&QFSV#RBeNhrKMlG6 z0Z~u{stutPHPJCb8_NAjTRhs zK;(<#Ar8q&O~IiKA`g}<1ubhxDosmEEs6(sr@(a>+zu4!lK9j-h>^&`$@zI{ndt~; zA10p2M-rv zlSIk_$dL=uSDJ^-ZG`Q_VI)KtoS@-lDzcSu>yk1HOA}M#lXFrNi{g_LlQU95o`;E| zrf;YyDA8u5CKkjOXI3Gr28n})Q}U9*=?tnG78gj|_yW+v9nj*U5=cIU%H@GN*JybP zSqjZ4u=1i*Y@!hFBl)DDC_g#1xEQ$wo|_mCH5|hXB<(q=c`2|8BB>zFC?4H?ATf~7 zQ9}eKiX0vwF}P!49L%r)$>4AROjOKW<07^gf7IG9kk#Er9n`yA5y@^rxoSr#;2qfCl_TF zfCkZX6XS`}lAD>6lUbaaoS&BhE~Rm5LRJW|4PrAWtsyjl#ULYr@rh|AsYUTcsl|!8 z1v#KGNr;<3!*?K7Mq*xGY7Qvo%2JC!D$#-$G|hpk03)?wSCW}mP+AfXlY^EnFm7^w z9@ILJY5AokM5%?vFs3OW8H^NzsSJCBW2!@naA^4CCKiCgs3b8nuNdyBg8br4P%8x- z_$Ue?RUOP=h~|RI`1~UH(n6%L2T6m10jeC`G_c>2auV~hvC0&eB<7_g7J*v_P!r-o zCT14LW7kv+9gISAL1uA$Sz=Bm#GIVW0<^L!86*TwL(o|nkalD-Xqym49kelsDh6qL zqKHA7f64iICE%GP)M6hdoSIjf3!0k(Ezc~=OiqPl6sR6h_@$(RgyS>w((=I}g&~EZ zBPTx{StTf>QTWC2Ir;eoNuYVL;?jbG{32+D0do>4_vfb}Ye7mmkR%Qnt%cbPO7u_> z(AX0w457*pJ%)Jb$Rt>@pb}h(CL(t~KwW-t@FDa-oPbv`dfep`r5YBiM5>1LK0r-9 zJkEx>1lk`UQZ+1%U~@KVhYY)cP&LGa8HQ3=4NRoTu)Z46%JBsorepAi8>Vt101UNbM#pYM_onst9m6 z3aSvlX+^1prI|&kDe({|;`b`7^h9$NNEoAPMpJ{mUPeIMewSsavw&5k1c91--Q6*$G;6#Cxxsi;=nX#d2;0YRu4+=)4zyeKF zXO@5kp_L)BTFh_)85xffm{1eS(uz?Hz?QzDW@AshP)X!yPtH%t%uA046%(N04YVF7 zbn_L+IpC21NPK4Im82Hsq$ZZ7A|)1xR8C?gW<-MoL2;3inw$t84@yae3l*UWltb$k zunJI6Ak59fGA0Bu2`q!f281}WHE>ZByTGDYECgjGG>3zgg2ge61&LxBiyYt}pW~=w z!F~prfMy?92-zx-0E#Wh7Jxfm@%eeEgZJP#D=GzV*^5s~O;5~&1S}D%Q}a?FV{_0> z9fsLOsYUsv(C!#6HN_=~upS$3CHVyfC^liFMzCu^5e@D2A^8C$3hQn{Z2$>DyOl^H zxrxP~u@%Vt5t;!ga`7oiXwrF!xzLOacSvywthoc}@n$6ErR1c-^AoseNlwf`)WZ;E zAeY7`rliE@C6;8Cr9v7d$VCv?0z`6zbD>>TsDCnx<1qs^D?cf|Bm-PHU=Khf1!y)P z$-wI!h=Y-23i68~4K55BjARH>id1lbeE~|mDB+A0$QW{{aRBx!vhtFA@SFuSG?1*% zfh@EIRZ%&hWdvYNImMN^U`A;{PHIJbQ4+#wa1n$Vsd=CkUMQ6iNHa_r7IMh^_;_$O zgHC^;$bgn7ple5}6HwGb3x9MmNZ}6?EkKNm!h|s04w+d+ZR4corGsj8w4?zQO#>~D z1czO5NqkObaY<@kY7xZUU|par1?|v)YkR0RkT4_?(A1{prGQ(qXu>d)A>*Rxl8})g zB%{FvZhBE-K?a)AqEu*`H#adaF&)FDkkJiLj{}@-kxVSfgydwX5F#?czJxmuDW6NC5$}@%ap-9iPuY@>qNZj%MuX5-bIZ4U`T%)X!i^v?zp& zVf1&AH6iC1aLgBgs^{YTQpgN1R4GCbTKYo8!9^_A%mwmbdSYfCwD^Lm2Z^F3JdhBg zCW5I0)s;voy%<`|L8Xx#jWQz-nV(0@YD4(ws$moBX^APABQX#`P&h+7Hqd1jAjcr& zz?B-JvVus!!XLuJ-ok;%f&2_&felT~OHPeXOa_l_AOzrsfu)O5b5awF!I>}(EP(J6 zgb()7D#MINs3^#%C>aJS4stul*+^WBYyy?T9+6N~46F*LOVIxHWLYy>1d zg3hMMFUl;bjL*zVNv!~xlaX1Rk(z@!T?-cnIRGV;P-Kus<>2D5@Iw*62tl|k_ArD? zBPw#Z5F*5&LXas_w6YOJ4$Vyv4H!m4gprIdNHaw5uw{V6Farf9iX0#yF}TGr4rV}r zWN-umND9f7ATEmQFlN~^KvUh=TxWznl?_&j#zhJ z*cGVBsQ|JP1~uTojsd4nT(j&M1!<7zLXUQsIFjEW3Q+8G0cDhsRW54 z2NFmO?id&cGjKpMI06PFh2(M&7sbU!=!@%eK$RJK;DE$HZbOY)m?)}?A$1x!y@3>f zoR7`LxGFfX{U|OrLMr=nK+DKbR|A5DFhUr!&I73f`2p3nh+KxN&I9QKcik`p1(E_l zK7@J~B7ow0h!9Gc~uh0!$vQI{_2M)tdmx!#o0G!}}5_ z0+6l*TnN#V0108~NPvW~_9H+NNIonrNv(j#MlP6_lb;M;)db<8t1Zog$%CUOv7{t1 zIRiHDo>U61*}*bs5eF7aNd>D;%*jF00a-MJEDx=)kWB*xK1x0Ss|Sff>cFH@&{lZR z{@dI{NaQ2rkjJUu+8_+rP!fcfT7u*(#E=v=Nkm^AT^6ft@X=woYOqLrQe`}BAOO2e zJb3mFl+e-SF|?ryfINoYD212+mH`D0rbFRU$fI>=y0BOSOTkDgv7|^OSy0uD#T-OZ z1FJiA#u{a$rj#OGhRU>=^Rs`Z=@e)`Ldz}b12T{~Q1<+gz$-5Y3M@e!9LgVMj)2Y( z04)kkEGPhVjX-Ka?gr6lrlRTrDZs0cIP*ZmLh*#mf+hj*urWw2Hiv=K;Htjx=_yIf z1n>R9r3My}xa1KLh(!g+QMjsN9Lh=X10JVg$*)-K!&>oTQHSiuygbaFWC3U$coDc! zm75PK29SgbAXB$c5rm_`CSlKnP_>}^fMy^_2xee%E+~tDmgs>KVlrs;JW}@`x)>3Z zp;A(j#GwliF~uQ^4l%_ci;gkGA&Uz^!qB4ykSv6XAXx{DEt1l=wHDFKmeL6wFThsn91iBeQK&{}Jp8bKXpXrhJag(p!27dnpuitM5! z$RerKqWF|d&;t5oaGeH~f{z10`QR*u7CK-tbiX3yvE*FPB4AJ-9x2jcdwUJdU><>q zAR-$kf{19S2qKc9`+boOhU^hWN=Z;P&;|G)kHAEb42I}JG8nOMI5!bIT7tB37)=Dp z#SmT4wY*@fAR-8ZA^U!j91IaaaxYW>VJcJr;aZ3Q)XdbxY>c)>0ay@}Xi!>72w|j_ z21F2^NWeNlJdAb*L?Mn64y+B)B!H+zN(8Bi*^m|jdSwom0NDvn3vdxs;~?4*Mj~Zo zSb{_nf~QB27)HKE5kyuB&ACVhLPd}jfx;DAJ}ycE*BJ;YXdcE8hvi>PiISpBNZv(S z906rQ^DK`1T9gFZ*92L-Rg?rdMhVFmsCl#~32Br8x&aUr^f<>>5fUK36BtxQRg97V zQKcYVNTj5QDh=&j;FQNdaEdSsmatGoFp@Vy8ha8)$ioXogb=76hmDG2iXw(X5pqZg z8Ep{-s1<;`<{EvG1!S=jv=#xy2evhs1U2Dm#}U*BZZ|__`=DtCvQiwn1O&r%xLSaC zEkcSj+@^yR!Pl%3G!B-3&_f&SA82TTO$6_lz;7JNDr~$;L3t2deSkL%fQM-as4oVc zwx62_%FejG4$jZ$i=*)x3`(J(CDG7j&Un@0Nt@u%f-d?&E6TAO2~|s4Iz`e%S~^A2 z2zDu&v~&v92ud)xa{-bf0_hZ4C1lzhZ)k$7g$zyNS4KoSh1v|=l#16lqSG?e z%%W8MX&I`FgtQFTQ3BqLh&L@m)e@PO;k)p0gg{AAYGN+9dMCcLEkPbw+5RIVZgeMgv6yYsHq22`G4R;A$a@a)~h&StTJ;Vatdyf&^|a zNCgg4@FWSODQHlR#~KW1YH(VD;uJzAp*XiRrv!dt1Lz>v+{F0e)ZD~^jQpZhcw$0` zAgv2T2qHX*5QD7`EY8dUuO9?;j1ldj;+*^v$f-Y|!%iESf-B z4W$QyrVW73mxv@cLEsRy;%y7U=M-Z3YLJ>q-YYzE3c6Z zf(e6$dtfWEap+9UiAN3!$X;nQwfO}FIjKd^Q^ru`Agggv#c4J~J;KQ$2JX7@@2n5t2YqjV;K`1D(%?&!q*K zCCT8W`bcIWozHcP^W20JcslQhvv07?Ltvaf(?WfNVf73c&6{vjO~Mu-a1%h1#KjEiK60!!fl4B5 zMF8A3kOIO14pN2OTfmkEKxzmEH%JkY!3|PMFt|aA2nILy(iQt;J#r|53@2E~f)rs3 zVURe^fJKocs*nZiz+1?IRNyNmVe;5YI@wGI%HDf!?TD2Nm|2#mpl zk`RHMRPZTEa1Qt;2M8}O6?~j6lmoioDhGN*Iz*_r6g-m#yJ#afF&@f<-Le4^Nh^k4 z7zyQoP7MOPEv+~nb`SvQR4@=5T;+q~0@Tq$R)s19>4L#^fft*AdGPa)QSC&WlMFx2 z7fm_HcDQ{AQ$WHMuKlvJM%SnOFppf}H*Yy6Oa9W`xP3WkZ-St_%p02PG$v%`i4R)1e4Jf)Fl* z*Z~F-0(l&5bq`1w6b2wKz%xC}8bp2r9q0!tHPEMt;NnQGO9d;$ZWlxjmxVCdl6aI& zEpSUf#d8{H9bGE)rT`Qf$XO7e#cSXt!6kTtE#SnIp9{LY71<<^ zaB5Leei6DLC^yGL*J>kK08$U0!i-PL$xkf7&;@pKaYlX-J}Jm}4S08d3c3m4vIfmU zXoO&Q4MegSyL(V1@mUK~h}G#RlDJF(ML0M%k$nUgj4y^B*o5RYxFmY~gC#H{7c7h| zIe@YOvaKLKRA+>kfj|+YM|E?lojU{6lLa>q%m;mE1&{H zGgCuDb5nC8LsW4CDA&Ts!o(QNPT*o>U|@JNjfr6%I|BpbC>#xe(GVC7fzc2c4S~@R z7!85Z5Eu=C(GVC7fzc2c4S~@Rz#9Vg_VLaR@dgIYegS??jvmeq4h&Es!vJr0M-OLz z9|wCCXD_(0k&Cypx2LC{3rN_l7$NTN>*MC+a5USlA zoqW7p{2crebCJw&bM<%ib$1DH@C*s^MpEqM)`-8suxMv*U8`2&)dVnGXUB1 zZvH-g0bU+{4nb~aCJ5E;jxO#Vo<5!q$)%~to_BZk@^|ra_H)QdOv*u0?&a>`=j`a_ z;N|Fy

)wf9C)XCpU-8yy6msY7aLLFJC`r7l&}iFhpuF@p24sadz|waL6gCKvM1H z>EY_@=j!a>>FyVVFvQ!*)x*)n#lg`h*bzz8!`s`_)5pQDGzZCfJ}w?Ue$MW`4v8rx z$ig1ZUOv9Qo(?I+$bR?raCi6i_jh$jN=0^upNpG!fRmr6Lvd~ak|6=ko<44l0san& z$(bn#hXgqL2Y5SsyE^!I1|vmqfS13IpNqST18C$HNwvR+mwSMtuY;#!5JI)7vtNLx zpRcd0LwIJMFCwj(di(phxx2bJI=Hwxg}TFyG4*wFadr1`_I7X$3i3yUm8q}0o2#3z zqlbgNikpwUinCj>y^5Qky%|DhfUCE^V}PfxLvdb8W* z#1bLp;_T|~=jY)9mNK?CL6~Od>f`0>>g@{FWoB<;jL5KNZcYJS9{#@m4&jc$jsczs z*^zn6a@p1^s%uUTNMWk7CKPP8*M;9+ghtRz2y!`S! zgaiED9KBtf0(`)sW@K+-Y=Mvq@N;qx@N{-@NJev=g{xD5uZM@9n?pcOVkPJ>Tev0* z*8mq!Zx1h52ba{u6wsM?2q{lL*8q2K4-c@@!Grk-IbRn?zW_H6cL&h=MpP360=#_P zoqc^Bz=uxRt2ld_pi~Z)PELN#j*ead4*mtHdBHyRDjvSBh|<^6*~!D(&D-6{p*%A$ zH`m@I1t}|9x_End_y@Q+{9(h*T>>F??6kyurWN(U^x-0`6-TZwW z{k$D=GxIXR9)_Fhct>?+>!H#NIT~-W0O*A-|{+S(S^kldlITlfYuf-qZzr z7YT|YFGm-T08dwl$5T=hlS?wo5|QczCr1YlS0~p17Z(?Y;1bZ)g>FUpx$dE!E=I`a zc=~&Jc=)*ZI{4+6*qdhN7UZOY=5A6E$=}J*!OO+b(aAZ$6;!dNdgeK%q&R^ulSekl z+s)6_+ttq%mPzbQb5o0p6OlsB$;rXh!_nQz+0)g*J+;IowK%&ZzaTs_FD1VmVUm-B zo0GGjx1YbigLA%fQEFmIs%IYL;2ac1&YqrrPOdHv0Y&)*VTnbViFqZ?pu;?o6?u5L zdIq@rx;g}a( z`lREULYG4;OOk*?&ad^U~dY6&fwcyi!<}{?2VC%S|=xm0B09Z?*MOz%kuN$ zi!(|~QjkjtCuawz08c+3{{SC^C-VzHH{53CBMK8IX9qVo7k3{YKR1L2TvE%N6AOxw zLd@C0-P6~@#mmRX!7&FkYg%b(;*5$WXO;N#@);p+y<{r0BC1&PV2IPCRtar5@}_V95C zOD)Pws|-sm0{Pc7FD*YD*xH&)y8++3{P>BGZ$U`>I)xpWh$IaQt!wXbigKxXcvo}pn%u7zq$w5`< z?CIv>@8#+R@<(uLiF1Bl3iuLh-^Ai|p5ESmzP^s2g2pYsC^fwZl={%i8dnETZ%=10 zcV}-0zfd0^dsApVj#=QkI(YgydHH&~yF==P+{C;}dsEO%sl6#^3L7bhyE^#z`MY}i zdU`te|P?TDbSd{9K3cA4r z5ln6lPF_APj@~YA4nfc}R&a!qn}ds2fQz5It2e>{IhjdCiA9xIUEt>6>h2Zb?&RhJ zOCdO&;O^oa;Oz}c!rHU5zaMg1&djqnEhx%QFG?*&idJ_A7hg|*Z+{P1;TW1%fIX(%9bCOV++6*9 zJsk|q>@AZrOYBX9Gt=`@Q|wJW^GZ_FQ;U$D;O6J%?&0L?izu2Q%@J^^nwFW4tk%Qb z&)3s2z{SDXh_JyPeqQd*9{xTK!Ii}&sk!#1jz!5CnI);opr$HnCF1Vj%j zP&1(jc$JGZ^L%`~s{338$2z)Y=y=hWuF;c1S z<>2J$EaJ><>e+;1f&*a=BIe(xq;R=6+>L@?3RMu=JR%N_4W1g^M*D5LF>$53$EYz#5S*W!omygVir+sz4o)u4&i=km2#bP2 z3*%DlO@k6kQel-(ioL15rM-%aKPcSMeCFfe>f{^X@968~;Fg$~1Ii#Jpe|T`VhY|` z)W^Zq+1b;}-^Cm5M`*nRs&k=DXo!8qsMZ%F2ds~STR?!Tqnj^MyBo4$qZsUZh(RhK zN8|H@kE5$kfSY>&xPb}n2HBhDr`emPq!xp2IfsNKB8q$*e0`jpJbZnTN_J0IS66#e zH_-C8%)E4a(}4WUJj9rQldpq|yJLW(tBW(1kiu8p`#HFI2Kc%7xOu>X3euYc)lyYR zv83Yc4Qj1`qD#fu6T(z+_CgLXKL>YTP_M|t+rcp<1yo_$o2Dh^X6C>eW>~VNpM!^+ zv!|=4FKCRRxFi#FMQDDWy=fxM0MLR-P#__K$Irpj-O<^}%h?-CHpEt+_&NBxdiVwS zdZGpnqK>dP1r1UlkMcPAIr#ax`nq_#IDyBtAOlL^fh|O{&@Cr19g-4MoZWIz@}$3m zlas59r(1w4N_>EJu)!?@#XsnhP=wF?9h_bLyxiR!5m}}Hod1I}t5RWQfPZNT)?&cl z!PUvx*~!NlxyB+C{QeH^ZtkGKaf3%c^crL$N(6rg&j4p1A8#LD6i-6h!O#%J=1DKl z053N$7k5bO0#sZfweaA#)F20opPRS0bAXROI4(fLz>t_gE)*cPLYgY}h{0wje+PeW zCl6moUr@R4=7SuOp~c{=Jglvb_8}d&^>QagGS? z0EYkRrp7y3e`K2YGtcO|~gS><04bUirn}@3- ziZKEC#U=KpIKmXofB-iSPY=I<02Bj)Qo;Q{>@EPg0)kPDa&>TZ_i=IrH=9)4d=O=j zPkw#@sI0@%{zL?bn*(Sf$JxaTH0Oz02qBkY>7Z6s3V3wa-ZU9mySqn#ho?VA0q9zh zoKsqyS%#(ci_q=v;OgQO;O-4>`J!6x2fmQo-W0UwArZM(j;!0$!`02p!xvKcBA1`Y zPDjomh%T42hl7i=mzS@Xrz?6|Le>l|yCD-3NHc;U?Op*MF3yl51()4LsU=03sh~jc zM4HHQ_HgiU^Y`#~gSs3MWyn?+gMu8|S_TbVAw2Hk;Nj)wl_{=j-p_SW*HSNdsjQQ&6)T zIf@a5AVRCRgAZuF*VhZuWWw%gL|lRWjnM7m;Ogk=@8S$ir3l^rd7!X`wn|{liDG-x zM5GxuXCDVQM?VjL7e{A20S=C9NK?b!v^cX0+42A<7gv9GFG!JxupBwSVUsHMrX?Av z_NIsh4bFZJuAtdEXKzoi?YPouGI;(1>IH=5eh%(FZmwRQKA?3HsQEJ(yaxi*2`)ku znML_Ih;Fkpr~&Qm<>%yy*}g$`Jh*59j}_RPCL^UaKZgKMj{rw6SZV=JoI)Bo$l75} z2Vb*{aJs*PcYv#_vom-t2ddMF&>i65?dIg=8Q=%WC4_>_)7RGp;c*v72WK}we;+Sj zykQQy&(95-r*Xs*q2gr-!qzyE`(iVB7`b;n%S>n+7=h+MA{!M!Q|yK$E+kzD}O_Q#DFTL&TPwgO6i?zpuNW zJI)*o?QMX1E6DnM0|LCygX3{v=Sj#!efLL!qFZMKF%&KZr(nSmJr^u13Vy!(CX>nJ+&Y0ZnGv4$b@1@@clGgigd{CQh3=RKor<+L%}-7Sl?f^KrsWx_dG@AA@#OE| z?CtFA;qDFT`D26`k^tJcuSrH<#k;4L zIKwS=b#`!d@^o}@c7Y@?z5U_VIOd_Vx3`-${TrS&KlU5QxD`S62sD zS2t%TcPB{i2*cS(0^qcbC_-Fa9Rl3Fo&EfM93kO_qtrw+-4aU?(d_Ex;NtG#=H&sJ zmqZ$0MtBmEtzk_@gn!)}Jp5hU{G8mOk&MGyB!f~BYfwRJAUym6T>OZMCa{MQ7JE3j z`1p7_I{QHTQ#dS!XOC3mWDT;|-`CyM%>%V62i@sxZ|dt9@8$0l9}*eh8Xx2uj9A+0 z>fzw+=I-U~>5e~b;IP!w!PUvt-ObY*5=BUn2}zX5$qU>M&U~dO!PcLsj&{A7` zg$N`+6_;j~ATJ$t^>GMrcXV{}^gyke2zl7o!8O3gC&0j33arO zkTyRDKSx(D4^Pl21*k&8p65|o;7EOBSAPe0CnraLZ&ygwj}g8onI5$80+E{h9ef;p zT)ezpF(VeU=V6+dl!&wr($(L=-_6Z8z{MAu2S{_ao3n$DtBbprBdk@5l#&VB4Vpss z^7V9e_kpx6@%kAx2f4X81bBJ7`1pH5+F2M{k&`1d|H1>z&DFun%h%P*#RZaD&2b+_?9^#4M+luW?!RNyvB@{PL&;)#dw{Bcw(mL94r?gPWU|n?GpTHF}Zb0$v`Ll3ARbpI4HZSDIN;32EmQAZlTECkJ;w z9}jmAS4bU>VKuUsAtx&##ge;|gRj4%v!5eq8W>wLLDrp{SOA(9PlJ2g-O0h<-^azr z6Lq2hxnu_4cnu!$L$=k~!Ozpl(c8%nM}|VS71Pbm4*nj_PN3yy;93~3k1^fs=HTJs z>FeX_1!=2c=ne*-`(SShI#w*V0JKyV(ROh6bntTY_xE)s)x-V{K7QT-9sypE^nsj{ z2^K%@0S?Z7p3aWGxZ((um0d~;a#Abo4dLq}khj2icsY1`yZL##gJ!5fWj<2r2VQzx zia9j}k7Q482PbboKhVNP=bZdJaH)gZPyrpz1)GjSX!CIhaCCEZbMt|uOJpx2M=!L{ zO9ho6h=BI=b?|cYa&~d^fTR%8bbGrxxH@}z`?xqmbYsLZD8!IM3N$1Eo?U}G-`mx} z&)wbG9W)*b%9cbK0WuOPnR|OXc)R&|`#U*8%5SV@;EgzM9|un-FBd0o98Fb_Kkyph z;hg60Gc^SMXFVO9Gv~Vyq!F}p)(fX?G&J8g5Vi<(7YG&jJuD6tB;?b zmzS4A3ixDP)K%?14sKpPPL4jF0S*DBImM~qX-;@}`Z&1z2KYETdwM#Q7N-`WFNp{F z)7{O{*B7!3&@&IT93M%um%Fd8t4n~JgL6J;h6~+ne>Z1eZ#N%j$U^ecyv)K<#NZpK zPVn{w^-~;jGLy0rle1AB?(5*}<>=$=>*oSq|K*t%lv)=$BpAG6&f^G{!miKe^ z_4IM~g8B?>0kX8etA~%Xmy3fFXo(AS5d>V`&%xOvAi&Kl0BK!4Xciesfs3naK!B&8 zGuV9p(h}65^K)=>cXRi0fy}%^hewcfd-^%Ld-?kLIV7f(B^G3&Ckj6YuK;g9PbVjD z2hW_G)bzw0dsEP{KS(BcJGy(i1-N^{>__#WpM$T9mzS4^uaiS|YEfQl4yFyh9)3O^ zpmog1bL>bK_&a%e`FgmxIDpncfLGBJCFZ3g5|^KYzrUZelbeqR(h|zV62x$*zk{>8 zr>mo{hZlIuo4siXcw0w)9%5~!zk`djzq_}WyE`ZmfYOI|Y9;bU0e=TqZ*O03cYhb~ z#G-3nN@gNxB|a#px*@M2@^^6aaq;kXba#dXIjkcJ_mIDXhm)6&pPvV0sgj?ozaOaV z15KBL#*R?U@^hIv|=@j4%+CAu;ACg#{?VFnGR+Nf}41Whd zM;~{07cVzNbU}`oLUOX7lc#4uKtO;)Kw?P-_UQ2Q3~+Y_rE2JwC+HXsk|F-!Wy7GU zCsTV<$FK;T{tj?(b@6s}4e*C%0?=|`@L~kGVF3=V?!NwRzMx%U`Js7;#uJhXcMmrw zUq>e&*j_J;HR1sdUj81g9*&OCtqdS5;z28Dkqq(n@OSoe@$z4xXS1FKj!60vrOooxQw#e0-6YqUU7hWy9BP8XEfeJG=Y)IXc3(<{BD1 z`+GV1dAfoIiX0scV2(00_3{ev^!0NBwTc`ai|kb#9pQU#4NZN$Kzk*;LG1@e$8uCj za~Ds~0N(%)h>5u%$pR1o-=1e^?&IX+>g59-2Y_l%1gU_Jj2K#agN8+%-9W?8j*e*{ z$s7;?RtBFBH*|7$0PPNR2dxzWNhg7bQV;>w5)Wb}g9uRD+|d!TS_+9a1D|rSS26=?l@mC54ha;#L<>&}@fd@!U zDu@8P1uW(R=T?GP2y5ZBC&+z1ZXQnFj^Iv}qa#=i*dr+*Enow|T!>5TRa~&R&k&>( z#eIHGu6{nwUZ9Seqa!%dz$Ssc3u>-`G-QJa@Su+h%%gDQ!Eu7=ZGTTU4_6--NP+-6 z6&%H20ut~bGf~6SA0z~ibg;pQ1n%VN5a8+K<>&;SmLk*p=@a5#3+#4WDFEVPbeDQNcsshf`uIcgBG|X!hykalAdsQphzHvNwhe3tJlb#? z>g($4>JRQ0gVVYv$RtQSfn8v);_MC*f*1t$sy%3-Cvu$mffb-6S)?HLcJOfx@bC@* zPsTesA`${Pg@LmtIJJV^3-%YpE?6$Z8F#)8E>5n_K3>j{WJYp0V@b*I9kPa?HME`q z?tXqgl*b}C)R6Lrzk{==$tQhbJH;Z(v4(hl>wr`vPbH z%+Zk=Ucj`%+uzgG-^C5d3*fK@Cv|Yf1s8(BAg_b-Y!!$F_6OVxV4tF<0;E*x@8IJE z+H>Op8t$czFTkZaN>uy!dAT`3ChnkCkm?Ia6^iN$q$0xK0kpT>(ZvOlA;57#sueC+ z0tzM1`TKczIQm2DIdG~0m!IGa0dAmx!yTNZ!0|&`@$Byq;Opz|;tpQD0u3c_l!6l^ zPE*0vHdq~6go862B8Wf>Vm%yPJl()kTNIg(J5M+}I(WLdIyw3|LYh9{pnez_MT`fn5nM%)qR4kb@8nFlT26FBeZAFP8vFEdh20*ch;CM8tz_3IS;b zTZeE0SRJ^of(Hd8slg9wF?4oy@N{u^ba8TmGLTtd7TEvb^bF=Ak~i2Bu=WtR zjfT-40+;xpjDrXQ-BvFU4j!AxDW;Bda&gPE;tgwTyWCG zSwJGjYYm+}9X!09d|f;oAz2x0BDn4Z=Vx%SKwa;6x%&jTx&(k$utJkOI4OW#4E7Xt zt@HK;os;4R=}?1%51b;v1lUe+c>%T-e-BA2?p^9P;AE(LRunVE5T(tI7+}#2aZH=%N?A1!Ic)6 z3zh&Yfe$-^k~`=`Bu|$Bq#_p_c;MJZEwRDb2~?79zS66Q*Z%Cq}MUc5U`MP?!dq8^q zU<<$v954Y+V&L$nmUrCTeO!H=p$!Xglp|smoMgdifoit-`Fi-egO|oZ%Oh~{3XUqU zMPL@SZ1eGTba8VIfOM3=eghYxh$@ug3IsV}`1%ESxVS+_z`(X4LK0kBfa8){e)IQp z_i_gv0tU)+VB=9U7T9ax>;Ez9YDx_7^KtX>@D};E)H0AUI0E zxf&cVU}V?37U1gV?&$6asqDdqf;|Hc7*f(9q%VvzEP#{_ z{T-Yn;K21V z*mAHKWj=EAaq;(n*2rLk!A*8B3mkG_M}qAEyBW*^yBQqJ-~BphzFMt;GhF@sbHI*ldrpzlP7v# z2b?~@0Rt{*!07`VVwCwUz{%0W&)W%UAOT$2f{h2apTGpzTi~QZp>6Ko9?pIqpf$#h zj^H7Gn0e4v14{7;&dVfKT*xJ0fR~HEpQ{T}GZ!3s;8YKeE3nVNQ3sAhm`jMSxZt+A zI6Am`IXim!J3(4A;8Y7Hz*d6OA=n~tID#!FJ?fBcb9eXj2ypg>46=fQ37qP|7J&(H z`Ui`FWl@5fC=Vjr=HcS(1wN$?l^l&bawG@g^p~2 zO$WE4z%sQ;Ns*AUYQQl!Du4;FJqrT5Sm0 zLhBmf?iT=FHVZAH!0|-IQW4o#ex8ng&H?_Ab|-9D1)Sv28tLFD0S~l-eGHBquxG&p zIFP|%4i8`WxgQ`;1$cUUdO`MlK|O^icflzG9G28ccg_yZPHyh*0p3WpB({zgxO4$t?gnUILCQa3>rbOkm@|Q4Tg9Y!RZf!3|hg_$CTL54JVI?JPHe}m896bWuoLwR9cQkKdvjZHG zU}umMlAg|vZZ7W7k{4_n&UhoUo<{bYXMnq_s|U0vgfoMIn+D*d0!}pGQTK)}&JNz*UVdJ%`7^L@z>$Qjtp-ksV2i*6xK;tH1!qKrMTo(67iR|_M}L1O z=tL?Fyx{BN?H%9;-l7dHB)}02_Bz-{;HC_?ya8uVFc<6*(xczi!NtwX(a{?^J_0r# zEtbJzU^~dM5)t>H&7XemPCovS8EC2poU4PIi?@%fvma7B6I*!)o}WOJN?`ASD`Y~U zhwK$MS663OCugM2DA+1+FoBaS*n8kK1-1$;Oi8lJ&E44rbg>Ai;s?7Ht$GI+2?(pe zv4r4~VwJn2kFTdAbd~^Y6_^0$ATR+gWWZ*FEd}QfaJ>Z4|mr9H-8_b;uKN5fDHr38o22N4m%3s%G1f)*UbmKy&0Ny@q2>; ztGqn|JiHviTS=i-q4^42XoAxsI1<7B14k1$L6Q6Yhz?(RsSqMDmf$#@7 zs=z@9wg((AL>5HI_ISH^c{=%mcl|-_0mlj0;|R;ZR^SXYTxkH=9v^34e@`!Gq;SJl zo`L-c4t{Xy19k;CQGgvlls*0do}Qq)6F?;>IE=vM0a!oSKVUI%#szc1-Ccx@;6z4@ zJ-!|u;3JhB;P&A357+S3A=Y(9Zg3}r}5rZQG9MzQNe}69*PftJO z{0X)L9Qs!yF4A7oLwFLVB0dlCgHUUoVLIv9XQ*9!xT(_ zdzF|LB7)D;!PVc*)z=YxqAoO9gGT|uxgHz`;3NdL0bJ~W(+F4$Yy;R3gq z-N(rr5=)4f2eZJ2gVP1L3;_E9Y$R9=Yz;UL!HV%)LTMe_z1%6VEp^K-3r<;$bvlsa21!#y789BtIaOBAG_4M`k z@BLhnPBZ@pSO@aSd>D1RstD^$IwS z!TP{K11{LWn!yHwV+FxQP4Qrhz|9l*PFPTI`8m4#c{(G{_<-#ID+Gr;*dlOBMXMmd z$q&gk@C-ci%05I;c{@0{`#T1B`5>+Bz**dY?Fai2VL#Ysh$0&n^UhAlb~rnF2e`O_ zkCSn9MCb)O5$p?a5P(faxE;&_7eC-!1GWepW8io~8c+9j09_8_3^_ajI>HSOb+qUv z!ujBG2kb1w&PNw-2NyTL0Cy+k;XQDk0ILQ&4s0D*AJ`{gw}P`H*iQ%_gZ+d(j66L2 z{oK64M>s%}6r$k;HV!)_1`bdhb_94j`TDy;3KOsw5Mct=Msc$B zcJTD_@pbm{MjB@UH!s2S7+{Bk;}GFyu)W}RCpb6}J_56_`@_q{F~G?QvR4S~4e;~3&)flWjB6znx{MGTez zD*-D;S~U+ksMXWm-_0L~nOz##^<3e^H{FJE6DXAem3 z0Lz1e0?Yzi0Ite$S_*bDdX0-5LjL~#Ufy2*5c9#&OS8xb@b~id_Xpka0vpBx2MM?e z1bYRXV88@8z`#BOH!8rgV8`JJD}M)9UoU4*U*t_K;8GiG7?=Q8ui$hB4n(x z!4B2|KO?}<#oxg-z}3gw$sK9L3R@uuPC4M{1g9`C0X7SqC=k^E#1ceY_&a#Gxp_Gw zk0^qD3pM~ufFl!Z6gc|95e4=Lm;i?lI3a)o4jg8PCGRf&4xXSkj<-8x$QNuN*hOH4 zL?ly0@ZigH{tn)bjvk(%t1v)4QLvN2t_9l#c09sDM8X8C0|y$|@n9V=YY?jokORxx z$JfylbX_aR8nEB7MFuzlfP)8YIM@;3x)$sbe39Yn=o;Yd|(H?;CKZ$tHHX!O2A@ZS@@N!pakU`5a8?Ni#)9fj&ZP4!3Ki;h2SER7T9)h_5qs@ zRszmBNE2oL4t}n#pi3T+S_t4U0y_ko13RoLb!`a^z*LcJoIX4MO+^$z33`5ODy`LSO=HAUGKyr6_n} zb#-*`@^W_e4S;Si1sjdXO<*oK0>PdK`wnat*karn6WM%k4|jJrCyY@BaI}GqAt{g$ z*%BotygfX<9gzw@gfehYg991t53uXOHi82NQG|md8|)r%6A>i}y#0Or{hd7^%_ndK zVyk@-c7lxoCs$BBfJ6j3)mZA$AhB? zoVmea3-&Wu2U4x=>geF>=I`p`2tLLYy1E%`B3L(Cd?V5mI25twX;8XDwIIOF!{6K0 z2{LpIPJ>{Ff&&G2k^z|mb^{{y!2=JRazQIi5b4UH^I&VXCW{P z>|U_Tah8E#-=O#VkQ1zrtD~Q@A9RZv*eq~(gVP|`5=i)fT?e)g9DHCk;P3&nzzGZ- zQjo|1ZNEph#Lv^$#myglGBmUZ0Y?YeJVcVjnUm2%6>f(gvK{^|p6(uy8xEiy3ve+C zZoJ|eXGN;qKq(1pYZqaMtAmTXm#?QM_~dpx{s4zL*j}(N!SMx7Jm9bb%Yq3o7k=iq zp{uKdm!F5fyR$E(2?Y)qaEgV5EVyFE84q9+!4U=yRj|!qE^?l5b?|Zb@^*4^gV+Ez z3|#nt^?=<74g;{u5RnQt8_~rF=TETNV0B2D+ttC>F#vQ`D5RJLn@>ss0@eUdgkT4O z>oai8hE%P(g6^_#adL!?8-S(33K1>>yO)TH4>m9e_7GSTVz+~g(HnVM z2y8SsU4dN(b}=~W!2t-)5@5H3OKwCW0Ow9{v=ktl@9N{@>*|I)HV8H!tPpG!%BLw3Ip=ul$l&LgnN;PtxT000{bPG8{A0w)kGWj5FqU`cSL0*Pq&!N-QKo(|3) zUICyZ8NszG*zaJY!65@qD`1y{Qz6*-kWc`H2WqtkE(bv^4v-Y26hw*(PX}jj7hm@P z@XZj=A`t8&a6o{)0Jauv8PO4r2rEwqcP}?zKX*dKI5;TCDE>Shyu4lA-2$N7E?@)p zV3(o>JJ<%W*T6vt_7zwR>@`Th*sFMeb1R$%XLk6-TA*n015GqKLXR5*2Ru01!HEYP zT;Qkzv%ro3TZWJYD+X(WL^;?&NO{}a!O6|p+06rbVj);L*r{L>!PO|31u+8bJb2uq zG|9oq0jv}3AV~R$h!<}MmjHh!cUR=8dHn4GL`nlE4RENTHy)AAcXJJJ_VRLutThLF z6P*3PJ_5TQ94KI8zzG)2MU66WHUbAAI4B{t46+@*UhZC=zR2sSsBeeAua~E{JEX&e z%^Tn_0DA)*+F)-WxTqci=XJ1$5FLD1ZwG&0p8!8k=oWdf9pF?5&JI{wA>dRCwg+q@ z*fOx8;9>zB0^s5WsgCt^aB_3=^L2$T{Re9X#{`%K)(lP|U@O5UfL(w;MFb$C!q>so z)z!_-*$rtB1d(9C4hB0D>|n5+V4J{BK%{4|1Zs#O6;-|tu5SJT?w%N@Nq|kEPLlF< z@b+->^!N3^IxbHwfB5+LdIh*ZyOrSh24^X7G=ReZ%p$k?^L6m|b8~U>gPtk~b~HFW zfU^@gCxO!;SPUF&;EV;f4&3ShI{_SBV7-tw5hAWZhyQu{xch@I7lc+B*xV1UHo<0r zV;yWD*kj;iiW<$}G=P*R{2iSATpd0AoM>2l_&a!d`nZAaCIDBl;CR5F;1MYr!2){_ z>^HD4;WwZey81hS@4j|M-cbq8TGUIgZjKIaex8m#9{!M~4mf1NVGQnOfg=}e8@M(A zyAmt`u1COJu=BxU;3xp=K;$_$M+XmA7jJ)GPo%avxMTolUo2%4I26HY4D1%%;R#Pq zZjKJ1Xz=ud41a)YD{xSP{Q))w>{6t(2x`fIWx?KpRo&PIrVuSEH%A9QcMm65rvON= z80=6;(nd87Y#G=As7Vy;DR7*na-?-UT=@9&RP41$9IT)2Xr3^oSU zWN^L&n+ItVfK3I*1d{s@;RQNxF2LEv33`w!I0nI5z$~yOVC%s74;-siARmJLg-EyP z*&W$@cSm~!A|Biw{G5DU9eur!>QZpA0S+jzG2oB^hY`;C zd-PaBnD6P}>F(_65#WMUM__XoI2|EE863Na#DudO>E`L+=NaG%TD%OdA1H}qMCc)v zt=<4hl0Xq&H$6yvh4AnR2DiHDP?cn9&54uec zQU{^=Aq$j5!EOWl9h}X;mV;9<*fwyCgDnRKC0Gn`Fp!(C1L$ykXJ_zjP|$dwvLDfr*p zT?-l&h&m8#B_hFtO#mAYP7+`i*wJ8nkxBx1QgU~9Z~X#Y01gXq4Gh)|juUXG!`ouuFoKUvfX6Ao0Rb-O5t+~3 z-ND<(+1JStd8`hs5Nrk5Ot9y`sTRyah@mDZbQOqL_jGXa@^Eo;LS8?FsEEKG0{aDQ zC76q9GFTE}Eh5Jtjgq^2I{5nf`g*w_Zxo_t()D(5c6V`gb|tzc4o(2K4@`g!1Sc|ZfFObq5h~yYFIX`+zH*U`4{-8y z@pBD;%tV5n1U4UR7C1K|xQILm4u3?;73^rRJHSOeawPaV_;@+_JG($nAO)KUb`H2i z11D3kMufA$HiL^HSkgtTg8?OXxG30uP$ocl$KL^bps*YCln8JzgN+BXz`g?q1319J ztq`znU@@>dNce!QLD@)wnBMVlba3(t@N{>9o;U1CRm;n0( z9At>xgy_|IID+mkb8-oQY-FQ>^SwPB9bJ8)6$aQvsD%SKGQcSdOo06Xjsi#y!zqm@ z13erad;+}Pz5SsR^jItbM;$mMz&-@0Yj8}0tpjTS6X2WzRtMIBlr=n@9o)SA{2d)V zkY@3yT+n(rJ9zl{fd;RUTI7gW13M1vU~t@n;}jfHUld!(k?v1c{n@x zfG(4C_eDDE1FR2h0@%x7ec)sTW+B8-V+~yevK;}=0ZuM1&{jD(yun6+!yBv*Y#X^5 z&eg%g&B@Wl7kbPus6+u504mP#5(dNv2NpPIAUf~humi_4x-M|j7pw(zG(AYay(Pj+ zo(`T~jxNrg&;#Vb0S`7B5&DP%5iAS#Fxcy865#Lzch(W>4Lm^y)Vuh)KyGA*w%Wj} z2f)UF0}3_t!6t)45zGSn3v4fXNeTCZr>ld%x38a{BlyC2M@MkO(;hPZ1m1s(ZR<27 zK)}-A5l*m;V2^{n0V(wnH!OI9wy1b}IC~)<_Bj`1bYT-4%oe5a}ZoqUxHPD zB@yMVC+IFfCr598H%Jx%`yU)k;N*p*1r&*3d$DB}uwsN>#Gs?6uY;$fx0ACQ_*Py= zM}!)%7Rt>BE&p})@$htlRN>%~2;71Odla0Yz^+DQ6tJDBAp@2K`x+d8V3s}N<`gei z2WO`MM`uTGq?LkHjtDPT2X{w*A9r7Gr1S2mZ-4;Iinnw zizs*y#(>ie(X9t~;oL+k+1J~qWH^ZBM;4%-w0~ae` ze?W?6WP3o9Y;Inj5PQJpQ@J4Yb_R_kxH@_VK86 zh6ZPMa7ch-9vp0732+ermIcQ*LNB6_@^*Ld^9k_wbq3$u3p#ojweEw(4~zy!JJ@jW z9ev>90$dP-Z2(&hjt+3L0!JI-ej#sf2Nx$NUmq{%aW9bS6Q#8cb}_gz0GkXp1x$c_ z0Zt2Gi@=pE*fy{sh(@TdgTI%ni?bhek{CQ91xYHPC2gSe58)wPX z9YNHf>Ht@8pc*RPzn~;DKd(5rB)=#zJ=MY9(B3{iJ|{mpF(GuOux# zAEM5|!P3E!!Pha~J=D`B-aFXEF~rf$KgicHBtAIQDI_w$HQv+J)ivJD$KNpozq$Zt zUxxVj%;LoO#InT9oW!J@)cBOtw8YY!5(a!uV2F<|N=?jhFgAbyINt<~Z-IqxXkY+W zpJ-uhXk?ZK=O;td!$@Rt3j+&7B)*}6k%0-Ep9;}|&WDI1*_UcynrM+|fly~`m|~EE zWR6L4a&mGCLf*{SBGuRu!H0SXYA`}PG0iZ=1j(Fa=0LeYj z2!jL*B0Z#~nk5=orlusNStMB^`yU#B80ymu3{A`vQ=tUH-ZWh1SQ;6oC7GI-m>8uP zBGN%x8d%&6&3tHbhKeH6k%@r;SUtKqFmX(C3~{J8La_qoeiH*@9O5Rp#4QcdQsD7p zVqk6wceDYryA#2}2z!#!kPSqLCxgs`r$-Zm6tF%R0as^eW^8DLkVA;0*<+ZJ1_6+C zf>4J&9hexVf)gNIoe?BF5PXQ82!0wkfx!93hDIQ_BKW4JsC=;T@N{QljOH$59PTns zH3jK}n`>eY_9%j%4AzO@r=@}v!1-WTAjJpRG-Q6Fp@9W5KRLx5IsVKH&C`;R_*IK?Pv)NswTN$3s#YL;+kLM|qG0iEfb5U<^|aH4bhbj1N|XQjF8E({N?R;7Z*%4Lc22W@ct) zl45S0hMk5hgGK`+m<&t|AXN&CMpI@C&H3mwnle}cgHu_exp9iQsc{;18g3diw?N%y z3`)pQHi&{MGchnSF)&L`!$rfDp;orIOhc;!P0;u-(+p7)oCz8qrp!1oDbdUf*3iHe zye6OmD%HRM7Y#EFlp_rc3`}s)P-T$99Z{m8^P$R6BOa8?K!J+DXv$#9(P<=QASqlJ zuFS#!AA~A{)iAhdxH8lff~CR%l}PCI0y-aRS~4gW(hLl6(J*Du&`-ueLzO|o-@*VF zjiL;ZvLVV5d^BY+X>^()*}^ihBtJJZIX*c*w;-`7H9oZ>IU_MIJvF|#q$ocxJwCOf zASW|9vn1Zoj1<+zMx>}VGa*H_1w%?vT25ked~!~HaVm_HR+I{7XXa&=#3z@g7H6cU zmc*A7B_?ObXO^TEC6?qD!PJA*l;syCWfp@xpPQOjf}tiawF2E7n0Bxfs&<%EenDy; zGCMvoF9p=4PKhr_&W%qiO3Y0yj!!c-BB-X=(2TGeG;`82bKt=Ma!5Q#fG8K>REX{j z+zPR{CB7WR3t+wFnI#$Vxv3?IDTyVC1f$$H7BtmH3h}hxK*Rb#G~mgN=?L-jL@Z!e1su|@Ii5EYIb}{J|bztTv?V{RGgU) zPsmU%s94C%OJ_(a$}KRCPs+?oiHDc~8eW2mCMV`3m*ymvq(Y<$@-y>FilK7QTnf^h zo>~$QqCvR>o-yK+^Gowepo(ECz*z=E2D=_mwuWj4F~P|noexe^AgjSDAkha^lbe{6 zlb;Obfb$Q?dFbH>$-E#Fa4La>K3oYn-4kIHPIYMJK^%=I(Zg-VqY_~zZV#f%AoM`u z9W~{EY%R?L1uHZNp~(i7hmwd=Qj_CTQj_yjQXxenL@7u#&DaQ(GT}CY#Gpo(Czi!0 z<|LMbS|~f z1j&MwCzlo#=NH9i<{^pzP-cZm=cMMPmtY%9{O*B3UR&1b4Ad&zU z8GO3H4R(;H(DmVzK==+_OR=FDPOFLy&2XEDD-`03jg4@dXl#UPVo@TjcmgGql6+`4 z2jn@ZKr!5+Vk5LF4l01~983VA1=1owgfuAel;p=FX~EK*1=(Jbn4KD*U!Ip*RGg7n z5TBoh-2Z?mC<4`n(4r-?Dm5P527o4lQVZ1D7{W(b3+2Nd3U32}j7GG9ka&nT5J(VW zExbhwtANpk5s42&1|AO(8Bj#zCdMb`WTxk(ro@Bmd?Y@!nF|srD9EXdFU!v<%}tF@ zOH3}wF9Jsz4k;uj5>!E)IpD}6Y*w+62?G(TjE#s;Rce7t5hzA+*n-Capfrs`7u2fa z%Dm+Gy!?{Pw9Mp0(1L*Y)H3jZLtbVH*e$pe6lbRACFbB&0!?cm2jDWLJQ1fg1^GES z@t{Tv4m%+7#qkh_g;g(RKN3CcK~@*1 zmc)aHi?B%|WdY>K1?elz!{#=^cH%G+A`DK@@G=$IO1O1NnT4f^De=iUsfk7L$%)As zsUXk8L{ZZ>R1}nGGg1=^;)^q@kX3`kLBlC|$>4MbRSk;^ByM~GXyFcMaZw2*pF-vG zK%Hx}JcTTUW)xU?Q7Sf3i1(3vQc#qioLXFr+yc){jE5SIVFr@+oYcG&SOt+(kY*H* z?mmzh$mggb0ux0J50Dt#u`mv1Sb$`3I2R;^DKw(spn3-1$_f$cC zaVDsh0uFo>g^;QaW-vr^L1lb?5qxPOQrLr}LBRl3j&2&*Z%H|cdD&QHic1pnQWA^6 ztplhD@gNg3i{r6tDuxb5p}8QlIKC_~Clg{$PG$jGS(OYD0;eJ9tPDsyvKX{Yh@uYK z7(^9=v^`P8AkDwz{JawIOcH9b4--z!E6oMXO@WqYmSrZVLNW?e4=DUnQbEG;nR#jX z;E=+Q!qAbEpN^~&6w)aC;`p5W{DLIVyjXE*K|y{Iw8DTn36%Tu(~z|wr5s2Shm6+3 zYz8HIs0e862^5A=qC_jLj!Jsl5 zl;=SHLoHQs%OV%iXsSS^Oln0zYH~?xN<6gILaJ-A*b35t()__GU5x`CiI9q_P3au7%y z)ZEPhRhZZm6oFUFpiZHo1r8{+pli*9RKw7Gj_ew2{Q(SlY(|1L5-=B(u0X+p%UErpx}UVL0zq~G;qrbIx!BJz5qKM ztfmMfCu0i3$E6@59 zBT`_2CaN<_z=F`q5Lqo|IDw3eM+r=*31w-;s0Lt5-%zu$Ctj!|au@W<)L4u&TNJ&jj1dj)$q{4-Y z&;-h%^$J)8C@2u-f(r@^2ZBrj%V4nqA&zVfTolDFuqYM_L0JjS;b5g;aSUTYqL{`a z2RO*RIO(eje)JJvh#aO2J$9;*(O-6Z0SeON8pw zycEdT9JEu1VRlh!QGO}3JBCY5aY-Vq$A();enA0>O&F04b}cBPp}jsNKY&DG-A$+s zAR%bC5=kUCu^2SA0+~NTGXO;{J|ziFIxjI7nz7*yDK3FEcOX69jKsW@oK$#z0v9dG zi8+XR7@`d1()h%bl=!^FlFYJHNTUR~2m)JxNRDtWw5tmBPiApEX253UC&ia!fC~rg z0f?jk%?2bHc)bI0Fp^9`eleuMg&~8H3_(hf3J$O@K#3P6oRI<aC@siIt%xs5LO2aBf-oaB546Gyr4j;Zh6%$$ z4w)Yx56))L=`R!+(DDRy?MQV3idtylk1hr&{9&R6h;dPv5T@H9Gpnd=oYcH@P>qh3 zG@znspyiR^uq!T!&&ez^FWJO!Q+JJvY@Pl%jqCFyiNxxAYe8=pMkXF^BG7Ui_gH(j6Gd~r9iQP(t(Hi z87zqwg>W&9{w}g6JmU#K{^h{c+@Kpsp_%*=xpUr_ZR zQM7~y5<=8OFm<515-Ft@LyI}6G?JrHX5=CB^N3k(2p?TFY+^kvF$HrZ1|kRwXK2R; zy37LP7=#?SQbSZ$5D8fLLwML*I1o9IpFu3Jp{aSvsqu-);IR#a0NgOJbWv(fYGN@s z6Q+R$5PpL2;eG;bv4FZBYu_Kd2Lq%4-3u^{xZ3+*jc`|iRD(F+0vzmW6cI>44c8Kn zXyb#$up}F>IMy~hSO&?zu*wWJl?zH`nDGb|1^E;u!$8GBZU;FViHngQf!fTTyz*%bLjnI)C+nRzLx6(DmmGK(`(b10yQ}mKvu$_1{~Ni;Pi=WmOY~&4H8}G(GC+w@*6}UN3lY5sdgoXn=Kb5F()d3aUd8;g^${2U-{doxR5_b1~#WP9-oy zfmIJm;=!r{G6jH?p0KLIoL0c94FALeh89qmf`SOE1V&(EsDj2e+Q1QpGWY}oh772J zflV>slms>TGr=>@7)p>LG(R0afC|rzIr)f1d?0pCYFTOyXeBLVIt%Rc{PcKOLIEiO zvB4A1FisgvRZ(g>s0#&JO@$Z%0I5QCL}?xlh1e^uoP4M_D1o6S7GxRZ@5LyWaRSuCua}z`m!)S;wlJTIL0&S^U4pkQm5qs8I_OMRhTx zP6MYmkOGkNvAGym1qZet#l=QQWq%H685!zoK(G)-2xHcHAax)=pt=^3%W&0sAbsGj z8)l$DQUJ(@Q13znP+SiYLJ4ymRXfNcRILbIh^6MB&4D0q;F?8%$)j~AV8Xb16Cin* zM__DtUjjt{(v^SVAY8^IY>Gni-wTpp%oUgX`sMI$p>KdAW=vi zm{bbd3J=7o+SbNzQ=C zfGmcDEL;f9NQgRwvH59fpin~$&w;WZ2C2p7FpwHt z)fYZJC5f5fy+63rz#ko}mKhuM=X z0IdTr0ynC1^C86ml28F;>J}=3a5UH??3oa%7L*^*3i$C)BZ4wiN-B~#bO9o!IAqZwrZ{BLF@`u~aUn<;db9wNg-{VBt6(AsOJE`h0FS5aqJ;F#S391IV03YNLmKzpkVr!C^av8e$k!->$V#C(7s)`V2(ltjxMIu4MM>Z~10e;?!x-YQ{EI13 zQj`hFyGV;8piF3<#gSi&l0f^KAd9z(k|4(@A^8F|j}|2%jWR$t0D^)Z=h!Mj0_1lB zgQ}>CQ4%1k6r>A@loU~=p}h;7^7sc%5oW;>7ODtF@2?lpAKvF~?og%A*Oq=5kO^~&ap-KG8h)AbUo1vRh z@ft^TT85fgl!`wsLzR(`mf<=|z`GIgre&yFBGWQ_7e0;yMF!)R5F?=EY;GhmHdylocdG5(uiX1(|uE z^V#sZv>>x28N5^<$t@h!BU`fhnz6b^9WoKT24WThDDt@UqNZNB7A$EPAx9u3BPm2p&j<<3RE;5r znzrH6m?<1Cg*TnURbff(2sx~29$wi%idV2|kQlU8jhqBCi{nAN9jW zW)-9kL0bNaYy_yoPcR#S6k*FoAaQsWK+n4Y+CB_n{e;K~SKIbt#bL?M=p0G7a!5s>AuBqoqF zj*I}3DkeIifp-RiS9X9Z2tpBvsv3VZf;QifU~ql`Trqxgab;hSzY(j9P%;#3TM()s zWD6v+7<$VPoX0^2;2;t+SO_xE1JMZG6$>{M`;6+gOwlHlPR(a zY)J?tk2H{q@Fw;G2c#S%gIZjGMX?q#Fd0nsC!Lumt7$96kKukl*@AwQuQidx|F$)Bc4d_Jy*j*S&m%u|Q6|%$xd+tEe4{8s?^9Y(aYEHo=i95fb>%h!4Xev-U(`b^A2|eTm zj_95RSUy0r8Fb?g_!t40I`D~a zc;f~+1EHCQk`R&PKve`dt)Yow4pbm1fOi4n>gR z1PfV^B5WZH62}>^D3U}KvS1x}3t5l~e5E8z9$QHVmP2n|nc(dWpj+<2dmxGnkOboM@+&h_bHK}G;Sz}b5XA*KnI)hTWxxy1 zF^V~4Nl>1I6d}l>NbV>}g5J*uF5$3Ua|9X^M^ObHAw^6x7o$ibMjO#u9dK1}C!vVI zH=`ho11Ut>$&Qe~GCPKl#NIN8+lB1)BFK_yaQKuIB`20Zwu=^*6eZ_^&%Xe%3M%2E zdB{rgl3_BS!4pWs2E+pO29X9@K|+wzv_RIBr6wDv#U~f#7lYSDK)9gR0YWk*A3OsE zkpc&SF?diCB9M~`K1B)60pH{R;pL@*kF$kxK=)haK#xd=2o;xtXVPF7ZR94#Lz%E! zHb5e2#n1~Qp&ZbuL14F~701I40sx%~24aJ&d~jTVI$Fr8P-P%pFt{%8ViPbAejYNa zorrUi;ivhcDF@jOw+~?oNEpd7gapWuSk!}(3Krp#)Ew{x1)8Ps^>83@WD}8%LDq}V z03I8QPf3D|tb#W6f~|!JgJv-ki$GG4)1N?Bo#4xiFnP3W2ouJY0YUPh^r z5GGp^kFu!+ZV9M(P6Mr@ONHJPfFc7q3j(xw4ZI{6MGm(21ulr>wd7)WX;7X9N`?%@ zB}wtnk`;Wo5wZYe`WGDd;PWJq)WW1d_n*cWlon@zVi}8GXrv(NOv@=P&cLb#oS5=+ zL6^58n*cLZ(@o72vi6t1iz)mjC$S=Yt1sSga@9s}Q zHvwGMpjilw5bUmjNETyv4~ir{YhenpIvqt4mnonK2gfF|kKlsw#n1zrkh}($M2~;4 z1ZL!dg|Q_EP&Poe6~u=eF^(Y$>Bl2E0=jwyQD8tli=q>E^qPXQ;=F>Q%)F8` z2Cm%9yiD-!CB6KlEPVw9Lo-uDLvvGeBSQrgU}#`qW@e_KU}S1(Zfs_uprDYzmC~lB zrui`|Nv=hbA)4D?b!M_iSqf;kKf3{w~w7+Ais!nhEcOMpQy1$2a{N-#tKqHYG7 zItK;;Cq2-)@+!G`Fm<097#MWEvogs4fS3s)6$Anj(A6DdU|HEFHvdRxri=^>dl=XlJ~HB0cbMk-fFdR~4W0XRuFX} zIMn$vGBDh-Vq;LZfvDrdq3#v~1H)z;HilkXh&m1&>Npu081CA#F+8(_sKezxP`o+V zvoU1aL)76)S34LO7~1XG80;M&>R52tE62dVkPcM?OE1{+M>+!oL#qQD!yBktc=|&t zckLM%7`{2MF_=0+?8T)Hl%DM!*%(|1r~~=W52^;A`yxT*xFZ|GDyUkxy{OrP!9$*b zVT&Uh!=D6*`^p&@7#tV^4xr}`koy*;vN0@Ag{Xs@hvr|9x{ax94BLoMcOaFG;V2R6 z&Zn|5Tp>c;y;L@a#{|@^W?*1=o65#8BMssYkbAMkuLA=E!>Kej2Cj67I$ZvhU|?Vn zfvUk5AE5N2lg`F48LAeSd0Y$(40EAs@RVlOW96d4#8HbK?kGtZrY zf#EU{>cSZq7^HI97z%SC_Tnn9co`TNWgF;sFdWNeV@S@&Z!ai5 zyP<0E*$YzFM}#^zCI*Iy`D_f=3nBLM;3%I#=J^*v)ZnvMoq>TNsECcB5~>zgyn)(7 z%|&bsLB$X=aoHQn$iQ%_n2kZc6rxTThx^_!FfiOLWn(y922lsfr`XbcJOcxRemNUM zUpYh_uJ{G1+g8rTAX-6Kooxjh!yF>i9jRbrP_D#p9w^;=SF$l|B0}A*N;U?^D#GSv zR5Rt2z8)-N@_D3LqrS2UXZ`ArDssTv8#oRA)uA8x~5h(29GxU z>dcrJ7^>RX7&df3)Pc%5Z1%=6FfbT(vN25Pgs20R57^XQU|?Wa*U83E*9B3>jU)ep z;^R#h8-rjsL>;d9c+0@RAl=Qzz|jLyhbum`LH!D-8hr7g!N9=aPK3HIj0_C7DFfcI4&0=F%Hw(Xc=8Oyshi0)c{G1I@hpXJ^Vq{>jpTov* zaxO$2uKv*$1_lPnd29@4=RwrrGEa?xfkAja8-wM1h&o*12WlTUL)GB(uP&(nON2Uq z1_p)+M5qG|f88fS-8TjXhUf(lKj3p8D7*p}vN1$1gs8>kUn7t|pla}$m&CxpFqH^( zG7Jn1--%Es35t(J5Vzs87c>r_Lxehzd2U3g1DO|3ggQ_a*R43mgZ2eNl1k?J-Op$;_uu$>5Xpm07wq`F%~s{2BOI#Bt^w-^+PuvCS8 z{1oI52_n>i%u^&n9mqUwBGiG}%O*sq1KI0Ngt`Nu@r}i741!A_e#X^)?qg)04xZGfo5WiP0GDzuS}!Fdxz9WL{Z zFfuTdZenAw+yYUD%Y9;u3=GatHLw`Q=Ds5g3=EN5*%)NE;WsagnSnul8yiFIPKY{O z<8>A+3=EvR*ccr5LDb>$?{Q`ZhRl6z3TsFI&&t5C=MWpi>EjS}xWZ){BLlJiGktfSvH2t7a;0z z`FB1e1Hifua908w1~6hOds6@H@}Q!0`DY8^fW;AQcP@xcmXCSIOk$L`>)v;Sl>X@ z;R+X!EI(8YzHkB6TcSj$QwGh`5ur{NG+#!9x)p2;3^{Mu7(_mUT*|=UjH4d_N`ITa zvN2rv3Q>p4zkUo147a|rF=%|lukIYEUj|b19ik3bKj11e1B2XmHipl?AnHKjge~53 zLHY7G8-v?Fh&o*J5Jnsf41E9D7#!F^DTjf<6o-G?85tOYIN2F)azWJL>d#yWWnggU zW@nI2gs7XvjJ;k>VP{~FZ)Im_?t!So70#-R3=E3>>|^I(ILZl8 zhpXTHl8J%Aor{CPfES_;*F2K}3j@O~UJeE>A&5Ey9QK0BjaR}P4DW>@>Tsn$Q2Xj9 zR1LoT_K1mrL0E)?;j0+LJY41EEhYwrMsW^?Xes>aK;}J{;$S!_i(g$f0|P^-90$Vz zIfy!tzp$m}Dh39IPjVa#Ir0#7xbg=m{JWrP@c9>H?{s+%1||iFd7#vQ&E9Pc3=AF$ z91Q=U>OiWosq0{3VDMAqV7R6XQo#V8ea5ElF(U(mkO~LGS5=5QT=pumFfdrFaWKSa zLDb<27f|l*gQ~&jUywahv^W@ML)GF+FQ9s6DO3$U^FZ!7qQ${*5~>!Ld7$x}%TP7= z%mcM|{}G`M)ZP`;hJ*<|^FZl9TAPDG1F9C6`#|$iMo=~Q%mdYrUfLWC`B1gE%$vc$ zz)-2p!62vuaU&?)v84lJMh1p(9S(*rU5GkdaI!)5Or1_lOsJr0K5 zP<5bmfz941Mh1rOdK?Ua1`zXvaFj2g{K0C-!BAibQHQHt*TlfUFaxRvU-+%&WMGgo z;$S#x4KWWCKiJ&Y02;bixVcOW3^U6(7|bdm>Tr!?@-s0ooT=ns zxLplVhs(cf7#SE2)^IT7);P3}KGXn!}JqLqG6GR;@^Z1w;7=ATzFmSZuSC`1b z!0@h>gTcQGq7K)35K#J9+|9wTq8p+PSN;Hn|E6vZ2KyfT>R4D97(9D87d2h&n4A>E$S> zf4PE#;mRtAI#4-`%^&+185oYN=3r=A3sEPE!#oK_28NJz91J<@A?k3If0+yn3@g@i zFbHmdr~{=RZ1$Rf=GixJFqA{p;VS>w85tPnZ{T29wh>|;E_)9%F)%#Z$ic953q&2L z9>ZquCPoGZ&aE5_ySG8q;VNHTm>C!}w{tM$?t-YpmCr%4%)3D(ER|w!7lXoqn+SEF z`b>Zbb)fk&aU#?$VPasg+s(o7XfMQ#(m3K5G{5j+A4CnzC)mOdw9r#zKL2xV*J<@&-2CV}Sbs&FXGY?em6dd4Sm;_aat9(gdWMG(cfP>-BL5O*{>east3=A6% zaWJes3{i*6JVOQshV6$r7+w%i2P&UF9p+#VID+3i6GjFGsUsW=en%ndaHT&`{@Qeu zgW)(-9WM8|FfcG&Jj%f!dJJM7u5ts^p42?X!QcT^hs!)rz6pY=!56!d;_`1l0|SHlWe$dIP<6P{ zGf3U3%Nz`mS0Lu$QP_3}2w?aK#5m9mib`hU&Wz^YE$b zzsteEbPvC}=?n}EV)r;0)GUjGr*kj4b@ZJTY4p+LLz{dMJ* z)C-~xm%R^I85qucaWbqAfT#n-3buHQ;ACLUr*JZemO#|u8lMEs=j|`yWY8~%sKaG%2O|T6B~%T* za{N031H-`zh*|j5&17O=*ip&J5Lg3Ii_6{xj0_CXP&N3>vjxpZ)p9aefF_6-aPO~p z&&t3MR?o@c+6u83S3XZ=W?)E$s=;UPG$say6CIokaorH}aK%R%69dD9ZcYZaK8QM8 z{;gwVU=V<+!Dp`(BLhQUKPSV92@vye`QsNa1H-ckoD2`o%2gU>u+Mh1qn z%Q+byfC?Hs>A-@Of#Kf@P6o4e5PNZj-!~>uKNO+{pS>|m3=CcyIT@C1hN#7r4xE`7 z7;bFlWRTtlQHRUFp!K$@P&N4M6=Y;!sM^lS(7gj<9xi`OU}9icxr39zayLYsF^+y2 zXr9S@FDJv2{SbAy#`Qq+bZQ46YVf%aR4#fR;A9Ads>S7BQ2!(P04KwIs5)HrhdC1i z!{!5=46hDB?8aqYJQD-M7pNM1?wiKKzz}?tli}q_hGS{N7@-kjlNC^!pIhsz(X3=9laXE_=66Hs@Fk%8glSx$!8 z=OO0d>JL^iGcf!=&&g1E8KMr?>QL?K0wUFmHsv|GB7Oqz{wE&38D^{ zKS0U7@DnG4%^!$5T;_2wF)-x);bbWL2T_O1AGaA87#jX@GDQD}sKb>GK~J_%g)8%%neb8Yn%||zMI@!4ELbw zaJdgOpZ5Z)2A}&t<-ITu#7=zbK>N$gc(@oILe=7GXM@TKbzUw8D_)41xZDSdHy2(m zhSN}WxZ;C_k%8eQFBijGK8Sg^^7$^%J_3F&hV%Rob-2s}^|#*gb1{erK-A%KA1IvV z1h^QK38(|jSLh0GG3+9s4m93*M1YGyTo7U}F86`R2?pVfZ8vz z3S10{3J~*fm205!(^3U4hAB{WxYCtABLl-c1ullOiV*W~<)eR~_0&pS41&t|)qxha zg)4J0gsb3J2dYQsLe=0)SD<)Xtir``52_ZIKS1-pFQ97hnFku@%24HEuvLS&4_7#Y z@?|ts4L}z#yW-#gL^7Q3oQirDxE3fpfZC3>x|%6}Zn+ z&|_d=$kXRym;+UZD_lVP%9rVLF~k`_%mbw#Z1#fG%`)I(2r|U44m6J5XUN6CWCT$M zDqpdg2U2HZ#Ko|JfI86noLfd*40}x==HUuwkUD!)E(RrY{OVpZGB7ML=VIu!gs8(+ zPg^rGFtA#2F*sO5)ZuDBgUa1?)?5rnpz3gib0BEnoi!JOybZ)WT=@gk&$G4RVmNCD zQHQIaw}p{`;j{x6L%kD39jhpXJ($;80W**;9^LCs>7A8Ub8YV)J1SHWX3_v z1GV0<Yo2z4NTh-X8>4xc|j<)=Im>OlV0AwpdX3j>2;HW$Oze2Cqkb$Qs* z!7|W%ULiycKKFs<5zi8#ZW<#4LvIlmgK`PPOkC-HJ0k;wV+j|-k5Y&_kpHo{Z#ih3 zri_b0u^gffsvz&`Tz5>5_pmQMtp=$7%2PzjQ5~*%F5$Zts zdM**_K;gBJ2z8+FT1TY1%@te>JD_TDHyg?}w-Z8UeGx#|0i=X%$W)?4^%&43ull&-c03U_zzWw%RJEhCFe9QhKbW5=HY6; zOk-wXcs-qqp>Z}u9VlO6v-dJ+9%42ZL)$!vI$ZIQ1KRgKkBdQX0Yn`x^H#DmFeEJC zV&Gf>Q5S$?eg-rzq`8`lp>7RC9j23Tnvs|AnI_X z=iLkp44GTF7*w`G)Zy|+H6sH<*j6rvi`yXTK_=zzwFr;tiVyN8-QHRTYpm|R9 zU0e)_yCLdug)?ZsTvb9R)f|*@8x31*$+_%BC)w|DhmU{h5cL%sYgI6 z7#KkH2{v`0aL7E$#gKOtq7GO31C9Sx9OYtQJ%(Q$XkR}6F)jv20_s5fy1b8ZF*FcR z2b#y|f~vt+4wZt|{S%=M)DGi44)Fs%^FaIaHWQ%^)SkaUgt{^Y28I{MxftS3KedrTntlALDbOkY7{G8kjlQ<#f;R-*{ev&tw+zfeK_|<{TJHf@x;LVL+ z-4{?j&CSg)lLw*>SNR88f3=o}o8c-{9j<%{Y7gA!;bxHKg_ws+9VmX)dAS*?pz3g? z18_d#4ww5t{TUyq8hqse=zOVCBGiHEi~U5X z1C67fBSKv$=)6NB)Pd}E5`p*?pFcqM1`?qTWN!fx>Ok`(M@6_9I7A_K;|k{~ObiTa zqTCF-#3AZ%r329UgbWhg4Ed4}b-40}86yKjGgJ*e|E_0ZVAv_e&5$7rF%Or$p!M?W zWVspm^BGiHAA!3P82dek$h)@SQcWZ+JH^W>-h#Nue4{Z7TDK7)Vc|~r9 zE&333xc2RU_AmS}4p;br#>d<2xEWOJ39Iw5=Vo|CKpn_lAqQ@T`40Ha1BJ_R2W|#` zNBrtQ=9N2gGq5<}SI5uDz@X#A&0y^eQHQG>0TtOaG;if#HiMH-noues!Q_q7~lU3}T@Wb(3-QLqX?VUy0&oP>g}7 z!)0$VBLhQl3^&8ESp4ch>HbG7H$z%Hes!Ss-;8)}hMok7I$ZSvsQtGfft%q3R2{Bx z2DSe#C2%tcBtp!?rA`erUy;bo;E)Va2WpjLD_=nVU6RbrAf5_Qhilvdq%J*`o8eX( zes#@E3=CT7+ze|oA?k4TlR@gjv$z><<=|I$j*)?(C6}8aC?BE@)b7FN50JVS`P>ZK z3JI(8DdJ|3Dj}?HLkTxST^W9Lp-c=6BIVo+nUxTApz%O#?pw>q!0@n=o1w27q7GMl zfYg1e=4O~*i(egR9Fw_@o8f64L>;d3G?@{!FPfX7paH)+(ESxN8n_vB8X@X%r7Is+ z1_rZ6Zic2dh&s^zCT#u{W@2FY*Urr#*acCCYku%M69dE1E^dZBy%2S{`T?MImUeyI z3>_2jtLtTCVBng_%`kluesv&wB`0$;2u#DT?mROC!=!2442iQK>TsQ7bCZFAL31`Y z1JfLcI$YrgDtCD1a5D%JPzUOVO3&eDm`gxi80emhIou3?q3Upz<42en7@X&FGq5j! zxDQu2?`2|O=v=_f;INW~ki^QHRTY0ibiEHghxhZ^N%{HX{SW#ckXSIXfWgaOF!-_&wRd&0w_?q7GO1 zfzID)*vZXs1*#5LKRJYnfk9vwH$%!Ehp{BDa5G4shp5A~9%Kq51B2v6Zicm&AnI_{ zt3N>dd!TCY^^bZO85nk6=4Noc3Na5?{xt)g;{{cN&pdC?{%#`FfzFS;aFv@O>N><; zT>cPeWMIgHs=;S3=={3Y8xXVbsgnThd%wxe5Oy1)7FYTM&D%eMs=;R-=)CKnx49Xf z+<};ft3JBM$iTpJmz&|yJ^boG=P7XA=VnlP08xi4KDa^c><8Qo<`40!^I>9OaD2$k z(EAvo4p;pPYCnI3s=?=9P&-NF2{(hx6Nq`Z+y@#LPh63<_{f428J2$xEYSThp5Aqt~x>cQ{Hnklzzak&I5E`#|LhP zr%-jc;te!^{{90u1Mf$OdAQUqVPIeo|H#d-8>$YMKS2G3w;#C~qCZ2-!&T2bVq{=g z_?erb`71;nu6hPEF0~)324DPw?uU8um75{@8^kK9WH+?U|?W~ z{Lanr6RHkZe7pvooA(1^7C!e?F)=Xk{35I_n3;j0?GHD@&HoU!xctG$#K7?OKR3fW zMo>+OdtW6eU-B^VFa$8+S2vx7fgz8Hhk=zHq7GO4?<+q8!&!D7hC|{Ib-2n?4@L$C zKM5X&X_63ixY8Bqyzm83HTc5s69WT7h!hWln>55cT;&=ly{&?(!Dk*w-9{qS?I2R! zej?O?+Qk@c!>NJT|XF#Mnb0XE*5vk6VNOe9$stYDkT@;b(5{XooL8Q8TBGr`x zN~F3sM5_Btq`F^3s$-NTu6*VoQXM~$>cogtCrhL{6(ZGX6RFOKNOhJ(s&gPxojZ~0 z{D@Q+N`yMlI7$o=>OlR=Oj#a=9;jMe?bv(<28IPtHTdd%ka_%aJPby1kTi(Pyg~*B z1~)k#hV4*wxZ3xibHHDM)X7841C^WD`emT;jlc3d3?2#)b-3&WtuKjD;9O+X!}{e4oAhv7O@9jOkY5 z!b&_0N<^sBQQ~2+CPJO75)VTN5$fWUco=etP*<+R!_Y=R9VlETDDg1NBSPH@B_4)t zM5sHU#KUlo2z576}Nk?MGqA!z`gy&&@>iBRXs zz`&rU%)^imRSWW$0+#!`LFZ~LROVq&Re{)zE5Cv6m&{h-VK@X;2Wp>VGY_<0`JW08 zgQ_aTJY4A+wC_`2m4{&#R2?q!K;vB3pla}i3uwK{JyjltzfiTf>;<`sT@6ISOv8Sj zAjluWM5uFPU|_IP<6&rks>M}qfcA&ZQ{!Pc167C1eV}zG*VK3z1k@pJ#8s}{V`N}Z zQRiV`(uAl3g%h^;0IjPJf~tY}7@IoK{+SF-9tLhLh5Ffb(Q@-VpR zLDb<27m&RvdOQq;P<6Q62a1nss2Y6X2MXtjM5qI;mpVYCx`#xlOJrbRVAAJdNY;n= z7neWwGcYjZ>hmz@7(mqF3YT(51_mz!9)^R45OuinIcQz;U#J><;R3ohP1Fct7Cv<# z_ZSkP4m7`GLWDYy`_hR}SHZx*P;bP;@ExiaSN;X<7veVNVc2U7@e8hTOwhRKQ)3>6 zxhDA4f#!vdLe=047f`u@j$ppmZN+%EOQiRf{XVv@$R-l$!D|{DP_j z#Sgap2D+(DZ_+#gz^~_g}hu@i5eR zL)76)SD=0B9o{?)Z=vdN#oKpA1_nkS9)=&j5c6=UV+73)`tdOE`a{&=suw`x_k~b3 z_`)xjfq`MGKM#Xa0K`08{spCH2dEl+=7HuLJp*_cLINS?;i~s*K<6$5@-Qd_L)3xH z$Cmy;>7WX#2A{p4bl?;QF$C4%)`}Rc*e@WP@cfU5Ss>3hbw+T<>J~j9)?{|b-3(3$IQTR zB#nn*QWnHKP`G1D&!F?Y<+6Dgeq}?{;VMr-=ej86@G#WmLe$|37tpxCHK-bV;S5@* z5R?Zo3!ger`!ACSb(NrYL>>>r52#vP;R4!!#FS6iUeG=Br9`Ov0J`V4fUtQf3=9mx zg**(~3n6N8`co^(fK-A&NZ+%P*3^7nO`1}Dn=kwJnh*|j5fzF|CSxs0SXxvt64PkYl zbvG}FPzT!IShJR}c@~Te4FA{iFqp50sKph(pz$W}^*jvQpz1)`09$;3;_1M89)=qP z)Pc&~N9%bQLO0;I7j&OP<^~>yCa5}G_BJvyF!XHTVc^{aF%MTc30miBx`~Iu6{-%G zd7yp0{+oChHWE5{OU>=85lH=^DxXkfnVJf1_p-TCwUmopMt0Z`5&9Tp!2lyPxCNXpTVyVw=LDb>$2Pj=Np66lMc>%vV&^V#^C5Re)`3Q90gWV+_h96M1xYFrk1_lP6%RCJ4 zFGI}4r4F?2mH7$}L--YlI$Y^MhLM3G@d^(^?Nx|6T;&xgT>P&=)Zp_6XkFU$>pTow zuS3-0av$iP%#+u77#81xr~|bxvBew6JHZ?x)*448QI| z?8ep4lV@aLh`YzbP{&iJPZyG@vBp2VqmCw$ip!I zF+?4%`W)2m)P4d{gD?C*=lwf9;bEwM3Q>!z{}I8&z_9Tt4+G~5h&s@C8@6}@jkhbl z~}m2%J1>3b7f>;D1Ohwu;T+n9jNS)P39)|i)_|@%Y zU|?YU%)_wnGejM(dOw_rfq~@<4@2}fh&m97Ek0H-Gca8K#=}tk3#5X90W>a*O&w_e z+tJ@V4Ez2-)Zt3cp!My){_rsL{e!3jr88{ifz*}!=V9Pr1ce9#11@y}j0_C#7Oi(*v-b(;J|GrehTW_Xb-2>iOVIhnY`hHr*dXd~h4Vy4 z1_l>)UWTh25Ouiht>I>1@a5!XI4J^A2ipIP&3!AF7#J>!@iJ6OLezogZ?UNZokPzr z#mn$e8ln!DKXjNG7_4P@8Peq;>OkYT*vtdvqfP~0hKY(0b-4TsIv?Y@A}_-;Wr#Xl z<-r{m1_lWgUWP9k5Ouif%P*k&v9)*^+;kx7aP7O3Vq;+Vpu@|+WsG0lDFz0HEylbI zbtVvXxZ(q(?wbiO!vRzL>Ol8lTbS`Obecod;RACKHtc}#=uZz!^_a*1W^k*{{dV4PGw?XNOR?7`0Wl+2O9Un zrtU901H*eyUWPY;5Ott^;n>uH){pNC=VeHWfT#nJ*wl%zGB7-e;AQv`3sS+r09wb6 zOs4bc5PwY&_*^$>NS{sT7iK;^b>14Ioz|AO|#7&q`T$TdRL z;;ILuLFX4Y@-m!+s>7A;LFc6MH}Nv;ZGxDG%O8HAeF@FH45`f!bs+a*^T#g+28M;r zybQBjAnI_{3n2HYxAHPHwL;Y4avx}a@`+Yn2DUbcI$ZG!Qm4|!%aBAs9q8V}hBjUX z*LH|`xYB7c69a>PJ1@hbPKY{O{S;8Soz%t4P~Qbnhs!+B{YRZ$ybS+)AnI_npG}z< z7?}Hb87wA1)ZuE!&H>%eIf0kKViH6hu5LH9r}n#{|PIt9Nv z&^>3Xr|>eInFdh@BC(}|+n{|3(|H**XM$AVzAvVYfq}tzCNINIs5($Ng3UZozv4AW z%`E)pfz)x$;$@I0LY?L;UIsfN)OpO}Wr!t0UHU9uh8hCuVi_42+MsIi<#W)u)Z^I@ zzv5GO8Fb#r9Kz~mfbPqhM_Aou&^n_9gw=uO!G12_Wk_2HQHv|xgU0^~7V z8NNW(;wt|@}_IXU?^Y4 z%kXnK#5`Q(0cd>Oeg!W>^$LhOT=5G^FRd$h86s9f)Zua;XkA7bR1H4&fy`UEl9!=o z6=CycLDk?h4-_tEiBJbhSBk578UC+^*o!NE7cwz0Sg+w_xU&wT4p;f?z|6qlu%4Hp zVG~3huJZ~wSs55iHuEyr?trKRjaOnT|E4fAFzD{&WjMG8q7GNR3c8=pcP}r)ntc#; zxZ)SIAN$ijUWUp8_|@^TGBE5vz{_ypK;yROco}w` zhp5Aq4nXPr8&nNG_km3LNrXDkxrl5RK&oM;VeeOf?lI{lLfv}?28P~?ybLv$AZFt7 zFDRVbplVp1Aum#7=zXfmGZiQr$Bm)Pd~%OoTeneqzBZAlqTC z!ye9{{(Tq`>Vg;;7*dH)_ll8$Vfz(chK1K4cH`<_g6?PMzYb9Yb0N0)0IjPsBtji% z-GAbBUWN-$wV-s1&Ai=A3=HqC^D?Zw1+g1fds3H?f#D}q4LU21FhfaAwnI<-c3ZRJ3@py zXGR8wYec96#i!(bNZ8>E7tpww%za)4)&~%^xZ+oxk%1xf0WU+%Lx?(Die)B;0C3t@0W$1*e!`057#K^!f z_Zu&R{tt+GxXP>ZAay@@8FD}u?Sjt2$1y$>%*4R(;}V=U;vO%gf-! z0BYedFyK;G#K6Gt1*!&LIM*{UFr+a;%p#=jDG};G_NFlLFuhCI*IOtb7d1*&*iPN>_hC_m#8rG2G{Z zsKYfcG=Y(U;X5}ULpv`-9jIQ!=04E)h%6r;gDxLL9j;biOkvrN=*3}ZbH@JO3!U93=H2* z`50=gAm-tU570Tn*RA*%bgUujaGAFsv@Zdw24DV&Vqjq4wB=(+w#9EA=zRW0TRw*A z_7HWr;scc4SREj0@YxG0cLj-12U_OkhPyYVqha>H*PXq!41!^D#{Gz^^VH zG{5D+#~|&AUma*1-O`hfp`Cy_(0Tt$J^2`CHYlnbjVYXuL|AN#x5veYc2z8+QQwl=(7_>tncHiut8{445cBY< zTT#Zxpj1v+okKYv!!9D!-6-c{2&y1#UO@#P!z%*nQn?ryI4b!Va+@LMfyRrlh2Jk` z1_s|&J_h$rh&o*TEoaa^v`#(-lOBjVP&*!*d7${b0#$=AT{W^XFx=?jV~Cs#F%Q%$ z#AY67p6T{vJ_e5|5Oui9T~Pc6P2po$N zhsz-9aFth}@gd>m5Hz)I~5bFsxg}$H2B4zr7)#eebLJ7%J94 z)Pd3uw)g;r!vd%peC`942kY1HF+{9|n1{>1s~8v<%GUBRD6E61!(}h%9vBa(8hrL9 z@G~$hS;xn~vJYY&XrB=_f6U-vV7POLkD=r|L>*||0ycGbI2jloT;yZea~q-#)GxrM z?hrEr!;QOq46`0W)Pe4U!=?^&OK8BOOAnI_9CxWiQmHf@eP{9g{LEP(%K<7_-a6r`H3ujQdlS_m;P`a<>;Ahb0 zgqVpdzx`xnV2I`9X9(klr~{GM{A&s7M{)BrsPceR;2x(2t?$w2;b-t5psonCzK(~V z;Rr9pJWzWEo4ufMWg|X_8hqg*&(6T$!pF~$EDBKzatAi^K=%N~it#f%6^E$96>kyD z3=F0c{0zO)5OuiR$H~mV@LQUnfkhso4p;s0fQ^A+ojgB7pBh9RuKuM6BLl-JHGYPB z>JW9f#sxt416pYCGjwP`)Zxm%pz>-1R1LoH%VJ<)xU9j?V54zFnH_mGoOUVVrb!^%W105&!BDqQHRUCH;fDn zjRyP-xkeCmp!x}0Islyu`_h=7;j}449j^A(4A6P-X8a8H<`8wb+Pk3r)4}Ha4AY_N zaQOo?&cENBpP|4CVjiyY%AJvcLC>0>q0|PV4p%xY1?{)8;b*Y2ho}RMi(vCFsQkR* zz|X+t2vLVCe$`nS7&ILD8T{QK>Oki?VKWbOKJa~aeufMWh&o*6fzBy!^WbMt_JXJb zm6q7d1MPQN@5Rqh?F~_ft39O5$iVQ|o1a0!7orYVxdvJ{9OTQ-5akC^$Btus(vgvY zVSyh%!$W_FI$Zg(fQf-YHh`a@HwdB*mwC*fb?ZU=4A(*->Ts0@pngC@7(c_7Fo-%_ z=?XMXb0UnN;ZQh49WHx8`G_NepCL2?q7GMlY-C_yD30J~P>F=7!<9}!<&aM#KSM$k zL>;bh0rhj{MDa5$gQ~;TK3xRrr$q5H1Vuy4!xe8UK<80J^E2qg;8$k}+Q$;Z&oDg} zq7GO11v4@*td8Yp_!kFJhs(d9_NiJtKSNJEL>(^kK>O|%#Pc(7gBm;xxX(idjRR{Y z@H6xgPzPG?xF&(0;b9WQUR?DIX#B-LnV;cVGDID&_yyhn%aX#+us#K%4irDw@*Ajs zE}Y8GAe;tKhbw+T>p}d}_!(-`A?k413)(j}8>$9hJp&rYJe1DQP?!NR4_CPcy0_|J z20uf5CPW>sbZ^bbz_2uvpW#6kL>;JKfX%-kbrIS843l&4s{@_y!=1~|(3FQ?T?Hcp z!>>GkhN1%e>Ol8%zAE5nxKs#Hhb#O*=lXpp*;>oPDfEG9x7 z$hHeb{0#S@YH{UX&^>@Ji})F4l!A2NzBdij-Z%hNgU@}ScI;y!)Pc&+&qSyLxr(I> zM8a&vKCTA}7hxjQfyxO7BGiHW;X;Hukjp}dPzP$SBom>ISBQb3vy7i%S_jBv1_sc% z^w`opXuij$7or9hGT79C&drMR%-7=99=4m6K#I051|eD;FO3nfxrFOlkw5}^)suEyU9 z{0wCiA#TJ~Z-LeibwSnO^9LUj1H-n7{0v7XL(IcfK7-cBicH~W2$%vvGox1aH-2-Vqj2M&(F}a5uy%6Vv7&ZIRr|Z_!(L@fmGnWpUE23 zuieDY@EfWQmp=j-7#Jis^D|^^#%~^|U$<;CKf`&bI$Y*~>Z7-t`5FE~)#36ls9ngu zg`Yui3&dVr>Ok#S=`H*W4N!Hs;@6gefnm=Ueuke=b-3IIN>}1r`59!k;h{N{o7`>o!=&+vS7rf7+&t;XK>pMG8uP&3p7uXvYVgbA^~-v@r?IS zHTc~34z%BI4?hFzUWmP*;u>4{fzp*ER1H4!vKSc{?Dq0Atk?%J4_AMtAGEK0A3sCw z0f;(W{Xvkr*9Z6+st(~-*TulV@Zu0ZL(yS~I$ZG!I@hY^Fh7IUQHVNR?gQ;}XgJEx z5ONHn4ww5t;d1C0Kf`mVI$ZXGO!){>a~#CNJ+2HI58^t`&mc}f9Vq`Q9_MFBg{s39 zZ=iETDv$FsOg#ay7ngt6FfcIOKEcn>eiEV%)Gxr64nXUh)}Q2Ocn?*F%U)1>gZUK1 zEPUw&)Q@!}LLKN{OW#xc4CkS0apg`N=JkR4(?qBP z)q{dp_!-KsK+MDyzo7m?7gP;CdqL~S)?MLe=(-9q544X5Te|XMWMH^*4Wb60d7$-D zayR%Hrrdz2#g$G$<>{Uq{0x#eA?k4X1JrNSy~)qePCy-~A29nSKf`sXI$Y`C0s{lX z!<+mJ8*f4E#Z_*A#@FO;^D}hahN#139%!BUl-v9aK6fDMaJ8Rv7#JA7-Qj0Qx(iVU zBC&-tsJ!}dm!CoQ9!Lf5atL&uwE8`Mh7PDYT;T^A&zW|>s9lRK-azvj-=S*oxewGn(0$C$FzYeIJW#k}GY`}s+5uIA&pgn% zwFe*bGn{z>F%Q)L!e(9=69a?HQ+|e`=MZ%u5}P_udza${KZDo{kP6)C540{<;RQc~ z*(-=TkUnhYfy%2xulO0%UqjU4N~fTC9BZf=eEw}`VqiG(nxDb@9mG5kiOpVnCI*Iw z@Aw%MK7v#*fOjZhQwKW#dCf7FjUw#Ice-QIj{)IReh*|j5 zf%3ICs{lhJD?}}>bO1WPIGI&|p`Q(+4&;Aq?gPc!OEv)pKX!;ZT=53VM@j4g3>Tp4 zaHVI^IXyfa5VP?412q3($05L=#tBi2E8alu2p3KPh6boQT>b^kmv=$c;IkJrzcrUr zfMG5d#9mzf&}Lv@xXC5Jz`_kths$2j`P6(+HTdjZ#Kgd`fLnmUi4S5PC?2uJ2k88h zb9@2}FQDph*$X=7LH9*RD+@3zSB0pMZd&Op`T@&~BCyrwC@@E@uU zSGINIuW4`bbp1PmH@+hs9Ie1g4S8`Y7@4%hKYe8Ra=1Jp)N!%$Q{`7 zHXGBGd|84EC+Hif9em489^@~D~# zFmRbe)ZwxhggVeTT&=kP!w#rgT(@BLG$AB&H@aJq3S^8HMV>X8oz((EWnWF0x=I)e};{n zfnkP=0K+_Qh&o*R{I9YxFr@hiFjxoUR|m3}IYfY=Aq1ihm%Z663=Eq>1Q=u^A?iTw zC~W?SXJKHt9Vx(I6^CElb5MT-W0<8zRA1}bblmIagS2|q{x~C>VfMI(gL>;dB z9JGJ%bfN%*WDlLQ#HCqvZXDo;V{KG{TuZ$n!hwm5n%8qpbpf} zNrtMy7cQXniFqjk3}vYhdvVnZpml;5QUw@T(je+U?Eq}?2D*<=CryAMihw%MIl@h8 z0u1fx5c6<_3+Voe>*)dv92pRGxZ*8SQe@{L}e0E2NJL>(@BLE}=k zc>)Y-1rT*05?g$L#-)xy)!<79pnmP20s)51LXb(g=eI!VqaCUSpLw8u?Sw)BhS^0B z^KgYTXk6-bkpP2ZDMTHp7mUpxp#IDCQUQjiP<6P%1vD=8zEpsLwE|)uNFO%yK>g5( z6#@+3pz3htH_*5gS0%(OeEtQEOHHm6V34YTsKw;k=MT_$SnM>2o%qy& z#>4uj2{4pThp5FBzo7R0)ae2Y7oqBKg$t-1cN?k(pS_^*uy@l17~ai**o(`*p#F^3 zOaX?_nGkij>;;X7#Y5HLvllcT_Hm{F!{j*-^FZ;9EnR`e!{p}*Fj&rosKaG1Xgth$ zt^mXRc@TBD(y0O~1B2y!0fxvW5OuiP?Vx?V^Op!PJX!`(hbvsxb1*Q-Ef-)oyB?wr zL}K#?XnZYqg8)O-29OHe^El@03=AzB1Q>R2ho}S1pJ6i(G`@CnhX4b^E{Hl@?gO3Y zX0uCxp$@7JSGos{<4xKnz_1Rg4p%sXY})}C#O>=IzOwil!Ww|_zN&JXqq zFxc*csKb@+LFHfcJ^_Xk1k`yjGB7;bC%~Y00Ae1f-Hk2$K;v919VT#fdc{z ziw{D~!xes@dvvxR6kvFN2%-*Gx&qzDYerxblZM zGXulo69NpoPD9jz@;x^HZeUFPWK14GkA0fxX!5OuifbI`eOm6rq;F z0IgTCzbe2WeGR{P*BKcYRIdpz{J9QMhb#SU1Fd(wA;56#21Fe$f81tZV7PEYfMNAb zh&o*H3u@<_yeYtN8=1nJX9Sn_kqS^Gofnmg&(M$ zSW1LCka?4cR5yOlRT&qSzmW?*38xCaSieBlBbfA%Iq9q4|h zHX_u4Y+Oo&I*@<25}^(>eszfmbs&4c5}^*1-&pU1RKr3A`#vRZW(EeW`vMH6k05G6 z`{1#a_n`BxUp^9G@OT1IhbzB<)*Z$?5nz}FRfj7dfyTLZK-Iuph|ONm`02GL0t|sq zA@<@j?=u4fL;h0%hNNc@b-2oL7ZwJFdCvqG9=(F71I=Gxv)6@@f#Jk!0S2D85Ouif zU(kKbL2m^Zs^3A>;j$NW?)3I|0t|NVA?k4X12hg0166}BTtMY>A`$99_GZ5qVE6=8 zi_3kWa~JeJK-`AUUQl_T{y~6&^&>=GkiE;lLe${17ZlE$h)@R_XWUMNI#7H3G!g1R>GZ}|0fy7xAZ`Se!`RXvD1M#4 zL)75&2PocriBJcccWEO+9jHC9jRNkq83zNy$sYm?_KcvJa|Q-bIfpI$N*EXzHZln^s4_#;;W7^->kL(c&wZeNQwS02 zK;>sX5$Ztuog0}28P>CaT)@D9tA7bPSDceokfDMVq7IioK=(dOgQ~&j574;ONmfAy zJvRL2f$mT7VH0Fn$&Ozgs6Tj|U66s31ELOB`vr9Gh%kpB!+EGWT>b#92Yw4xgU=t@ zj0_C!oPrERTo8Lf`59X};ALW9Si&X9u#pG9Itgh8hQ~aD48PSO>OkWx*vvEKWMFu& zDafE=3{eL4;EgUZ>JP&N4C17zDCBGsKGLLKNH zv3s6^3~pW^lX2fa4LY|X%1e+T#2caxS3Uxbi>`yJ!RHUqxWFfGL56rAhi#O0c!T$b&4D$mZ>Tr!ag8I+z0|XiVLe=5QZ=ic4 z*#iX`#DgH_;cCBt%7fS-L54$6b-2<2sNTO8B*;(_3^5Ovy~`OH80vxr8B{|d>OkRy z&A*`g4eCP$8P|REaELlw`5bhv#?x>?2JZ-nI$Zt$)mHIPHTdEU zWL|!RAj4ItT72fcgsQ=39w@*5BtjkNJhQ(Mf((_B5P#qbXGKN^hUQ2?h9yxDb-2O> zbnf0Ys2Y6k1Fdh8i56riiH4Ym%fFy@P6t#CKJ!51B~yt|2Qu#>5$ZtW#rKF%2UcRp)($$4p;uT z#lXPu7^()J`#|N>-UNtQ_|$>Y{Q)A>f$E2m;pmTMfB?&St zOorHvOWj!p28Pwif(#WY_|<{VM`}tDWH3&}uMU*{Y@urK`4_a#t%V46PuLk4Zlnq_ z?8$}Li>qB?#K^!Ok%L{8GZ^fz;h2Qk`!ZVe>%u!R{eaolZG%=FK5O9cUdHPlX^uX9dJe zT={4&2Lr?83PA>gMuaWY|bwvz=Jil^XL*T@1o ze^-#fU?xNzuJ8k?%epKc{mUGgg8%oAKqSRE+;?k7^6{u<)Un@@x~Hbw>p{y1ub@J;38C2KfR|l#;^w$eAByNDH!&Og%&QC1eAjq(7BSamp{JV>Rf#J$V zL599f5OuiB+snYfuxOJYL;Pm^>Ol8&6>k<~n6(9>4p+Mbr0&obL57!G@vCbEt@qg` z$S`diL>;bn`$Pr?h7;Qa86vks)Zy|kX#BJsss>+v1GS5%ZWm;b+=1UbPI(Wj!z@WTSkb!+SL>;d34>V8byjzgrCsZA-cmuU#dG|of z!slNNMg|7iJ%S9bdm(CZ#T#h+Aa}1IL;605I$Yrl+TY!}PmtjwR2?q&Z2{ezwqKB8 z=K+X$_|%;}Ajr^oh_Je;hXffskKk7a8n=i&BFJzGst#9tfX211K-J(2Kae`MqYyg@ zsk0zL-A@JvhAbl0B_AVfFUY)@i#n%P-(p7tP4Pz<5!RA?6Z99j^EQsY|;g$Z+s7VRff33o@u(#jg%@FPXtrK?cL? z5OuiHJxE>TbwP%xn}pRh-4tYKzKvfUDE+OzEyy5o2ciy_f9)9<7?hxD@TDt|xi3GA>tK89WMWZ)HS>kWO(_8usXrFf(%vf z@v8&PvoC_G!56}6m?E4Hc6IXcv zQYZ06km1ia!s^1l3o=;!!mkcA&NbziAj3kaI$Z7pokOr1ss^8bLGzMliBJa`&v{6M zx*LoP3~zr4G93K_@dvK-0#^4&km2Y*BGmm8WH`zI8ezbFj;jR7UIrlsRwjr#T;T#z z$Im3hpw3KKoguRjgC`4qb)fa~!B92$!WpElkw|sxiBJbR59%(95W`Pa{O$v(V`dX# zkY^{XPLo}T!IcBQI#BuS&mqLHhk!cJzQAJ~LJZto5c5Fk2U~p!I`^m;9f$Y7+ zCB$IQ4KWW_`wEl~W1(vBnb*q1z>vl*#PAw)FfHgTPaNl4%wuF=n9nD~U@HKz7ql(_ zn?FGFDXRs97e!0=B< zh+(rZ#5`Q~vNAC+>=hPbI3x;Dhbunn85tO?#Do}jibK@lavx|uMOH$H;hqFU9j^Wd zNL)Zth(S~mq7Ik)!0V|cg%~Uer~{oF?<^_AFafF#S2_UAL#&e&VqlYk*o!OtK<<-* zs==3DK=*0NO9?Sdf~v*U{{YQDor9{uXC5e>UL`^uXkFhuDIo@FX^1~?`4^=7H8_oh3pYDE&PlLLDgI{3AjgXug|Ep0N8s<)o~<5JQpzL@loRGKrCap-VxC z;f*4Gb)bFVB1#Z7_`(^~|BWF+9q7Ed93s?#`uo3#PzUP&b}9=od{u_H5m&whjWckn z2r=}kK-A$17tp@Xtxz@i{QHrCf#I2o5W{sJT($xSZV@MhbtX`>eaI* zLJX>=5Oui157h3oG!0NRuFZd zdKQ~H(7hu`RzeIvq3UqO+k0jP24-s^hEH}7^FaL|Z03R1S?;nIVp!_{QHLvBK<&b- z4nhnMpz3hh%frgR@XA4m;hQVOJka_9Z1#fu;ovUBaNQlE4wrc#*@sXy_|g^VJe1Gw zLJV3SARV~(Z8tD8FnD+fF`V#*sKa?*77GKzAs-=zy8#e&p!Mn4{BfFzf#H9k5W}eu zh&s@Ed~E7K^R;tAg&26lAnI_XmkA6E44z>^3^$?baHSW}x-{c(A%@0qhb%O9Y76sM*NG1#X;)Zua;D7{pt2{CMf zs>2n2`=7G}vr7R(a%xs7{TT{4f#x{tZ_}m9t->%Un#NZ57i_1LF zxe(z{HTcYX4QgMt2{B}MKOkXE*wQm-|7zGIA%>lkAnI`CwvQ{h(T=;#7tb}AE;bh2HksTvs{Rwe+7PZpnP~0ss^7wdRQ13 z9<2~!$Xg3B59EJr=}(N2f#KRZA%-{WA?k3IFQ9xrZG#X4_eO|1T>b#<>kZr}#1OLy zq7LL;dFG^ic6VzUs#eW*HI=>W99@$Y6KhKel^^Kg|Lp!UnE zEkX?Yq3UquH_&?9lUsxsen8dXD#tS6G(ESTrwhA$rZG*TGmpV|t+hLm!LkgJp-NFg&4jNPU5`k%8g!E+K}sdm!fFvKN#NRQC!ogzSZ=!(}h1 z-5Ix6h@qc=I#4+@bFUCX(te0}xZ1Ixe0XfX5W{7tI$ZXG)ZN`L#PEs;bzk-iF)$v0 z*o)6R?gK&$5(LzN+D%FagcxiIsPkZ9U~oSm#L#~PVlQai3tRkx?!$d~Qi$R2DTq2y zKEM#Oc_D_V3lMd<`j-=!7#KcW5MpS!3{eN#Pl(Mt zPTsC{+7GLKU5Fw4I)3v&=Vg>$7h;$TRfnsd2BqJl*M%6)L)GC*S0Ml1ye`CGcLQQC zF86`vg{p1{F>Hsb!{y&c3=9lcLF#To%)?cGfW%+k6k>P>Rfo%cp!)I0O(BN=M5yDu zCBz_b3t}%W^BNc#7^H3qG0eIRQHRSPpmezdss>-W0_i_aggVf@EvJZ32kM75-4SAt zxQpL?p#9EfP&N462Wp435uq-Hk%3|QT_J|w_aOFy;s;xP1MNG}y)VS@;yy$ju5bpW zt55fZ7{VVw)Zwb%K;tOcP&N4c0Xh$~iwJd~@vnVEr~~a!c|e3ZkUvZw3Ng%l2=NCl z|AOwHUJO-(&mW+B3;Z5I%)+OxjFEw%>ah^RhbIuVxWW%quCY9YsKI9*sN5BKD#XwL zRg23X9~c-Ix}FL#R6T>3iK~18wM!N~6Jj_ERfo$wP(6M9nGgfpbBKAk)PejV09Av} zAKsw#6-20$Vq{>*CPE!(ez5h05Ci8+i2HDbGpIi!3sr;9eIRwtM5@aqQe8h0>OlR$ ztwg8;wRg`Fp$=r;BO=s+&e{G>|D;JmM=z7~=~+Q2wnTLLDfaXAz+el&%gCp$_D} z3q+^`jbG`#ChQMTKQWXDbs+y%5}^)MUd<*#9jLr||5}J4aVhWARK<6^qfG1I#4(_5upyW{$&Lb>Ok(>O@um7{uTX5*dI3;7#M6m3NeU&f~dun zt}cP@5BMa+@SA|T6b1$c{?8Dz@TEV{{AV!{>OkpW0uky!^JPnkPzP#<-6ldEXngz^ zk?O?05cV%<|EduY>OlR|Cq$?Nxi9xCVS7R9I*C-blt^_4iBJbB|L%SjV&M4(aU-sD z1&UtGPX8U&`h8Iw^pnfE_{uZddW&0t-!2JW_ z7hLr!XdYJ-ss^8bLFQ@y5MnTZs>Njwg~h~fHQ{ObO)GBAAoE5z`L8I&Rz7(nS8o4ufZiV>?YgAyA=9WHxgm>3uw z*n}D8a6r`I8czhxuZnOAGq7?&)ZsD@l#i65YVd_KsJwS1LLI37T}p&H&^Xe5E@1|7 zZixGE#T)3H2}P(HeC`9~Ul$_Of$EP)BGiG>eLoTEK;^+YBGsKDLLI37ct(UeQ2jB3 z2NHJp!UfbWQRF484&)DaBGn}msji+#b+d_32dZbb^9nOOgQ~?>&-{X_!51!|e8|s7 zI9x#aL!StBp!DKPggQ_<%^^Y^C_WmAPzUnCTq4wg>V*wNr~}mt?}<OkgM z@Pk|liw*4Oq=LfFi3oL|dM1%ynBfpqEv|A6w9fYdR1M5#Y~?Pfo>?Y9*nNh~3=De& zgc;rnL)7A$$BtoOVAv`GQG?H3(EPces4#=KC`2tTe}L+#OsE=s=7Hu1D~M1B8gJh~ zggQ{a=Z2^-!!M{>T;(%J9gCPSgPIt`AGp+k_Tw0c2{Sx}s>9`9(E4I#afn&?{0mxF z8bX9RQ2!&INOd(ts+&oqx@|;d52b#aEhN{8mK2UwxL4-O`K3YeFI?y?b*HnZV>{KD{!nr)Pd5$Y9iEu%-cnTI*@> zM5=p0ggVgp={GH5hG=a_IOA$JsdF(fc@dgT)9cBOlT|M}#_%jV$IM5@sv*b_7U;IFahKh*W1wq`Cki)g=?54iqkBM5qJBM-LI|K;bf# z2z4NPR}i5Nbl&hjb76)m3y{gU_n(8>iHj_R86HB_;VS~ z?8K)IbpF8{BGiF;@7IY?2b!;VNrbvAMg|63D`AG^)(|)1s<%Mr&3(5PW=OMvsKXU5 zAlVihVTS2Yb-3y`P&vL3ss>-UykuZtIBFxzu+A1_GVXJKLFf4%vK3}vvxBI^Wv>Mb z1A~B_FoTB^L>;buF`#~}ld~|x7iWk%T>cFQ?KyW5X2^7bsKZrWf!6cag4DS})ZsD@ zbiQM|t1!bk0_yHCGcdey6=pc$2{8{>y>*;Jp!j?`!<4D;)!VH|g z5OuiPSD^C`41I+e;-Ttr`S%PH14FT|FoS&n#5`Q;K;{Jp2s1PiP$$L2z%VsHm|=Yo ze)G!sda- z6Q>fP4m1uoD^!@_TR6l_(6|@2bnt+If#Fw#FvG1#h&oU^6q`EGd~HmWFvG4Wh&o*H z3mS(z7bVPK6b(@aD*v&Wr^Ud)&=W1punnpXmwBM}?x|>Dh7SbPf!2qy#0WDu#X#)E zWgcjKc36xsLqAj|rU)}cLDk_h544^$1*!(0e?j9z z(^G^QK10>ws<%M#CYUPB;FJn+A1-_UGcYjtqzW^5r$N-=GLM0gfgv+Zm?1MAzq$%W z28Q-@VTS)1_|<{-OUh;nGaS#vuWma71H;oyVTL(b_|<{VE8UeP%X)rp$=4E?ju4SXuaqgBGiHQZ)xTVGX&*A+=weaK;>>ht}w$i0_s5Z;Ferr z2Ejc1=7G)u($5oSm_tAvs9&}Vss>;9f$sT!o+r$3CLdxiuJQ#`&pd#t!Dn7D69WTR zfiOdC5yU)P^%m&fx$Q;541FaKb-3zFkUD`TtOabbg(0l`w-|4MZJioCRC`3o18`)Ce=YgsQ`pe?j^`*9bE()k4g} zr4FaAN29|nZh9iv-^KiAZmoqUiXfz2koNs}s!<7ycnHd-YTZI{lI`FG=VP;_X z(jm-X(F0KjT91V--az^CMvpMVKd3rf@d2ujIC_N{ta>5l;R+Yf{E;724Zd&&nUX_< zI?%e|`d(p%%}}+t+y|O3+uJM5@P>do(0b}`P&N462TCvOeZmYzeIS?O?l*$wHQf4y z85*GKaFu_ca(7;zFvA(BI$YrgGVej3FoRS-#9mzHf!duG{lW}|1k{1{_w@G*GhBhH z!<9}!_WXgW!51zG3=9naiBJdXPsdLXW>`D{;tyQ@UB<+~uzi9sL+oUTI?y~NwsZ;_ zPnk=kggQ{aV)YbZ2L7oKGjXLW&^Uu6R1H3Rzc4T`*iIE@Fq{T457+oE z==`u;s2Y6cu`@C-JeVfT@O(PNJY4bi8niBLhA_jg8Ti$K#y6E_Le${17nINKiBR{1 zfq|iLrZ7Y3EQpz)ek8VdJHx=h&^}9;A$T@K9j%Lf+AquJvSNwwR`%GCZ%&?q*I#9aYxLBCsA^~-ve0h7ZFoVbv zkPC3%n;Xx-z#z9on4uP`4wpYb;W`nj24B2&t1vK3TO!P$coJeSX#Npf_<_djj4uc? z%)JOv2QnX$K=oiNR1H4=g2vG|5TR}c=st)0!VGZ_A@<_(M>q=u zL(M~BhRaVO>TtD(K7#P%^3p0Empbm6yGRq5LhUqUM=Hapz zbbqbnD`AF;*AR8M(%&&g28K(og&AhNg{T9yf3d||BqIZZ-aBE2_V*BVpm7Im>Ol9% zUVkslApH@d4z#}xn>x@r6Nw*%88|*c)ZsF(mYIPe;*&7L)vpkBxXxz)ty9?jO_+h_ zJ479>aOP%WU{Lrj%%J@fq7IjTLE|6?e+o0ag{s4q?m_wOJ5&w6^ar{(Qs9>`L(ngX zy|~P~%E-VF^Gle)@DD^CuJQ_Wj^Wfl!VD^ZA?k4X_c=EMgT-HA26;}<3K<3lP(6z+ zK0xE`j$9%PvV0JAAb(*~C(6daAkHtspd$)V2O4L=rtUKn1A~@0L=C=h0gWfhN{KM^ zNI}%%3Kvj5nkFT}5G)N*hbv!xVP#+_lonw~Rm86jw7#iXQG{U;R2{DH1D#K^O;LnF zRtaJrTwO&RV0)#5S_v>xuVk_f|ls5(%*f&GESzdw~k7+94- zAp|+k4Xh5$zo2kYhN>atK7C~o25YEVTzNac83?I?ep}7xa9>1yxgSIMR|9Yy5FodG3Lo*MQ zUW%b=2>G{ORfM4%surJrSE`CI97R`$#lMeLMHt?rt3z`i$iKX5A`F^pg#GKTCc+Sm zt`3WNg=!)Uy-;=d^4oMZ5r)N3b-4TsN`D8{L>O*D)!{Smv6=|Od#E~G=7D_4t1iNz zs19P`PtW%1A`IT>>d^cPG9^=8grOE)9hUf*tuDf_99=6zNd zVfc@(4$VA}`(!ml7z{N)Ed2iU*AQWdMpuVs9>{%_8X^px=<2YzZ@GpD!)A1KXy$?X zgBLYK7(PPP;fvqj8X^qrnrQw&GY^!WRW(Hz9HHv)ndhx3!VnHshcCU9Le&t;znz*Q z3{#jvDZ+3bsuq`dp!)YMR1G2XzG{jvFlylsKai}vmI#9}R2{zX zv(*w|@Pw+vMa3)uH(p zWZrZg5r)<1>adu1Qb&a0D!Mu>=6!~$A>?0nT@eOhU62cKrx#FqHiW7nWS*z42tz1T zExzz8hN>ZCUc0Uc!(^yhT;_q?w^3Jw;Uu~`EaCTDSA^j+x;nJ*1DPkNC&HkqhZZha z%yZWhVF*T7hh`oqzZL3WeUVqN~H=zI1&NhH`XuSo}LxUxZ;Hx;ixTK(5*kRYSSBGXE$WMW|YQ{(WyC!oX|@G8w;r6%9ogbkWtJxew%DZ$lA=XmoX0{99=#!qAGY z4$VAJdA}H{hLC?(8;US&hpNTr->Zfq3@_2uVR0Y3kqCpZ5n8yQxep|3Xe7emimnce zdC5j14EgBl(98pcUoTV*A^*-b5@A>gRg2HR$Bjf7ZlkNi;=Z3oA`Gm?ARi&612p%6 zWR;CY7|hYtVKFb%ScD-FT^*WvAph1Ii!k(~tHWa6T4NE0o#^V&%mdZu*P&_%h2LXi z5r+3rwfMr1*F=Ot(FEjkq;SFFK6?`p25)qAXzl}Ze(i+Qt6L>QK%t3xvn z6n;mcY6$uFs)-20L#SF@{sq-<|4l>~#7#ji#qVEJQxOJ7baiO%1DO(UD#B2Vt`3WT zCz^^d%tcp+#k}37A`Iuz)nPI3t*HpZPjq!?=7GXb+)RW)-3;VX{Qh+{6JZEMSBGXE zDE#uxL>Stk>hSd!CYp&b%!R7M6(6Af;BGS!hTG`su=w|(L)8$RC`8VHEgrOR$7N37-T8c2N zMOTN#zo#ul7_OtMLvtS}KE7ItFtA#IT#Dbn%2px_`snJ=%mbO?YbC;vh^`Kc`|7Pk z7`oBbp_vDA-%2YHhMnl@u$Xt$etRSd6X?i+KmFMHtSatHWa6YikjP|LE$lm?vu^ z!k}pbasg60Kr;^%AMQ3H4B_bNu$WhBBf`*#t`5ySP}z5n=d?t`3WN(zYTD>bCg(3o4&oZABPT(bZuwuh~|Fp&wlxntwszx7JpK zVK2HmEau&|6=8Uet`5ySkbhb2L>P4K(ENeLJa0P@hH!LsXy$?3S86B1(2A}Oi+PLf zL>ShitHWa6S*RL9@q5=!gyAJrEw1=<2YTcivuv;WoNDH1j|{{b?`4!0P~FA>~Uf=4m^KFqosO zLo*K)zo8By45{equ$b5EAi~g(t`5ySPQdW)uFi$WJ;o&2tz5lIxPO3>?Xo6A6*@q zd7$vy>n6f*7F`_{^Ip4&FnmW>hh`qA{t$Hsxe^w7xa)IScM%3ns9JpC=k6}T5RI-5 zi~B0wMHpJq)uFi$Y6C3=g4darqZC{`cQYghAY!uzyXxMHn2>)uFi$WJO3o(fontK9G6J zz9J0n=<2YTm+C9RP>8M$%{)+i^!ti1%tu#;#k{?~A`BLh+#d?#s z#YbwO2tz5lIxOZ*4isUSkFE~QJWzeK7pjI(e4GpvVYmuaiz_}r=6!~$A!OeFKoJJ+ zAhd8nb04Vu(+m<}@I_aL#lP7>A`Io|>d?#s*)}yugkdGRIxOZL4-#RxjIIvNJWzam z3=&~r3yG2tzKqIxOz%4i;gUj;;>PJdpdg28%EpMOTN# zyvM;J4DZp^p_vDYU)~UiUkSypWQYiZDpW15^a3);8LEbmdEp@<49QTnxXc5!pBqC& z80Mm@!{XoFAtDUN(bb{(7ZiR^Lqr(9qN~GVo^YrLgM28+M@Z=ai+Q$CHH7@@8!EyO z2~~^BzaZPnLq!<6(bZvb-^x%ChOOx8(A)wJ6Jf|iSBK_4Q2o&kRYNHJ`olyRW<%BD3%{LVA`EBI)nRep>o5_9@965# z+y@E`(Qpw4&2SJ4DO|9a=N>M?5R9%4i+P3NA`GqQ>adu%I9!BbJ-RwH^FZnOY`6%+ zU37I=%=;ZK!oVJZ<_|39sY2BdiVxEW5e7%7T3qn~%D?duA`I2&>ae(PW`qdCQgn4# z+;=!agyAx}IxOaWj1Xb?i>?mMJW%?Rj)a5>A^$2ziZJLy)#CCmC_a3lY6zJZ8!5t& z2~~^BJdn%UBSjcyqpQQ>-<^>n3`fz`q4^i&zQ>Uw44=`}VKGlIN`yf+3giN$bdP2p zsQ$2qsv+cG_b3sDV5nML{sr~l3Zq0AI?>f(ao_SN5r)m^>d@Q=a{a|95r(Je>adu{ z94*4Y9}Uuh+rOaj(~TBkutisg#k}Zf5r%Yhb!hGbnb#UE!Y~zG9TxL8M~g7*M^}et z9w_|oMvE|fg{s5X|NS2=!oVGa-@l;ti)M@ngD<)|EbhyW5n(7tSBK_4ka<&ML>P9W ztHWa6^%xO`$LQ+N%malVW2^{+U@V$Hu$ZSGE5cxnt`3WNk+C8Snds`UnAaXF!Y~zg{s8&%IaCcH^M1#QFtEpgbl^_UAX(Ko5e7?iby)lx z9w)+(jIIvNzo7EIF;0YGF1k7_=IxFXVK|Pi4$VAJ{638nVfc!!4vTrh@gfZJ@gN`J z_Akgh+jtQMUvza?%*&1!VJJsehvq(zZBye#7*?XI!(!g?coBxn=<3kS1G(>Gya)qh z0?1?}e_%0BK0$;*8(ke1^E{zy2&I?E1QCW*s9JpKuQ@@4VLG}xEbiNyAi{7ET^*YH zK)$@6Aj0q&T^$zl1QSIVWD`L;kirGcJW%*qCyFq5qpQPWUS^^QLn*pCH1k01gUL`e zgu-ujq6ouss9JpCcQjFi;U>B|EbjZBD8j&;gx|j)S;Zs~22*r(Sj-Df5@Co(SBK_b zka@L9A`BDJ)nPGjeUb>nZgh2M=7HklW|9cQOLTQu%wta$VGvFRxd4Cs8bZ|&iVw$R z5e8qVT3qn~DzCDWMHt%A)nRep(qs{ajp*vI`1gFW2*Z7Jby&>%n=Hb>nL^mV>QFU= z{A->f!r%;5i_5>D`X~{qhLCx=DIyG&P_?+s1G#>BiU`AMbahz#doo3Y;VQa1wD1F& z_c=v`fjt$(LP`f%%u`JjVK78jhh`oq{rRVgFvO#)!(v`-st7|Tx;ixTK(1PzD#EZE zT^$zlZl;PbJVjTBW**3W%xNMF!f7Cr@%z^>O@zS~T^*Wvp!Q#Mng~NNR2{zdUu~KQ zLnl-nuJ{GD*OsS=Fq}qLhsD1y(?l4)qN_u5A80+HaJmSCaXMlD2BwQJ#G1oG!xf7+oC}^B6Nk7W<{2Uk z&QP_u;saz`B2*0_^GY*B7#g8!ahV4y|K?|iFziNGhsD1)Gej7kqN_vmFDO2kGesDL zGeIsTke)L|7;Mqip_vCx&rmgl{9BwU!cY%Yi_5NDb3_;n(bb{34`hmejtE0Cx;iZW zZOjp2=tWnD#k|!yA`JV{)nPI3ZjK1UOLTQ;=7HjuJy(Q5JQw6r{PAm=E5hK2t`3WN z@lZ8{;v+v-grOR$7FT?L((}w*5r(bk>ah6ta;^x&eROqL-1j$Egn>7Yuz$7lL>SD` z)uEXOD(^#~Y6$r^F;9dc7pfMQe?hM5hN>ZC-uyffhSgBDxXc69zbErV7+#{Q!xDb% z`63L$`5+e{r2{nog3^m&z6gUOx;iZ8#pjDKWTUG?GY?eWcS6+=^6$cY5r(x;wYdBX za{cLi5r+Hd>ae))Z@vfvX8}kDe*dZ$h%lI=tHWYmXn_brBDy*>|AK6*FA!mvjIIug zc^eBv81|y8Lo*K)zqboS7=ELx!(yIfp$LO&A;<;z{p(yP!VrwE4vTq(g(3{K=<3ki z2TCuq3q=@KqpQPW-pN7{hO6l6(98ps_n)C^2&I?bg(3{>Mfm*-l2t7dVX#D3hsAy2 zMIsEz=<3ki2Qsg*NQ7Z3x;iZ8Z7vdF*pIFbi+Oj8L>NAztHWX*f3XOIbTP;U_~X~I zScJh7T^$zl(u+kHiqX}fxepY-6QODd#mB;85r(x;wYc&xC_SGp7GZdet`3X)I7>tr zL`w+!*SJK4!5Lj07V{EIL>O|>)uH(pRNi+()e!RU^b!$<#Za}l{0nmZL8ux+=3Olj zVR#5ti_1LF{;2;YA`I%KAd`{O0T%zdmWnV0qN_vmFDQQVOGOy!(bZuwZ*Hjw!%B2@ zSj;;PRYSSA2l- z@5>4i2Ej_g{?)G(VX#J5hsAx7l_Ct8=<2Zex4lw?VKTZpH1j~^-$tk!LjK)bDZ+3P zsuq`jLB4zrRYS{TEgNa+C0eW37DtrB7IMOTN#zu8qH4CUzR(98p+m#I}E z3`^0~VKMJ;l?cOmbahzFdka-V$iIK9L>M@$@%tAft6nX_V2!R0i~Az0MHo`i)uFi$ zWL|T%2*Y%Aby&>XS}nqG5M3Rbd7$vSUoFD$9$g(4^LT4S7$j>zF2EhXpz_Zgs)kVb z+1H3Lcth3VOMjU)A`G?Y>ae(Pc8v(ba&&cQ{sp=IXpIQNO>}iw%==y=!oXY$(t+E* zAX&v)5e8Fqby&;`t`%X3M^}gDK2Utr)`~FnqN~GV-s)NrhVAI;u$Xrhs)kT}JgpUB z_y|>tD?UK^m%mPg!MqOSbEI&=;=a&25r#x`by(b2Unj!QkFE}jd28!L7`l3bs`M!p=xpY7vxLcdJzW2dJqf0f9>l<7`)Ndp}7xaN@l$XLoK>GEdHHc zFT$`KT^*Wvp!9OIUWDN)x;iZ8eXbW__>Zm*%{);4m2CjI5*B*6+b^mOA`FI5wfMr% zzd?i{8C@L~_cb<%F!Z9ULvtUeU{EJkp(Uk*2lFr0^~#TOrM z8$}oxn+W??zDa~Z8(kfm`#}EnY!YFJL|2E!zvWFL49)23u$Z?Hs)mq%H#Uhd?1ieu z=il2+A`I`*)nRcTZ?g!4WHVa0U~!*$vj~Gbx;iZ8r8bK&6r!s`GY>Sr)(=%f$iK^* zMHn_i)#CCmD7{>Ssv%_F&1MmXr%<)H%mal6bBhRrcngSyKYy6Eh%h*!t3&fI$dvdN z5r$%Pby&i0Vv7jFTy%9<%-h`}!f+m49TxN6wumtNL|2Dq9wRw}M=X-@mS{ zA`F4(>d?#s&DZ8b)ewr0$*m#`^Py^S#Rn*U_d?YWGVf@s2*X9FT3qIVeEA-#hLCy8 zZ6XZ(Z6F;;;ezI0Q26OW)ethzwoQb=6RH-M`#||49jbJPV zJGwe7;V0T9!l2p(G8riyU@_0RON7B6T^*WvAX9RoY6$tau}g%Z7pfMQe?j*~uI>_H zxR0(5i~Ig|i7;?>gIs{*4>b1~FfcHvcZ)EDqpQPWUTL=oLnFF6H1k02o8K+Ma2Z`4 z7V|!Ki!l5}SBGXE$bHg1A`IR=X#T)rUS^L7Ln*pCH1k0BGfnOhVc3tZ4vTqrdqfys zqN_tQ4^+Oe_lhux_Y!uWX|D)_Bf7e>{G5^`(5>f?)B(EpH6E%4BErDHAf>>dho%m+ zE~^nkod8yKO+>0|CQ@Ask?LBBRM$qNx^^Pfbr7kplSp-4M5^m1Qe6*`>UxP(*GGgp zP#CO&wak1l7|s(bVDUhi>c@Vb}{*3koN&IyCb@ z<=X9D5eEJ~!uedcPlUk|T^*WvAXCEoL>RKs)nO@zI{QQzrlPAuGY?b_ZSE6cIE=0i zi+KS(ptHWX*cfSaObU(Y2Op#rKFSG%tL>N5K)uDwS$iHb5L>S7@)nN(0DHB8(7NDy`GY`~W+Xq!c$iJs1 zh%j7(s>SEuFB3!oO^u=uxaq6otzbaiOvfn2p= zq6otgbahzFdo)pm;T^gFqELH!{WY4 zlSCNip{qkP59F#nlSCLUpsT}T-n&U648PFTp_vEruf${#293!elkxl4ZL$bM5V|@v z^FZZ60aOj4^j9%igrNnh7FYTMnYRe4hLCxyCW|m^gQ~@6-W8}CLgw9>EW+>tsuq`d zp!~)*1rm0I%oCa-!XO7#i_1Jv{;+|nA!J_26cL65s9Id+f#SCgs)mqxeN#jjWCb7Z2!kKGIyCb@rsPZ& zVQ4^ChsAyKriw7ELRW`o9>{$sriw7!Kv##wyl+!Q7?`GkT!7>cH1j~|MFFaYP<-f2 z6JfA`s>Kx_p!^XARYSd?#s#YYrW4I%#~%@AS8gQ~^n-<}yF3=7cJVR7HS86pg)(AA;259G@iGej8vpsT}T zp43bc2DOadtsHdBP530)nUd7$`MFjItK1G+ja=AD}+ z9h!Nd^!#I{2!p^Zh(AF6K3wf*saYZnYEX6f;=^T@2tx$AIxOxhn>Z~&?nSA2lXy9ZT6$h>E>MHoIo)#5WxU=Aeg z2$?4_M}$EIsurJlPEa+3%=4Nf!Vm^ki_1Jve3U@d5Hhc2jtIj9s9Id+fyT$z%@JWZ zg{}@u{JxkY!te!M9a{W?+$S_wgh6R8Bn;v9VlmHQt_Xt-`+;?rR2*V?Eb!g^+{L3&;gh5~)$YlKaO>dqEgB7|uH1k01 zg9xY^LjFyeC&EwwRg2HRee*;Z7NM)d;@<=FL>SJXt3z`i$iJ`Vi7@;^SBJ$siTNT7 zD)Z681^0q z6I3m(_yw7l098ZCyo^O63?)#txXc5&e$pZlhGppLu!P@{MIsCr(AA-ZA1HskTO`8p z4_zG=^JEr_Fla0Wxd3;10r}Srs)mq%{T7QbL_yW!^KZps5r!Ugby(cDYOx5zHgt7Z z{Cj1w2*VR}by&<}S|Y;0w}h~Nb)aep`PXEL2!jJuEiV6p;v){KhLCxAOGFr|plWfM z2a1mwP&I_iTd+igVGUF*F7rU4acYSO!##9$Si}LZplV<~#?}AjSs}t8u>zz6cYXt<7c;0D zLgv}65Ml6ws>K(687o8>YS7hT@$ak^A`HvW)uH(plwOXk5Mj82t`3WNpH_%4{6klV zW*(?LB(o9{CWQQJvQmV>0jd_4e?jKOtrTG>Lsy5zeN$G7Ff2eC;#XO%?A`B7e>d@Q=vaM{D2tyaTIxOa`SS7-+1zjDQ zc_8;)S|!5p2wfc(^B7i(Fz~Dfxd3;12AQX|T7S`mgL=<3ki2MWJOYeg77p{v7Up1?X0 z2AOpr7vN6MApcrH)e!Qp(>f6bKd4%K{>@n@!q9-O4vYKdtrKBbg{}_GzaZD2SSP}8 z2VET&^M0)pVPIPi(t+E*pzu>!FT!Agt`3WNLF+{r;?UKhxesJs&3X}r9&~kB%v-fy zgkc-HIyCb@uD`Nggy98L9lr66PwPb({z28@iVsl!kl7%@V6p*ZGE%r;abM5|5r#N) zb!hGb`*(u~Ll3$-Eat7+Ai}T>T^*Wvpz`I)1`&o2P<8nH`)h*;1KUP4f1sHMl2zF# z!eD`}4vYK3Hi|GLp{qkP59Hs5jUo&a(A8lvZ{0=_hF$3D(98pkH{IAM!tevC4xfLS zHi-X-~m;OE4_fm6Vsq-2$@&3Nra&esuq`dpz>u7 zR1G2XmTVGX*Z@_F%REqeJ_l7p$h>EpL>N9n)#5S_p3G(u1`ViMT;_rN z>jqUr$UMKzA`DSbwYba!<&TQZA`DZY>hPtPd7DKTRzcO_G7sdw6HqmT+;?HK2*Vwy zT3qIVLhjdQ5eB|3AQnZJP*#5xP1o<^^mM zVMsw&hsAwO+e8@p(AA-t2MWJ6P&I_YZ_73jh67Nw_`>hrHW7wT=<2YzPhh(UgUoic za6xk)NY-k*2!kKGIxObpY!_juKv##wylLA-7*?UH!(!fv?IH|U(AA-t2TISMwu>+@ z?EtwHfBY)!5Mj_kSBJLlm5G6Y!3(Me79R{!*w?-KpsB+(&JeOggdqW{7GJ#8?GRy@ zgsu)txNO)V!mtNj9hPvpwL^sA6}mbs=5g#4VG!Ag7Jg{q0&=$zR1E_RfWie$9SZ{k zgDEz3Xzl~0drLHRxWdnIrwD^DR4vFIV0CEbfn1-xQ-q-&T^$zx&fO`(uo7Jznt7mf zb$q7?!*z6ZSj_vnQ-pzW7sv%j=?^VjK<<`@s(}R`I9;Ks1BIUw5$aeO7#P&Bsl(zA zZ8UY@@WbK{>s=xY?ohQL_kz`-xep|s3RQ#8AE11cL8Q7YBGu(!Q-|hXQ2Q!xmk2`} zR4u;t#)MrW40E9BK>h;z7tK6StnS(+!f*~<9hUHWvrB~G2f8{m^FZlJY_|x5+HR1^ zNa+QOc`my}7y{7Mp_vCVC2zL~Lle3>EaolPEyAz{T^*WvAorcxEy8dMT^$zle(V-u zVA%t50g^w^%md|fC8!!$dI5(s7W1_Bh%lHz)#8c|ka;0cHH6HI*(1V`0ac63Jdo?# z_J}adLRW{yeLMDuFdRWwhvr|9d5`vpFnmK-hs8XRy&?<>dqFyIhaX7RZm$SK5V|@n z<`wJ}VW>e@hvq&|{Lb1d!mtWm9TxLW>=j|Sg02p&JOJhMPf#_m_yCt{X!e5q@nx?F z1IIoPi-7@Oz7*Og!XO7#2ah*2dqLthP&N4M1^L&0p9q6LR4qPxBln3gq(aq!%m=4a zGi7;%0s)O5$CH&6s6JdCat`1AQG42;(;N1^$ z0q%4K3K#ADA`I5(>ads>xnG1K6oc0jg(&4u~*l9Y70bEaB&IK!hO# zT^*Wvpl~TVAi~gut`3WN3l4}dtU*_YB|c6;)!+*kkU!2I5Mg)!k`0Hi_c!OgCY!0P<6QM1(ibyP&I_?O*$yTPy|(r&)%AY zA`Bf+b@=RE2314I-W3N$7MkX)!{Sm&LI(o7f^M$%mal6+hGv~sly-^QhACcUs@a%VQ@iLhb8=y4vR1pp{v7U z-h{&<40F)cq2)I=1_p**P&J5jpn!Fs<^eQy;PMoUy(bQfFkFGE#TPE04vR3b9U&Yp zDn~>Z4A9k~g$u|Ozat_HN$BdZxUb=e2tyCLIyCb@<;$ugA`H9G)nPI3#t{*QC+O72@hokT;a!cRD?kcsuoxHfyQl3plV}|T^(Bb1EuFP$3z(J zpsT}T-mhaK3~a|CVS~$kAoEm?i!hi#)!{2A?T(8uctO?SGcV(~2tx%_9X|7#j*Bq# zLDj*{Lo3%n?d&yBHL&;q_bbrUf%I=6LLJCGo3W`w3ujQe+JdGISH9eHT!i5SR4vFI zV0Bo`dj?el^9NWRmUw%2T!i5lR4qR9Bu+r$mXLW0Cqx)@plb1%=LJ7Udss)h6zx$_{>`eRYSH=Gn<*n_SPi+Q(BiZHxGSBJ$so>L+W5~n~eKx&7f#RsS#Uac|Kk5eKHJf}e>BZVIp^R!NjFqomMLo*NL-w>!8LjH|8Ey9oiRg25NAoJRw zY6zLvcUpvD7E~=h^L9Yh5Hjz;X%U7qP_?+s1J#$WplS%2_v^F>1KSz2a7GJ1kgUoX z5e5r%by(sf?2HIQ61qAx^FZ!vI3vO^0bLyy^VXdaVc3PP4$VA}`))wh5c2PvGa?K> zplWf2A1Hps&Vo#ZnTC5EvfNn_1}&&sT;_pH@;EEP5QDA`i+`)miZHaHtHa{oC1*t# zcA%@nV&1j0A`Fkv)nPG@;hYGA$T^Tp@#iLH=47bqLVKMK=c@YMd3uyj8t7kyzSqZ8J7Jk^y zePd@}V9>%)hdG}N>Mt0gslyc?o)<(ILZNDL#Rtf|VyGIJ`*4}peL;j_I#ew#^FZlk zD^v|3^A27RVK@s_i_1Jv=)S%n!tftm9hUfzy(q$43<|2hfDYs5r$-Rb!g^+!nyH^2*X5lby(cD{)!00Zgh2M z;jG5Mz;F|)hEO>FM^lF@oVl-xFo<78^Dmk|K(eM+MHoEM)nV~R`c)BzVsv#_%$s;s zgkdSVIxOZLzAD0S9$g)pd7yB9dsT$tFS0xZ;=hx(I{hb^QJX$(mmm zVem#*hsAxF*F_jg(bZvb-{k8e42#j#VKMLEbrFWM=<3kS1J&oRp=t>E_w#iThW}8t zxXKMs{L0=CVKBV`@;UzbZu=V|4Bk+6_{__^A;Qpzt`3WT=id-vSdFd@&A%Y`oxCB! z@DN=c7W4k!5MkiH3GoLm_kr?<=1ma>OLTQu%nQFM!jO!v4$XZa^BQl8Fib>OhsC`0 zH$@nBqpL$R4^+S1yeY!)6I~q^^Tcn7Feu+5>|e)QA`F4(>aduXe@ld+8eJWl`#|Y$ zCR7cf^tbSq2*X;aT3qn~a{cLBA`JJ@)nRep-&-OKoVP(baFpc+$-un>$;tN0V`yvd=P<8msbG$FY5Q(l1 zi+{`Si!d~!t3&fI$W;sPi!f|OSBJ&C%lAbX?xU;2V&32TA`F5LKqlkQzxoeE7_8CN zp_vDY-^d3d44LTau(+@Nfe6E7bahzF+xS3);UKy?Eau&RAj0q(T^$zlI3J2INIoR& zU-O3|49@84(98qXmx&KW7#h*lVR7I5hawEC(bb`u2TFe@ABr$ML|2E!y#EhH7`Pvy z`2($=0$TT=2~`8jAK3bXp#4Ysk3<-}p=$A^`_M-s42e*6xY9kyRrQZV7$&2u!{U#P zk3<;uqN_s-7m#_kABix$M^}f%Jl@A543dvQI*`&cnt7mfYW`S+!5v*47V}aci!ct``RMAfn78+_2*XKqb!g^+(!uk`A`Ji0)nPGD_K66C<`ew>1SHlwSq+!y~;gdrPU9h&<<=5;<5VVH}q4vTrapNcRXM^}et9w>gF zJ{4j3i>?lfdD72B7}TGET!7oZp!jfoCc+Skt`3WN#m__->e1DqxesLC+-D*TYthwV zG4J#<5r*sN>d?v|P&x4xssq&$ETr^;#XPqcA`C(3>d?#srPG2JA`Bhq>adu%?1c!!CUkXJ z%)9VHgy99cIxOa~y%b>(dWq%_v~&e({~18lz~Td2{RWCR8#Hye;?3oy2txo=Exvfm zdnv-ugsu*YKNh?cVOWE%4lP_jzC86(gy9~#IxOb>c`3rc@d~5^DSpw+1J&nhuS6J} z(A8lvFX5F4Lk_w+H1j~|pzDFda=DE_>&{5n)&jRg2Hw zlTbDI>;=WbDKvGs?7jC!gy9ubE!&`8VU82tx@}9lr3J^iG6f8M-SoKgItQ=zbfxV7!1(Wp_vB?Kfm`P3~}h{u(+@0y$C}Gx;iZ8EqgD*unk=u z7W1yW7h!mSt`3WN|K5u*@O^;z7hilxd=Oz!fvUq7A5I@c7{buiVR2u{2N8w_baiO% z1I5R@4nP&!711kY>m8Wi>L>PjgYVny@098ZCyoOIA3_Vb__{>`cRYS&ms&pP_?+s1J%>B zplS%2x9GD7!#b#1eCC~jsv%_FmCqs!51?vsnFk8pf1gDdguZ}S_{(R5FCq*!=<2ZK zkEkyq3|Z*vu;ky4FCq+6(AA-t2MWJUP&I`7yX%Vx!!f8@eExm%MTFr8x;iZG6Z1FD9Qe;0feVORrIi_5>D_US38 z8ban>`zpfl2&xvBd7yj98NP`ysC+{U7cBmD`X<8QhprCIzo78T`6j|pgRTyXd9%KW zFf2n?hh`qgRY#y|2>JKMHxY&>P_?-H3o?)Cy9k5WcMuDI{xJD2!r*|e4$XZa|Hge6 zVaP*QhsD1=-$fW^psPbO59F$CP&I`7d+NIg!!@W{eE$6cRYS-;jvpcnB0oSbKnfQu z?lXd_A!MH04-tkSs9Id^1C`GOP&I_i>-ZtUFa@d>pLv_0Y6zKk>W2u!HK!(v|DPZ5SLbaiOvfy$Q^P&I`7 zyWyt@!yc$wT>b@_cMGb9ka>@OiZHx`s>Njmk5K-FG%>|YhRiD5@B$Hs>5X- z$drU%A`B(y>ac|0q+cQo^U&3yg&!zB_WTlIID@VZi+Qhpi70A99p~4s)6|qS9+QLSA=0PR4u;nJNQ?G;VQa1 zEbjaKSA^j|x;ixXf&44`PlUnnABcq%E?CU-|0lu_jjj%hd6oY}7<$pwVKHy@KM{uQ z=<2ZK^Q%xbuuBn5#mDo1A`G9QYVpN~;C~SY)&GRU&-uRy zgFm`DH1~l_$^9?F(1@-MOZd(IFT$`IT^*WvApf5HFT!vWT^$zlzW*0tU}g{n2Qx1J zg2ufS8AKUuq3ZC}Gwuwc48c%!xXc6jw~#@Up%q;n7WXY?5M@}8t`3WT&oYQI+(lQ1 z#k}7Pq73Ye`27nCKUGFi22-dyeEzj(6lL&+s>9`9P&+J>QIw$(st%ud-Hf6P)1m5c znFk8Lt&E}!C(+em@$Yj+QHIax>d?XuWS$_CD1#~!TDV{_&zVV-!5>{6nt7n`%ViQ} zs6|(Y#k|=}q72K?)nPI3D3d6|RdjV&%=^qF%J3gu9h!L{|H?9pG8i%w_OCy)C_^;5 zIyCb@{;gydW#~j#hsAx%nMD~kqpL$R57bV)2vq~lm<+i01>R&9Wq1lzi!Z$}vxqW? zvk>;LDT^qBBf2^?_kqGMo<)?Q7+oC}|4w8PWtfYu4vTraSwtDmqpQPW-dh$?hM(x_ z(98qHhd8S!gE}i=|GKh@G6bTlLo*K)ANf!W#()!|Anp!j&sA|AL6z+#>%mnefFx;ixTK>5udss*lwlTBEk1j9K-J*07u4R}%O%Qi z6RH+p`g_bJ%J3el4sI`+KS1f3ms^xUm78#UICG0K_@k>sGY^zra=Aqr8qw8ZiQoC$ zq719i)uEXON-rn5MHz0QtHWa6cWzMzW*#(uU@=dTN0h-3T^$zl{CPwfqS4i%r58}S zQ3+Lph&Sx#xmBa7!d7QkW z46?jv;fKXMYhFsZ2fyOPe(A41yzcOA?h9;<5 zkUPNY(98ps8w;RnVD1B}!{Xl+yrK+Splb1%cL}P7ka@RwMH!w!)#5Xcg%1)Jgv{gN z6J?Nqs>NjafHI1HUK(4?iSqaG3{kpBBF;gB`j$Eat`Vi!x-Ot3yjKp!C-URYSM;Rm`pEar&`iZUn(;`cAeJO@Ehh5&SRSj@{46lJJF zSBK_4Q25Od6lK_ht`3WNw**BQo}sHlGY{lG79ofq2&EShAyEbes9JpK#ZE|+Aqrg` z7WY*Mi88dHt3z`iD8DTd5@k4qt`3WN4}?S+-k_^PGY^zrxP(O+d?#sg`b#+ zD1(^@VgH7Rh%zLgt3xvnR3Ftr)euTAZ6cx!lb~wh;f!V;s9m=~M3mtKx;iZWeI_Ex z@CjWVnt7o36%Z9=P!T2UUnfyf20wIlSj@{26=i5ZSBJ%Y^F&1%R-vmyGY^zrPKb&! z+(1`{#k_B#q6|!8X#T)ro`RStg8{laEav%%i84f?t3xvn6n+(Aq6|Ig>adu%N=%et z8@f6y=3NmJWq5(E4vTqg;-U;f;`sdwN-qZDq6{8Tb@=)fLE@qeaZq)*(jUmbHR7TS z6VTORao;*|QHEXU>d^cPir*XJq6{z4)nPG@O+u7GNCLlqLGCk<5M^*dSBJ&CBneT5 zJalzv?gQ25Jrbe}E6~+pG4Gg!D8nUmb!g^+{QCi}?6lKstSBJ$s4@psm z2y}H=%qx==WoSZIhh`qAJ+uI-hERF6MpBev2UIP-^m0v7l;IV+IxOzvkP>AOkwOa> zH1~nx*GNi~!3|v<7V}c1L>UUu)uEXOir+pdQHE9M>adu1LQ0h33c5Np^FZ$VBqhqg zCXMC~Eas_5i!vCXt3xvnlwSO#MH%AI)nPHOMp~4i16>`Od7%1e8B`6S_+2M0%CHNn z7GM0{kQQZlgRTyX`?zF88N_7J!UfHJpzt%15oPc|SBJ&CG#OEbB6M|F%$p!1%CH1o z9TxKr$%rzXLsy4p9w^$`Aupiz_}r z?yG{TA!J^ctSG}Ys9Id+f!fboWJMWnK-J-^KOV`7GQ5MT!)G3koG62V93))unP(*@ z%HRf7hs!)r{!Nh+WoUq^!)IQXoG8OIs5*S+ZIKgYxPYz>OZ>i*6J_{?t`19jk&qW< z(2$4t7j7>W^W5Y`8G_K&p_vED9|ce~gyOeFUX)=1R4v?IH1j~|WgS!vA@lahi!z*o zs>Nj<$bB#5MH&8}tHa`7DFsmmH3hVA!Qws_1yP0&bahzFD^d_;s6$tW#k@ICHH7@T zMnRNe2UIOS|6WrNWq5_I4vYIZ6h#?C6bbv+NKur*30)l)^AZ$A8FJ9oq4^h7zH~v= z5c2OdMNx)DP__8{dq7c?;R?DsEbjZHD9Z2;T^*YHK!s=<2YTw@OKrVH>(SH1k0Da5M)L=nd7${vfvO=C z9~R1@3@%W$_~Ii;S(Kp+T^$zpO;HwQSb(k$&3&Nw*rzPYa0y)<7V|zRi!%H{SBJ$s zDHTx$Jr%TY!D60|iYP+_x;ixTK=DzgBFfN)t`3WNOH@P|HlV9RGY`}rItNulC_b*K zh%!8as>Kx_p!j7_h4___c|5A33=&YaxXc6j*9@wLka=#Zq6|S$wYba!#czSCC_@Xn zIxOM0NL7?!9lAQS@B`JqXH-QQUZAVPVji2CD1(q1By8}x&p=I-!3A9%7W0zSL>cnX z)uFi$6n;Hwq6~A;)nPGjmzpTUF?4lU%zFY=LnuDJfYhnu_b;e_E~hTaV1}*^i~B;< zMHv#%)uFi$WL}-RD8n>#by&>XqAtpC09_rLd7$vSr!LCy4qY7<^LR8w86-6D`xlho z%%Exrg`b^A!&3z#MPSFx&Sc0w&i+P8% zL>bPZt3xvnadu% z1*(Qn{y3s7%5VXy7GM5&r!C6Bq(j)h3Ob?;I_T=q+y_c8UOJ)-Y3S;(__swzlwkt8 zIyCb@;kQmllwlvbIxObh(Gg{MfvyhCJka<(o31E>nl54gy6B2B1fZ)!GY^!1^K?ZS zn$Xo@ao+-6QHC|>>d?#sx$l&&D8nmsby&>f&=X}4(L?hGnt7n`Gtv`fa6(sy#k>SP zQHC6Jb!g^++Am#DHH6aNG(Ayae))lb$HUKXi3y?gPb#jJ_y? zfj(NeU@^~6Uz8yVT^$zlD)dDedeGHjF>jT=D8n{%b!g^+;^T_GD8mzUby&<}G7x3p zGeGkP7V~rrL>X+*)nPF&%0QGM4P6};^I8l<8K$7C!(!eh15t*3=<3kS1J&nuplS%E zmnR0I3?HCsaitfKd3=VDFd<}~n4u_x5>zcd^BkaR2$|<$D9R85Rg23!Q2Z7_)ethT z$xxJ`52_ZId7$*X#!!^u0J=IX@o~>kl;IV+I<)uzna5!y${=F|2}8KOSj@9B5@m2h zSBGXE$iFE@q6{VI>adtM$w-u89=bX#=It>OWjKSb4vTrOj6@l}p{qk%j|5t0Bw`Hl zD=a>6ozEqPrViJ-AUR`E1}&&sc=%!QhX+&*%wAmPMHq`Rq(If;G7l7QO~#@O)6msn z370L#q6`Pn)uFi$WZpevQHD?G>adt6U?R#OV*&|7TU&LtHWa60TWS%GwABDq=Q#bHL&==l@8vZslyd-UqI?i@rMhjypn^e zf!T}8JT+5M1|z6iT;U9gw*aUbLguBIiZT>H)#5X+52}Wcd5cU%8P-A7;xq4zsVKt( zbahzb?VqVA1D6^8@B^g-4Kq;&3v_i@%nLIUWk^C-hn5aN=g~Gm)xi9VEggXR$!%!r zaK+m^Gf{?BP_^)IL9>^Ofq~%!R1H3RLGgPDO&uD1)3OS~z2|*9NKv z#a=XZpz#MgBGuUwsm=kLIxO+)h^7vge*-N=8DgPoafLHTyc((o#lL9wg8I{SXzFm; zJIhj(VHs2{$b7ImH1k0D@`$Atl_Njbc1 z)uF{3=>Fdps2Z4man<`BHlhp*plU(o4X$urWh2V44XO@bxLmOjWq5(E4vRn7Y(*J_ zZ1IN+s9j=UE6U)3t`3WNX||#aMd<3#!Wk5A6Kq8pmY}P{V%{NJQHFEq>d?#srK>l# zq6~k~)nPGD%1)F)%?`hRLFT#Gi86$stHWYmk)0?*9lAO!?weyL%CH7q9TxLW*@-e- zLsy5Eu0Z$QeSxaMm##qhm&+dFSC~3n=}O36ltB)v7M`xq>=j~QV6cIzftiObJ%i%c z4^15|dz0)%8STz1E_*NAi!$7Yss))3R)>~eK;ie-UX(%50TOp`by(7izJn-(HM%-1=0!S)GUTGG z!(v{ygDAsvbahzF+v*_7a1vb|7W1Avh%$UeSBGXEDBc7eMHv(w3H#UHQIx?OT^(9F z1(lzfP&Fv=hDBW#k?OLERF^}9I#9iki%lJta4AGn2Tm_o!nx5=l%W@@7M@yKbGb-2PM*+rBgAF37} zE@<|G#CxG?@YxHhKPI55!)5Ob7g2^KP_-cQ!RpZL1(kn?plVR;MNP_^)I!D8M&s2W(f;4+WRRg^)< z6)pW?G0y<1hLCv{uA&SsP_?++2P$8ZplS%2SLQ0p&;(VB%REqdy1-SGVF$W8Ea7*} zRg~cox;ixfg31jBH&F%wH?(lUVxFFxD1#NcIyCb@{*8dDA>`j2H&KQPs9Ie91(`R^ zO_X63x;iZGJK-kEa0Oi*n)^Wh{p2Rfz~oNYzY6Z63_9rQ(98qPV|zi>5b|%ByC_2v zR4p$5g7QZLR1G2XI^0DWra;x=G7pqqHo1#3oI+QJCH!8vi!yvcSBK_bkaYvBh%yLx685j2rznFJx;ixXfy|5W6lKUkSBJ&FU7n&0)6mtSnFk8LEuNwbN6^(_ zG4GM5D8oB+by&>f@e*Z_@gnSBD=$$7H*|Gq^%3YiloY5MSbShR4+Ru1nP}>8mE*Nu zq70o-weWbuV()UO8hrMG^6wfnb-3)^;U&s&1gaKpFIxD4{PD<3l;In?IxO)a;w{Rc z;Efi}Xy$?9*Unp%!4F*>7V~nvMHwp4)uDw8sNSCjRf7^PSkz4?Qr!$9)y*VQ-7F&2 z%_c$}D4ymJsct?I>OkSQh)8vdv8h9gUr;-H37R@w>Fd?#sm4C0G zYGCmTR);0MeDW4$_y<*s%REqemhll~Fz`W3r&!GM^ATl;LRW|8K9G48KB5dg=<2YT zx5`J9VH>(SH1k0DWiUfmhsC@Q zKT(DRbaiO%1DRLnC(6)=t`3WNYy3nRcA%?6D~CYgdJU=u79ZgH15F($U*05A-7ReD z(EI_)N4L?`;mSvk{X`kwL)C)Z3s#3_9w;5~`a|LZ<`1wsEa9T+FUnvDRg23!P&)PZ z7iCCBSBJ&CMt@O;UUYS6?gPc!YJX9N-RSDDn0M1(l;J75IyCb@^(u1!asK5G5M_{t zs)hR(OZ-|w)xg3JS30o5rVh=0p!l^$Q->=)oC8D|{Gn>$?n5&V6n?o-H8A(#GOsp3 zl%W%<7MFRT_+1_#%CH+<9k%!g5M_9Zt`3X)m;*%_gaaXQ0=E~7d4_?a47TX%(98q5 zFFH_^As<~G7V~-oMHyzIt3xvn6n@(SMH!BxtHWa6(?C&%kLc>q%mbxA{ve273B`wW zkSK#XR4qJQu$boxRYSp2Z=KLMpuXCUyyl{!J-VN!H_V7+l$4#;9yaPcyx7W=7IcM8!XDui>?lfd8>m( z8MdRVLo*K)zgMAZ2!-FnU{Qv*P_=M>U@?z71magh=81=hGAKjU;xZ4EKO93u8A8$3 zVexNqh$urnx;ixXfx>TYh$zEabahzFI~^j*a2;J8nt7n|?<-UdA^-jj5oO>Eg@g?* z|AO474pl?QJo8Xd24|>RT;_q&Ut*{zLoK>GEdHGxD$1}NT^*W#LFOF|6=isct`3WN z|3gI?xWfqhS2IkM!4h2^7W2ZxL>ZFN)uFi$l%5-*Y6yj2ZC;=Y$*q6}Zr)uFi$WS(%iD1&-9TDV{_&ox|>ArM_17W4AMMH!mW)nPGjVYn#6 zT6A@2=7G}R>2Oho+vw`BnD;YWlz}w@%^z6IQ;ra2Fh*C0#k{}>QHEG_b!g^+!mm0) zl%XG89TxM}Mu;-(L|2Dq9>~AfBSaaVqpQPW9&4m1gJ2|n|ANW`eW)5j>CZe;l))LQ z7FYTMxi1l_hLCxMk)jN>P_?+s1Lfb@k)jOS(bZw`@6||AhKK0t(83R7-v3BZ2I(lY zaKU1pWt1p`E4n%~^FaPhjuK@kMpuW$yophw40F-dp_vCN4|YS<5DLHJQKAf&p=xo3 zA1M8Oj1pyFjVA10x!5oO3lSBJ%Y-7%sJ)6vzTnFk8L ztudkuC(+emG4FYdD8pxTb!g^+{3{qM%Agoa*uVC%q72^X>d?#sl?RznHH6Y%VXP=a zEmSSO^fw!-hLCydV?`NuL)GFl?`Eth!&`K9Si+AxPLx4B4lP{J{0j;{(>PHES9En) z%u9|FWynWYhh`oqetY9Y85W|e!(!h4I8lbv=<3kS1G(>IoG8OjbahzF6OR{VP>#p% zUr_nt2vtKUK78Xv86u%-@x@1ZyeLCAx;iZGTNy9PuoYb$ntwszcR5~^;W@fGEatH$ zh%yK!;P)>m{PYt<8SK&3VKFZ@L6jjAT^$zpwI_%&%tlv-#k`#fq6|mT)uEXOa^K?w zQHIax>adt6m?+91n~2}Pp!{nMRYNF#ofAbF{Gn>`#cwWD4I%Ry6Ga(%p=$A&w>nXj zVL!S$EdITlD9Z2>T^*M2V^0!g5Klr27cAzPCW$gQqN_tQ50w7mlSCPc(bZuwZ(@=t z!(4QAXy$?9cXyH~!)bJNSj>BwB+Bp=T^*Wvp!_SG4DlfN)WzdGI#T6f*{oae)4Jz11tGP*i6_kqH1W3nj2QFL`!%zKSbQ3h40 zT3r4Gxz9O8lp!2l9TxYMrid~$qN_u5A1Hq3r-(AFM^}f%yt65y3^&o$p_vEDzu%#1 z2*vOJ6j285R7lw1^RFgU4I%SvQ$-m(p=$A&m!2xhP>rq*i+^XPiZU!kSBK_bQ1~5A z6=k@Lt`3WNA5%pc{-UcxGY=HM(rKa$`e|t4g2g=FG*N~~baiOvf!tS~Cd$x`t`3WN zOVdOdHlnLTGY^!W&qLJ^ijUiAq72WWYH`H}==?<1bWsM)bi)31PZwnfMpuXCK2Z1- zri(JPqN~H=-^J;o4C~R=p_vEr?^&oCLjJv(F3Ru}suq`jLGEMD5M>b0Anae$3{eJ0 zbaiO%1I2HAhA2Zmx;iZW?adHnn2D|q%{);4-40bl$iMqDL>W#))#CCm$bB!NY6zM4 zGeeYtH4`md(A)>=|0-vSGT5W5!{XoAOi_kRbahzFYtIyASd6X?i+Kk#MH$Ybt3xvn zae)) zaF!^;d31GX{spDyw^^bLjM-@Zz+#?!wkU%(x;ixTK>qcFsv+dx&}>nLM5tO^{sp$p=xoN2g+~rb3_?-qpQQ>-b43{x^9cLbK2Mav8(kfm`#|YAGf$ME7F`_{|IW@6Wmt}`4$VA}e~&`d z5c2QEJW+-`0FlhKcCv z(98qnxAjmpg#5cbUzFi6R4p$5g8Cm1p=t=3$55 zp=xot4-_AX1)>b4=<2Y9-{b;OhWY5~(A)=#kG%z=3}?~RVKMJ@fhfawbaiOvf#O57 z5E3SY{3}~1%Ag5Vi_gFAP&I_ii!KypNQbJ$Wge)!YAqCHSct9;i+}eQiZYx=SBK_b zQ24zp6lM5{t`3WN;zgni%0-Z{!RJ25B2k7|bahzFt1c2{Xh&Cv#eGYQL>acCtHWa6 z?mMJW&394OK(Pzu${R8JJ7(`xmtSO0h(g!4h2^7Wajhh%zLjt3z`i zDEt~rL>Z={tHWa6<`Pkc{pjkjn0L2Cl;I<~IxOb#mx?k-m*V#?D1TU%iZXbjtHWYm zdZ{QwF}gZ5_kqltSSre}5M3P>^Y)jDGMq+NhsC^?rJ@W!(bZuwPrOW&LAeaSe?k3U z$1+idRH!=8dO%$3uky=88LFY`;N=0@dNVl&28NkXHL&!DZQmWJ+})0*4wt?A%S0JY zL)F6VMGF^Dx_VhA%J3Im9hPvBE*E7`FUKD)AoElO=<2YT_r5}u;WxTEH1k0DNU~CtL9>#uf88rZ8G_N(p`}yM zc~FH=H7My6O&usdRAZ>ayiXIPt_GVrG=G57eJz?gTE5(R1M4@ zxXjyLDavpdsuq`dpm2T&RYS`9q~j zl)(V17H%(^d7%8^S0&1jgsu)t`fI2XW#~ayhsC^ARiX_0(A8lv?@pB{!wYnESi*&^ znsB&)>T`B9b-2PsxLTA!9;y~sxPbDdZM7&vD7rc<{wS^%WvEA2hZZiN^fI?vlwmEp zIxOa$t`=puj;;>PJWzW63RMG3FSz{sw_22evj!4|xcmz;PaUd;ka_wwq72qhwfM}7 zgsLHAUSf?XLoQS;KJ&VvY6zJ(xki*>K2$9(^FZ;j7pjJkd1q@x8E!(=;xZ4EkG|K4 zGH}^f0~dUSPI+&8yQlwl>hIyCb@ z?X}}|q71Lm)nPG@vtE=zv>weLXy$>+1LJy825)qASj@|;7iB0#SBGXEDEubZi!v-m zSBJ&CgY}{eXVKN6m6M?JeO^P=z|u3e_9Q5r85f zY!YPVqRjCC_^r~IxOaOL)8$9-`P!~49lTv;o*$MyrWPx zgv`6zB+Bp*suq`dpnC8>R1G2X*qcQegq!h)A1J>WHj6U2qN~Fae#y;`?xU;2V&2~tQ3l>t!v5866=g6-SBJ&C z&{k1~RCIM%+}GSH%FvIl4vTqfTSXc6qN~GV-tAUVhUe(&(9GjuU|?WvgZPzDdKPRG zWsrrch35~ndWM~Wfx#N824)_(pMs_i)X#ImrVfkyoYBZ!?YT@ofGY^#h zD%(UEdePNk3BT2Cq72*7)uEXON`F_|L>Zo)_s;qJo{F1k=Pgu+jc zNOk%|sxu%`ogtCxjEGccOr$y!Z0gYB15`enqN&4`UToV%89bqC@x@0vR1HdepxFz` zM;U19aM@eZF3QjVRSUNl%{)*#o!2hPunAoqmUMNYU6kPtx;iZ8{c0Cw;Ojt3e^|`Z z=@4bGKv#!m9w?l{plV>@g3G^g9ij|bP_?-H3o@?*s)mqx6FNj0=0MfrGjA7E4I%Rm zb%-*YgQ|s_hb3RWfvSO-hpSwBOQgDYM5=pFq`D78s{2T!x=%!^`%I*|FGQ;QN~F4P zM5_Bvq`DtOs{2WVI#4?LMWnjFM5qJRmkgbdyh|wkF%qeciAZ(KM5<#UQXMOi>ez@> z$4-Pg(D`s2M5qIWGY^sKc!^ZUN2EG_BGn0CQ-@YQgX#}KGQ+)s9Ie91%;nwmnefTx;iZ8Wp{}(l%uOdb05gOsa>KBE78?qG4FVn zD8prRb!g^+;^SkNC{VT^$zl0=q>SGSSsxF|WN_lwmTuIyCb@ z@v#xA23B6-ijSS$q6|l&YT^DsGY=GgkGn+~zN4$d;y%$HQ3k~xv~a;>g2u_2}xbn0K~El;I}2IyCb@@%z0;l!3n&%^z6I)9n>yutZmf z#k}xdQHFeUby&>n?G=R|!imnced6)Y{8SbO2Lo*K) zzkmBg8ASUbeun!4i+RTVq73%v>ads>+b_ycimnced6WA^8Rny_Lo*LlKJSI9Arv2H z`$ZXULe;|kfo2{k{Julg5HgQ@f+&Od1hjC$(jGU3s)3mYZjYnc3+k7dp{c`F4mnK_ zW$=Tl#pjQl38D-Q=<2Y9^SlY746D%9p}7wf&L<{_GTcB{hsC^a6GR!9CK3)81*jU7 zaKRFOibSeYB2t|)k?K^4RHsU$IyEBIsbf=z79XH;NCQnBuJmFsQIx?3surGJu!M6I zR1HdepxFyb2hnKiaM_zZQIw$^supf9nt339Oogg}*^A4(xf4YhRzlU{Gw(Q54I%T+ zPZVXi4ONTJyq{1tgv?`{B+9@$iEz5thN^*?2QCk=#D@-%>U4=zr$?kZeInHv5UI|P zNOeX;sxu~1oe7cZOo>!yMx;7(BGp+Cp$?QTEs0cTLxeg|ztNsZbq++Tb0kuo6Orni ziB#u8q&in3)wvO&4%9z)Cqf-4oV~HB!&3hFpsB-E{)J2uWk`Uk1*HqHIyCb@<#XL6 zQHDOKI*>cS>af()GbV{LEP<-SWge(}J_J<*E3a^wcWRO-!!@W{eCBbON)#5S_k>PQHC|>>d?#srN2{9HH7?oX@)4nJ*Zk- z{so2KAE+8a=5fpvWe}N(7A{!aX9QJ4$ULi=q6}_OwfNkZ0#!rEyn>mc3^h=-_{^IH zRYSmrfrE)l8jGLh=85UK7ek?O7ysqQ+F>TVFJ z?j{lHK=E{oNOgCKRQG^Lb&rWu_l!t&FNsw5h6r__cKUlF)qNmB9VnbX5}^)c?-wG~ zeZ{5@OZoB*O&zZO!oQiK3|zC&$}6<|3+iuaK-Hk+Uo?9`<+u)-I$ZYJ%o1hrfT{(B z6IdOZd7yGU4XOraFIXLxdLeI?C_@!gEk5&RK-Ca3Z^0~4hBZ*N_{=*6RYS!u$h=vzMH!Ys)#5Yn2viLr^Ull`Ww-%Vi_1Jvdj1AgL&!X~Iid_gbMVIp$UFn6 z8band%n@bqfvUyjK2W>d?{)$h=c?L>V5TtHWX*!(34Y zp1Fkmt2I}Y!3td+7V{$JiZZ02t3z`is61$bsv#79U2{blra{%>3O|r}TcBzPnYU-I zD8mV;T72d`gQ_89-n+S?48Ndi@tG$v4-&?N%u|>r%Af;Pi_bhSs2W1%1h=Z!d zWge(qTmw}@$h?kuq6|}@YH^tdijPfDHH6IDHBXe`7*s7j^PWJ}5Hj!0JW+-pP__8X z6Pr(5e8|lgWzd4E#bq97T*U*bhLHPW=8H09K-J2DcS4J`by?Nb7kyPMF|;c7=5S|G}B4yqRJUo8H315&e)aJa}V6lKstSBJ$skA1v{01c9=bX#=KWbH%D}UTuz$4{i87d> zt3!)7J_ZJc5U3h_@dipSNoeYD#ar1TQHCa{T3qo4O3w=xi8Aa!SBE8Bt}PN}c!aJF zEnGnEV^}Q8AhMWnxEL)KWw1k6hh`oq-eMMuGUTAE!{WZK#i9(;(A8lvZ_8p)h9l_e zu$cE~u_(hkbaiO)2FhTt!I&Js}u3#eLL@dhf_!j_0K6rrob;*SYS zL>cCwt3wMHka@e7h%#J5SBJ&CFCaBbA%2Ft4@GUf24OGez0#m`pR`nzp$e)N)ZfHqZ_83qh6zx0aC_0h4`klDrJ@YS(A8lH zzb8vY89tz^Lo*MQkNB2}GAJ!W3ui3mIV=-p@IhCH#k{O#q6~HD>adtMXPGF&3UqZ? z%saMBl;IY-IxOb>SSHHAvK-AHXy$?Pxzch`1|xKJSj-DpF3J#tt`3WNRm(*gy3o~O zF>l3kQHCw(>adu1X}KuFBXo6G%wt#~%D}UNuz$5yh%#8AtHWYm#0pV{6m)fH`3=-g zY=WvmqGZ@3QHDF{>ah6Z*9uVvwv~j#MP;QZ zg9*AiEanBR6lI7*SBDlZpz@#wss<4*nB@T|{neqV!xb)lD@7S*LDj;;1E$Nj_*6)ethTYqcoDG^ko!=7HR|1*(RSd3#oiGMs>_#b@3# zs2W1%y<08H@C&LIpLr5%h>H(}HKGhUP__8X^Ma}&7UdtL$ zh6zx$_{>`eRYSTsC{a$nwBQHCaTby&*D1#3ka)}X6HD_=n6!Kt;P43E&&VKI+kohSp( zI{f|x)mvKYL>a8m)nPF&Vx1^M3c5Np_kql7S|`dd30)l)^ERv#W!QtR4$V9T1_p*( z>qHrTLDk`l50>?!3adu1 zV7(~A8FY1M=7HSzYP~4KKXi3i%#+z5%Am0UzkfmT;kH4PAq-s|7V}Crh%z*wt3z`i z$h>(QL>bnhtHWa6nGK>0H_+8#G4I<3Q3ke+X#T)rp2|j11_N|;XzdqJ`tXCQft3f? z>U~hW`EL|uNQbHg>BE)J3pa`~)I!xUGcYiK)uH(VRBp`PD9W%MT^$yGT-_+j@DN=c znt7mf@PDHygYYK8;bOQ+l))BV9h!Ndc#Ga7%8-q&4vYIbH;FP#MOTN#yv>_L84jbX z!(!gUO`;5M(bb`u2Z|5w&7us-n<4(i7azKtMHwui>TtyesN4wOEXq)ht`3X)rfwEx zSct9;&3&No+rL?q;XJxJEatu4EXwc`T^*WvAoq!H5oIvnLfF5KTSOUrq3YoNMN9Xf z@xN@S8brFs?7xA|-KyLo$}ktI7GF3o-y+Je8LAGKy`XTp2vvh(FPi&6_B_N;huMz; zh0CKYq6|!135N^MR#65Cs5-d&(83v%uFSTIGWemZ!xC>fTSXZv(AA;&12k_x4XOsk zzi9S?%7a;3MH#k1)q?yFE;q2)dtj?5!x^YLT>b!=_iC#s1KT!8xWLt6F;8WiD1!mI zIyCoz;>~ZHC_@~&IxObZY!hYZKv#!m9>{&mwuv$vLRW{yya(Gv8Q!3)Lo*NLKCbPe z3{u+(``2Q-D1!^SI<$BL_2ZMEYVgGysQ;0IrVdxU6>Jw}sDY}5#|N4}K<3SYs)5;y z%e-aVMHx0h)#5Yn0#pql^X_aHWq1Kqi_1Jvys_;NWl-7y31@uiRA+}Mg9TI_F7rU~ z76w&A$bD%$L>Y>pYH^td3cm?ZHH6HYvqO|&1yn6A^FZ!92314IylXo|86H8^;xZ3Z zPc!TkWf0j32|u_xEa~28rznFRx;nJ<0xGX!c8W5TpsT}T-lUzP4D-;{p_vDA-=3YK z3>VPVVKMLBPEm$m=<3kS1BIW&E>Q-JU4;GXwo8;D2wfc(^9pu}GBlv8!{WYqyF?jQ zp{qkP59HqyyF?l8psT}T-mhJv3~am6{DEd3DE+BG)euTA2D?QWY@ll4=>V-90_CqL zs2Z4g*xIL{af@g)b-2onHu|_~H!|F1C9`83Lhd zLFo)vyhZO7Wk`pr!xwL@dqo+RqN~H=kHdRK8P21tLkky>``+#qWnkQg=3gx4$?p?o z&_-8>W*#WsJokw*M53$1VqW<^QHEx8by&<>xKET}Bf2^)=AGXs%5WQ99a_AB+KE4* zY7p^;*&YYw^S@~7aK#(zeo+R&{b=Eb<`0m0`cO46dvTd(zF(BV8LAdnIslaiiBL6! z%*)*`%1{Yai_1Ka`=&$H5HfH6eo=Ttzx=mAlNM5tPj z`CxTu_JZ734^@NWJ~Vql`K9oRf9`D4NXQHD8CwQzgU%mc;St^=YB=g`$*G4IU* zQHCGr>d?#sxlin%D1+KTNZi5g#bTbzK~aVPbaiOvf!vpOP?VtwT^$zl79131Sc9$( zOE{l`sv#84r_t2m3g_DgMH!w$)x!OY#UHGPL>VLx5e^sgL!u1M=<3kS1BG+qAyI}x zbahz5rT>s9!)$bQXy$>+mz_{GuyDcU-=l{_87@NA;`8tOL!u1KhY9;v@vta^F1k82 z_kq%Z_hC_ncyx7G{9Aiil%W${9TxMJ9~NcUjjj%hc{dM>GCW0Bhh`oqKA4Y)G6)|b z>|etpq71g^>d?#sl`qjyHH6}$_=qS&Jyb2O_yCosbD?SonYZ+aD8ojmT3qIV{Cgg% zhLCypkBBn7hN{J79;kiFc~q1^^C%?zK;^k;ljl))aV4wreL^b&hil%Wt^9hUg* zKPt*F8(ke*e1QDB^Qb7pNpy8s%zJ)Rl;JbFIyCb@@gaB&;#Wf9CwWYiK^3YNmw!R- zbB3xRWM1$wQHFS^T72f!Le&s5ulJZJ!%V1JT;_q|V>?t0A@fcj6J@v#Rg2HOuTV9F z%;P*R${>0i64toP1BIV4R1G2XoR5n#_(Rp=G7se6+~cAQjp*vIq?h@}MHyD3t3yjK zAoETh7iGAMt`3WNzmJPDu%968U)2+$45sMnu$UKoLX;sMT^*YHK;=~}R1KlJR!>P6{-%Gc_8-*pM-=7A@^yY6lE}ns>NqsC{zt0 z^Kws$GE_p<;xZ2uztf>=2${F~q$tC7s9Id+f&6Lc{Di8-Wge(~AbyIt z@KZh|%AgNbi_1Jv{Q8~}Wypo9!xz8hr$iZ=q3Up%2MWK1r$iaHqN~G_o-dygWw?*7 z4lO-{%7eeBL>YKbL;MW47mIn?r$rge(bb`u2a1o-)1nNyP<43yds>vC8LAF$FPeFv z@#2N2MH%*@tHa{H+oweto};TnGY=GgtY<_SB+n4`ulX5K24{42Xy$>!FY$~hLm|34 zEbi++Bg!xvT^$zlcAgPsIEk(fi+Rt_h%$UeSBGXEX#P#`tSE!&S%`n}#fSY_Q3h|Q zI(U4b^(#Q*44F_hu>6K=zeX;aI$Z6X+Owhzolv!Kd(r#>3g_ieH86W|nYaC{D8pf> zTDW;={s7I-JcO!&nFsDCW3l%!nmSzeem*P8@E@ucZZDd7p!6quPL#p$93(#A>afJG z|2a{HXmoXG=7I8Cadu{eqNM8_&l0F zu$X6fUX;NRT^$zl;?IjRWTUG?GY?cAbeKD-bisei)Y7prPa~uv-FL+-NWyprA#g{KjFNiWULe;_33!1$k z{qvz}5cXoucZ1T)f(xPyJD_So`f#P^Ll;CD&Oz0|?L`YeQ2Fxaf+&N)MYM3i5`KCY zMH#Hn)uEXOijRnkq6``6>advCc2SgJ61qAx^FZ#~a8Z=u3c5Nh=6$*-%J2_e9hPvG zxdibmzHkQhdt}kn;RPJWzUOy$bOIq4dXl zRg^&zsuo}TnnTqPGSB;}C_^|@Ek5%~p=t=3*LhWxVJcKDF7rU;^X99f440wm@a2!Y zS4A0KLe=3i4-~)b*F+hVuR+2PpLx31L>Vlh>TsC{O3&d?HH7?|eod627^)VZc@v>( z2${F=nkd6ss9Id+f&6>=nkd6_s5*T9{di53;V)DjF7rU;f%J7z2L0=h@Pn(vlAe99 zi!wx_t3ylAAoI$vi!yYhtHWa6%Il>hY~_nForG%hyF29;2(nVjklSQ3l={X#T)r zp7sq<25WS6Sj>yOAKDOQzWjKhg4lVpZ>E-@SQHJ;E>adu{drOo-@)myoYA`S`nBNj*h=;1f z=ikg*q70=_b#VWpwR1rIqsdS;u=v2%&jZEVR5W$C+9h*ui88E&s)gH&7A~N0J`Pm_ zvlo|n_iu?ZyoRd9WgaM;Id6+HNZy8oAHHx=zAeh24^@ZHJYT39Lhg&bEy|DyRg23! zkbm26i!#iIs>A2LmA6G1wnEk6Gwae6M&O4$EqIV$Sg3CNmJ~zH2%HWQ! z4vTrIcSIQq(bb{FFDO3x?}#$YM^}f%yuEis8BU_B!(!g^JE9EV(bZuwPxP)RgW_Gn z{HLe=35zxKPL46~u?@R_&tt|-Gss5)Hcf%-G&?}{>fhN{D7 z-ru{T44n7y`xjJBs^1f3aE7YGXP)mpQHDsUI$Z7p^;62BY6#_z=6j+H{ZO^I%mbA# zYoTfgnYZE}7iEZt zs>Nje1CmezQHGW1>adu1{DCOLb#!%D%=`L4 zl!5Ufnm^FY1Nm3}p(ukfR2{zbV*5~(!4s+u9xiC+f%+Bc4@DW;(bZvb-_nPo3>(qa zp_vDY-}4Ve8J?r7!(txmBT)vyNBI2ads>`$&`_6I~se`#|QkKN4k_ zj;;=id0QWeG8{x#hoxO|AF2kHKd`k+K=JnQktoAgs9JpS_WzM61NUS6;R1>`&Bvk) zp6KeZ_#^$XC_^#2I<#;B#oNTkq6{n1)nPI3_+wFq%joLR%man<$H$@!j8D+~fyF%e zC!!46=<2YT=lMjGArf637W2xVh%z*ztHTm+3!!T8#T%%+UyP;>SNm`E6H$ikP_?-7 z8z`KwLe;?R#bw^xC!!2Lp=xoN2P*HypNcXVKZS%dzI0{#RFuIJst%ud=}$!&nxX3O znb-SNlwl@R9WL`g>3%y@4I%%Yek#gv9jX?Wd7%2}D^v|3^BA9rGVnfwgbOb7K=G>$ zRYS-;<7c7__E5FB%mc+o>@!h@N~k(~;n)03l%XH04wreL^s*MJhLHP?J`-iQ2vv*A zJdpd|KNDr(ehvv2eC`u{F3O+?Rfo$wko)YPi!y|xtHY8nOP`A}G@`3R%jclTMOTMr9wR*a7bfc@oV&2M^q6|mT)nPI3@k>#L_vq@-%md|L-dDu=SM-%A zgCbNdF8_kU&mO9VQ22Sj5@iU7s>Nji2MwB4~ zsuq`dAosOF)etgo+8a@ZMNqZ4%md}O18+nbZlJ5fQoek9Bg(+^77{kN%mcYk;jJix z0lGRY=J~x9Wr#vohnD_8>7@dyhLC?7-ik8xK-J=MA1FPqdMnDX4_zG=_uY9b%J2eR z9h&<<{$+b7${_X*EnKjeXYx*z!2w+znt34q#zEB(@^9KZQHCO@T73SU@J^Ir3A#Eg z?mP5Ol;Iq@IyCoz{QKseD8nCgby&=kdN0bL_8u)<(98q**9EGEkbiyNi!ww&)#CGS z*?UojE_8KR+_&PrD8m+Xb!hGb`S;R$QHDq8>adu{@IjP;=L2E?YC+Wy@~_bcQ3gAx zT73SE`5?+rfUXXU`}#hJGR#6(hsD1;K8P}$Kv##wyk{Ro89t$_Lo*K)zXBg2VM55i z5+6kwRG?~c`4?oK6I2Z$^SnNaGK4|Z;xZ3Z9+W`U5HhdhqbS1^s9JpHZTcw6a131? zmhgM>QIz2Wx;nJ*1EoK{PofNRpCDlfw-<|fHlIWpJkZslnFsQ38dMD-|5kkxWoUz{ zh1-k8yd|GR8Frwn!{WYcpF|lRp{qkP59D8l&!P+hp9%X{@3Sa_6}mbs=0!l&5b|%v zXHkX{s9Ie91-Wk$R1G2X7JU|FSO-;$&%85GHH6H&^I4SP1yn6Q^Vq&X!j6!6VqZiV zl%Q(yndbmiL&&^ z;TyU-Ea^q$t0;rYS4bGb?ZslA(^pXjKXi3y=7Gwq9H<&X{w?_`%FqB+3%3`|JW&3f z_f?c(6S_Jq?z`|+l;IA#IyCb@>G{`JQ3kGWg#D}WO_ad|T^*WvApZtI)e!P;)HhLv zG^kp9{%!dt$}j_69Txv?`zFe82wfeT`#}DE@J*EA1G+ja=J9U6f%5R4qR9u0hoh zGVj@UQHD=YwfM{v_yGxHLgp#_5M|JTs>NrX7gP-)^P+x;GNeJ(;xn%Ws)mqxQ+|jt zEP$%TXWl-j8baos`ytA33#t~Mc|V|P2${$6Q)Wge(LlKLge zVDt+uoUxQI0l!2UV$jv0l`o+DTlGtnp$lCd7V}p85@pzet`5ySQ2BBRs)mq%Z~YQw zcm`FA%fF!dh~>8^gT!yb{x$n8%HV{q4$XZa|0etvWhg*bhsD2rzeO2lp{qkP59Hq+ zP&I`7d*HVy!x^YreExm)Ta@7+x;iZGllddcpz#MST+rMH@~_(;QHC&dby&aE2tHa{HPya+2{-LWwa~~)@%lsE* z(D{!RE?CU-`Y*~5hOQ3HJkb0?2~-Us|2F&=W$1yb#pPenI)GLGMHx<^tHa{H7ym^W zzM!i^b05gRLJVRIN(^FP=fUm8Vx9wo7=sVGIyCb@{>_4_0UHQAKNgF5Wej2rO;ELP zd(q4T`F8<>7{dW{by&>1#~{Y=3SAwVc_9CCFp4oqFcS8!8KW436S_Jy^FaPhfT|(n z-yB9Uh6<=!eEyxrD8{f2T^$zxUSSktcz~`B&3z#M{$mtl;A0}}UmYef1`Bj`Sj-E9 zsv+dxG$t{IBB)wi{sooy6PUypmY}P{;@?9|Vhrcd)nRep8zwP^Kj`YPm?y<7#-PTG z7A{!KbAhTM!4}~nYWKcjNuei zEiUsw?cEn3HLPgif+hUqSj8B$(A8my4-Zx`h6r?ZSj;P96=P^ZSBGXEsQtG9s)mq% zSFnmPY=Nr9KIjDdv<%^zsyfyyf-E-?l(bahzF3*i!DNI+MI zW**3WbzEW$edy}2n74*Yj9~}5IxOa0;}TBWs( zj3EfB7M>2!+y^qRfLn~A16>^!_buZVW7veQ4vTpgxWyP=psT}T9vhDsgAfmX|AOMf zfJcnM0bLyy^Wu2K7_!jSp}7x~UOITh80Mg>!(!ep9x;Yv=<3kS1G(=Bj~K%jbahzF z6XF$PkmDunUmIRA1|M{FSj@}f6=NtvSBK_4(7wAVP&I_o%N|}ah7(Y=xY7$~z0WgV zF$N|+G=E@mp8}s4gATenH1~nb^Wqa@NJCeL#k>|iF@_1~>d?#s#m726F@}BU>adu1 zhfj>*1-d#k^FaB7jbDsGj-Rl9ZTQ6)JkZslnFngWq(RjXijO>gF@`FrT3qn~O3yR+ z#TeG0tHa{oQ~Y8K*U;6WxepY6U--osSOf_BS4lvOK@VLWnt34q`asnX@^6TM7()V7 zEk6I&35YRFLRW{yzZ(R^81|s6LvtUPfnFsQ(5mXH! z|5^!(F}Ok1;`48cpcq31x;iZGn&3&Nwh!PfK$U|3$#lJnmVhl6T)uEXOijQr= zVhqR7)nPI3iLe;M2Xu8<%;OUgV~`Ue>|YxZF$NEGby&gLRW{yeGH;v3<9Er{i`P`#$bi64$VB!_+JE64I%%gh>9^3 zK-J>%FQ`866BT3FhOQ2af3Jv&F+4z5hvq&|dif_R#=s{=*uOerVhk4O>d?#s`8N!z zhLC@g#Kai#plb2?w?|BjVGFuCEdISDCdP0NT^*YHK>qzBCdR-cPT0R%;$jSD=<3kS z1Nk=us)mq%6U4n)^Wh{UIU7z#&Q4 zziN_V3`Xec(98q*Hvp=Jkbh$&#TYW6YVrBEO;U_u9lAO!{yifp#&82&9h&<<{{1E? z#=s^;*uN@LVhjf8>d?#s`PUDshLC@wq{JA~plb2?w?#^fVGX)EEdD(uCB|?KT^*YH zK>qzACC0!aP1wIm(qasH=<3kS1Nqkns)mq%Bc#O`QlM&a`4`lmX@aUDWZpb!F@{x8 zwYba!&2OEM7GrpUt`1B1vB`)r2+5#@3z~mH;b$Nt#^8Xi4vTqlGGYu_=<3kS1NpZD zs)mq%m&k}QY=El8=ihTOVhr!l)nRcTkE|Gjge+nIn#qbWxS^}VVqS`@7()TNIyC=+ z+8cdPHH7@TOje9x6I3lO|AN|o7i7g4o}jD4;yxxhF$O+4!v58f6JxMJSBJ&CC^<2P zG<0=X{M!OmL&(2#&?=d+shFj?Bu(afk^=^ zT(J08K|zc`2VEVSe?jGemx35W6uLSr=2a+&F|?qo!(!eds2W24-J~GKun(#hmw!R| z?G988A@ja~)G4Bc3!3{t=E*_T5Hin9QH;R}suq{~K>0U8QH-GkT^*M2o1`elFb`cF zn)^WUu}4vi;S9PuEatsZ6l3^?t`5ySQ28vP1PK#D{xwh%W3Yj$#pPd6`ioK$W2iz` zhsAv}l*Aa8psPc3AIQIll*AY=p{v7U-UlTyhCk@)u$U*MOq_qUl*Jg#plWgX7gQdE zK-CZmzXWA5h8(C`T;_q|qf1$gVHs2%Xx}TY{iYj~#TfQL)!{M^)E~U1EXKg30`W6k z9hUG@P!VI$L05+semV>c3|=Z?3}xu*u$VVRMT}tqx;ixTK=HdzMU3Gbx;iZ8y-^Wk z_<^nti+N(IVhn1kg#GKHD#j3it`5yS7X}7~JXJAdxbK3h7{eWOb!g^+{QFB) zjDbsyu=_OB#28G_)uEXO%5OnxVhlx4b@P_jNuZxIxOaWP#0tPgRTzEJW&3X(hy@X(}4IFUwk-dh%xv;)!~W{ka<}eVhnBQ z>ae(PiG~=%26T03?gRPvoQ4>~J#=+g%=@Du#=xNo@dqyVfyzHMs2W1$pOK~*gB?^Y zF7rU`p%|zdLguAtiZK*G)#5S_lwSHY#TeG1tHa{oGn!%yH_+8#3BPZeVhn6rXyJmz zJQXc51_N|;Sj_W-sv+dxI4v=TET~#s{spyPI-qI@nYTbojA0E_Ek5&3LDdj4?~#@m z!#k*2T;_qw7anaf1_f=x;b*5U#^8mn4omoDXp1papsT|YAJeqO7#5+cLo*MQ-wr_4 z5c2OGZ83%yP_?-H3rc@%I${iJI)we}q9eu-fUXYBeW3iCrz6HthprBbf9L3kF|0sW zhsC^OP&I`7dre1-;Sp3VF8_kc0|s4)UkRDVqbtTB0ac63JWza?>54G~LDk_aPh)h& z7&4&haG3{c54GuvF|0#Zhb8>Z=!!AiKv#zrexUZyH(fCXB|V6r;r3!N&p}U&!3SL( znt7o3&C(NNs6tnV#k?7MVhl^r)nPI3ke(RBC3JOI%=@4x#_$JS9h!Nd_K=jm7(<*s zVgJ_Xi!pSdt3xvnMSBGXEDE#gjh%vlFSBJ$s z9z!t(2}At;1?M-Y8bbaJG8AKogQ~^lUr_m1V<^Tj2VET&_w6ziV>pJc4vT-E7>Y4` zL05;xJRu`7200`A{som+Hc&N${Oe&P#t;Hki_5>D@GCMBW9Wmb!&hF-FcM=}0#%30 zJO>5_hC@bT3{1vo{=nj21!FM=9dvbA!q3ZCj3EkL9TxK{jKvsQ(AA-t2lDSCs2W1y zx6fFN;S^LYF8_kk%L}L)LgsOph%tzmpoI&X`#|UA8bQ?%GB3Nj zhn7D;{yk(S#&8K;9TxLGn29m`L05-n9>{%C=3)#+<`DnF{ei{20CO>h7<6@L=7H+V zDswT0K6G_h%v)nF#;^lj9h!L{|6Vf}V|a$H4vTp#7Gewn7HIxJGY^#C^q^`8r57^` zF$O27T6nminFsQ30#pql^GYnl7#g5zahV5l-#n-qLgp>A5M$T`Rg23!kozuJh%r1t zSBEA1m@LH@_$={e5fA6fs7?`Zl!Uc-EiUsw>G>E` z4I%R`*@!XRgQ~@49w`0&fvO>7o`|g&gMuwuIHQFh$iH?_HH6IbvlU~Af~v*kK2UqN z!d8r72D&;d>1CU(7{eiSb!hGb<&OupVhmr<)nPGD$WDww&W^BuZS2Gte9+ZlF)zzb zjG+u&9h&<<;Wq`UhEVt|u@ht109A`C{6PLaXD7z+2wfc(_c7RuG4R+E_OF(`7=snM zIxOZz*o!fwpsPdkFUWmO_F@dv(A8lvZ;QPc!vS=4Xy$>!@1DIF!#i|!Sj^*b5Mz*V z!0%sB|IN%nj3EfB4qy8;#zBlB1F8;Je1P&>n}Zm`Jalzf+_%R;jNt^jIxPNu<{-xK z4P6};^F$oQ7!(`{``6AgT;_qsV{d@eIHUOki+|;u#Tc~E)nN%g4`(rk2y}H=%qw#iV`xHGhh`qA|F*zc zjNu4W9X|h_a~5N`1yzU3zaaB|IEyjJxDfWQm5UgI8@f6)_kqfT6c;gu5_EM~{5#1- zjA0(SIyCb@>17X84WaaM!9|SW4pc2J|AN{Zzo2RenaAWR#=z%_7A|P+1DU4-RYS-; z6IU??2dG+H?gOnV01%#!v=Ti_g3%P&I_iTi`0jum-9YmwBN0IOQtFa1UJ_ zmiYbSD#pO!hClp3<+GZb7=s%{-9%-nol0{6klV#XK1gF$N6}{Qd>y4>zb9Lg~-XLyRE`suoxJ z1DRI=RYS5*)P_?+s1I5Q3s2W1%eee)t_ybjo%REqd zAms@OV?ySsd5SR@LDk|i59Gc8PceoRbahzbx5-nCp$}ahT6zK1ziT|j7|x)p!(!em zPcepX=<3kS1EoI^FEIucFNmMv{=j0Mlb0BSAG$g;^FaR1fvO=Cehpq?3_Vb_aC_0r z1C>{+yu=s|p{v844{0}<|D>130)nU`#|Bh!AFeY1iCsb<~{QfWB7!w4$VAJ z_zC!mF(~*#{0#R87W3?U#TdNM)nPF&!&i)<0$m*z^QQTVF)Tt?hsC@DP&Kg7!nMBh zg0C3E9jIEkKhVqrh2JkNf z7(*DkIxOau1c)&-psPbO59Hr@P&I`7yDC78VH;E}KL1_`5M%g)t`3X)gaXAFb^sA7_HZ7#^Uj!{WYw zL1GMC!G!&*5iG`FfvygVd11j~3`ywf(EJNZ&kayDg#6nREXFVcsuq`jLE~%Ng2fmv zp{v8g@`fSLsy5zygwmg3>=|o{y;Mi6d!6(HH6~BC{&EW4yqPce5f!mFvNt4 zF*Km7!{WYqp<)cH(AA;24-|eULd6(vpsT}T-nURO2Bt9l{sqOa0#pql|C)q}F*rcg z;_@#j|Hg%hF_fXJ!{WXvVPXsm(A8n_@4hfGhI8oZu$cEIOpM_Nx;ixTK<*O@7h}*1 zM++A$=J|w+F+`xNLo*K)A7$ZU3~lJ@u$Z?bT#R7@x;ixTK<+ySRYNE~o`s7se1fXQ z7asxQy~IG(5OQBZgcw5&R4qR9Wv^6k~9Ls>2t4K9OP!5m0rw%mamAS)>?47rHtu z>19Qv7{eBHb!h1Yl>RP7iZMJwSBJ$shA1%xo+$kO1&zOGLDdinKcgry20N%)T>b@_ z7Xwv8$h?#&F@^%DT3qIV%I7|)8bapHiV|a3233pCydzLGgv`4TCB|?EsurJlzo2Re zna36_#vl}pKR!U|#UNUY!3C-gU;6Wl7GsEls>9_zQ2wZh7Gs!!t`19j*%mFva0p!; zTKWT(R}Z4a7=ECu!(yITj2MGb41WKD(z8R17()QMIxObp#fULfp{ql4A1M50#E3Dh zL05;xyi+k^4A;=rp_vDA-g_M zhBBx+eDOOaR*Yc_x;iZGyA&(Na1UJ_7We&$6=M*HBkW(jI57q*baiOvfx<5$PK+S~ zT^$zpwZ(}sOhQ+OW*#W~HpGcB96?uy#k@yxVhr!l)uEXOavx8;7=uhaVgFjii!r#N zt3xvnls{79#TaU!>hQ&HTf7*BX2$lC!62%x6K-J=l-+hT<4ELbw z@THd*iDC?2pz3g$2XdcKk{E+p5?Z)m@vlpg7()QMIyC=+!Y?mLjG+!)9TxNEB#AMs zKv#!m9;iM#mL$gT45|*Fe?KING5mq5!{uL)c~Z$@3|7ey|Kc;xC0UFi0ICj`d7$=7 z9#jpX_$^BoV`ze^g`0=vUg-r;H8ArSq_E#By%0?uu5sIy$zlv!p=$Bjdl{+*pS_^? zy@I9=m%UGt#TY(7)xzyXi(im=d?{iKaw&x4LnB3u!33%fmwBM}Ul3Fc%pbVi7ndT& zkOftX%REs2=zyvrWZs+(=74_zG=^VXz_G3-EBhb28Tt!|<5V$* z_fWOC!UdEsdD9?a2eTKKd7^1z42n>-xXc5k1N$^FhG3{VeC0-LnixYSR2?q!K;v-j zX<`hE(bZuI=Ywft3}?~RVTrfbX<`h&(bZuwPcmJMK{Xu`Hu&7@DN=cnt7n~@*k>(Q2cUdh%ty~;P)>meoZsP7(CI{ zVR2u2h8ROJx;ixXfy%Xs8Db0z(bZuwZ-0gu!)bJNXy$>^^Gm22LjL`jA;$0*suoxL zg8VC;DaN3mi54zc+~=Dq#t@0F4$XZa^U5>D7`oBbVKHxIrWnIkbaiOu2FTr)p=x0M z#a3>B!sQy8I$ZhuUZxnsE2vtK`CxTu_A)UrFmPl+!UUhaApdfqsRNsb#lLb{VhmbP zwQzgU!VlCB@W>KlNI+MI#k{&KF@`R5by&<>ktN1(09_px^X_GdF}y-ohh`qgzZ}_O z3^Lh}uz~vni+NVrVhnER>d?#s<&TtXF@_R!by&=slr6?E4_zG=^Y&zmF`PkHhsC^C z*7h~9mt`3WNck;y;-k__)Vjfq47=u^=e*c2Kx_AoJ3o zY6zK^S0Khv1yzg7JW%+}C=g><0ab^uy}PMEjA0*C9WL`g>F*9y4I%fvC=g@#0#%F8 zJfT8J7!xv2u276Y3#t~Ed7$v~fT|&6UO=H3Lkv_cF7rU)R|Qo=$h@vXF@|YSwYba! z<&Q0eVhktH)nQ40&kDsDKB22aOV6P4PoPMQL8Ax~hH!hanCDg`#t?+A4$VA}e+!Dl z7#h&kVKHxBkr=}&bahzFJ5eOYa06W(7W2Lpi7_x0qxl2PJW%*4K-Cb650hds1_!8G zc(`COFRoaOp$J_a7WYjk7Gs!$t`3WNyNbma&Y`QrV&0o#F@_)L>d?#s#fMml7=u~~ zTDV{_&!t3+Apl(+7W48-#2D(()nPGjPKg-93Uqa7=7GwCV^B4O;^R_@7{fiNT3qn~ zGVc#m4I%S5O2rsNO3}gv&3&NoGb$BhaDuACSN{2wiZMh$)!}nr8B`4+_cfJ@G4w&z z;xlgzR1G2Xc9e=S9D%CEWge*fdjwTO$h=pjVhrD)YH^td3O|uDNEj0`Po+$Z!2qfj zmwBN4>sKbmkc6%dOZsal6JzK>SBDnApz?23nHa+%bahzFdr&6E@CIESnt34qa+QlQ zNR>nU4EF~X^DN567+lcRVKFbMT#TUzT^$zlCX|aY%t2R&#k^fmHH6~hRJj<#HKNrX z4^#~y^Af7W7;>O$@tM~JRYSB#xK-J0s)mqx zN2)tJW#0-V=zEhhn9aq<%?g97(*PoIxObZ)QB;3 zpsPbO59GdOHDV0=pz83oH%`=uF_bpMd4$Xa__iTIlM~%mc-TN4*$B1iCsb?klSoV`xHGhh`oq{1!mf5DLE)^`i)jbaRQplWfs4^&?5Y7}F*gsu*Y`#v;^G5kSShvq(z zf2Eql7_^$u!Uc#GtFgVqO(g4Wan$Y8GRd233p8zo7iK z1*(RSd3&107*0Ud;xZ4EKb|#EwWix|T?s9JpfJ<}q_@Bm#M7We&Y5o6$LCG1~~Rxt((bahzF3u_f) zNJ3YK=3h{G&;V6K$iF?UVhl5&YH|4&l;5_siZNV5SBJ%YA6ms2{-CQva~~*wNVSPE zXtkk*3l{S{+Qb+_(AA-t2l8(bR1G2j*0qT-bV1eP@-L|TThS)QZ~$E$7Wdt26JvOV zt`5z8Apdf-i!n&F6ZWrJyBLELx;ixTK<-Ou7h|Y^s>4_QHMNT|^g-3(@-HZU*R+c< zoIqEH#lO$m#TY)Jt3z`i$iD&|Vhjo$g#ByRA;#c^t`5ySQ2b^<)ewr`k`6J32B=zG z{sol>^Pp-7nYXM%jA0X0Ek5%uK-Ca3??#6h!xN}lT;_qwQ>IQa2C+`U;b+n*#^8Xi z4lVpZ@e$W4#*l}u4om#@bc!*|Kv#!m9w_{_LDdlQ@4ikkhEq_r`272#Q;gvcx;iZG zlj;&Q{Oi&s#t?$84vTq3U1AJ%=<2YTHwUVQkbl>7i81Ves>S7Bko&Gd z)eti8S(g~YC#YIn=7G|SK(`o!N;g`#VDYb0w-|#Tx;iZW&FL0ns6khU#k^VFVhqdB z)nPI32viLr|6b`9V|V~ni_5>D@cRcla~~-DEPBKke9+Zl@o!d- z7(*GlIyCb@?wish#;^ok9TxKr^@uT?Lsy5zyf;uag#7!bM~s1^7r%c&?o)%RA!MFe zuNZ?9R4u;vNPwy#WL`n97()$IEk5&RLDdj4Z&j}t!#1c|eCAz&sv%_FlU^}~4^XwZ z%mbwtzCK786EaV!PmDnisuq`dp!o2Csv%@vOrID-22?FR^V*j@dRYShLCwe{bCGq{gAN6XPymI4I%Ra`o$Pxplb1%R|Qo= z$h^LOF@{-CwYba!)t5V5X-*uRs-7}O>c;or$(3^q`8xXc5&FKV(FLmgBd zKKFG@7GszKRfo^KO_RkKu0hq|Gw;DhHvQV(98q1Uqq&fF{n&K3l}WrIZYE|@IzOJ#k`zpVhlCt>adtM zYnm9tGIVul=7IcsWSSVm4X8SN`R&m(F@|?gb-3aKR3Gt77h^D)PT0Re)5RF#(AA;2 z4-|ei)5REi(A8n_@2cry4BODvp_vEr@0ICd3@@PS@cH-CbTNj1P<8nHD>Flk!DR+v z|0c~4W5`2Shvq&|`1Q;XW7vYO4vT*;%@AX_hprCIJW%-knIXo&Gn26Uv}TGin4zmf zGY>Sr7BW+eAqT1sU-*^H6k}+Ds>2n2CJYP=3ucNjTtQcd#lN3siZT2{SBK_4kbh-n zi81KRBJ5wUSz-)f=<2YTR{~W-sJv>KCB`rTsuq`jLFsSZEHQ>d=<2Ze_rWYNhBxTy z(A)h%tPFs>NrX$XrMm6EaU_t{8&>R4p#^K>qcE zsv%@v*jzD&B&b?^<~2ao5Hhc0t{B4SSR1G2X4$T!~I0sdW&%8HKHH6Ij zGgpj(V;&@|@tLOvRYS-;vw30+PEfV@%u9f(A!J^`JTZnEs9JpH&4Q{SWZtTIVhr1$ zYVnzO1*(RSc~9nvF?@ik#b+Mhd`K7*GEZ*47=sp6Ek5%+plS%27cpOqAqA=ypLtDC zHH6HYG+&Hi9#kzZ^FaA+4^#~y^N!3HW4HiSi_1JvfAHOWF$TT`kTAqopGzzdV^D#r z!(|?*K5~MpA>_WW1!4?IP__8XYk;aDWZskoVhjtQYH^tdDi8KS)eti8+yXI%TTr$5 z%=-aVL&!Xig<=dM3nAf*%REs08bQ?%GS6zE7=s&BEiUsw?X{GJVhlAadviZIKuQ(_%D#pqU4nuT_AmAr!wl zi^Ui$plb2OM;KHMA@i~pi!qcz)#5X63RDdt^HwYtW7q;!i_1Jv{9b~pA!OdW#bOM< zplWfMXTZR~AhAS@!D9&|objdSpe14qaZq)*%mbz8nk8Zk6VTORiQjcg#29v=t3!)l zQ2M*EM2z7Dx;iZ8u`Ly25LycH2QK%4%4dV6VhjOLb@==nwN#8D4XO^Gc`ZxD7?wcQ z;WKaDQZa^IP<8msyRlS^;RjS5KJ%ECi81gkBkW(DWnv6IP<8ms3tJ|}kOWnS%YC5o zuVI-O!wjf8eC91$CdRN1st%udXO@XEJb|jiXWqMIVhq2a>TsC{N`Df|Az?x&f9Nb1 zW3Yg##bq8S{e?l*5Hc@qxfnwcR4p#^K;_j0s2W1%%~>wSumY+UmwBN0IJR7j;T}{S zzVLgoT#VrhR2?q!K;b8}LX1Ie1tbjN>agTDmla|R0qE+`@-L{o%3C4E(1orJi+L+n zh%szISBGXEC_XN&5My|Rt`3WN3@gPLcvhnM1B-cDE5#VB(A8lvFJh$_LkhY&H1j~^ zOVdg*hDGS=u$XsXr5M8*baiOvf#T!UN->6i=<2YTC$mb7L1Ptu|ANeOgQ_8v{-Rci zF{DA&!qWko`#|y00#!rEyeX^17#2X);xZ4EKlVY@5Hj!7Dlvv@P_?+s1G(=DR1G2X zSXPTM2&~2*exUrO2USDJJh#+(>G8xgyQ4JdNBr; z4fy>F>Q5^{)etgIYl9er8B{H<_yx5OLZE60nOCqujG+dq7MFP-_sxQ;A!Od74Pp%I zplWfM2P&V>K-Ca3@5u%+h7VA+xXc6PH@=OKFeYT4+(t15EvQ;t=7Gu=52zYK=EZCj zW5|H2#bq8Sy|h8q5HfGlMlptYP_?+s1BKrns2W1%UD+tc@Bpe7mw6!n{)4I^WS-C_ zF$TF!kg&#Qo()tDA@c$@i7~`L)#5S_52_ZIc_9Dp*(Anr0bL!I z^5ESjF@|60>d?vqQ2LYDEXJU*87*9}nCG@xj3EeJ9h!Nd^ilv-L&(21o5dJ9plWgX z7i8Wts2W1%ZQ3lxun(#hpLutnY6zM4VzU^-7pPiX=7Gu=p)HWGBV?Y`7BL1js9Id+ zf#TO?ix@)$x;iZJQMN^lp$T0bT6}=aTd+lpVGFuCEaqL>BF1nJT^*Wvp!D)*ix>mX zR*0YB{=j0M)>bhFGjw%m=7GX51geHm_{D4$W5|H2h1-i}9>~9KTg4b=p{v8=N;!(!f}tzrz{(A8lvPh^`IgTgkna6vN<6n=Kw#2Eb0)nPF&XPX#91-d#k^FZM@ z4XTDv_|4lU#;^*i7GLR1G2Xigt)G)IrtaG7r?gngdls$h;jp#2Ai1)#5S_Nj@tM~GRYS6v9W zap5PhTZ};lsurJlR!}vB+~>Aij3EfB7MFRT{96E3L&&_I-C_(gplWfM2TIS|plS%2 zcWk#9!zHL%T;_q|UV&1(yVhpd))nPG@V=u(7gu+i^uNZ?0R4qJQ(98q**J-a9 zLln9?Ebgn=E5^`*t`5ySQ2t%CSBzmBx;iZ8UD+$f@Bm#Mnt7n``?pt&fo~tg&v1WW zF;8co7=s16IyCb@?hD%|#*l@s4vTpm`@|TgpsPbO59Gd0`@|Ryp{v7U-h+K&3~$iY zp_vDY53c=U3`+Y6``2N=7=sVGIyCb@?#tRQ#!!W>4vYI{>=$ELg02qDJW%)@f~p~u z{;us8V|WBr3y(K6^FaP(I3UI#ae%OY%?^k$IH9XUGY{n7gacv>1?cLq__yzX7{e@d zb!g^++_&R^7{dj0by&=McR-Bc7rHt$^FZ#CI4H)TageZo-42Q|1fi=#GY?dM6hPGw zijSIuVhkNnwYcH~6u-+3iZSd$SBJ&FHx7z1JV957=01>lOozl6#10YmugM`X1_yL? zXy$>!FYb^SLms+1Ebi+$B*riUT^*WvpzzxURYNHJ4jmF>I0sdWEBrw1$v03ngv|Q_ zQg;|FT+rMHs(7Ucq58h8n0^T;_q?H|ww%!!~qv zSmNW#VKIgW=<3k?3yR-=hs7B9j-Z7L7V~tDh%s28t3xvn7-jpL^3=5!YahV4yU-m)O5Hj!D5iy2GP_?+s1G$gkC?xC%na6Qdj6npd z7MFRT{A+YnjKK|E9hUe=IV#3bfUXWLK0x!+eMiL@)}gDzV&0jfVhlIX)uEXON-y7_ zY6$t4?U)#Y&@o8Z;PbBmR1G2XT#kt`1VGi|G7psg@}O!6nOAj8jG+yx7MFRT_*eo} zL&&@x$HW+pK-Ja>Y;imyrLn!=APKq%&K-J<3KT!D+cT$X@4yq2{dZdn%VhmHD>TsC{N-vvE ziZPr*SBJ&FFHVXvd_h--=3h{GC3H%RLFp7)xL`5Q;glGI54t)u^FZ#)Iwi)?gsu*Y zc?(X7F|0vXhh`qgeWy-|G2B8|hsC@fr^FaoPNVq)%{);2DnZo{ieJ6cVhmPLwYcH~ z{%@plS%2H}A91m?ICOPb;-dzthLC?d&WJHgfvUyjUr_sR(-|>_bLi@@ zxbMvwF@_)L>d@Q=GEeNR7=zJSv~a;CofBiQLsy5zyqI%h3>oO^(98ps2W{uX z80Mj?!(!f^b7Bl9(AA-t2im{-?3@?_-+45DU@=eUycmN8x;ixTK;aj5UW_3PT^$zl zTF#3xOh8wMW**4D>&}ZY9D}OESKePZFUD{Ost#9r2Bnu@=fxQKE)e103t|iw=<3ki z2lnp;F@`jBb=drSL5yJnx;ixTK>1@GR1Km0d+LH1!!@W{T>b_17rsE%5Hj!21u+JW zi)i73<~~q-s6o{bGSBX!7=ss7Ek5^UK-Ca3ui&B>Lk(0dKJ#Wl)etgo(M2(abx^hV z%sT^BL&&@<7sVJJK-J_YH`H}sC>zSsv%@v(G@X~89P&I_in{Z8xVGdL+F7rVC-E~cj;S#z!EaCUzni#_$ zbaiOq2Xdd(buk95>yR*n+l$3KkLzL#A?WJR%mcZv=(-p~7rHtu=B>Cc#;^rl9h!L{ z_g%U!#_$MT9TxK#Ziq4P+(7dOnt7o7p#@b#C_e0Nh%tCU)xyIC%{)+g$$+XMWM0V) zF@^@HT3qIV+&2%ZhLCxiZiq4LgQ~@49;m#!164!FyeBur7(PJN;xZ59KE9h`3`#fA z!Wm0^INTIt@IhCH7Ji`k&AKVZP=&4zi+M9{iZLufSBGXE$iIi6Y6$uF)J-vlYf!cL z{QKpm7z4*G!v0meCB|Tct`5z8ApZv35@Sd}SBJ&Fb+^PAy3p03nForG6;L&V{JZCt z7{dvuT3r4G<=K??;aC@8banRxhKZ30jd_4d7$#;+&wXdXXxs%_?P9r7=yrlv~WRlA1M9l-4|o9Lsy5z zyqNo93>oO^(98pkN3`7+V_1Z)4vTpQ?u#*;L05-n9wpN~>|YJ2 z8ban-JP>1WfvUw7A0YQ7JrH9kLsy5zzf&HFF)Tn=hsD499*8kqLRW{yyblk=82+HE zLo*K)zfupy7_=Uug$ow*JRXWMgrKX#VqVchF@`2|by&<>@KB6l4Z1os^FZy5Q&2U8 z;^WdoF@}3kwYcH~lz;y~)etg|OL~cVEXI(At`03eK<2eP7Gs!!t`3WN+a8NC970!zW*(?KeehU};Rm`pEar(l z5o1t#g5STO{O0gPj3EGB9TxNQo`^A2p{ql4AIQ8JPsA8jpsT}T-mxcQ442T=p_vCN zpFcbiW8irT@dv*66?-bipafNiE4_f?!{MnILkPM$Ebc3MD#lQUt`5z8AoJ!t6=T?d zt`3WN=bnl&+(K7}W**4DKc0#)h&+S%7w!)%<{3Q`W3WS4hh`oqK4PAUG321D!(v|7 zGckr~=<2YTx8<1_!x40KSj>C$OpM_jx;iZ8@jMq}kap7Y4x4|Ui!r=ISBJ$so)=;a3NHxz*Y1TF zgBQ9wH1j~^U&aeDh8}cvSlqYjg&4y&baiOvfzr#B7h()g(A8lvkLjft1K&$De_%0B z=cO2f4Z1ok=0&{}V@N|+hsC^>mtqW4(A8lvZ_`ULhJEPj(98p+mpd=T7`{N&;Y%<7 zUWzery~6KbPGVj)FF@|SQwYba!mCr11#26IbK*9xIdeM3##$X0jhs!)r z`U`m@#*l-q4omoTy%A%WhOQ1R{6O)s<&7A_5p;D}%zN}kjNu)+IyCb@doRZD2&xvJc?=&QVNA$8fe&H~GElYn%(H^3A!MG{ z2Qh{)s9Id+f#SCWs)mqxH6O$nI-qLtnYRq8hLCydK8P{wf~v)39;kf20aZiDyeA*T z7(PJN;xZ3Z|MGnlV^I1Ci4(XwEcwIXqZoq^y1KIboRXv*m{S-)<$D%X4O~!4fI$z7 zx@>If(DEN1XY9LJ}mYwMpK8&-t`~F7Fhf^| z#k`QuVhjoB>d?{)7Xt%B9aIe>-V_-0(Ch`38y#rs!0~~_-U*+@80J9L!s89iUQj;T z1yzHvR{*QMyV2C)viI<3F^2O{wQzgU`~ixOw@@`OdvTff_p=xS=NJ6(0m{GXP&I_i zGyWpRU=LM`%YC5q7YkKG$h^WYVhpuVwYba!xo|x;iZBnf03(gXlL%7{cwvVxI9gF$Q~d zb!hpLm4SgF7ODp3Uj|V9hGs7)9VDTt!kd+i_1Ka`|d&25Hj!CH!+4!P_?+s1NHL+zC*&8ka;@a#TYE0YH^td%I9I<#Tc^C z)nSRZj_+a&Q_$6+g&!#0Z~89Aa0p!;7V{o_7h`yXt`5ySko&lPh%w0hfcP2i4=m={ z{19XCKv#!m9>{%ZKg1Zy(A8lvZ^{obh6U*A(9$Uz0|Uc8s2W&&Fi2sqw~nBx!dR_11SZb-3*P|3i#{`zKmBW3g8gss^9EpnRl-rVf|A zUO&Yc!k}t#`Im)(fuRJd24OE|ID^IoDt?MF%z>%}g%d7&m;DrD*aTGvw-+tFfYRTE zpJEJ8(A8l{FHFD082Emng&&%Ep!BEnON_w=T^$zlqJD`nq@k-riw{t^v_RG13l~s) zbo>%ySOHawFI+bL5@Xm0RR<3jG<*3N7#QwA)!?%iR9?M8Q->=)*nW#K2>nJ2KQwzm z{xyKA!Dlb1eltN+hs$1{-(n0AP_=OXqQx619hChRW9ULxhb7)t{1#)_g02qDJWx8g z^jnPK5xP1o<}v&cW8nEiI9#-#Y6yjk9-2B_;o|m3j3EfB7GJm&{1IblL05;xAB+Bo zF|0#Zhb3Ij{1IcggRTyXdB6UMF|hqb3l}u=K=G#XSB${|T^$zl!v2aeB%!NAGY=GR z4S&TLCZMasV&1yHVhp>`)uE+lP&&NPmIADsurGJ(ClSmU|@)Zs=;S3C|sh^)ZwzX_@5X zR)R1t^O_jM8Tz1V@tL;~h2;(wu3xhbr0jOGh=G}v;0htBDxXgRTAkOd!supe@ zmT(qeM2}z0@&FVrf@tb+*(=E?&Y%iai_czXs2Y$Tz!*pTx}d4UWv>sTI70+fEk1k8 zplZN&f(R`3g366@Gsn0?F@I+IG%ics*afV!| zT733)L)C!o1QA&51^IU>nmSzeu4ENw*a}q(w-+rwK;~U$6=!&kt`1B3V`UR(5M+ad zH7@f&?KOQiaRz&Iby&=cWfNz}L|2E!eeG=G471VIVKHwfn>fQ!baiOvf!z0)O`PE~ zx;iZ839^eb$g-pP7cISj@~<^i4JgXM7)QLhqp8CcZ_(`H4Czp{@NmXrZ!1&{*iI0E z#a@tqC!(ptW$!|EafY=}wQzgU!Vl!%(@-@aD?u2SdDq#+86HE`;xZ334#LPG&LGQy z7S34W!a>e<=T4=afaVeb-2O>RK7@ZiZd8;5)K!CPH~24baiOq0?Ox=oZ<|<=<2Y9%W6(> zhVAI;(98pc^Hol9hNtN2u$afpCCsOTj6npJa0c1y$tBJZ2~~?P zoRhi48S(qap_vCN56(l? zfWi)hafRP)ZgGa^P_^*zLrVvs_+;gQ_!VRp7=zj`X!e5QjSWp5E_?ZS#2KWZYT@>x znFsQR1yl{#P6h@BT;{p(h%*F0)#5S_6mNMv;tWmb>ac|K0v>UOHR$Tl+y{!cQ#|4f zx6svLG4BVDI0FkWBnTre2 zM_zG;zfiRxcYxKQrDxFmku)D9uOY$(bN&rfpIf7;1Dl5>{|57kGsHvH;<8tXfq|hG zss^9EpmaJPO&u-j?!7iZcYGtHWYmp`bWJExI}^=FNtxfrSe$|E?AkXV?x^i_gDT1;rU&qN~H=K6W8- z24Nw>{xuX5XK+MUhsC^jA#sLmbahz#+X+=e$iFj%#2J=C)#CCmC_WBD)eti8vXD5# zeW+So=7G}7U#J>F<_QXmGsp_#4?mE3)=)Ks%<~r(XNZQX#pk|Cs2W1%^$LqK%!I1N zXWn*UafZ|A>afJ`OJQ+_ujuNq#ILZ3ID@hXTDV{_&rw92!53W}7W1;9Y6$taT11?o z9jX?We?jSGDO3$1^R|kJGaQ7f#b@4qs2W1%y%!N@_zhKy&pb&{NZ1iFPg7K!!4#?% zpLxMhHH6Gd78Pg6hpNSAUN2M)A@k;niZiT)s>NsCai|(X=3N&RXLt-%i_bhpG2+5c zP)wXb7OEDPd7yryHB=2D_c@D+Gx$T*;xZ3Z9^{INGc=;B!;(Mdi-|L=MpuU=f1DH( zXSj*34vTr;#l#ty#UWvX%YC5yp$Jt&$iIf-;taM>wQ%#$+8dzuR5Vl#%sg;E56vH- zaE?J!hpQe;5f^7DfU3o3Zy!_*A$$AL)Zwysy0|#QVyIet_8x?)!Dlb1oqY&R9WHy% ziHkGbf~v)5?+>UNeD;FY_5BnVXONYEqytd=;Ida;LY%=Est#^1TD*bMbD)GcLn^vD zEa|UVLY$!=T^*Wvp!By^LY!eQx;iZ8-Ifq%c#f_PE&YM|p{$Y+zaqi~dwZ8tQk+2* zsuo|k=u3(-SVPs}3ztYqafV!Uby)n-Eh)}09bFw-xPZcWtE4!?Npy8s%zG{=&hQys z9h!Nda2AvjXHb-agbmyuSj@AR5@+y6SBJ&COet}ON_2Ht%$qJH&afC=9a_AB+;TFD~gC}E6(r@suou|0F?(Ka*!|~ zWS)kcID-jPEiUsw^<|KpI71m!9VkEJa$kd-I71Iq9X|6`$%!+ZgQ~-4-VHf%h9^*U zxXc6jmr0&D|BA_rGblmT;xo@dUYsEWT^*MAEs_^!s6$tW7Qdi!e2%<0!y0sTSj;;m zFV1icT^*Wvp#1SgUYvnP0pe$T;U}gb&Y%QUhs(bp_c=h-5K1or3gQegP__8XtAeT_ zWL}qoIKwokT3qIV(%%*ZafTaEb@=@INI{(89aJ4I^FZOpqX-E*Lhe&h6lXAis>Nj< zDE$1OY6zJZrzp;l1yzg7JWxH;0aZiDyeW#}3=5!YahV6oANv%=87`r#!;=0!D2g-u zL05;C{y_efQW9s-Qi6mb++Hl^c_@i9grKWKGY=FWMNl<_{M)4@&M*zC7H%(^d7yFI zElT1H7oh6!rI%Yu;tbEA>TsC{3O^QQaRv!x!u~Z=7H4omSBK_4Q1~S%i!&6UtHTn0 zeahktv(VL{nFsRk4rOtMD^PX#{CiJXoZ%Hz9X|hZsE9Mjs1WwAm5Mln8@f6)_ksMI zq9V>vg02pWeTvlNWFCjAID>*J#J~8= z(^3^@FoUYYWgf`CAy74h(sPQcI70zcEiUsw>90>!oM92VIxOLLKvkUK47xhB@B^9m zN>!ZUAG$g$=El@86K4oRSBJ&C5;bv#26T03?gN=OPfeU*9lAO! z=ABU!XSji`4vTr;plS%k2b;P$gOECY|AN|U22eGG%(GAzXK;b4#T9;_@;(WwhLCxA z>f#JlP_?+s1EuE~P&I_i+n_Gaum`FZmwBM{ato@4ka_Ra#TkA<)#5WxLIV=Ugv`^? z5NEJ}s>Nj<$iHD4;tY9Eb@=jcg@!mo3sfC0^FaPxq#@3*4P70U^mj!=oZ$hwI<)i$ zDxd#ph%-oOLi`N37mIlon&J#D=<3kS1Nk>eQ=Fj+T^$zlW@w5tEJ0U?W**4Dhcv|* zuA!^LV%`^!8Z9(`pqU5Czj9jQ40`D5u$bqgCC(6mt`5ySkbldx#2Na~)nPGjjg~mW z4s>;B=7HRIO-r2N8M- zx;ixfg3R;N5obt3SBJ&C1|4yR9&~kB%v+@+&ae+%9TxNM=!i4CKv#!m9w@!A>54Om z=@Ry@iLN+<1G+ja=EdoXGvuMG!{WXkU2%pP=<3kS1BKr{%t z`r-_mpz83sZ;!q>!wIN5T;_qo?-^7LA@_aL7iVBHfP@P!^FZlE0jh?Oc^U@d3?@*u z_{@A0MFjm4m7wWS*X(ID-{bEiU(g;x_`ShLCv)hT;r4P__8X>w>BwWZoo0 zafW$NwfM~2164!Fyd#F<3>TnkahV5-k9SZtgv|S8D9*rUL^wWFplS%2XJRDI-~d&N z%YC5oKF&y-p$J_amhx|ckvPK~baiOuA1J--G7@Jvg{}^Zc`uB_8NQ&aLo*N5ei1Sj zXV5Z+gbTj%%EVZl!2zlcuYZlj8H&)=VR7FCV{wK#=<3ki2WpS+G8SjJhOQ2ad0#+k zOd$Ti=RP?TaRwuFby&;`FcD{nL05<7K2UsAnTRuVp{v7U-U<_OhArsou$XtrM4aIf zx;iZ8F_?-o@R;KFFUWmbrs51%=<2YT7hx*Skb35&cJ1cKm0)M(}1cWWS)VUID-vTEiU(g z%!`7mA!J^XnK(lpR4qR9dZ20unK#8uoM8b}EiUsw<<&l@8ban>G81RG2UUyDygyJi zgv=8#7iW+$#~&Y{^lSxHL&!WYb8&_+s9Id^1DRI>RYSDd7%9E0;-0Pc^@pq8U8@k;xZ4EUZgA`VNA$8ElY6* zGpJfz=7HiP#8RB01YI4L@_CY_IKw=2b!g=?$bEY(#Tm|^tHWa6D@$>PZ|LgK%mc-T zh?O{ljuphu_}T|%R^kj!P<6Qc3o2g{ti%~g(A8ma-y|z>hI#1f(A)B8CC=~*T^*WvApc5Oi!&HlL;MT(2Nv`Eti>6k(AA-t2a1mhYjK7SbahzFTV^fJ zunAoq7V|Dxi!(exSBJ&Cf7ap*TsCO_z+#?;jW~k^x;iZ8h1rNRB%!NAGY`~WYp@Y# zm;+UZFTE_Y5og#0RR<4eH1k05aluBM;RU)nEbe2o6=x8##qVEGdNHsSXK+DRhsC@k zTXBXwbaiO%1I2HTtvJIRbahzF+hr@xa131?7W1CiiZgscSBJ$sAv#-AOSb(k$i~IK3i8Gu+SBK_bka;ic#2NmetHWZR zl)X5EnmvC1g3NQV7iWk-SBJ&CGJA1`CUkXZ?gPc|0()_W4e08yn0L-zoZ%L_IxOb> zuoq|Ga6t117W32`#2Jjx)nPF&z(JfL0bLyy^XeSL8M@Hbp_vCtFDo3x8IC~J;Y%;) z9K;!JLDk_)FFzc_8F(BC`&Y|RoWTrT9h&<<@f+eO&X9qw4vT-=9K{(Xp{v7U-Udf; zh9l_eu$cGAQJmo&x;ixTK&Y-KqV%{q!afWZ`>adt6;tcUCq4to9vp9nRR4uOb0vbQ{a~5YPLRW{y zeG{C;8Rnp?!{WYO&f*NG(A8lv?}f8C!xwaQXy$?PhmeangPse-&-n6(g^M_Y3sfC0 z|ANwUl8ZP)8M-6#Tok0)nPGjjjK4r4s>;B=7HJ=*IdOJ-a*yji;o|! z;tVWq`27nqPsvT3!3td+7WYNCi8G|2t3z`i$iGc);tZ3})nPGjgPSd@Q=GOx*9oM9TeIxOaG zaTjMefUXYBJdl6yxr;M=Lsy5zJP{9Z1_clN{sqN{orgGsAG$g$=H+;ZGgP3fLvtU< zylEcd49n2fVKMKBhd9FpbahzFd*>m}@DE)b7V~60#Thg_@%tAPer}%P3~^9(_|i*; zr#M3iR2{DJ02IHIJjEH7p{v8VPVq4^h7UcK`aXW;Wf^9L65biBkFEYQ`V znForGFfVb2G<0=X%xm!yXPAJl4vTr~yu=yyp{v7U-W@M-h8O7S(98qH2b;GzgPb?S zzxd)q!&{ue1gZ{Sd<1!mGo+!b!{WXcZ*hhR=<3ki2a1n%-r@}B(A8lv?~S)O!w+~@=<2YTH^)buVFkK6Ean}9sv*>V zx#lCz@Cd3F?hiEcK;g&WE6$+cOW415zTymC=<3kS1BG9PuQ)>mx;iZWo#rdfun1ip znt7n~cfePi;RaM4zV!FVSDfJ;R2?q=g31FPKXC>HKO+3=C(hu7t`5z8AoDW(#2ISP z)nW1PEI)CEW$5bA%mexNh@Uva9dvbA%=_gh&cNo6<_|RUK=GmCFV0|st`3WNLH^x>m{;R3&d`Id4vTrK{KXlzp{v7U-W7jwh9~Iiu$adbAkM%SfZxBM@Y4wpXK;b4 z!l}jpz3g?7f}1TB0!vB3c5Nh?%Na~&ae+%9TxxI2@q#^gRTyXd0c_w3}S)! z{R?uRNuW4G08|}5|3(FhGo(S);qosiJ+}mkGt59&hsAx{0>v2)p{qmlFDO191d22K zKv##wJh32g2BjeU{spCHhaho=0CaU&%*zWBXQ)C~hvq(zc{75<8CIaH!(!gCAaRCE z=<3kS1NrwukT?TJFvK7D;zKA{oIwt%4qtrO1dB5SpsT~;zPw;@hAMP*Xzl~0ml?t0 z3|r9EVKMJgusFj#baiOvfx_=kus8!x2*kf|e_%0BD@2^Z3|$=-^Fl(z8B);IVKJ{M zM4X`yT^*Wvp!ir5BF=CGst#X#oC^_WxCK=Q_XnDJp#1S8M4W*qlnDQZiZhs@t3xvn z6dxg>;tU1o>ah5?FI1di7P>k#^FZ;jBUGH>1iCsb<~<7)XZVD!4vTpLVd4x5VTAo_ z7bec&g{}_GJdl4gplS%UUs}S%874s0;))NDdF!BR2$^>(Oq}5wR4qR9zChIwGEX2} zoIxfWEnLw23!2Zff~p~8o>RCugCA5aF86`rHz!=2p$4iBUwUo}7iX9RRfo$wPvRYS3^#2H>d)#5S_6d!Dn;tW!egu~Aw zQk=mBT^(BZfy_&a6lW+ySBE7&rbLP}EI?O>#k_ry;tZG2)nPI3L!>yvA9QtS=7G|S zR20Oogu+iNN}RzAsuoxHfy(=kC~<}ybahzV*A*qsFb!QDn)^WEw*{((ko!(Vi8EY* zs>S6#ka?e?#2MJ4A%2Fd!{R=bXmJJubaiO%1BIVov^YZ?x;iZ8)kKRkbfBw4GY=Gg z%b;oq`FCHmIKwHZTDU*Z%mexNMYK4>A9QtC+$R+y&Y%{97A|P!f&A+dBhCgBQ9wEdI@i7iTCzSBK_bka?5h#Tizi ztHWa6iFk2_E9mOb%ma-#eS)eXuMJcUA@f2K#2FHxYH^tds?Y17 zY6zLvl_1VA4XPHGd7$$^wRg23!PN#YDV$q+xo?ZslARG`oS)5@CR4v?IH1j~=w<%ei z;TXC)Ebe=fEY9!&T^*Wvp!~s?BF>() z7ND!cV&1+~afVaq>d?#s#m5V%8bbbMNE2t^Nh9oEEvOno=Gmo*Gk8JO;!A%SY2pku z=<2ZecUGD>!!mSrX#NGo$B{H~h8yVWu$cEPO`L%#ov?ou(#07}(A8lvFDPA{Ar4&~ zn)^WUTazx%(1WfHi+QWk#TmAtt3xvnRNh~Ksv#5~AJWAc{y^2@iVslxKq>trZ~ejs5*S@ zp$D1b3~!+7aG3{cUvXuLGZ#OPt{bR4qR9zCqOxGEXR5oIx%d5-zyR1C<9h+2Ra7P<8miFDzS}AqlDu zmwBM{(vU6AFaxR%pLvV2#TnK?)!{M^RGyy67H4>Xt`19i^)FkTfhz~Ue?jJHboafSux>adu%FISx56uLSz^FaCIMXoplOCH1@ z_|gkso;ZUPR2{zbVv#4#;DfFXi~F+j#2L!a)uFi$6u(pQ#2J>LtHWa6p*(ShbLi@@ znD+*%hEV?flPAu=kq-$MT>b^QPYtSuka=eL;tWnuwYba!)kg_XHH6H|$rop+fU3o1 z9%y`H8dMD-^XBD?GpvHD#bqAIzbBw-2$^>yU!36yR4p#^K;;!vfjEOy0a`d?iC>EX zaRwK3by(szsX&~e2wfc(^ClFCGt5C(hh`oqf9!&)A>`j{1>y{kplWgX7i1noA;hnQ z%#$e;XV8GE#bq8Se%+vI2$|o3{JW%;!1yw`HJf~7|20y4;T;_q?mjhKp$h?M9 zafTkKT3qIV!f#cnIKw%pI(*@Gqg0&X2~-^}^FZ!nDuaXxA@_-ui8Clc)#5YHp-h}1 z1YI4L@};OuoS_a~9a{bcl?QXm#2MD0tHWa6sWNefYv}6G%mexNOPM$WM>)jLaDQMi zPpw>>!3bR)nt34i1(b_3B%rIqVqRUjI71h@IyCb@?SmCiHH6~hK)E=>8K_#gKhVqr zmCvuBY6zLfRw2$HRDl*QXy$?Xs|FR~3;|Ge_|jigg*ZbRR2?q&fy`^E5NB8dRfo^K zbrs?ayP)cDnFlJbZa~!#^6!%hafT02wYba!_4oNI#Tm>hA>o40eGZl43_ehGxXc5U zFIiAEgxptDDb7#_Rg2HOIZ!o(%v(|^&aeTh7MFRT{BaJdhLCxWD#aPzLDk|i50pQ6 zsvu!U$UK=UaRv>jT72fYLDdj4FRV(OAqlD$pLq>XHH6HYP$kYV2dWmAd7%8Zt4f^V z8oD|x<-r$_nrcYc;4%*sesa~~40`D5u$bplEzS^ut`05#g36aNs2W24?W-1Nm<3gf z&wV?fY6zKkqFS8c3REpF^FZPEsal+YuLdn#u=rP}Mx4O{T^*W#LFqZHMw}rFT^$zl zI%>ojrl6}sGY=FWn`*=v4xy{VV%~!qafUbO>adu{RV&UQRZG~v7PaCGF6ip8n3n`q zLnuCqYQ-7qplb2O$DCSmh8^hYu=w{{tvJIYbahzV$51EEAW%oxzj}4z3|8ps(98p+ z=Lo18LjEnN6KAM_s>S7BQ2fq;#XO%TafS$Vby(t~4625Zf7_bG z874v1;_@#jy=-U_XSjf_4vYKVHHkC)LRW{yeG<*$3>wX7;ey3Hw`OsMAar$D%qxJZ zA>`kNW^slds9Ie91;xiIs2W1%?P?ZhI0jXV%REr|_XMP-g|L6+TErQ&(AA;&7nGho zTErP*(A8lHzp55-hBkC{Xy$?ZyQD>&VGFuCEaqKm5ofrEt`3WNe_F&Dcv=bjSF2T= z!3gDXIO=<4vT+Jw2CuaL05;xeVzpRQVHLVMEdD*wBhGLIT^*W# zLFw;Pk2nKUFT~Gqd$E|O&@0ZMgRTyXd0tR8g!~)TE6$JxRSUNl%{)-~*U~G_umD{h z7WeJz6=yhwt`5ySkbhtFiZlE`SBJ$su|9DIr9QN9!D5~RR1G2j2K0$D#6Z>J@-Ha8 zRP~87^r5T6;=VO~;tV^`)nRepwLWo%XXxs%n8(sD&LGfF*uQ#EHH7?Y*DucC1yzg7 zzo7J-(J#)>fUXXU`{wnFGps^ahvr|9c_;eC86Kdk!(!gQesKn_355NtF+rTc0$m*z z^TH;GGbEv_LvtS}{WVMwXPAJl4vTr~CWtfaLRW`o9w@!sfT|%BA8#gzGyH(6#TOr9 z6U7GW z_^_BN&ftNr4vTqdQ^gsI(AA;259HqoP&I_YZ^cw`hAmLF_`>fJR1G2XUQHEe_y$#r z%REr{iA)n`(3pl6E?E5QHcgx%2wfeTe?j3_Fio7H0bLyy^X5$xXIO=<4$VA}`%X*~ zXSjo|4vTrerinAKO-J(wnt34isZ1AVFhN&`#k`>D;tX-<>d?#s#cvH%4Wam$GF_Zu z0aPu%_}DjHoZ%9>IxOz{FkPJC54t)u_ksK?HA9?1YX(}lU@^~QhB!kAx;iZ86+zVy z@^8}&afUvqT3r4GwRhLd5N9}nt`3X)p3M+v_=K(wi~9s-iZdw8Bni8BbzBJ5v-S>g;f z=<2YT7X?*A$iG>$#2Lz5gAy*c6xub}F1nFm_$!!Z})S3>DeWUe@a0#q$7^FZr!?4W80nddfFoFNFR7MFP- z_Z2|Z5Hhc3t~f&nR4p#^K;uKp=87{MLsy3-y*!yK&hP0AYll% z7mIl|^TZiE(AA-t2g+}0P&I`7TQ^Ufp$n=OZZ8(|RzTGdGVj1VafUNcwYba!`S%r6 z4I%S>%@b!}n~xSQXzl}*S1M37gv_&;FV5fsRg24gpzuqAsv%@v)qHV=HmF)$=7G}7 zlKJ8cN1*EP<+pS5#Tjlv)!{M^wvsxg|;D@dbOZv-MAkI*M zt`03eK=C_mfjGl5bahzFJF-BW;R3okH1j~^!8@oLLjL`>K%9YTA%6dY+Aj(V#TjhS z)nRd8)IxEFG<0=n?gRO^WuZ936m)f1%-gh3oM9ijIyCb@{=EZLL&(2R7K$@`fU3on zUO@SSZxJL+2$`p}NSr|rsuq`dp!DpsNSq-DT^$zxb}bTTn1-$n&A*`V+p-T^*WvAouYs7H7~|4DmC*{ARXToWTjI4wrvH@sR*kLn!<*7K<~KK-J`L}6_I71&)E!dfYdER^9P!Fp!SO# zR1G2Xw3dl8m_gOz3O@_bc^=Ee8G6vwVe#*(W#SCm(AA;24-|e^mWeYwL05;xJf`L1 z41CM+`xjKd=|I&G@~_KsafSe>T3r4Gg7-ht)f z3}>KfahV75?<=SpLgq285NF_9fj|5}`AuhqID-SaIxO)Kw?dpD3tb(S`0ZFB&M*UA z9TxMptq^B8gsu+FJdpbytPp4T0#%2vJovXloPld4e*cCrFfeGW6lbVGSBJ%YvsQ{T zEJIg^<~~q*KC)7r;R?DsEarV$DbDZ@T^*Wv4h#$oGOHkdAQZnbtHc>HplWfY7f^a` zgQ_89-lSFH4D+CBahV6IFZZkxXSjo|4vT+(trBNoTMh9GF7rU~tFl_0!2(?!7W2YZ zi!&slt3wMvP<%A37H61%t`3WN>sE_1>_S(E#k?D<#Tj0ptHWX*+Zu5Op*8sZ3n~u` zplS%khtC>uh6t!yT;T^Q56Ylw2$|QnMx0?5R4p#^K=H8ys)mqxXV!=_+<>aZWge(K z^bM+pka_>sh%<1l#UFm4_|<@_A!MG#T5$#!s9Id^1Lcn-s2W1%<*gNGsDi4+Wgck% z(F~{>LgpP8ulHuz;$?8}H-hLCx4)`>H$fU3o19w@yWgQ_89-nDh&43D5{@tMc4 z9umfc%oA8I&L9I-i_1Jv_*p^K5Hincy*Ps(R4qR9a-eDmnOCx2oS^}#7N2?ZplS%2 zw`{#Q!zQR&eCAz%sv%_FjrHOTPoQdXnFmTQOdE)c53vp63`$V7_{?+IAkGkit`19i zU$jA-p$=UgmilAP262Wp=<2YTcWQ$;!!>kuXy$>^%NM8`LgDvkgE#}nMo8G;@-N6d zHK-aw=ILz|XRv~*#bq8SJ|duM2$`3&QJkRysurJl(>97TtU_0ZCHzio6lb`Ct`05y zK;^-wjp7VUo6y1qi+Kv0#2IwZ)uEXODo?$jY6$r^Y?C-c5>zcN|AO4t098ZCya}7c z8RkIM;xZ4^-q;0IL&&^qo5UF&LDk|i4-~%)o5dMKHWLm%qs`(BcIfKR!VhF#3{(vv z_Z4gwXQ+Xy#pOOwduY~XafVG$b@=)Vdp3(RoPessWge)#`)so~1KSqD{#Dr`&R~G9 z4vT;Nwumz%p{v7^{u;K3GxVUVLo*MQURG@pXV`_V4vTp=wum!4L05-n9wG&uWJ_gBQ9wEaqkG5N9YsSBK_4 zP<=TGs)kT`HEV}B!!oE^T3v!Fg9| zuz;$?@GiZi@} zs>Njd?#sk#^FZkf!B?1HMrWgaN}Za~!#GVj3wafUZgwfM~AI!Ii62ptq>kb|nlWgaNK*g(|~a$m?n zafSq_T3qIV{96ZAL&&_ggW?R6plWfM2g<)2plS%2x96ZZ!wINbT;_q|_t`;lhF|FF zu$0ddhr}6F4ne{ipLtG)#2LcS)nPHOd56RqHleG-V%~*A;tY4t z)uEXOijQAVHH5;C>99Bh-(kZ3)q$!ZWS+xeaRwi#T72P`by%FC4qY7<|IRrq&aeVq z9h!eZ_3yF6;tbc&)nPI33rNio!v2*zBF zaYUS93%WWi=3RoSArv2vj)*h7gQ~?BA3R4PVM53}g`?sOI#9K^%mejjypD=9q@k4G4IY%afTP@>d?#sm49rwYhs9Ie91?69l zW8w@k=<2Yzuj-gMLmRp}H1~nh^O9rY3|r9EVKML0F>!`_=<3kS1GS(3K-Cb6Uzy|L z3>wEFVS~%Rpzw2psv%@v+;MS+ET~#s=7GYmsUkQbu-Z^mwE2vsr=7G{n1XK+n^D@qf zGn7Ep;xZ59zDeiA8J3}|!xDZ+&WSTzKv#zrexUmI-8pdvw(}4_!|lalp2~S~1_N|; zXy$?Z>vvw9Ar4&~7V~P(i!*eftHWa6vh(5$+tAfvG4INGafS!z>advi52}VxeDGZm zXOOyp7A|P!f#Smgs)mqxJ{QCpBA{yF;f!V;X#Hf_1#yOLP<8my^MMQE3}>L~aG3|{ zzrBL0A>_VK7sMIT@hzsyNVVr zXzl}!VhF#1yl{8_~^JM&M*b4 z7MFhw7#J8fLDdj4@6k1JhIdf4xXc5kKc4H5Fd<}~#C3576{uQ#<~c#t5Hip2x;R4= zR4p#^!0F|>IKu>}I=t!Sx;Vo!s5)Hcf!cpZu8T9gKv#z)e%Wq_GYH*)gbgn9K=EsE zL!7|@T^$zl;%VS-{FnmRTH1_pT|)Uh%! zFsKrt4%80Q!cgbnpohg?BMfy080uOW7#J)t)CFLu1C77z!BB^3FAD<$!xId3nD&}6 zFfhEpP={%r90LP`+->x5##9G#pA8Y}q8Jz$QZUqE`WF9>q|H={`_8 zxQL++(>!hl28Qbx>ad&l2tysFd7Pkd#!!c89w^>EVyMG359GeD80s+11Ept%JLvI- zX&y)&D-r5I@xeueIxYqV20;vUnC=6qlO{qP$X-?FfiQ2P>1Oc zH3kL-<$HwvVa~w7;7No!kUtuTQ0K?Mz_0~F9j5z0fq`Km5$ZtkaSlTr zru*_h`(Ymvc3%(!149=P>Ol479Sn7t{?KAzU|@NKZZD=f(EfNmBGfrCFfgQHsKfLx zC_YvYq0W_of#DMo>Ol6IKSuWlroAo<3=EY-r~}3CUJP}Z;R3Sv6^1%YdqMeE=Lx#| zFwN6vU|>ifLY+DT14BO%>Wml|7`73q?gbI*K;b9#6x|<~{#9jQV2Hy|hv{EXx!X*H zI#7RTFNQh+jCL$2J@3O%hiNaUym~=|I#78a@eJL+nC7`NFfiC*sKX2wkh)eP)PeHb z84Pup_Ja2Lvph$4AEv#a`Z5AT9j19o3=9lqM5vQuU|^U*q`FN+r~|q043X+y5upxL zzj3@E94?g%3=By`sEc4=V7N_$I#UJ)2IH6L_F{%JNL>aI>OkplE)nWL`RFha>Oke% zUn10j`o&tW(EWkgE(V#Wi=htFzo2$v6cOq`{l*R=)JZZhFzh2joi(VxMT9y~IVtoS z-M^UrFlJz2a3MmSAp-+LIT7kW_O2jO-7O;2f!xRQhOj>p7#JArG1Os}8?vBwF@`$K zaOq)SVAzA94%57D1_lO?x9IM}R0nD|T_!>uF9QRE;yZNnFx@B2z`&qKggQ{W(~$^u zp!^m}ggQ`vrk@CPApf4lP=^^VpnjC}dvyO|ssq*2$wa7gV_;xdPJ}v8z4ev|b)a!L zlMm?b!;BA*3LhfWIWjOXBx9(<^lvZ&1H%#`)qN*IT`B_ugV#rpD-j)Z2LVibV;C41 z))S!)6mQ(0h%+yk2z7BFdx=m7YDe&XMzQ*x_Fa&%- zw-?hqP&@25hB{35B{48CXn!SaUOoc@LoX5PK>qzhggTJDvEK;W3u=e$Cqms41_lP< z?}W_*soPG3I?(t*^bd6NFvAZN&Xb5x2P%gSVW`85H_$ktz)!;V3NbJ+NE4w>jDdkc zmk4#Bdf%A{b)b4Hh6r__{z4@Y>Ok$1#YCtB)#o>fPzM?p<^F{pewghmCI$uu0V34- zGB7X*VW`85H&DAI9zz{wda-9FOJXI!yP0#w}caqlXK2_vH|w zE|h_RVKs(2?DjJJA#5)w{4|JA2TBK#M5qIe3v?5qPM(2*VLuV-K;sejFw|lC7u3&F z_)FNopmm^5M5qIe!_{G^!?YJPE`JI`9cKK3%8e^Tr~|d@o)MuAG>`L>2z8+LBOkpU-V$M2-k`pmw$m z5$ZtYa|jXYK>hP-BGiG_QLH3Foi+ml!&xHKfyN1c5TOp_4_O9~uknW;$X*vB)Pd&d z@`+HV24haYHMJ_th{W;qVZ zM{_XLVWww31_p)|M5qImSKEkC2P&_Q6QK@NZrmh79cUc$JrU|a`I3`~aQK1zp-zN4 zQ2k*+q&iaBhv)Pd^hIYg)f`C~m1>OkSQj|g?3 za`!S3>Ok$LyBO*)(>-WhMV=Ww9bl>h`6C%a9cKOjg>w-R>WUZ`7@9EDVcHAw?-vYp znD&C)_m4<*ye#Pc#WW9;?q!Hj2TFgsM5qIqXHA4UQ26-}p$^o3i6KHAs9lmyggTJ> zs)7wAsFf~%>&sRL4-Px zz0nxzFzp4Y%Op}=IT7kW{lQiu)D?ini!sz;#v5oooH#prd|-wPs2wJYp$^l(p!};! zggVf=B?BVWc@wEFjRMjzg?lY0MjwX4pa^~aT4|iNL>~Y>Oke>BqG%vBSIahJ^qVGb!J?I z{Q+`c5s~Va5upy`zFS171NA?|xQTP084>D0<7Qbzr~}Qv%^^Y^$RF2;PzM^{72+Z6 z4^V&Kg9vq?_^2RK-5Mg)fx_hh5$aMvsp9Zrz1evylD&!3H6qoS5TOp# z-*O^C9jJd9M1(p}xFiyx4zy0c6hj?mdlEFxP=lckGu?wixSt4hAoG?Jp$^n9+eL&r z(744hBGiH0cbf=xAbUR(p$?R;{u7}NWFC(Q$aehc4^(bQ5TOo~4it${2TE54M5qI` z?;VIx2Qn|32z8+HAe#tvpzy0DLLI0-(?x_jP`aN)ggQ{aXBiRdK=XqeiBJdXr(7UH z9mu@5M5qJJXz+*uXdL7=5$Ztc?;{cFK<2TE5sqI_xh747I#7HV5TOpV&cllc zb)fu_OoTd6eVIpuI#9e-5TOnfE^S1p1Eu>pM5qI$gUv*!1Eu?8M5qJhqsK(31Lbo@ zal-KnGEadBbs+!R5upwgexXFD1I_zo6QK@d-XtQ_f$EPHM5qJhx5GrJ1N9qE6QK^Y z&XQ3AJ)AN7M`;WU4DLjz1C4{MB|=>o0|SGsBw>5!F)%O`6QK^&{}q%%HxILY1)5KG zBtji%{3V|Vb)fms=|rdlnRk!~bs+b>Btji1-o&NR{filIeGCi?u|%i?+4~wp9cK7} z)_qCJpxcYt-UW>_NMoqOG!HaR?T?`j(>&0+v|u9Cfy|2_LLDd{#bBtzbRVdEP9Z`a z$hD0>3Izi>Oki$-N8_YnGQheWMt9f4O1OxTqOoW9cH+I`tj3fZ=OboXJ}3mRuFCqf-)zvOfbb(r>o^2Z$v zb(r>o;#Wus-F=wmfyO6OFw|k12io^EjRxyetL=h8;wx1J%geWS`U6z{c@d!wRBm)&sKaz0sJwrNp$^kt&^li^ z4Z`m8W?*3O$54l9FDTv5!cd24FDRVPW2nP44^;0*YZ7)JsNK|0gt`C*28Nv&>M-pE zl?R+!==Nf&o5R4s5KV+SkUzd-sKX2wkUzAw(e1@l2TFgT80s+H2kOu4$54l99;jTq zON2U*KlpVByAPCqorzEf%9oWG>M;EQa^G$Yb(sDD%^SQWLLDf-Y3dU82dJHsj-d|I zeV}~4977$Z`#|c>6QK@d?{^G!nD&Cg&s-1PADHSu^I(-2>M-30sy{Xop$-&&uZd6x z+E1*hPuP8+`MgFV)Pc&sr$nd&m4Ds_==Ngz7v#Qb40V{{2ij+SnFw{Dbgyej*j`Zh zB@>|zlnxeRsKaz0$i{~l>M;Ea>X-c_Qk}XH$W;8}M;Gg094*%sKX3DP&+%)7~Q{^>OlGEAci_jdqMqxpBU;e%>(VL%r+rxFUY^g zG1Ot&3yR;*M5GdH-`vyp#J4T40YJ^ z$6E|_*z?CHBGr8-Qr#aS)Pd?>MoaYki|G$gyU7tl9cFn7I_IRC2z8+L%R(a5f!5nz z#88JBexP-#SBOvtGVc}<>Okh*B|=>j0|Ucj40V{{2dbx2tq6xRs9(lvO;{ah{3;wn z9cFxh)&W!!p$=3JwqmHm^ap6%_9})tOnX5-xJ!h(5(Wl_uNdku?FFqP%d`QRirikq zR0rDEwiZJjru#tsyo*Gr1I=@?+M?TwX&$H_Do><3TO!nf#tEZ|PzP!cRTH5O@E@NK<#m92aqfA`vYXJJCW*A ziBJa`A8IB-9jN|TON2U5d3qN^9cH|N{HyOs*uNlk(L}0iCqf;_znh6r2O0;ti=htF zAE0q0c_+gD0I3KhLLF#5a3+R2OnX83ax;cH%ybWO-*Y0=fx<=78RSa*=?~O?amG-G z={}IUW+K#q`oD)U)M5Gqbk65*40V|Hg6h>w7j%DMssoi9J2BK@+6z+mo(OfI@d#B{ zbbB$|O(6g3V5q}xZwe9WK;`Z%BGiHE^Ai~AFvA5j4$R|*?hj0Lpl~iDLLJDz$1v1k z`WLhw{U3%pOnX7)shc~x`!LM|<=++zb(rRX^2ad@b(rRX+9f}TPzTDtLLTVu!?br1 z0|P@05$Ztk`wv4Mru#rP>Ue@k{P`EOPRoS|b)a@#5E1G?^-&oS>OlUOLWDX{{2n4g z9jLrw@FMJAP&-G92z8)!P(ehf1Jy?vM5qI$=P4NKFw+4jUERS@hZ(=1bxYqc)M1(j zQkUk99xj;bK>6buk?JIT2%85gU!sUm2lDSc40V|P1;y_UBGiHEnMWAvF#QWEhm?HL z{efvO=$y7F40V|1fzk_$9~Se_&Z`Hl_d(=H2&+NB0OP!RkUA+0^DyfVkUBXc)hQ9F zPK`))T12P=^;h*U)M17*$RB1H>ahFQib!>KM5qI~&j~{vcK3Nag1zMT9z#y-67AFzp4Y zOCwTU7Ln@mh*VcZq`ER9)m0Ixu8s(Gp!jXVP=^^Gpz!O$P=`I7`-oIGiAZ(Rh*UR= zNOkjwRJVvob<2oUw~9!0>xfjhiAZ(Zh*Yi!X_jxP{B-D6Kzazv`rBT}6mk?Q=2R2N62x;!G))e)(#k4Sa% zh*Y!dq`GxPs@q4Tx^qOTyN69(az=Ks8EDoQGKOqm$jI;xO&tRl1A~Hsg1(EZn`5X? zh>xd}l~QSGW{O@;W|9(7!akl(&b}dNYW;m&{2YBn&Ma41M zshK4iF~#|%Maii#MX3e(#WDFQnRzi~sd*{+MKQUVd6|i&DVg~(#YM>oo?d=Z7K0Uo zjUYo31H%OdVFr1IhDruF?VFiglwX{mR-&L0q^aOvP?DLOS(TZWuHc-XTacNPTEyVd z8Kc7C(V3$n;L%y4BH__lqoUv#=F$Ad!J~VQN(1P2(e96)-7kE)KY6s?E)n(UcINPC zJy4?U(Jj0)z@CBO|Ap7$|3RlWFc@EQ+yN5t=sp1v@aX>G(R!eieaD~w|Np(Oape88hSMkT{_-Hwo8@$42PEm%T)y3hM`ANJ_J=FxqYr~pvH5dd7E z01klr-T>r#3y|*}JerRv#2>bV`wTQW3-LE-n%e^G?}>2P`6I;Nmc;rSlu#j=!T^-4 z5B8#KE%g6_NB2Ardyw?)k^qm^|0Tj6-R@v%caLsykM7g|FTA#e<%PpY zg#k?L7?Kz$;tW86VFAeuAoH%n%zFej?;M&MkN*!qv#-7~pke_OY`wxD7ASi_0&p!n zh&IE6?I2w2CCH)ZX)7hJ4(bbkZ|3EOedNe&FY zt=~%69gl;IWbkeM4`PFiW&p)(0?6GNAP@2Sbc4g;JS4nZzm>A~y8V5f>CyVOgtNO1 zR5*6Jg3aRWZUgNUb?jsVna0`OHi3bG!LgGaWQql->^1J522#*^sYKJ&_)_bo5+#tR ztZVn7ouFzAY(WRR$N$qF|BpNVKjdP4spuZ0)J0Br%|{d*;~nGT566S9l?Dgo0no*0 zlAwU}XuVX*w&UOb|NjqrKzs>G2=6?)J7<6b@qkAs*I|cXP+9BIeG8|=S~ zUN-Ms0ScHBZsP+Uoy<`A?oQCsXRtfFLBhrdAQGSp;g*9V5-keAIg#k9?*;>U)dv!R z*GEIF>T?IDNjy~_EaQ82|MckogjwZ*JdfHE0Er+4#|=G5W8 zdH}U0IJzBJI-5Z$8C*Gbc7xKnN2dfNsUo`ql=9&Pj2yT?02MGIu)+kKvEU7x7!`vZ z-@r~OQGu2ypdH_kasjla1X8eqZj;*rPWGUd)Y@`TvIp(dUkFZs%#5(6h9DyYgE7vg z1}KvUKpGn$SBiRcyF(fq9^K+QK_N#tHSI0<26HL6z*c~TXZJDeMK-8ihcntDFrzI; z1-WGdx}+5n^q}oAke~-Gl!OF*Jp%&+BFfc3t2mMocf&Cd2B#@v7)pMZI9aJ#D z>bVjX1JCXo9^IS3sjK^!N9(r|DGz8(?a{ms6t4`W959jQ{otfm664XmeFLbjf$plS=;@_?o^P)U51Xz+Nndd%F?t?RL1>RJa(tvN|Wqz`())=bi_1J(}NWc(k4@<>+<%=h1vb z<1nbA;^=M!W!p|BP=lSbyAhP<96K36MFnSfBPiQBb~5^Oho}Vjg2MR>C~z7Nf=YV+ zmi-J23=KQAKs6dD>=jDNJ(>?H@V9^#2X+UtfYeV1sqc0K2ceWp_aV>jQy$$HAX&dV zMnwT!ylWix>5fr}@NKFJn07;?@09{YJ2^{FV;q}}fI15yXVXyH($p}=+aUF(- z^(Amv7iFfU=}kN@xb&LFZI*)!4CRSssh`(e7VNpmaKOLsc2`<%i2@2gF}VbppORRT zh|FhT;ouPvk&sc)FtBj&2telwz-1y53K|&TDgzP<8Wu3XWWl@x3<3%U4j|@)1se`r zcmS$`85msFFfcTL{9enz;KQQ8V8g}-yZP)HACn_JN+!ZAj3$L1q?KHg*n9E^Z!PK7IiPK_O;ggb0ieqCpr*ijN;nEgu6D znpq4C3~XS)!NtP|jyp9y6B`#RGX@?o01JU4-Y1F)1aPSao52P)L`Y0ZK~2xh&do;! zmueO;U}NLp-~uNtB`qT}pLq;mQ5H5fkU0WEVlqk^dS+$^;KC5aa%y@OE+J{4)sT?j zf#_mk0c+&o;Ns%q;o;%q;}a4Rl8}&)latfX&=3+5V&H%Q9v&V60Ra&a5eW$i85tQB z6%~-rz*tI33Thq1Jdj(!MncS!l9EzTP|(uS0-1;3UIhgO9UUDW9v&3)IKYNscOTq5 zh}CFH#l*zq}+21I^2dan+{8m+*}L{?Ck8Ie89}X!OFnF$;QLR z$;rva$-~Ak#K6GD&%?mR!^y@b#L2+P$;QJ820Uy+Lg4Ub;Nj$C=xy3=IdRk4!yyIcm+p%Ucg0{D0#1!S|hC4xata zc8KMt@F9o^4ff)$iVnLkSxyWLi=4W6RyiFu+~g#lzsu?0;X_Ve^-nuBY`W~E<$cGg>E{zCg}rZ` z=1l$OG^6Ri(``o~Za2aC!2e0k(nqE`@9&=ByfkdK z^HG&~&f?z|I8QsZ*x7Q?GUs14E1fS#t#RIMz214P{3homY+IeDf7tHaba$6?+}XX( zss|1@U*3AyIbq!~=MyVWI8Bl=~WQITiW(Z_tLWK;B_#xweMOGnJCRT_k zf>2Wgm>HoEzitLbB}N5Cm>DoVf=sATfC&{cFrh+_8O;9={ag5N;y-fTVa#mAtjwg$ zB+LkbhD-)b%uGs5ii|>x3ZNkQ|L~t7t1zn)s{*Sciz15~Txf>=~p z0+|DuMVUpJ16Z6{SeaRwO<0UsLK#CCT^UUootRaa9hn@NM3_WCPGxcc`H|Ux*^tSQ zNtj6(>OV!W|5*M9{}cXq@h{A8=>CEE6YfT|IB5E3%98jG*?^guS&0z>LE+8(fAPPhe~$kQ!J)4B z4?Wy*#xpd$5B>{gHf7%U&xJXNS(Q1E84@M|jG|1UOwLTsOeRbwOsvcf%uY-yOvX$s zOpc5q;Mg%>WM*Xjf8?+7-$Q>E{+alPocLk<_wnz;za^~htf{PFti`P6tSPJ)|C+HD zu@teWv#7JUu@eYD{WONz4h%iA;%1s*C}QVoYL8qRh_BtjvOcLFw6q(U=kBM{wF< zGGYSdSwlu)MsRLrRAOZLU;Iz`-{rrP{uci={Hyp^=r1JK!qPR!pA0MvEV$B7;nx z=7)cin39-4en^12J&~D>8I%&isSup@VCfE&@>G~W`O_Gh_Ez3Er;FXvy3zdrrE^mEhCi9efuCjE5! zsrXaq=f@w9ejNI-=!Yy@;eXG5=(q&%BQLHuGNQ^^A8If3WnkoMB1){emU%cRh1G^IGP$%(qxhv+QBX zXZgnbjrkShOUCbv&l&rezcSxsp35B1T*rKhc{lSHW`E{`zwDVmGks>d!Q9KdhB1%v z6O%sE#9tqno-v+eoWuN-c^A_zrt8eL%ukq}F!eC!GCyW|%ru*6Hq%b#Rm>k5gM2Mma`4rmIY`OvjmOn65DGVCrV_Wm?HJi|H6s z4&!A;Tc+(yml!WG>M~wrtY)lcJjxWqG?QsLlMSN{V--^+(-y`pj29Sr8CNiNF&<&` zVcf=;%^1x%gRztG0HZhKGR7>%>5LtWOBtgWr!lrORxp+`rZX;Nm0~^kjf-{TH%r!q z-zI*W#Hz{K_^p}Mg0=8l;Okm|?ZDI9bRcCel*2L1pqQRoU(#X=t zvVgITF@tdm<7CEG#t6nTXvtE-=+2nR7{+MMn8N7BSj<|)TF6q!BF-Yt5(+AxSd&@J zSV1{CiB*j?m{o!q0vlKwSU6ZXSQ1$hS;SbxSU_bL8w(rD#xK~)ekT?c7GoAiW=CdI zMi)kuG9O$Afpaaqj%E9w`upn7D?dzrZ24~d{qeWS--5phef#wF;@3l87k*V@1+_Fl z`4^sdL3x6Okp)u2GB7j!ee~zhpGAKf{}ld7{1f=c@sHsj#Xo|782^0y{qXmt-+S2d z**5-O_&c8MIa?juDYi9iyV)lG{=(+Z_LMM?Ip$wj7J%H8D}ycVB}%kz!=SX zkaY%YC#wwW7FKW8WvrW7r?XnKShG~JcCc<@UCOG>qRkQoDhF7kS-4rJv1YO^VfA8d zXRTme%xcAA#WIy8l4TK#7Ry2wDVD$=Z7dlqQ&>D%CbL+wIR0p5iC|g4$i-OBIDv5z zOBqW#izbUCvn2CG7EYEH77uXw+stACE`ytxo0v72HJBTj8^I~i9i0ABS;D~eO$tj9 zvpTpe4rLZ+{`f7K8B`XVF@s9uBxW_{MCOIxf|(N-*}(OPDYFZ65K|D7Dw8TxAX6X{ zs4Wn{%*qUD4}i*JVq{KfE#BI#u^ zxJ_{J?=RMae>eX9!TO!Gp7k4RKI?yG2>i+#&-#Vs3yVLCKZ`w!JqtezKg(y z=PAo==G)9~m>B=QWqiwcj`1$zJjQv9>zL*;-DBLxc$RT5IHf&jJjFPN@jBxtmJci+ znLjeiGs`ox{$cqY^6T=?;-AVtAOBeNqxgsMkC!YjSx&R=VST~c$9j{sj&(Qd4c1=P zXRLWFc`PScPO|J`UCkQDTFd%`j~B#mL8VJERR{Pv0P)xW!=fz_)Ct(m!)841(%=G8Eu&_G3zqRGH+%! z{CSb-B2y(}2cr-3Hf9^hnRGjqnTxx4=`_G_GVtjoW*R-WX;t0V-u4$(?%vx zi7U+{&9s4;n|T^@Ci4}%%aSJECDR+jLD2<;C?_7BPjiY z+TD=$IHF7zVHRO_U}0e~0=L4I;r#*wP+J_S@5B86@W1ANPXCntG5`C|@}K4EpVZ%> zzf^us`62S77+g|M`d0kS@SD;%PzlZW4c=Y>mCY;+EdQ9_Gyi42&-{n^Jo9hn{mko` z)-(MA)%8sCnfjUfnSL;TXRc?eXZptUjVYfgpXn=8Jku8@f2Pk&_DrC9Tb@at=_4~g z^TEH9{|5gR{`-M()8BXCvh@uUs5Jpg41l{?^;%Hie)YQ{h0L{Yc8uD>rU3$tgBe{SRb;= zv0i13Wj)SX!+M2v2WvO$8php>HyC>vA29JTU0}Sx2x?F4V!Y1ygz*uhFY8LyS**uc zb6794+Ol3^)n$d1%STxu8;$(;BLRx%%A)nVPr8qIo;m6!1VBdp8~{JELc@#jKT zZx(NsWvp4O(^)H74S#Zi%is={4i=O$7*zhUWU_d%c(H)WZf-_U={kXN;g1TI#Yknb z6bq;vE{B)IOBmZ3L1i(l3`?O{aeE5`is8>sc`0xpBWZD8hw-$3QAGqVYE03$0Sq|}A?f-3Mw!hW((T=HjfnABam7VN{gSKs_ z)on!1~Lz1 zE=Uc?9*}(?Js|r*`ax^#XvJYey$bN{wL4HTYAhSVcg3N*k3K|1sHponn zS#Dg)j;-np_Pz5M?487-9J=6Bh+1_4R&|doIUi*ws?Dn$LRP9YS zOtrU|!E4`e#M9pTSB1TmwWz)4k#_re_rvV-V&v`BD(~38n`vindqBs2j?H2RKh>Be zGWY-bH7KNCbXyVd0Cdg>$T_}j^FQivsQc|H&S+Y=yAL!^4HAZ9(6Sf?1_n2uwL4{g z-8S;sSaIlU#&YIOXDiZ+xW3);Yr6NOOyF$AD)x6*>eBnJ`1D9=S4Fn3b^5br$|pC& zW|!sr*WOuvDB)4c-nA#>o-goNa!(@0GUl@Q$#)YgJL_6~nI5IH&ILN>d;LN}f<;>7-e z#~BV!gxWvDk%2+w5CemqGXq1iGXq13Gs6MHBMbpdt_%kryD&^hcV&2S_9(*!7B>cl z99IU1*KQ1=`o|koVEQ{885m3sF))}rGccq$GcdS1GaPU_!mxqSl_BA#3qwJmE5nW( zM;R2j+!zYXT^X3}x-m$a9cPG|0JXotk%7VG5Cek&RKKJ%14GCWh6xO=3=L;p7!Fvw zGFUu5%5a9yjbXt*7ltS2+!)wwk2fUugVv@VU|>jaWMBw6#K52lb^kplh6AZb7#RM! zFg)1r!cd^*%8>E)D8m*ZHwK4x7ltcYUVz=h$!FBb+EhGPs75^f9)l}8x5mbx*>1s-Rp>xSxA zaAaWUIK;pp?99Lr?##fz;J~2JcZA`=dl!a;UKfUj%PtISSdKBsNVzdAU^&X5GsBIc zEBrXa{Z4lV28RO-3<8b}43iEqFmO9V(%_4O3=Y$eFdTU4!q8CU!qBkRh2adxF$NoH zHwFj4qYQlAZVablk2i>SK=m^?GBC_L#K6Gf%)sCQii1N82@8%eC_HvyXmE33c+la( zFoWk9!wzXTh84X>8FFge815z?XV7W`#oqx2h6fG|466v749d<74F3)>IP5;c;Bdx;fx!|KpUw}Ek1sg$xo*4}w^p9V8L{yzZS^9C^L0wvQDtq`JYGQ5s(ud`1-(o6 zhvpw|OF2D{9|NtCd(F~%pd`oF(nbZcte(ZEyIR4q8?xBgx0^@Brx&!?j$s#QiV?gH z-?JOCg7jr5$con6C1_fF8E|V+Vq{?84?k&qfM3w}yho?2KGYS*UC*;H{QtiLw8+P& zv-CQ@fa`gFLEroQf-WispZEn_^*`|ox*q2j^nK4S==+~v(3hWI&{v*c&_~6AU(mOm zU(k0tzo74OenH>uANUWt@pQVVICylsrh9a{_Iq@@dw6uZPWR|`kMQVpoex??DZ~3BZO?WNrxbH1!#T++m#hpv{2j6ZR6`yWMS^$}-gJPl_ z+(hW|LsS!eyK5PIx*^GjWX(H3ZUH5>G7gV!e+ST#TP}}oM$p1ZCeV^={{WB>D>#H4 zoBzE9ZNBjL0EwY0ivS5hl!4jw-3XtB(@Pgzj0|SE^ zcp;S)BLl-a@G{plMh1qjA|Q2zj0_C^U>(hj3=BKLD+6c3WtT8AFa&{Rx4~r-nPBFX zFu_czV`5+k1*_|3f|)X%iGkrP*j`Ysf~-wE!3106d5ejGVJ+A#Jj}2qpi;~*Sx>lF zI9#lmnSp^HY~Ct33zWzu!eI-?eL(BQPlC#}JJ<^^&+a%_`FJ0fiaG_4*8dPKWh^;ZR@DSAmOS_Wf=_3? zf=_3?#>+jRb==^(#Hafh(tZKBg4v)&k>ElfWHNJUzDKXWf=BB~{+33Fj1T|DN)C_i z4<#j_ZB!0vY5ejnpn8ZCSnGY|HK!t|~xHy6vkGz=Fv)d1}c(Vj4H+y#b z8DQpScLC7K$N+G20J@4FblDsvuc^UvF(^Mnaw2GDJwz6C=pQ5(gYq*(HkpBe0V12l zz`y{>#i0BQk=+8%r^n#xK>c!vDVN~+`3YS1HQbaBa9MBog6c?69Sd%$9EFQrhKqe= zgt_GpBLl;KaEZdm1Z&%GFZK-M(_Jp% z3u$sNaNugTC_r-rGj`!ezlCH_{>)g=4^?EC`$0-#D7A#A_4pY{sAD7 zgirhe?g^jx1zW&d3qf)PP|w@^!By#}}j1?{g9 z_Uv{8uP>JK>=uJm=+GR5TK)QTLriG?_a0oCI`X@|1;qJN`OBl*-vW`p zKDh87Ocnsu-=GlhJ_>3EfMci|63HHoM?gu-F)ltf`Y@<*49PXB@Eix)lmy8&poQHK zSx~+eJPKC_T9ggRnHS-C3e>P&53Z}?;kgD> zgF@70Gs1GsbVgXsc#;uT_kDopy?^k$XN#JXLcw_noVee5bpLeeehY2o`(e)vNUi)p z>?(Y^i$V1Q|HkYDP$S(Glxl<>d)+{-bdWucagMQ|poJx$ZcsBFNdi(wb#74sl`F;v zKrQwk44`)U9u-hnfMU9&`-9_tcm@$-1f{U#8rRk*{HBk#i00}VhEmnmZ{VgTsBT5BoIJYy z!TAZ&)C6Uo>I6_LbBPKlRy?{P@dV0~pxE(fJc1OJhdsKB1E4)V93>w(p7J=rJ*E<= z)&u;l&j0`a2S=q9nDLgs)$srS|DaUF21-S*TM!8c?E9Am|Ns97B;iK`hI7*>Jv8mQKSv{BD9GB8Adb=-otLRsN?$Nx-Gm|11)%j1iB|Y&_Ue>NTByKFfeQd2RbOJLmJr*pz#Nf-Zd&G z{{8>&(fGy!+@EI!HS0W#k9c&pN`SNUN04tN9d}&B6*}-1_Z3_!sy&QPg2S(S1rq}U zs7EB`SYBdfgOWDlmaY5j1>yy(2(%wnulEf={pO15n)$vVuSS0KeuI6_6_a z@NbvX!Pgmk+b zbh>~x-Mf46YvwZW3xucyfD1Vje$CJa`~oQ|2K)jsDh~XbsRI0(u?qZxTS4V4zhLQs zPTvEd9x1j99d3H*YspiLznovk3Rce@94wt|#G`uMG&+z)Pj^nwKWHC+w(HGLEK z1;Bdv1xpw3Yq|#T3zlx+7c9NNuL1R#V2(-wzhG(uzhLYH(5@wRkkJgUIX$|=Ao~bi zx<7yd%eD0zf9rDw28M6W0Rp9}9=(%0KuMt2I|8f$Srur zgIvDjvTu^W_r)L`E}dIJ#nZu8Y%ZNkSr{1@j<<3!GBAKfT#mPbx-+2rfsVHdFoL?J zAXyP8i+}x;UXV_g&czKN7j(OG@UNc?Y6fSXy1)+F9DSff!=qdL{{@fE<_#bftp`fv zJi5aSJYbIXfM&DTVm_Ul!Af8_1lo_D?nvgOBbRYyo7PMj63|#Dj zZqS6tg7(Kj%8b{H3=Aj1-SKaX3=F5hEEy&S2FRYRlS~W@kezZDnHU%jfXkN0@Bsm7 zc=uit-n|cii^ajkroub=2bdWc)`B;cfeLT*gE|xt<+uhXt}X_w=dX=RMX`sg@i$0! zdNU||K}{z`P}RxN>-O&@XhW9=YX9Qpa!{|LGe^Y$stXj4%|Bj&hEGkf+u~xm8I*=g zIFXX(YH)&VeG3|z{07=332lOVbc=a(pGEE?A4Tb%LlQEm;^@v%0XGm(n-@s62V}1= zbiZhKjfw}TT5^m#4C=!|B3l<;XM?UmfJ8Q^4-1j?XJBA}Bn8lA+z?qr^|20K&49K~ zM}w2hE_gKqYEeVRfxf}FS%R`ZWV5d!Xsi^Rcn}c_u9}U%AtE&xdtf3}&SAJzctaY( z@CY>n)mOrxU7Ou8C>v-|t7vFYfesA;*VEwo37k($*!TSf4`p_TgW9X$e6|zR5dOe_ z5L|(QYIqCKX%;0a&{Ic1RWl?UHQ}KQ+L{UpM^M!akp*3Z4GCq?l^PJ)neb78?VxLr zz!?coNEPA=2l&K52`&{>52;ccXks_<0EHE-#D$f`{Jh;4KutG!hVEt%qu1jpWR2R?c5YaRZ?AL*F!i9f=z;1j=~V*#kfV)p2i2b&0MR)WTxYr$y^ zbO~w=m{r5Tz)%flwJ|U-RDoHb%*4{``0p@(_yJ?E@rR(s3py4+Pp;^8^Z=P10N$6} z$<*!WvjpudHt7!ZX}wgU3pr;)jb9$L85q22S<1Eh5OmXW2eT*W ztc}Cavo;<=92hMS}A@D2m>Kized(aKBi%g390+6@+(e z96K3YI|V_0(FXZNtvk@B^-_sEh)sm+L3J?1^&tBg7z7v^7#LPDSj;X2wTF=TpgqN) zy~PZ*3=9Wi85oqp7#KPt7#SGK85smNGBGf`V`6BqV_^8C&A{Lw&%mIN#LB>MhZWqy z0=3&g4OftvAbTEy{0a?-!A~`S2H>F1Y}nt(VhZMi=tdS(@LC`c8>#>lwP1M&A4R=A zl6w2^PZsV2sbRcVwEz2)g`n$Z7(i^07zBgY*&v$*whJN$B0=&D47C@8_t#z!wg=H5 zQIH)V_dvzK;vhK~A4v^J4vs1F}28Y>-+I2FrpfFp+h3$b1+Z%^Z+&uzu(oM7W)J z)PwYl;?WQo4S~@R7!83D5CWifG*)lokYHqF01abeYQd!*G&G8-ngL`E7U3Z!j@4_p z!hw;20d!}OiNTfR2FT1`V4rFff3+sh~CTpe`BcKxEK4QKAeC44@HP@WN9D1_scw6VNac zXp#*yhy)t00}a%fGcYjNFfcHH#&1D0JD`Ch&8z);M{z%YZ6f#Dz{1H(H;1_sbnNEj0X!z3mK zhAT`A3^L3N3^~jU42PK+7*tpo82VTk7?@ZY7@Aob7zEfD7*?|}Fodx)FbHulFg)O3 zU^vXlz_6E#f#Cu-1H*qF1_mEK28Lby3=BSk3=HhT3=Ho?7#P^Z85lw&85r(JGcYWb zV_?{($iN_~%D`|+gMs0`4g*89Ap?WA83V&kYX*i*pjG7_3=DPt3=D%lLDUkSe!{C@CT;djFC1wS7AQ23GXW5Le{KNWr^ z{0#V8@K@lk!QTsiKm48Wcf;R+KLvjT{uunZ@aMyy34b>HX%KA?X%KG^YY=P@Xb^4? zYLIP^X^?M_YmjV^XpnA@I>61U3b> z2W$)2HZVOCBL8iR{4|i zXUd-~f3E!b@<-&a$={H_C4Z;<-SYR!-!Fef{+av>`B(C<=ii)vYyR!|cjn)ne{cT% z`N#8L=D*H=oBuxlWB%v-ule8cf5QJ6{}=pU@qfer9sdvfKk@&<{~P}w{D1NP!~Y-u z85nA6Ybt9>YYJ;}Ycgw6YZ7Z>Ya(kxYXWP0YdmXQYaDCrYHVt(YAkBZYD{X3Y7A=h zYIJI}YBXxpYE){JY83v+{gM46^GEuR)E~(|5`V=1i2V`$Bl1W1kI)~%KLUUFZTR=_ z+3@Y*wc*{vW5ctD+lG4&mkrk*P8-fW95x(#*kjmb*zd5-VT)mtVY|aRhc$*(hV>51 z9F`as8J0WDbC`3OeVBEad6?fYonczT)Wej+~F1cLy$ zzF=VIjtIWwxO7-w}a|{WNihc0>-OE3}KYhJ%|JubPhfkfI zFn!+4p6;gB73-HR+_HJs-s1;Op1XbL>ZSibK7M=rg;Q8m^x|poQ>P|?^wHP@sgka zzJGZ7==Ga}$IhR)d+YL*%)Io3(xU2`z_9oTUoU4DNjY%=Rwiy9MKyg5TPtG|KTiku zpwO7;vcih`tlX4jJ4*v|B~=}5HbxG9DOnNWbt@Mv-?Vkl?r9Te%>Zt!DpVQ_3X+;EKH48zHW8w__Ft~Xp__`vYB;eW#~hSv@6 z7@jaZY*@gsv|)Y2Du&$+`xv$`Y;2g^FpXgj!_0;bhTewuh9-vQ#x}+t#?HnWjB^{O zH%?;Qz__(>f8#F3)s5>ImoP4De8Bj$@qObf#@~(q7{4%nY`olfjqwiS&BhaqXB&?< z9%6K0bZzu+^kR%|jAIO83~VfJEMu%;tZd9+%xz3>Okz}SRAba()NHh1v~4tRG-4EB z6m67mlw#y=ElSw2f&G)6S+9OlzB#H!Wg%-1Lm;4b#h}A54FnzBhehy1;a`>3-8KrqfO5 zn2sHrtZ3=G+Vk%%NZK`joV#;pHV@hF4Y*JuSZPIVjVzO?sV=`ef zY!Yq~W0GN#Y~op$3cz_Oz1@X}_ zdh-lq0jQk=QwK61G&ccL2WlJr`v3nwh=!@#E_;wA&$@~8rkXSFoiY~T?ChiBn|#}( zSOeVUcD&_KDsj}{lwGW+9|VnOfR>cHRM{*XR~*&zeT}qL7(HtE_ld2nC!ap zZ;<1fwW9IT8?Hnjnl?SGv^y;5gqdVkz4nc~WxTVK1qGtgMI2-+7suYIt*V}1a5gQj zfPOUpY;d*UfT5ZSxQ?(wD!LDZm)KkC9K@uSevoA zMOS~VvBA5Quf*0aiRI2&Fd^6Iih9B43;)74-1YP>yy+utcKkQn_rppGTh2|jEj^{t zVD)MK+~42acJKK8aara6FW>AQI=}w^bk^BDFLW=|z9}zgGdYm!ZXoH+V!JNvsO4(5 zCUqHUXXT@|40=Th2er=6?GvhK@E2RSj7M5*_gQ)2*IkTDzIn6OUf|$3b@n6=`|9Zx z8@7ejWKIw*yw`iB^!DA!**OP-^0z({Ok(1*X8T^z zjq>E>mUP)AcEe%%v88JiZl|pH`B#0Z--Blhy*Dh}|7UH&Zq;t(ZIh-w+N7_&wrhr2 zPM@QIe)BipckQ2JSI%^C$e24fO?!e?^{Xk*Ww$&CU?_dksb==l&ieaX>+B7md&&yG zhXxq^dgS~0U;KwX$6maxJ!E*=?tI&||EHVVc3d@?U3u~CUaLD%3x3~t_kbre^6%N) zhTDA!=EwX~jHYo^wRWGZi&@)M{Cb0Tc^EH?|3iVJ-WFzUZvEQsPF>XuF}7(3Bc3=k zg$Bnu2hOvWRn=6xqxgYgzK*+WoQ9LHq}kVi8%8>1v+ZYRM_H>~6J?%w`U<`;?AhfT4NLFk&rP|NbyoSv-3yNnuzXv1cJu3mlXbmzdrwaIzf)k_o-NB) z*KSz!+iIQYv)`-E9N4kE`flaoV+m%n3v0j6UlF}!lDu2#G$}EojOB&?J%S$0P z_vtJ%cmKJ;ys~nS`M(_}%-xS%HJ`NNvH6eUPv!@t7%e8(aa#P$6|!LaByEu-qGl0x z-@xKVtc}H_*&Y_3mV{auw17FjYrYOqvs>a?7zJIS(o?QF~B(@QO9PTXM0%e}`k&H02SxBFE~Bjv}I zZ=Zg&ls(U2#j3|?WgsDBwQ`!YmFQ+wtL^0mRyFUft+>=Ytd6UPT0LV-wyHQ(Xl1>p z!Rn=Cr`0j0NmjS(XIq_~wbbfH>;|j%mv>uj<~U*1!Ex1U{)0zW?DIcb#jIqozQD?9 z9rsqynm<$8x}{0g`meKrb@~o#>%`aY*1j)8tYh~lSOhxO0v6Rq8Y zW?MUEEwxtH+F<=?&Tea=bH}a4PhGKAnD@xqG~%Q6vStRG<##!3W*icsn-?`nHdjmvZO#`o*i1XwVPmy+qRj!7*)~Tlm)f*`TW|9; zb+=8^NaiJS8QV> zAK6|!^}#mUg2Ar5m&49DSI}<411UT8k1BS0EA{Q#g{|#QS-ac$>V?>u|46iJSzch* zui9W&T-jmwD14&bos+Zde%@GOm)pPIPWs0#yQy-=?Tq=a*oj|%XlL8%(6AJ9XXBjXufabax!oZuO zVXX*A>x3CJVa>qI4x*S@K|E%55CtY7N?;n`?Hf>g1k~0j0NvQc02*tn04-Vvvq0+! zK#Q0d!8cEVHr6pRfVMz{f@ML=k3pj*j0~Wa;L%_)&^quqFblLM7Brs1$N*Y809p&j z2p&I52g`!?Er7Q3FfxGlGJFDy-C$r~_zGr$)kzwVFxn+>;ACJ( z1hW=!GBBioSvxox7}CM41Dp&DSzy))P6mctFzW&*1499rb%PT!D#pn0fRlj%w2y(2 z;RPoHLnT=D11AGRJ(%@_lYyZL%mUrL1{zajWRTzj2M7Zrg8~-=1L!~%Mg|Qo1_n^e zn~}kQi-7^O#hj7Bf{TFxG~&m|;K0Se0J`~)k->wDfdRC-n~@=ai-7^O35Jm&f{TGc z5geuoTnr4L4W*0>6l;;>3c5pK=aDn3kH1@~~X1(BM zU=RSaK#?H~W=ZfcFo=O!20RQ5l39r`&;YXz@GvlFgIPCt7#Q@xtPea444~zWj0_yS3=AeVMb1)0O#E4A{v?v$O zWk=<5pmI4;xm;i_VwG9bkzda1-7w6Kf|M6fph zVk|Y-0V*jydU+&3Ymi;KkGDHIcytSTbUSHyfbOL0zW)CLWHBjp^_fSvvj^y28y4e} zuUS00Jrs~PkAuoQ2GHU!1ISvf_`|ZGG4EoKhe4?qvLgXBo();-2r7{U!OKTyF~GJF zBRuB`UP9+;47tL>qx%GO=^Fcv_n;-*)~Aa&j4y$xQ^;-$EOQ#w*^jtdC zK?~QIT{@M0y0?J0O@S7ngDOPO7AIctwm;aOM~~LGB_R-dmV)dl5rM2l1l_6@?-=J8 z6CW2Feb}SRf|g=>D^PT8#U5Q?b&!?EQlKL-L_NAoR16>i4_?9yTBCf} z_`nAb{*9YwfObE0vO`uMquiGR)&$xr3(6W$7lH0Tgm^QMfq}sR>`hQ(4B}1D5?G(^ zL!b*=xL^^aaoG5hXQu!t!My=_Spn?2i6CEe-vIeT)p5sl?EV1V?--+^-~qZUr}cIT z4>I4QyA!l$WXEGrJ_cQ7|;yVrmM3AEl8d!YnbfsH7X;8}4Sq!ej>!{O6?$Olr+f;K66G#}wO z49dx_#wUHO50r8^b{_cMh`=F2ZVc*W+0Faz-r!(l5L=NbBR}YlMyDr@VE}cT)1+b1D-2p6)odVE>uLhvS zuG%2g-2p7Emr4{SfCPwG{R)ai$m&;6I)jzYC|BWmCO~cl0;R?7Ko8&61F$ReT#Zlq zc5;CBFujL(*^9%Y`$CCi8u-d1k8UpxPeu-i43|guh0?c<-3JeYlC?)SlZQ2^Yr#;; z>(TAR;bF}RnwTw-M3Rt2k|-7M?Bww1X8a$(0Zua-hdn{32{AD2U~*t+0H;vUgf8f= zCKm9tErbO+%m>tsU;#J5AS~EEK1YuK2M+V|dUEi~J21fFw%d`THvqH>K9HmIbN8Xc zpe)7#+TaS=@Pc&Eg{Oe4@qfqe?~dIse7Y}rbl-IB{_om--?g`#(a~aKGY+KS5k8WoHmyQjsJPZu2|KZ#=9?05cpxhigC;Ir48i*80Ch-=o`Gz{O%G$bb?hmkuyv z2dG;Jk^r4V!eHqLDp|m7lh-UQ2TG(tGA1YKQc4=xfw zO~B9KlNMqb85kg}Iz|QtJFu+rfy2fJ4kN{)NAnv6pYA{b&+eC?>ief_>wyvr(1L$H z&u%VI(aPr0?OE`e$@nC!Ed_Eu*u5aQk?$q&=!T@@T4 zkG6_~w>@7fAsjQHqe?75(F5Q0vzhTFXb(F$?V+bTkWU-HNt1!UMd|4Ck zv~>KJ=wt>px1$e(7Oi-J+pM4jA^^HZTLE;fsDVdsr318cBJH@tmjKRE~RZ}Z@^z6jc`XMw{2SPLr-?f9l7a1#X7vxYQ5P`9_If@MJqiXgI} z9y6r*0=i~611t;b9YbV6B_l)@w8t zinceL3M*$hzrEbsu|ejXop7&JHn333w#28N~JAO{VEK!O}JPzaF) zwT4%KWkG|25LwWW2t*ci0@rG=ENJ)xA`80n5h4pZfomOD7Sx-9$byP8h%9K+Sr$0I z?T7D_1YLm&kp*qy-3Znz3qM^1G=NYGu2G;#qC^FL^p$}}Z={27_eUR40~mZrptR$T z96_vU;x$LJHKRxOXHXu?#jeDo`!l$!)LievR4nPZBM-YW>v)%9IhSr%2FIPCW|wETxMw%dJy4eo99RDjzrG7n7XXSX z=xz1TPG$UI&`wE6sF*S^Ftme1#R?u~pq>jv7Bqmn6)YPImjw*~Lu5hOZ3kEubTSnr zHbAE<`NKn$!vlV|H{^<6XlW(sxMM$d|9gN6K9AmRP~!IKX7lJ4cj@K;dBFhGM}u^- zKvg9J>NZX88>L|3>NSS zd1#U1(K{0q-_Rt|da_j7afg{O)`)q{3aX^UAo0_E8lFVJX&x<|90rZtLXs-z&=g1- z0gci^(g>(7!J0;}IcLh?a?a<$<(xnf`bCyQuur!TG>Uw?B|Tbi^S6jIg0JJ{Z{Y(o zF7dapf=^O$Q8DNi0NwKo+9nTbG6{75a_MF^zU10^fWKuu0|P_a1Q-7GjV)l+jT1me zE-@c!-dO@#Z&Tv@*@<6(rQtJwWMc)W6XM7($THcZ8?5ZJBfkL8q|f}3ouKI?gu(`p zLjH&opPl#xc^Y0jf@F9aK&n|lsvE(o`6D|~)xiwKVxLPl^Z!GR)))Ajg8%>j-wk%E z@umL@GQp$uWQmMNx1dkA69?#yX8{juM}cxKkM2MY59SMw z|1Ws7S6g@-KLA?od;-dY{vHKi2oOb_V zVqhqB@#*abO%-}{UkB}6w{UI!R$}bf?WyD2>-OKV+fl{%k|+Oq&>m{XZciSM?;jnz z0~H**JvBV}*I)2pzTm-p@intY_eE$3f;^ewXno1G`;ZI2+aZtMsSE6((d*Lvp!vu9 zQclP2v+doWIP&Oj29>m+*#%aQ?(44=JXk^eQZPrjxx$5^9L(hK=ynL;-xk0LDrgfx zor4UQZXXqk?sFd93?BUJPj%L)7=TiPiAOI_2PlCAI5z)eE#>s+Z3Wr>S_c%IJ`j%a zC68X;1)U)(8oNMA&4YjaaZrdsPS^A3bzK1Bd4RkT;L+=%V&c(#8WddyAYlbioOytw zyW2&@z{C1DsM(q;hPAw5E>SUYZGBRzW&Gd6ayKY-mGD5q9pv{?VVCX_6$j8|;RTM? z-}sx7|NZ|DI`9P)Qy_m@{TYuFuVfGf{tm0)JhMT7#L1~8&J>SM{3!l zoXUbcQsvS7#sGPW0vsqlpgQol2WZgdEU5ZJ9ab@j2e*|#^P3QtxiP?8fmQ`~|MKkq z;L&{(G!!W5xT6Pq0K?9i03EU*>A1sB6l*jkA(I?iWCQGcZ2TDTZhU9tLgPfCMDybhOjp zfCN?aO5kxLP~QXMY0!WuL>*|6!cnlgaCmzb973RSOAZ`6;IT}UTj-k)7J!5L|B3H6 zqz`~cCdC*S7&^hnBS3>OM+I#-(YO1jFDO7?d31jSji$0e#$Q2`HSCU%flkngNbHW# zQ!Bb_Km~4$N(LyDczwDLgN{Tv54IO{Tn=lm+uzrzpphT8Znun1R|b$lT>RVHKx2oF zoopbpIJ?{KfQFPn6EXiw%s}lVL;mg4Kq^`prIF57xM0X76 z1ne9Y4^U7CfQBGI=|282=l1~_Gms>P{Qlc-3jWTK?a$@ z6CH;^fgppVQUI)y%cI+~0!0~U-T~CffrKZdum+8IIKVo_p4}fkK_j%EKt2)m=yv7+ z&7x|1bPMkU?OOVO;k7vAylmr3jypgi9^EG(0v_EzKm{NB4$$#buXDj!-UoE(pN~p` zNB32bT?U@r=Ri@a0WN#M!QcG>G)W7xObm9`uA)b`ut)cK$Nv|)kG)of8192|I(PT6 z*SsFxEh-%h3=AHf;6W_VfyLc9pfk)$Kv7+zk^$*D9|nzcK#p<-b+-(`i5GO}v?Mq^ zf{y=z$Q}lrqzaY=xe%>nPym+KHcYiKod#VU`H+z z3IHV>0l)2=$@d1xs?;L(XX8}-F0xg2#7j#zO7j!n@7j$;u7jzEr=yXm1XA)QACoY}I8K6>0 z(y^Pb*YV#&=p4WG_r1E0=h3y_}#JV8SN{sJD|;T%5Q?5`!D#`tz0@qpYh0lm4w z2XtG3M{jX~2dKFLYL|dp5f;9!|M^=$oAo?e-}1Li0p&{2gbL`qEstLB3XuOm&1tY- zKu4S^KrQg-ER)~|pOV_i1{LBLbW`x?bQ9ng^fU0WOtUC)^|4ITC}s1pOfx941kG1= z?gI@qx`K+eb)ZFG#+Q6Lmw{HYc|dPR08K6kBZ>zJ;{&fvK*0g>su0Mlki+)GJ-SaL zA5{jf6QE_;j&J`#jh7TqQ39$px^qBfScysnxKN3AjEg_)3eQ2HAvZ|=ng^d<2dzAt z2R@=&l#ziU2VAazR^?=aS!#?73|U}S79#@#q|^tk(+ZGgU|?bBWMp6n1hdXCGB6l| zS)hZiA-S0!)bs}H1(o>`;KB+#lkCy!RRCUKaSI&IAm7S(bh}G{%5Qa#Zjg7u)jX)m ziTC*b09LLb`x~L)uoMFWLkqYP1|1O&u^TkZ2ss1|VXbHPP2>N*-6woN)4{h(m_0hJ zeOphKVjL0$ns#~Zk?fe!{DZYT4LVQc*xdulVGN$kC;mV5?Di_~=|1GyeG%+|*9zzY z{AdCm-6tRmX?auQ`A&{Ye8Y*I~d0K&vK}R)Inv)SQu4WME)s z0M*F%!65)zNp=s+0v#WJ7t8{!tds|<1J&0M7ARrLg2j%2cBz0_pd=~>W`T~ClL50Z zTS48PSX)64z*d0+8&p6+YHe5nrQq59(YN~pcm(w&C^2AZ1%XzR5Z4NNTguw&`uBA@ zsI9}+?V91($qp)SxIMaE3;4Hrm^gOwfehmW@w+_?K)nAY;Dh>&KtlZ60!*mf4g&4^ zg|>qri#o7RwikGGPXr|xk4^#5tR(@Zffi78xLQI9pkg1is1{u8gQ|Q`PJ$QvnxJAI zG)VjZ!fR1T76P>;Pq_3tRY15N-N=PLXmKX^s4;#)W>8s=UdHnaIwyE^Iv0S-D^R2E zxN`=m2nS_aiEhwZ4QNTO80==3)5 z==8P#m&~BO)E=Oc8FY>~$VO0p0M#-co!K59oy7q@oy8F#?+Stoj_z;)A5d-A&Hq{y zW+UVZA87UkHRB>2K|>J?psu9@s77!A2^v7_4N!&Q0WPXTR05!t2q-mcfCy+073BHW z1Es9J1>atafE)!{cHq$oDz=Tm=f;B^gmSDYo`Spkm``tvN`hy1w18)KwuEPQv4UrJ zwT5T+W5+n8rXZro1|0;u7+g?;Hk3mOYS7YONI~rgFQ}71Th75v2v9+70%n00INt>q z&!Cf^Aw}e6kUB{53{PQUkZG_4aNGI^dLvR3)QBV@MS*TTAtpt^Q_}wuiEe&Gn*k*y z{fEVCXEh`xRU@UOYP6J8ZQ#*aZ2>9;1U*2f?sxkMcyxzx_;jmqe9-Y+%ATOwaz0i7~#1MRHDX1x+ z4$Fw3><>wSl8`(N?J=UX4xs5w!LvIXlD^{O4ucLifTRP^PynP+u#$m+0g}Q%bKH;= z23l4RNnuRz6sE<rx9s)9i2uLnG#;L6Cra1Wfq5*Qg69)Vf;j0_A9!K_9`1_ns_ zo6N|-&1!DuAW-c>3rSE*2eoH~TuQnce}fj2KApv&V$!Fx7+y^JbQYr& zlRllr20op|;Eolv8uiE0vpNPVvY-W7r;ADfw0!GzQHcN*n-QSG(g0MP^@46q>kLsz z0EuWoOIC1^2r5)FU`By@XP`yK;2S19K zTk!t^xWDr{0cT)O8E^#4qS209usTpa7cY=?oBf z&F;}T0kl;HR62v2zaE|51>oY;r*{r`afD}gG_=?(HUL%4;0_gNxiX~e1dSd-%1%(z z8j@l`=PyIbPS7M2r0fKBtsvbf&|Q2_z-3}2BLl-@Fsle&Ixd8liR%$%V(X<6Wx_FV%m{9pfv>tu=w|Zl zzVV4)0Mw#p0OgKuCzehZ6_su$4p2*>+ldEsJ&OTo%Bj;uMW@?I1C*o*0aRA+l6aX5_=W^_I_~+4lBq91RXeLDgy2#d}*GI(xob_4{ zlyZ1<`=}^@I;LQCpq*5r;3IoM%R1!1K?G_IBab*bhB<<}^t2e}PC*;y=Im~pAjQCd z80DS<=COlX-H=i4?r9(et(QvFA%ols6F|v@(ot^E$}wo~uJsaZeKgVt_B)U6&Ifi3 z44`oyF37rKP?#iiGkNqzYQQ2D9MYhe_3ggU?Zo2KeHawKp4~qYQ4Eb)&+bd$c!kF} zB&tEti8HEQR3HUt>w!|(fzzNBSqYFmI*?cdt&gh%&p|nW2GSKgx<7#yhI95hff9y7 z++k2T3_7RZ2eggiWGSCdcZrGz=s5T0BMNa~j~RIMMizkj_u$!62k@LbXgJ!(aqx-aH_djTAE0IN9W!HTG)H6Np!TbX%S^5@g zB4~A8ay-`AC6DfI3n>N$SK|Z5m;O%%S>Jk~#Ikwu20I3Zf1qLf?$bV~LjpQI3JNvP?tkEcX-CjR0O-;?&@Kj#PCL+?C-|)SxWk}( zu7bej1E{2M1+zf+_d!^o0eZ;n5~$3u2M^JJ`fQL{D9~C_NQUxy0yLUp zWtpW=V(Qaf1iA~Z1lwJo=Mck(zTL?85dJ@AUCvXq1{Bi@miYo@0cjJuk9r(@z~;g1 zu25p#Tw%dbV(QUdU;yffV4CdFjbt+FJ%j&`TIUNCZECJJU;xb@d4K}f0c2La1}MW~ zGYfRbDfCJ-)L9hA=KY|C7ek4IXLo%CNIQ5qN6w?$89ZnRxp(nAD5^m7R2q))(MZFF z$k!7hD>+;Z9~kX~Pc48ZZy|L%Xd^CU2vHGUE9=8+WzfxJ5OtujIDSYnKqxp@6NB61YE-Ik)`V20eJ}L^vmt4C~xEddT`Naaf zK~WZL8mQc1291IBIyrzkzRdp*9PaiI=yu@gW%2lb05nNhiX>mc3772kU<8FD|N4X7 zUZ5?B-#;FP9Q+R}n4!o2g9dTmf!2}ec6;e~{y)*}1fOX3;xWDiUQFSo;K{%Kga`9M z$8IkT59WiOos1wifks1oyDx&T6u1Q%A?AjJS9gpG$6>hfw@Y5SbUX2MdvSo)-teyn z4NikrXn3m_U-IBzf5NBxgk!g}4%l>W1<&svJo(q3gz&(2^?EXTSRV$pTsS=W-A@`H z@a*IQ`EGdQCl*)ZOU+iSuHA<`jyr)vwN$_LKuI)cPYe7GP>*gW1IO+Yj{gt1 zbc%F%bURsqXcucImZB%G4O$PB=zx`DQJ?10DdGXr0A_%7Jo0Ehe}^s<})g(r(^X8?!e|AQ{pUOYwjL2FGcg9OSfAe&wsyH9u=e8}d( z?4D% zWi?bvGq`kL^68G$DCO`>KH%6X(tMbIn~REuXYxhYPLatT-JTpBEZ~_%1D8&g35R=G zIJ+-id}Vy0mxZ(OQD|srsPTpFQwLu%^|FBO)nPC`0cA8kGzNha-50wL9(=%Z@D+>k zg@X@S8XxiV^Ya^D>^{_e>fj5GgAX~34<3BPq57ZUKZEh1g9ii-8y|2rKA8r(+L#MW zGL|xLU_6}G&SZ4puq$|%h712jE|5G#21&vPB;muq5hCG%B;&%r0i?Ps0e%pH2?(;dqC8D;6Dh` z@xg=tAXpDSFQ-d4r)&2?7v)2a-3K8q2JhcO$Z+G7(MFR2RljK;_%{k3hA#JjGCUAM z8=yk`8wERER3yNzlYl4(s{)ss3Lf2{?a((t=yY~y_~~D2 z>e1~CDlAG&Jgl7qie(V8uZ=vqJt3EFcb{nf@xS!K@n+Ci8)z{Hcu$!^^kI)~ZzL5Z z_Zohtmp+D>C$!0KY%KH4gU2P zT#YaJBp>rhzU5*OEXCi#2_7>z>Dl~)g}(>X?(ph00ZnQ$cxZq3>eZPG+HmLF>+#1! z`@QGEmrNeL;Vhp25Bl_$vH10hfcCUN7NmJ}J1cnf@`!!rkMQC6!>@7hGk=8BpU?b) zAR@wB03`pJKZ1t?BE|HXUy$iDKUBh#`4~t}03zqa0@B6;m-S#iiY&_klI8GdJ|qBE z;o1C>v9!_G`T~D%JQD+hU#|?<^*-A7y?S}3`)VHo+3dl5@Us(t1djy72FB0)f{Y%` z2O)NNHossjz4=m+iGhJX{E}n$tJZJ)eV|@;^FIOpCQxI`r@L68*YCea@_@b3sST`XoQ|>*blNz`(!&ii!`QIa%%h z9@^JEdi@1_w9k8Je+O;p6L9?C(aXZud{n@RUx3HJtCz*nv-yw!f5a*Nh!gyRo*(!H z7{M|gy)0ZXQBHmVM~HgQUKUQ6sHXtGfTIFfFq zs3`a(Uj+v?=mZtd?gJjphZQ`v5Alaz@&KnG&{bQ$-6udU2lbkLwNJqkk!SNu7EtQ( z(EbiiNZRkcdPOEd(~^I$NS8-9Gb9BGxN!XW%pY+Gk%T0$4F5;egBmB^e|MM4AC7m#Mzi7ivwgB^72!N)cd@fI0x4RKTg^ z9;k7v0p1%5YJ-9rh~Rt%Iv%7AoG(G0S_RNJmIP>!iUV>S0A!(T^P7Z*DoF;%?vJ1$ zBG3?kPxmK&%@WWZH81%!Yg8P3k}W_|ppgKNWQ}g7PRKQkEy2~H_KMm0xa_nyX!N$PAukqcp`%3c2%4@?nBdsF_YLTvv*3moXo;ZBP;lw7TsIdCvA)JQt+0G(hA9~T52&kR|B47w7v<2VZggG=`m70?=q?z1l4J&+q? zkMXZR=g7bQkR$U+N9Oa^C+b8&Z4~XZF8u4yxiX)3>4d0rMA2DEoj)MsQIH60ra` zqhMQ!J-fpcJiA%`e0JoIFcAV(Sc3c-pwf_K#%F#(mKh*H zUS~!he%HgjwT!ODx2@0l@VnmNcm3;XeA4=ycQ?x&7k<~1AftpqMmh0Eurz>{j&R&Q-};;*zw3Jse%BwaAdU;a>rYTY=h^(0k-v2>c)9Hw z=*_?`Di%K7E-D(Ju_psqlzUnq;O~tEEy@NZ@`I0zt>d5hBUomF62MG| zTR<@mj(L99!!EtCjIPF)z|nreh2QlqIGjOo?%mCE1!R^K)GQYPPz`q+RQ0hmf+MXF z67Qfm_w03I1a)`#T~9dnR>Py-5gbP#5rM&QVcd0=g{FM+G_dYm>pn8!YD0f*aHd!3b`U zS4fKb72Pf10@=5BDX4ID)IQh=5okTY-(T_T|9?=q4l43pI^jipC#)iwqXKFl@N1N) z1n_IrfNvvA04FQX}$b_u^FVc*O8;M86*Iz4vs-@)h^}ruv~Az0UFu^ zEedR|XW{5RTF2vI*$x(*df+u1=v1I72SB5Ipxrkv-Tk1F-lh90sO@Ut(!CjEwqy6X zi!YsfSJl@BxR5@r{EIIUE}wgL-K$#y1@g9u{zH{>fOU?a{p*^YCvi@ZZ zXvYxLI~PIqxRmw5ntP6yU%7OGP4npX=iuKaBB*@`=C6Y7`tVr~rEzuN(6Z2WLzw$FZlx0^>N_$e*wympn)k+SMsP! z_a~p^hmdNIjSLMNqRxgweT|McgC#1i!{1P@UKhsuTMgs-+l8nE5qMA*l>u zWO3|1q#++d)RQV;Bim#^cz_VuaH~7v`&;%?AZMn_n~Xx4MJ+ zY27s{0lwBBYS>;ox4x~j_wMCU<6nQxJC|Md(Ej@F0kxm~(%v>xE^I|(|8v%7-B)%c`q_eIAB8%r+!c2Gn? zwC8ztH-Jh&r*57OP$AgM!U-y2X)`wVn!~f(LBpw=rNb8Fk7C8{6X3!s&9yVc!@#q< zg2R!28;|AXN6d%73X1MK{y*gT|Ddb&W&R#V=nh5Lxkx_A2R*uPIYO6I`GUrfFL@sP z$qYK_rVn%shvN=V|HrlamW%cQly@hntq1Caj1M%}2>dVN`hVEt{{c_y zLp2{fI@hQ`);nxb0iBNO)44Pd1SLHN zP>tmVN|=~67Bq=N^ni}H1GQ#A*Sv$x?1tFk(G9WFqZ{gm?jErBJrLXJK;aV~eb^)U zbAyeTLXn2!4tMMpf@VrITtHn^&}FvNa`k(v_YfUSd#WZP|FL{f&z_wbAW;nmb?!h5&#EnDVtNLh{kJCr%sUtpwM*f zU||8xJQ+B3>MU^N-^T9w|A_UClA!Jz9@fX|y!V5KzO;Wd*UWJMr-sV_luc9G!v5_DuKq`m}gVSrp}Zx3&}fKKU&0ju+dH(mb0Wm%aR7~;XQptE@( zrm!%>nmydi3=BzNSpjAS28gUfC}=yiN8=mNcm--E2c<^PNCxPz5zriNh>C`9_sdQn z6&9cFTb(*8-4}h6Ke+VzD!8^@;_o}h%)sE2+@bJI+j>`31cGfS5c&;4UvnfCWAK?@(51(~POun zDlDM3o`lE$W1vDxz~lc>ShRXtf8+1@{SVZstWi$4B$) z23Q95Xg=KF*?dHyQ%A+K`F#T@*MPGOB9r)7Kj81V`vLGAb77f=v-p{R%y`Egtx}uEDKrq`#?F&=%lOhe^=vg{M*=D zPL{~q{4dpZ+%Xw@x%^td=0AT6XcdGF7k^tLXw0yjv$8H|B?u!>+k=A+y)h-V~ zGpnG!`vsJl)tBH(=fuHB93I`xp!R9wLvXX|)WL%Sj-5R0p3TQu__ujWzh*Kz`Tv;5 z|Kpy`$5^b3S@?TQK{axBjY@`3?_yBn#Ic)2#rj2wtmBU5*!=`j;L%;8Qt%RVzK2im zLXa+3)kXXya-up6qS8A{kd2E1nOc4l!k z{_m6g0_4XWaH8<({>Crw!oUDlrs3Gl#=p&m-J#+C|57d7DT1GWn~i?2`&XOK{OzC< zzCnQlI-XSfm{T{80qH>lnr?pu9?e0XZg=Uv)qMzQH0Kcq$QSStom-$0o#r2mbrv4o zn?V5rsz%_e8C<##I9`0=sC~e_mq)-e`J{`bOrI;i`zc3$_XDoo7n^@D*582!lxOlM z7fYE9pa$(x?8yZjIO2|={iWTYk)TeNjUL_391a~eY_GXJx|=~_E}b?Tz)1?Uj3~qL z|8WmcW91l&r}bO@o_XNHJw_z~)Db`)3XEcaH6lPa9i)OYc^Z7_4Cp*_NHfNik%1ut zEDPFJ0cm1@8YP+F?gVJ&!WFz-Cx;1UUNI8`1H`-v__-0W@Fq(NGXq05*t|@5lf|d| zPH3=Wh-0W@xMy$nN{_}jpe_V*+r?Gh)%b}|@~`gDdeEY@PT%Q1-3J}_f@V2EO_g;l z3=E(71wdzwGjyNw>?{`W?5vjXO#TT?s=l4Y3Z0>oJ$g;{f|?rq;Rl?1S#+!qx%Zl= z@C&-mcjWgw3!cq>>DhgiU(j_uzo6@WenHps{DQ9c`2}6y^9#EE=NEM4_ig>p-`C2- zz~J4>Qt#U<;|NZ|j{N(Wn;)<>|6t>91(mnPCq0^fvGDh-V_;zL>t?y^+w1$+tCxih zHdXVPU%=zXXMO?bG_}jw`Jl<}vx`9$H;66h17Zt!Kxe;Q&MpVbuLjF6hm1D3oL&E! zKO*GpX3#Q=v#UWyvUoHf5CE%L51E~IIlCRKarb9_L5NDQh@j8e-QckS>y!LF1)zLw zeX2(2? zScDJAKZ0OsL7%hRA$~YfvjJqY(97H417_dy_kl*QKsG|%-v~P2hymn&n3W)Vpbmcg z610~OG~wI|S~LlDa4JYU%)x1(CLGkgi$Tr-wP}1nK`!V5jwc4NxtI|V;d2%g@-EOg zf`&QNz1*+$TwCAPX@YFR?odc_SPXNhCunfuu&?!X{+^z{pgP}mx>GL;=xjm;-{gBf z$tPfqSI{~P|6Y~|kdHxaII#O&dO?u}T9f12-yPcDcnI8tYeH_q{gQ8d1a86|f;HiK z7pFOP27tUvs>bCYji4qRsTxH<8v zL#KQ8vT*zKvN(a-aij(X$Vs5qG`JmivSv@W?__AX(j8jg{8GTD+jY8Q^AQ1$=HmjL zzLPzgk2$zlKjQD1`WqBYTfhSwpe7k;5CYUB1GR%Zx_#x5nqi>w&M_V|O%ok^7<2_N zq$LKr;1|*o1D$XPX^CxRfwjqg!`oy&-FG0XcnUzP01~~`E5D}qicc>5pj#;z7!LkmI{1${jX&?FXJMP!`V|8MgKuZDfM;(7 zi*IMKgyTNYfP-&mwF0Q|#;+MX!&CbO$V_mD(53qUfB1pVj{I83KRfbA9{9{3cbq@! z1HT63SWf=%lb`sbPJpLH1%34YfLjav;U^vW!w-Tqo%_Te1=1aP0@T0I|I8nE9%RfZ z{-}>wjBwwD$M{hj`$b84<<1CKN$3WdFh>9Pc&2Jd_TND5P|L?-D5u&01 zy5;~>D1pX>K;aE)ErS+hfyRMALszZe>iqYEt`tg}0P+tp-hr%+1^L9IHyY%O1I@=l zOGljlcr?FbEZqT`iAw;@#09wUYq+R5xbSQEsDL)p7l77Ofldgr1h=z6*R&YdfYum- z&K126t{y>7TL-RWL2I?Pfmxs;4kGr2fq?-cmd?n)01>NVgo%OM=%6iP;2R1OKt~K* z{O!^Gsr5F0AIR&VR1)xsUjTHzCIcv~IJod@x~Ld{QU-XlM;d?LZx?<|9~A|V5NLRV z;WPiU|F8;-Km5Qaey!u5_#=*e;*UH2i9hn#Cs5SG;uaJkpZOzC@<$y6i64ZC|340q z5#;j#wV;33Yy*dg0yuOGKmp|d3bFuDf3*PArU4xokr00vbT&UfIHYVD7#Os`EYQW* zieMHf%phR_TGtF=fkFUzQWCl)#Gw15Z}(5f?jNARTCOL`>XRIsf3VfZH2-5N zQFPofTLkOsWY7qL8WRIUcaBO0=%lU!aH9|2>T~Tr*O>zvFg@OV-J=^aD+Eph&t852 zEt^lyQK_*0&hPWjC;2*PFL~cBu&9elfvfSi&(Qqn106l#muGP8zUSHf&lB8?0Ijq2 z?e+QtTI}T68!q7a|Dap140u}7tJelhPw?n=`T#o3j|Eg29RxK3R06tvR00V1)zd&n zK7dCk1jy0s&E(My%5>yuW&xeKPp-SY**uyLDR?v=<^Zk5;&1)&57cx2RT~JJzB%r~ ze8aPwn#E2bYFP08?>Kb z1vsaJj$enA{GgPKt*h_=ay5Qpecm(qg=6;>exIK{$=^XO z=2xIo2fOz`7uS67NnQhXg^xC<6$~z2pj+DcbqoS4=*=&Mcn)5Bc=^b9nWNfR|Fk-4AN*If7c3pj-f%oapWW&ruMO z7r-M{j{E|Sphgdw2K)oLfblbb1bO;7K-wKagMj4e_h5odB_81LY<|Va-}(leD=yc% zf94lpnNFs&I(dRYBU#p$Yj%1xALH=I7069yndx{FE*78WcQm3MmlYRM>oq2$i|soo(ynB08c~z4|`hQt+@v}I}KC;!wv@mHG|^gVh@8_KMTP* zq>F)pVL3QoJc5r+onwL>{R7G&9H6l=&=PZSYVzp*<(quNqx+_-@c~Hc0gYt&^agQw z_6Gm)0H+{N77tJgf-J-b7v`|q4HN-RuoUE?BGK)mB0*Hz@dPct69BJM2E`poTEVFX zw1kLMot$9j!qN*#b}~Vhza8Z80H+id(A@{thijd|WoA1xsSt0XQzwtJ=l=t~)`x5M zf@*LM(13)%VLo`2fsUVoOxii_2Hgv7eA2f!h{X}KS5y(~h;myA!4qQceq0LU1qA+G$+hmBA6vIw~tA8>30&GmsS zI_>!XpmQ$^gYgN+POm?XjXyy>dH$Y=|Ns9V2CZHV0;fcm?t}c>4tXAYA>m_u;NU|J z=*D&@W1fcYgNKC~7#JYx974h48K8}*jc-7+A0EA+g*csa!Q-!;TS4ozJUaJ+Wnf3# z9`)#St?)>01&zsibpQ0}{@~FW8sK^G8LJQT#v7pGFZrcUcR#3-@J;^V!oR)65wu3j zk$;;TuSezy4-U|(-FA@4X^zc5n9^K2U0HlOO9ecVO&DQaMMr+uqmGtr-4`7Box~h1 z-B=v?o!nTo4;f!_{9eZ4ksQwAk$j`O9drg+XDmafYlKTL3+SRf2GGHMDvrG#9Q^A~ zX&;!9iZ46oQw*HXE!3WG9y&n7?)+frjJa&Rspa1{= zpWmbT;RlayNQ=-z`=L*7h>C+xXQ+f{FV7{9)=PDwpvAHWCU|!89P(s70h&p%K2-D3 zr+X==*W}xM-lwxx!L{{&ovBZEX@K!q!__xQhcyu50=ycUcb8P<2#NV30!oc9z{GXA(C5(lE!T2O- zZbjJmfK!8w0z(Pc|AXKu>w`7#9j(vvw}`ScFm$^zxEen(zU|ojpNYSfk%@u9r8^dM zwGDsYZ_t!kx37S!@i*6QM-I>?sBS_2^{1InbsxO=v-tsIr!PyVD~E6E6aLokpwlq< zw>k29WPSieU3WWpArJF*Py~Wj9=U@yKX;jGCLKh%G?CZV&UVy~w(cK0bee_5^)h*J|{Odn|%MVrt2A^(Ua9D$a#3T8V zXSbsO=#HCimJ6PpEPRka5j6hhVOgrd-v`PI{PLjm%;0E!o4*&-8u0{eI_F{I-^OC- z*~@dpv)fU^hkrkhN1AKv$%;sqPSCQh?(?3R7eQg?(wU=T06qSJc{(Tn zK`UZ4JbGO@Kn%!9Nub4mp=eHoI_R|&$lbl(jE?;4PkDkTf*^QhYoFqK>_ltO> zwO*Brg{B0MGkX*uDd9D< zWA|ZDIpg{Nh-dR34*s4SzyJS#`G^U$9sI3NXRZg*td6VkC6~@n4p4`D16aX-pUzqb z-`2PMt;(S3k#FFIMWAUy$hr`x1{*a7{?^x!^Z_dGZ-9IXD>UYV(mbfppjrBGh87vl zpcRY66&aUdMO60%@H_@Dbix9u$N(Mu<=O2f;L+_X;nVG_;E{aEqnqa=G=cDY{6FSt z{gJ=t-@pI=UAjX#z{PiWEQ3e4s|M)g_fP{5P^JUj1MFgbnZFlwU9hY10iRCS1aSC) zg3_bgH^8IYRl=wHq=)uFk51nV50FKjt`^RqBA&k$bf=?NHw%YXH;)DXHWopTULMf3 z$PAv{ZW5l%XJM(0zhfZ-1A}9C97p$O-)>Ex%wM1gaO7YA$%lDADEJ+j&0LMYrMb4= z=5Gac*}D(Vbo_4b(*2kIrnD?q^xsxdvAk8^<1Cn%76!vFsN@A>~IC_#HR|K;HCG58BA2_qm40FQ3p z3J+t*HXn~}NOPwf(%k8WGfxH6)ycg$@e9Wc$w5#?x zPtbVEdDzGZB4|o_U75`tnay3SKi6DB4wIMf{{8=3H7JgP}8WRHp zWTERdP_Yf}SoAY9Fm!@hl`JsXmn;knkQMN=Sz)p>*@Zo-9Bv0#FC!;xnmn45 zfuS8N<_J31!U41dv%t0cCTQRA#osQSF`zkP76;Jyhb#X!7X~Ir{%tM-f@#fr96=|! zFq8@-)+d9vVLCX*9R{sOs&E9aRswZ;EIfL_Q(mBrnFhvhK|=uoKAA6U*cccb`S&yW zFs}qH0Rt7139kIxrkb!ZFt~vBZCbWkfOc~AC4ly_be5=Sc=k?p2i<lk$*pnXIkq4{tnP!mMj1EJ-(p(xItT~__wt}&3)j~S)&r*X#JkQ z!qcU!ZOeOGB?K zBWSdkrOziBG>9z7(&uY^oWB=zB!Xx6aZl|79-z6M9#DSr>eKb&awNXtM%y`W>@Xgj@GArdclLE-N*TT4!ba)bL!<0;NJ!s zuohrx`^+B+>K_U^{Qzm;bk#oX(ko*5n#HA8MADIe+bI|3b70r?ffW7$?K1rXQpf>P z$b9`ZvkUWObYGvopUSOB=YycD&}K3uwtPIDvsKn@9ldyNht_e(&0S#qs|EkN=0j$q2OM zBf`}hl74y~{r&&nr5kj>FC-m;wsmrVRv1fwdVUI^p)U>4t~CKi>(~6fpfyE~-KTw% zPidcR{wYvY2I^*tbn_p(#Nd&9wecZz7wgI9lOI4!9S&f{%2 zc+h-Qz@yi}0Tc}Uk%#yN9TxBlIBbA0H}DI19N>>U#V_b_fM4SjzktI9{>T&jf*uz< ztS{Ca^hiGIn|#>Q`b2Fm!~vkar`iWW>7>TRqt}rM{gbS(pzwG+;AWP-_d!C9co7$7=8MF9F3H)t)YgHQKI zP;7d*YG3e3e&yJGXr^QLr-T3G9lLL8-)(;O@B8on$6SqXGk@rYWWYn<49LI!v`hB| z7voDV+Lv6kFS=Tvcjfo{3ChpCK9CG}qSuuXv~XX+rx!GL!Qhj8!Z-OpIH~u5bKyF$ zm)H4PpXcudtr!JoL2%IW_kd<)LD{c2jKfp=rB|;=7fKf7SvkR#`Ick%X&39;HOwyB zx2;ciALI8q?#g`2sh5WVmiJmfdEf^qOZ@;T)j`S7#aNQ-KT&UDg5i*XMh+9{OjH4fEY3S>)jWC7!myI z-IstEA^hvzSAZA+{OjG)khi7#{rV-M4@kF8u4=cYqiU{OjHKfEYIX>)j84 z7#95N-H(76Cj9H&Pk)kJa7#jTR-LHTcD*Wr+Z-5vI{OjHCfEY6T z>)juK7!v&J-JgIMBK+&!Uw{|_{OjG{fEYad>)k(q7##fT-M=v3==NjbU+@2e`DVBO zAO7`x%r`-?Y2gUEfd+I5QFo1s$K~Ik6Ln(1^L+}Aafd-8CZI0$fMHOD201OqhJk_M zH28!{(0l}BQBMd10|Vp;Z_xF0D-{_Sm>HHZGBBJ3vsN%NFq{Ch)-W#N2eUw%xb}fr zR~Q)>_JUcUVXr-47U-7J-C))OMh1pmVAd1R&81-03q}Tp9bncQMh1rMU>2yYvJK4o z!pOj|70mhpY6OB=pq~0>Fbi}I$0jff)QjH;W^phvFl+#`c$gR%)`M9BObiU`z$_6a z28Oj@mIM<6!x}J4hKYe;HJGKq#K5o$%mS6rNVyVp&IaTvj33>vx<5iQ;{@!P(bf1; zuNx#kzQ>*)eUig#Jd&Lod_Wg3CHq%+Bs;f&_EPyUU-U@!FY!or?m%!0Jd&Mz5ZoM( zWakM8ZiYv)^ArR(#Ut5y27;U5k?cGN!Hw}qc3yzsMtCGUFF|laJd&MPAh-b@$7c|)F{lkNQ{Y4+{@;VLZ@5Z{26Xr3$Ey zaPYkR%dz{A2Y3_bfR`ko8;u|(325a4q$B~|R17Ie{NN=?C<6n-DR54R09`NzX2mcu zFuVk_K;4fQU{(qf1H*GLD}#xF;Tf0(T2S~D%qn1FV0Z#%l`t_dJO;BsOA8-?Sv5=y z3=hGq1||lE2VhnU69dD2Fbi}^+C4Cn82AMnLDPc#f{mc%H~fN) zpwSY3!A8(hb$-D{(7-JdMj22hHj|B73`p8z0&4bUFg!?4>E2iC<42uR|tXz zN!tqq85sDt&(9EKVDQvF$=_-W8k@Mp-wHb7y!$k4hws6c9H0)%!50=D#wXGaK8ErP z(v0VLfm#;)+fMLrKkRYv1qX_}1_5~m0`d|B|h5a@k7l&*!kPJkm|!0X#4cg{|DfcJP&{kKm-rW5J;dIU<41UyaoYz1p@LC z1mpz>$aCP7=Z6FhD72sv3v>DLc2EKW#r;dru%qL_m&~4*UpRIj)IRk8#AtYJJAf8m zE}i|L088rxXEo6Mbw-Xmu4B)rjyp=ROElN%IFt$?XMS7IhIKz3aG?wuXm0)?P^9S5 z-DV&FnwEXxaqz8y$H4~z9-Uy3UPpnKpn+$fPRM=dj(b3B*&X+TN(2|}pRU@^U0eT` z@;mOB47MM6H`MW-+6P^`kN8+0D9`oio(9t8+{>cq*nP(1 z@>{5hCp^Ai^-zB0arv!d_Zi2_&p;EM-0wUtKl11VYv^>-NwdCNqO_v{JUW%4QUTgb zWdUFC+I+;pF)ltXHu^CCcK3)*_XOhuuW#%){r~^}*B7C;slGmo`?euaS|!*` zvowQ|fniD|sIvs>`aoEqP78zu>TW<-ppF8BWx)i~5ejEz!&&W2Fq8Y37#QMIK_-J5 z7!bXnrU8WY0j>kI^%Ejyzzj1v0M06av%27{?aVMc_AxUsq=W5v0G9=AmWP<9zydSR z0nP$VmqTP5;9?8ltX*){Ef!e#J!D~ESO^ZkXDqNys-NMqzu>a};j+A}3=D1HSSV&? zU|0**+ri4fumQ~KXN8$Og%xJrLAVak;5THWS_Ww19PGCoHU@@NFsp!#fguIVDq&+_ zNCvYi*ccd+z^ocJ28Kj1tAUMyApy*4VPjy32eUfZ7#Q-wtR6N7hCDE90viKEE|@ii zje#Kt%$mW*z>p1Q&0%9;$O5xKC#+t*a4G}`^Jn^=u-_knhmfo2n66Zek%Q;$1>&MJZ2e}KMF%=p0T zC!kIT#9t*03=H?cDFrl@ybA1BNQ)J;!w9k>2NZP3P1hFiDFZ&;Yg7Ul85kUl{yTzw zexMU{b42&e&JYz3kmoI6OT<7;KbP(cu$8``GSq{afuY+)#l?Yt+j)n7#~nLedO>^# z2dlqD7eH6i8aeLx3@*39tLCA*kUn`RH2+{l3YG(&2VYow9DKkE4;EH%ux{iB5b4%JEU9_LI zenYQ#g_{52t9QH4d3OKzxcnYmJ345c^Z0(hRfGhvDZw^NPK^y1;Km>7TcYx0D03{oTmbWEFpqVAaF%u5_ z+m3_sK!79YZs*gWU1H#r+mhN-V)}qJd7arYbJ-%P?P(B1^pYyo< z$g!8l0GdT!US?om02hBQ-QOIwf4FFWC!_H5fHbaQ?O<>Lc-2FBH7MVDTz-q3(?Pqt zQFHn^Z~+K9D#)iBTmXV63u;s<5MBV6AC6ilJ-%P?R6gJV>cpM9{M51gB==F!K%j!> zIGHWJl1o29Q=D==?#59iVFjAv)H35NCVUlLCupe&QvDAq{XzAAAsf8@ckC6>>OSjv{4l5i z<9YctEb)7OzwE1g!}Id%?z5L6wcUM03&5vy4tNtuXRwZA!*53Zb_F&D2LE1_b3=F7E0Ty^uz@z(u$H7UxqK?)_`I|1XfZ_*oepdGj&~72n zAg!bJ+wLvk6GmX|UytqMBdHVUKUZehWU0$5T7-F6RZpdNg015Kk2 z4gdK;4OMO?Xh{u?hM(k?)yE(q1{nnbm(@5Lt9O{ON5c{D0#VdxK*<5%qWZe0@(&MC z9x2m2!CrZ zD3zf%`Q<@-+y;4wflkfE))+_zFBV5lVEFPmp%4RA7Y;7`+fIRw&9rdoJ_>4J8+de| z?sV4ao&w%-*I5GE(U$|g-u2}I1_lP$)KaEz>l0ALCTxqfa&!c*4&g3$a_l~9eBl3q zi!V!mF++}(cLlZWQ&bEbnU8^N)^Oq9cD%DhMWOquNB8OG9|~pcptd{1Yf}iN2X%`J z|F-iGCwCtO=>zo*z%E90Wb1(vTbFJ~an*g)w^yabvH1sMiIU_06Ad*g0SvIBjK3xA z_y7OhYrqEwzKr?}ZS#P76QD&m37|D;8KAk;0?>|ca7EjEqyo~Th(4?aZ-MB-+bl-# zHj4{f7IY!eEpW>Rv;i5??f|urAZ?LC1_p+^VA(RbUeFX6L>AO)f^<4S`)eS27s5>j zO^!iiLG34qENIabqzwdG(Fkb+fw~`%Hjo#*O9EQ>4AD^mZv*{;vq0Ssh~7+imjpDa z3UO5_yh}0<-X*DJhILLrg(F1o2WD7T=sPoP1nv*~3>Q@vn2rr_7B_tKP98pbrv@Lr z(}s`Ug}_Je9>H0*Y_O5PKsK1Y5o|DfW7%M>$u>5ay~^wi3@gE%Cp~rshKb;5P-k`q z28b`c*cliuf@8*?9j138I|IW~u#Oq*3=B)atP|`E4D-M&(6}4Y@;dNpssLzaapd3j z(GgTZe)8zP$=?E+q60P95}@r%A?#TQbO;0}$9c5=$CJ^zr+{~>_B#D<{=r?I+I`CS z0Qi&{NS1SCJ_fSMfVo8lv<$$d`&9D}1sDEp$3RswXqwfd`!s0Ji$?cV=&&28gI&fC z&U&CMknj?e_>j1ubLwE4UHG@10~zn&(tQ+U3Mfl-!kpI* zAVa|3gN&L%1_HsRU^k+>1?*9f`=G|efI22QD)6oU0giEpLA`uPWIDmK3V3HII5I)Q zevpKp0hi5ZfJHTEC=eoh9<(L_9Ic>E2PCT17-3Njx@8^`=`NtVfWW;6&=hwF_}GBP zH=qUUuEtLs`M3Q9t=A9O3u;<)=BR|Yc7JdIA8`(i_yTLJWhdy=572f(&}5UOV-! z0nH(>9(uJ-2|H*p!lBpVp!?NWd%YZ9b9?abJ+#vvG@*N<`(X1Ahhh$w?guX)GchnY z?g0;5Xg`OJR=9L~Yk>A*g09qQ2Cdug?)DG>wNaZL1VGc}lPv@o7+O#Aw=#pK-%deh zc`d+mz@X{zNAT%!N8>4=qMHFc!FmWjKMqr%kw!p)0wDzwgcJx6Qow;*0chG6A-nw) zk_X|!FG1;f2Y8zNAb8prG^*&>-O~)3D)VGM{r{myHza^wa(KWd$R8mrg@hesh8#J7 z;1)m>K&Hs?D1gk7<52*aB*&uwGE0s}0c4sSs{(#V;D7=QJnxI-!Q;)Kqyq|#v`%ok zaqM+u^y&^dZD9Q0ga4FA=VVZt>U7q4t%y976Yd3?zy*)ALi0y=wGK3u6H41I%nzD> zIPf={f>+A7fX_+rRk2+>38Wq?qhv`zg@^3o{5-$g2ymGXO?K4@VNZKv-<>SRp zNU26%fU6Nb&`wJ%)kgxT`Vj2y0oN}cmme|z9IZh>GoPTfkeIdDXbl1@0wA^6Age*1 zJ7BFrE;zt54&E9hfvg&Y6VzvY`3ltR+6C!>TzBaNPg^@{fX;{k_dk&9k8V&~k$*e5 z9SQ1xz#3SP4Y}Y|x(AV3*`D1X9=L&ps~ZAR0B&I6QvhyY;Zp!^VBu2$ZeZb40L~uR z6*%&5I|yoEL1Zx+SYTls4J>fDVD?51g4&`;;RH7w8ekaRH8cg-8(3h6Kuo~iz(P`h zy@7?K0DJa8Qh>ew04spjv7m56s%ODcSQ=Okpx($~jNXVma{Y1A7M=~dt98KJJ)sGn za9`vCI7x$R5)~XZNycbRl6DZ*9|C6;NLy*NCh<7<0(({g2hgCbN#SAeX*UV$K)fV~0%E5KfXfTf79Ku|g$9p>=VPkaZ28PfmIht?Y1E-D@# zmmhVX@aYCEOtk({j@tPEEl$_|LB!}4QrlGUS*qLtuaDO;CTjnjp4)n!v(bX+p+tAvWrRt^hP<@gpT9o zCobF%94|kG)+7*B3E*Y1+%GOag77oAFMy_O4s=%QI4U3Ke&N`Cr1=4(^>a{9?5Rt{pB9a7d?~x zN<5O?!28P~+yakeH}L*)2sg(g*$uqE9Ky};NOlA7FNbhbJd)kO`^zER1dn7l@cwcL zH^w8`4ZOb`!j14qb_4G(hj2qYlHI`j%OTtVk7PIS{&EP{$0OMdyuTd6_3%h`1Me?~ za9uo--N5_HAzTNKWH<2satPPPBiRkSzZ}A~@JMz8?=OdNO+1p_!28P~Tmz3}H}L*) z2v^4=*$uqE9KzM`NOlA7FNbhdJd)kO`^zC*1&?Gm@cwcLSH>gR4ZOb`!j-eD1+~5p>HGXw25J`;^D!UyjUII>o>VN0VT}f$2291Wq-egBKjJq#964fu$Ox zWCBVxNXZ10YLJo%DAgb(6Hux_N+zIGgOp4_sRk*TfKm-oG6AI;q+|k0HAu+>lxmQY z2`JSdB@-&b14}GdJHuG0n_56A1eRKmk_RZYASDk_YC%dKpwxntJV2=hDS3cW3sUj`r52>* z0ZJ`M$pe&Hkdg-|wIC%AP-;O+9-!2MlsrJG1u1!eQVUY@0HqeB`O37)WYi4pGVI4C4SN7kstS6!4M;<4e#fR^+l}H>5^7 z4X=?vDFl{Ukdg-|wIC%AP-;O+9-!2MlsrJG1u1!eQVUY@0HqeBN-apq1C&~jk_RZYASDk_YC%dKpwxntJU9@=2efEm zK`vU{IG{XeYPs4O#^cd^Ou)msM5TbgSxo`7G`B{@1GE4Tw8Z4)+`Iq(gBPI)E8tm# z0zXF*)`f>`uIeUwI`P5Fr=YQ5*lHBGc_{OVqw7(?Bbm@XD*6ED=z0`Txdjaz(9#6- z0K(FNCuTj$admiS1X@@U-;XDDJqmajcyv9A$H5nPN4pMy){!B`v(N{LL6MG-rN(F% z>ggtswN2n556Ji{-q9|QWATo5ffV2!?E)#lJK6rr5{DENni4~%9IL;yi@F|{)Y-t{Pzitvm; zN;Lu*4<22Qa_|M-`4UihH(G0{PC!W?Z;b&`K<&JNfAs3MAl4e=h#-L)19d%0hO6=qvX-PoK$fH&>8#cPFFtwW z!u{9yKmPS7j@`$>YTtubq_`N@v;@E=|8QJ80$Y;e*qiyk`L8^Gdm!js49Hzij@><= za}gLknNR$G=-6FX!@s_+!4Z4~iCYWv4@dAFByJsGRssKdw;nJnhkw1>1TZUuf4$oj zFe`fD}aB!+Zr&-hkw1>1~AKm zf4$olFw2F1z1t2j%YlEr+a55>hJU@=0Wiygf4$ogFw2C0z1s;e%Yc8q+ZixRhkw1> z1u#p4f4$okFiVAhz1s~iOM!pA+Z`}VhJU@=129X1f4$oiFiV7gz1s^gOMrj9+Z!;8 zhkw1>2QZ6+f4$omFpGtMz1t7wAKh+$9QoI?G5>(A+i>hYb@{&|^Pf&TfkX)?av)IxiX2Fk zfFcJHC7{TGLfkX)?av)IxiX2FkfFcJHC7{TGLpw#P;5Zr02CXLH~_^4Bo08a0f_@pY(U}w6dRB@0L2C*4nVO1i33n< zK;i%t8<02v#Ren}K(PUd15j*0;s6vIkT?Lv1|$wZu>pw#P;5Zr02CXLH~_^4Bo08a z0f_@pY=GmyjRhPD-Jr{mK{*8+8_YE-8J%?;(CARalSd#c7obyd$g!~+k~dDn^F|Le zHXv~TiVa8{fMNp@2cX!1!~rNaAaMYS4M-e-VgnKfpxA)K0Vp;gaR7=9NF0D-0}=}eTNPPyd@aP%=P#J-Jr~w>6SUOcW)(D_1 z@?XpjZy*vstHb;PR)~R)tT4lNWW`y?f`3QrzsRRmd;~3((f&)sdVi#hfxO-y*WfkM zdVkPxHI~6^Tzwmma`50aJ_X>xYkUgu_OC!z;O$?56yWb)jZS_d&G18p?1<`LQEhcV zHR=(Su+u71Sm4Ec_^%6#Jk zY0&eQm|Ijp=V~G!u4D?zL&PuLcZA#w2fE@8dMmDDcMs^8Do9Uhbm{)^UAo`h170fY zarxKFNuaCZA-y3TeZF z`V3z{{Wfqj6}fv2jg`y)yRY}MXgU5r(#hu0e2fLw{4AD$tzd=Lu`efm{r}(iz-w0U zvAm$0CqT!oc{CmY#RvFM${6q=_x$pp7Q!eV4S~@R7!85Z5Eu=C(GVap1l-^!!}`LH zb`F9c&zu97Ery>PTLG5^U0X31d~PfwBka;N7e)pK$StdP;g`StgR?*fl0z;?bAVsb zmI!Ax!C9c&Wg+Ue!^J@Ny(%h!u6+9r7n6Wr%w`E^MZsCsaMm0+3v_o3#N-=rF>d&w z%!({9e`vA5{4pPX9osS%28MIslXX|auVVvU;0C!FoS7ANA)E@FAFjFkqVFyGvv%~z$ z!~wHcg#)I}70yb7v)bXT6>!!`IO{o_^$*UH;)I#3#L2*54EB*LC(MpCIIA7bS^;OB zgtK13S=?MOy=rh)Bp1v_g-HNbp3)07K3lOUf5Bo5Bav+X`nbgR_pqSufx$PJWmUbvVlo&WhrPnViJWz~Bn@Q7c?_8Ju+- z&Uyi7aSFgp)`GLV;jA<`Yo-9qN81EoKGGJ1#h{TOEC$U5VKJC12y<1xAk2;paMndQ z>l2(MECf?$0%rxmS($KFs}RiOULghsKd^r{z-2GOS)br6VPUw*a8@XsRR(8GgtLwc z!~DxH0`qUP2+ZE4A~5rg!CBAYEDlkatR|f01!rZzS@m$%Y*CoWi$xh2!ofZ|2A6#f zXK{$ZO@_0);H+#os|U_n4rkpGgZb!_7|chp;;kGkL!R149DXzmMRu%#v`E;Vf4;D-F(ShqG3|S%=}Q z?~*Y8UX+6Q@`e-x1LW@ayHYS;ev*Q@g;|<`ArtHtE?JlkL0Om%FF%c?O2nV0Gz=Fm-u~Fm=U>Fm)}8Fc(Z!WMDW0)^SN07Du@%u+Zp( zv)02|7vQXqaF&oNOosuSJq$S&?v76`VC2 z&e{fNz0`sEh*1~jqe5L+3|8pEVz5>h7K590VXnHW3$yndoF%RY6SIJ`BH^qmIBN!+ zwI0qotp_vtvK|8ihcc+O`3){Bt`9dE&WePys^F~IaMm6;>pGmpZ2eWH>7v&Z>a3X2My! z;H=wl);Bmy(-`I>H)EKOb{oTD@Q^Vq29F!VV(_mq%vJIxFt<3sS&48~6P&de&N>2T zJ%Y1-!darGFq36X85pF%{&g^g*^vlmHNjbn;jANY)>Al(%?zej5zY!VgZX#98O*=J z<}iCr&0#vi;H+{uYX+RP6VAE?XZ?V)_$^?jXj{NcHnw13PzL)b3@%#^XU%}KcEVY= z;H=+pmW(CLWOFzx%M#|JCQF!)-de(9@QWoZ27g+@Vo=`-=Bhv|n0ZBT)?_$q3!HTo z&iV#tiCDw*YQtIX)-aR(tr-|}!2T_Q%T9)~w!m3e;jC|PmbeYfWD7Ve7|v?9f%!MZ z7Us)rTUdQrU<>nQk1fnCQ*0R+Ou=sXYX{T8Vh_{7X%Ew(U=Pz_XwSe92G%jj0j6W7 z15C#}2bd1f1!d)6b&5_fbsA1Eb-GS4bv8~glYN~S7-oQVFuA~DYoZIxznkE!%W&2g zI7`$Orp^q`3W2k-;j9i<28NwrJ66NR&cIo3;VeEkn0fkeRsft;2xs-cS^M2!VSn8X z=6XhVxZm7iVG!aDOLdX%urTO{tJ~-fv-c95^%>3*@qo#i!dYQ(RtcQd4QDO)fVp73 z2Lr<)u+J~SWk17NBA#%Q;jAz?s~pan0cUN7vz~jxeBR>)^W|hOSWM6Gg86cr7tAgD zK(b)B9QJ|fIOPM=aoz`}785lglEDe7K1`jYx$De`09n3QDXJBvxvrPOM7{b6T z3x5WNP%z8JpMfC+%yRH&UKdrg1{^fe+Gs?Fw4iEfgu3Q3h-xO@CUO({23VH z!K?^>28K8=E5@IJAr{O^@MmC%0kcy485p9$tPFn!hA1#A$De^A63i;_XJCi`vr7CK z7&5@D3V#NMbTF&NpMfC_%xdswU`PeCTKpLpQoyVZe+GtRFssL(fguUZn&8jCkO*c? z@n>M@1hZ!NGca_3S#$gu7}~+C1^x^SZD7_Ce+CB7Jxa_BEBqN4TEJp!{23UU!K@Ab z3=BD1DLhPpMjws%sSxDz)%Nf9r0&is0Fi5_%krnfLUk!85pX; ztPB1O3{_y(6@LbXN-*n&KLbMrn03dWfuS7Cdf?B%PzGi_@n>Kt1+!lGGcc5ZS#SIq z7>dEH5B>}cMPSw!e+Gs^Fzbgu1499r^~axqAs@_Q2w-5y1G88H7#MQFERFyMh8!@9 zCxC$=8_W_2U|`4svqSH<5&;YhE5R(800xE?V3tAv1H*DKOC^ATVHudE5x~H( z6wJ~IU|?7RW*G!9Ff0bMOad4f7J*q70SpWa!7Q5q28IP-mO}so!+bEyC4hlp9+>43 zz`!sU%<>6fV3-4D1q3iK%m%YU0vH%(fmsm&3=A{Dte5}>h8bX1LI4B9bTBI=fPrBe zn3WO0z%Uie$_Zd#m;z=M1TZj62D3^67#JpjSrq{c3=_eung9ld31C)300TomnAH-% zz|aR~bp$Xl^nzJE0SpX1VAg~H28M1hYf1nE!)Y*UMgRlDDKKkJ00YBGFl#{o1H%b0 zYe@hD!*MWcMF0cCF)(XQ00YBOFl$2q1H%z8YfAtF!(lLMM*suEAuwxC00YB8FzY}7 z1H%C@>qr0t!+tR9L;wTBJ}~P{00YBbFzZ491H&FL>q-Cv!)`F^MgRlDE->p(00YBL zFzZ181H%q5>q!6u!*($1MF0cCHZbc=00YBTFzZ7A1H%?D>q`Iw!)7q+M*suECNS$y z00YBDFpD9Ofnfuf#S+NCupZ3f2xMSb2WIgEGBB(KvjhSe7}kJUB7qDHZ@?^xKn8}_ zV3tfE1H&sYOCgYf;U$=*63D>t0?g6~WMFs>X6XbnFgydZ3<4P#o`P8>feZ{!z$}YE z28PFAmQ5f7!y_=uA&`OLA(-V7$iVOb%<>3iV7L!v`2;dB+yk=$0vQ|Mf3=DU` ztcXAchTC9POdtcpEifw~kb&VQn3WR9z;FZ1$_Qj&xDICJ1TrvO1G5SO85pjDStWrC z3|GLcia-X2%V1VbAOpiCFsmVuf#D*U)e^|SZ~@Hf2xMS54`%fQGBBJ2vnB*GFq{Rm zrUWuDdq#I312dTQB9MWB3CwyE z$iTn|W_<``U|;~Vz63Hb2!L5X0vQjqF)(n0 zSv)}u3|wHAKoA21CzvG?#K0g4W=RAwFi3z|GC>Rs;$W6S5Cel4n57cLz#s}{X#_Da zh=5r-K@1GSV3t7;1A`ElWfH`|paf=F1Tiouf>|~}3=9fjmO~H&gFKk!62!nD2WEK$ zF)+x2Sw2Aw3^HI=KoA3iG?*0<#K52pW<>-sFld2UF+mIrnqXE!5Cek-n3WR5z@QFh zWdt!WsDW8IK@1G4U{*m81A_{fRT9L&U;<`U1TioegIP5}3=BqKRzna2gCUsJ62!n@ z0A_UrF)-+ZSv^4v40>SJgdhe6T`+4(5CcOfm^CAafguFUniIsp5DaE52x4Fe0<)F` zF)##zSu27V7y`hoH9-sv{$SRIAO;3MFl$Q?1A{M^wIhgu!3WIR6U4ya4Q3q(VqowB zvyKEYFnEGlCxRFlJix3oK@1G;VAh2o1_n1U>q-y`BFatv_m}L{p zz>ouGIRrB>WP@2Q!3+#pV3tQP14Aa5q#&JLo=B5BA9`p3CwyE%)rnHW_<`|U}yld zz63Kc)Pq?+f*Ba|md3=CVqER7HbhRtA>P6z|TCNRq& zgn?lrm}L^ez_0<#vIt>dSPy2|gfK9y1G5}L7#P-qSuP6%xY0uma4A2w`AY4raxKFfc3wvl2oW7?y%rDIp9DOTes*5C(?DU{+2D z1H&RPt007dVIi1R62ic+0L-cgVPKdKX4Ql+Fw6t98bTNt=7L!*Aq)(2z^slC28P*S zR!;~6!z?gsLI?xHOfYLo2m`}GFl$B#1H%C@YfcCQ!+tPpK?noGJ}_%Z2m`}jFl$8! z1H&FLYfT6P!)`EZLkI)IE--6L2m`|@Fl$E$1H(x$YflIR!wE3!KnMfFaWLyh2m`}0 zFzZAJ1H(};>r4m(!x1p+LI?xHVKD1T2m`}wFzZGL1H&sY>rMy*!%HyhK?noG3oz?R z2m`}&FzZDK1H&^g>rDs)!&5NpLkI)I6EN#b2m`}oFzZJM1H&UQ>rV&+!$UBOA(VmP z0hq-S%D`|R%;E@TV7Lcn@q{uk+y%1)LKztDfLS7;3=FryEQwGChFf5kOeh1xO)yI# zl!4&}n57cRz;GSR(gxCUnFgfcK(1+xr785pjBStg+j441(yi%NI16S4gfcLk0kc9v85np}Kx6e0p$rV% zU{*{h0|OVBl@Q9nzzJrhgfcL2fLR%#3=HgGR!%4b0~?rC5X!*73TBmrGBB`!SrwrS z49s9wO(+8c6PVQy%D}(~X0?PeFff2w9ia>i|G{qQ31wjT2WCwOWnlOVW=#oYVE6-O z%?M>+_zh;w31wjT1!gS>WnlOTW-SS2VE6%Mtq5gc_zq^R31wjT24-ytWnlOUW^D;& zVE6)N?FeOH_zY(431wjT1ZEuwWnlOSW*rG-VE6!Lod{)Mcn@Zs31wh-2WDLeWng#< zW`XZ@4u;<8?9u#2!}u-eHs{V9l?2BYg9n@ zY=ds__Q$TI`@BzQj!FjT*#GXYpquR#9KT=btWl|G{l?!?$;QCo(wol+yFK|Ybjum^ z4pLHgo_%2dI-g~!2H93I9O(+)o1fQWz&hypdiKqt;z8ypH8`L~@+b2Ns^Zoh!!LAdZsB}N8@o!}c`Pc{E!=WmDHn>?ipw9L$t z`O^PK9^H@tdc^?_BuD_k^+Q97e;Zl=p()THq(Fg?0trG21PCeMz^#BE5;&j$gGMmi z{hg~pNhhrnoMyWJdUb~!HgN29U_?2(0QJt~3Cs)((3A{K*CcF1`vAV7%nEwf4DwaU zpv%el9J|kg#F+oSdGi5cOhiDTftY-I>Oi4 zg4W(Di8C;;FvP-TQ{d}tGvVuO=Q6p_x)ss&1>ny*W(7Vz~aVkO2F< z2rfGr&e{TJU4^s0!CB()HMJISRxq3eT94ZXULUQ(&cGlJwnKxRfk6z+(qU&{5CyXg z*cli^z$_DX1_og;%YvPOK?uyUVP{|v1hX9285jh>EEjeL1_dz7gPnmv9?bG#XJC*6 zvjW%|7-Ye$5OxLz889n?oq<6b%!*-WV2}c{64)6SB*83?&KMOA&=unX9-SpB5+0p3 zDhiHa9?fq89Qn6>bnJfN(*4P!`zN&VCuEDg@z*^^WdQ>NgGcLaQ2S39yQ=OfDxh6l zy-xp|e{h!vb)Q1){B&eK26Bi2bBhWnzqxduYW|_%!oTg9M|TT2GkbKO_vmy{(dfR~ z-J;ULz`)SyqM}g74!Wy{;k7G-vV$ph;oo)+WSoObH~6ek3y<#e-4K&IT~rJZCTMhq zs3{)G^F4+@tXg$U#2cdsG4# zK@EnR{F((jz$Z8GYgVjq?0)IOujw$u)%b}EzvhIB|DaPIF1he)&dB)x|382DZ5Mvc z6qN%0aFBG4N&YE&|FZD+M1x&8Ma6@W zfuYy;&u3Tu{O}K-`SYckf3ooRY+ztu_{^W5B+&el1tb;!p!p|DslaFc{450!KmEeX z&5R5T%`X{CqCkX0^G`&%_x$x&adYQ?{zyK2TzX8$b!k_c-We6h!LmGcxmI8nHNst*w()g2J zfEey~()g2Jf>l$b0>{I=Ml)> z@E0IMKrYI;_3{qL=Z^e!{x2ZM!~z)$cFfI} zGZ`2dy3c}~lLR7Mz+uw@%4Xdy;P3{icmDz+&wxVZN*aG&0LN$kItg$%@#owEyXptX zRTn@(bm_GxD2sri3MAtXlF7LU((u{|y*Ak*Fdr12-+1hL?*yXeumMMZ&; zfx)#mgVCdN4|prBPcJB=Fu3q*^ni;4PTDcR=l6 zRFVSf0Cs~~@jl(?XDu)@GB9+X^6UnkR$u|?-+|jl5D{==*ALVxz;ePINDH{Ji%$W# zv5QXuxUq{*0l2Y?PXV~Gi(P>u|2FU;3lLe16N16QFO?Y?72*j7IB+`>`ynJ? zy)K=LL1_nml_K;6636a8D6QPrCSV)KId`y5%WKAXOPr0NJjGoIDVl~k90T>Sa>vFfC>O; z;D8De^Z>$i987y zd@IRmP+b8z0t<9GHPV4RqnQDm>Y?$C9@nFpfxOIsQAwbjQ}tL1p6ZDoAasGA{%eO^ zHTZNt^1S@U`foW8tU`Fi#K1smg^)hlP(q4kj69$=+E8L(fEEzgDg@l8wc)Q5?ucWp z6OIx(7~Y5b0l787kC*2_H34X4K7)()f7Jd0g@+bG%2CY0@!`^67|jL@WM%`rC&6DM z=OlO+<{#jGf(>eA@X7P?JLJmX0~wV8@=OtWuNii|4$@2!D0N#vi%e*j5Yz+2cfKIF z^Nddcxbuup0l4#wPXV~|j86f$^Nd{qsE9*&0i*K_)`a7F9dNifcK0-asuNGEM0h*P@n=?QP@a7DV0=zi`q=2ZLL9Kg3R`8M0b%6F9^r2_lf`((ePk_dcet2|$ zas)BoIa>b!jVyttParZce}WoH+CPXm-gb1E!NC_C(0nku%m6%$j+T$9okj5XD_Xd* z_A7F^;aLaESQF@=N>Ik|xctb2`$HOkUW$rB8h>7libWcKzAmUfPvg%!+r37`0yJUd z$e(vK4Ky>wpXZ{Y0-8Fx4xN;Fl*XTT4kU05B!C#IO5^_oo(cj@P~Ax5&$*T6$bb7@ z8vpCZY5cd}r12MBVNBzH54GqYlz#sPGzr%Znl9{aQE>p-%%6WO&5_^2;ZPcX!3n=K zNB;b)Y5f0Ar14wvUuF5kUvSeejsM@xH2%B;Y5b3Fq=A(4|GS&!$Zy4eizSWU!r?GP zBUn$~flvGe4?#K}rtv>|09E}6WY}XCklA-ZI*x$&Ak82P5&ABI^j%Eje{=z=?=nc; z6|f~x*TEFK@E2V4OLO7>cM)Xh*)$ihCM*8aED%8#ek=Y{zZ@G7mTGdUiO39`tl4Q z#USq;Pvg%A#j1q^Xr>Yzi2p8tBoBl29|8Fz{{h$N}CDWj2yH{1nP|8Z8L!s;B7O36yR+$ zffV3vGl3M~Z8L!s5LF?NaNarI9(D;Uyx=E(xRn{Q9uu|A#QX!a2DAHwNB1j_%Wpw# zCVof$ZLo!zkP88bX)z&XkkRfobgmklghw+717u8q+8G3Yizx`yvqfK=$=`0x1kX5F zT1?=U(u;1;sx2QC73HJQw$bHh+#fotb-Is1+dj5;;?Vq8p-d9zokG2? zkkkEVf|eZhf`?L|BPkw`krc?P7{~7V2A}Q+{Oc=f9J}3H9QoI~s8oOsCwK1vvrEA2 z9x%HA%$@*d=YZK$!0Ze#dj^=D0%p$vvlGDV1z>gzn7stdjsUY)fY~8n_8Krd0L?E+@+0ka*z>;qu74VZld%(eitPk`AbVD=d>+W^eI0A}le*;l}9 z4KVu#n5_b4-vP4~!0ZQLwhWm41k9EIvtNMOB4G9#Fk1l3{s3n4fZ1QbYz{E{2bj$Q zW`nL%V^LuSU8o0YhC2Q~cKJW^KgUjc&*tMSF4kLAKn;#sL0BvL<>v25OK&~8VGD@n zsDRqCj{F+MFC6(bs&BaRYdD{9<<|&a;mWTOJ;Rk>Be}z)8?yAsgglbof}unAqsUX`n@fpeFe!9-<*|feE%qR|>v#SQ)-_7;%*EJ@{hd|8SN9eEF{< zoRtJ;HNaWZ;VjT1VqWleuIq5IA8?i=e9^BJoD~gc)xcSE;jA5S)=O5{5@1fY z51e(Ai-AEN?D{`&F;VeELnCsPeV6N}vVPN1X1})2< zzyph!sXVZlc?Lh2mmPixuLhju31?-&S>15fN;vBPoOPF%fk6Z8HwO42yozuZ=qO)E zOee#|THvguaMm$6>mi&a$`A7!=-6J!q1j%oaG5;Wx-k9aMl_)>nxo00?v{ag1O#U2o^KbgkUiOF#nc{!u{f4u|;D_saO2B-SC;{`)WeHde-jaaD z;C%^L42nv^!r2^txNZcTRS9R!g0ptRS$E*9UvQS76l~LoF8t_SQz-@pAFzKT;Ifr) z)+{({H=K0`&iV^y$w|XZwuG~CrD6U(A(f%P?LxG z*Fzp=UM8H?1!t{>v(CU-Z{aL%1(;qXILl4}X0n?C14Ag-znO5^E;ws7oOK4ydJAXq zDZ)(FhqJuktXf5we?f-{L&_IcC0P9OD8b?vw0Q-x%?Wf68sxBi(Ba3Bat(C6F@yy= zz!<^;9ZU>ifsPM`ut0~$Kv*7$)F=K;=uj|9ryr|1sz=gVSzS` zLs+1#&kzL!E&^3e4(JXJC*7vnHrBFi3z|Q`8w4 z#KEi?>I@9(VAdRU1_m`SYk@iggDRM{M4f>_1I@8^aAjsVpw7Uc2NpY`&cL7xW}Q%HV9)`x&ZsjmXoFc7)EOAG zz^p6k3=EoJ)(v$A21_vOjyeN_1(@|foq@p|%zC2Ez+eVuy-;UhFa@*Ts53B_fLR~Z z85oSgtS{;e3`Stq4|N6xCot=eIs=0vn8l#Mz~BI8v1l+b*n?Rd8Vn3}U>1)C1A{G? zC7{8;U;}1}XfQBXgIN+93=H02mW&1igBO^kpuxc431+EiFfe$4SsEG)4DMi-js^pR z8<=IF!NA}OW|?R(Ft~tOpo6AEz$_aL28LiT%Rz&IAqdQJ(O_T*1hYIe7#Kilm6^du zgMq;xEEb@_z~BdFg=jD^_<~sx8Vn4vU{;I<149g$m7u}E5DjLfXfQBDfmsQAFeHOn9U2S_ zNnlow1_MJPm^DFzfgu6RnxetL5D#X}&|qL_2eamAFfg=%Sqn567+S%sB^nG2EnwCP z4F-m0Fl&tl149#-wLyb{p%Kgi9k$&7X6?{mV5kSP_GmCL)PY$CG#D6a!K@=13=B13 z)(H&;hH5bDj0OWk6_|BFgMpzE%(|k%z)%5Z-OylQCytS1@_ z3?*RJ3k?Q_VleBC1_MJ8nDs$}fuRu0`l7+WPylBA&|qN52ebZYFfinSSqz#C44@;} znHg9#85nZFVjP+b44@K-nSn=>fguYlCZNf{kO^jqXfiNlfLR{hM?5+sIH0@8AV)dC zwvmDEw}Z_JxEkLEZ6ovOz6shy2Hs5u-bS_;w9N~)k<79CBWxoXWOEpJE14tzwjVD1 zn&8ehXd{_RXN-!6Z!b%`Pp6BDLuZbPk7M&so-#$p9g}RZ?ho|oc2RL~HNM?_-i2Qi zvN!1RQ_yy_&Keb!?^io>R5bX*Z}W#g;SYZc*_;-B8?@2vD`+oT8f3HCKlIIJeze_e zb{aH22ijKknEcIVhcP#sfs6(3?;>fl8Aum+v)OI(H=BVR1D;_-Y&HWK3w8{2v)MV& zJ~M)w%}#?tu79nBypyH|vBL~h zO(tKXNtx|MC+VD@n6JvkEx&UxKzs53~K3X$K#1 zctBPHfg&3o%g_XX9{He99bHllT4n($JwRcGC5&7;cVo*VC>aE_+sqg_k3`5}T><1M zhcut%YW#mE_%M?0LylS}JTAZR1TBOEE&ic=!`XwE;0KOe0#z>C5_F-#~| zjMfkO>4N|tzyvf<1_8lLcpUjgC5`~bYi0BytBE6>Ysk@pzfC!=N9{&;`beYczF-hkpLf4 z=%W3dU*3g*0i`Y|!qY+;-D_hEIm+#&7TW$x(2*_B^Yupe+8{PE;bUN zNX=rfFll>iM%RlnFrXjeg1@Se6vA3nFbTo)3a+Z+2U%4Gd-sQzH;@-lBkia`UO?UL zAOPBdI(l#?Wf|h&=%zD7hHzj6pKObF0X69AXv7I!&?HXiJR_6^)IH!cz`z}B<{uv2 zuN=Eif)`N##JcJ1=jf(0`r5lnDz~fG!L8tCRNaIc~Ae+t{!CRj&ww{4@ALSiE>^Xz3 zuI`=!-;j3g|0(dC7Jtqo*k-e%u$h%K{v^;QD9~=FJlIY($PTp5Y8`@`(9-zxRl!{{ zc?SmQ(S7{kx0`=)^S3uMFfcfF_e=vxgAa{MbL1~L5|GAUa4#T@-$Da)z+l0VUupaW z=YE0sC(`(@fQ}k0I2-^{c>=5mA@~a{h^q1s$h7mQ3J-xyJddOfysgTSKOepy%aK3- z6nHNcc#l~=cxLQqnhSsa%{2Zim(xJ9kj-GAjeNJyr14({@8^Q=#Ci?d!*v$CEey1G z?i6^3*G13f7mWO^KmLJsT0u5neRkxp=SSa-<-p%m@)V>dMaAH?pd&cGUA51+bVB0a zr5mzd8nhJ)wsxAocR42mL)rvr>53?B36- zf(_nsCcc-6wE+R$fprqL1M6owXb09A(6%h*KQAFWu!w0sjPAgK%v6nbIpJIP;jIH` zA04A0fh#~XA4u4uh`;%;j2UZ}vmbGz1okc`QBYE2vI)?Ct>_>doNEeB%E@*dfB6 z$>9wi$qqFh{Od0u#Qa-4k{v2M_}8ECV7`bX+~JYzP=YDk9N3w$lrtl7rWCs^a;XNM74i1>Y2RxD;Y%qn7cqBVmU<#k`NOmy66h7mT>|lT? ze8D5xK?hU#ibt}82Bz>0k7NfGOyN5o$qovb!Vf%>9b_iy;-LVt2y&h#( z8>HR#GX5LVt~SWdG@sr%;KQ*!yQ3XI``SK$_O(3#?Q6RL+Sj%Lw6AReXkXg|SALD` z2BeK_@o}-EJJ~>G%!tBM;I|3PW?*2rQVH5Y0J>WNvW^~f;Ra;2yA6Cd++Fz2w|{V! zJbd?>1DurzXEniD3*fBnaMl$T28Iss+S2cEF$wsNHcL1w3eKvAv*y5Ad*Q4ba26*U zY#qHOd^g-Y_-?qR@ZE5$*kCK_f5Z2{$-sB7*}_?Ia8^B>wE)iA2WMS{v%a!3FiZsd zO#;4;%@WRvg0rgOtT}MjUO4L>ob?mV(uMDatB3D~6NK+sGlH{%;j9ukYbunZa4%a8?DJH51O-1!vuWv);p5JbVlci@|==hwozxfU^qWtVwXz zW;p8#ob?sX5`gb_bA<1PYvYIQzhM%9?bJ|#?`U&{v(n(Kb~tMVoOKe;dJbp(gR`Ur zVJ0gHGBB(G`^Xi(w=E6MYKOB{z*#5ZtXFUrH+)B%8k`jl-yxR|-ywGizC-RNe23gU z_zpP{_&zo>_};c~II9BAnh9s^g0pVJSwG<{0r>7U9TAv+O+*+Nwu1c|4wtQfvu46s zyWp(ba28@WoUABp=aB`Rl>^@)w-LTWPE`zMuRDBiTLzrf31_W>vrfZVZ{RG@Za7Gu zQH1YfvxV=Fa}{S`*a!Ae23)oi&RPX$orbgCz*)TT-Eewv7HD4_#I_pv4!J4t9dc}v z4A9-DyppgTR6>%llF$RbLoN%x%dH#ES_5aDg|ptlS^V(bYoOh55Er<>#S-8<0bvS1*_a+UwN=W`Xv4b%R-;yopV10`2vh0cL^rdQAtjKzqHWfmxuvUQ@v=&|a@8U>0bv*JLmYwAX7Am<8JFwFt}t z?e$s+W`Xv4EdaAXd%Zxr2bmc_d%fm?#Xx(#=7L$Ey$MWh0`2u$0cL^rdMyXDKzqHGfmxuvUQ59&&|a@4U>0bv*A_4fwAX7hm<8JF zwF%4u?e*FSW`Xv4Z2+@Cd%f0!S)jdM>%c6~Uaz%a7HF^6UN8%^*J}@$1={Pi8_WXj z_1Xnyf%bas1hYVUy>@_EpuJw(!7R{TuWeu!Xs_2XFblNT>nNB7+Us=$%mVH8It*rk z_Ie!xvp{>j4uV;ryva~)0`2uW17?BtdYuNd zKzqGTfmxuvUMImU&|a?-U>0bv*EKK;wATy50`2v>3Kj$H^@6ZKd%doJ#Xx(#AS}>c zughRD&|WVH3$)kk5?Bnh*9*b|?e)3{76a||g0Mh)z3zj>KzqIJfmsro3=DU{EE!D( zhC5)Ef+hpQZ7@qklY!wDn5Ch~z;F}H($QpKxB+GvXfiOo0JBUq85o{}Sr(cM49~zU z8%+j=r(l+YCIiD0Fv~@gf#ETj<)O*I@CeNE(PUtF2xbLnGB9weg05Z;(PUuY0J9=A z85r2XtQbuO1~xD&L6d=j70gP}WME(cvobUp7?{DV98CrWCNQf&lYxN|%qr1jU|;~V zDl{1w{)26+(PUuw2WB;BGBErFvsyG682*4+9hwXbzrm~?O$LTvVAcdp28N$t))Y+! zh96+o3{3`x?_ky(O$LT4(mvqW5a)W&_PLyuuFS%L-yE#F6~ov z+_9K|s?Hph0$1a2pjGV#p!@$cK=*bjxVApwZ{cQPU;rJr1G?GSqqnjJG+hX~eXn~B z_$oiwmj5LZe!VOk9lNh1me-f^yu888z;N8X2GnskKG1!(^=%2;{{t6ami}UPQOST_ z^4I)Bfxk_Y33Q4hfl){3xlivI85ppRIwptz@kn-qj5a3wRd^(`L&t19lKojclHK5g zi^={R9?5R-LB(W$9*<-<_+VnPzko-w8+;Hk*~G;@n7OZK<%NOpq{_9go}cqF^Q2l5~0Z zJd)kugLBFL86L@Q@Ikp`{~V8GH~3&&vVVa`vKxF5F4@1tBiRi;_?GNn;gQS+jX?0F z#=jWDYhGqBGB6n5ZYJu0Fn#O?gmuUN;a?wD)9qHlzn-1>Mz=c)|9ZC)5QBq%y;}i@ z!Nb4aEeFI9;9u{S0b+>ouXjrUF(mlcyCr}aGW_e^Vn7T9{`GDVAchM6dbbb|LxX?4 zTL6fm!@u6m2gESoU+?AtVwmu+cXI(TEcn;EIe-{8{OjFpKnw@|^==j*h713CHxm%U zgMYo70f^zlzurv;#0cPD@1_A_gz&F-Qvop|_}9BBfEY3S>)m8Pj0FDmZW16y3jcaH z5fCGTf4!Rkh>^p;-i-&uDBxf3#sOlK@UM4c0Wm80*RwI-aO^&N`8Q*H4fqmF&^TxF zaTZtWFU7pDvgD<}pa1`NFfxFS1kF)V0NswM0p0@!x-qlBqxpyh`24My=)?T-plm;) zu?~C;&lrARs0I8+P;aHbm7w&p7!*uw=FWgOnpR<+8%)np^u7B^aFfiDF zSr1qk7(l06Ff%-1VPLQVi@jiBV6X(U-mow*Sb$j{SQr@0!K^PV3=C#q7U(1`Q!wif z3j>1*n8m=#z+en!v9K~Q7=c+FtPBi>U=|N61A_sWCBVwSpbut=ure^{fmxujNL?^X zhLwRq2h37nWnj<-vs73a7_`7F4ORvQO)yJ`m4N|tN(VE80V@N8I#|qvm4QJG%(7r* zU{D3KY*-l>RKP3;Rt5&p2_MW1F02d;N?4}Cu~0Cpft7(F1k7q-Wnc&fvpQHA7=pm89##g1Krm|pD+5CSm^Fo! zfx#cln!(Dz;0I=bMr(b+tOcwL3_f7i5>^HVZ!l{GD+7ZUn6-wLfx#2Z+5kE#70d!% zkL(U+flfPf1GDz9GBCJ;SqE4d7+k=tBdiPz&S2IFRt5&pxhTvGXIL2+9Km82SQ!`` zz^p5*3=H;Q7HGWK4$N{04h@60jRJg=Klpb41nolYehF&WNILEacEH*?@@V}Ax`j9d zhivB_@CCx2y>Uz)2OqO|bf5KLz6aVx?%~mW8PxP~03AWd(0YKsC7F?dfq$Ey5dXG1 z;aHqQO{|fx=p!~+a&6&~qIDfM)SQoeU51-Cz zj@Fa>EssG*Rlj89Z#f8Nu$SmG|CHcwWd*6*9wy+@ecXkAn>Uk3_gNp%^~+%E!A0%M zC*bXmZUW%Dez#>aId*^PY=Jht-j*18cHj5ugjXJ`G3e@8h{XS6oy;E1 zhglr0zZP8r9lr>guJ!2U1r^XB>nuFGk9r(@z~TxDhlAbMLDwuBcyymV?xLarI%6Aj zKd=JWV6c;3hwyI?6Y%Xm@6vtTqZ?u)M8cYXdz=6^=afje8b3*Mv3^|4?~x2~RQGAm z?*E@xLo_lO9{^pu?%>m%qT&HM3KG=91mCaDn-qfq`K?m}SVwz|aY1#V|53+ym?AVq{=g1r}S($iT1`%sK#9 z=Me1C{Kf#$F1NJ6n)14Xbv(LHgKpDy@c4cU)S&ige#8!HQwM+&ScFe!iHZg&&>;Z| zim5oz5vskr4Fnh%T#cXbLys}X&Ut*l>Y=RfarrH>qjMQSdz^xGz$cG;Y9Dg#KIdb7g1IUyaru>F_qoeY9lK9*U-bmtOwHcutkdx8KY#0qfB*kC|8OW3 zK(_5aC?lYR4O@|o4q_X*E~FReTdK4A0cWbbukd$|P>7;FRrLlS3TfE-RlD3oNo zbVtKN;IM1=H6QB><>5YH-y1u2pYuF^5afH$%P*n6Kjr!TqObA^&&w~NzCY!{eHQNh z?hB3$KkZ6vKxwY~bn_#~wdkz}_*=L91j z9?eG#q7Or|;#F~YW|D)qT$JIN3DjQc180ps2G|HDC|j)sXCZDz28In_mJqycA`Wkn zI5Wa@Y=pB|m>3u!dL@`(dgYm5?HUy(SZl_U38r@g69WUp1>h{H(Cw(>(S7sVjTP)A zl8!s1!ATT+GBC7+11C|J?qD5oK)Q5;bCFLs@Cpcrd_APT@YSm9FuzQGEg0`Lu1_!NL|u)?PRydE680?@i< z&|)QsEXE!purPE-3~2LF^AUyU!{D2s9kmaDuY!i`u!AlDY5~l9oc|w z`vllXNZ4RMe;!Ez_VeeF6ktDp9!UZA^XHKiU_XBzNdflbwZRGyi@T8T>;X$5)`s(M zKLiax4{+!?9(=_N&b@~mwGaG1F&c=FLj(pE#^WiR1v;PvBCE~_lXZo&Kn)9s zYzw?6*2Boa(5DD8c|Ba+1vu*?oF&8rGtU^#3W2jg_W?jmo&Xm+2|q3QA`=6{bg&&? z;Ig9dRUu|@Rydqh0cXvGvv$E*SK+J=%nS_EASN?2Fo4c|WM=pQk_C(XVP;^M3}!K~ zFfdF4vshRd7$$;Q94rhB6TmDU76yiXFiU`ifnh$FCBnkMFb~X)fmxu2=}a(7hlPP*2AK5{lsG`+6Helw?y*Pf$r2Hd)=MS)9^Fn7 z9<2vT*gU$0JpMm84DM8bhg^4LI504PYX}eR2Oizv;>^SNh(~9Oih~0KgHNZAiiKzQ zN6;x}2EN@tJUT;EG<>>0dB8g$1;LJShN?^E7<01;?wJ*;$VDf2ZI0ugU9!epq@40#8J zH29Tue!V&h{M%ejJo(py8vaZk*2nnUwljbRdYuJ)dTq==ZprZJX7&X2DbD}D;M4s9 zWK=82T94NMC4wH^&fpN`@aPuy=spi}*jcc(poToetDt5)$m^itBT!g+7@q;F@Mt^& z3UtU=5vXks^ZYW8=4uIslKCFp!k!=_A(oc(gDe#H=|1KGa?A;c84z28B|N*odv^Z= z`Ng9*M8(0S`+yJVuzv+l{`E&Z4?bY>=;Z-ljl{r@HL!fUpL(E&SL;3S)(vn|sYb;B zWVZ1oka-@xEUgTnK+^u;(aU4u(|ru;mJ$_<*YO~AU{xSnLGk(uY>YF9Z|i{)Zg4Q2 zaQuJawS-T%w}3CGPk0infZ3z_g7Hbm{}*9VyXoKm{~q0;7CzkvV7)7!?gzfD|4Z1q zeGMEt`F&fzm9T@XbL?dEX#G~g?9-_RQ|Zxt{AKmO|NjLT7#P;bfO3Hp0|UcZ@Nu-D zrW<79HIjjWVOcpyHV$6=fes41F9{M0XJlXi9jME~PzmY>flb-V$iT1(%mNL;Ls$Y# z3=9w*pvKu2uV9^4bG%9>9{*Jh|)wSOG>Z z+ev9IYa%;cFnBJjfakJ3@Lcv5&f;TYV1VdwVq#!`HF(fq~%xxwh7V&K8Q@8)qA6$M5n zW|reFDk`jO>>O#JK~yHDV=gLMObx%e>LeX^oWMRR&A-h>M!}=oN5uf%QffZp5Pg^v zUV%Hq`!Ap)KHh@+FaGcfJduHcVJ)~4_UMdJ;Q$Ri3WR$!zfth)2HoL`7`fDT#L;B+ z_V8^zS<2(m?H$0s-Pz2CfBgym?cNcd%qKm19shgqyBvfzH~2tJGG>oXcFO3ThG`*948$<4@qyNpuvGwuJ(}NWpoX6SbYE+!t?@~a7ZgAPQVqw!ZE{eno_`U@ zI)#HTS(_g+B7{4^W0VL!C7fyJ3ItI?es~N3=FIcS_}*f6Dk=PI2arm7#Jpk zS)jpV&`I_z41o*`3?OwJ3`wY=3Jy?@?q7(|)N#ienjWqHK{F7#*kz3`Iqv8HUsQb> zQn~epM>y^P-N50|+YP#e5hB{{F5uD0?9uJ6;nC^L!N1+zA+6KdtlQn7)7gN3o4ZMe zvkCt;che4MQ;%+U3y)4`i*9#`PG<*DI2k}<1T=;RY4m}%XG3BHZhLtF z4>A50WzdLq>m~jcDJD<@?Ers^AQJ zjR!%~0pK7pKH$;Yy+nY4!T6F#XY&LB1_s9+K8(;tC1}s|1yBPW)D>_w{_oLg?bErt z1Ei?=ACpgKbpd4KyiaF!g-7=}pU&Mg1Q-}xI(IJsDdC@b+@mv_!>2P@z@@WT!lScT z!KX7@!=tl0!lN@g!K1S{<9IW8oQsKp;dnFXvQg04jsLHEH2+}XZ}kTEvKTy?ojq7U zXIix6*vBq-50wLUVNp>z);W7_=tg_!Jhj9NTB&)y(&XJ0|WPki?6sZdUT)r z=Az;%;L=;f<-+gs$+7#||Er+h<5mU+2FLDWAd4J4z$SwxbYNDp@VEH<|NkF+lc`Nw zsX554KBd_pcP5smG}Nd#JD0|~bk?XC_;#NG&5LRHbRPwE!#Ybq^Z$^v`#Q|VAEGtA z)BvP4y;KvVHMmrXpjK&ElsPv4V=CcxZT(g<0X)5S)c61>9|s`i;|y>kHr6pF9@J?R zf#>OJcq#=gQGzs(K#is|;8AK&F@6ioDh7=-!^=kr^|?>>&oKU{n7FNHAp&$1W#_bs3TdM9sAzb0U-#@j2TtOkXcGYq zLA$7!G=FCVXLT193vga{QLzMPb{7=~P;Ten?xLcV*6HkU+&KVLLUvY%fJ$gnegPL1 z4Uhv9JiE{E3;3v5xOC>IB=8Hms8oQWF$1K)10+_!FX-FBFX%es|21%$dkJ!zi{)C- zb|d~4P0TT&0*C8vf(20h>Vv@^AC873gp_-SPiF=u{?W2e8XJyiHy+ zfl`I90z6T4&rxXrWtqk!pr}JiABRB;P$0eDSquyeN5Bcd9yB=tW_7?PC;Ax~7$6nt z6! z+x_skBWR%pgJ<^#kK>IeKs|F%tatxB?xN!0zj8yoNB8yPE-Ij+n8CCA9BA@Y!Kc?p z#m1-C)c_jU{H;qNDZ<8+(cR;lt0@zI%VbbOZg_<*bNZR3-k&A*t6lze-= z{-=Qt66D|J!t2X?0Yv$^Fdy(}KFH*0eX{0-r}e2?HW&VFF}%J`y)sN5-RB*dZ#Vxo zE=_mobv_45I2Mrf%*fxW!w4?JEb2YF&zCy5^g3Sz$(umrS@>IdAo3<|rIHObDyBZA zLJc)4W{#!Y4K*s}&ipNcj0_CB^gyL@_xabHyFjgh*G%1)Aw}H(YoOGA5+s{^7-X&i z$bXPh8ssWirdke~!@JQLqhbT@TEpf@8){Sx{Q0-Js2KBa^HDM4-xi``SnAa9E4|d# z>Qt$vO>n6xI67b<4Gval0J|D^Am_Of)8-!yrB2;3DmJfIbcCoFKpfcNqhbhhm$66p z`3@HqBZ#*dOC|pwewq9K|Nm|m6-1P@=KcfCwRv>+g3|+17aVlNW4vRWBe?X6jy=4a z3FPJ85YR=;hoK&|K3sYjzp@Kw+UKpGsNri1IK1dxkER2=wEb${wUbnu;g<0GVE$?5<9 z|INRY_**SO+Iw9;k|IdEi%LlIH%7-!cLPwsM>ux4n}8URjvel%9^Eb~37(zq z7NCO3E6uUf-NEs=J18(196QTHd|RKC7=nu)P&FRf04kXxJiE_!x;B7{AdgPh4v=g{ zr|SfMLEjbpg04Fp|6lQJcK0y$?CrlGz`)?y?e5{x8*1Rud|;zzXFDh}b-Ow^cD5f7 z03D3t(c1~Cp!o%u_yxQKJbEXAd4i4&`~pr3_ys*D@C$ft;1_gUz%Sr*fM3va1HXXR z1%5%t1N;I`5BLQ=FYpU^ec%^#1hGGOG{0snUCS@P!)g5a;MR2-|9i&6poRhDwyMwk`HX4& z*-UBt1qW_?<}WyL1El%J;m`c}f@$DO)bl{sq#sP<2hq2oMqIv_#{c@zVF?BX==L2@ z8y=+_as#|{=ch;ONl?>RzzciBx!Ya9!_u9jSkiHaARZ;%{v00KCp@fgdhom7^k}_Z zBI?mC?$dqPgZY97v?ldnj2D1(PPz|!bjPSbo5ZMVNu;Yljb2c(1Znho!kfgP6(n+C z*-&^V3$zRmGWrQx;voR)WJ!cW*C#2s8vpm~egSSG{B-R84)y0VSFHZ@g)C8ej$PKH z^(}wPaZn-A8KdF=D)hSh3z$Fy^mje9?|E3C=5KCfU|?uHSz^%L4m$tOqj!1*=#=Gy zF5L$`m@mK@6W#4#UCbX`I`@NEKFl9HT5p4b{~LCTjW2n0pNG~uC9EFSUyIs2Ktt~_ zDh{Bf0v@373;=a4K(%fB;qK`m%YC{JYhMJ_U5$r8j__c<4mLq^KFCUs?xU~wdv+gx zz25*HwB`&941dA>S5UPF33$+P$B>>1D7YEG0Ureqc+kKsB;Y}z0#SF4fq{V;tnM;g z-CYI-hHKz_2wD>f(c#g3$fx_HNB4g z>;Fng#~ljTB|W->IXsfjdsv^U*J=L8Sjy?a>@DD7ed;x{hxM;=Nsmr%0dOe*Ns`?q zDi)ydjE5vqPzwhx0_w6syb4-Q3Q4JkaIZSSgA`rFv2neT513fV>oty z01afkY`p{u=L?=#6M*sU?&~hyhnxQ~dUT(49aM18iIgC=gg0}8J zoCM16kN_}+I~X*^G67r01LcMQkM2XDW^!+cN`O!IMOZuVglG59Gn|x;6K@Y@Zbyi4z})tAg5Uzd?BGy-@)Ga5Y)=)-X##y6mwt5N%a9^I{=-jGlCN008g2f#xcKS6Cs1K;i!9^K$RK8Ty)(cKD4 z`5<rA4gr)gGeW#C#LgRC= zCtXwu{-5X!ZSd^=()^>2zdewh0k&htV}>LDHqfvIXh;C*$e!l^Ec{JN*g&H(Z~0p$ zv4a-peFsmj^n=gjf=o?wd2|aqqRma`vw`*kFnhx0roofMEs-FjKy}6i&@^s{iiW54 zG5%H~kXfB2Dixiz4c%<~Cpuj_JbK+&JUU$`^t%7)be#d}?;C)ctObt$ucFQ`p99T6 zm#AoT`=}^%ANOH?&~lQ$<=nsj|6ekKyw{nc63|(rV&My_G*7W1+Vp4Sd)*62WU2}`vNFgf#TyM$nTvsDiyxH;VhlC4W7*39r?GpGdc2a3uiv~h=cnA zh|36Lv4a^N-N!t&4}rYe{DQ^9`VfE5DzGu36MUF2Ir49F0%`YRKKPJ>`vi!~2xBoF ze8}P1e29hn1jGoJ?vEY^Uog3Jmd*gBj;3VLvT@_vKAoWpT2Gc_LAtLHrJxMY-{S;Q zz`xCj*|XP)8Emv?uNT-hCePj=MpyoATp;s34?f`V*e(#uQ~Qu7_sQ;) z9@Yo>dw#MoF!(TEbo_tV)%sr19Gs~Rly)zGmQ8xRZ20^Cf8&49+8zF;8fIud$znmI zvvlwgT#$Idmd@OnvE>s(kWm=1YowW@7M$GVNejx%j;v;C(s55i{B&TtK?1pDACx}ZyVlbz!aOB_S#R3vx1$zg?VuQ2T z!K_Z-H6T?SAURI32#Cc6XK{mBoxU4Ds(3(hykHR!3lc~m7C%_5({~F_<+M#`xYoKav$>GK6UT`2Z-&+eZZ6Z1e|lwllx?+>kgmJ z&;vf!H~D*C{{$_BL0)Hc?*9c)#lHu9Xq`tl(nw(A5m3bq>XU%RSmL5%4}&X%y`a5P zp&gL19MEvj`Trp*4xlloL!j!p8#LM08=?ZLabC{<`Tsvy6KJvlQ%7t4&;S27GQO;V zC@V>IffmYH|G~}#_rliz15|oFNfuR#DCcw$S&;n+Oa56Bof>{!vqnf}h8BPXg?Tw4oUpOz8xI2n@Q4f$A9dypm75u#~q)sFR*vqQHosxw1WjS zRuTYeG9`ezp&6i!FpwSQs5>?8vZ5St>CyaysYn&Ni^Zq=h3CPycJMWLuq`Z1FF`AF z_k;Yu7c`uv{ZspW>wnA*EdLdX@a)+*?{WMXXuE~S<#*s+0uEZ|J-%P_P~HsQKy&#W z@_rI?(7v{A#O@WB?z7U-6vhR-+^~WtF5K5pf~DaX z2Y>qkW(EfSZKoVTYor()FF!H9*M@wi@|M#53btZUAlk3nkz<^TwC7q zw~8?`Fn|&vYFaBqJxmqU$ShJp4$lWH9-uIVq_c)!{JoAWFJCg!H%QNb8d+G9+7>2I zdlH;#AW4mIka}KzzFtyY_c=s@ zbL8K4(XrvDe5bQc2`6}4nG`4mp>Hb#%{ZcNxkCgHBtxPH(0+J^q)z~WHfUjH$nq*5 zc;IMtpCz1fkTWCywo4w}rycp%U+Z+%0gnW|=6B)Wb_gWceGM$odZ0wYvH1^U3Af|_ zGp^m=UzWm74FFBwf_8Z-fOdImfJW3ZK=oS!XfO#hq-+3czC|BagjaML@cn0?O`nj8 z&K@r7#=yXE3p|Dl8dHME#=>P&;CeIRV^3vpS#^_QLhRckEAu@6!kE&4B1IWr6LA>tliKqg%uR)4P%d=H_)Quzhsb;X1-tVR}Ks zAP{>Gv%>VAVuk4i4WdB&`va~ckqx#7uZWF-VFk3g5f<(k>IfNH1YH*o8eD|6I3{Cn zQ@9$x1-A`AErJNg?ysQTu?eoAMlT;514C~lI0I5|(!~;#Di}z* z1%bcmC=&w%WOTu$+gZc0@dpDF0|S3E__oQ(piM{coq^4uJ-4lw_*R0NMzMR{>}vAYKKa zjevL+fHnf+P~gbF?PQvxF;sRtXp=C^{cvIMZIe4e%jmREHUDJiZwCz(ICf6~t&3;y zWWMtM5qPldBsg$DfdmO4xPD038DInungR_%3KR$_kRYT$fRF+X+zR+1fddLKXbOP4 z-=%XkDD9+mf)kBn_h0aZNshe^j3`ICqu#jc4I1fz-#iaZ&O}`0!Qbro_y2$9ACA`l z$_2sqC3T{gbq3o15LY9KSYsX3M@jmik(_Sm72L?1Vp~r_lm02lrcDb- z$_8H)0TBTu{VAaS2DUrw4tgAX0ZRIK6@ZdHUIn0}k5>UG>El%ZO8PhyfVLij_P|2C z0NIX+v=tF73{Lvsjj!4V!CN*VNxug?j_%2P26W;*B!FImZuW+45{4TK4JnLG!e|Px zZ+Hc}0Ad364X;QFuy1%pQh$JqF|=)=YbUOxmC!<1kZcnd)r-hKehVnOOZ&yMBR0`*cV0y1}^Y`gE)NmrUjf831?NoS+n7+J#f}t zIO_+TrO6Dd@13CMhO;othPN&jF*7in1N&$>yqy8w+X>zWD+xaZ+zQT$hO=tmthsR3 zZaC`_ob{fCfk7DTHzD|W;Kpzk==2;&Xq3Xmroma;;VjVEFc5X0;bO}0Q^i}@7#M`W zc66{YFbINKJ!}jN0$|nzHUt^y&3Y+Q4ljss|1%mLJH3jm#J5&`OEBi6C%fz~K?yTLQN4=A_) zC^zuvo(39OaqMNW=sxdp{4iwfHAV|KEN1@{8{C%?}vA-?;pM`-Wquj0$+X57I@dQHcN@ z)e!(12LzqXp#WO*3|`U-nO6rbV1o2sK&2F1QKV-nzwglG4U?pq$+uw24`{m>oQ8W!xb zt+zobg$XPP%O$@#K!;~CBgBxCHE2_{1!(;xEFpsuHFSs;93SzAK}#MjK=A|K{o>HU z4vs#^NG+(N0tph(_$(x%5UX}T8!aHRpiMlGh(c`Q0UbgB3E~paXaP9DKnvU98AU7^9@j$t0XIVuM|8sC6d9Y9tWg151|in|(r^XYV5-x0dPv-^ijXX$;9 z?wiM5A9qABG8}h(F0he_;kfH-hIb(9y@Nah!*SQo4R#>vyHXg4`h6f4MEz%|1yRf{ zYd{ox!)_48%}@?9g`a_g=l^k6VF3|||Ip&B-yUnj6B0F`$ zT20~8xkd%Fi^8XKj|ylN21puwE|)$h1A|8|Xk7^d1q0Kdh=mQu^S9jQKp$gegpYZ- z^g1#^hIQ;cm=8dQyy({;N8|F+`}O#eL&zF_w-KH*?wV~81gDWC-&D4~ZK+V2fy@#%ix zdGHPL=>AKj&;yU{(?0YLpiO$Xc0)oBbU+|v@!kWA@${0OL0_q_ZDT+xCj zKd$&HKk&T#1|EX0+|Lk+uQOQ3wdDza%T+!G22iMZ9DKpzaNs$MEC04Ppfg~Vc|kK^ z&_Hbdg*IFbI{XoJG?t}E2|Aqb((A|q$+`m^h}GaFBbZ4r88JgbVG?xU-*z6F3_T9M z1c#qTr|WeOWmcrX!%TsXuuO))CYQh|@P_BX*8?1QcHk)<%)nCvudM;+U-T3RZdQ82 z+h->|zhCxMzTtWKHBt(^he&}wow55ngLNDmesl4+`|*I2;dxjvI`VHjj2es>X>bn4 zlnFC@`m#5W*{2&6inboTEJs0IR9F>)l$SwE96=}W(YPc8bup3W1z>sk4r1bj!jN?A zb^hzq8GF1lSZBp={uU-~cv!MJ9C!{|z6Aa4OU6*?te97*>^dB_m z0=>4w_vj1C>O%S%%|dPC1cTS_G^%|A}_w}LLD0v*i*X+?o{=7L*Q-H=um zXwxo8y+^mNIDB(m;}K9}(lIVRI`*(fckOzQZrAh2-9Z%_gGVR(Yi-bZ@x7tjeL73G zgY+`A9w^~z{&Biw#!Dek+pN2GeYflOPInHUPIJeGKa8bZJ3)(QUwSYzFgP~+VJ+8n zZ1}?j+U4;Kdu!0K*Z1EJ2POuFm!RDKov0DQHtS1M*>i zn6tf%pq)!7RV)X*Wd})4=m%q70yV$Ey-mo$n6z)%#r^}W=Rq#uAg5xw{sY|-4K3hU z#6VF2>UQFuM90&@>vY!P-{!iUf7>Y!;}b6Y+f)pDy%=AD+OCcbf7tn3OF=~d8R5I| z@Bja(*;N27d<{GfJ`jMGdA*JTFOk9*v?G!ZE$6>~u!rx{KhzK3?U%Ywr8#zrLx;@y zw;kf&#$)JVd;+|u6&&zs)))9&GXMPl-|NNr+TO9@4|vDZ51e`7#|{S2PD)VWmUj6O z|MpY->p?M?x*U`rJi1-4r#W`=+i;ij^KU!Q?bCX{)1T+1Ea>oV9{yI)M5Rw}Jr6Y7 zlo6Y4PW?uYR*oWR{%y_Xp#Acl?9I+P93BT3gT^*Ekh?;lGJ*~fssnPJvktg@>7jiB zJUnxdzg_e) z{%uDfnI-ek3s3_Dlx1Fm_U+m5^0!7natq19TKo$=SOtot`L{Ql!Gl!*8mt1y!Ahrm za`or`|J|6ux(~b>lRClLeTaYCrGqa(6Aqvd_Aoxdzm3Jv!}tOyXBfRqV`N~k;p1;T z1zP4pMzR$K`2eM~A57s}`r|*g@QwRHrDW^c&F$C31#BVg+{@Cyd;lEIj-c~f zT=#dl3-NDrJU%2IDDJTCn*A*S^!ml~4d{^+d9{K+NzfW(y2(mqi@eJP&j#0@%Dw@S)tDaJ`@= z1jGf;;Y(p`nHU%#E=XcxV3-QlTLWMAavi=<<~4kw%qRFlneXt0G79i%D?iWyqhM2} zfsP*qvk*&RMBxiyG~o+i4B!i3KnrCc?u&yjfSJq!bKff#nEU?0WkFZlLWZvM;EQ5F z6H}0|uL4~O0gjKw@I^5fSz+d>v%$=>flo|LXM-)!S&Q1`o`RH#{&qKEw6?fa_4?g}JJp7iPy*UYH$sco`U; zgMIms7iNbQA57MfkAYzcSk{dXCL6*BGjAmy%msV-U@kZU*LxDK;}cwm2S3aO6Zv6w z%;1OFF_#}^$7=XxxS&&(AaO1t0JB3{0H#+#0H(uJ0OqRs0x&y1!qxo{fZ6dEu7g_; zrXxWR=8qCVm>t!EFgqFqVRo#9>-aASv!eojp>4eoEVf$U7urq|g4yv_2xbSPFw71P zVVE7f!Z16`gkd^BTTmfo(^_E$hJJ901x-B(fR>|4AkKVgexu;h4Jw)$9KqxK-IY4N z-7j7Ew|xT7-cAIyBpv@BbJhOm(S6gk^%9}wJD^4I0Y1I5pb?LsEc!2^*3E>`T~?6kuP%hNJ-0qXb=7aqtBP z(n3D40>l!w1L#u783dew9D5xZy}CnA8=zzlm+s@pIi$n`X~fH=`#5NAh$(a#8}m=d zs6_W6(6W%rFFe7^G(EUKIChpYK$Z%0f=3}bt93wYK0xc>!OK0s%i0n^>j*%L;X!*D z<3am)Krux&2Au#7Ib|pZzIP6EOv)7S(k@Ux0m1?um;zye_EJJviSUJKO>ou%IBPqc z1v>c*bOkjt!)Lgd2z=?8DV!AsXO+WQGvKV9aMl$#3pDr(Y4OT3!*>3u!xykkfiGYK zEoFl&U|RrRz;+F;gW5~kUb4X4@{xss;S2cs0TK9eG*dVWw8$P3lI3tQ(B*3ov7KEOVr+hS)e6qZ^10k z616vA7HEmuA218FMC~`21zMu^3(Pvg#=!6s%sRow!0-diI>W}m@Ey#$z{bGv4a~a2 z#=!6u%yI}01FhBxKt4DFe02=`n=spiRFTw&WF2LUkS{~Ew13H-!d`p9i ziUH_2F$+)r6CV7hKxc!bb^0+Kd?Aq5$>!002(&)A(~SjEc z06812!%YApuA(B}8OHJNK!=;4#}3fcHs}Z;ALRq!-0aioCc!W0rU1#ypkqE@hrcx+ z2>>ll<%S(hO}6I4Bdc4I?!iGi*M1FiGw{_D6OHX8GvjKwX`l>olo zUpz0r>pttCbspy$ja2Y*LC_6qN5R*ELDm?6Mz5jes&g-km1FmL&&zMR&q9_I_$WU@ zTTI@n%2HveD*Gb~mpl<=s?A7fJh zMK058jTIdHEubT8RoIWQadfybZRD_FDdRlG_J8C5V{S|g4L|?$w}3`rj=3>1IDR|8 z$ln6GqXX(f)5B9&|m~3777J-Of6W{|`8J zpYYIr;bDEYT+NaBfXC%W|Ic?i>p+9Rk@<(izvB)ab&TMo`kM6^+kenhAoN182+(Tk z0MKqM_+4NcanXmR;j05dcjYVucT+(dk0FC7CU9A6_z)FnvJ4`d4qyKVx@`f{!Mw@< z>zLhv>jf>>hUfsb)*yxaYerZX4RinmL=1Ev7^nhcWpD@%hiowFz6H9Cr5kpZ+;!|@ zlF$)L9Mgo5b%8g*TH)&g1wiWpZz05x*9AgXui;uf3tnc3x_b6Ecrh-n)wA`W)w3lk z9-tf>0LrYeNn8FFUT{uuhpjz5;9-5ET;HR6I=H{t%OcTz8g0k2$M zu0of@mZ(6^;fjxsrs9&=J>UdRyCt!pqz*~Up73$hSok<<3Va+j6F%}f6`m0giT)Qj z(Sz#>Nt_$*p@|;HBp;*zM@^-WCF8hKX*GH(g%4x5`~)4#iZs#Eeci+QN4XVf{Tw*q zz~UN|I8PyV(>r#Wdvu>-tcJudbhho~I_NrYNRrilsr{F#DHgnyADm(#?uVsV3#7R& zkozx|t975p;c&>(KkjcYt^R`!dZjQ$LdO0`U!)B>G#ZkYh~20fgp!s*JeuDq_;x=4 z@6T-hi#q|5nZ$BHXIjHbLs0t#yq5qH zS)dbJG{KwU5gUhGz)KLzZK3rkY%c*g;rVp`gDvp~HxsV=DF5=j{NAzqB)EJ5cfDSM zmZQLnN7^Pfko!Sd8+pAD#Qps3fuLDr$a*2iUKXqF^B9izRsP|5`8_1@b)R(Q{)bqz z*6FN+u^Jb&P5`NE>e2i|0ckPD2akjA6g&<-P=GJSPykDmJ06gh$!rI z*6BV58c#a-P{D)y1h~5J?Br2_R2PVC6rehz8G`yVyTA~k;1>MsQS*#B_%n`C506!m+fLoGEK9Id;&#v9uqYUj}bu zDHMFpZ3OCQgazmhM-9*JA3oi$5QjIZSz;Y#_HBI&+IFXoT^5?MHL%ON@NY8*Rgh%& z=GUlz1~?!yMV%?2?m>+T+H!Ymj0M|-mR5kqSimz_dm(*!TAz|M6I@#(Rx`p*18M}V z>4DdFTA+TX2C1izbUN#Jb{_++%YqK?8y|Gx-^L=~(&3__&~mbZ&$H8)!T5kn2m4D< z*O7nQLC7u>l?OHlN_jlGKf3m&Gd4eye_h1C?V!pBn+K(Q9^D@u_kh-4H$UTl9Ssux zVeF6_qup0e}663@be#kYd7eS zL6F(txxQx%6FhodR17?skAspIj*=F0`Kk|SG6Sid4Z7YPHgJP|`6_5d2spZ-_qvn4 zeDx@3s0(qS`+4w%?yEt)KeS8TLFVnHNuZPE;5$S}yx1MQ=oJ*(|Br(<+$r#H zyVQNE`H{Vc@d=M!Q22w6pk!eL9Z-Jo0bBPc5AF}(G~jFfhreeM_=r2u-71}>3@#lE z#s^-D>}dG^|Nm>&9jE{Q|L=L+6*Mr(06OE-0W=5#J=?q+n*X{%XY(S4@ghLiH^fID z=7W#qp`HT`86rWP1C82rI0`N@5lsisIna=XMI{3R!wIl#9lXIX1>O{y4R3ZVfXjjw zoCdLOw*v$K8c*9~B z6Kv*vKfH-?7QTu{gBdpSZUaBE#u+{)4_fODu>*9p4aAPu@KroPpcOmd3wgjtvV&H( zx`3C|fF|NOK%*DX^Yuaf70>RQ;Is8ld4L9eZkGf?FUT!n_vvQ-f8n*W2lQA_3&<_9 zhTvOd!8hiDPW3HO1YKDs4!ypp8|@ZZkM3ijCL?q|XAbPlZ}1_}po4@qfn5o5mITOI z3ZQXYh3<)A*KInCI*Ii;96$`yi|X}%)qb$Y%gf3`*JYr4KoA7GB68t`TJ5Z>kBgj z!xAtHG^@H8%mQ8hz6i_$UH-lh%mS5{@WWE~gOjwUhV}VkB@fWldaom6r?Un)J;4%O zcMSY0jOJlA* zgH}N>fRZcRv{R^w?}A5XutW(bH1RnrfKs0qB=!9SrM|QYko5Npk^Vrl_6FVO!Kn?L zWWntfWRD^FsJleP!1zFCwFZ(Sj1L@l)BpvHM`t4_`FeCZN_ccSDtL4@f-+lYBPiz` zcLXQU!^Q{FI>Gfzx1-L%7t9dJ!w$iq+R>x?CivLRZciQ1#Gdtqa@AfIL67cJplKjT zYun@U1LP0^Eis5b3<^aHP$}c!(OY%EgZU>kH+UQedj*scJevPI6zRgRjt8BMV6Y36 zW02PhJptw4=JyUD#l?3(WfFfAX!ls!1kA=PNE|i-1ad5>F)I$++J`dz_&OczDaZz_ zgW4Zj4}cDGK;2pY1K*J!2q%`f!j94O&_2|C4s_&@F?8U$m&M4V`8@|Hq?-S8@Hd0b zYX&tIRKZ>a-8l~%x+)a{#kd71Ro1A04}yq33>E>WF;IaB6$G{9y1;7+L2GOv?H$>70!=>G$c?$hAFKsuTXVF*~}g#mb37jp2xtcJ)#R(0W)hb-#CEe~1Kg z(4C=R5zx+1(6kKX*iXm(pe=7M+8P(Xasd);{4^Opk0Jt!4ng( zx$89Rd*IOq7k<|Vj@?I~gAP93dsIMy1ZqN8fR5%jfbX|%KH>m6=P5S&FlfjGRFaa7 z5n~jfBl;jU3}`7Z5wa$=UIcSOWW^fGy+F#TMW`Q>5LUi0@U|^UHZX<%m6d*c4 zH7v9-Q?L&XR*4 zXlDm!MZj4V@I6QMEDQ{g(U}(b@w*e@vNPbaphNQ@p}QHr`$&)#Hm;%u-+iPDKa$r7 zzWXR1zWe9_NC$YF;sYxKLj~AxUsxF!%E2tq(Yd9w9ySJsLNH5!je(&N%o1T^U}yldB-j`j>cK1-HU@?|FiU}rfuR=6Qek6Y zr~$Jy*cce9!7Lp%28Jpy3)C-=2!k%<0bSD#o`?otn0Toq6*RyCK2Of0+dHB4WU0;s z95*Q#BDLAF5e7D`s*Bl#|b^zr(4-dx8 zptcHV;IKPJ1$6kdf@6Gq++k3=7X2CpkaLknqd=`?=tX)y-QZTc@omVJkG#0Xi2U&Ziq(y!o~sD0%1!T6iX|9k1ijeZKjyyyD`*tW%h4{_?Q)N$$l z2ig?_npx>a+7*MeQ9ul`gzX&461MJxpu4ppXFG%U!&o2VZ#oD%l3CsbbkG&j5zPsp z^^4^1fB}ui;vHwhrvN<8hED-_oDH7>@HiVj1>kWu>*S(V^%`4Ol& z0^aNb9q#C?)_HjvecKDp1Dlbyy~Kes0(el)ryG6S3;3+^Q;?=R>VeIm?JS@HXB=lC zL$rYRyMWpZxHiOqG6G%`Kp6qA0#HW4s{oV{a3}z^^+DTGATGq%?*bME-)sTh?*dvE z;Mm=>2Go#3oYjnNqYGFoBn%)uBGB@1MA)T#N=4r(>W(zJxLqTnph5pxh7GvQ(z;H(oY3=C{a3=GT+ zui;`m@ckyB_9aBGA6yJ{KR85e0$gk(oOK4y0<|h3qgDZI3=FJbJ3`nP7+Aop2sQ== zW-u#;je&s)%t~NmU|uDqv$^-~-E+fDUQ~vnoLQ z=E1BQ&_T^$Rs$OY0~eSDIs%##%mN(&%>ibCj(}zdvp{Vcg;4N*6$Q`a7l_>#l8!qX z9k8wj@qnC~+=N{gwCTdZqqmmHO`xXdWKj*Fmco173jAJ7@tQXaOVt zwlE?7ZGOVNPCr)sEoE)~%Luw-hO?Bd^=&EB|AYM7+!?J8@;8I-Ipp6aZv0mJghyvS z2k25!&=KLyuNe7Twt#MoEu4ve{Y6C1l7IufO2Ye25hh z#w%yKjP5H+}-I3^)l|ShEKEm3*LZ zEP$SXwkp_2}J+<$9Tzu`xeBZUFZZ)kCE`y3R)iX z0i0S>;j-m$SB50E<#AHy@0AlhQcnSumN6?l>1rPAx1t^px9e1!`Ul9lz zRJC;6!HPo`G-uxJ%;9Q$p!@p&3#|uA>ml)O0M6}@Id#zDmPXKAeSO(=SK|Y_{{R19 z4^mrl#iQ{!BLhQ&2UJ#@AJlOM&BKG$K<*9wAI#Bupk$>-<9m=ksM7LVpjj?qkWz@M z?&}bBjm9TI)2d7i46xb2&TjB%X{Wb@N2j*}=p=0Dlsfp@7*NRsb*mgawm^rVLrMxz z2?Z%BY~Zq>L(mU_V+&M5L1dA`6t+^?4tw(Qz|2CR3}k!>l7+e<125f&J&uFZFsK4X zawO{3Hb`f2Oq_C}HpZ;KBUuHS0dmS_)VIG#-SPF<1h? z)%aWMZ68Qc0``9?XsD>(!}?vZxJUO@*wD9!^-)LuDaRbUkG)Rj-{x+@AAW#;o4@I2 zegW{VI3Gx{2Fm=#2R!)K`&)eW;MY3%nLiS|h=*VQV3F8o{+NUO`Ui`7JwOZ5K;hls z(S7i<55LX{h($ikCw!S#9CSn%C&nK7#NOJ zfmm-rCv<{kJ-Uy0bVdkxbY@6+bQUNe*A4+b-H8^S-5>ci3r=`;|KQiG*Z~UH;|?64 z%miMu#R3S<6l&Y+<9%r9UI@{mXOSr6@QP?6{f+7$~D zYWU1A09p{j016xaZO*13+V}u0c#b22=vWcYXa1OD{QAd=nLWFYe)izkxd3h|UhwE` z0Z#~cGGFlMbWu_8w7%%c?|u#OCQ9@aq5qeXP%UbpP|{zUImA z_R~ZAy2r&=9?aK0tgn0UJ6(Sr=Kb?qMF?d)XEpdL`hvX>GRnFk(>%NBMu9gf?Wr|HZf2;3**y(R2 z|Ns97T`dGj)}X5iAxRxnr9A>C^(zbv412*z{TCyQB?>xI4J@X{1gpq$nHU%#8DwiTupl%Vj zWp+X-n@?}D$7^Rt&>?UPV0MT{H*#cnbbkeh8%WOhl4tiBXgLkp812ys4&T=QCHbJC zerN>xb{_%j0c|RUT-fK?EusP%L&nGey>*O8nZQH)kO%V*AM5|cww~SpL9qs&J>+i% z-G>P?G9T3X@da&z2KlBt1vFw&1DgnNjEjqnJ`9?16huU3=~<8N@2`2fofAM|#{8i( zJHeyd*}}*AM=7g^^@kEqtZwITbpx$9p5}pAN8(|9+<|`zsFB~~(S054Ah^)W9C)fo z`v3nws1k)VLUb4y7$9{B;wBML@dzn0LF66f9%uU44M}NEhd5{kIovE z3|~;6*Au)Az}v;O^(21_=%!guOAY>ZW6-c?_chnnlO^(L@J4I*A&*|31D=*T9>tv9 zzy2R`w7yvMy8F9NugEuN{_QRS0%;%T)%>+@6%?1|_{OeDEtFcp{!ibn!%)2`zfCE&Oz3}T5k@D#bQ3>#2e&N{b z_}`cLwe^o;{a$xQk6xAop4~nU93Ive`I|#PPIR?QaS-5d@nB?N@NBkm_+P^A+nfF0 zhxw&v_a$f&2giNufs!y#(!p|9xSdD0w}(gXWRN;g3~+aQ!%F@WpyLCWJvzC3x?NNP ze7nzrt66w=y%lubjz{-yuukjuMH(K^DC*_;=3!~$Q7#T@ld`#3CMT4bL#|Zz>8os861_pEZ?Ax^ys#6^5_n6;_zSwXK9d;pgFA~h@-1Le7lcBmBOULVfht&Y5`_; zEyu{NA3$vqTaWIq;OzR{we<;q>jVY{2AGKjpzP`g%C0pk8K6646F>uu;O?+voMTKp zIJ-V6Ma!=LUh{T4CxDVK^ADHKVsL)-uzmr`uRlt>LGFh-+_(EU)FxMG#UxX z#h~spq#JSsbk8(6pQ^)4c_+}OKd{&oCRiusI1?;q|AWg4GsAK==#(5tZIE{GBnSWY zgNH$9pd1FRitRplxcgxD#fvW*A2Bf0GpI5!G}v<=f(q0#FmNAgepr9;1^29NT??NU0F4nqbR33@J%h72;Zp!QaF#!ul>=vi#=CmKCWEFAdcdq6&=en-HGzqN zp$p6cW&2Jr3v^&s2bcxAa-<#1S^%0{0JDq_xOAUry;Q=-FArL2#Nhb<;Kf&^KOLD* zIx?SV{;9w}{lH;R?V;q+?Zn~H>B!;H?IhsQ=_ugQ?F3qVECF8U%;?d5@Gx|f6<85; zD+@>+oGIbaeb56m{J_9)7}U+r0&m{|m3P@-7HBy|4w&Tus{cWonIOxRJ4;jyKuZN6 zcLjM^ZUl8>L0j`WL1QuCvbee0fw9yB?t&W7rkvdhpeo<88KfGtn6%LgYkS1Q(!7+@ zquU=c1PfZ1YXPb*3_N;+1wcL*1pBo00BEa$kVp6F{};gB_QR443=9v!MXVkJ14FMc zsFw*k-{v}4EEZn0gSG@Ig3}w~ZgJ2!6-0*%|Mr9Y+YWgie8J&kd?4-MLk_4X(wvNW z96-e|XqN^k-LNn?{y*Wuzx||7_a#U0(nM881_sCO1JHd#j@>Qb81!U504gRff{Oys z{ut0Ph8NNt4?YByRXyPSBVajLF#)=J7h1wXlz>KA7$8cZ#RNVj&|(6g5@<1jPYJY` zz^4RSOkh{y$iE$Q<2DDPm_YK2BPblxKurXE1p@=<=sQUM1)XA41I{0yd*eZ&&&&Xd zJqQc5QUbzS51&K?Erf%JeT9o5$_P_2x-SPy1_ZV;#gvp}n#Yr!mVf`Dvnb?rU~IjGD69NEw$1rY+*s_w=-PN1NGt!@S- z35YbPM&wywY*X|S0^C~CsZ)5W44PtcZKInPyg@ljsfrAe@oQ-*!JV9H59(5l)3_4=% zJ9tddr5l<7p~|57&&imlq5I%rVFm^Uh&s^l8u+eB$S#%cPmbL;__zIZZ2kd0wyD?* zYw6N`8tSBjFPWiRm$YyAcK3i9cMPCn4m4QR{F1Sx0z?FW2#w~SjQp*jIjHW_pzRO; zFF)w@mQX%+`33*B6CTFro%pxy5dRL#bRF{E)VFoz7jo>5hpwbeO-#}Mdf{uJ=W&m}&K}Rh!Gi+plY%gVI01aqB#J<4A zKyynFG5&1_L7Tgc4?=gc@o!_{ILrf8$im>!{6+(`JBJ-w^?-_WRnWWyc+ZYBxb|p0 zP{Q4P!lU_!26!;m_yCA`c*j4`cueyVjl-a0PI|xvJZReN9=L!9nIi%+SOiu-aC>wM zdHjC>HsUa-EO7!?X5eYN!w?dro~={Rr_+(+Fbcc*jfPM6NpQUfI@!pjThJBMPhAAQ z9msBk*+Z*$=2a8UuN z6YO+R5%lQfQE}wo=ONmC@Zd|fgD=<{9x|#hcKE1>b-1XAf|M(JHXl&wa8VHl9eN<) z(aYlC*~`=5+3CsQ*z5G)v6B(xR{?$j78QO09~A+9K_-4dF9EQRzySpfFQ4v%Acbt8 zeg*>rD2PKK{wVSB==S6Q`PZ3$nW z+3CUI*z5J*v6BOAv`Z(~VdDdbp;@s;MSy=>q>QWa0oU#ej@>_8yKi_j|7I$(@agsb z=Xvp^M{l5<=l_HLy)vAj6ZQ~e2mITd{(JNWF}W}w;NRx-&xQE{xLkPQV|}}p&5?gw z4Yw?41f0XUmxYP>IH*Y=aF~CaqfGMw>B9|{{-qKPm5k}7LJgHn>7_gkmCV7VkfoC_ zw?WPY4+Gt_+Wf}Aqx+L1q}k)!{Q-0YWq?QbOW3iN=RCV_{=W)2*%EXv3?nnhx&nk- z8){Sn{6YKKgZa1ls08tE3sDIyHEH;jUaD_(s#M!1xK!Pz`}A>k|CJl!!Q+ZGpd&4> zd32wJC2d_$cX-EZ{f-co0La`;hmT4i$RWWV-RC-7RD!?`U@YbM&-{9V@d4=Kb5ICG zfX=-Ft;{iSjEj#u3|awx37q;JLW4oxa}4+Be&f;n2Ice&*9RWmwGp1(A3eKo_;mmB z>HgSg4{{x-4aVt;wUh*n6$W_phMsWT0h$Q`jT(A*cAt0rf6b%$hd6)hG$sZHMmC>b zP$|OT(d~MQe_JT%J|kDq__Xgmk4|TfZr4kl&H~-8*E*d=x?OK|I!o|xb3JvO**`r! zxFeXwrNf!krNdjsrCZ#w`_yq)aQW+T@TG)@@hQiHk2qY7LwXq;yFuIhBs`5zpbL3G zg!s3)o<>%Q5I%!l_$+qebJ&H?V;8=FUHBq);Y(=3{M%eFBYOwLhIsr0|2EevFx9BS zSFsCU!!CRsyYLO{!Z)!9gM~mb-hBw-kQWlJ#v!c?jwJIhc^rHt;c0vkWQ?nE$W#W8 z-e6gmPI1sghKvjUwqzN{|JOXNkCeK)Fsiv4A8@g7mMJssW=Aa{9Qn6}NXR<=KhS*w z5>^)vKHzXPcA3i1$weo1-Bz0 z7q-|lz^)nqwJm>v>qKvOoe0_<3~6tGt}=lf{R%4XAV&$nM_fI+XM%e-pi$SKpk+If zjyp8mu;wk;>>p%>flqg_1Ze2h0kqo416oAzw}5umyBhyDzU0w;+NEV7}pD{Sq_+c(X(kvY5;0Nbgl;}aOtd10N0r>WkG`4?@B&`hGAh7u8^e` zklEGaj@`#!e*gFXKd5I3X+MFw$$!CV1vKRh86X6mG6oq81l`Gg4O}pTw&6fnuNfE^ zu7brthX_MhDvS&a5Lr+^1TuWs4Qt$nO>n6d=y)89<1Q)|pz6h^*GI(! zJl6->it7Msr*^}xWCuqUe``EwSz3&W$?L@(Au0y&y3i2hEMrJrXasQ)BY&$ABLl<# z!{8CyZWk33<4Ycl?h>E^1LTqzSL55pCq0{gF%`-C_Imy2muKML=E4)pd;vtoxiBB_ zXgHhp*9ONZW>tm&!-61L#o%2gTTV`*UD1ldn z@NZ-D=q+P}REZwOCk{U1a5NU_@oav_So+vHL?xoc))jQNgv!7F;Bo_!fI)fFHvv`z z`t;6G0i_PelxeX8Y^F3OKK8IjZ+`%20AmLe1L$)05ETcH-cHa62EPCkzkrv3NADys zPtdV}U%+Vrzo6#?egUry{DO`P_ywE}@C$lw;1}?^z%S@{fM3Ar0l%Q<1%3gq5B!3T zAod5(<`;~mYxxD3I_HB+vt9>Akg5EFjQoP00x!e<{|BudWq_2)pgswt)ZNCwzyN9D zC^5nE3TP)Wq$EZx?dbLt_%H0i=qv#l3<6~iNaql=E<*x*q?m$7_brd^pRTP3O4U8O zJq0|v-8noOe}ST`ECn31;59j+9-Z+Ls8Ly_2Z~Ct5=eYPVpAJO#9sobhA4zae5ru( zf!Cs7q3>1H1*@G48NO^BWJJP9GHmN6@MI44{sH28d8_ zH9i0;ycj&1k9ZsgT|{#iT%;iU3tHFY04l#f`nH}dVFvXUx?@xdJX&v;Fm-|(vlXC~ zL(Cqa`{P=`%L*WNfU>Fq$PP4%S`UEMl7bhqm8gJ|h4}+&!PJlcR02-l# z$bq|CVGIlm5Ql-9sNCQcTZ7t(T!Y_~1f0_cXB2JTck zM1#{@Cb;4Qt-6Br!a>;;(hCPI415Gmk$&*u*fe-zD1{Hqf*b&;yx{5eCg_rZ?q8sO zDzjrJ11OO)fsT>6UBc|y$p~^~0I18FPS~q1Ag>m{z3QR@_bMm|AYKKTr-9*L8PKL) zNa%qI&KS_bf)W)_hYOtOPy2Kq1Fr>ut%X8W09v^V2^vuQ5fU_@oB#2zsd0JbUxwP>wLqr)A<0TG=}iJZ+LbF zKY&(Cpgw4)^A2b!?s?o9Tq%JGkM0wo%8AvZ`AEj$<_ZT!kKPHOnPAX;F%JBKOrJo* zq5+>kgQBIv9^DP#o_S}00BF7wq{X9i0;szMJMpPuA))SH( zxb+0;Q9hMqU|?YYZRSSQ1q12~L}@#q&VZC>(1hgC-J$}TMf8LwDBn(RSc2;Gz5z{0 zkTeCFwPo<^blyRqlmt4M9FmeiOQWGFsriiuq-E0SqGAAQ-D`jd1=sEqD6M+XK1uvK zAejb|z(D-~gbv6!aHvP`8kL4O|Nif25MhKY!}8F6fPA8IYl1K%1N688*Y1lx-Mtrt z85w-LZ+LWXy&=rV;M4umqkHcI5UbO5hEMk)kV_LhK+ESmI(<7lI!h;%`Wv4-4qikK zI{OpUmuWz7`4=60$qHJ-C)gSQvgsjXCz#O*UToAkH$<3`p>t}CFe3v(4`>+7z@xV} zfsv5`WSxd*_f?bk%4URNV{<>rQf<7u0KAoiv{DPqi_yt2$GWZ33H~6-G<8M)5 zWnf6-&p!lF?7?4m9Moql0Iji;O7q~aI}IuVDm*{~0$*7`M_bjXWW4qSW!wtTF0YK& zwjPixK|vnK@PV$1=!}8b=g|vZfDS5~D!}IYbpPWQblm_F$lw=rUEtB(x`2_9VFf?` z_5-D?2Vbzf1`+=c@Nes#!pO+Lzy8F*hiuJzD;OCW7(0DbB3ybGo)BbUC}nqSJTilk zkpZ-i$oGKb4iFn096sH>AjLl2ptX+OE-DF*;0erGpl*G4tA-G0RI=9qM0L8}aO@3a z^yqYb(A}ya#K_R;`og2v^@C4m?g5|9&=WqLt`|Id8^GOu0Y-iSPXXAFfS^MIzkuTc zenF23`~sdE_yrvn@C!H|;1~4Rz%Sr=fnU(!0KWjJMNzo5edenF299?h@7 zg8*Q&_yrmG1w8~hw}Sff(98;|@)AIcQJ}NF9-Ui3>Bpt}U1y0(faTULpb%;4VqjnZ zd8(At2eg3A^}uTuk8a-%(C|}+Z}$nGZr2&0b()ks<-@>C8oB}~?1CYuD z5KF_O+jRnnfk;W9qh~;oQ{mE`qY~iC{M4trv;iF3;F%zBl%qtTWA_nIy5schzTSP} z|Ml0x;G}YjiGcyMxU&0#@yY*}J-TmrfRgDJCI$x3n)VJs(C{s!8{xPkKmau2GIa+d zC~CSv>tnzX>CxR<01?^`I!jH!b%9H_k4l73Z!>67utz7eNAtr29^I`SAVrTovU-_|;nk&&Up+0>)E6_i*!I-M=LTR};MU(i{g z)7inJw;5C~@(Z|b@aXIYT|5pN4hY~EbXI_-KaWn=j#7We?!O+rp$9yee?n57C-YC( z7%SL;@VEmn^?^ATw(e**$PUj=29Hkgf}q!&U`s$Ig9^t4k52H$2B?M5eX!koL8$>@kg9}Ixi2FUQ4?tJw`g9)$X@==!`hUU0`gm!uWwA?HME9}($3RP|J$gkr zJ-Wdj@aSd{^x!|?(Fr;g5-Q>%$N?4t(GXSq+g$_&(hi>BNb7VF6tFIKDcbUy%cFBM zC!JXm&&^9&jLST^f2%lj1qSW8F^;-!jNqcmkcKm-0(dP_hPxppvqyI? zs4jtxFnTl|0o6HL#kxp~`_FrHAATAA|Nnnbp9ZrwFvGTZoMdKTfE-6% z!vbSmFc*L>t%R62 zi4|rCX!}3JrN(S9b+&L;E$EtiaQN+GgDq3O&&I$2nVEPG7XuAXdUU&}RDg<30}!hK z#L@r}kdhQUJ_*`82WiPTggS;ehIur;0cADhhDeJFXs0=-2*?24$=x|cWdnGP#V3!> z9&p>vqx&Uz8OBX51_tn6feTvT#SEof;N1HXv~;nd(p15tbB;;|DCc!(RMK z1=R5O>0F}%$^oDQ?m$HvzvdPd&>cJcntN10TMAtGHCt33fR35r*X&Wbz`(#zChxdo zSrFE;f`6N<>1!7LZH}P*b>Mp4aW7~w3T!Kxi}s68F8l)EOz&#^A2g?@;nJC-V$fWh zz)Hq&X%qOjxmJ4gdMbQ&UZM_)xMQB+i#b{N zn?SQHzM!^M_dQSTo4&n~j2_LeS^RrNu6P`L&E(fBGv8PHpU3~hzP&s%!IM?KmG0dx z@_!t=ga0^Qe!#!YH`B5Ez~x8H4;Yy*abIxkWf5@HzHsr^#UCJ#@JAm2@9uNt7jyy% z96}cO!@mt=7>l)Iw=hXYF{Sb6p_s%1G08=q#j!h>g(S0_IM5B_ff>f(*d5G)!!S(m z3HbH~DL86h@a>HjxcCd*&@Ud%uNnDU_x}6;|24C3_YZJfNT@L|`1OkPLo5W{IV$Aa z8-&mOKfop*fQ6znzaYyQWUHL{1zpatc>X`=V|}h@#mnug3=EF{Px^uqBxrOp&4oW7 z+_8bB$Xm$iQ3aG9rO?vjgEanpkQE2OY10whS1I}oN|Z;^_+Q^hbL7uEn8yF_bQ=G! zJK$ss5%_e2Kl&nn^l6+4^EG2>vtO?WG+|!jZ{}8oCd@BMFpLDJSdft%F5S)?c#ORKz_&MA!A1L!M{l$M7Nb94 z82!bg`8^|l>r(JcaE(d=H1S^JZ;n<5C0?27pv3FkEn@B3&FtG7O{#}LDfkMCt)L`) zg~jv#As_2&MT=g3Q3554YaRz*F@bvZ7Ot&t`CCBy&K!GPL6_k2x7+{~xxG0m93XiI zU+wQcy@{Yr*3ijl-%8i+7?mH6-7zYE9J_5)9J@kFe&~4l5jX*zcV#~5%6ti|&V~Cx_wnWj zj4s?Kx{rZrQ28f(`GKSM<;#yiNz191g_%G4z~u)n+J_x4KlscadF-Z>|DJyKirbiU8PwU-(-O{`>#`C3wvasQ3VFAoJ`#<_ijFPwnHNF!ll6a`g!m z|JXxV9TdXqpb%Df?+#HX6vCk31qCvp;Jx_6QTsBa9E1k%#UG&Hz4!y<1+eS!1}{kU zdB=-C;i~ZmFG%?{$BVz<%1H@cxF!)$5QBpkv<`y5^%yuqg@Cr3)Tn^B83usXfn|XD z@&=%;y9ek@o&q1_s{5rpC@UbU?)jiA9ed+hJfKx~Ahhb1>GtgY530RAwf})Da#-!{ z!oUEEEhkX<=!(63Jd0F5Ui{&B`N72>$R!XsIXNL0k`NJCAqgr4u#`W1#Gfx?`I$eT)1%i@0NOUl zI}C0C@c%mvtNgF0w9BH+fXWhK3Px@9rM(NR{pZ!r0vfFsr7w(ofsjt zKF>r@t?%E%MZFwGapLh&pX8*eG(C6 z=V7f;k2|1(!r=x;{|Coz#}CBmdIHw<0Ho`SW4GfM;&iv{pw^~15-@ds2Fiu*qE zN1g|j(2o3q9$#Rtboc&B@atun?%XZH-hJA!o58o&fd!kH zAoKVmA%1q|7xcNp@)?wHu0Si-{V%6WgGvO~Owh_MP^xrfJ_@R6U762M1W=% zT{?Y0`!as9@Ha`ZFferIs3>^$x`6827VsT{zTNM9x|e{N9=%R~e6`v;{19WrzM~}`H70~5vKHWb-BQqeTM=uL#ZN6^j+aE0olBI4Q|&f(e}&*Rt~FW}f+F9Pxu zk9)U^z5vKqA|Q`)pn2``14rh|U@skZVLlJy9RfN16v9{F630bFfm5+y@PQ-qAy^u9VLlA9 z4wNv#ae5vUji5k;$JZTjAUh&Q#SM@GSQif*T~EM@kkx?Nm1uGL2C4`VT`xd(gHED2 z2e%GX&Oj5;2S~G|_Rs(S9^Gre16q&<2)AF%p#fAV2| z0pYOtFhB6s{@~gjqVfaOSOT>v|8$>q?3Ph+?5+Vd*k_{+(fU=1r zzn~Ym26Y0}pw=H8yPd%`C=uGgRj3oF3YGuj*zF9iLW$4@u0x$bb*TIg$8KkE9eU#O z0~8m6vI@wRAlJde+zDET3W35L6o89FKy|1KD4;}UICqPPJ9eLRgf+z+yU`5=8HnU< zpF1p|l7Z!p$N$3~*7u6`y$k{k9>RtSLHA2Qh6?3~Zfbq^ZM{?y>e+q7v-=V#lR_F; zj{Nx|d`Jzf3!tVKfAlR-6N^9cP8$EOi~P}-_%#mmM_%C~b0c~G_Gbl&r|!CZ?6}$k;QTZlvwu>`Ua2|J@yF(12O)lc}Jpk)_0rWUC1cjOmz_yB5zfi*#gJ@{blt~?=7(7C8M`1P_(aPAgib3|%* zVKEhCBFyXHCKssTls*zKbC15(I3c3-;u*tPrc z<>xNlr@O&5&Y{Z>v@d{^`~i(|9B}Mr(E~9+Dd7}InC0?=ZcrVheF7~X`GXr_CqSX? z4sL@v^5-2!SHl9z4ktls*g>^978Rft*h$CkvmiC@prPF3pwV#DcACEkSXRKbJB-7% zJC4V(J5Io{yG{h;K4^;z&1W!A@qqkt0HjU;#5w_DiGX~heFEe%Q2PVa#Q@c{pcr@L z7j%~Z6+$3MuxA|k1^wZTuoEyfkTw|9Hy|aD7MLS{-bt7eNDGWV?>Hz(KuTckFOX+p zir_6Ue-qd4FbUW0I2p(8I0eV_-K-A5~z!0{N9c`lL1_o-8o@ zW&`p%$hUC6IY5dj(6B$eq3rJhjsS3D>!fe*e(?NE^hw{|GLUM}0fw+PiT@d}f#CAv zEX91p0+P z`XtDg+9yCZ!)$T~4{RR>TY3lPKTuR4)dc=eKoa28=?)r4K8z*hy#W=4AXD65q=6&z zB)TDAz_K4ao8K_JoQD8ercc6Bg>A zF^ldP&|!~6HNZf*fWf&tMD7o2`|AX#{RK*s=oKnBkAYfWphO6oQ3F?~UZ4t94&DMI zN+Y;N^#awXa_}}7Q5wNjsu!qAm4mm!Fg1d55;XHVf$G$Apfv6Uty8&R?Jy~BXr1cQ z3vP!QId-2wYKLJO3o;VP`z+uFsLvf14@gUF&&v~_*;`mkOd3AuB?xJWg=V_K2fYp< z2EER?GGFyP_>##5H0t#ov|Kg<+!VVAo<9JmYS@4mk0Erh3taji=8wMlnLpq21GqKz z=>%xZ3s-Aw2edWD15QTZQ9)@gkoOUzxu790Po#-hNAPH_N3Y04&`>U9$O}3P>%ag% z&Cs#iMGV>=I|Z7#12x-EgZh<_a^^TFl3syZZx}Crh7<%4-^!&i8=R>y>$_O*WVbCE|IdJ>y9DnmW4p3mZs5p4_igY-2 zvlw}R=1IFxVHgQ&Y@O+L`hz@ZeH_&ILQIPuc)5TBH0%|c>C=7A1=RR*groph=A*_Z zK?^!7Kr1#u4KNGOW>Dh`bWt(5;pN-?9@d=l==JA-wWmCL9sl@38(;qqfCjxHK*~Vp ztapC`jdp=pj@>VOx__cKy+9*fpdkh5SXYQXXwC#Q*2Sam-W{UP;@Iu}13GX58#MvX zMRlJ8`w%pifQW5)g$o_x0@vEmAucSZ0}nUwfU>|TP&#r^ z;efQlz>N|VMc@{ei;4jH_!b9fXoLqeG$H`pD|jS-OV3}8cEAYYm|cDrjpMz%oHplC@CCCuSNDxesF1Se!n1?E3+%s@s} zkea(LDv)s%sQ~>b3S;Qd6(WeC zBUeZv?1~=1m?Kw;pxPTca@Bnif3Si>)DPNp@&OIKfX)g9Elzd-9h?9fyMi{IK$n2{ z_69S8XEJa5^!l?v79sJ>gw%Z^SHLTfWM)rr?sfrt!sk8(QQ0qF+UY8ZMRfaid)=y?Lt1FqFD^t=Gi0AbPd2BZgE-C^hf z4Sb-~cMzAM)OYtFH}m-Q^2~ScX5sVg^<;t6bSS2QYPvh1Sub!!01p7rJm4J`&;N&g ztnU>ac)694fdO>NGHCz6I#2_(R|Goe(gPl}_2^}RG)X#3R6IakhXCK+8Wj%Ez;1v? z_rVFq|G_mNXuA)nlIuQy@CB1kuWRQ211{Y+dTk(P^~!*0(EN>Ow*xyUVjTImarpK+ zI5;w&b?O!o0I!BP^qK$JflrS7kq6TFqYi*_t>X*NZinlR{M$S(5K#EWv)kc0NZ|tl z3O{&uJA4Nz{6IkA7fsK#fSN@i}rorUY-`u?f{TCS)kq&0NceP1c?evZ~g(@xg=9 zMa9GO|1r*N(gQjYDFd`$q}AZs|Nk!h8j!`Jp54bmV@05qr7rv$kd>tT8jz)> z{2Gv@ras+}C8!?VkR_=e-CH0_RJ$QdRzdd#d35)HSHdE#qXsPu1)Yx^dl+<{8)Pwa zC z!@vMhXDJ2~OA>>L9hQfQy^)8B>1s1DK#p|)H5L^>%Z>zKr&=|?ae(et0-t<;5p*d; zi#Dh$QVCi=s>lRtc{*mg_6Ga_MJ{Mlm}_qYtT*BUItIkC(zp9OC<;JBb1uv`K%Em$ z?Qfu-Ntg#{J^ne*?u(GKuR&G+A#m@6Kc9z>+|J2Pzg|ec#Mk;ff3r3R0|V?dbINs0hH;Ie|#r z>2?tV7uHC96Nn6!z6nI)Nwkk_PgLkh;UpI>w_CCqM&rbY;Ea%bsBw$gO^Pr`gzSif94tg{nae$urbl8P|TT!KF z_xaEK0-${Y46e){K;yXu{M)iJjW2l|1Pv3v0A+IvP*=kMlxsoh$VdAf=*T_~aDlxP ze61*Ce%`Y;ki(<-00-z2x=t4r0nk`6Xt9w1c(nNc0Z_3I9v0$2^iEt<6!_&CT)N}_ zbf4{(>#k#Q>JHKS;nwY<_s6Z9N6)Q0M31H04c0Yr>^=vYlYanhxts-;gQ$IzQ=j=G z4}l^aZ{Gw-70$j1k{aB76C_2jzKKhB98Y(gKzE%8$S)iqzwo$q`{)UPJR{QW2J4uR z<{2zK6RJ8U?U75d9LD z?l_(9ID_sw6OeB-K)%s|Hi}^$0C^7HEdhDbq}xryqqocfYohziABhNWP_G0Wl(6=) ziwdY$atc&@f_szT00;F-z^V|j0_v53Vg;cH)GGliLc|KJ(TuPU)GL7|f)AeH^|PD7 zy^<~9CKqS`w!)*EM+MZ#QUHx1fC_r>h^+{CMh@KmIt1!l`1Gc6_%Q$Q=>CWpqxArd z(Z;C!@$Icw0QWD>LZ`<=bU^(J9ngA99rx}K9nj1TYX1Tjl!QXeA0xorF~Z9qBe>kr zL(3l{upp!0NO9v24=VQ$9?kC<`CAph9SpRP0-cj*-~+9tKt-1Yxa_S4HE6r%fO~j| zstVM+0+omSO`!A3e7moE^ail_YG3s2b>{GFe#PR|E7Ap8<>ueZa>7IVgXjN4{=GaC z!J~b!`l|aZY%C3F0TDPxq19FQS;VLdL>yjQVd#L=R+u{Al@*2#NM(ho1725Q=s>Tl zAa45N3A)O&RSi;C75l?N<~6g2_6N{N8nn{tJ`3tcp%@D4U7SX%vkrmB(N4qatV15% zIVuIn6&L6TPDta+hXK}Dy2HT00BPVQF~J&mNX@3uFvk!_(18U8{M!mMeY$_~Z_BK7 zWc~oF`y82HfcEVafClDrR5X0MAA+ho(9k((p_;GuOVA!4a1$)SqZzb9Z6>th12wQc zdp*GwpHKHga2*F)od)W%a=OD8qAvuH+aINPi0P+eCXzjZIsFVP$Inx1+ z$?1T`43)LbQC~E9%$NA1y=Ks;ul1{ z=Y}>MhX_$bt>=b17>7`TsPo)VhvMK>o*Q^1&MySy7afpa3_yM{aqbS$0gc7!z-m3H z4YXZk_n-g&LB%xW+yPKMmjIdok?`q;6zm?>r}&#e*QP@U8^8l;pxfk{ z4{-P}e}E6B{qWI#2pS>x?JZMq?3M>*NzjT=tfS<J$ z!KMRR&0*+x1G3`>HXTG&b535pBHfPNEFz%(7-+B>)vZL;Y|Tdu9OE5hKo_)uPLG3( zOMs5okO0+23XWm?+Z;0;dn0~;BF(_Dw*uCKXTHI|&9Ty_`xfX_3k&8SkXq^-=n(QS z&+bE>po;|$K@T0gceY6jOcFjt7Hosu;>@8#QJotjivp0*w^Zx;lUXy7ay*XDI zyn02NCOCGx82<6-%~j|=?a^B%09qx1X!RWY%rEGAquUKM(2CM^<`;AYO)DCL5(#*P zE2!JU9|>0fpxX^3jIRC($R(LaEFS+4 zd0JmCI_S|I16qj*I*lt1eB3`|C}}mk)q~R335Q;cXn{FKOaDgN-P`kE!4Y=tG8X|hd1Rf%~R1)FQeblr29B3&axJe7Dj3EmODO*pt9k!Iv z*ZMkt^FhcU5qNk4-13I*+t36RLcLy$p56aFwSW2ax^jTlp8EH)TmUU8^zY^AgDok9 zEkJc5c|jq(E`=>91Q`JuSO0MN5jYmn*G)hdA|mvGmp^zs0qG-RK_NmPc=dzF8<0M{ z3knZ`=HEbNA87FdxTos@UHkxAPzVmd26hGp|6Z19PQA2ogqLP6%&$QFl~{ zKkp=DNui4h>Y73%`@jus7Zm}Q?l2DIp(PhRY-9g z-gL)(=&t($ax%6Bh6vw+7rcR#K^DA$ScnC0ARfwsH%K-H@5F&Fc>~82+M+k4WCmUK z2G)VP@C`{3Wa%4N4R|yPz7i2h4S1oUiwbl=DwVhaDbT&p&^9{$0V(jNje2a026cXb zx+?-$7HOh|p>wy34kQdgEJPTBc-X@b&niv0$8oRHL@0p=A7p$AGxYqyfd?C+0u|z* zsWH$H1AH(KIz%M`nN96ZQ7HhI^D{sVFXSOAa776lh~aP2167#a_kDVuIefKm`Sg0Q zc!H1LV7cIV@HG=?UGbq{!vs2h&z z)(>3Uf`+p~R5(CmCn#fDE;`t%T+pZ{#9xpa7vvR4jSFJouW{WlDqKH|`qmA-x+Q*q z3+!J|@^s`EbVC{83Q>^&c@Jfn%S8uUecO#1ckt>Kj$oC-C#8>cwCAfw30tX1*9Cblpj92f~aWE!MqBuXZ@~#(&-D3SKoj}!9IXS!M=bT zjIFYTdlg*Qf>e=KPrz1}J7O*`hgSly_2r-#!BNjbSD1rT;HYK&!1V-d6s(Y#Q83W$ z#?TrT|0r1a56~zWXfzBo3Z1PxGVb-r5p;OTC8X^+u;c*?GktiN>7&%G`q=B% z0SGhDxR?lN#cy|t3ew0SXoy9@2Rh0Ds%9ZIYY(_)-2*OGJ-d&4bi;^~b1OJ^zDiR-`2h&fOtAsP*eP@P_3F=+z`RQNyYir0UhVJ46v$y@E<0g4&?9 zE6840XY?#aRo0DR8?}Q@!KhP`1P`E1#JQGu|Ce<+`tH4BLg1(04>SUJ^)E@;LEqW&-?a9arkPV z@a-*U@oatxI;`q2Xc3QpuSpyDT0xc$ME}^SJH!~Y@m9gH+wG5UZ$D*Xe)XkeEE zJWvN(^e+Kk&=nd6YTSiFldA!Azz#hB2pN)d1+C}*<D`)FMCeSX1J>Wq*U(g+{$cF@i&sn+R+kFu2kU%lo9}@V2u@rPjAh_!S zo^Ige0;g~2QE`YhVD+FiV4wmuoCADVV2lcMp{~d&&>}GZUYTy_A~4WeQ1~Vl(9t69 z-7az@9U2H8X+=IX5F!9RX~?~sMUI3+1Hn3x4h;nJoxsNnft*Fsp@9&+pk^v)_vdHO zz&2yDHA_IF+^5s{^FjB?F?e=^6Dn*B7qo{4 ze5j=pf4+**Xa0QrhY0fLA4}s$J45gg`Wb?UKxYbpwp8IcL+}M-X$!wbiHZWs0!ANp z?1{Gt($)6pt>*C6zUkRpq5^IXc=g(x2lchRdQJMk%>ka7pzw9<4l(=V((Q)r5W!PO zhh%|{o1aKa-U=p;Y zUceP}a4o0^bL@5a-^ro^KCbsW6X@7jXqnub#{oL=s?!IwUsgrMv-voOr}iqM#mV{gobQ{gs|OXniDjc?jVX1<$xrcA_A-tZ+v< z@(v<^<3vHQBx;%hZCeMOb_6=>4DCQcuxfX3iUFOH11s*_LEG-(NeHAL9WM$qCk zkYeB7T=4Nn?q48B-n~o%wc0^dA1EF`gF2vz2H)O1@H!pe?mxcT|9n7ee83@&C|^K_ zIAVl1#$Gx6Cpc2&d_i!4xSwdTJl^)&2r$-GWxxd-fJG`gULU)xPN2TMb=K2P!{bF?sfy zY=N#Y;NQj#Dozl^Cj-hEtOSnj0~a0G%200PB9vq!QPVuXpgRlHnTTSPWHTW-1D+B1 z1>Jd|h9Zhm2FTHZppp}G9RR3>Pxxp-a2Q}KPhTr^RXhy`Z)Lw{y3M81h+y zIK6@GEWv}IMSU!&yAu#U56L50`@OjDk#hH!{F> zS4c9#7X9fm!^EyJ!^Ez$qAUbNw7Eg0G)FLM%Nx802DF^h2XrGiWWw9C`#55e!%xs+ zK1a|wC!nS6-50^7d>*)%Il$lY8Jw(ZR0@21Q#m}f|AE&5g3248?(dGh8UG#mxA|9+ zUY3KjWrCNyqZ|+0eU=(!xr+*V`QoC2T)=<^XQAiAf{P3m74#CuMFqKt0jY-`5DQih zn$!fH(hOUe0iHKPFJxR);H3=cj95^So}vO8@jM7BW=N);*v@ViRDY4Z3{5 z1JwVm<$w&-^rmrmYM=7}b(KEy%QHBFT2`PzKg{EJO+b~W38?Z^bMFo@K|d@Oq!T5; zsaKlguR1~HGjeH;T8x0IKIEVVsYWi%Aq51HRVS=8cNYOwpWs?4_vg1GBGfK7LD~rRf2Z&^hRa+XhSz%PGMwV z@ai==%fSG;1O`;B`}QU>f>ufU^{Olft&;X+{t7GA!STlpIy(7}YqyIExMj?ZRI+yy zQLwwA7wm3-9Qn6_iZC_TZWa|#>zGs{QR`QJK{wE@Jg_raK+XiWkV!QYQVSy$>~1^| zXNIV8fD8q=q0-wbfxwjSji4L6&_+CO3{wX8#oJg zm$S%+8-vSwm$R@1-1DGAcCbP{`mh~*4<2YpL;$qw2z=?g0&L-u0eDHGV=rjj?w4=( zM^Dfh?A@;*BXu`HBXv^(ag5Y~FHP_SHSc0nDq6qsL$6Bs&))*NS1^q~A5^65n|^q9L0$&KzsbE60wN7!gC5QXE@eQu1Y{1#Ni2@t;gCBAh%^a2Ne3T7;1_h` zff>c&*d5M+!%@gLCV)LA06Jv|d|^U8%7qDFNoa-um3%(dXZf3rnL%Agkv@n`EaJ#R zAK0CZa#aF2<+Fg+5yIUKZ{+w`pDkMP@)8pR!~b)>po_^tSKNX}JUqLRMm#__eISNy zk&>qhD0w1{cz}~9>a__NBOd&D$4~}5zJnm8fay(KWMz8`4x+QuSyRn$)Y4zq%N*^rBio^I-;ca&O|Qi&*2#Y0ac2q z#XLv^oNgVvMQmW@JV^#(sdhn00pvtPDUZ`cj4=d|gTNgvco7d00jFb-k%%%LyOE%K z6u<)^;7S_hA_Z^@%N@0V2bm2`&`ZEuyHivGe5}tFC;In7QnXXIh&WOU9=oO3#zH^? z<2c&xpk%!m)OL>m4U&V8n9uNN2Cr+5i$ClD-<9nSA0!8D+EEe*UFR7LA0#hzpD%-aY9?fn73j=NP=14rC*nSq9Cev%A&zCP zilA}u=7TJt1+Jdm|3T|pJ+*&{^m{~rRaaRtpTg4Vdkf@c?@ zYg}P#TS0ro4!&ga(S8SA+*+by0ABxUd2qtHK5KN_zYI0t{*@6oN}b~h~R-b9~BYw1+5`^JfQu!0w7O`fEF}D z7u&!OU(b)*=zJTVGA-;t!U2TLeU4;w^)qqaG#I~0jy!ZohmJRVsR~~1(>4pq6fChkJ3stz+2QoQ^3##t2o!x zfNBwxg*2eWpWsCUj@{=_suWN;09vC8T3rIV{sMfiqr(lPDi5?Cf{-rEDvualm{lG~ z7mD+cR*xgsc%UPF_*+2%3tB|u3R*-1T9XP+K}f4;P>e$MDX5ye46f!_E<(4K1W@F`?vJnHsp=n7TPvQ(e$7rxqW(05dy z23MP~Fnn3-T6`HKrT*S`Uyv!DT)u^?;TzVpNyV zm8dYUfYhK@me8%%NOdK2uQj3^af8>Cpsm)}7NUlLwpzP@wp#n>fbR4FtxE;p!vd-< z@%am~jU8N-BZo7hwuJ1u#u8cZx)QSI8su7!}wSY!@BSbsjpP>pXNo*LmoefVy|c_jwR^!338$%A)fh|4l1us2?WOvXwaRzAEI017HHfW-T)YYeFao@~? zSc?ix>ZCL+1}L~dt4?LWt4`5_3sf3oS$GOs{0ABat$@~$pm|kKZN#cm&_P+g#MF?W zMFp@aZqTYxl-d%stpmKP<1Ee^1gWkBX@@O31#N`DuL)XJf;2&UheX%}ttmm8pq)dI zCKSivttq9zr;uDPb^+Ct&fOv`pv(uWD9@qY_WHZ8-k*i;HU5>4S^)3cW! zfugY+Z-Vg#x5%7tfYz$QmYNbwW!~Ua<_sExfG;#9tO=aToIyhn@MWgxX#**hdBb}L z&fvR_`CCD$47AA91-{7itYbH9ktw=K$f*o;KiVP4{b+}vOH2=dk_(lVm^y@pdo;ee z!2ntS3chvQ!}tj3jA+m`9-vFb9Xz{#fQHX1K-Y+OLT?lYt&^F{54}!&3O@sbZ|i|l z=yl@l{0t1R>%>9Fr+Rd50bdI4)42qEJ|pNlanL>6prau{muLHQ?g8Ie4!Sh_yg&9i z8u+E*j{6lr7maKGa5er0Y7S~l0No4j*nPj$8G3dBu6w~#!Ao4vf)1!;XgyHE)%>HO zI1K1^a0mWv=Z?3i%t5*#9JJ^Vd_y?s+IuF4hJRdj zvN*2@=ilZV<ibpM>|4PRD3LRR4RP<-9Th#j!H(uFDCxhOT3^Z zU#SH?-L0T4Q~cXbg2s&!K+#a(*=y1Q3QCZKZ?8!MDEL4HJSZykxEUClUvlubv~z$M zjQlOXSs56be=_p73WKCbW!*t0T5RpjX(QP8h^oo8=v_LPTcs!UvLq$io8C= z2y$sqrw8k8pp!QoJX$aDx90x^9WMh(&z{}Ke7d)QZ<+_43hN9w=eRj4pe*t-n1_MEwKx1fc;Vknqzl?%ad6*9`yMzB7(h!}LE$d~3V%7Y z@COAmXrDDG4sN9J=O4nkoE3aq6KHh?NXbJ1(3;jX{-VdAcsQNLUvLmP9-R2=L*yXw z(1;Wd{LL4^)BoVO@Px+2T_5e6pymU7JQB39!m(S>y_*FbAE0@3U*riJ$8Mx0mEijl z9S^<`IQT*!&9&1-@?V-0BxYVSmTrBS`0xLJ&u(z41+B;lXuZwf;={Sf^viJXUtd~oVzk#o_$2U_j(*by|7^ziZn$Ahm04!#lq zg`wlYm!NP2Et2tIOmpvy(fI~S?K-d0+&f)#-le&B`sjQDrF@+){H?72|Nnmp+9d)G zLwnFPLiZ6+cz9|b0If;_2PEj40MNm!$3P8G3!m->{PN&?dtC`HIR&lFA!^wv;dM&j zYnEYw01hb7;uh3^0*Rmml$eY5wTr(TLFk9$#h({{I39e37Eo#Koguh`2y$E^XxlHS zvl0MmXFK?GyQma^))Qrb(pLf~)fs@26R2E9$tWv+gUSVPMnNvLKp6#;-tgy-^GGwA z7k_}S`US-^B$K# z2JQcFVZIGIz&FLS`-DgLbx-Xxp4|sMv_X56Amu5jj5z$6KObD)@IlI(PZvONP4>A_zgBKEm$$y~3b4oA1^!@e!Kgd6s zp515px7AjkUGzSrb%yAD zNptB8(R-EV(ix-oF0C_0?^9Z5jovp%LQDl88U(oy6Lbi8w-4yPOUNN+&~%srx;h|7 zB?FpDLsSAlrQ&N2(2g_*U+rs1DYV!f5)*yC-A8@24}#OBJS6@dy9K+?BS*(+P>%@W z2~gp|^#@d_a6zKu5;$AD-1!4334Vnq!PlUs8|aQ_-|nlRYRrZCHuGW7Oh2gX2F;zO zdUjv&=|1nNeZjN)l&|(VkUKyr?J)RKXCqLx#|PSp2C1#`4y5ttUxp^WPbbp&f8B&t zVVBeR3l1SyVb1*ZA$%V^nqM-OuJeLkUG525zQOkcbg$@rAJ73npepPLv^fEJ=N^p_U5hVHwl-@u^At=4Q0;ecYO7nrIG}lfSy-(m2 z_ywHmprv6tQhIX%U9RGz5`jP6`KSbdlAH%P@qtp?c^~a_NF^eF^Ow)y)aKDE(g#Ux zkZrq|;qTb#B8VITApSpaTD$ym{pbJxjsN?$zAZ@yS35q?8!1{Yl{mTZZ@c8!eaM4< z{c+!39v6>Z*GztS1`o&u4ov*pI6QhC1ei~|b+br;T6YJ3fSSaP|G>SJoBZ2CR8&0v z9|zs1>uLSBXq{*GpOD-l5bc%&;4K+S;o*u$U=l#u%nKx=Du zgYPex0$+JE8-5=GsJjbUdGmmQfdO*k!3PEg2FQ&Eptd(;Lu)Yn#)Am>jR#K|VF#Ch zws1ghH<$%qp>v%bCL6^CWBuTPv0m_F-eeFO2Ho?j0d3y+cK-xj`&{AJeE`%TE%4}m z1#R(wx4eS9>H^x$?ZUs!v(kn6fD7{p&}0(m$irmc?(06?_k6Xld3GQ31@)~?LHoC$ z{0zDRBwvK-Gk-of4$}A`4W5IvG0Ub;%q5|rVwwwWH>+hg7vjLvk-$5N!1)uI;p1qkIaQkcjICh`uj%9G^js<0B zJy3Sm`{UZpqUQ+OE7tA$!=pD>0G^xAqU7c?;4zD-BRP z=M%Vb^99t}f#&2~a8AwvHD+o+w|a}HfO4pUM|X@$z-u;7?eCz_gC4oyVSTmO+OwBs zx`*~FczxY1;L+>A;@Eu#R27^(_(~wHGlcDrV|NJKpUaORTmxu$f^wt;sQE5{6tZIQ z{UeB#W8kx(!@w)Y1YG#HRc3m0zXAn*r7JV|oXrGR<_DmZm;q`#AAH3G8W~6cN5Df+ zFTWFX^`iC%Q0Q}bXn*kO)KT&1e&q{}1e6=|Kv8ktu{)N*wL6vtv{Ubgd$$j0-yVye zGbk<`yInyykRu`kwP-z0ATpf5vEc@8R62p9!>tn(9Zul*a03^uZk;iDU(%d9L-f9Z z5*>eQ8Z<&YKuh~0K;t3_KHVS>fDeO@QE_<92J(~k2iS3+pjff@=w+GUq5TTnfP<$r zXv~}k#mpH=fl)1A!yHA2vhzodXpYZKI?W=tPbdh`aD_9Qa&jWX87*X;!f8KH8 zvNz)gP{5S#1T9_^fO*=}`cknIXpE+pWtxxnF(2(qh#6)`iUhYG&x3sJ)EOcHN{|fD z1nJlr!VD?ZuY!`f1Slmb#2-c^zhKyAf0TmHMWw>E`vjuk1GTO}%ia0gL5a?Ve_LRt zPxm1g{%w(!uFMxfXQ{d}-}G%gP?8S5#pQ;t_BqgToZ9z1yDxy_$&o(~v>Ym5h7;*D z3fMA5VjJ50^&#Alp`!JE&c#g4SxG-fA`dWF~PN)(XsoKbGHci9w|@_ z2MQv`?i1RlK%*DXb~VSpG`CI{jz8cqKLg9BvC-%ypeyr3cnRpr{L-TnS^_?Sm4GEG z0iN2&L3vQYvp1gubfCLDXdOJf;BWyCVw~xY{nK5`&|S&`D*eDkA9&cpwfi#oa_P&@ zT)K~g+CK+h3tWEWqJ8P$YXOkTHz3X_aAT%BMDGKLcL?09=?>BR0-DNz`wCQ8z7t66 z^wIbRD*ZG-{k{;5S81Iw8lcXfkH)*S&Kivmpnjmnr?k!z4QQJ>2AUBgpwp)ClCcC* zG9E`O8J&H5c|av2Ec=1O`wU8Wp9SSar0{m^^kGB^Z_E-hHu^AiOURETl#s67p+6kE z&%1(mH^(wKcGt2vgDwGw3@(9A8p7B40Vzdven32U=Lf{W*7;!VeWxfn@Nei8E zB%wTE`T!p4+2qlyvlcWj;bHw6G+uGdvzKL^ul6|)?Ypof?$XTw4lYJ$aDh&&K`Bri zJAIhI30wfu89WCna1B5uxdo`ybpW+BJirIG#2t2lH;2993(*7Mr?r4fXT%0kq_yQp zg|ut;3y{0X$ne1-ge0a%_|@=s*C_k~r90 z3~0tf`=)32El&`6(YN~q|27ZsJOGdIXa0O4&}r#u{CNlYw|Ri}Ac5v#4uYGl{Q00x zD0m*`U>g5x&;-H3G=9?NVfgDqgu%0zrClgx`Yjd)1|RL~e!V>HKHC31wO=4bJ;-^k z-Joe0eo((0JPqRk+9%n4qWK{s^Cj+!j=ekrj@lPM(=b25(=gy}x{n|vA@7G4^^nD8 zurst=dt?8@`gPziT8uPT;?sT2SNpn0_is<_|DN5~eY;Pfxd+rg7ZE}_1q2pcpilu# zn}C80G;MMOHf?e!jsG>Q2K;wAjsF*X+5{A;pdS6F8?Z4ajA;}8`VetQur;9s+hWkN zHSK$Ty&`Qs+W$O3T>%FMNO&>1ck_t*f^HDL`~Vb{pn=5h6RylBn;$Z|aGwMXjP{Cf zxN4tpy!`O;BT)SUYEZa!`Ur!EpM)V{w;3hue4yu#`E(xzr8!W~0X%=D-~+mH%eD0_ ze~Slr4#oF>Cyxp^R6(ovK)1JphHXGcHiK>=(LUwd{TQT$!$;hqKshI3~KX!b-z93*lbCi2FqS40jZat|i*!MRt2%~kstD2PFGC!pE`6r?W% zK*0*G$U!4uuLMBzCmf*p6BnIt;5ie}{7DFS{v-xGe-Z+kKluaNZma?t#sg(tbMSK2 zDbOR}PWylY?}vx>DbMbgKA@mW;sC`r=zPH+pz{U)ICh(;xOSJQuz-WmwcAGr6l6Mo zT)Sm-Kuf(qE&gZS*DpVAe#Gc_8EcRshn6ez?e1&Mj~HFJZ#i~{=zIZH^XH)1&Z(D& z`{GX*?bDzJmUE{MD6n|MK_b^-B5#~}dE`JM_h2F)9D8}h9JQ~3W=`-s6nd$&oAAe zL2T=z#YLc@3{Z0P(Z1=U{nJzXgl2s9xMxj-lzzVE1?5q5%rNB0q*P7ei_P6r8(&H#Z>#}G&8J|mCL2oBJ!S!clw zkM2{Tu{Thi&{=W8qx+RlXTb^3U@+wP7fWy+L>&JT4_c4V-2zTRzMy0S8Qb^Ke$lO? z;sfeU`lxVt^agPFbUy-Z`me-3X^C?6R*VkTNlTD+P|$ zb8(=tX4qVu8pfoh2?>*y;7k{z2A;H31EnVyHSnaR8hFxD?F)Y^3(BOWE;!R6h1e2s zo$90g!bkf7xWp>pfQ;dRJY4D89rwerJMNETx1D2m8Mb3`KvNCi#y{ey9FR`5pnBxW ze1Nn`OA~NFfu<;70c8RXD3Azj(h?j{L{3_ofP=^c97HDIATj|5kqNXx$_vhNKA`;w z;FDKCmt%E4k&AK zfu;q7K7dy8mv$nxd-Cwxw(nI^BhxT1~{}psz2&e%J?=0|d^9LnW91SkV z&Jf0bpxGTpNK^R0OVFispdpnA*WR%I9-!cu1qlxD{&$b=iyqq7z_+v<^8sBJ^A#2x zB7&fXl_coMZ^%+0aCm^i0o0}fg$edH73^d+c$ZJ`x;Zht6suy~B zpkV?&j~o;pj@^Q;-7JQn7AR=pi9c}*Pmt!+!Nd4lK{)|OFTn=hOK^tw5XapNPkR!r?GQtH~Vh>&FNnpwzl=`5f*qdM@Tprd} z`I}dRM{&OU_OeU|jW&P=_!u0!1;FDtENr0LYCuhmlSnhfPMt1ne?a4j(4oDH9?eH2 zKtp@bLF@?7I4Wci+ZAaL+Z8m39iqYk9z?wc8^eiFaqt9P@7q}e8qE0)szbYfxORvC za0G4Oi2nm!|LxxGWB13so5jw#JH!q&LSTn<7BOi3H)vWEbd^0xQ}j-qE_UEi06TF1 z-|hp*S9V|0+&V+-z~cgT;OS6v@K}Hybo4YHHWmPlT#%Q#Yg7V2Darxcc(xO4JR6>` z!Gi#2F$MvcFMvnMpyS)1Apq#G_Ic2tO$2OM8y41r@L_FO`U`>1reKU{g9`r)@QAjH zN&;v+06L-#Ug8E$mY@;Y0B{=wGzbhI(e~-h;7FSQ8KgObJV=8&iVYfU0S{wCnyior z0Ua9zZsZfcW&$>x4O%n-8oUNah#ojX^uQ4U9ijn82y8ez4i+Jx;p_y^aJB==aCQK; z;cQ#@;EW3c12{g;pu`7gL>qT}5IrOVx@PhdXd1_n`2*;fVMpc{paQ@XG|dGbl3~PN zn}Mp$2ym6=+xo2}1H6;{pa-ZceB4L-xNrA0q;_%|f8J5hY97?dEyyVkL@ee3l?)6Y zz*CT0{d!HH2M>QPcK6Xf>D9}!(F4>rzlUTWs75;5Ey50M7Jx@&5JBbC8N%@gw3L7m zGGTfVlrM0UwD`wk9D+l!E@K8wV0v^yMprz$Z-7#61au`cs5=3k{GP`PUCBHZ%(x9( z$=tyUTgkiyyiC)ha}Uugncw+fZFuk_RxzO@zO~8jf`E;)Z1-d6_T`hQNa|USkvcR)f zqy)AjmOLjNPUFuy{h7bs@xf>QdQZsE|y_+VU6gu;$3 z^6ftAt9=CQ<%^(M9z^6B* z{FuS;im|jCGZP9+t!;V}%-u!^kh5JDFF))3?5z4sW*vle(`GKSMGNE=+YVdr8D@BOQ-V_mrn0D zoz5TlTa7_|=pPIW46fbhUJ5gUX>gE0R!p7&hn}bQSs(COJuf|a<2Zb}A4~xGA9NU; zOLz2-?&?3?#xC8(ENc7}g(?F_%;+UftqwbT8LW2gHE$Z@1*;MPD0s6FPQQsL6=qoM$6 zI2C}JP#MrBTZ~GCPp=5L`~q)9|G)-HvNDr=wcjAsbD;2W?iSI;eRA+gM0y3CiUzXF zrTe5Kcs;LMr^{tfhKB98-ssZ(@nsJi0|T^oz~6EbzKU``q&EOsr*<5?sLPf48fakG zmHD2p_8ZUyZwzRtsr#^J_XR|H1$7D_w=FvH=Zk0}&G3R(sv)iA1!do?Q)&E3hd%Sy zJAg_t56Fb>9%%MufriC#{^pmgps?U+N}J%^Eu!fNI!Oh(r3t)1jrovMXUJiY#}0e` zKj3S9yy(WuZLACo;C0BJ-EilFGN&u^Jy8Di0If-bo^>{ zZ@G5A_UZoM*j)~qoKlYimHuYg0hWYjaczKr0s&Knu#iJ4XUQjY|(7==`@Y=wgQb zERc|bmG?x4R~x9}gVlFO;i+IZzEtoOlnO#Ux(|DR?%PDn?t^y#I`Zes@F2xMY<>eY zBL`YV3z{(cbrBT*Y5ZBYU}+!%k_JE<01?suiW%&wHhy^q(0&`o?o;qBZQzr1Kv#@7 zb&4Ez>hw7bx*h?P0!VFIVpxYp0D_xndfk#F`DZod?!=*DsMFW%!9Kfx!iwvMr z{3tjTcxoT<=*KIeG(F=+N4)JMGxUWw?U z4O-D_1DO?e?sicJ&j^FGeF06jp3*+iS$d<>_lrxX@14%t2QHnipk(o)GZd6EAj1%C z;FJM66bjU|0hbxj(gTq!K>q2DQE>qEtSUeabx_)XUTSchzj-keBp7|QpTc{6B!p)Q z6H+;`51vA{KvIZrrY|_7g;u&UpM#VW*L=0XDa0F|LeeIH%7=rn^dW+rK9I`?(1xO< zgK7L(7h&ll0Fo{actMLuPtYZ-mq3G5y)4s^%17sJ78cO>fMfSbaL9JDT!!3W3o7}* zch+7ny8Ut{BRFaJW`bs&__u-G3`rW-KqKYgqyb44JMpB7o37oje7Zk6c9(-M33lwZ z2c?L3(26P>(26RgBEqrT9i@Z-t=)eFDI!3H#brpT5PGMx^npvK?~~5j7cQNyZ(KTK zKfp?cnc(<$={^aLS3>CkngpN&uHe90%D}+j)5|gq6#IzsnyB!4on!pXxAlKX7Sgg( zH_)5&;^>X_fp8+6DvL9(M$E_V)>p@6oo&d2~biS)iF-(Du03Ovd1)tl++! z@fnb13?7X~Kl#Ti6C_l*98d@+z(ipc}m*V}Cfa@IjAW5%AfK;E_Ji-fkc00#;wJl}l%Up+0H_2HKpGM)c0vjyU+uG?E*4T54!OY_ zY$<5Wv6JN*q|kHhbh!q)sT&kvH$hfpfHGGCsP<4mgr5L>%nmf=q5w`lh=F7NZJC)K z;01o5H9RjsF3WIb{s3xAffh!0x~Mq#be{q@L{eY$<$L`ucpm}En&|*~3*lH}OZLbGv+v|ba_IjX`yAS~lTj_re(y9lC zG-#o}W2cMC4cE>Pl{=1|H7XBWJAG81ICjRUym0MwQF-Iq8Kd&Su`@*Fi)ZtDaPP1L z)S9XRt^M^;0k56{kIjLCh66M$B>*aF13aM3Do^W!#nzxgH^E2yr%x{n@@%kkw+K6^ zwgH`0;n;nyQ{ZB)P35qQ{)iDKaQOthd>_kus&XN3luuwI28Z|mITTIQq(dB zykH(Y3;2q^};1#xqKr3t^3v4Txe}GyIzS?I&jmkhzP`~0VsB19+RHnd6 z8?-f};Kj5=H7Iw%r}{ywL>DoFT0+py7V}kbpdw`_5iU><13Dl8%E_PxrgNvrVP|Lq z)5rQ~(G5ff0;O~XcmX8g!u$Y~*T4l3bkzIcD<&660mK0=fDVERAaKD`q7vZS{S~xQ z-dFph59pluQxNCC-P2tP8pT%V26g_=Ll@9Hf|etK*ULWxEf>88ZE!vYZ^Z(2&@X^g zK-!xi7P!3$Vu0J5r?pQwc9!07?DYNO+8KJsv9tDpYp3fI$IjRnj-8=zTsvbS>xo)H zBfc3}$|rF22f&IXAM2x_Lrvhp2`!&sLoCkSB7)%Mh0p*6t(gbk(&pSLa?H8Y=NPET z4hqq0piq+lWkUr}2?bd#4=&+B_sl_-MWB>Wu&D(FP#NXZ{m2Ef&JbKiRe;N=S4`kC z3c5%aycfPRMkT=m9P~9R6285O9H6m50r2Ty;H3J&SNkQReFR!I1KN1009r)z2UJAK zsDM^LfI5B9i=9EGzf1RdaHIdQBecaOb10VHQ+6XE-AvZySf+fPETSNskZ~)19pp2LR+x_8b zeWch1R7@3siz%K)$dtKzHw!=LoEgaGLQs z;A`QLCc$B+pM%$Pf^R7V)dkS23n7yTp4~S=lL#K&uR!zR$Gor(OM_Y$8J^nrLGuTJ z;AREroT5RFl%hQgCMz?MjZqSlVSTKO+2SB0V z+R1XrvC{`yx*aXL1`1THN1lL=35K-X{NOXNpksm|Z9Q8Wt91#Z}%zB z?#rO^0<_M{qc=$5von9b2s>!7SpwAjMp~;08)(I`_>j;zvlD29#tTv|O!9+nZ3Ul9 zuFV2InY;(&40xdha*umAixu)XGuCyP7k^#+=?ofYcJAZ>jWdHzZ9W7pTI0c2t;9wj z2DQ^ceF4Ybi2vYF0F4a52A(}YD=?4vcAo;-2oC`ZazX%Yq!~Q&j4{xR91cBx&>ccP zph;_G@CofQQ^0LlkZat#S*%^VxqW-1A*aiGpf6PEJ`GzY23lt6*6DMd#i>)|y668x zKGs)@_Mn9VczU!%1+u6T+(-kZkv>827_$dxQ6;!)0rea}2Z4Y>%(K^@!?XJhNHK>? zH|RdEqa2>v?|r&|g52lZn+vM7Kx--Ee?SJf%w4<7K}#w1LF)qbLF)qbK}VVqwUSbw z=#`Xb;43K|J40_c^+Fd>I(CLWfUlo)>$5Xh{($Bz$}GIXs#Vf|Q4eZ} z{s6T~9lIgRN?kx}P2IX#|=Q24lav&iF3&yL;b3$HFea_jT~4LOT|hMYmEdK+r0 zmV@^_^5JV(O7Smv1Z^y^0QKoX17YAxNTEv>yKjM(L4s#p3vPIHR$TDutT^G(eF)T5 z@qjEsKEU6{hMZpx@VBslXY{~Z-#L7>|M>RisBrl9rg4CZGtg)|xPJnfenGkHP9Jn% zkNzL`ZXSL2?ht(z*!0U|==95TT(d9TJSyNxh)Xz5X+ccBfcrJ@6Iwt#&?E%pbQTZ~ zw7w6tb`N6;29!TSRK9>ZJ$FETpeLX%(HoGK5B#m_piJ@!H0TYQkzxfcwP`-a;R`w` z*t7ejPxlK?&}mE$5&kbzaP0;a5TJ!G_Kw}C|XdBB@(e5}v$H@h){ zTM~VsC3SAyEW*f*2Y+0*#XuVm;7Mv9>vKhiUVOsK z8f`@(`kb_Tr^|U3=T4UMp8pSm!eB2bsbFoz+QUaI-QcZQ(7q!`D>fHi21mimU~tP8 z)PlemQuOV9=>a;K2)rNuIJ|og8r*|)?@xic_s|Wr6^@_|{!1oL?W3S`fV{wI?kecE zXa2n7u+RrB5a5FBG5UlwmIylf2W7etISK8Dt#<%7M%y7hd&r~*sAtdM+RefQYWl-& z3I^}~WWL}In#OnT6oIulad+okv3BR7&JZOEAXRjZ4YzNex_vp>w@X`L@18L)< z?*HsA{Q>IffV=a^`{Dn%cJt`DcDv{yc7%fF6G3}d9)r5iphEtk3-fhH(5&KV@T?;9 zHJ8qS2b}>oTsl2oxO6&v=q$L?S@EPZ;*CpZ#23%zw~V0CMHiI<)Gj>uEL>O@o(FUk z9|Pzz76(7*bb&APRZs1su$GZqH;VvhP!WFBGH96bks~++&#^dnikt%{y0b-8@4kcb zZ-H<3MerKbPSBD@P^%6!bOIVkLv-Rntz2j){t-A$e(;5~apf5tyX&F7cTnYZ4mw}w z-t7abxLNc;t0+J{d40&_40L=EbQ?QlZpH<4v=V&!_P9%@--AxK8!nx0FFM0Mbk^PJ zj01JqLH+eFpoxCa^lb^Kf~f%=*OdY~8ze*pd=63uo?g4LZ!b@iul6rcg$wGggAY)L zcT_=pXPGa!bh;d4fmIhrz++Depw+D;bk;#dLIQNfpKte1Q0)Qjth-^aH;_B)zG$8G zW1u7qTHl9!$SWwv9*2znW8_%m3I6@a9d#>EM;+vKw9fiD*nvowA9O?3J-T+f9AW{D zNqYQ0-~k?hgUog2fW~l3R03cF81aYkchwtzb~7+A@VD!O`U%MgJ$gZ-OAP$mT9`n) zKD&FGK$Hjbss9g+4;2gegu>Mz~px@ z`4&t*2a~_RNn-UpMn!Q?G4`4vpw1d}(w z%lD0c{tO+tN=y^hJG+B1T=LBW`Rz|nF(gaFfuSq1hYUFlR)JYJ7#SFLf?0PM z85p*LSq~T)7?y!qPZ${(mV#L?7#SGWf?01E85p*KSsxe~7*>K=Uli8t<=z?b0bKvN8>>QMg|5Dlfk3c-^2Kl;||ai zclQmjQ1@Yv&g~%9YhB~p9@d2x9{lcw7Tul+p4Q*X47!gwY9F^gQD&!oqSHSCsuJpu z7!?By*YIz5k8te1u6<4Wp!K0Lj$RhQPWOb@$D8XN7(qv+Sd)O1c{(^Kdi(Kt*yDIR*med62GGH-ppKA-_19u#`#}M5jDO1E*Qxy5+)eny z5AbjEH-+7}?9<&2wuOKF3F8AE{OkQKK6~(M9sJB62|m}FU;ki{*k}HjgZ%mji+Mdj z3rIjkWrIie!OuSYIwv6B^kF{X%X|Xtj_w<+w@alU!N5|g0+ENw94HY6HQp@XvGlS9 zd^qiOSQvs1#5TSJ<3ZKFj0c~XTcY9sTG3FW0y%3S{%{rp1H)ACAzZZ#3=CUjKv&&? z)@0rUi-ATG4}w{s6$ub^pnIet>L!9R5?FQ}C<|1B^qys8U^oIUy8baTFzg4*f^K8n z17?Aax_%%DQs>3QzyM)+w4N+sJI)9S5zrAjY{!{+7{JGRm#{k?XHa8cU^wj23l7J_ z%nS?+eU%{Xpk)jojqDx>ovNT|LeQC;CDP!qYCTZG-F*TyxCH9wG8ltT$UnRTR0V?s z4}(_0cMF3|(`R5{xCeG1s2K@vvkQR6=|N+mos7(&RbU?=JNuk_Sxzz^@az?7;NRBB zz|6qlsD03*w~K+3fuWb>0P_K#XOGrs_*N3W;HXMRD)EuZ-X8n=LN*Nb%i@|j-{#_@gulGy>45%k>gnI9?* z5fK1MM|$o7so4Wnvj?h1!1Dme9EflvNTERE0jQb-P&I;35vVywK;|5QsyPBxBj9-g zWDZ0aYR(C$niEhpf{hRns5xgq=A415IRjNA;CTUL4n!Dg&IPEN3s5zJjSvy2IafgD zT!E^&0#zg6c>`n)L>Ow$4X7H>#aoczgNQ)QxdSri4pa^3zAlg&0nY~@b0ES{a~?p| zfG+0(sS#`hiSTc02Sugz1^ymOa7KcbbQYi{X8d8$zPnA}yaP%=0xq4P<-DCPDiWZz zV;rcrw+nbCzx3!nxRV7`e0Bfw1#J!CQE}Yo04k|_MZjYph?cKMH)xxNBmcH$kp0XD z_*?dYw)*$(W@2Vw;Fo7`<=-an+WpQIRBmN~#xa|J3-ULE)*d?aQ$Hb)EPHS*AfIF=~2W9%29;obCPIaTln4?Rfc-tMN(4|A!pA54m>VI{1Rw zMf(QGSsvXN9Ged_c^v%3+S=^ENFv4A|uEqymJMeE~a@+xOnB(Oi9{&$DAN=8> zd;sD!8OUs>z{%wxTuEtMXx^MV&UvfP7n$5TS z#V$}0=h*z4xs=th`_5~jgD;r6Z#6&QX+FT>*!-K3-{l7AltM<2URzM<>&U)ti6HKuXUP#S`^Ff0$sTd z)$iEwkY@*|B5<)f#qVb_uoi+|fG{%wbS zAYS2jKj_%e3(7H$9j)L3yf=c;vHOj0?^b3|q22w(xBI?h_n!&Ye@jLAw>dMq@NbL$ z&-{Xao3StReUIkD%$~_k9IoAG;Q502m@hL^^FwyWPA3jWYbTyMR>$ruuWhj84CZsK zC+lRO%bDJJb3{Z8C8-o8q!rln5(bEtY};g@4;YM}C(h z|BpL%uz5BgV{+u*1`adppZqzU4JxgniOZ$+ zK7R}7QVp;lyU%)bU+6voDy5nauz4~Ip(GO5?xU|Q(Ml;7=&nNM|J)xOyLipjB;C4l+D4h9AVhRZ)Zk}rX`zDI*j4vlO9Ek9-W z%rD67(R`T2@#1g(RzJ|Z-~VGC)+H(q{5_BZ8FEwtKmi6y1E7=#$|u$*iZwiXMSMZo z!;|?cKXf|d7!!j>ZxASDw1QOqKWKfT<~3;C$OAmq1TB>!;QiH11_lPuXbB5LH7L6> zfLd3eWyYXkbruHjCISXfZ}ususSTD@W@KP!2eTTOVZB~O76t~;?m1=#*VdE#eNK!F z44}mmVUFRTLe~RyrCw)@iiS`3Cr}THfMl@ zDhmU+kpikPJi2eWbf0kC0j?(wzGQQ3{>fa*>S}!HwaCF2Os?G*V3oyBMi+jU3!oz% zLC0+z24{61XtVs~I#4eBc9^472rA;Z16)r`0}ZNe3uN-_KJ0;9O&s>P_ySTtfM;S^ zJhD&ndoZ7XX>R_>%)iZr@gt;)fQq)BEM;@)b(DUs2dXOM96^m=2B=)aBc7e0s>0Fg zf(yUP!Pkn8J7ATCm}B>ehDSUQ2Q~a+apZSB;nIE3k$>9-SL+Lopi0C2RL5jc+V1EE zFV)q1^s<5i(nI^8Pp_keNAn90 zL_5y2*F(U!`vj!P1G>CIAcx}tzkmt{zksA8zkndW#!-HaGyKOcN%Kd(;*WgHA9;#D z@-TnI8&J3L*js6SK}L_}g948Hf|{PqF9pE+pm+a2<=K3|17uO_rTV#t*%=rZKnEzZ zFu*#d9?b_mc7Yc2?f^9e5w7{nFTm2sAAayNzaUGaNAm$DkLDjjH7`9H4^9BJN}FFX zmYzKf$`ocbpd9SceZ-^rO~q$^flQ9i`~sc|pZNs>W%xC2e&!c&l=#daahG4S0Mtt< z6yVpaxWTXS>@&ZB04n&o2BC z$6fd}J$8KNkNM%kuNknygxpStpEoZ#1Z>B6sZ-<4nE z!e0V~&Glc|cM>KfCY?28w|CiX1Nd8Xh}9p19${uilyOUjr0w8Y(KE`2{N__%$j(ZWWA_apBj9m;v&v0>4JV3yDW0|Uc%uo!41_eU^`fsujX7nlXQZtW(R1-ifG1(*dYtRd=< zdqE*80^qI?__RXM8C8%ojWdk@`}A&_0NSkGS)yX$*nQKdcOi!asKfojr~8*j_d#$4 z09u^y(R`G{r+3u^Sq28r#^VO|AT=MrvjniR)}y-xdgswO{_U|0KHUd8Yg9BmyRUn8 zp8-t2YgYOM{lme|KlFb#}qu9kE?q& z9%JBSV6gsOGZ(Z`tr(n=9nm+oQrM!NB0t*`oqd@7vp=!X?4L0190PkH+^mz+vg4;sF{!GywH3EMVco zzm2i`1f(23_>u*5DBB58^XuRXrsfCCy&|5>pz_f5U-K^}{wW8)-H`s){EX@HvtFHV z%x@hVY8e57sB3JH40>diL@_#?e}D*R#Ja z+zpx(@aR78+v{cliYU)s2LaFS3ywQMy*@}0$OtL|8Tkby_yvRrmw*f&%?AYd1+_ey zUkQNk8QA;(G^7B0>Ct+-e!e6Bw%Wg*-S<424|{laU-9fd*ZQqQ%BA}pv`+z!;$D_- zp1nNlKs)~(cY=c2Mf;pX!+)kyF4yjZU=^?BU5yXy0Cnjd8vZl8@Vgv<)-yiL2fzw; zf=bl13D7bX-c|(_s?Gf2pzO@jj9jQ5h7_tV8B5RjbVH)ev++m+3ut(`?d8kJzyK-hzcDf}sDWjBnHU)6fn_H# zF)(O?#kMdpFzA3;XW_C};j*B11qZ?cVf^Yb9{ou&I%~8mwH<8(= zyEMVG`{%bC9-W~FcFBNpR;TL@U+I&^-(2{&)yq0IKa}Y{X5lW#?{bKLTe!Rr^9jfQ z$Kla&;+rEQM~N=~c6T0+UT{6bzpV#!Jv0OVwu1*=Ff}}6?r`1E;k&8BcXNm9#+RU$ zsN**l6@d}~%hHSzZg32`^hR*MW_9Vl*L}=|e|tE>q!T{9pi_q!9526e>^|(n{9*zq z{WCdsA9Cd1=F8~Yo69oO5!4X-{=>2RP)F$InU3FoxbUw(IP?2YkcvNG(M@Qgzrms# zkwjg!FLZ=%s9fZke0UdVj?ahrgh#jQ4xe7v4WRv~9=*ONJi0>ZpAl#3|w5<}T>d>w40Kf1AIg5Ay*R{%zq>J65k+waSHmTfDU6rH77} z9ywlm?AYu3|NFy>uX>#rT^T(7yD|v4GDLuOc`$o`?n-+7TFb$rT&6_jpX=p+u2=rK zUj65Kt-XPgGdH;20d)*mR9w2xedZSc&zXWsI7t59=FaEB z?{d;b`-EfTLC~sYet8DR9Tf}=47(T@7#v)!Pq^~?90U!sbe}l*6I29U{^hC-IzILg z;os&j=*WDwq}Zo7^aQkb?aIH63%n5GA-~2cvdupMHXqck z=KFsPWR2(lL;Ty^g*=;&u`r*o{#Y}^qucib{3eU;IVzwkz@za9C}AVzyThOq2)Wt< zG^!5D^ehZ@@U9W4LjyYHjhO+o^AIwj4vDPhX4*2Y4wCbUY|TMF4fA(ZQ8}djgkF_s4E84$tnJKHaBWI#pD< z4|;b0@a<-p+5C_lq*I{z5j%hQZRSIcdqIuFgFl#kl5hAjOS<%W{%{11jkxk}6LviK zh{FY3K{zuZv6(?^P-g@rclnWP>l6MKV-^Nb`xD$W^62#!`^+x@Za{fJXVo=JR19D> z8mL-;RZlMb+blhL9shd#Kj_j~qrw8J(Ioh{ad`0WZvnN(8Thwt0i7kq;L)q&3aSo0 zm|uaqe+sSN_*+0{Ryyv76zBh-LlgYl7(D+U_{=XLees1Qi%O@93Wp27{{yg}LEU4I z&N(WeZag$Gf%5eZP$}hc@P#!0{uoz=*4vdfh&Hn$=sdv_j+Y;~bRRzWo7u7X0lQ=O zC2$qusD0w{EA7kt`(r{GT5nfeg?hF7^nOrPckm_0UQn9}q?P$3yt;7dJ`A$5`2oLU z^ACRh*5{yJRQLJIFF?D+AAv6oIaeW%WIz8lX7C-@cI!B06{Juv&^T&a5=mCC>i=X);uYfaZ z#MRIIkrzMnM_l{NA33E90I~qAJo3=%9lOtJ zpWxRxeDEQQBQt0Yp`{BHr^Y8i`H&5?jD6}sPzHnq+QHu}p4xx;TTg&TQgc)kTw708 zD1%ZDf$(wFe#+mf^zZ+F&{}sF?GzObP!<5C)4n#)j&IEz6&BDEXHYr-Rk{NFok#wH zuCnn_k$9Q)_y2zs^9>xk|A5ky=l=s2-?>;n11BYZ|9{A-2_C!ahG9);fAMrbOA3_bq)=Q53Qx6<`&C<)l)%=*>qZf414#Q{u$QDonVSvUd zbQL;i{|`vNHmK(y$ae4{3%|x;=F=e6&}eAA%|G=ZC@$PsJe!YkI9`6_)XT!?!hE6v zHni!8H9dl2i3v3Fru_^W(sc^1+JDi)cmp^dL1E1H2hxZ!@JQaG0-A&N1RwLV1)MWo zwcmi+#T^Xb5(QkrfL4GBfLh$3;uY#isNX!3e>POeF*t&DpSxc4IQT-wg?}51fFo!C zThX!m0642QKVWt|_<~vc1SkL>uv~uO2(BnW3)&7eKVa`Z;ld2cfo~@zknPl!SZVy<_|yQ+H#;$ z!nO5MB`d$iiPy~h8mC>ELA^TfACCVII(|FgP$KNwdZ~h$U*j-D!ubzK0%Wu(!swDr zG-DmV9Z)C{$6_uwNcXn`0vN`!IQ|Fqp(?~cLt&WZhez{aftPE+u`vgH;ICt6i3*2D z^AQK+iVYMmm%+WeHK580TsduqS8O2fgNFN97~V24Fo1FrGXv=AJ3 zGYh(U0@5W04Tdzn0eSN?zd$4pXyk)mk|#b>-Li3KD$;k6`895QcIA%*4SJjbjX8kFK|li)kT&0E zNB+oLpuq}9(Dn3={E=X72SEcG{E_EB^GATDWUqa8IPz;8{mdVE=rg|{*JpmgD3QSGd zr$G*S4b$ezFPOms84=-+0gW|9%<$=zaN*a;Q4w$i`HVmEkt4sxmCyWwUSQKc^G9BX zy57giUvpi$de#zzkqyx$dz9sxdX(y z<;t%SyaMD6kY5WxV>BizAc>2v{2JanKz4F`=8w1q@)(b2_haxN%sX(l3S?klfTXiL z1_p*xU@=h35t8CS$rvIFntOqC(?AC`Kv?JC130%B7#RM7_3|<@FgyjbK&MOG0kc5o z_kIDhz+)=wm>C#CgFSk4R5p4vz5(?sQ2J=sJvz6jfR3y1=yhEWI)3m5sB_jE=mNS- z9kgrKv-=0=B5+V!)(5mM4KyUN2huV=e(cWw|Nr?lw}3||JhXrCYwm%R?b;t)_%&Np zKxMb*@nbVU^#s3W54hm*?Y;p@y5J1r(S6HT`-5xuPe@A+6b)VnJbQyc0WJ`9#G}`D zJxDC-42X2v;|LkabOj4CedZT*Z3iVs{+L6b`6DiZ)Odl`FSsrTC%+ibsAuSAkZ{nI z&-@XmL9D1dAZ?2s`NL0s<`?wc4ifXafNlzC)q(4LBvW>S)CE0(oAL%^%50b^`$4i^ z5713{0a7#_$&|w&bwMBCru+aYn9Q$n2xidn&-?;hpan#q`6EE1gaSc-KuY>SiIG3% zFv!KHp()F?+mS!~=4XDvSjd7Zk6vHMn9OH>0oU%&`~tp{LBRzz87$&D8)N`j7$mG9}wUGK;*5YnUK(aX}|$S(#;M3a<8phH1 z!>@6OKk_GN>^kxyf8=BSh#&kKM;-Yi5Aer4;@3FOANiL*@*KaQO1C3_tNY4z=OR&m@3YWDl~vUK=ppY-e%G4KS<#0!{6 zd1@c_>^|eE4Jv$nSi3#D5AbUo^wb93*&4~h_}PhHAchgN?jwZdk9RK%6Til*&rbZ2 zB8;G70g3&8P5R^&CF$hRehkvhZ^7> z3=D)~8XX3D-v*y3xkuzUrh#{b96&$Cl zg35QuFq0VrY?vvD0XED8nnr{SJ%RcJeS)AHOuCN*do;cg04-(FEO_D58z=*+cpbZM zz$!7HUMC4~-3zXF!PPCSZiQ4Q;2ITF+<<$jF8rD+D*pff4-x=Zn=bsC8#15*;J|U= z*Q`+~0Lg9snf&bX;P=1ZeF8rDy zDgoeT0cZ@^fdAOHRiF7I&VA;O`2x!JF8mSj_P}RQHS5A3a{^Msf$eqV*LVr4y-$AT zk9g?_u8U(%yYe5q^dD5%NbqYs1*PaWAgzx48oxmEE+7F=VB=DJE02~V-cLjhdz=(qBA@vq?|Ah3sHlLaOkDXR&w@$X z)f(We0!x43VQCdm;m~@UzyC6%XsA&sZ~>JApw77qzXtRIZGMd<;HuGuUt2XKi78h2H4v_1uzQT91xeW-@h z(fX7lzt5@ei=81Vpe=HsBLy8?Tb}FyjnwV}4Y2KG1I-n=Uf2N|8{&68=%RfCG>`!s z1_Rx2pnb}h`GlKgOp8N}fFu7lH%1kXZ#Se)Hauj0E$Gq9BIMD1&O`fzC-VvZZ9JNe z{|`HM@MwDeKj341sOA9wc4r-5&@BnzWm(-mDi$8yS36x)G(37;R2&e4$e`(l0C*kK zd<1m{L4<*U0aDd~R^O|DV|ycLbvu~lYJ8Gk<5BZ37XBX4KDS<{KL=kh`Sd!n{6FB* zeZi%d2Xq_*g9mgCTiOIruzP^AyFdU7xHS}c7<3fHXZ}b~3r?`%4QS1!Bfp@>8?ed` z5H+AxqoAgdpvMQW@DEV_ZTP^!zyQjj{E?up1SpdVHhck#f!14t#K2|-fZB?J4L@Ke z{{YRH^S2g(nw-|BYB&zJUaB+l>^|tIebJ+rrLp_eXMO?DmAg>)LG0!i^Z+%q8vbyA z&ggx~SbFQQOZO=s=s>2%3DSm~9r+_!nm+SKg2$VW^J^SD3`z;0VOij(ZpYR1Qp^^W`k9FF_~6BxdOhEooL zy16kI9r*~xnV!8ovpsrQrhE39O!lxoQO@aM{guBNbkd_^;{$#M zhK5=$$NwiBn@@aj{C@y6^ra0NN%ZV>7Vzk0nK6N1z~h5s^Faa7cmuz{gb&}4oXIcP z0Okt@eBc-0fP3N)f6PVxh=crs0{j9V0#L8;3rr9|_9DMv1DG!u08*vEFEBv?O&ZJ> z3{dcBKH%VCeUrbZ5VT~bJ4ZzVl!L*?swwy;AN1@#+=plJHcFW|xQ8RTEk#J|7{mhb=nf98)k{Fy%n)Sz#G z@C87%kzfQQoeE6gfG9cyvkbx)4B!CGm3lP4V&rd40IdtMK3Hqw-OJMKseJA_`V3>+y%%A~gu$v$| zCqDB>fLk-4L4yhiwo8}8PEZBN?+YHr=8rxJQgE1`0WwI$!Vn2(fvP;n&=siOWd;vj z^?(LCz%1~DC1?~^0(Is@0W{GM(%{*B(4~6~_%0Yc7s!%C=3^erp$v|ob!)C1)~-BtEXD_3yEt~AbnHHQ z`3L_tE|33*ln*-gdj0?}TZIm(!_9*%o_66r(EO4Ex_0`__nVhrIC3B0-*&;#`f&Ft zM}C*H&K*2rjvYdt{||vqrPDs{VSSjtrvOyc!ou$VaaZQIp54b?Iw67Sq3xnl;KTgb zgZVcopMh3HSMc{h1|nlr64H)?7OEd(X8P9PsS6_vrovTD8vT$UpU{Yj-#gXr(0! zXlR|e`5|NT14hUHM?tl$GZSQ$gi9}rr(^d?7v=-a57|LyI|aBJANY2N!?XJ$WEdE< z4AiwZlGB6vx@%`Vhb!}2SL<7KtggmSUK@cH3+@3$m*eF>9{&%yC_ngr(DCvU$nx|f zkmcz~@a5@mT)7{3_6Bk~cAo|r>ew60?%2yB4KmpHfMcgS2WXwC^`Sa0P%#z(p4#T$ z#sp3dm;XZ6!YjW3FVdGqMCVhGWiH%D!O@uovg-|4`}dod-#T)?c%1~Y%C$F^!?~A5 z(u4Ur$R0=QLl6&uS34qk0KD4%0c5qkG`LHI-#3n+BW~X~?gCvq>H%4&4Ov$UcOU;Y zXXfs+DhCceWwANnne5Kt(|v&7^(AOuf-f_B_lf2MJg&wkK_2gP=Wv8=Pe5A=4O&FZ z0vR~Mw|ba=`yp53lZ-AZ9%-F!98k-;!FwQ@4{-W|_dr0r58VZ!2U;w%3lx9gT@cC_ z{vX6=ep+W3SDK^sCH{7Kb_NEJ`N-QWnh$VbvBDbNiq?}Q3P{I5;IpqIjL&fwC+HBt zWOt5l&Wxb2>ptPi?|K;;e2&Z@4}-h}3P4!kS)f`8-P>`h^<Y)VG^2B;a6eOTOs`JhK98_55@-Qqso2VA@3d7#1kklmO0 z(0_1ZcC`_R;}aT4AT+D-*Sn+MTiwkVgLmMJ1iie$;}wk5|D53F7NLM6G%JmCdU51p^l%a zG_vt2KR*Mg84hZ>foFkRE|p3ia}(q6=mk}73?9tKK@rEG?$OEiS_>+;6SND%gZa2e zCmX0(j`-%l$gu;|Qa$D-B**W1u)|Fd5=sYf#Sa%Ee!%sOv19j1@Gc#2jRUT3m^}U; zN}GVEdU5RaX7)`!3`)DM-Isls4CfTV{D|H9JUAw-9J^0Jvn1$vY0v)$ zlrK8=di`!aStr3S&wwMo!ExOYCh2N?(lgng!v|EyUj*6Ie303fnH^-lBgh_We^7l1 z+W&;p76Q>BMT`|(#+N$6q+hy%c5mUdND|cE$L}BMm!M@fm|@Kh3Ttrrj6b|tP{JD& z0-yj#1hy^q!2Z*EvQCQ@w+Ixg1j1YVn483ZaE;b|;^0fJ4nJWK z&8i~b@KCHh@E?52(%~i# ztt)O8ON9J%2w_cWqj@B1IYj7O9k07<`q7Q@B zp@^(ygiZanF*7iLRsgUt=&&*{fEs(u434Y}44{Py%na$AFm>R@A85!0veFo|e>cIi z`-eyOAs^5gW`|rrJ0(HeBtbidK@I;1P!AV;^OFbYxLya)8et32dEo}2T|yehw_Uq0 zJ2wAj;co&}`hLA8cR~FI(DH)sH#~biSsc4xc^*Ih=l}ozj(b6C#8-?ExBVbN1}M1Pca_@3%Y-K4Nn1zUoY@)Fj_rgP@7v4M2#cB4PVhnm7tqOsKA`h*YCwB@N>mI$N99<6j=Ke4 zV&LJ?oddcz2XsA-_5qLX3!cpn{&+MWNr-<5iXhPGeUL5!D3Vc*tVJw5SLk-)@#ub~ z?F70bxx14=f`MUX^CNc0?vKoee7hfj7s0)7v_1*C_vNB%_eanI(U091waMhN&M zA2R;mdeXCZI*$Yc1Ha#SkK}_M-KRa6FMxY|-L4$I-A8A7CZB75#17iD!Fx zzyJStgE+5QT5s1$c+BwV?FV_qqxq14OZQ=zM>^+2+yL6^+>3km)fKhP%U&Ki{h zAIN%LP@bzWJ^<=tgFNEd{R2GP0bBV1nsxzgHn9aoET{&Ajgq?YZxd1IJ`EnW{r>nO zWYD(zAn0t*2@DJj)-sL!eW0^MT&=J1H%|tg8sW;UP{PWuaUHZ6=h`<{Mvf9$=UyHm z(8%Hv&=?#;uOlOf)6)#H>vg(ouNNbz$8zx{zs57i%MbZA&LOqL9f@Db>c}4nU&;y| z`aA6S?GR%L8@~p~O#W@+uEr;gLA&uEfhH*VBVU5XRhw3Tj(Y)*tAY*=02K%Dkw4hD zDsp&y<`<9z&tXP7{sGNs@JD+70hJb@?d_n^RnY7Sf8ut3LyfRX_-Lm(4ud3*$XaS|xGbGRDc_B`$gy7~LR<8ek%&F0a4 z%cuLIYwLl!c>eH{{M)#V4}cD(Z~zZ@vowLG8DNtRO~{iD9@YnI?!1-+r(V$2Rqzz- zk$lj#Q>GELvhM$3kN<~#dqtXDt#8yU0TmA%;N9Pd(LwBQ!6axc@dEhgD+y{RBkL8k%JJ=Gi9tsK8e*mQT9H`jw_{<-7 z4iwf3pZO!WKYQ@&oa{abl0DE3+Li=bP{HlN$PRLtgGcib14!urISw1Nr^pYy)C4pT z2RTR(TsDK|Kb*wDTgqBbmWY7P`{4KJc9QUDJy62t(Jkch|G{C0;4sJV9Ssh!^Jzdg zsDlRq!J7wKR0148yIt0(>~LUUFuv{4xkse}#QFwWm{Q;X+H~^+e6i&gl>`USq7=~7 z2WW$62x#^rMg??0NU5abjymk2(A}>f#lYax>!K21d}#;x2$}C6!6SATJUU%K$J@LB z7xF&cAHWw}fGT#M&NV6-4h#&CMzC+MjhwIbbN=S3parJg>p|vu_wuNCTA%mmo$nw8 z+PHMwgWvg}NB0Skkrtl(>(6=|e8}X{db>o$quW^kY%u5$7N2fr(AabLx&IeDx?g~V zTlGLkl(l{<5d*0f^yqd5haHDUx3C99Sq$hRIdH`ZaTI8t#{sn702~_KOH@E%1RCB1 zg&!!j3U;@HoY=|O-404EosKNf{S_XT?HW=H45cz2-Tf9)po>aRmI#17?{T~xBml}< z9^L&b>_DB&=^&qhS}>s40GTTcid{Zf?6P`v3xRxb40LM{vuCF~sHg@7_7HGnt7DnLB!(Ho)?;L;5`ujjBY|N3Jdy*zF3vs*m7?}PHZ zf{*oA{?;|1gBeIm7p>p^{{R27_}~Bk+zbp1Yru6TXc8SVrv_T&1<5I;3=9mb%Rw2U z3VwArXt&#c@F6*^prby(dgn7TF!Tz8)P07}!Z|a+#zGt6EYR+Ih+YTKj_L3n4GK8( zkpd_m%~1hu{`Kg->Cw4Hr9uImeLyW~4Nyzk!K3vgD6ee9o>x42SA$XxXr)+z0s}+0 zF9S5ofHMTh`0pPz7>xgelRG$b`lu*?mY^7Tbf56)KI#E#mK%eb2EEOo zB1W?-LUw^`*@t_6hn%!OzZ_t!|fLAY%Cpf8eH-n7wVgByH zxVylPfkFE?<8F{qonUbf?e8Tftp`dZ9e0SvV=Xy7dM6iv?C3rXHV$-9738w#8WjT% zh>t+mW+?b{AN2)oRtNvL2L!1Ej@e0o_xhs45~wP2&eCBPnr8tT*i4Ajmu@a+EE?aBblcbd$+fJ#SDRsa>Te(=JO z5mXR%gUUTn=>{tZn?VAQg0LG>5Kaa~AV>-J0uYknJiCuT3qpHvK?rgXQbDM|z~C6~ z7!!XOw5=UddVm%nLrM?O;x9<)5d|+jK=UGydR85DrX9G<03F!{QJ2KXz+eNekJI57 zi-E4$as{g^f|ni};Vf@v1_ocSY$7wPeOa`yZouPI4r4nTiP^SY?jeB%Efzv9t zPdAfC_eJBA9{(R0A2{p~?9u#20aV;{cY;Fh|71{#^;?t?YjzJtFay-0GuQ!Ysezh- z&;zbOE3_bf(E&B1!G1A>`vvSsaL*HRN}5L_=seTg3eg2=Lhcz;+JReX#5QlF0+HSM8LWo!ES_^QBvg5 zh_1i%c&{@f!U>04Z{rO!a3cp|YWHQhxl$gx1Q-9-ZJM{+ivR6Kn<8XHf5gQaw1m;7{$4obA|s!1#9a4@S`DHvTD)?b59W z_@^B1J`73*(T73B*aUFO1}Ah-GIN0xBAqcR$b&VYasu3yc>%ghuKOn_e+EKZ$0h7O z-OT?lymt2J1~(K-EIdGs#1cb~Zeh>vlkmz)5mfz&gBlp!*FCx+XDfnR%IE(FOL%l2 zgIwdGj@A5MV}uYjrtC2-l+>Bs>t)H)pnz>V5YM+uK^CXY@=kM4u8 z6AC>*iXew)d4SZxnGzn|2R%Roj|>clwHO!}vcP@@tu4(4vp_>8Ibaqj9WWaoINW-v zgw?n8Kq;H6@d3x~3!M`?>=+opQR36t*kA`b6*1WndUTsh_XW`O1@j>vW=7ELMW-W& zwIfd*$QZ}&Q_!gw*aXSJUaudH%%B-Q(aR4Yhqr;wMgvWcgC>MQhqt|OE8z>HgmR9oqORQ9>RVfh@QS}zz_!6uL-pgx&_BY3;5XQ#bm_fgNzdJf0VdI6VC&6gD* zWxZklJ-Xk!@`s;vjdeIU`W4;@#!uXPhHbh7FX^&2~A0C~0U=O%-e*oEW zlaYa;yBjj>@QptlJY4XxyBpMS0*wtE1PwDd5*lYX;Q0T@XHR~uV;;@N1U~b}9Rn?M zJ^(ry24tcqzs?ExNP`D6=s=d31CGsqnE9LZ|Ns9FP9bWbwcp!)R04c^%LPCK!wR4+ zCZLf&et{Yl57+L$uz^BP=06_RfBBohR}jcMFnC~klV7k##R8-+0u-L$S%CzQm=3*^C-8%-)PPY&I+mCy8*8ND+ zKA7g%DVFBg>Bi*Qd`tqe7l8w$Rb!6|C_Fsiostp-=&h2lUJ9rq;G+Vm>RmcDA%&rj zN(O9Es826sMY3mibOdPc(+5|64d)A>y-Wu@yB{OX)__YiNZS-Ncp3z*1wq>rAni-g z#n2EIXo~}+X1ve9z+exq8K1yg5yqfHf29~0SQtP9wh&WJ!Y{`Koy!~u)^Qcyh6PQq zK};!fdNwIz6JFp!LCwchN;s9-5?HDXT%Isw}qL30b=h_W(EdE zuv?zPTfLyFK!{&}$)o!qzkrtjzaS&Opr^oLXt56KC*Z3A4ucNywgfu_)QqwMvp~mk zS%X=SDiqW>hIG3vu(t&e-A!G_cgX6V$3;;BN!14E@#!D(DzXctIxygRV$yJ`xar*rmG>qz1H_ z*ubNEBB(*)(|z7WyAj0lV4esv6qJu3h;^zS$u24etq1t~K+Rtd#%7Qv zAJBTZZV(G>s7rS<*n;lKAfsHg4|*_P1RDrT${;gKw()P9%n6!(*>(tg7#aIc&;jJH zg&Q96?F5~y((sFE2k2B4ewP!*2RgxH1)a^HPJOqFib*GUOrYCE#iFwrRF-vv2KHt^ zodcRhfcqBI5K8a>c^n+fISdR8KfpsXpava;WzERI;0%sGP%S3_+L{8|FN=uA<2V}W z&AUN;9|lk*bOO7QZ_VHs01eH6W=B8+E#N%exd%LF2paJO4Sj<;*A<|N%K}iZIfK8o zf*CY~xp{^iXsq!YXqz6WhW6=Q4C-#&u+L;9^Gc1$6Xmfz@zaW0|O%ifAezixsWC*&>;+u z*8lu1??H7LbksuHr<;8jNU0}epb|2Q2RcUPwU-BE#DTvBGMH=4Av{Y6Ql*y zqJyXhRidD~BE@~Wk9mU54TrVr#6iY}OMte9Lp%%dey5JgK9C6@A9la>0NqK$$l%f4 z4XPA9yG2wyv_E<Ww*dUZ7co6EEXZLwT1$yf}20b!G#GZ13*RzUvoBhgEK2=$!QJfHt7=340aCa?ia{yVxX~c z(Cj|Q5#U_hy#{>9fk)#JPP)qraaP~)2! zTty&qL-&Q&1EoG`6FicAR182Thk{Nj_FzQREgs!qHpmo$wakUq1E5+4HIeLsCz4&z zL;|g4E`aKlZXXo`^tuHpp-cu1GlSL&KW$AhLp%R14kAj@f!u-~$LD{ndM*;tSd% z0AuzGW&Ec29@b99gC5Ap(Wy=5|`Pt(;QTZGJAH~fuj!6 z_KyJ_9SgrZsk=rc0@T0+H}qo;yD`Ap#)%-Wg2NhgL;<8-32L4|%C7nFmi-cV`|~{m ztThcymN}r~AWKvvKxGJcG#E6&1FD-n;DHRfCBvh8BgiMvkmPRxo!@5s797Uif1#qF z@I_qO3YPQue;g`V`rgBGH96sH2M^zCpn|4zH7I;NI#+|j*Q0Ya$W5z(Voupby1tczA-A%t1mCG$jTJMbNQpkWd6I z_<)2WINcuxpMUSzeZceL3y*^@K!kND zHW#Lk%m)sGiX2eun}wml>c3C-$HIYDTm{3aE&F- zzwMCYZcvq_!siICvTT@Kq38cIL6>y8bf1D;jSC3^9tX$ngYc7@9lH;}PV#r`KIy@H z!Smn?CdcLn%m-gGJ05(=^8I4-Lq^B%7h&i7J2pRLbn9*b?K5UDzU0Wi{;=ca7mobf zPC8zGk$C{L-O%y-2apSyL5uggV^la=4}c~j1zh-DFF1A|I_wNTUkKD#0tGZP187VE zbea`21H_k*t{>YDkQWa(Jc2gdelhKY^!yGU{^rQUR3gE@&65!vSPc)^z(;*I{AA;I zIds_gz+q4Q%0N~HO>d4i%6EX4_u&_GZEXqRyJQED36&Houog}RS}T1o++*)`D6f&=Kn zE?DqllZyduv_Vp>!N9O8jz%e zT8-SJ> za`Fo>fHsuBHiwl9$6ZtuKplBd0YOyh0bZiRFUZa>=r7>W3@$wwO58k}!NmtdDVIkx zxcp!!F~TfB%#aEYVbJs>A9!_6>wywh@G=|7SZe@iv04ODo`;kwm<0;BT6=v)4?Z{m zno@)e9azDe-=M}Oq)7x?I`XIjl&wKmc0ig$pqsEE*?%zu0|O-cgQl<{W70q24J&A- z$Belj9-vE&Asr>qiSVGZ9Z_3>CiFm~$>0H`-=KaJtYF^_9_EFV_Mo+4t3jg#utGc8a;5ZA)UJAaTf!52=+O$LnH0}zL_UOJ27K1ckdQ`wxLDxfp zrlCNN^6ZXQ@a)dk0L?wc#m5~69Z>>V8U-3VflNPv79K-FvV?(w0n*L^jk!gDLlV?{ zTnlC)((>^2b0pZOsD4%m-vJ6*a9Cr+?+4KOsqS0gp`wev-3LJt3Yo=eJx~fdH$cFr z`#4z8qZ<;1prvbIUC^ikt;N>>MS0^9kTuwsuYo3`{($FDKntWGF#}q@hKQNL=SoOe zqPh~YDl0VHG0dZPjmm?A|Nrk$z>#-a1;8^gA0VrvK;inn!7_lOJrPB z3_xve$T5wet25x%`>1F@+T4)iaX{JK05l^8X>;>ywl+vIFz{>kP5{~6dZ{EHRMoSB zs^n0Q-l-cvqP;#U4&cyeJy5Fb(cSw3&B_4Z1fEQh0-QJ7v`7e%6-E;osHj#LO=PgUc_~d~kdh3M$Q*(lLmb0*fOcYl za|lMQpa9-d^8jfu9JD&aqj@KI@|wQ|v_}Cn!))LHS*;IR)djjy95iX|0p6;!Ma2P3 z?@_S;O)$$lFrX~s2lbIb`vw$1lhxlnzJK)8{^ntQ%!A+UgE45}5o8aj(f>aV`&w7f zih5AT6|(yVu`Qq*WH6SU0Y3ce&*I-1pn$eBz=Qcc^>zkCgZ8nszAe%2hB&Y{L`A`+ z`=BTP`g1<~>yLW&^7Mcs!iV{*ul0BSrrY2tTxb|}uTcSqX*VP+J-d&AcJ_dR9#XRP zsDPpiRO?_e(>!Gko_P#!K415eX;K$TSc^wGum)N=6yZL*&PxAN(0@X-67paL1RJS%le>u zo12d)IL00J=sx=TDgItv06g--85kHKkq5fM3le#t2!%u*XcivQ`vYw$gG3(aPS~fgu1KrRb}ZB5+M1!lq6j>uo$bn<29$;Dr+%;NCfC zS}FQ4{xwUDKSA3u`P=nDZJp$U(38jbx3z%w*E4kYfNq&&@L)dm|Do}L!#hCh#Siaz z^Z)<0)?Onw5Bpo?fB7lI$&@f|Gk7EC?|lfS^^GcXBS ze|~t!Q!x7rn0yQ-LHG3>-th#?egq~TfXRno@;;cn4JL1a$**AYCYZbdCa;6ZzhLqz zn0yH)?}Ew8VDcT9yaXmMg2{Ve@&cGV4<^rn$+KV*v^VuIKj```(747B#*9o%%q&c- zY;5ctoLt=cJiP2oeEh5e0*v~CLc&}kqGDqD;`$Qol2X$82WGi|F1rV_ zJQx`m9)MXsj0_CN!K?sA28IJ*RtO^l!$B}Bf{}sY6qp5S1s(#k5*Qg6-hx@68uT!j zmBGlsa0JZCVPs%92WAy8GB6wkvq~5l7|wuM6^slF$H1%_Mh1oxU>0azO$~fLRL|85k~tS)fxv zFM(Mr7#SEYgIQ}B85pjBSsNG`7;b`DpmyyeFbgzN^$^V3!^pt!8q7Mt$iQ#|%sRrz z!0-mlI>E@m@EFWG!^pt!63hav{d@vuU14NkxD953YM57G7U*Q!XJFO?Mh1rKVAc~x z28L^37U)>)dtlZZMh1rGVAcml28LT;7H9?OeK6|>BLl--FzXK^1H%h23$&~Y5+9&e zC}^?0Z}$bC?pu&`6#Olq(DDSIpScZm@K&$Ge~(@kC(mAw{~ok>#YDfZ2`7S`7?i=(+7`UPlwO^bzVO_dV>_w_!AF+4*E#r zFM1%5#-DgHjlbxj0DtrW{>Vf8f{qLLqYr}AKH!f$gpd_<+yE7SfhK-{Kl&g<=LH|@ z6Gi>LAa`9sx>&`t`;e#hLC|4*{7s@N^&b zu)fOgcBuP=_5}~dZgBm^{E-p7|FjdV!bAIIi9zcnQZ|j2E(Q(y8oXvP{@?u_u;bTU7Yp-NSzM4{tsHGgU0rti!6SD z=k-qVw}5)5h&2MBwCT}$sT5o0Lt17AQu~e zoELw%m=oNBZ9Pz8=V1-r5dqqF(1g9c;Gqpx0a|4N+N1;8j00LL0Uo3Tn+e-s1sXSk zES>-zDi8*4lT^YNrGQ3BAt3@LDYdl0ODKFVjPHXK_LL~twV6Q zBjl!44|oR-MwBZzJ|O` z*}>Pkf}>c}vHKrr!LovD_cKTS?eARqx4(NG#?GTgEI8t4K7DSml|?n55EJPy8= zH77XAxm>z0{XgVreX-~z=nSS_9tTH$0hSY>bM$>q2!Kbce7aLqG(13?e?Ni8tX%lF z=cpKg2m{a>uL_SwP|K*kywoGPMn$3ZKi=(t-JmH4(9K(jEtIT|-QQmGctH02>;p~l zwtnMp1uX{yt@;FAJ>Csl>sbOi#3)Clz@zyH_^R}H(5VJ6BfcOT@fI7HLAG$i(rTZAF-gb{hB)#uJMwFgS{39CmIWRD0+9usZ4Z$( zWMp7~?9McU_hnh(t73VWV6uYnRk5JH2c%%#z{J1+%VMA^1X8ww@=X%qle*$!ef&ge4Q?J)wkN*dbvw-#` zGk^x;IXZn*SkfkVbl>;wZ33M@=b?Syqqj!|w5WeSs8j9HD+*HV*?q{jw`0Qh|Np&v zO{Bbgb)@`zO{DyLZKV8rRiylTb)=m51zh-cI6{t`U(oRT|9>ZbfgJ7~{2CYdHEx1# zgO7RnnP0%`34g>D{$nQ@_+uV?<`;0f!+-1m1Aio#{Q$xSU8lt#ahN~m7QbL419;oo zQT~V{pu2fu-Z=6LEU@_f|35$IE_4AFP5#K2j{E{1U~2>;8$gB&t^_sM9Qg$_`C}eH z?xS~l0dfGyCWs?GfL#Ew^$iwV1+duq;TOnOM*(Mk0T+G`h`k-4^Q=KW6pWkzu^O~4 z&6!_d4c`fnBjNUcLAC!6lKnqW?H6oRfLb1N3*>8vmsfx+kG#n*=&0b#FVLb1vIgn| zjsO4udp5oR#TdjT3-|?BE&yF85pf0N5*~p+AO#1(3SbUmLUj)Zl6zP@nhzPEx(OT; z1~6y60mTF;K3?(*MsDC2bTr@>TnXAi;n951!K3*-BY*3A@EIIFDh9pbOrRz@zknsb zK%syOzs7wR{+P%B{)ox|7k&Z95BwVM_#+A(_yrObT=_K~xbVj$Cb;lNI41DNWIDL; zYg}>U*SO6ek!aw;9}{Tc${%sZm0tsNBit2`2Q<#}NBnc)k2vDOA9IF3<{ZDkMo_@G z@JC#A9`nas<`f8=3D{>Z1U{EH0CnMZ_pqDg&I60VlKc!1r&%b{DO_3Fb4;kU?(J+1fd}W2{e#F z=z)arGcPVr-!(Osh=;MjbC#k2X4fJ-Nf zif8j{0nqjH9E|*}JAQ&MJKLjT0NQd6x{e66;MN6nsZjt#WQj@wgfT~@0K%A}(g0!f zs7!z`T2vN*18R>7s5J#L{E--50TV53=9llbs{Vb45DC`Hwy#9U$DAh76t}suvi5P0|PUd1zK3m31-QGng(E&7As6| z4l7J=2`fzRR92YYxvUHf5WQR2VYjvUbo!_$fR5pk=nMgEmGDvFK)%+%qxnsQ@weWr zKb<8iDxTd3J9AVFe7jG$fDV#*+4`2huLpD*dP6P4fBx1O5EFD?y=QMMBW(FOFNpEa z<`I9ZEi(hdw}Wi_EhbQPOrE`^utm%KEmF)343LG({4IPS72SfK-DiEefBSSl19yMF zu`#)HU+8pE(Qw=e3bqm<{%y=VL0u!zt~y74my@rVj88f;pK|Q}3G$RHXj>v^46@rr zMa8$*m9_Z*N2iO5j!*YHpYGF*|3Mev@b^|SF)&yktP z>5lx{cvKwuw+ZoYJ7{!h1rL8K==4qWmG|EcffwE%wyv_*nZaT^Ptdk>5Ng)aOsqI&DZUtq5$&aYZefz+eJm^|MBh|6@%sjOupTR zKp_gcsY(MB+${_K{r~@(6U+n^RG<^EKu(85Sa*nuic5D3*e$&*f{xY)`1?Vt7F>^`6kHqA5noG0^H@OpY-{%wwo;8kwWDE|4H$@srx_a$&xFduhe{@~c_#$xTF zqQl=0T7Tr3e9V*iXoJOn{#Fo|e;X)jPBlFI(D3s^2|xd~Qw_JI025dotR$=95l_P}o{}U`#5x-Be>?cEG>m_n2NVA`F=&*Py5Wef6Qw$i|BpB} z|KZ~A1r^!gP=WaTC8#+C3O&$u;Qi$c3=ELKgoQ?TiHb_=w^CL|>k}mjpiQ`-8pN?T z&9nQQCo`-oU&8nQaPx1jnoZ#T=4&3~1Ftz+Z#(eMKMI-6 zL|xyg0Qc_UPzIR0^BG`%;nz69uMg@3G8D1x_+QV!aQHL70H_RQ;MYG`#I}Q>;s5`` zpZO!eNdp!MC^Z3#b_CzUJJ^^6lUQHm_cuLXYGR zj@|8`dcv`@@dM~uhRz-p(A0Dq(l*Z2ZB`z|ak1`~eHKx3DlUfOHBl zFm%H>Uj#s^-C01&LE>Q69|737b3FKf)v^1UXYvi7-d50*het0DWIKmr z_cfp1R#212v->PK|9bTD#DZoFLF$>$f+RuHAs`V@767@}v$y}u@BjZDvya((_RhEY z{r|s5@AMNa3=AH}TU2&1GBEHlFfjON{|7DpQgH1C9jv+0@%s%>&s3wcMFliDyOW1dhd@f|Nrv~O#Sio|9^0oO4CKf0c0(xu?gyqYPzTx@JITn zfNqf$a8VI}DFR*cDd3_a0plk?G%3LN1rWXlgx>AGn`8k!Ix3(w{w4ltFV z0*POs^#|zWUA9;uW*nb9BkSbSjXE5f5EB~=e4EzGFKV12b-DcpA zbWw3|1*?q#UHW~NKk__(#0`*9pnda^E-C^1V2N{%{E;W1GJpR6cj{$fbK#H7Q3>!! zz5p^L!G%AvL?ywIKk^djcGAdqknTZbjS8qob&x;eCMY~Xz2`_Dl>(3wfz}`3?k~if zPz68#|L2dnz#rKIKG6o`4A3QO5w}30{r&%cP-nuCKk^Dx|BwIw`6FAvmuiCa$6Nqu z_y7`gQE}i0=?eY99~q)zz#j{`dX|Ad;sSp>geB1W=SAHM-MWlId-xH zI`y)!do;iH@JK%4(fn2bB*6-j@C30K(i}ToLK!@guQ@V*0|~Hz1bi7hlFvJ8{{smy zgVY9sSnMDcSQisWQ78lG2q6WK44X&uArEj42TAxcfa@>NQjKmO6%EihRP#X((A@^0 z@&KIkLFf5)TKxI{AGDquR1$-Z6nf#vuTgyibgs||(78f8z&EJ7@@quTa0T6}?#i#R z8dMy+@@s4c6~(Un8oMFoF!J$3(T71hqE>^iLk4Ak6L2M|$jHC|VS&ySg0OtyVk#^Q z3~RtT^jH`e)`D60;9^hVVxYsQLp_?`7=S8S@Xol-8kGdl)P;sm_b1SCD+;dNKRuH_ zxVE0GllAES<=FimbSlYtNLku=9MqomXnxCB`Vw@BL4`;6WzX)jjyo9mp`~=|x4J0M zykCK1_wj=d*gU)cg6^WoaOq9^?=i!J`GDiaH;(_0_;kO36oNk8=UqX2#IAZm&rJ7d zJPw+r_GEtR*?f${v-u5U>20u0pt+C?pY9SB4aesHjG%K+6+km12_VZNO7%Uuuk8d) zmwOz1#NnZR*^~JycmThfMXHn8qc@rZd`SC8(81o0v2oFd4?bf9&Cn$ugE$?2xdro8 zm=f@W9cVz=2ps&NIs~%*2y{ZWDOgM!el!KBuxbF+6E5AL`^;m&x3qRJ@Gvm=bb~Hz zkLdt;3Z!ZR11|%E<8Dwh0CY;&1W+gaGjs%C7pT4P89EB_nLiRd9PpW606cE`nICf1 zGyHmC{)h`80~;8)89>%WG%zqTFo3Q&28nhsaDhc*Iv7|$qL9HFgxN5?oM2TE4GgRx zRglZ&I~X{?qA?u|Y#>owdfAaBBQAah<#qk5MXdbWPra7m*S}T7>ezkywa{n&h%2A@ zbx!ebKbUs#1%I0Mi8RMfE|4P|ICzkZaO4;0-~hQA76738KRNioDxe_&bu|abeb6w2 zdzb^_R%p8lrV_>TF`%dr=-^-in+a7J0}e$84p=0B4Fna>`d5p!LB0@lJou2sk$?MT z&t%ZyWFk(W<{O%i__tpKse-EjP3VM!+i##f0YzXIXjNMpm=({!zyN9Ifvz%!wDUkM zNXTWypj}JVV0EA(paskV9WT}nW<6(MV3-MJfr^ItV3s{2149Lv1zMQX1!g5OGBCt~ zS)hY)=YUz=phLI8EYQN2C@^a)BLhP+m<8$t*n?RPp}}E@rg!fem5#mt|L>@9hGucJ zCAF;yPN01>TOT-q7R>&KT$|$pTD<58J^ zns5W{IL-o>Ev*Mi62CbyF_j2{cAdL+zXEl}T=`vIzh-vre(%^ljYR@v4WuA)>lM)e z8EW9#eaqGQN{NzVuM@a&FXWjFzK@a_vj3*_Kq))eDX&>wTW^;pgSIO0fKRMH2e#C= zw+&>hWA_yw$hIAb@t{tXLH7mM)=MRvj=elgj@D=Dv^v>9d)a!yzJ=`%X*>c76?l&z zE;{zGWA}BR@qz)lwx7su}7{4StA zw1Z>w4`%+>YoJ5jyN`PGPEFtf?a1&^u>gl{_m9>C{2k)V3=G{qDh}N)DlXP1`1{$J z85mr2OluISU>M{QHkN7d$7|-#e}~HlmWqKxr0X4T)NX( z9J~MX%QJxYu{_`c?PFmOf@TUw(BV$4pb!8pzwHfC32^N`18T1ve8}pxpe!n zc=Wb5$bz=8xPUIPo(eGtv>XrA`&IDl?E}@ZV2}AEr?LD$?$~|YyO-yQ$H9l}9?Ul# zL3h3IPd)Jefb|c^<-gr7Dlsmg(@h+kkF$dgeK`1@x%-Br_I>CDIead?J5X=Pk@8GF z4m!3x!GrmNOZQ>V-ab&^f~|DyoCk`Vmr9_vPwVYcZP54>n+;nj7x+|ym%sk}|KEUl zq}0nt;FeG;XrCnLl=t2cXez)SR{veQpCb;@bLsZu@#yUZ1%yZPai3nWDUQu289{Ee z@aW}f1UrrSNArK4(o)alBObj|K^FP+wu1CHcK`S2?F9*V^olfrm7)hTG*BIPfX*KW z2eU^nIN-smvANMB*^LFXR>R}qLk7wFL?*$osVdn2~15H};Z|g%obc-Jx zNc`(vR7_r8_>DEBAd;JZ|NrmWu@6)vyj=SG|9@%ttqP#Y0?6=gA$(8)w0;dTKynN& z20DQZvSSCd_}K#7AW?WkB2vx@N}sq$lXMgj*iX4}b8tf!1yDYk>PL9?geX_%%QQ!2q2!p5V+c zz|!T>8w8rd^yw1tY(B`rA9X|L7rx zv`(itAO>Svr_(zSgDI`k=>v$toYv{|3B+JY>vZ}8Vz8!lf|m6iJ;avQ30li{^bmVm zr_(PV>yt%IzSbv-b3XG6fXj4fD@_2}CP8hL!Ph^69Spj~nP2}9q)8;luYaS6eJ7~p z2W}J{`OL3#0;y5t+{xn&UhsFK=-^>V&=Pp?usdi9TMNtrtrzhIv$iuZFiZni)kok> zi8Blg43MS-XcZ)+DRHb3l)xAc^KUx{y~mV)JBuTDoT&x8Yd-DZNe=$)2M_a~1fBWJ zf6}A-;Nj1prAHjyE-D=SCpukJI6m_WxTtU({sa~Vom;`-!4JN#fa4QH25!hULD2Cd z9gH1;Oi&Y>-&i>EZ$IeMeewSZ(7MugHqfvNq;B+>;nLj$N|_8k%qJcHKXhzjUL!P}(3Z9@ZNYkn1t6&nC_)=P z^XGdU011Jk{WE{P#{rP43m_rzh~8)ZdWQ?1)+dUVgIC46GlNc{1nmZbr2@xpmM;Di zj-4!B9{&$|Sf46-=h1w`;xO3ZlAzfK{%xRBb)1+SJD40hyqJ7@gP6h7ZqLB#j3HhG zA3E9mOM$-$G@cCgVofQ+dkX4iR|(7J;x| zcs9ReES(09uxX483?LI(PC-mZC}TMV_1Xil*I@1kg+G$}Indn?>eM*=@a=UF0B53z zZWomZu-F?2AI$y$N)Zk(z;YnRLsWbLi@rgKg83gnd@$<^*xmEM?glOY1sTQC1u+(; z2%gLy!CVc>A)rxykLF(rMMbcrlZljcXqjY+L75+Ca@jFS5q$$3$l2h2H;8dK4K%+2 zN*femP)Q1@2|?!v+bV+gIe=~(Tvo-vz{&t>*gXZ8q@W`RAgmJ%3=9w!Xd51c6~@TG z0MP+DMd$@s2WWQ~gasN!hOj_;cOk5a%rG6GdqyB)CqT!&gZ18qv);j3sw}XxoOM_j z7=(&J69J|yuw7{mEDQ{GVAs2VibyaER0G?CSw3*F05~gzg@M5VEE@r5fof_;uvh|I zT?(9)0cYjFSp{%b37iF*D2A9)0~Z5N1%OQkU3T64i>0X2r#I-2=fPJ@KE0kCp8pT| z^|F9ga)Smd1RQ_(_Ig5ci9olDiU24%J3?{^gbmImj-XruRRvZ7&LxhZTmlsZRxj3po9O3JS*$zP+B1vPl3`SO|axA!QSU4Jn%(!DW*GxKx3NL&_#caM=VE z2bV{V;Ic^o!hQjE{WOs46+VGne-L5<*eVbH6JDJx2chnNaM-8&B&5W>4l8jb9e03A zT zU^xjZdk^DqHz+Z|+zsDB0ZJc8Wv@iHi;4tT43erKY;dXqm%S3;gaHu+rz!_fs)CAw z(vkxxRY`zZ&{XANeX3XvWF$)`L^H?=kOG!YsEZ$gciBT$pn^&pNFD}_=0aAWg5wO- z%2^4T{0FyfKu5_yT0Wo<0k?7_A*~#2@pu5-bjSwv&A@FQ&>|aXsm|X6uARCMf!aGB z%?B0uDbBB=<)x6hxMVNcaXNuF3^%bc?SkZ{%xF~0|J>GI~W~1 zJehoZJ)!NL!=RfcFY&j4ihOWoRRZ-^O*W{qYJLE2)_msA2m42X2L36QNSoluzn#Ys z)SOLo?BsET`r*}K1Nic6(4GxQq}sq2Z-d75pj89B;s9kSNW}qK5&>a>_G~~{pi?d& zI)XrRk>Cm;70v=}e1lXMpz%5g3$z^nAUN(p-6T)|@i$d4FfhOZDc`62lxOopa3F!A zgf;=4LYxkoB!{pv7#J8JtZFz5v?#}Wb`ASvI0MoAwU0wQS>9J_fMz)8IU>ic)#qj(X% zheS2V_YfAy_YfAy_Yjt2>xmKza35eFqxFeWUH)xejE?;K96=lf(D9le4wLnXQepmW zUd$j4Gs;1k%?}y74<2rQ$PVhgbsvOWJMLk9qMQjN(Ca7xIU5PIdI@?)FwR>_eY!6> zHveQP)p0dG=?mK40NRu3*nJSzwdiJPa{Pa=6LKeZ_d!r8@aC{%^G^=`R>l6IVpbY5QeE_-_u&B_p`+_H^>!E$otC!`3NAoKdkAtt6JpLc@?d6$>D9S$Z z3pjl6=ygy?6EFkFzP$@@HN7&;9OpXWK!34EKc7w(Z zv_FA{0#14Kda{7jd;UKN+C=!r*ZM@!)5CtfERc=qm%#P(;YLQ-Knf@__`xj?a3F(O zzRU-VAqqkJ2d06~90HBmP6x9RzvOg4f^zzt&G#SDrIPMI(vI@NG z&jPa9B>pgH%m7lEw}VP{@Hum!m6njo9ONGf&@`$7c+;ivZSb*iA3eGcSxE7>tOIT2 z>2+ZAw7yZy;qm`)g9StBd+=B?sQl`7)Bqh#YT(gbX#w&kXad&4r?;5VgW1o+3*>ZW zKLh;E_hBrv08NKlK+g~?RmSQ|pYFpRpqrf_6B{1g$B(hdGk_*e!B@vZ4_0kHq5wLg zAx6akG{pfrF&iubnv;U~A9N_tQ?UO*g%`yC)8MrtXh!r4SQgZsg2;k;osi)~P&jdb zwyQxSAAG{TZ|i{)X^(DC3(x`A2SHaqwO%UW^3Xoz$$Y}2)7zrkTf?Kf*uo?GA~Z&W zJv^9$4Lqz*;f>L13y{@BNfc8Y)E@xK|@y%XM%3~hMZ~$+71Q@Pf#dIAm38fyG8}H&fBB$4QM$w^8IA3 zpmghM{KTcxMFqU1!NRrshY$EnL)Y#vj^7S2@wXH)GB7k)74x@(4m)Y6V$9`ljRy%; zF%|H)f(}J#sAA6HZ}kSZG^+$u_?x(S7#LhE6D9atl|h2to&t`&Au2Miy)G(@|ARR| zGi}BPUcTT4P10F=bQcRi(D1=`D}}Z`N*I#Ld9Kzs*HOiGN#- zin4FFufW0Q9G=ZDRk(kEgcT1y=YX&j4nF7b;QrwOzIJ@os#U8zzd!Ui_=?5D`Z|AW z4A>#YKHb#<9 z=}_PZ+Q$XHf7Zi;xl+NS+f(4baC5Z;qfhq-&`xbf>_viS^FQ`dJCH{_I@~!xv}cDq zCy4gyaOVQi-W~4TAlj$Hod-nwcDVC`Xul43zT@nmHNd{sr2;QO0~?OtjtlU&fQCXE ztP=TKL1!>DR52#=w}Qq=8mgEw`CCB^+J-9T6#mvcP|9hp5|H3;-NOm-5`T*(Xrm^? zI}+eRGeji-t9OEMdPjo4l?!x(+BO%JApUJWD#75WI?fKN+YUYidod6abpc=*kY7SN z{W(BtL(>jEiCE(HglEnj>t9n_2JbGDUdg1WWejw|rDfI7GhR!RJ= zpp^y2?zEZ2S?$#K6Gcw3Hp<7XDUeP+IA>^yn@W zaOp@bAXJ)w*3p1UlLByQ;?e!V!xkjO-x|gaI*03zgGG%>L5VPYxWME8aTkjen}X7B zKHcC_!lfg1f-ixR0(7h{*b>j~^N#Fln8*$_xyjvsi8)t zfT8rOXSb_>cQ22P2kT4F-ZkbEpd#qMv&PE^HU_CSC59UBfDdcJGD&W)ofxp!j zB=z6f;iU;1sI+CW@hO#RsABZwZ{=oVU}&gf3g&NR1hbe!UMtw-@V9EY;sCPK~@NWtl)Os18VJpEMjxq18OdKWLG;Bcyt?kFjp&(QfF#_ z>r6O#lX@LtamrivMM-$8eu^`oKr#}bis7{0C zgMav2K>G-K9sj#@KkRhp0X6o)EyEK9UcD@8;I=-qH^IhovV})yIjkwbkKK_T-Q@xv z-PREQgAeA$(RlIgKH||0zAEgx5AzFG<0s$-wU70C{?=g7mahNK4!+D^U-mJ9Rzy8< z=}hPG=}Z@RebbSD*K>~IRGXaA#DB+HvDocm2qtN5mGAVxC68r(F1fm3FPj~m$N|0iGSB~E|>;qkOqIS zh5)bz(7Y{@22isR=06HPARX|E}kJC>kI(xPYAmI<*nW51`w3Kz`saR=4piRc`p{ zUMkn{%db?@vEfHhsj%Y?h6WTHUYCPZ3&2zdfK&&8Rl9&yGk`AQgZuckCrGs*OmzrC zwKqgH69ZBVyq0$ScAT+9!lm0sMF(8^L82MlQ}yY7;miEhv-_Km_4_hu#~r4sSo7Fx z#bSAnZf^lk?Z4gEA@_cR8|Ed!KD{P(;3Vn*vP2A|2)s1UrTY*>g`wyFW1uBO9-yfP z3m@wO0sbbhU;qEVmH<0T2vkQqf_H&I9rVEy)X(Y#7ag$0aNToMz$j z23pDT0^Idr#R^kb4418f z%htnXuft_QRUJg{W4Npn8v}y|*p3@)FgxzE!R&a#2D2lVoq+)o8YS#7*-CbpY%K>& zwuJ*G+sOfwT@RPt#=*b<34`5mSv^jeUQ{T9i^z@QCgb-`J?;jA}smMk|+ogbW44`;1~vu?v#Ke!nfbigJH z^T7Ne#lyhh2X?7E56mB(JTP_hcwk#iK^A2hII(CN<6?Wh47oN@5!E(DL%biaeu zIG(*MdiaNEJQ%Yrd|SWybb{`5E>Up+^+H{7RXyPX;CtP@1^x?zmb4pqSRX5u_xSJZ z0Lr))NUrX5=kVxs7x4J+h#>_UoPne#&`cjB)tI0)o8 zOydK--4{H%Z@PjmFazx%=HJHE@H3@UqM?d0vDBrZim9a3uAz!Ku@pS^7JazkXIiN) zXaJ#95G<)^;lx}j13Eh@`Y>3JKtmN%aw!+MPunsZboRfqL@DooXAK|5U~oJ{gX6&f zQeZ*bA*Vs@kmH~#fWZaaC1V2jRGk>X9aSeLa6i?F*|YmnhdZ}RXE^l8mQHuj)it1C z&cq(fE}*+xyFs_Ag2K7mUjP)ipteIP3pl93N4V@~U|i&F1=-sON_+~C!6nFjA>dQnenO9r z#nJEpC4&Iyuu~hMVW;aJjLaYn$k(3oz@hj}^2PyQ>KH*_~ z(}Umrrbp}T5*d%~>7ZH!G_c8h!2`B!gmFID=bf5I-J^`*b!GqxZ3=9nY;L94( zLc=5ZmSgt^$c0y+w73wvC!7B`^qnCvnboZ4tICRPsKtr*h)8oKi z0(+(V@b?=YGd!$M6g>th>}BEd=mdu-cxWEtXJpkL5TnYsdUPN5=!6DP4D^O{(9kzH z4-*O^0=~P7+jl-5-Tk0_jIFopls&qqgPIN=y)2R*)`!ZJx^HRU^hiGJ!F!o}jbJnn4G7`0zVF^yvo85P-6cy8!5N0x6$v zcF*oBpnls0k8aQ<)TPeG2VR1zJrBsO)g>RmH&&Ot^yn7%>^=j#;JD-_=&DV~J=K_( z8+&wvZS?5W@abF(@e5c0d>8Unk8TbR=5CPRzya*h4Q6|EPX=*4na_E2LIkWY7IS%Y zc7sGcx-WWkH-pYc@#qu)9}Hfjq5#?imH;ZVBS3XzfJgHY4aYd}hAvPO6OxfZJEkGo z9CY3^B%6cop@GN-F)%PxfU|ixJez|K4S{5H(6VU=%L99cp9D?`jvHg{1{FAX%5OzeKW2|EgxTJ7w{>Ow}m>Ih;3wB|SGHJv#9)>N(Cp&w<*Qon~ zt|$U60tV&W0?^4=44~vx;i!GgqjL**zwN;X%%GIzz^_@NV(^Jy0DKZxr;CaLWLKW? zTjSf^A6#3%l{k5J|4EzB{F|dl(s75p0oG!je;a%EIgiT^nvZdKaKHF}*yI0kN9%h< zr@A4|;SWF1efa-DALa+2JovSab{~W~^AmsMG0=?YXGeZPcF*SHES}a!LE|Dtjr0i8|(x=J9_5p=kwg=6@5y`sbZV|=FOR>E^$+N&xu7iO*?q>N z`Qd{!{(SICcW~sS@#j7808L5vhAY6Pzd!TWd3^Bb4Fa8?8}I`(56vHbz@z!FLK=U< zi8TI%3u*j6Pp9$!yqw1W@k|>3$19)t>%A6$bUlEm_<8y>e|^w~&-`@(FQDpC)E)py zU+}QLSv1Ad`VN2dR3-+7U7+rq;|@^K;>5p=M>|dXlq3KC1N{3>@~;PN+T)4$_^SQ z0EgrQkmLm%k}o{0Z}B&kgN{oBMHZ-hbmZUeG8eRqnFSUrZ&3q;qf8XMU;s%JTpEGH z2hKMXEP2y1eXd0#=WI-tD4N z;n{qg3B1vF7HB@(093pefLbRRpj@ATDGb|rl!F&7Q zd<@;o=mFY|0L#Z7kkXpJ6?B4(2dK9*12n(}TD{T_9=7`i&f3jP;H)jc-vlZ-kg~Q% zFHgS*JZpm%4|{eW@o0YVA&oyDyfb7cXfi8}KkueTuNMbA9i{Q-dH(R|^%UR_Kj7JX zOaPKu6Hlb^|2mb%{|S_7`9W&lV3F__0Lgs7CId>ao?o!Z7d?3;3{>Rt)OA&Yn0;q0vjEM(laRG2d z#DVgBJUAUS|6neS1Yh3-x-=DB>43&+z{$%8l>Po61+A#t=E?vvADp2fd6|Rz1Sn_! z0T1~ee8|!1{|6lXmyv8Z-28*3)X3HN8`wC|4kyR%W1s_cLCcVib+Utv>^|mceWVCd zu0R}ixcLW1nG{MTX#w>F*v+oS-yFNofl@cvT+j$0sFFdH_@%euY+s<%Wcl{tr-Pc+5Hmmh?eq6a7Efeb|Tjh3CQl z3f%`?I_*8cHQy1Cn?RYNXd-B9J9zm*0Vw@vfNDKRCV+Yov=-wII1_|3GBB(FS9RR* z?2rf_8p~jUWrtk&&=|tB)_87ny~V%H_cl0YkF$YFQ}Fbw>rK%7s_PA~IR7@+JDq+U z{M&r*!sj?$@1=G6@$hf+y^j!jl-B9T$G^?@F-+G(7~=tq@vPJL!e@Q~*9-jHe4m2_ zKl2N^3Gi=oeG2CBZ*zSDW4!3}6XDT;Tfwen4CI9B|vpqxnccd|d2d(2h>XpreKxabds6@IHI~gIqm?> zoPvgo9UT9MGB`HZN-#p>>pJ!cbC1UVAT9i@2JE0+sLX9G=7f~p?|#~mQ1N2eQyM{lSD{Is1;HvzC)c7V!Ns2b)AFlonq z4xnj@PB#gUUOxrL-Z(DMcn6nj_cIUc`{mNFg~56qcW{B~Wat4+KHZ;vx*z*ke+RAn zS&Q9Ej^KurN3WljNB410?PH$S2YmRQ4tV#d( z*EGjY85OX1d{h*`v31;s-|2@Z^EKb@=}k)jnIh@914l?X?n4cq5ETbc zZO9#!@WgfGBUID^Wkw=T)I6+G36%SC&jE|LdFScr;)hqjA~1~?;lS|4%k z{sPJZpe^R1{k*^857GrQmm&NB8kG$4+^ub3h?weFW?*UmCh8 zZ9v@=q5@0pk2un}FQhqkiu?4IWk8p$;D|v`djgXCAR#7)NKr>XWrrv8b>HqI&}{d| z$NE1gkKib{Je&V8fetPORT9`Ej?bgl^@}I-agW|!(4?{t=u|XNEoOZX)PnegyMQq8 zY&^CMbR1U;Xw=oSH;fT7BLJ&9JUZPxJgg6cikWZNP4Vas^8gPw`3it)LXU1g4-ZyQ zO=lrl!VBtMf|lXha5I#C?~DroUF`}wW)*bb9H^4>=DYy+*cgQx@DVh^zmbj1Qh-A(vBDriC$ zqV6f&JU95B|5SKmF^7qP0iv!D-dF?$0K~TQpcpIzjhbA6%d)}G0g#6Gz?7I_rl`Yv zV0m!aQfAmpWEEU?H{6s9aCO(=rrd$6`vjN$$;`mu0d~tjxU4n{>`VeT_#R4M&;hsL zkPL$Fp#&W|2=P%jXgveilnHRz!*Ej`!A*GyH{~5%9q340h$*U|1w>#|K!@W(Vj-Lr z=9V(}9C0n^z+A9fn&5N9o8YoL;il|^%f5!2!Udl}7G#5kt{59^2Gk8M>kC@Z1$Iji zT(*`C=9Wotbu;0n%!8|Y377o<+86*H3;PO}m1Kv_hv~!TtIgOM7?{E8tl{(3?eOD% z;oS_-L1RemS5U>#U7}Lp0bT>{0X|sFv-_pv_XD8&;`m!Yo0}ZLgE0Rudw|XhXay~n zcj1zzeT@RMf$Tbbz`TE-I=>Y!xK7G7?(}iLHplR)DY% zdNdz+;nC?P(ENZM)KYDBWsrEC3Rx!%wx6YheaGql|Np-Zf(U|L=Fxo~E(yA^@3kqY zVWI@8+?2t62M|{g&VsER?(`FAe#qGAC*jfT%D@4d^8uYV2VYVVqXO#t8GuS5(0mi5 z&;eb}2I(D&DSw1^Tsy?B$j#do2gdA0x7LCdCWNV?I+-& zeIB~b)W`Y|f2#y|JOX2e|R+ikl=3>0gb(EbG_x=%ah{M%L2ONlfm)-5qO80f1B$~X!?M5l|8%N z1spqh82Goj-UD@xVX}8&j5}ZkM41l*3;#CP2cF&j0*)On931@HTpz+Xoc!BdAHg_W zAO`<7*T*1b-2B^IpTWD&m_28h**yH)Twj4Y%U~P%x4FKAFdC!ABh27eK^C{%yW*zzmSxj-4SKBCgiy9Q;kI|NZ|DYEXc}8Pve+M(t073ItGp z8nl=Qlm|ey?`_bG93&&y!#mcXTV5cma6nT%kdhcQ{tqdqL1!UD>SoZ9=@3~NcpVAa z&j48~0otnuk#&O0g4QH`1ee{QTVo-zsP!f_7|x*102< zi>~ZG-KFKO-9LT0Q|rO^)qnEoe(BNcy5G0^9O!&R(A10v^eE%j1N^OB%nS^m+;#|h z&KUo;(1XUe`M3EV;NRxD-|_o#PsH z@aR76#J`P&)53*!e<`=e|AQXQKLu*uyw2&4z3#yrd%na2(gFjOy^Wv?L+i`5Ah$JL z18oolH2^#sK^K+QmtO03y$(9|iP`nMCnMWSr~m)|cVGSwR(79>fdN%k1$1x{#EB*9 z9^I!Ao9^Jw;cs05DgmHQdhuGtCXK%pG)2+yGnc=$hlzm!yb2aHIt%g@e=F#Kbx6(O z(HnZ-1GGH*Dx?O92aRAbvhjx>;NRx^fPb4SY>3J8xQhyCgbI|q9)dfL{M&pVK|7M5 zjF9Ho>7&BLzs>g^0HrLN!Q-%1q`F?@<@)L~l5ytom@}-Dl2k7MA zZLZ%y3`zcNuHQinDgJG)KR^s={%x*5K@1uGZLYsS3|anduD?MHIsR>~e?Sa*{%x** zK@0`{ZLa@73`PEJuKz&{CH`%$3?POw_`JLh7ZnxId3GQXRnU=oAch+MHdhu9L;bZ7 z=vYSRqJZ_9I zeZiAatUL62>!lJ=h#Hs*-frLPo{W58vtKiTYHeS3c-NtOjtZzZ<Bd3P@dqC zECt9eAlN`HzKuT~@Qpu;pk|2zcrlfWiV}D=m5Yipbk~mxbk~n6bk~m>eAiD0XjS$x z@aPr7w=pUK9^EA>5uk%p)^-Qk5Z=qfWv@f!>u^<2(?>{+A>wP6?hDR^`nx^{o` z=*|=Xw_qUGJ>su-!J}f}F|zKn|F1!sFW>;du`1o8`5y~^>n&(o(g_qO&fxUy(rFHA zt~r7^AeIA|)#=aS*z5lXG+P8Z_`lcjfA>qsl5`*Fl5|hdqCjSE4-bM%(mfcnEkG*+ zp`CXeOVWKn8|k{GJ-X`!pv%5+tPBG$9=r}}a9s9aJ_b6(zSErtyiO3bP&Y=!12mZc zk0|gV_VI@;;ZXz{^@7x6puuWLEeE=74pM)Du4jZqAgCsV$bt?cg~)-PpNvC4EiYml8-6`+Y#AA9g25~~8~iB-m< z(_I0)d)G(B0$#c_A8~Mu179q`#{gSHZUIlVp!Odm^?*uHNb1Rg%Yx2Ngrr)~lo>>} z8J;ddRVXCY_QTU9XxADfU2b4xVBiGzZ$Ss-LiApO>jjOkK};&wKl-r@=m{`Jlkpn1svkM8dt2OluK zmgNV@6oOcUUMA?#?FL#?Tf*VdEe5LnF2Iz4D!mvL@H(sn zQ2Cz$S}q5#><+_}`L@0-VefX%aqM(u@NIok!UGx|4&dL;;MmEc0$NbU=Go2W*~t!S z*{`#M#~r8?f-K_lf|n_vF|=9W>CQ(C3=Az`7AGT&1?mc;R3k)$x{>3K6(oc@^MTh2 zSOcB;0C*n!GU%QN%y5^0hC6=?Xtgy)*zU>0am0wj%qdTS6CIcdb$amO1H(g-MLDq>9} z2OltjQwit@S1h>`kxD?T60oF_KG2*fB$Z@hPbDFs$S$!5#czovW{NSzOfgE}6eES4 z)cBD(gEe7*nr!LdX#UB-z>o=M@h~zlWPw@oj0_CfV3rOe149Ow6$~#Zs^Bb8azGtD zD?n~=fL26UdUk*C>8=zYZuAUvR;x#E=n2r`TIA8QPuQ30Lu=E|*kwJM|Eut~o&p^z zy3O?z|2E&#;HCtq+Yjnm@^5oJ$$jED8)$kCvdH2Ds5Rnx4zjl5JXkURHrKN-#u*sn z5@e~xWf=b=jBx?RxYp?h>R4Wf@vp)dS73}=kku5Bl^&P_SD*na$l{9oFdb-PR-gf? zPCpq!>pT1uKtqP0aVHmU(AdV)gAX}CA`spa1oIX5371YcPa^jBxW{?004?DpUyJSmMzdMqY(B1V({+ZZfTe9G7XRJG7J0-U0+b= z>l&yyxa`4v{53O{Ax_ZdW{>701)!!AXo!tNIR!MVhAfr0q{WtK#dcd1XK)10&W?3N{NIbf48lMD>_b_-cANJ_J-28}f=YPV1+C91t zHa}qZXg-kP!F&*QY!PBN)(?;FTOQpXK@Cbxet8D?*p&x#7*`WMj9a1x8^XN>9?Sx* zHUuv|gl-LjjN+C)28)B5{P4{+&`3eC^dsm{Rgl%^JsN-g|Np( z@`%C^tPt)}@MxU@s*dtI5b18vKpnWJ2#y|@i#+~6z;vbY0Sm?wF@!B1y`GF7{|_`+ zGL(Kf43-oG3t1lmozevse~&dhK^sXyOUWc1ckEEZ+EMdpuI69>6&yQp$ai~7fZJBr zz)7?hAUE50--Nny9}ZW*T)7{Ie7Cm* zXg<>d64w8NIUq?Fbh;eGe$ajOkggfX#Sr^J_as4N9pSD6xfmh~PV5H!nk6a<{2Cvj zJzh!29glH10p`Rfc;r9x3zn#O5D0FNM?4^NwEu%SK-)?bK%iFzO$L>6%%oKN?i z(BN>#P>83~bDeyt9 z`Kk;IuAnAKnkoZ>Z)fri@QG|GDhgmhH&q4(&(2~Ak6uv*29SL51>er<6OR0v#XCUj z<_cc(!seV^x^q+}Frd@Q<0Hl&ARzW2um1b3G4Jsf^=Za9s&&AJPy4kXF;7AN9J;k&Jq=5X0H4+f)^6#z;~buB4oW6CjQ8y3Q2`|Y1Bm$$gB?Lz7(oVix~LeGQr~bMa4AnC z!v$z*xCku`m!PHLGPE>YftH4=(9&=Xni=ld{l)kID2+Q>x~uTFf!hAvpoPD^#S%W< z?>u|`4LU)a1-c)CiUkpm=Hnv0&MY8dpWbYVm*Jq9dr%hi={0fi=w@{4=K0X+qoPr2 zgFmbLbic!!+kL?&9cm!wcF%4WNDw=9`+Q&lTS9)8_vq#W6(>*&e7fI3N)!}3IH+m| z77KW2ZGix-EfAr$1)!wo*?k6-?O z?XI_Q>HYvpnKC$=>Ym+Sz?r91#&HME+U1K!Hy?O8@pVW%xpb$fD138Kabe_d`3Ji2 zy7e}H>u!DqhF)jJm#-iKrFlY6GwiV#31Cu5AA(wQ4+pdrt3~F+M>K0Hd@}(WDUIAUp2U>2}&2s`= z);JQVS9+5rN@a0oL6F(7rX*56=`|?>JH&(e2gojPKv1QUfpo7CHu-kv3xLOT3Fk16 zUXib$MYW#HS3#~uP6^0vq`VG6cOzOLP+o@+uz>P91l2QLH$Yp0}Lg6|o`5*pCEF$7p_amZ(5FSD=(n=I8`0(I5(X zkM0ki-JoU_Xek+Nd5mXwxPS-qG3Z8V(6O#Rz;g+n-Qf}#f}l`sy$w1W=y(b^Y4c`;aZTb)&GMzJiGlZKrZP12)5hf;5%sF8PvCf2o|K_D0reusI<@B`FK$6Qntz!P@_W8<49*Vv*0 zI^5W?`vQMU77GJ|Yxe{2(Gs9T{xUqf&rj%f`~n{1FX3oD#>rgx=d}Q6AfLP0lBtxf z`4|UtA&bMeV~o9SjQlRgJem&{bbkO}8GHhiut2pRN3RphOVGX0#KafW-SFDg13Wel znltBMphE43sJ4kL7}4DWIyxc%)cpW2!AVg`@a=x%*zNWM976mppab%azu7V^4f5y(A9DA)7RD%nF|uKdBp4&omcfC6 zp~Tx3bOLyZwJie!BSVR{Edv7+Ln$Xrk%{Au3+h;Bf?rF*)Cj;B>`;blx0);Hd@|6w z3&;OQKyfDI*v$sYZ63@&KqE;XK?kG|RfT|ZnFl04gVJ;g=sGM=CzA5I7!hrb-Da?u z^X<;#ASRPth2(s2u7W2)99avJqd>#13NPP)?lb(s$lq!M+V<33$HK(lY6!YWt~-qb z9A=;C&A~LFL+!V69A)qRF%QSb=Br zTSlMG9F+wA*0X>9|M%z)QORhoQHfySZ<)fuzyL0BpyMr$-Tq&iUow^$H@~zmk!ybG zP$Jp*6Lb<_sUE0CXg(&>dZ0x4|1stduVo#(*_)5?v>qrC2J?9xo9&rP*_)4XI5L+b z6-%DY$6>|NLU@b~r((&2`G>3VTkytU2H)&21wPCrDgln&V%?{jUo!HyTmjub^3tBa z8%h283xK2VxT_3LD*24OJ@z0lQ_g$8s#5|;gF#Rs%K7EIYuCyJ$u6hAO!}b9CPH~ z?gr`UQ@_Um@-P2(H!*Ph)696O*AL7`bf-E^)!}0ZJ!0ie2Pt>j4u3gJW|Y4+DRjCNl$r@wet* z4v0pAR^u;lQ3`K0NILG2!R`ak<~jjzGgKD4JSc}tJ9fK)aygG{cO8#o^Kn*aqag{F z;X#hZQ;0wkHl)!2G6EdN$63Mt9b#pLV|N)3EKB=#mkAK?Ircc_Z#De)|G(o7BH|fz zcr_@VnfY5C89|4F)=4n9cGrRJb-*2CpxEN@>^}Yyw0j90&IBCl*&D_Ii6%(-1CADm zMrt|lx2GEI28K6dR_ZAh4r2Rb{U5?SWB@3=E>+RhuAZK^D-}Gs0GKw!qcRfSWQOF1rLSdlfEw2QK>%F8dWO z`v)$|2w%8u1Yh)R$po_(G^Ykx6Bh)RjfBg_!S#ZU#D%zd8eDcZTy{NN?>4yXZn*3l zxa?=R><_ps4}3YfFf+_m67YkaJmIndaM@6}Y$jZ`04@tUWD4S=WpLTGaM?|8y~p6P zXW_D!;Iho{1E9EAV6NhaFH~2C%WA=8_2IG!aM^UYYz|zu0WRAPm+gVe?uE-9fyQt{aM=@Z+4FE&P$L#%@(Z}^d${Zu zxLzIj;ZeqHFncZFhe!FqWrN|epnC}+CilW+r@&=F2UJ01*TQADz-4#BO$Ob$2uYKm zqpBeJ?H62@4}MgYC_BtmQt+dy{Nb{o>mea}qv5joaM?1r-fFn)Ww`7ub_NE>+Tr_f z*>7;!zi_=w@N1c3;FmTfbHH4c0hcZ2U|^5{ml3n!>K4J(Er+WEUGoI-`60OMak%UU zxZdw@**|bu75L>&+MF;K7{D)gih#?;!(~(8vORFw$#B^jaM=TJ+2e58GjQ22aM|B* zSq3iHF=iTE4A6_P^toWJGJ(ssz-7DPvJ>F4v$+@;V&gv++UWqaYWli;!!;j%a2vUlOKAK|h;;Ie<=vPQhH(6Ho%xyp_g78*ft z*+{r-99(uXTy_>*c0OEoGhB8TTy{TPR*?^8uLd8?UR^$zy%u~74AS73PJpXRhpWqh zt80MEw!>w6;Iez+vPa;uC*iVp;j&NQvM=GXGW;<2Df7eJr@;?%pAB5r87}Jqm#v1& zHo;}v;j**gvWwud%i*%O;j)k5vd`hNi~=xMaR|U%#VY`Fl^tBx6)x)qmyLtVrov^j z;Ieb!vP~py6CqV`VMQ|Dt6N0%wRtV-kB_Wvm%;2)Na9Jm~Y^D$cgDrR? zS0kJ?4bB1`f&(d^Pr>z`hwJ?gH~Al2mRT5Pue~tLRc^vCS9!x_c;EVBsARpKHrSILRMT%`<`4S>sr!)0UOvb}KGDR9}D zaM`tR*)4F{op9MVaM{mr*&lFO9#NR9ghgSlk`RTt*&8k!1eXnm%Vxu6i{P^5aM@LG z*^O}7ZE)FBaM_D+*=ulFPBECP1jJyj5*35FN)s+?0GBm|%SMaAuC=I!t80R*Ylo{l z2A4exm%Rj+)f9)hz(5@40#k9A3sT{-IdIuRxNIw2wg)ad5iWZOE_)I#dk!wkC;_vV zLjqx~Fh;A0!wURKPh@L=tA6v?RHuft_u!(~6gWxvB^)umys(vyanY%C3P73k;;NZFJOmj#`W0BLdWgX=vC zmpuj7`x7q9AOka*RR(6Vqzuen1-Ps#Ts8Re8f~(sOS9cmNdkHRk9WKit2eX4+4rT|B9Lx@JIR*xGaJ)6c)pfzu z^~2T8l4D>n0IRzLSN9mM?gd=kAGj>DJj^x@d6;dM@-VkJz-3+GvXOAv1h{M}Ty`2< zb}n3Y5nOf~Ty`&9_7Gh5HC*-+T=qL$R#O4yDgy=wA}bGYm~xa?=REW0AiReXvtR|zY^T;&Fr^@Ym@!DZ9nvbk{CBDm}_ zxa?X**kMqc;IjANvQOc9U%_RCm0+%tQi8ckUJ2$Z4QX>_oWiA-L>Gxa>K&>~FX%lM2jL>?$x?5M)P0J(V84KAAvmo0+p zEr-i4fy=Ij%Wi}aZVTbNr3KH?Y{ayj02?8%cJ|fhxY3dCw|ZcZywEc91O=?R1CmV7C6Sw zJ-YqCo5^8&h{HHMn%^>(g0E+_01w!L_so{4fG+}$KMXpf&;cwC7XlrW38}9l;JdA3 z;RjiO4!3~Fg3e=vjN>eX%cAdoWHwYG*Biy;*&Fc3?l-2mBMV644;Bdyki=gs5p;E5?ver^F*J;nz>$3H6gKn@s4o8ZE~jYq(pe>;y? znoB2-qsRXPp4JD7UV_F<86Y}*x-WXPo-8p19UEidYJ3u;U&99yOrWz`96@gK?F|CC z$&r6QkMqHY0*;+tV7ELz4BF}fF$bhy$)nqe!=uxY!=u|tz@yVqz@ytq!lTns!lRqX zqm$92``}>@&_o320y0Mq1XBRPl4{+2T%`| zg#qKt4baGkbeT?TkK@kZqr$-l%z&~d`lZ?5!6zMo zPQ-u|E}-*BA%zR54E+U`4S*LeCk{;c{ptF>iFM7f+WX~1=8)5Cu0XlOF$Av=1 z2U`D^a)3@6W%lUQg9L9k{J^E|8c55)G48M#yrj2A6P2R(Yd_&s|a_&uyIlpDDkpY*goQKaSB z%OdEbeP|b`T=vwy2)|+1k$)QxLk9=AztjARvGo06M}7eYenF&s% z@G$?jKqmfej?4!iaBv^u-xkQozl{^j1`!}>M>db{i!S`z9N9e&KH%`-KES`tkrlyY zK`@#5w|O#wxSrexJPtnK@Zdhkzs-vg&g6o!27?nowX+bv0Fy`eL4E-*0e(S7enC%x z!_b-*QT^hsln;YW0Am5qet>#AtY8-C2q-o%3w&>K7id9RXN(Fsae*5X-8aC8(*5*o z{=vcD#06SH)$8!zqnD)#bS1Y(FONUsI%UvOaPV+3fApbGpxe8_iVH5@aN-y6=@LleFSvChjsNFkevOO#u?N$f z_^+w>gAF@$m62cL7JtZ9{?HrzVYm1r?m6;D-uTQP4Y~>Z1n4Gk&?Vqd-5`A_{v4jo z2RVGKPw+RT{{R2qgjG z;3n!fP`TWEh{LDXoyE8NvVSj&hp+ZU&*oPwo(EqsdG(5PdHz4--^(-cGrvI64}Se4 zWvu)f7hbdTYh3)yAA999f5gR|3=RMPf98+8{Fz_pP?|RAHg%6bAmx9ER{rlZzkm|4 zszF&ei3PW6kLE)HXwG5sY(Bu@+5C!;zt!ab|Np+$r;9BmXEI`o*X)E)$c!3f%IC=Mj&T8&;`0v@v((KV2 z_{XbPq{+kjL^-Rc_2uGH&+bE@1kJAjj$Y5+V9-7DE({F(Q9&O->BXZrNFj|s@^Tvg zvqSt*L7cA)d z$U~p_bq;hge&!eOdcz-e@)LjLsZabm2Rj{EKngz~D{=%yz1J5MRp2g;XY&ij(&fI^ zr-~ImJM#;$bbIuAvhYV8@ce%eZiC|=U+Ysv4-SLI98|#3a*%<6Ap{%+u%4YJEc`*& z48y`7bkzi?cquCLgoOPe&)$Il9=#$Hp<#ciIL5R4f=BbCAD+EV3ZOm>zktI7NRT@? zr13|c;1_V%@QGiL6UsjDiC@r@1IoDo;s}5^AO8s8)^Je zCqMHGda{6|A3&uXIY4qRU~=H#1os$sgL;h8pFqKR3KX0m^`8F^e&!bhh2)8%r-#8s z@=|<7G7Eo`04ViBisVC>MKb6DA#iD2qN3o@{NN9`SWe>yEihwX@aVpdS}^l#fXn4H z{(2S_mu?mnP?>z1U*lMsBY)n5G;pbWF^wNyo~A(x)HMExdujZU_tW^FK}uBQg88(7 zXY)Z8pz;}7FrR`J%qX#*^dSwU zMEk@ad*u_XjQ+$QdHEAk8T|=Vq!X|H-zRk7~A zOP~^iUjtHn_Bt!1fk(hWMTbYPg9E7e;MX{n#vgeojsMw!H2!Ohpi%^h@5sR~;BX*~ zKN3{DTu9@8b`m0a%~JrX!x3JZtOOU>^RX7#MR}0IS^!#DpYlW$)@c(y@e4S80976E zf|T(Szkt^bq;eXh8B{{cf{T>XpdtmN2OM=e2RcE8%BiBq zNX0X#X8|dmLH#&p;{%8Jw=?bn-#%u2sFa_7o1-NEHctsy!`q|zjRt6-i5=GFY`s*X z3L0!u0EtS2yTPpoO1QgEcr+i;0G|bId;r8eyyG9J&(VBD<1py%`5s|Vg|5%Qz~BaM zc)IX!W8~k*FTYgRxHFVJH6WjzufjOL_RW2fFdEzi{vu^J^CV z?STRO+Z{{NnqRPiF7GJk=HKqA6U?n7Y1B|VOV58DLU!Ryi82&ynVI+=_Q zKzs#OBZj1g%cC1~tqVedPxncW?n6G>r=a`xT==&!xLU9o`o0oHLB~=-1woxT@SbP` zr~qhxv<1I_kBR}mptAzMpf_j>y9GlD=uim_hEjHq=4u0m5+PUPldfRLbe{$%4A{Bc z0id~}2++kdpbJtR;tzwyEw+G94=-k5V1RT_Iv5xjAl(zteX)@4$pr=m219UqaR?0# z3wI241l^t5o1=2Uqw$Ras9Ax0F{6S*eL7utcyxwt@aVqj z(^;Yt;M)BTv_->*n449>H;RI+=nVl~$aU`jb=Z`A-@mRO_WmB!yH3t@Vv@wW8Zv3s2K#X5z{H>j!&AtskO!!+tyCIG{JAg9DcV`FC1(rVD#S$Lf#{4b+L5Zmq z|J`q(gRwk%Lpi|i^XWCQ@Bo$Vt)PY?|29`X{%yYekn4R<@NaYFMKE~~Ol|~|3&G?> zFgc)1kLI5N{7vUTuIje-0J*aDKWKOmXW)SbDLi^Z4|sH+`+wc5*T%r3vsM6dKP4!Q z8lMEUMZw_*>c(2Q@E#~FhF!?{;w5P75p-k=rwyQR2TioNfc+2N+4F;?R1Y*Fx6zduv0PUvf07YE4uL9Cm8c@CfT~GkJ4?Q~eaP!aq zrI#Uh;YtweFbBB9AYNxFo!C&L;t*ci)lj42=vdm^P^01$#@|};|NsAn8Wm?({?^?8 z|Nl2vJ23IL@`95q=(yz213ulx9^L04TcjNSUjz4KT0u9Cg6^;C1)WpceGV?J1!~uA zb5U^uNBb?=M@DYax_l5359-W~VnjbJW)E-FbbiKjf`r+UI{|&VVi}_oh zfmw&l_*?IQS%*#dTQ7m{t#y|0=>7y66>EOR=)wGH7bp#aODRw>x*n{gTEeHdT)?B- zno#)w(hEty=b=dioPIO^{r?Y2zb^dSa`_<_hDdlGe9Ga&{Q{JZ5lkKglN-V0LNGZI zOb#d$IX$(_WbRi{`~)+ z)?E9BfxqSXpa1{)x4S;r32xAU4oFF}J_s6n_x%aF))YSS?xW)3(_Jb6x@cC=MI`}r zhid`9plbuapz8#FLDvPYt^XmVv5V#IH4F?4{4LT<3=Ghsj=z-;oWz+yj!yvJWzp%P zlHmag)=n3d0#FrG0aXhrNUA|aqeu67{+2G#Eu)ZI(?PjGfWK86bjK^~*7Q&ga3({iPi1{ppwY}bhie`fh_#3ASZ*$BBU!f zL0r`PHa)mccy#(+09}vnd&8x>85C$>zxVp6B!H8K2k2^Z*9#ujKgwhscl=bvx-;0L z+x0?s=nZg6(Y_AKtscx5z}i7KDX#$MQfCRsE=NR)`v^J{u)B0ccWno#gkx#_Uy|<8 z9jxHd?K|Orumy*9jo^bh^#}-7jM5)oWtm$$Z4a`U7bC z3&)i+9-XB#JUVL|JdV3A0G0gUG=AJg1++(y0erlG0|R_y8%OkaVnlxh=yqs$^b3IF zf`h*`5ptE382`3fagT$~IXt+3@NaV!MKDDWOko652*DIYFa@AYkLKeqx{o(MVE1f3 zChQ_)*#Xk_bUgsdPzsL!FM%RO0+a~Q zW19UZ=zNUY3(yqi+IpbG0vgn%`e^B`^*{+$GYKxR}I{*Kg5Az2|^?UgH z|NkD^Kajnj)?8h}1WwPTyxp!GplAqq37V(%IPRhXnhIxlSr0n#cw|G$<6Gq-#LB?oNk{+G&t(x6B8 zWKa?T*$FF&TI#?3|IaTF`oM*MyNikozkurppU%~woy(v21$-IO_>Uf9=NAliNptCR zF5wq+4oT~DE&^YsnZ}=gD2>1P2FoXYLGP3_{yOIzpU%~wV+Yb)_>Z1qOmpe5JI%5AClh}=GuXk99u}z5{QUL*e{eVQbOI$+y;#*LFOZq8DN)BWx!`bLB$GWcY+Lj7qv1YOq~XNk98;`0|RK?0W$+A ze?rVV3|Dsot`4+|8X}tjzeFn)zUz4^69a=Y0|Nsy18AQ#MBO2{I?zA?WR_Hg8MbOP z1ioK6ni=M*1o(dCa25szA#kU*2EIqQ8Ggl92Yd&y04r<-t2isn6dCwV-o>miQ|_@c zFo=N7+sDqppa*7Mhwp5?$IieI3tnXO7{2cnwA&Tp0#ObI24S!c(0*2k->NuZ*NoM} zccT91gsIcxg4yBB1ruxLg6$Ro?fC@V!^6y=4Bttq#SL@4K71$TWNw&_+1v~apbKP~ z84kmBoPz5(57!~a1M_b?56oU+UIqqzuY(@L_2tl5wzL_Jm9{Xfq?-$ zbkXU^fqfhW)PHDE0ha{OF_qQ>B~%_y0i9%u7*COM>F!Yh6%8KUSADu&R4QNxkrFYk zQVkzh0hLOSaTQSe4l=It06wk)3N8sya4CRCoIneZUU+oYfbI&u2@aw6phh8d^;fTt zN(8hBEa8XV!v<=Xod$KTLE|f+A)W}2*4z9oknr^A^;Q6lO*uou6s*|;e3cG(sNSQK z8CsEm7bms8E#cx9aAn{Z@D+G%440`<0S`uLKm~{yowBU9DAD$?tkx)H^RTQoD3NzH zKG_Lw$)gNVb^ibz*T?jl!vnSws=EYqNMMXgLicgdpcUw%WQX{}8la0y!I=oOY78=# z37Ry4jAeo*Cn2L*RiMFXaDr@MU|{eDvp_3uAR}C$MNW{I0Ij%z#KbRnOmH(YF!X{) zNkMUfc7>h;WL+41;Rk=qMLY=>8eN^$5@-S3-2zEZtyE9Bt{%vNi={*wBUnw&M;|9(#g= z7qsRa640QF?;!!*4j)Pc4ctNk`VKsxLDv&Q0{R~VT9B8hNPx2esM!Nr>dgSYx$p;g zss5#sSRZJ>BIg5ccs?k#1=a8sKHaB5C%8p`u8#xV%i_^}uG>YW0CY_RsMi2Gq#NvQ zkM0;yQY%q`j){W#AdkaEK&!wq!Kn_kJROqiK(}rqQk@St2%za@@HsvQ?)YwS!p7zJ z(E^B=0tgbCpaKXInlvwfuq7x*P%j>QS!D=;++YIE1>gnTF)9YG#s@$-0DM=>URGX@SW;VJV zSZqP#hWst!j0_CTKbZJi_`nQ7{uWk7So@F>y?F?&6u`BuGY5RA0NkPh)q|kk9K4-~ zXw~?T(W>#bD5>|b^ai(TybVf<`M0~V_;epl>vVSU=)T6k-Hjuy)7h=tji=MuquWh@ zf4v(I|9Ur$gD;pnoqZqzAWmARa{&K#H-WTH=O9qsl-B7T;?aG&vl+A?8=PjyuA)I5 zH%MCu)N%6yCmYZr07w-Ln!JZp(Tm|#G^hyS;3I>7ydLG6no zaOnl|1*Eja?u!@|4v$V~tu_b}5X8~gstDu+M1-U1R7K!+?;x%sa61|!kkP7$pWrO} z3Vb2&CH|K4pzHt|Zv&rX-FlM01vH9)Ys{cCS_0Z#MeBmK{s#>ju=5M}Q)>~1B}&&z z8{86D4eDXK8XxfJ+ffq@|aTm*nh^FT1G20pSd5nhHb zVqjo^l;NPIhmaK;ph66@7mK{0w zoemFJP=yQ$SkSOP?tpbfSt)dhzvU_{V9&q;_An?N;8-byFJN6%z~j->Yr)teg{mPm zAYHo8BEk}!Zo$E6eCarN*Z|aRpd@IU;XykE9<)p0X%@6(0~)l=Z#+O%Y5}MoH2~jx zrvYL@J1fmcJiv>~LckdTErEk-a0T!tH3!dbP}u?AEd?1w11&rU4>N++r-N>y*7EGs z@Bk%u@cH@OjiSb56f%={ua>CB>#4I7LV@hKA>i#NB3z^OO$`RJER%v4sM3J3-GUZ z=iy)P4r+OVnxXC-{OjEX4!&T9G(+8C&Cm)+`!lUGJH)4R^9@kC?c59+sr2Yv4T^k^ z&eb2lf~!Fh1`=VwmeaaxRKTNlj&X-UMHQqm3Q7P$;8+7SL4(08(2^`jV-&O|5;8Cc z$~KVv23p|+X^eV66Ga6mB^ZE{2dFCxYAu39kkSTduMIS9G{5oi>AvXOda{%qydKiy zuw%C;hX?<9@PHU-EEhC3gfPcPMZvTC6xdT9pkxL*h5%|5F$)7hoB6=QwVY&L1aHM8) zcytyEP`AsMj8tHTgEE|}@c~dq1FhsIl-EF+50vo`dCfbdvl~>cKyqskILTqlN6kkZ z;tzx7{2)1K7Q6@r4edj63utvCB)6C{GB7}MOJ|IVL}*yJV~At8NB0|#<~It)|Bb)- zc0cd|$ARTWkmV)1{M!qIJo(pu@i_R7+0v?1cE@eda$)NaWx}BDQ4DxgRKYRcF(y7P z_OM6iM$nRrZYIzw&WP3nr4BB=WilR?m4q%i@qp~8hpu!8=5XmOHFycyJkbogyabf* zeR|6>NHGYsIuLv^LNy12NB0k(&QkEz^%Nbs5Nd!paRIb$3v@9I?qeDp!OMrBK6`l_ zG%x>yiNEzUXiXC7A#&Umv?%~`UzA7p4~nl?0r>^Az7Z5g#P|iI7U~z!1t}11j?MN= z{H;GhYa2ng>-hARseqPj;ynEZbm=B|#XmfvJ(zzScQud!)fXTWsO&9Jtiz9ar)Jc5 zmV#DRL#}h8qPaeusWdV7xGQLXIs-f?C^}UIHRU*VvxDxFidYF+fy3VdY7&Ff^9=)LkFTvBn-~>jbYe2~z;+o^G z2B5M8?iez|4wB+Qjsf`^Vl>!H{_V~#X`Qa%(utyzd_eI6TIdZin}55r8`yjrnBDE{ z0X7=yYBECv6b_*9fP@Uh)!oiMG%}fgyK?~8Xc~kE|90mfTAAIv8&nT7l2QU^*z9KW-JvR)}Stf2;4r{1|6l;Rz`*;@~k+JK!#Vz~^ee{X}UEJps; zK5+4T407iUu?B%M4J1-Ms8CuW)t2B_iJ3hE+|<2P7U3aj?WzXAl3bU}$2l$asD zb?j#EbanvUJ__nx6*zW-x>q?MKBxm#1Y)qHbvl=Tr_wzj5d|8^2FMT{btQLILRS;Y@5-uADmj&(U1dUI#F!aFHO@^xj-Mj&@V=*HGLkQT-pzSRX zw_Jp)y9PJ!72FiirF{?|eS^!&!SB6Lh2MLj1z!PX&cwhF4z?p1t}Y9%E+4M0iiv?C z3aoA+T-^$|y0vh1pew&1EJ9Nc?uQFff1)Xkua53|F@cu5Lfvl*@40TX5O?a9Jk!S~^bntr&dpwRDoK z3=H?d$38j0)w#pf`M}jBz-80nvN>?sIdECf+B`_;u7Jz#fy*9->jmB90rBMzxa@zp zEDIa#EK3Ra-5T=nyERncvO#d!NVseqT($@5P`2T)PT$C!(~n2vMF%cY`AOzT($)++YOhU0GGYS&cI*?9>MtyR|mSa z0}|rw@HLA<91INn;N0i}zh%Q4e#=GxTwOE=0|RKyA`1g(y(1)Fw!zhP!`01)o3acp zyBaQg9WHwhF8dfR`ws3FQTW{&GVr@K6yY~-n8Iak;IfWzS)#@_cwxgDpuO&hVGYpu zF{pNHc?+(61^HW^z(2fQ}5J?yyEW(y&G}XjlV2lYJe12oXGD$iLp52Rf6D zWe5>GgAJO{J`SG6293_)AKFNPFD3(3dys)Z&^!iYXah7j2pQS{ZKp#T+5pdmLPwTf zdVq&CJX$Z63c*G#x^p1IkMV~=Q;h+}-@ro_F5N{OzWnPyfY(NXR{D9gzU6OWVq{=I z#LyRT#4z!!cD#-BF9 z$DcrL6G$Xofe+yaqKrRbPCKEE0qTMC$N~NqP`ktzG!741NREgK$g&S6$g&S`RQ$mo z6*M0ML|gXZ&O*@`pmPv>iU7Jh0c6#3XsRSpvIm5e*tt9r2|-*NB3cne|=OSTu^|sb-S){>}2z8{a?Zk za+_l(J2?D(I@LV7?}Icjfoi8-P$a_kiTm`m-~7 zy5(>!`0xi%d_l@;@O%O2YzNG=*P?O(oEqn-7%+ii<%vi49u*5F&^bOIeL<^HKY`a( zoh)H??Nsz=yEO%!3zkpeMc(QP4s!kf=}hVbAVs;N$?Ax#@v~ zk?|R@7;4;RTfpKqCO+;kXha+mtv&Ge&qa9EdKW%B{th0kpzTzUXtif#V8{VCyg{q- zMxzjNAPRChG9;of4=oBoN2@^2Rav9L04e_&KyyjRG}b*L%-AY2WXQ%zo6>|enHm*{DQ6* z_yt`bfD=Ew_y;Wm=yXvDfD5>&BzSbUf+`$P7DLX^8lYOm09313fQnoPPzh`SDrMod z3T*F@M%Dxd?-LEDgG|9}>_ z^!lg-7+(TA*`@mdXhV!3Z1)RzTPkQqpMSgS0iW*F6QKH|0;8fSfL1hOKHa_zpnAp; zq^IrnwFQgw=%LqG5s29G|3Y07% z6_p?ptfEqYv;3K0+k*<2U|Ty$%q%~Ax<7$h2B2I53PYw&DR9+1npu2QhH+;3UjoT2 z?4URYwaE@RcCvvZ9?>SFQf7hdGXZ55$jaxzm07~ViK$s!jlY5SH~s)E5ox^yYQY(S zTX3$O2H;Th?KJf0oCB>}K*JfJF1ri3vLINe$byxDDk943lo*u^P&b0|It4T+L{6Pj z;svTtz->eaSL2f|ou!zS$p=tn!V8`2cH%=eCW7`{LJDcndFGHp8nmhgQb>dL1w#sH&>AgBAuYxTJMc}3 z5q994F(YjGFz5g~NMo}BUPy!HdLYftMR2j*jEDl6lr#qJF#H0S$l$c~ADY&_l`xN{ zF=)Gm!ZhX!+Br>~GzKZ#Kxqt8whfIm=Gi@3j)50rfr?5TZDR`4*pMy9AbqdVatw7q z1%Ll*fa=O2TaMvQW6<%o5*2|^(E41^cv~<2F<%eZn6EFW>;J=}`xkhy_W)?HR|3?y z1r7Rwn`j=Lkip&qpyn26uy;En1E>q{(d#P#>c%_n03E&rDiS=9586ZT!}ANeegL;m zkh<*-NbCTQUKbSykTDS+-L0TuXiygxxie3*A>wDC)k>`=OCdwVp#2H1#wUF`QyD;US+qmLD9dvu5H05#woe7X-n zLdV*52QeeX=3rf5Euh%KF;dL#(G5B$myLwcV#r8wOnmHN6$SiB11tnt8MsQ!y&@pI6kU#vuAWO~U zk2O2O`w`~gR9p*6#Xg;-9LAS?I#W?nG0M2J2gpAh5+&rJBY zNI}a=SV(~8SK(U~U%G-$CV+2`f|PhCAxB?;)?^g8wjKc0dEfY3euK_Ygm07rjc$Wi z6oD2Jffq@DCQ%_Fhiyes1#FQ7bgOPBE%r)9BP}ZO2Q4ZBtyn={P6XN%MQ}$VIOIVK zB3wE*gK9Qxk$|+<5Y#t?RDPgC`XOc5KKOJRXzeVd3G!1_d~W$Vcj z)9#C&{Od1+B3hvvwCS{z2ekNC0lfK8#x>&tM7g9Su;~Dh39IR`4t> zNI%k@i=cH*3dXm=J=#;?Q?O2!WPzNV>H@w(0en1VB={W2k{~=zegSgw55{s1{_SpV z)+b8UT2GcJfn9vD`z6RcQP4Rs&@IY9=3;%Kl+_io4f7>vMXC$` zcDDk@Ua$YgmmIqfI)4A)!@vG$^G^Z(_G`=x3_iV(DWBw%j@?s0hxIaeGGF}vsQCpW zf6IAh1_m$#G~)nfYz42_Vl3fy+>xlr$nbI&GXn#-2wB3+z~I^ZLx8^tlq{M-Cp9rJ z)s%R4pYZ5)SMX?l_yKw^#Ap6|hX)?bhXp{Flce!mFo2K6`^;bOumB{r0YzxTXa0PT z10W%=BR})kdmI3%x&RV_YrNoLeY0q}r}Yi~W?5zihHjSm{3kkD=6n1<>}h?Y=)+46 zm?Qs#Z!zHK-{#KL!QSD|2*;1^)?gOBMSUYO(4HQJyMDA$ODiA zKt52Qi4USdZj^Um;Fkv-s0WIDmiV-TCpgkNS>mBSdh^nZ33OTy3uJ!3`3DCmJ}N=+ z!3A+W3x5;n3~Ts(!M*+h9{&${^vZzm3Cg9D!1|YE; z5I%_cz=QvUN2lWj&?KP&Lihzl_y&?NMC1Wj1j2p+z5{9&IC4M(oS*mwSnfm2K&WE5 z5B1r@moboQZ&->7ksQx~>90bUMC(NC-lu zzktNvAjCl246c6g?csBn~D5}Mh z+8@BfmzJre*f4DZ|8~su@mkVx$0S7@$BiEa6^0g|GjYJDO+oLr2Q>jeM`ciiLEGaY zr7ANc?6gx*H)4qb=q%ZAMh1puRSXQQ3{8v-3{SzgAWem{R=`=Hk#2}AXkrqg!-a`~ z;RRSnIGhFAj{p&?g^Rhe!0aeuf$3<4vu40qtKlrrih0Ni=x3l)YKuW@cwWO-(Q~je zFf0YTo`;ozVFj24I;D0Qm?Z)ilYp~8=hZF;%PO!kFhE!;a9PkrkPuUJ;9>@FmI<6? z0cY93S)i5r5WOyNF%LM)AvD}EY=?sY0|U6)^U!|afwY{VMMXgXw0`J=NB1j_?w{a; z81{q5EiUo5Yy&NAYduiP1=jg;C1@C9yJHFe_P_#AwNlFK(R~#n)yK%dkOn%S**UHG zhhT|qTJsNPB8+PBxF&qMn)f9sEbpwq1(q2$?p+@~87 zP#)cLR6wBwX`l6gqZYQx&7<)MC=EEq#m9n97iL%kUUmi=oQ8~8fDRsR12=p?mrf>t z7sh}NI!^+#KnvfJ!7NaxH3iIKV`N~60<%Dyo1(!iGe!o67%Octy%mPhhK{D5T(86!GYZefOxaX5sAIfglg@5sQJkU>es7u0}% z<$ue1_1^J(4db2C|ST-OMKlYK#d_pL(&g){u|;7EFO@o^$GsgBqjz1(A;Vi z69WUdrDS{w+(-hQ%Li%?I5C0JAXo@=X1y`!&VbG(DiH#Zm3g2=?V#I=Kto}BR1yRj z7{KoF>0F}{0CLy&kNlcjR6tSknO^{WE38lV3s5p&0ZHbb)^9z!|9bK}{{^@AmV<(B zdssE78Lya z?Vw{sJV6(&oCXgbfsVK{KIzj7xk9x02TM_vZ*RcgvtoNAp1zY_dF{`&&J&PZjleTAwHe-G$9#;L#hz z;_?3=Hsyan*EZksWq#=2YtrVa{gS^m@!$Xdpqg|I_MiaeLQo`v7H5Lu-KQH8<@_3u z`1b5R0$LITN+__53QD?285NYq!Rvuy4}+SwkgO^WA6&GBcXjW9t}w3x)l4~{m<4AU zP>O(Lt!bcG24^kM{Z)|NRiFi=Enu-(@V#}Q9eP6G`fV9}=iO68#tQ~tHMRrfUT{Xj zs6jxkhh@B**fL%)DA_WXN~JYd7b%nq`+{~jT!qcbzdR03!p@+s3=jWyZ=^n+cK|2} zgR%@LpQJ&Pva=f~d$xl-e3HKfw4&ODf4g%5G<&*qzX0XV4AA;&NQMRFPEdje-D2i} zlsiEq=8)V8%8=mP>Cw4GWd3B0C!cL!QOy5-`malAb%_9wSrOzD7~lggYN9+-|k;vd1rXk!F4NiyJ# zkrYr;Qt&`Df*?hDi4SRw5y?GBIbqo9NW3Lj8P z0EHz|N&p2qC?&+l9`@)y`uZ-mq7Agk3DTwj-2nq>Q*38|wJAVDN02tfa|T$O0yLMx z4^9M<@I(MQ7^54USU{VaAc+8UZ7QTq0or;2OAe!LitU4-O>qW$p#yGpjj%SwdF(+k z=-U*K{ZvG^DMG`-cWl7fk}nWoVDRkz-~&1r&ZGMmXid3=NB2w6it`BLOQ0rw0i<;g zF2nh^yB7KM`nvga`lxt-uDEsN-_8kb2Xpdo_YD9`@NahoH}ZX;RXnJRSkB|fznu}( znh<4%G>mycSCH{Vkk=is*f8kGg$*2G)T_ACc*;{bG;0jNnaN96^g z!M{WW+#3A;5!9cZ0BYT9Zc%9v0N>ILZuon2g4W|({{YQ7^E=-FZ;Sze>Xel?S55vjCz|j5Eqx-T4|N86T zR>pVG-h^JzftKB8!J!Ch?|=8{{sG?fbraMJw>}JN@B3m;kS_e&Qwu!1kNNVie-2)b z4{h$Zfz8bUU3GB{6mJ@i&HornMSZ)EfLb60u-?T>b=6@Vz;(om>paU(z0|b!9&P&h^0^^gNJ}L^}U3j32XLo>Z+yd=s z3UJgucJKkSOZN}NMd!wEjc+@4e+MlXb@uH3JE8eEN0FrC4teZh!M}~Y`<%z+2hGPg zJh)%{KkV`UxTE#GqEp?VbIrR$R5bX*4|E^?f6#~d#U~Gbt)txsK`!~kA9)OPcX;fdM+6AOi2Cc)~j=phHl0gF7jpt_h@Jw+UWX?}T?!_QCuA ze;FAV=7Kvj5=;yXkXjqmzn%{k^J8LQSO8{aGr=xF*$3~;9A#o)=m6LKr{JBLT=;<< zCCsqqVI};)jsx(^sz6gd9N>u_PtZZ(KfxKpr~3jpM}p?Ez+*JVCw=+XAMynCT`PQh zJ^pwee97d|8_ePP|Dabd59q2m2KbbQfYT3;-XO@3v_rRxio<7q0Vl|iG?)n*l6C?O zNjrdtmBC`5A!#SjkTg_`|3s(L2asM6yna4M}QOBBFUT9?7|ZzaO0b8gK?6rvA<=i3yb4`(AL+*(hT_<`*A1P`Nw z2Ash&YoN2vz*8Qe(K~(^iw(|VhqIXAEI|khbfr530|TU60UC0Ev@O*c7#JXJOG9|u z(wYG_UIiKngN#@CgRVOTw=ErlL8GXS;X6QGMR2mf=sAE|y|8`*mR_)L>;KX+(88Jw z(CVXPSQoM+64ZSx3FhC9y8{d^(>Xw=jai>4)oeXkq6!Y&YoOuUOC=JZHCNDE#=(WS zNB4`Dpo4vk-*z8;`2yOxhIM{du$O{Iz(D=e6QJor7XIzt0sPyY!IdMVQiRyV-wIlr z393D2fv#mm>SBX>#-LgeG+Gbsb%XlY#h@pt<^=9^KdAwI68f zGN@x@02_pW1{|dJ1I>j&yGIfFSjTGlw+DjG`|LIkpC1yr{1w}4K+;olAp z8F1cr#~wC3kg(xz1)U`Vax3V(^zN6CR^wH0t8o|j20Rzgi35nB0xeJgw;JbwR)s=Z zjYilLF#q=W0?+Ov9{lU?dhoA549zxvp55mioByzs^80ij1~n5Q1NN^K9Gm}epbK*Q z^s-1pcZ+&3fA#IPiTBa|3dueSFJFPPk12NBJ-Q)*=+S))Iop65JD{wDFZ;w1pMB23 zvk#~chK$65b^>>UvkItb0~wG2ovAhroXtRO`srYn54=GGI!O(p7nHdmS*jYIrB)$j zDbNAKpbJR?kf-%rR4k0&f*Ucve7Zk&e+4aZlyuyor-!vTgWmD3k6qTY+pPkWs6oda zf?As%pjNYkr)3-HMmhfGRV)k)Py-CH8{pX8)&UBX1N^O^ee;gZ`%D-?Me z?$}&c!06e{=GpBhQJMxj@GnF~;r|85HNEaE3=EF^+uZ{Aw}q%EH6Qrn*x3d$qTyF0 zfAeSX_0TRVifIQQaPaRx0lJ}Cf&0V`(7h*)oqeED-iBWh@Y;d0Bj2ff9peLuq|N${M&p~jG7PqaO|80w&iCefAcbM$8ejAikV}>k4XOJX;51Y zA+}xs*=oRj0cz_su&qBqwjPAq3Q}MSQ4DsX3BrlsgYO%DM3gf-b{}x;Yy<7)bL^Z3 zS``6adF9yI2fAw6u@fAO;Etv#XgSLXetCxOgP@!`4RqOJ_cTz1cv^>5@Hc~wdj~Z( zfLJ$Ux?gd_j@K2UssEbG4D`2T>1_CZJf?JZ!_(>ht8daMr=UGjvO z>DUd{z`y-~N3V>tN3Rp3$NyuVo!|t}>-Zn)ulcTQfl$=RqrmCxB+q4)C|y zF)=VWT7qMYzr_$Vo!0%<@i^EzP$#T2M@1#g(K@eyzgYy-yOnofKvbNdGyy6SG$6^s z@xK@(N$7&iYzCY8$EUYOMZ>lGsZXyixX}nQ0JciTm4BPKfAXAVpon+n-*(EqyJZNp&Cfaur!j*UMU8vg&UWpmsCq8<6S z9dz%6+SuLG3bM(O`E>Koh&uM2palAwt@UIjlkrLC&K|H69r?G16?C8CUw;)AOT8>Y z9@+;znUA=3LSo228KM;C6X)(J;64Y`2ZxY-a0t~0hY&tEM7$3UVfx@CZXfiDoM7d)A-xOTQo1Es*`zbyPsQU5?!T<55$fbNh1XY&Hb z|3_Uwslvtj1%K18zyJS(iXd2l)71a_|NqzKpoHNF&K{sN946q{xekVklAt2I1QvY23@?l zu$+N`g~1Vi33D(50|P`hih+Tl5nRy6!(~DDY(QjZ!(~B7xI?M|erDK$7#Gl@x=PT3 zn03qy3{7Ab8w&$NGniGt0u%FNMQr#C4hsjJSOhs;J_5N0aDCxw{MHkCD9lY)coi^P z7e^KF4Rq@492~OU&fqElRKS2r{{RomW)PddS&0?2Qcx9?u+t`Zw1N$phux6wX3)4i z=#tzipsPK0gS(-Q-ADObL8V(-b9DiuM>jL*lvn;%P%qDM2Ro=i(HnXOTtc(+w{8V5 zdUOtGe!o9hLLzygqXypQ$ko4%D42o|L z>)?vwYS4akP=~d^wfktA3#4KJt?**t-+qFBeK#nUk?R)z?JZ!_(>htgLA^*9>jOns zp$2w?Rf8)Q8+TB};`#rWM<+OK^*a8CxC&ISykq2V%>&mf-}qbBLF*OJZRD1abkNcX zKBmQ6!ll=N(W5u?#D8HA(6~V>s2jq+E%e0yZcuvZK6UUVi%a*VmP?h)%_l!NHb3J3 zc8H^d#j*L+2N(YB-Jpcz+q(p`kB7mfdp9Vw9lH;@bk7Ebs!Mk>C;@?XE%`7%0H;^} zR?u0J(0a#>fBhWLnd%IV-JlYWMa6M1s8aCh1?LVI<{OUP2l&^+$A}yc{^oFDz5%W$ zT&?fA^84R)vA*HL?{mYW`;f=M7c8#)>svtifB}5Tf#bm+9FEOD1YG!i4!H5J-vhc} zg~74=uoM6K7BJg|`3ES}`PUzE+y$}vhjmEZrbtMxBeexF~>kN81m zgUTfaWMf^8-@0^y{pivO@o^_O2wXaMgA&Sda4dpS%S%x5fvyh*`5IJ z%P_Bkf&=VFe~7oiY$E*2za5;XV2w7?_ zLopou$pKm!?E}r7E}dW-IwylN9wcBuwVq4oY)}E>(d}FTDt|6K*th8OaU#KgxCvOC<(C*bc`@W_8_8u8y$5+ zENmG;qi%KB`|q?Kb!)_K?cf`AgG{!91_mHy0HIMg(7Bdj6ffXnEM*5R;1Vcd^=?w|vWnBM!drj()mT?J! zmvQl9HyD~4Igi2VuH=nvBL+wK*I=-bvvMWIswoWs04Vj4!kQ1vL%bZ zr5hBwpcSp4T{@sUhDAX0U>2b5dk)~qFz^f*Y+?zzwB4io#Op-R^b!(35WFZHvh*D! z?+9KDj^G=D7l9-Air~fH2)>|4_vzO>psO4~VF7`*a@y?TzwmegN7n;=;f{i>)G{9Z<_XtZx+wdvqT}3!=kldq+UZ-e6G% z2~@~s$`;_jJq(&qfK0c6ZXkwCx5dJz+dyX|LzahwHiJQya)AafAk%H2ff&eAF3<=x zWPb`n1A(NM& z^ap3LmvVsV*_<-x$fkPT*m;v)mhf-R*7kq=FMSMXWhRdMoVnfgo*(Xc1pgm3SY@%4eqoIZnL-{O^hM7g@BfAfl?Z5LJyK2HbPT1s76Ch)h^vXK#3ZZilONo zG&kN1PSlXv)z$bdI8i4-6E!69Lfb8nMhrMnyQnCnO#ruC?!u?|Ks#*^X&E$@14+x^ zAt%UoU}#!~Ek1WcOn!o8K#R{o2eyD52|5KEG(w1276#g;jggkWL(b{uZvl;3dO#-l zZedT${M+LSJiCv3@UOp*G{G0-*?rEj`9H=4pNeDie~bw}exF_zP))!9o#X@0k(OrDo9R^eXVY-dRQlhSgx! zJx17)CD8hGNKW8@H+Fd8jUCXwMnnn+ZF~ax0DEZ-T4(KQ{2#Uj<0H6zVG9}%>)Zp~ zNVf#MEZ?;^T>!L@9h{nBGbEsadgvN<&_0LnA3;eDw%Y-;CJR!k|6qh~c)Jc;#rL`j zR9j&YPXg6lSj4@+Z7A4AJA^q#;IPM37(As8YKwsi)C2r2pbPAMt&i|GEn{F{@apw(k@M~K`0wA#)8}LT zv78UI)KtO8`Xqld=w>j`R`2GAAJQE8^UfejOnELDA^L{2Q`1AG%FY5xO=X6yKm+2|6tvw6C%ok{m#J0F-v%BT_A65LmGD9;cN84%Xz>}4?}nhA2bOEDgJHY z#eW)NS{u6C4^+coPxPRLF7OnOxljaG#STf#FW68E^U)>>Gz%$UZz9mGiK2|%epq1+ zE8GXTi2@maA-ah|r44k9qZ{bJTUn{Hfo>`Gx`LFE7wjA8R$w<6bL0iQ2^3V`LYgIO zz++mVA{kV&Ar;A>A`nz06C8OVZv$N_xLhW710C*>my%jg54VDp5tndKtzTjds^d#c z!7T~I=m|)a4QVt+8{Be0$V-DpSHvJyL-z^9I16Zt7|J*cXrrY^^DhDZCeS=4^h6H+ z9?*OdVw~jztT6-|X`$vJGg9hFpz5hZ61_ns) zzY*U12OZ-9>HQyKfSrT^+R6$!PXn~V7QzBeUqM*f@N+i?<@g1`6D>}7bc3$L0iS5$ z3EIyB8WaRA1M=u@1cio&^@TDX$hKPk7SNCqbcs8^3;*^)&~Zv191s3uehoTKsW1RC zUI=YA@PZDTaO@m~VJk zA1`7B8E_p`oq`;M1%AKvi>Onw8CufXIV zF!=&Z{s5Cd!Q>|}`4LS11C!suzF!=;bJ_3^u zz~nr1dpN=fUJ3uM@3<>VC{k;`ItFJ(-DSW^z&_YaKFw29Hfgumf@?m6P2nDl1I~)AKtPn;927fRM zG(QyqW`Tyh0>G>UMh1pzFe`4|#=wdKy0wV)MG?)ciZ*K(z1)y#8U>4{EmIN>hw8=gZ%v!_9z>oxHZD3?zNC&gFFfuR{fLWj+j(jj{4k1Rhw5=rv%zDGfz)%Wif$rGJ0JFX@GBD(VS)hg2*~;9>*~{YW(Hr>3t5>AS z!}>%yr>FI0{$|h=257@o^Meok8lY|M44%Eg0%;RI^Vd0j@aPQ!j}CY?9~1zO5b#GI z;Ez1WFUSazz5(yM=%`aHIdPTfE558dX{C~i|m!}bOoWXvlKOVh7 z9EcNG4t?eqWaQuP^ev4)?*xDJ$<%#nkCztb-?bvz!;2L-?< z=`HrPK2@yf%)g(-+oRW$#q<9`H0^(Utxpx*KODxu!0--ypciO1b`y9N3pj>A*RX+O z=qcnzLU0V-1TB09pDqYGQX3RQ9=$A1zP$ne{dz?vf@8?X`VxQhEReeH10Kx}J~-|Y zU|?VX4dR^g=?xM99r5wm5fo9r=n(}S%3%acBO(ea>?i;hM?@4%TmdYOh$xsiay*?V zRzj+lFUSZ|^c|${2UsPD`wh&(t;dlCr0N$)({Hd+5cemT zg+~(yNY!7Erhj0iAnqS93y&rqP{sls)W_c{^Z);UU+Wviz8<~7j9$GWQJ&0~eYFqs zZ)5W9^<)8g+@m*$8Ehc`HYb86{{bgl@Z>1RVNk7b4V)TXjW2-?%GL!fR|KUV7yfM? zjQra`T?S^ygAXMf5B?NLbKyRf=GMs*==uMUul0qZ+lQg2oH~JeNwy&U79jnIVMP9I z91uqaFnaVlFduxx!F`c`o5vq8$Id{Ad#)Y!>AnQc5ukHYz&Qd`U$`1W$}CXL z;@SLzgTD#9DF9MtHF@?1{P%$72v$$)OT{&w-4{HXAN}ChfD{zHP69|L6F&3!;MshT z!=u+hA&o!sR2qL2C{=iXj(%iB7I^T9U(k^QS>y#sL;xlN;z)QjA5`$MK2@~B*ZKs1 z^9crU(gkhCWbo`h056^(W#|RZUMCJu?TcQ$EFB)ruUI?|zGCwDf5^9&XChKig&*J# zKgl0{=@Y+}!w-*M2Z2xgk%z&_m0ytY6TgtU(k`|6Teo#hfn;G zm(%#8KxLXE2T0i$m@<$Wa8U(HN2T+9txpsyr#bWIv2=lQB`8C9{y&Ih2_%EwJq+3) z`UPBAf%X_42PY4Y<~JP12Yk9Of(s}H$g!0I{7s;vfkA`Ie!V+FNHMzyqliAUIGZ;e|F^0cYNT{d_VwnIHzx~ zr$ZY5qr;#03l7})%wKTgMjC&?#T#k-*8(qqRc^o`djl+c0Eg@Yuq~cb{cy$S8_0Tbl>D}1x@jE zPXw*L2k&3?l=vSk0Gg8m?U8_NNBzN6Vh$P`C=CQ(f5D7#)QDm5 zI1XNQ3~E_}Rx%(qD1!=N3D7mD3ZTu4J3yT^P;ZaH1KUB0;6CR~aP9!y)`Fp#C|?+x#t{Dh+IvOE>rw z3Q$ARvHP84_diem^)EcESA!g1Ea|vI0Q-V_4{fjt@M%!i?~7SIwEw;qfbDn%g*898 zKKakz0y;!zFQ@~`FV6s4fbU`bxBEM!^U3f2wfh=0Za`!E#+SN}Ie!1veZqsW8{9)o zKIWkb4n_~{AEk$S-5FnkS|A?Q8$q(gykHN2ECZh|-VF|959<&7ZJ>^zhxP~l7SNIx zq{@(gyEp7iCFB2~o~ZQ)kM0+sB;mpD_6`(ArJ~TuAov+#9^F4)n}7lhyOgBkj=wtC z=f17~mhyOLe=qF>Cj!t=9B2p*c~%5;g)Au2A=d*#u8@t3k2?%H>Hu;wn;d-6F6h8S zeei%9Xpt>sc5fpC14A`;!GPusA*O(u z$dFlZ(AgZ2<-4FJEu>rR(cK8TLIAYSdLoF?IT4h0pn0y_k%K?{fJ=9vfN%GWPy7Pl zR)Y`!`U}PfeE8R2`sB&4b?6g+B>2?iG=BYqMdF|MV-A8?#k?M%xCG^i29NGTpFH?= zZg?=?=w@s^P-^DdebBS}vN7Z^ZIABL%?}wpnh&aY^olThFkjpW>aBWMU-aO2zu0}q zqxk>_Bv&>6U@R5(=sw*1fZe0{K!ykNVVF2msWGaN%!lEodU!A&gqnEJgWvri!aUe9 z0-)3J^1&ss9s>gdCzzGSz`$S!4pGqg9}pH&lOJ@u2DthJtvY~JpP*@dNR$5(sL9yt z{NI!L0A`CHblf$(#Sg7PgE{y$z||V4Q2-lIOyke<`oSN5AdNra1gN2s#-DhCKN{5b z_4>mfd5AyeFsOkIkw?gJ!K54opn4H z9mR;$Gx*Fe0Cq0C3I+|3LV5^HpdNxJ^F@>n0;t4f;NRx-52fKAa~Rx3*yi*frRg7Y z8q!VJ#)PTbk%NDm6C>!%6D(T5)iAi>H3igTP;utp#v+N{TYy*!I^Yr1X}EJZl!1Za zBRI}M%i;NJK-;rG%X{{LYh+L{#{iav)Xl!#Cw#gu`E=g|*U+Fd1t8V#4+Z|F{oq*l z`0vx}^xv2Hpl2_Sx3BdLXeA9Q!aTcAdNe)CI0mn(;PcRoWT|I!J>yA-7+fR${BQd!hJ{xfz~)Ng6oA#{4GC_x>|2QAp`De zftqk0y-uK&;0eAAk|)PAYqVr0!hYG04((Zo0I}r>I*h0Sav9Cgf#A8`O}eq8;c;Q zk=J~n!SnyYgAXL2y{~770~i<>?mz;iGe<=Mvefh~_)x%;rF^}P|3IhnKu^i`u*~G( zZvidt`0nTc8usgM1ciSIYx4v4*UZfi7+VjNh=9g~L3>#ZT)Gc{8WaZq!#O;<-37p# z=RqrkEZ2i>SKx02Et2Z?cL23KEI`X#9a>NFw}P5Vy@4#?){?)({|oy-C2Z?~5*wH9 zc2H?=eA1(PI;imX=$;R<8(i_iOHf$3?9qMR!}^*BzdPj4^zP{(W1(g?zhe0x4sL{j z;@YFT9b^nx!!drh5766gUV;`4HUDHR4e1T!hid$Pq4hwCj7N7rM8iQ);ocjV0n*BR z0qhPmdpMx>fM#Jlx~GG+H6N0Ao!|VEskEp!Pzc#zM*Cx>E zIwi%iH$Vn#aS1=n(r(Bx28i{qkSHk8^5|{{jo5owf&-yMYAr zc$|Tafq~&L)TM`+L5aKqR9=E^;{a)7_dw`W1?d4@TV5g!_DSo35^m6TydxUWb*SL0 zGj@Q++QBRZ(Bep8kZJl13=EJvP(djjX|XnVOX^E-3%LunR2w`r1!^OL?(XH^?pR>_ z4V3l`__s$EdGN2l;laQDmdC-@%%C0ZnxNSY&^ZyH=@1SN##V6RX+2OP;KBUCgZYPt z_Kgx&(0K&c`M3KPdGxY?7W*@RBG>@5F7qS*cHaUp0~9An%fb(X`JmH2Agh|e=O#f` z7lO~f_2^xrvg5=5|2sfaw&1`(>j1PG@PiIg|KZd95$wC4;Byf!@wa?nVSrqP@tg&G zlC)!itML>5?S)02{Odn>@UQ>maqt~8*hJ$?j^97HKn}3o1=jwJzhyED=$vWLW>3|3Dx_@|r&vgC?TDWA;eZ#~0y9dA9KcDX7VDmwT0hIhd z0V>4XZ!?1q#|D?r9y5?9UL3oRf^YP=4BCziy6giqt^^hV^>LejAny2>$IQTBd=h*( z7U*~ckLKS3{7s;nLg2Hk;A0574}*r?!0!9ZpYPIRfP6=XOOF9q&;eD@0W27RDi{D3 zOh6S(0F5wuSf40b>|uQxa?b~%FWT+WW59o+)1}A2(fQ=Uj6~Ci)+2j-|`v6 z=nYXZK<;~N35bgi z%#W{Lf=*3y1Z}Qw0X2LboBty20RbOrbJC;vH%n2WM{n@oPy7NdXCQZXD0qO*+5`(& zfbZylOMt~3z|v3&3-C=H3SbHFnR5zI2?vM-vRV&NJI%xTRMBK`41osN!TOMGh3bZR z;r`1|(BfE#JF`LVgv^Y7<`)1b7(o{m1(0LGd=p3vgAYPe0E<|Fl8ay7&zZHr~V5uP+ni@b80QsN@Lb}Yx zg{tYH*bsXP0G&>b>z?S$r*IQw73uR10PT^`oXjN zEGQZ^_*<9$234I`;=Ru!CO0Kav_8-A}y0-RL} zXLZ3@hgo1pNt}Y;I&vO<>xdhCYitZF1H*c-KN46O7&d`fDXa_(8^Ej#xL6LHRlv%? zun{a<0%ujgWozK91~{t)&gy`(df==HaMlz!YX+P(2hIXDU?Kaoz*Rq_as=&p0{6W^ z>uSN>Fwlk|P}PrG@ptBc4l+Wk|NSJO^?w+L2O{ALcrd>}tN;B#^*`utHU>!LehJ(R z@c8}_v{)9}2q;m3YzsRKuJ$3r3GvX86VL7g9-tEpPnM{FhVA7+`9liSiuVDXu6W^p zFoy>tWFQgcNW;#Fpp&qS4;%(Hsk#q#Up)9qRQ3OVd4|SE|3SwFg&zPN1A3tKWFN=erUeEF9_J*9x)%@dsX&+>?6tw64brWO+M~#7jp$?qe92giF!oWRm(1!J5 zFv|x%_EpKiz|aS7BZ8tDa@?jzx1$3%qz{zHd00CJ6!9Ey1Pvd8%~#{!#sPDMj7PU8 z#1-8qKVBwBqmu}|{M!y5e89o{17rfZ+655W zmBF^_B5VhnFTs2qlK#L=OL%Z%aSm7+Tn|{eB|OxM;VjTaQ;?7ZHPIj}aF`;R+@K?3 zA=eu!cyxaR$F-~RH;?XfE}h$curV-nzW|FN3H(48xB(Ub1sFa4T{!-lYA~#G7D;A8!Wv6I9ng)Pc^bg}4Tyu3;xA>4WAZN@bgO zLUIo$SZC{jQtlm~PBug%dAUduoD`H%k^;m$d_{o63ZYFaOfdr~!8EE7f z5@(=&=a4va2o84)^JshnT2bVIayb`hdcgR<4>%GXEGlD4Sh~-@1{H!1psqTrwztP? zK6rR&J6m+0*a0ei9Xt6vx_KmgdU?QAW$VcjV~^fIX%Fk*fO0o}0Y=abd7#F-r@$wE zK_3+Z{_TzuX`L=A7W~^Br8-XX`eH@s7fG9`i z4<5}2nZWb5FI=rJ)Cj*8cWpgTBIMD19UzL1){pa^x0i;q6c4{x4=%D{=>>JKzU)DLEXF2ySYry|hwLj{-xy4(aZxx;+P zdLn3Cu$052yKx6A1H)nKiE9`b7{L4kAU>$6(HWxxsY`vjFL@^a@aTpYsG1(VEaISA zwcHAP0n90g^BJohz)lCHZ|E`6KHZ)MkXb*^?o%G!*C7f)UH+?x=!TRdP#vIB7t+KA zt=ob422=t;8hqWqJi2c-{$gNYWGLcpJjB58|G#x$LNRl9U;?OB2&u?HLw&s=Di(+m zqr|VfM8(4Tdyz&%bqqrZi}8WiQm~qtRomGEwA$MnqFLM7!uoiT0qA-Y$YnX0rj>32 zxwr&UdVwpGcyL?K0PcIJpf&>o10?K0XYVWo$3g}J0|O+sK+P72ET}kx$hMxWWCn$O z^1;TR3=RwowHD1k1!{RbAT2BYeJmV@8-Ie9n?Xftc{~ojUtKRsNfq{boSvfyM2|q}=2!t&HQLccjOcEj`2~s8pVapu`t!WGfH@|ck7#JX| zXgCWr?+Ov~Og`Y!ebYDjq9^kq$AiC_e0o`$Tw8DR_krdgJ(5FI47_?-rh;ZUJPtl$ z0&RoSJ^|?(%7eO%44)nO1q43x3wUyXuGwVa*Le7uKjsm?#*NQT{4p^TSw8beT;$g{ z^O;{TkmIuxe}oH2NYIgmU*qs+{z#6`{E-eUpZQ}>e0Jm)1o0ABKJ!OPcrYLO%pdXi zvlG8y3P^*1AjtGT{2Gry#;QyNnItk1Zp$Bt4T2zHmx+HsOFI5wSm4Aj$TRUXeN zCw>8+i9Zk){rSu<5cmUR(-)9U{4o#VHhlTa9|2zd_5)-U*dJd&)_lQc4cJh0dp;oS z`2w=y4al+&a0}i*ZG_qL0d5cI$au#$5PLvj1q!s76QH2+nfSr8`Go@5PmcdjV+7nA zkLH&OpvdeG`}hCw5&EmqZ>9NCwUo&8Z3%_Q>4Ch`JX`kc^&b=(r9?2&_ z35j1*MFpgmXEKNknauLpgYP`9r+_4fh+;1EJyyxLm-ix z$hLrz7ucE)phS=|k<{em$R7!sS&V!FvId;69Qh-UfULQVY7NxKjW{nEy9)=c`1EAA?)fxYa9nL0$w=sYg`3|7RXrksh1QH64{DKakk`xjHprXN%UohYa$f{c)k6eH_EaDKzHgHAc z$RBeCBm+_-=m3gH!GIeeRs0c$Kl8`j1i9DY!e@Q~78QOC&^kK72vFq|a~-7Vha0_(gWS})KIEuaG1 zqwxqRAAy%Wn1P3eK*vo$SfJJy2dH%=0G_b+OupIu(=+)1XrWE7Nz=E3%q5bJJMuWO z4g($h&jc!!SAk09UY5Dw3cvfD2c)tOfDgfop;Yyt@aB(s^qD^ux_L}o41;{nKhXbgI;sUh*Y+_^tNG!nxYy+rT zhu8p0ypEu>7znP_K}KV()Im0&)afao`6E3N;5K{!`5^%mJSm{y@o0Y0;L-d};XkPA ziJ1wm(?JDsK6rzF45)2e0_mB@9|kq`Ae~Ooc|Z-|<{xO}5Yp)c4M{dsN;8ybH2nGx zI`J1Yz39;knFoZlTfwb#0}a3am#{Vb;^LnU>h)?I2Ce3R*Z|s$lnS;1 zbYv>T2A5736^UTbsq`VBqlq2EJsRJD78!VS-}Fd+)$PgQ+Wo-s;vd)U7p~nO91s3w z^5~unO611hK>O{SdTl@_RWi7AE>QtFr1?0zOXn7FK5u^hgZX@?HwS1aR@NgKyymc% z$HKZBbdYJiWM?-hi+C^}dM()czl6&p88Ws1n%SfKm`7*JYtR_BPxoKOY7VG{piwdf zG7SR_bUXF3tOcD=>;qX$+Wg=LqcaC+!d%WH8SIu`5k~9DV8_XJP6j#7v)6{>wMgrM z5?+tw6CmF)fEF$?dvyN;)lmhYDk{OFbIWw6lM8gEAE@C49%SZ^{_ooQmcQ>O0|Ucx zNLdLQU-juN05$4DR193YpMexZ(t`F2*Y3YA-D|+L8mJ`(TQS2Q4q9=f1u6kSji0#V z{81nHHI9M3!ykU~6MxhRevMp^Zf(0r8TGryqwAJ67@jHNqZ z_Bi#5fKxE6KkL=m@*C7^_vn5BjxNw~7v29{Tc6YweuCco42mmo6oD9^r5fPvDStlm zKl`6Hfj|7fCw{HtpZFt=ed3Qh4!Sn%6R6z@b`CVaKl4YP15PM`S& zeHlEOkFq#g|EbyH+$&;+?5-1_)4RJ*fZYXhbaxLp?SPH&O}+=(+S&)2p>f;=%HKZS z$6T~8^s<wad(9qET$Ba*aCOMdTSwO4!8H_JL8I6yOLEu97sqRAu zAF&*K$#U=ki}8uZhy48f{KlucFLob1_=*Fxx8nha@rA}m{~7)>7+-YkK6UVrz~xtI z2Olw|Id_UUdj3D)Ykjz;4O}L@<~iOBnqC1dTroZXt|&oOr;ADes9sF~RjUOajYmKw z4s5a!e3m-!VL0FoM3=9w!XxAKMl4U9*1A}EXXp-d+BLl;IaN!2pMYaw+ z9>B-Mz_1O>0!_q2#6VZ|LBx(TF)%>HZo$>@Ff%a3fOY7D=6=8?TQD;)Xn|Rd%nS^Q zU{)qG%;ZKmYYANM5oQL46tF1{pu;>ND^eQYfY!Hr^ya8Icy!JM4Z3-BZUrsb^XS|Q zlHu3f3hG$#YwiU#8eI4_TS0}83%_PBsJwOI*9|x{$x7%gSq=xXDEwD_eam}2_S8b-5sDJ&8O2fz@;;l!==+#q|;TP(^mpil3F%j21R2XyFFXROC-AD`rxp2>H7m|uBx#(Fq*f(?6Z=9zp4 z#P@(6{p8W>!4KNJ#pSURv={gP|Ns0R%@0AF{M{P<-YWZ~%=tII?`^ z7vO{pM?gj*JU}AgF$Wfp=7S(L0U&|_wDsaMe+0N$2-@4m;bDEKbO)m3><#c$dpj$dcs;KRlX2%S=HB7kc)(N`RIK2zY@yxJ-`x;io?HM{+vyhadXP z9|>{~k~_FS7sqkH)S$?NbbEnDESx?-$2X%8my)r>&aF-&|6YOSq=z4;N#sWcNks_b@BQArgK0%O14;~O1 zzyY$ti9f=Jg9kK97Qz7O6Tq^G6DP z=8ud7HR{3k1cLMlc!+>45`bGI0(=oL zEP_2nKJ!Ngg2W<0&2WfSpv{#69x`C7B;Z!bfb0n2kN{CJ93WXiZxAWq01}FD;gEn? zB>}O@@yTcYNY4jwt7Je{JplRY2`uyiB|w&VfP3Hp3UI4bKz4+1D1fLK4v?&%H;5E) z00~97a45j6Qh-?Hc>$ETuE4EQ0a*nSiv;xtA-)0)UI}>UfGiBqfLo;lvLl2;14PAe zfMf-|L8O2KNGQUELjz`&2E-~y(13vF0k~BFVNUh^Fa~c-eAz?P#(|Ta1PJr1025E z_d$UIE()FgfPxcm{rr)1J)-KIhZc!lnB>%&=}>9_L<`dL(r&+6Q3j;EG%s9KqL? z^m_cc_|vtw;=iNy3I0CNdI*=^z<-^-7LL6EOg^2V3XY(bHv@8ceDRQDug8Cv?#G>> z29B5iIQBX)x^|!GbQN&C__Os=U8rmK`|e*Z-M<~XPjts}IDWtBc=>4e3CGL-|DWcU zcVS=vT}-6m0y^^;5}1zA5OdVN1G@gI^u9~CtAI;yJ!5yML3b>RPj{%oYi5`31D?&V z7(p)LZ><9NP;xa~x?L^0T^V`2s-N#%yQ&coOy01BQp9d`lPyol02Xn23WAi(X|EF7T z*I(@hC7DnKkRyD$OCA0n=#FIpO|*hs)alB=-&z3miBEDUhi~_}?phX53i9RYtWi;L z?REMOHnZDR#IrZ*k56}L0PJK>pYGa-<^z8`X81B6_3!Ta2kM4+^ooFMTTkYzp4M0S zo4`{8y)3PuvC-ObYH*y+I2?b65M*y+I0?b655 z=>+2PbUJZ3@^ANKf~a7Ds9=C70P%P_nLzR^5cxk4d50em1t2a@r_&FR3O0y}FHjX9 zASysyo=#A^Yr7{0M8zAZiWd+SATCd*(+iLpTo4scpei0fRDifVolXxxDtI6&?m$)C zfT#d*c{)Mur0t%3p3MgZAgZoFRb2oLFG5s;gm^lgE`S+H&sO8~p)nqLT>3L?;1|X^x%FAU=;{r?UXaZT}!@K*7r5 z*y#igWyelu5TD1X(?v+YtCvN{)B0%fVxR6<53gRHxgY-j|L@6s*2nrKe={iJ{JUHJ zfrd6bdqctHF*rYg3d;)6AgVyX570=l3%>xU7<>h4PlMXA8Ye#UM|kpphHf2sKJyC{ z{ovQQ2O8SF`I$fRCultO+-Lqs&;a#`&-{^~`NYeg`6EDM%?Cg8$Nc&X8o+kpk2(99 zKLRw;5cA_RzaXe|7xen`*@a)wlLKTC=VyMwN{}4_PM{K6K){h7VlS9i!2&M&1&l!B z>Mr~-pozv6!StD55VD&O(BgxX`o&MXaY_!1w5t;YF-Ftf_g|uf>}@#L}1U$ig2f+*;aOcmFUoi8BXY)$|P;4}> z`uG2Tx2uF>XB((v?9uI;;bD9hT=aQ#Lwe=ikY0H=q*ty1>6L3hd*vF?UbzOeSFRCS z;L*DjG)V;;zlc2?2wy$|>eoYd9nS@A1_BTMt8l;u2i!Oq82Z4&1W9mK9|r?NKUi!t zoOK({V&H_yPJy!`xfmEGfOV|pVqlmAX070dT{;JyV8~H{Z0Z459zMy9FW49uKwHTF zLsyz4A@pb9$e89@U;A(xaR^6lV;1gB`22bV_@bR~8j2_GvZ2lMVJN`f5 z@&BN!^}(9gF8nS>L93Pc+qZzXAYO8@KFZ&^5Y({iE)IY!k%F#$_2~7I@~}R~-wfJL zWXV)2)lkL4P%7fudZ|RZdG`+%1_p)_iEmDuM&S?044&tU&-^ikFCY_E;F&g1s~R+? z1)AXopDZL8_yZyhp1uQ3*np=SKz(z^KcG1+e!)zTDHlO4bI^Pie*~z!1g+5k$%70% z2HL?3>XBXJ*8o{B2%6Xs41D6zd_)3zG)-rT3V52yfj|5JX!P3PGr!is?pxg-!K2m+ zu(izm;g=u-l@~tqYn}egA9s;I>JT`GfQe81I-o7m{NevU@oWA5%&&EvU*r5|{s{1p zD9j9)$^*UejIJQ-G|pQe4_1nGpb+ z1YX$riC^c0^*Mh38{LOrvw}rHi=hHQkrV+MO*C-f*Kko$@Mu2b5Pw*cfq`KoI4^*f zaIFWkKua+)Lk8Vbf{|N#RN&!MufT;zb;Rdj(MWCTI2n#g22bM+CVgM0zfKUZs#W2Gyz=BY- zp*AIe#S$_gQ~-pEfKXW71l6tqF&U~7Y6{d$s9^U2MzaXPWr=!4O$ckc* z?wkKXWekH)_hI8p9^n0w3eg__K@0gAxEUB2Ou)m?pt&<+FbiCwLeB^CZ?I%2@#=N@ zZ~K>lfuY>n)%bSnr4mbz?wgQ>d7!S0WA_0U>qEt2F8tesLHzC$ZdM|kCG!6dSRX3- z8>;Yca2O96ShIka>%huZ^;y&Hy zeL#I9(3B)--8$6QKHW%5-=Sg-R>+kD7`=q{{T(t&L{@WNf^SnQGf~|h^!X_1H)>tEa*5Q$RsQ% zOg@1_43;J%e}Hz+nt@_X+@sq`0<_IR!=qaWbU_hl_ln2=2aq^`#Rzx-IyA1p?8Cb7 zAO|gbMhbFhs<;I*Jj|olh6^+cD&pDgzyY$w#j{($qt~DP|Ap70P%C=f*}-WB!Ubg! z@CNc;XLgV8w>_8-dMF?E==Asj2@y~_0w-R0$^eA|BxQgWJ44a~Xgzcx$N`QX%~lMh zq8`mv5)g`mp_JXDxk`YcL=7xh;^om?g%Gsi7hnQsVSWKG0e(SGfy1y3(7oUa$fMJd z1G-xpl*tiH3D6Oxpsml~br@OT&<2HjHkbw4hL!_n!E_u4H3k@P8|Tq|5WF25ViKqX zftUo^R1Go7r~44U#x0NTi{RbYpq&iyqM%ga(fq~&wDD0T0lcwM!==+lMZpzx%Ecj% z?u+31&ZGH=#bHp9lLgk{+6_AU1tbeL3REgVjKU%jgG)y&E{Ql?6477@Om9G}z!Znr zfGG~K08<=d{%8KkgNOO|AL3ts@Zb-Q!yf$m4|(vfKj?wM;opCdfBz|%vgS7$phRR1 zn(Qf+b==`#hhAQQ_G5v=%cGkUl5|0PL_ng5d;(b>0m+%5@P-`P1G*O$k~2YR*A$#C zKxtPG%mOXS02NXkphG1*SN9%!7K?tuKvhtwyKq(WXWeRHFXdDJ@rQ8X24QRg(#QmU6M-cae zuJwYrAGChh609zhfq?f?@}VfMeU;GKz} z#4ZAA@U$K%<=O!{`55BJ3t+>*Y*2La>z^v({md@_DGWiW^D}?!A%6W6Wz75<2l>On z$KZVCk2?rHYcJ}+XZ|S2Qhd--53niV6%F7?Zcvp5S*8X`T9DQ+$b3$i`G=9s{>&e9 z5Hh9%>YK!!;MX{Km=iRn2{wyg{}jK*gCZ2IkNEY^fSmOZX5MH17|>eui=g3+&-`(q zRcc2L^T5pj9qI^ampg<8hk<6&cX~9w0ku-$6XQobI$iBy$GTnOZviciGd}6j$pt?5 z0Cb!izW}pGud@QbfVTj@rt5QlP2cbQ0${!YzosiYzoxG^s2UXau;eJU^sw}h0FxZ0 zx*nDu0;TG(Y~Jb1?$PNg?gOoLAe2w1E5ApltNd$Kkj4^e4@(b@QZ|sr5`KOG27Un# zkey&Ve7arvJ(8I_ebtY_=b{7=rE_;t|X9<^X zZx$EOwe7(IKAl^@>o$FQw}VDPJiE_<{4I)78(jcro!6{j%BTCdPiN_W<4YdBzR#f| z5F@&8v>qri@#%Jz2Q?+QJi0^KJ-W|3{=WcfTBmDJca8iDK1#zu1Xqz#lS_NI}45?}-GcYhfD%dR0LC)Yh3UuRK0GI_jm?s*1 zcN1tt1F|ItRB&Dd5buNB zq~dCP(g&2fPnJmfbUP-1_CRYpM)+7eM)>f%pD;e))5&!h9IT*{2XxfRrBZ0|1M)wU zN4IB$M<*9f$pnz3Pxm3?OW*L=q$i5=&Zmm=xo3*=`rhy{W0e7UTLL${3B~lF$kqU~@*NTY9!xg2S zYg9lhos8dtdOZ<7oi!=}uAqZ-KxZd=B(pn$w|I7f));rzsF)mgQLz9Q2`(x&;4;BQ z#Q{_r9CuN10nyzhEe6n1p`68oe|@=thxLc@bdZOHJ-VI1t@%iBpW)p93$H~W)tOKC zF_gCa2awl|J-WjsK)Z^&{aIYP*&(M~T`CcBfwxQ{yWC&fd2~aI1QT$U1@(U-Jh~y? z0$-c%(e0z+0ng~oM-spr>O8v3C0x4yf(`D>j_~MoP5>pp;tZG0-Jqi2^)_%CeSHvA z*q;Sg7@(n(-f{*876#C9myle*#mK+_$pxV6MIpHWG_VTkU94tgV1V>4Zovx%&>7^A zf`JQhTU9VDzq)6n4yz{8Xyo+j^;l*|(De>@<+SB#`{25DnRt>)HJi zrU|5;&9mFWw-dbRH3q~F_egg10A)-cZN~_gP9BxxE-Ik?y9~!&R6zYv5Di*$2BHN( zk$T)kMFd25v$XK9cl7b#U+);03Q`@Jy{BBT)=bh6ma2*y`Tj7kEC)2v@0C6xDIrl)yWbr zk8Z~Zk4`3d{|jWEm-fGs3~AQ^2P) zg2Usu12~KObVf*c9Cv_ba|aEd&Ikja&IpT650B0OAD_;M2%k=m1fNca4C4cbL5(0t z+Jg1BA(d<^;n986qq`ckY|2ynmxuLn z4}P~F;A{FoLxe8f&OVOa7krYvJwT&gpetj2x{rhAu04*ssQ7?-)Ly+j;8mx`T~t7a zv4GNj2()mE0HyomE-En~nt#0uj|WJn!EqNA&`H7|m3WIo{`Da|2Hht-_}8EFGOIdmy{~h)??ic}T5QA!V#{`eg zz>LF(Ao)xKoEp15Bfu*d4|-T%C>Ql)z6dI310y`GFYJ5|TA1l_5UDx92C}IPn#-W} zfjSYOWS-T$6I3!Ylq5pCw~e6M*P}Z!!^8SUiMvN{U;$`|$_|tpq+vr;2H@)EG-xCU z+)u?Ers{@vdtqI082>P0gfNSNfdM*P1v=ic8MFl!RANIN1lpOk3 zahLUvk~oN){+EP;+$0Ng6Cc=3=l@>-wFq82ffT*AfmMdbkt#!&7-Se2!ULDEknuE- zMv%cbUTg5D>QH?KZl`y~s6Y;YfO-!!270r^A2d;C z0P`HkpVmK0Od(GAU!ntY2dIt*j|<9y-Ekhdjz{(u*nO?vN-RPC2Wganc|aYk5gc3K zLCiRe=z;|T{LF<&_@E-FYYT}bP~QULKhOyZ5HX+bgP=qt>;r0WgVW?e4{cDIgr_#< zQ^p58txtM(dPMAkq|8I0Rwy);cRMN=Uuw2w?G*6ncGTz;Fg|b?bP5@04a~t$`~p5I zpyS9K1;ApU)6E<~=OehNczog)^ppTAZ~&cV4^kD$0TR^c6a=;B55ta5{=~0y0Caj2 zg9o^^0ZQZGZMF&?-M7GphF$^!!B>l8e?uX%L3X?S!)rtd+W&{DqRZlJ458D6*U z1~-se|CbnouH=^R>~;fZ7G=+FF^}%^pswl5I8dJeG;+(}(~X>Cx(~l}{{R0!IA&hj z{r~?THCIF0m}eMZR|7w0U|`q}J{dxu5jI_<%Lp&SK(nr(+$`Y;KIg@=`v*ASKZ3f- zx%}Y=9Qg$qKzkwuK!gH_FaTwpAaHz?czbk)8-V)eoiQq)BQc?&0tyju)#lmDBInat z4!&!*(_O=(`y8n7H2^itEkHRRd|HBI9Jrwe76khWN#-zUh;R#d2DBaS5s&6K5uf=5 zmVmn}pe;oF0&Bo~8jTNp=C5~A5%K6`QR#G1;o#RSQE>oo`UKVg$6Zu-K&^GqPRvdh z6%|ltH|pDQ7Zm}J=y4VmP&2DDM1=*Eg9X61KM1&}D1e6XLAg}{bWel;i^^yIs8jsm zm(ut}4)O~+YJBF8y8M}6U=Cz+Wz-Y?sN4KeZ}~M&@oOAO;}<#2FX(BJ#xHV|U(gYh z{|h5PyGlU*jyUYm>7&9C9PS9(+XiX`75IXhJU@K8ANhi+mLAYak(V5IfbLK7?1r3; z?%Vo=zoi(oZ@IZfMT6D1H%CQ-zcmT8YpZh&c-y^a?|RU+9iS6YKpQqd<0v~AK!=9D zZywNWWCtoLggv_5VcAIBqx&?jY~<4o>Y0`D?govFyetD< zNZ$PoyrT?s@;o@#xH9kyx(e_Mx+?Gsx*9k_ZEXa#+hD1rvlN?v{f~32-!?VLU)F zYH-{YocTZb5bqx-C9uZxOKXNignzo3sw1V}UiL_iv7puEcuUReW5 zy$Ya)g$*dQ#G#?Z-|_{#cM@NS!7c|p1=@DdeID#8pYCJG#Y0I1sLas;nGA{taDgD_ z(Jk)LjnqT-0L{c4Grk0=`#>XDTfiqCfP4iyb-|~5iwY>dJ(`acIL3pHM~sg??0CE# zbl(H0g=>7^^%-#C11fh8pxCv^qg&h)Y}X2i=^ouDe7ldmo&;9^dNL$$BAW|3m=}^c zK~_U*SgkTQrDem%JgBdiQ^XJla51wJAebU)Y=DFy}>22g((q8D@%7ep^R69a>x z7)aKD307*^f${@*^mz_k3`foQT@d>X4ZV}Wc>}aW7*@}L%O2FaEeY&EMBN6O9)%R4 z;7$l=BokyWc(n?&Mh2I5&HF*2%uwP4N}~oI-P=JoxIyFec8M@(gcCHLA?ML8j1s5l zqaL91Wg3rzTw3DNTyMcpV&~CaZvcubQ&8HIff=L@Ht0MkQG#1Y@klKsZVjkT`f(3s$>Gy|iC?qehEMm& z&-?;~Jp7s!CqDBFR0@F3_5g2Qaujjl*W^(FEr;}!aBTj~!r$~9l=C|aI6z1BdvsO^ z>;ug!_;eOX><4wId^#&$cpg6nDoz}Go&Wf1A3FGg$y586r}igb?N9#?xOD%3mKq+t zI^dcYw&V-6>M%y&k7qBBm}j@60>8%P&-{^B!E2ZzK_?W5FnV@_O6kb^poN#9&5qz@ z@ca=xjGy@hgZ_Xtfi`}Ej!xi#s*SnznO{(b5wr(Eh6$tq!_*K)797UAH8Fa{p3Zq9aj~K}4 z7y%BD6EGZ(J2*H%!2#BUF#0op%q@^V!5a)fK?2zq+wl*wFV?}Qy9CtdtWn7T)!qq? z*0+mzUst-e-YzNe=zi_1eaNF%WG$#e=%Rfavoa& z1M1znfR6F{!ROlT!~Dmw`+_6?`YVpzhaCCWA9Lkjf89m<)GmgG|NmW>PdIiTaO7Wq z)J6Nkj{o%x3@+9O9Ql0?SRdl|Ih583T2=0-;M(cK3^vrYGlcn%2WYE%1E}E80M$?i zphc~aUIBQtD%LRurE@s!J?qnb$G6j4!MD>{!m~42AUM>c@eQb5<=K7Ev-<`3>PFB! zA%Dvx(8f{F4b+{m+W;C^7#O-aJv)nEcy?Al@a;^#;oF&g!MC&cgl}i{f##pA{H-^c zKzEV;2XD{+0opyTS)!8Q*?khU)HVQg>=EdQBOlNS_OP1OwcCru)%Z53yv_oxgBK_Q zZTu9d5&)f5~;L((|z>d3nq`=U>0z#4zA;&)w+-NVGr%! z9@?LMv_FID^9L@_3f%`!yH=fM+)rQ^6%5NGV3e;{&V75ypaRa*$%Mx#%@H$aM6Y9Oh=6H95j?P)!at8%s?N zG8(;M12Y&pr~4HD`m>JR2Oas>A93Yhf7V6&;7&*}d7%3c|N6r&+9$w;r1b@UpF`FM`F#$i zb-Mopnd{o=0x=9yR)UJicF@^+pi{X(eLT>)VBHWef&%ORLGa2?h?l`3)_o9ky$4u% zC*;5t59=22nK%4RzyAII54uir3wW@?2R<{p1Uz~II*lJRP69fN6x2Zm)r=J$jYmLb zP;B&J#~p^Nv5q%_`-QJpf+>XZ74XC7*RwD%KvsTjfge5(KCfK^)Fl9obbv;tKJg29 zf^M`*jS0yF5ONnARdp4wIfRj z+YZo8NryX`x*b_MomiSJSwHa$IC6aAk8@NgjR4)}2^t&{bm9QnBk+ko&Qk$&rzdCw z7tbetfk25*{BeOGA<&JUPN1P%PYo1RA|O=;D5@ktqrTt~x=;L(hd^VtonZ4pBfoK= zF<+P<$Oh2BZ=9#XC;rG&pZIkSc7pA21lfVC5@d@f$QERkh{d6xTL_>-!q63l;mws6 z3?+e}{-}aScccNRgGjYm4|{ljFq{ECV%YqK!=*b$#eiSqv8(YVevK>q$1XAWbe{yB z3)up?ha2P;0gp}_6~|`KB`%;H368za|GQ6k_R2JR_OdkiKrXw!!QTu@W*)sPU0@Lp z>s$QIeGCi?j@`GLAAf*efVUsiPvnoe1Y48@ANYK_8P?7G#^#`_Z#*tsphw~4lU&Q#C zKT?DXbT$Y`xg)=z2q&a72Rc?E(uWIllEMk7ln*E9B!>_#(2*jJ93VZQz1$(393IUF z1;F>tbh=t!F4lF`zUA4=vcs|aG~5+DOrQB989)vIY2jh?Xg&biH^^9e7t|&b@aVPy zb)Yy7=YS461n&_02kOtEPD=!U2UF0J5c3einii;Zq)4$yH# zF8l({0-s!tw5@e_aK_fPzS;h-6oa?rF3R5aq;Cw@VH$R_1( z=1=@F-(k}(o$TGtEFga;fa(|Mj-5~Zg5?IFs>Y$yoyDX3_$PircMXr`BLVS;LDxe< zrbIwS2bF@xtE}KtBA^xYy5N8U%|<~^^#QG!k^_r@=A|H~1c5H~6a|ZcE)0aIJIlbp zZ~!a@oIG z3+&DT9UEN&yTQW;w36Vm2XtT>bZ2gf1t?VXLDq=*bh}G{W=Isl!?UMBYYC6N-VPZx zh3$EO@Vbw^o(~;(Ha_rr3b>C0sVTeRc7blYfFvW(t_Vnoffi0frr$w}{~^Pppkt#U z!=s?7%pc%M9nd|T5LwVf4}8eZqccLlqq9H)bYLE6q8xl($|sL*$TgLqqr`E}?eMo4 zg6ieY3{dt29pd|mU!YI|bdoP<5qa{4Mixt3U(Sl?1^&JRx7Zh8NOb?n)hh%2Z z;p~vC4ywN)Ssk>f4pI<+j_!x3164AROm6@mI`@PRoe%keSkTxa*6tg4Aa&o~L%VOF zX8Py=7Qq25w{9Pa0T{q)g|3VOPcwmbSsery+wh~)VLJpsw;O;vMPEV7<3XJw(7mak z;uq9NBC`bM*Ek0%db^!jprtU4OJI-g>)<;+z@;^4w!1_n09;HX70s|h8Pur-m8l+$ zM?gg?=pKbR;6fH$kitbl8xkSqX)FWmP(#qA36Sy>RI)4%L;@cs{(#I~{4lLalUAr$hUVK#|59-Wc;0K+HZHOy(A z;H^kV8<3bm+mJzL1VdM_yzuD$1i3i1M5Egg925#G7)xbbx*b_SEo=tY?i1bT9WTD@ zzTSPm#KyJtK&d=;tsk@ z2fBs_)IU<#2^u%B^ifIZ1g+?C0x^74B*5B1r%H2x4E)3|aM}^Hh{qAMN(a2H6*RMg zbQlSERaAjz_eXF8`jt=jNAS`d(25u1OO87xFoKS1ge=VgFMo+;WMF8n)nN7L_0`~S z^#?7C>1_kejX+6N-Dhyf40$Z~J_9$pS1$4a%Xc-a%$e{+HkvvcX7QCv(_%?Xa z2&mfwN(7xTDjJ|7MWHhWx@rY!K})Zz4ruub@*0*GAe;L^l?!Ne6Vw{E1|=S}9@AOm z6+cHotJ+RLk`A=T#J(3aMgQ8%r~4e(H9p;k;R*6Z>w%IWPyvK(2@B4pM4)~aWbqXA z0*GvA)4E#2v-`1Q+~MPGpj8!+#0lSoWd>j023q~bC<025pnJ>qf|qVwVPIf@tStep z7lb5X5k}bl3o{T4ytX7Bo`iD|E8FuMtqST|Q#FqD{jbY~cV;#3C|{%9*raK`EXa3$GzeN)RBk8 z&tdooBWNEDL`(u6D-OY7jv*e6Z$QJ}Xj=t*I(<|Oe7hg`fbJXvM+4||bI3pfXrJy& z(C$Z%&NV8aEv}&LiJ-A@pUySlF>~1RIG@fv;AK>v);CH6U5&rFSQc{dw{fyEfG!Gi z0S!r>^EmjB&4bxbphO*fD#c}R!v>V8SXmhu9RDA4u`c7_Z@$LDz~IyEqhj#-47eP8 z3A(7e8#Ip1-)hJVnu7c0+Wih(h^%H|VBpu>0&ev1YwiJ$!MpHlwtxrcUHCP9R0`k~ zkVi6`XZIDCPS6U~?$faC3WT;PxOBU;c)0MdcVhuN*0uYbi)9%He_IN89pFXB?tdN! zAF+8bhY5fN(_u$u74dxL2hH+=MJwh0NcwnE)^I-&?@Ff@ol85(y zd|=9<({3Oo0#ScJmO#ctk=FZ!FoMLf^)r1KK}W+027%;}j0SOGi%DV_|G-?W1Th_b zK93RVZj25gBWV0o2&4%<+XYn%y3ayTg%M_O6bncZ7I%ZhvAY|5q*)M19?9LHb3ku_ zTmw1ZQxG&8+5DD~zqR4t|NoBGKZ;9UbAXyuxxU^1yn1=|f`&w0w2y!07m$&}>H-lC zkZIT^@<8*eB4AAzegn_sF@o;A_H2H^Sh~>h|3MdPKMww;Ie-8E?{;Hx{C^ZWo9Dy) zz_I(Nhqa4}0e_PYY(B35eC|3V&7)sU@)ERX4m|nO!U)bep!Bcc*!>TjeR3GVnG;m% zGcZske>B%|FgSL<<8K39^9yeO!SkO>_gT+_57~T}{RAAl9Yq|wJy~44D>+=d6M0;_ zGeHd!6~}HziB1<44N%0exfBhNwN{ge~CqV0?9GQKX{(zHH70Qp&N@hY2*GqT$-IzTyRrN~4w^{>2{1qKV7>v`O`W1*0gbM?;EBW6a*jKWZ^k;951xI4tvv-zEQ0rd zckco3;RelHf(l=sZb%^u+Cqldoz!>)RH!?~#3A2o2-?j9=_xxhGB7}H1x#dtu_my> zF1DV|$^e~P1YHsh88E#Dzu4NR`w*h~2JJ?Jv^pJvL&M?MiOc{Ec6Ni?Tpq?pJUUyz zSA~H0mimHrA-sYOnt~4f2e1DG-E{{p(MwpHe=wDnId*fjo-ARpK2fUe*zEx7Ua?v` zvy{qpXG=8yNju&5gcK>(mzV6w5yhH=kW8sF>F87!i7$#u$P7XSP zdM7@;4(z_2c@mz@4}KV5f~}@@>Gn}k@a@bK04)&*t+;jU{_fMcLKB#rgY)goTCC7r*z@hT%rQ%Jh<>{u2BI^xx0Xj z0Ua*r0x}15mq6=V{+6AL3=E*bWAGdrh@lHAkw6T{D6b2@0QeRukO*jp38?0UWbSTf z4wr6aka(~FXj`&{BfrKem(Cg$7nkm877)V*v=`5!(?!K0%@MTbUI29cs9+Cd+8cVO z6=;2fmn-OQ6VSYK_c_qgf`bp3L8rNNU-jtifh5&~kJvn!k9sg)aOpngdGG<72lI7b zW;cNsAh&`mdqxI^m!S3~sP25u=E+0O)dfd>4amWC{2CvS zMy}(&eRAZFImEB=&xK#(mt->H%RmGPpd*3VbE1xpbY@v1?~KTJ>cdA zzu**bbK?`gAmqX=uwAVON)17YPvieF>#xNk-~{4o{QqUkKj=1-*Fuh+c^uu$j-71X z&Mct&Ou?o078OV&Yrvvi1C|gpU`ay*mRK}k$w#9_1(cvbWj!c`fro=Y-K1_v3Ir`a z03}KnkOENB0WA#yxe~scvGE8f%R-ij#m7b;7J+YQ1fA6lS&m-M$iN^3o<8efWMF`d zA5COrV2}oj&0=I=*bZhbW@KQ1j31dWF)%=8RC1VL<4omD3=EJlr3G-Y4NS0!Inc&v z$e7YjW~2?0{4JM2ovh9h70A7%pe}-eNB1w|+aBE?Eh9O~;y|r<3&;gn-G@EEX$)&a z{y%8RxVr>&$+);jH|~b~Ve2?(L*4?`kdKo9wc)KiK<7_-bVH2MflOC{W;nrna6sEo z6p&Yqqi&S~Pm954sX(U^LYn2ETf-s!{ax?{0-#1UL=5C%@ML2G?BEp8#IXJ$$h0tI z1`|9xdET}qaJ+bkG#dN zaftuefzZ$Vk)V@+Z-Nwo=FGi8bLKZ4`Sov<$-^8C9}Kq z)=QV~x*C=GS#Zj=Ev#99xO>Vs-BvDQl^TD?4zc3k#N z5@0_0{o%n^Y~64?{-dPzQ1$V{;|EFi~(h z=mCi@s5Vf60V%veZ7j%8E@%f3q`Cy9FNsjdMnv#vmP>aahpX{zm+nLspYE3)-I)?T z-Gu_K)>rsjK|{fw*6&K$9DAJ^U)O^clm|F=-v+hIB0xza&ZqmYXZJr}?SG)*Y$2EK z!;amTj8B5y#J}ysYko)TTScr6M&DkuI`VHbvG;BLR?3f}>NO8Y35(Hz*UbFe*d44s zl$!iM?D79NNObiMP?-hl$^?MsJtIJiQ=mdIDhZIWYsXl}nE2Skj@^e}Uvq5!#aOBY z8X04Au?S@-6$c-i2?|d%#m#>hOBF$T;#gfR)Jny7fR?^sQ&y|MRFdFo{irnBwe?9! zF39yluEqx(yRX3g_~SK;BmXu#P+*vObl>HdX8?r+zbkS`yykJWzEuSEBeR3m@e(CR zkS7m=gfD8R4_r~^8~ z1auZ5kOEgzoFqCq1JBwI43zV=mR|hbZN_IQ5H2+{K5d$s!6zpaOvAIBOR&bbrW;2aX zb~|%`_V0E(^MLx>;8Ds>kUzSe6+olj7TwM|;Nf#nf54;L*#I1Fojxi$;PGr2Hvj{Mu0Z5a7mZZd$zG!J?HKkQXkL*3Vq zOt^+H0dx|W<9~<=*NQHBbn1I_=5u&hW($z2x=^*Sg1gS3t!oaTg2Vzm{tZea9-wR$e;76D(GNNR zHK`zZ3^bw!VF@uZFqngLAo#={aCT|ERLa@w^beGpq7TCkM(7PetSaIH9oVMe(a8)x zG%_IiunOpiMuaH}pc9|EZ-I8($AOMGi0lqzX}wh9<~sVMARTBJT0d~wGdXF*VKX0n53HC$8_K#kfnKAp)H zU@6CLHdo{Soy?BiZY-V7ERNl79G%V_j@@nwoz6VImURLp@jl%#DjL4bcYHddB^cmFR+30f4%$%E%>*V+XUx|MQ-p z1boQF`dZPw*DOAr)e^pzbrK~}Ad3tj7AZJ(mvNN%w0qlHU%His|s9gl8jh3;w=kM3*u?npUJJNbUoT=Yy7ZdevHKAJHW3X6t4}4C4*c8BHNRvkk!*g+Si;r( zlB0yb`6pwkc*8H|68?r?Jg+$#eleA>HvHl)Wp?clcKm+`WX!zR5YBPW|A$?z|ME8_ zgHMy11D+6bHU1A8)&;H9@#$UyK4GG{T7Z?m)t8llq1l?%6?8~@h>AmZFpGCDi;_pD zvq7m*>!p&Ivw$Wb z#H)AhKHqw&#HhI%G+uwcOwP6YAgGnq?QGC&&DwqO|3R;AmIE%GHWRG9If`C%2eW`@ zB~BHc>-OdW4bR_aU|?u|$;jUV8p>_{$;jUdT5s*yT&Kan-%`QCzyL~ym-t&USwLeH z0^mW1vgG8C@*1e;$kOQ5&2pj?bXB4tOT%kW z-}2yRew`CeojeWy4_OCu6debLdoWAUI^zQ``9YI_;U_`nbvv_IJ9F?mJ9D^L|KM+a z!we2!kh!3HwYo)4l!9*dVCmx5J6I)MDtVlxi-DoR-tqtO*GHRex)@5?K_UX6di(h6 z&7b)Nz~hFl-Oqh`!6U8w;g>*7gQJjX6Bp2}(9ROB-CrE}x7n~cd^`TDl*^&vKMQ~B zH1Lf;Z*1iG+qyx69U#`2}1-cVfNq@@aJdSKU{}!C0hA9KZg2&SHbD8HaRgTp&<2ZUYgSNXdUf-B zC>4by6@I-#RkEehpi2RH8ea=LHQO{Yl!8n={h43qpi?JHBRJ)HbMQC)1I_CFKjLDY z%);N~`0xLJP;zwbe$L-^7_^*3-hlxUJ~WF@7D#-8x5|+jpU2VTQ^K|Tgd_hp5k?xu zrz5`rXpajxQfU;QC!q1U5jj3Lg5z@|YJ6^V{C~j3+L;A8J~<%qX?)-{q-E;Sd?W%g zyzLkRUWx{43P2hKdGKW~yWtHr&`NShGZb{p3S_DbG-(DA1C6yqnx==~9YN5sqY&9B zW(EdG_d}2c#*$`XV1SHtt!06AB5T=TEb!bJXpA5Obs0v4XZK6r?hmlO%Srwg&~et_ z6|Iv&g-BeY)WtpSq7nk4 zyGvRe_}3S5c<`_P>H+G|*fK&dhk`T`tiO76-}C4`@4@eW7c|Uv7+g<#FoU)lmp(K; z@LIh)P@wr0BYz8MP7%y#1Qq|U7)y$pe=(M*z*c5~T44#GsmTcVR73NT497Uo97DGw zhovJ+iKOF>&=XkuW8KWY-N!)3S9^4y?+ylaPYpb}oi#w+Q4LUw6|$G$^=z1BuP4G; z?H2UL5Grp zTm3J&|Ns9Fs^1`|u`Y(EuWJkp47T8gC@4H3Cw8(k!uqv*j0_Ad;L%XfS~f_())KUs z3akTEI#`2QzKjeEHel8k_%tr4St)R7hpjLtli~(9k-29UfHhJsK#K6$)#^Gvw z0K7;6ba=8y^Iw*tXrEr+Kb{9)GI{iRb9nwg=+PU_;Rn4pJZ(a^;}4HsPk~OSA0E9y z3f+z`I-TxxJHF|3dH^P$bUS|Nbb0|M-*h{E>2&%4Cck(#zho>u}b*RmzzL`q=E+Y-}1LC168Tr7g`ULDDulgXH!pfANmg(m_F&j ze4?AVlcm4gjl=pv(Q}YXn-8=2Sf4FA^AfZm&eiw;cw*o!e@h&wN_XP`r7%aZaZ2!N zxtj>{PQL`z?O^jRfY(Wa5)f#yZ1Z6z*X|Ro2TH6E=3M}7oc@0hbWExT^9BBG&i_1m zdBQtbxcIj@|L<_&V&vb(+~LB-)a}OMVtu0MyeFvfahTc1`fAbUm!Jh@@HiU z>J70C+=+iXi*uS&Crdkw<2Ksto5 zLond8Zm3%f>%e&qvBeN{GX-QE3RFkrg3n|G9l#37k)T69A+m)G3=EB6y=Cz2iJ(>V z5LwW?aywXdDqI$Jt_bMBRp0K1kfjhlpfN!3*1^NrvygA=TTt#if=l)rNcJdpS;yvj z4siB7hC{xaA5?S%fQpU?(9l-`Xsv_-IM3dJXZOL^e4gE)I|++bJ-ctXcHi*iUw_E4`=ST) zVMp!Dp1nM6pZOy=L2IeN+CG7fHuU7zI{2AC?!;$jenFq944(fF`C4D$Z}R>B|NqO` z;3GW_ICkFv`vr7fAlyBU-To}Tp!3@fBi-F?u5fBj+4UXiw5$A1@JfX?8! z(W}GY(QCs1>O3VgdGrQx_%NUFNOt<;!+ZgJqS8z2mqntl6_X+-OPJf_GkAn|2JP!WU@L@g)cJ?LD?t>oxk9*AY{Qk?M`$UKHKaYbyIXn*j z5a{s!-@)wBe3;qO`VW6o707j<-U@gG9}EG679j_d%Ull8Qb{~2z;A(xSSlknF><-8z z59VWz{QGU(9D7*=jW01Dcl`b-&9T!Z79M)#AoqbvG_d<1p|=dQ)%v&4(vt;1`q!A*FBi8Iq`3kF={^CV0_8BmxYV@@XmVB!0`8S4$z?$Cw4Ua z|Np=FX9K^_iH3s>P8~e@9{-PnLpJt5=%fkIq7nsYyDCQ|0yGvE0J_Bm*1A26y7LuM zx*>MHy1>WLy%`u7Af+2o9^Ig2U!XlEl8!q(u$M=maT!U+9iF&k!DXEn4%uYp<1Q+o z%eX)XhlASfAX)?34%UITgAG9KU(f;LAiBGzMS*{PHHRnv`hOm+|4Z~8_kr4ckglz# z^*@j9`=BFdyU%*^yWc~!`d)j0T7ADktv*oO)&i+*>j2#w0$Nr8Y5tkOgVdUVfdLX7 zpmr%F&_MHrkRSz}vkZw2&{6}4Yyn(vIlQu|fmbI_;N2C_@d1$LB&ak;YX~}k8-hPz zSLA@JnjjoO*a{BdU|h1`W?~3-*=}bJOK1GedCX2az?$EnSr$n6gJvU4z)fuj&|WCm!TKH;>H8sQyc~3nH|#D$ z#gkaOG@!v6Nyi;ZxMaaKy)q8jWOkqKf21aHm+q1l1sDGH4ph;N+ zpUxZ=2S;!(XM(TwSI_Qyp55m?QM!CS%)foKKbJoA0iEUC%@1xD{DU43V) zk@=8EvQI06S29bR2mktu9tVH%craf8-OEz%VST}a-{+90^)XL=pJR^yLBmZjXRd)c zb28kS{7ogG+9(rr2rI;;aZDhW9%o=^_zzkuU>VQC-v*jDgoPM?%RUAMhO`O%>km6J zA9U#zaexGrBlF>276%vpZ7v#2-6ud4qvyc~96sC!__wiWI`VJxF=B!Wfa~{ecLUFZ z4>UXv{?PF1X1M|O9{)BMBSsHy(BKh^kq`g+LtvHM2R#q|;PE*4K*QtU4-HS|10J^j z|JO6ra(Y@H_vH6E{u*>KZ#{!Y@(EBgy!)VI2al1*!5r(51 zl4y8*fuoCu37ULvzyrmFf4_?>gB$;PmWfXM`&s-P5B}hBVTP>nhMuP9Xnov~-v=~+ z4C=u6fn9o`XdTp*MN{D}tN^(XR3t!M2s+QzrMJ2P$$k87pd1E^tddGp_lZE0w+O<0 z{M&dm9GTBM@vrCU0u9<39sJ4T$b6ppV)L*1T7F0C>$R+o-Pd1pHUFxwVci94DDdw; z1a2^Z8w-xs=Ne|P1u+M_kPf}>@&Gp> zK*M9;m06JL3w2f=QuHEbU#qSg45frEjeM85GSg8&0ViHPx~9sHmm`pKZcz`*Zv zfq&bf!yEVxZ{R(=f#>iB0gwp|3=AbQjXxO}7#T_gjW6v0U6}^rGBNPG9y+{1=)8QSU zX360lAYUBb0a}H8cn2s@4(|ZD`S1>qix2Mrx%co6kTVbO0Qu_h4u%E>hQm8R?gEia z42O*m9OegImk1hsqYeh`@m~tQiXC**N}@ai12Y4tu1Nv2Kv^yc%mR%@CWBd^=?91` zXv1wPSPV4ZmIh{lX0p@4EYLY@8DJJ@gq1!jTnw$BE$Kr_NQU>0aod@h&; z+K!P2W*uQ*V8{ovKudNDz%0;h0fk@|XbPnW%mS@(F9x$f?KA;MnnpqZvuKAk=)4nEy4T|u`u_^24T zS|8$X1)YK9VSTM6&Gbkb%K* zCurY?XZJOi&KMO1*anH0|JWHAj1RoN>e&37k-rr*as^sI#^quW%fR0X8oBZXP3YcW z2c4?_jlbnQI|G9&XqM|3NZ)N&kY_IOw}1lLqx)al1duC*P*#8OZ@U0m+a}&}sn?m2 z-{mlP>DTe@8=%Es7eVJ6frk>i*MJ=f9>U57PuHLH?f&)IgJ0{k=fUSpi1Ir80O(}b z?th-3g*^@oAp6i(n{l{W-zw(j-^Si@pae9R#SF30qxmok$j-B%D>r?azk{~0xiWY* zA7$}qe#=<8?By3WP#}SZ={>rqfL-GVUWWw_s>>h+&A%A=TepMmAZYzo!s%im#^1V< z2^2VQ`CB%zfdc0?f6GcXkjOXwmIZ7K4Bb5{3W5v_pt)EvSj!AFY7Cx>1>J$|*!-J` zzv&m~@EcI2;A(sc)V=x24;rcgB@Q9i?n92N0RwJ19$^I~ zb{7=`-|p9*2cI&5-3z)M40OBP2WWc^WIaEM`@staL2)DQVtu`Y*TwpB3CD5JZH5f5 zSslCCI@!Prsg8RzA7gQ~zFf2qv~tM6v-ubcXsZfn>6>Ts8^+S0~e}_E&gVW#TqKlwR3(6dz(I3J!TMm@)@M|0d?I2|P%rD69`2Uc{|HB^5 z$H9I&<;(mYl`m3y|s<{22RxP>>6|b{|0`QE>Ui>c+p#Mju>0lq7+&Df;r*3$J-Wt6V_~ zJ6QO)i5RJY=Ss)MOR{{LZ+35#Acb;vvZKjQKKxU2PJ{-%uo zpcC73R1CmHmn<_hFM>KApo3UI@eN)>$PDUzbpP_|)d5}E#^7=AITL8NMeFSn1JKDX z37(+kgQs2iw=seSv^YLH^9!)Fy#`(0`1~7L^GCpgqrfR2o3*6qt-#;D2|Sz$30=^%66k<|1fTA&pe0Qf z9=)K2%iy6bP~eK71g=MSh>8U`6S08T8oz5la1wQi&)SMRqMF^xexdg3rz(%t3YaHg^c7R{w)c?bt|Bpiq zi)1Kc3yQ85 zun#<%Uo|-XKjQiS7}(3;h~o!GTnVV|%uz7_t;Vt31I`uvZJ@g}yn0zcWA+Tb-H$xK z-vLcGI(QsE3_7F1^WYOEPwji4KuiI*pFF!yduSg5De(X&;5!VUvn#&(^!lizc=d`H zcyzynq+jFz{Na}zn}4ws6+8Ai{qgKR2|mQqrTYMMf44_33wXabv^@LFFW|%V<1>Gx z=O56;@u1@@BN;#QN1Wod-7YE~{7vuw{r?Zz zg$7QV8jz$3+H?kL%fLs=y63385MW^NXgmUH{D3c*ja6P*(fmY=OcpQAh1d81V#85iuu0v4i0-Cf3m+U^>Z$Z)I01aRM z@Jpa1&ySMiU-P+IKL$5+SRMJdxq^y)P*HUfG&&4P_u#VvdU+f^^9#g)(y4$7DCOsX zQmH@*D0vFhoEHFX?{~qP^q~dO0caioWq>368mHlT0OV4S&-`c^0h0E?%VYR8AZZ`G zHU^gV!5IN`swYxT;E&`0FCOZ2QSksJ2}kQ&{7qTlHE1p>4&V%N^fSMp08EhsIAc8f z2QAS+OV>f&!vIhoitu56`Tu|q^C!@NkpVbntRXSO-!$dl|Nk!h8Z9cIsCmix@Be?- z?$a-ozyJRaI@x0l_>w75=K|811T7MPERq1VyCE#lkz9})6aC)OnE=70bBTR=ORn?Wr&P#Fr^0bc-azD)ri=L0%cB=@r#r8THG|y82I*KX~Y7wL(1Uj1r)Gh>h2rbN@twLBU4cTy%R@yeuRv!h> z!R09`;9c_I71Q9Q-4PzmM+_X}9Ajf47l$IapvfLcz6347g|HC0b|XCh-UIb+z!aTEQioA@HCNvYxhmmO9?^8UxAl5fuhb8lyyMY6oN8|1E`nLS)<}%{2!D*BEXWM z{gwPi%F2XIBdfgfMT|i@d9{<6q{c;hwF9@2y0tG;VFK9h6Xy_l5^#b_A zPx^L02G#PQw)uHz%H#+21uuD^_XUycd(8nl3L3R92x|R9`hu5>pnbtFpZNt{89bVg zvVgAqDP0cABmu9*L1k=$BWNo>gGcvS*Iq}+$uc1KMSxZ?)TnrXW<|goiyh_fx-YtP{{kPr-~}$04)C`)GJ*G} zwpcQOmZq1e1c2}P)dm$-J}MF5A^vNiLJ72hrx?b#3|jg1lCdNY+|m38TA0=yqv8Q> z`o3XgVCeqt*Q){=Hf8WQ_zcqW1>eJa&a?ZHZ})u{{%xEt*5`{M?cNT|cJFJ((%Zhw zSA3X{f!nhhj-Pn1^DEYwQ0KZO?8rE7;t-c{*M*gSJ6-a>3ixuNh14A(?-~ z_`plhjlG~<1RmgV0(DT2v@=IV!@XCf-lbQk-qrXd$O3=Q?t2sXw=p~Sns~bO#xYtS zFIMkmap=C#>&N88zm0{#se?t#iGQ056Qgqni!hkQgK#0zFy4k#J^31={1wlr4B)l{|CVxlE=Qx*L}6W`!F8|$IVH^dP~r0 z_MjlN02N^l;Kp4{{9({aN6-YegD+?qz)g?to87NK6R)+PwI`ssOrw$$=aB>x9_gi-=l{dj z$BMRjFz2Xv^m;J%@_0Cbgh7kKK}X_D1|?PpkR!m?4uSUxYJj3YE*59(m!pI+NH08q zF`{1Evl|-qX%nDf%;wn5G6@vK@DPT?`vas9J_-s#Z1D~%A0TP|ynaXkP@SMhVyMkF9S@G(13w54>O>lslLltuI2dhZHz_2zdNI0PajZ z@?gH}qkYwv`5t%(<|ruLNr2M704TF5fbxe%9C%Bp0{Gf?(4E(y9WV*5-5-6sAAs&9 z{^$$p#{6Y~6wIK5Y{69@sMQQ|wS)0nFdsCx338|hC@}{3cK`6z{tats!S}?tbRTtu zZ!Lt5{k{Y38~*+O40BSIF-*V*N_M`h^>&X%m zpKj3pAdlADD7z(iL0d6FTZTbxP4J#!3Glcec#GyGh%*d8R)WsbjZrZG4|hZO+HmVZ z+X;ohTdzPP?i{Y&2f#;i9tKU(K-P|fROBMu<_Nlz_J9xgQ98zLO=2puLAOuHA>bVcV8ab|2m^x{uI&7<8H=#PSeihw_6Lth!hq zDgsSZId&i5-^Rjm7&K&$2tY`F05=stDH3FR_eIa{7cSkPt!^&Xr}$ezb16R72TM6Y zJJw%@7o7O!*En!-0T33foJlrZ;4T*_v@F-4=3Mc^i!9p)VP6O>ze+=HI zE()4vfb3J}0#ARv1kGuF*x2Cg$x2C_&Y_3sBU@D0PZAFg&ZAH)VZ3Q(g zLCfr}K!>kD*#?wWz+2IcJ-Wf&CbTv<#y)fvNOud=*+T3?4*;cfcoL6?Ov}A~3R;2% zi3-q!1%w4UR06`XhqtjoNs7y*`w;XX&BNUA=~PgXi2)}zP@F(+RCVnB3EBGE4Zek1 z50vV#BrE7SxY(0bcZmw*O4P%k4cZX*fW}AISwJ__9pY~Wb-jIhK`Y-Gnt!tKxA!oE z4v{p> zH!y>hfrMHdn_n=Nyakc`Euc;iSjjfW4O}oK{4Jo>c_0NP$G~JZh~#h41g&=fnb+yK zfgP;avH1rpe|sosgcierpsnHu4ugUy1JhyrEugEWAjZZ+jpc9E1F<%MQY6^#to-dV zAQ5zpw$u^K?6ddkq>*gxIf4dU=_3AV$5LH4@Umhcn}LEqUC>{-gVHX0RgYh6MQ8YjoA%NF%*f20K z90IdIb@wqa3)ILs31%%~U|=`{W`Wx17r-pgwET523$)C#;fLR^P3=F@)EKnopFPL?SnStRym<8G}&8PrcZ^OpIz`zV}N+3=Aj0tN<1U zhSOjcXp7@42Nk12<&?+>|MB7tDa0G6(L01uP5CZ9N?z7z+K=0cYzPwya2d)A*`_2ieQDM>=;&9%1&TqV0a4lc?v5m zWoNK5Fgyc`<*+g^JO{H1SQ!{zf>|Z33=FTqtO~eGYvAr{fV;T`?!FGV`+DH62c7Q= zN!e52dS}4(f=>E{q`C!ge=LEA+6uTSYv3-}05@d|+!W9{CWt9};HDgay9%_B2ads~fDaQs53etRAq!QuY&828NH|aCyPX!0;K&0<{R4Lu0~;(gv9Q6?8V4IJP4ck8QlbDGEOm;o!P1Zf8!QdUu)$K80^Af8xG5TN zQ*_{_7{E<2ftzB%220~Ma926N&2wRcr7I6MSi16IgQcqgHdt7Nu))$*1RJcxjA3J7 z_yrD^1h}hG;BLu)+nWQow*cIK}qH*oV@x=;G_f=^;?{>j1L4(iOi zcej97# z1ZIIwNmd54zA!K_sDfFb^NBRTtUnA43|e3oXqmJun8m`#z@QCgfsSQY1haS;85nfH zEYK0&x?mRQ&J>7V(7~Y)y`b?Oh+fc{-w?f^4Qvp-prf@RdUY5X7}UY0fDQ?Vm}0`n zzyLAD0`3CP@CU>c(AFG?DWDDa5K}B48FMjfsL;pftt_W`WX}7?=f0V-jE%D2+*iS)eoq(F;mr z5WS!@2GI*jV-UTd(vcgi15`RfOaU!zf|vr@l?E{dbO9B_lo&<^28b!3gMlEXq%blt zh=WZ5T{y)HW`QnOftUw6q#t4)a=FV^o`kK;O$3#>Nsux(6jY*yfeKS-nd<@)a0L$8B&@LPsFblLf#1zZ|rA1pX3$zQz4$K0Tv*utHYM4i23-bt2h(TLqKA<23 zx5zvokzfrH09R`!AZ5@NnJP#W+#-_$v7jw7Y(dQi-bKa=YGARItpNq~a!^o{HExO; z){S8Qf$E!XFbh=lwSifnT~JM67AX9i!7Nbtw}M%q@NWmRK;hpBW`V-L3(NwAe-D@i z3jY=`3l#pnU=}F+JHRYZ`1gTXpz!Ypvyj7@gTI~H?TP?U1W>76!42MP#|A2m!8JE2_XOP`jcA%mTG53cxH-nO_8Efy(@PFbh=Xmw;KIGQR=L z0+sm?y`VB5q8C)=L-c~m{93R&P?=u`W`WB5N-zsl=0i*YmH7};KxIC}6i}HDF$GlS zLrejc`4Ce;Wj@4JpfVrgDo~jZF%Kn`mX~2G5hOsRZ~>$M-~lysAq8+L&juz?iTe|j z=ih<|aB%@nmzSXJ-BW1AEvUV_4NVMJdzTs7(%pcRE-|`fWR)DGv}$pwMJ;_o!LbCo z(j)=Qs$^hbNCLA!4on5JK&3?lm<1{=!oVz0X%P-)flBL0FbkA=qrogt>Wu}nK&dw# z%mSs}L@*1KdQ-qGQ0k2Vvp}gg3d{nf-ZU@^lzKD4EKur=1G7M>Hw(-HrCx|$Q0j%~ z1*KkyUQp^y2de|6-V87clzNlFEKur&m;y?@5K};@7h(!1^+HSmrCx|BpwtU71(bRr zt^%cAh^s)U7h)c0hF{+JKc>kD>r*9p;4NMUO42~r_;%m&(f$No-U?bojJ}oa$7_DjrZ`py z(6&&}VWx7RH9=?_(0D*fKx=0}YmObPew1oMwx5B7A^Soh%Tc;>Kv!IXHjZ?Ima;a3 z?&dvgeBkv%Kls9w5;)5t6zp~l*Y2C(qYqz#mz#jD7zQn7@Bpnj5dm!vVCX&uTCW8< zUR=Wwv}NQQC>$I>z7KHez6M$br2yJNm+IRMS|0DC{TH$k2eP6Al=~2in=pJU4+;}P zz7;_7E!ah%Z;lIRt}N+JG-2DX|9~ zDz6XT;dm2$y%P9Dc@EI&t~QJ%Vu+vg4TJT1dTR<*5QI~ zJb{c%!B>d!Z)5y_qWOmqe>VtZ1CQv*D2u5pehyo3C;6?hBzYvUcDJ>;)-z?;ZltAcltzm@}UBY!Op z-bemg0KAd>HRlcn1||m3nm!lq1D>G8rmq2zKfyjbNp9-)TsPw4>vp}U!6_^DoeX7AMQ0Y?xW`Rnd zS}+S#`qY70pwg!v%mS4@4PX|i^l1dMK&4L;m<1|*n!zkk>C*ycfl8lNFbh=rw1HV3 z&2Kndx^KC3Uj(&|gj-LRusMD^#8M*fc#MJ3qxlHOVbD(CQUTC?Hr#2B#~GNI!J@9l zCp|i4R2;i+I`#%Kx>}#+Zw5KM`;epZA^sMS|7}6%0hH&JWHkQ}D7P&!0j-MB01+~v zRe+FFyg~cVH?qD2brCnRyae-@UxE_)My8j&3=9k#*21wf;ul*V-F-UaM~uc&%&;I+>tE$`*7qK?%PtC`Fa97@d60wNdyr z+eV?+OdAEAJ9ro!{~!E+_~IeZcF87o@D3>%6>xZbhVInm*FRCti{KnAW9HX5co>u~ zwt)9IfX*abAJS;_ydGPy)9P_lk;K}cE z!Q=md&fq^D*2jzPyaXME4Jy_o9e2oLw*=}0Iqb4fCxGq&_h>%Mp$-zlE(>)K=wKd? z=Klgk{45c6#&%|MTb-sq(PCS4#5LwW!XV9H=EDWHYB;-IC&{4|}Q$UFb zq7HP&A4C>(;t|A@d+^iFK%3wovY^xs(FU*Fp4tg3In^Vqgdr1zl1Gxfu2Yzt6#)pnwJ)z;p2M4sepQKH?uK~Ur z)T7r;fL{ZAu080+80~Y2?X>&?K8zncdL0xznh$aCYaHd*xXFL$4kPFe>X1A9u?Io& z5BNhbfTY4MgJcB*E}*Iiy}%!KnO`vAfv5F_qV*ou*ZG^@F@UcE04+Xd@aP8Jq70rd z1MR5RzUKkD6QXH0c+1Oqk6t$x5A9PPy(|+vnqRVbfX)F1odGP;1!>iR_Tw`D;nzQ0 z#>%g8;x#+J#;MQzv8O-t3wnU|Ps>PPlLBp5k6~uPCglOz)gHslfmIH)vsS>P`6Xj% zhllm`Vr`FJmT4Z^zd-xonXzdAsb>N2KDh(UVeo~tsAn}o5;dq9q9hJVaHwZBLJ}t8 ztVYmiwtWpq9cXZeAIt(BqynjwB;i}|Fw)u$@PXp6w1%_D10VK>r^vg_-vSyV@@W1o zP*myB>kUd@{T{u+|2=v|!aS@$f{MKB#qmDfhap9tpMXbixI)?lP+>-;Lho>~A{K9Z~4HfeXFEMGvVN1q6Z?XY~dSS{&7zE2uiTUE=A{8^+`bDNx|2((D8mHoZa2 zUY0z^z4(22j(b|4^yK$B>G}VFNB6-Fuivnf*E*d3ba?&su|8aMnx-B`Jyw#uCIzV6 zD$xel;|vWu5lsl5YaaYQJl8y}Pk8eCobdR6sMGt8hxPR$s^%ipC^YcwM$H|p*vnez ziRo5hu z^FtcHh2?4AUI&osBfh8oM3Ppgrxzzg$%sy3H|6@&~CDVR?sD>;Nr__3ZSPaqRR~NptLU7TEFcKLbOWV<)pm@}W*26$Q{Gff{M9oz6_h zT~t6rVxTj6v7XJ#zul)rA?@HpW*7eLCq25)gATCojAn7D>mJGz9euGmPIt`xG;=YXEP&-VW+zb@PFGRsSJZpS2z+QLtqKou=KgnUR43 zBG`Qb>{HO4VxUFD9=-EG^Oc~+qO{|Vv$j|r^qSA3n;XgC?mp1mr3YvyDf0n-ftF*S zX-j^Ao?{gt%LH9i6hMNYi@*Q(f#x=Q9T}S+Fn6|rbb)R|H{cg^Q2||lx zIX$|$p-!$*QRqGo-cSz84WL9;1WIIbjyrB+50ICjsddo#C>EgIGzy?RYT(h`{eqi; z0W?mU?$OQoKS02vb2Uihbwu+IrqW=Tq7P6-PH;sak=Mq}H7W*-(8ChJiNmKG~q%Q9xX}i+tJvXi(J?bjrjF z9E$9Fu0}p=Jv#!Pm{!a&G zhwcxp2P)+}y8R1$dO-y)!!A%$9PDD~_PW>LYbp&O*Hm`LfJVo2RNys^Li}M+oI^qb z)bEDGv?aVJ3_7?SA`40psU-{yEDWGaQ6X^&it`Mx7^v^GQy3%*8jOR4J*c?@37{~@ zS?vm*puMOsTtMLi8=2aKJp`bQp3T^0jlY3H#Q_{7=H2H&rK1BZTqc7Gq}BtKQXbvj zuu$m@V+4oE4$wBU*D?PmXMl#Lx-WRN9w_DfKe+(P>-JG`aBTj^=)gbam`69GORpQ# z{{RD67{g7&5ylSS3r#`g0C-<9sK8eMozQ+*2cFZ6L0hZA>D&Sy!d`IMKn4be7;tEY z!DT_im+@dRPz?bIVbC~n5?Bm0VgV6*$^c98&`>W?kpSJVX#no)!%hlc?T;@puE8#A ze8~fH5{^f&i;4qu&n0NDzk&y3|D{W>llcGX4xp1eK*>=XM{C`nIEEC0 zATjVgs^9}BnvWR79|lb*KoTD4)?P@m1YLp+NqCO%K=WW=VAu-|QeSwWfwsngih33X z(DcVj22ipDox*YetZp&`0|O+`Kr$Ff6_Sl#fG4v+i4<}LB>1El(54!X=1KvEQqdiM z!K1hE{M39z0kpZkM#aFRcPD5f4wQhSa8`*P-Od8wOG!cFVgD~+tH!!-fRik!3^#yW z1rEOG7JM>G++mMyexw=|%{Y&4{_Y!)Y67SB7!?c17I)BT>!7)uxWftXKuw32+d1$+ z1uZIuL-kNbZsA`#08~94p80%4?G2UBHbisw%IOOgq3M z;ZFS9T0kWmL(72@;f@v=i0Owt8h`%>8UNrIM2G>K`Jny**nCh7^r(P>1Ab;-;}KAJ zfr`-BXq=~nL!t-QDdCXl0YwU=+yE_Ofz;kL@TG>J1ucaoplxT@;oI6l#c2sx%oyGf zCp8+|Jn%(hJ9b&jARdiI1_olIvAY;E8vDRvh-d^A9*|ZEsAvM;*lqy2E(TP&8DE0b zYuz`Y)f}kILaO8@gDbfokTw^#_P}{?&U@_)I%dnkr~9lUcrF`k5n|#a1TP}d+5n;rTzVZ+Knw>E5db0*Kt#d+X`rIL^+0JYD8Kk#aohpgPX!M9?pDy{dOp3b z7x)ESR5V&|d-R4*;1}>wv2f&fIocU}p%bhCbT16(^iV<937xJRKuSyax4WqLq;z%S?uZvQj19w?RY?LGrC(6RffNB22LP+7>|@|b~v0d%I!J^m(J&KEyd?ojoRA_Kbio~@5C*NFmkp<2DLyBln zEuIQax}cjJA!4jd3=9x43nthIi9={`80Y{Q@Zt8Lv}^%M%b=ZC$hB=NXd9tNcPprp zG5!Y1=@p*cH$j;p!nOMWC?|q$qxb3l=+gZTIysgVfHkFq#ycb(cVy#`1)tp7S)!5w zS^y;JxZ^PPhBc^(DCxN42zJ@spq`wI_2Uv{$L2pwr7Yc-UrRggScqLQ=(u_$nzdx8-4ga z{((-cA2JT9E zK)b5YnPHdi7cYB2ndzTpiHZY%TLb7wJkTXgu$g9Xwpf7F(gE8AYF$KtZmebmd!D}o zbe}1xaTfuN1$`W`1?h;N{|8@}$7wto(pb0pp)=Q|Qeh2b1muL5R$N$qnm;HjY2Y^PV*dS&&b|3ZV?geKr z<1^r5*rV|XDElFbhv?YDpnEzX}G={RQvgfZC`KSII*tRJ3t9bZk)pwc=lEcyu4*2OTiT!;H1-@XVoO z3%Kt9Ez5GT_u72A{Ut!DUZFdj!=s!3f4D&Ff$nqsQx3Ks==4``;GcR7w3HFG`R~&^ zM+MaW_w0_A@a!&D@a%pZe;AZGAW6s?-c|<%6C~P}GcYhb1P3W-YXKxuLCfZ_UsngZ z3I%k8GWf>87ohV=;kTnM!d@6a3zEgyWkF?40Vwz+9d|6jp#;)G`i{L2hwA--T^1CR zE-D4khAB9QL;JC4<1d`hp}^Npplk}t@Sqi@BA~mqPdhgMXX2l72sC~epzxa02UKcb z{T~3X6AD26b5Pr}^;@Mf)Qp{=S&H7ki5}fNmf)m=CqixbeP z<36AsE+c4)5>`h2!ziP!LCdHBP`edeM%~6W?EzVQ3M+LYKnEWtKu#5pk0WU)9#SeG zhT>D;eQ!{q2q_gnS9(H91<)-Xkg@6^4v%`WNrjr zvX^igA9$?<8aQ@v0k!L{dq5lJ9^K*|-RC{JZ~PAu0L_Pi2kX%WDAeISKhVYJkYX1! zQ~@biL8~|+#V)9a2$2O{few+4f(LL00|NsjfI%wR45Q0wZRRXt{ zK!FLFB?rxVC>Y;%?EV1~GXRa_cY=oLTfvvn;_QlnFQdiL6>I*_SfbqgpQ)6!Hy%9S zilZ0i(ai?xgBiFQALzae8r1~#!Ya%_)vy7mD`Wv~ErTmt15iQ+b--G0SBQeAnn86W z^1K|F4;lt4Q30(DQHVbbn*M-<9_YMPNa%r@L6Fb`O@BaSL1(Z+TE3urej&1;8C!@f zXg~-udJj4%6Cw)=Dab*rpevOXK%-uu^MGIawjKbD)QjL~&2%$EeRcYOfCi}J2bv>* z49Fip3>p*xB_a!cdC;ljY0WnA45iW-MNt9(R~gyzoxJYJR;uP0O|zs3o!BvcnWy* zc7l0=4h{SQjtlq&JtpuAcy8bqbXdSI;CO&v&|?F?fae8%L5BnU0*(*(1wAhC3wVCu z7jyuzKY;I|U+d9aEx}OA1)jHh32Od$bnXGSuRVHqg9cha4R|kn^Hma{q^;o7easOw z*l-%}q}6L(jQJ@H?I@E}XF&-G+Dn0iNjJoG-G^VZLg%Say9XYPM?j$ux|JaMFz8}Q zNVfnqUk&MpJ%X1+pqWNUWP*xeNM!QE*B*e@+(2YO7X?COLF*A9vspfju=N9=)AJzF z4;qkwbSYpXPhlRtIVu-D8sC66+8`$gS8-S4Z=Rs-We-4;-ama?50tq1bh^&>=nb9k z(HXlO)`agYUGK;*z~IO)$OxMJ=lDMzG{X6s?f-Pp9F>b@>3aUwa7G4(9W(y_|NnA7 z7XyQDZzHI`&mVpOs)@f9R0sKjMj$~~GC=DA&+cATpaZxWK?)8rFfeoofVfN`E~x13 z2mqa>E5Z!ogGQG-0+>9S53+hRA7J%p{0Zt<7tMPKnr;O<3N(RDlA}tZksMUw-4XB~ zbSnTy%cT-axI;=b5RNDj!{UHVuSG$3PQB*$XgPkcH{r-Q<_v$Wrn-}Q_~Z)m@(@d4lN zfB&b0#<^P$RLWzW2L~@fc=?Tkf#LsjlvN1R;j0jQ`}cvO$OY6VX8a#u;L`n|^+2Tx zl5xFZ|G~M$u^Tel(t4nT_vIoE&_x=x{XU(w)A_>>9CzIg8gK>;DTQw57j)h2(do+V z*?pd0(Dihut1!Qy>ur8P*Vp`luD?CI+5ZP9xO9UiHk6R8*#!z3s6~*OjhD6@3=G|_ zyL~#>sDL&+JG30*Zvida^62)R4hn4{!k09iZK1{M%gnyRZE}0Wt+-1V}$lea$~O_}fAI z3-?b z{m`ZR=L!M-mTxQ!4Be2KVb}fEABqC3OAnWOcQ>c`5hezP|6mQ>uKT-fK_0jMQ50xhd$`;OMInmGE?AAW!D}=qFo`mH z2^Lr5H=2imfq^Ka*N|*9sD`rsQ4|0Q5PU)1L#oNVpgpD5CyD~Beep-)6f7q5;0tM9 z2~cpHC<+9bj5iWnNH$uASfjCp2Yy$BZh*5U#?_<;2)?iv+C-S#E3$c;I!wKHZSI z(D=YhdC-mu$QUn%B>Y-At@(!pOcCg^!`Fgo%|8@MO~6%K=WdW9 zkYOgB&EP7+wZF4jfC+T_woB(`P+%Pg*9l@8 zG4XNHvBd4U2bE5cHoq$a14B4?6kt9B0|RI&4-0to0Kx(V6@&#k?E1fbQ!klytd`0S4=69WTe9i~1LZ1$&wiGcyK4zmU>*1`l^hk2WcfuXzv zv=ZzoT=p$owv!ol(%F1w28IdXz4V}Slz$X~bV#x?FhE$K^LQbw^KdaOHkd5v6jX@p zJ-C=9J4|*8ob?{g^5THWu7I=HIALNbaF!AmY!*wCi-7?W8hTu?HPUfh3=EKUzZqPx z&?w-7`Fu7P149TnKAgE>GgcAY3=9*&zKr2!V3-7EC2%t^Oa`-3;IbKTu^es&hN)oL z0&WI|DPUF!oK*pr1&>`oM>io0jBkKPH(|RZ9I&sK^Ju+YBGGKkP$CTOQh`Pb5G!^; zdoDbDdfP!AEaSJJCBcxp?!o;SP0)NaWKmG}Sv-9!=#eL&9u@vB7PMEx-va4kfw%&o zE)_*xEDyYHgExZ|xcl%+(0m>!iX1$;Pye3=>VUg+pY2=^Dsw=w)7cIxbYT5}?$eI^ zQx6(na%?`x`X9V52Xxg@>w!{Lux?Pt+N0YK#VW87hzwd2+BKBpu8gr&pQU7ttHSKdq67|!C42~ zn+EL+2j?4oqyttEn1NPFvVaZ|VSuneqqYzhD4l`UOR_M4(iwyWN@oxjD6K$8i6Q$r5NLJaO(QfJ|3_#w6e~+CHG6bkKq#&}@;U z@g<+$9+QXgRS1G>rp_=n9XOUJfoAq49d{UFA3z81Fo2uhdZ5$*RL*MqfGj=gqkWWr zyMPn_b{+=pi_m3DCrjV7UMl4}-ij!%*+DHz2heII$f7d-4p18s91kEF5777$XjKLS zc*&BBN(5*%qX%T7qxC?ACuo~nfJbjDs4De1?xF(P_`(2Pxpdq`1=M$7z_)tIN5$j+ zWZ3E@P}GCNh8?un-2r@vkdKN2bQyw2_j%~@0~gSqoeCw8-Jn&t9-w7mkaKaM=}%3=FHmE?5hf1=TW;wMkO&wMqG)o5jF-=P<#RJsexm1$XAqWx$HAe$G0vSPUrj|=3<{g1dAU1Q$ zr4reWKxXiK18CmC7Chy!&FL5m1H((u!Y_~J11u08Xbqr8^8scE4|E=+NAm$D2oKal z^!R@O!U1I{$fAA7t-|o*Q;@t-QVA-RSsgnpUgu-DKLX-@{+3H6UI_PFBDr53$^D{O z+z* znCG-%U|^UE&X}NmGY}SJDhs}ZIsml2t?|tUP;_;J8tV*@?Es*R>1zDnv->0X)I;#G ziNAchVKc=l*w-w8t82#{s@P>cyV<~b&$Ij7{{RP2hk(C>g$1+^Ax1?3x+qQ%yH=>d zLfB=S_pvZBFgO&4!B#MI_kjdJ<2o(pKrO27HV~hGyEhYPNW144xS2llm#BbBQ)unz z0V;?<%K)HN7IfbHAgF=S?V{r0YJAeOn+-g# z%{`#8eCWCjqI?Yw8L+RR17GLhzCPXp_B3ct3gYpXpf#?bMGFcZy|oh@cl~2vU~u8z z9>L^leXESawfmN1_x;y$E};9O9lH;MPR2CqbWzc8?7rW6phO*ViW2yGS(om^j@_`c zF+12j|DW>se;l-{;&#z}@M#XmT~y*27&I6hyDz78x~K$r^!iRn0~s9T*nJ;dTX(vs zq_}oJ_ke7F291GffH$T2By+fQ)~Hk*cTuSUb0~o{||dKA7^R) z%~x~{6tEhO|BrwQFR*_coByyD^LNLngt+i;k5LJFsQ}8Hy`U{E|EGeQMxNciu-@!T z&^}jaDkzQxwUPqB!GtRf_^1RSC4ERDa8U_?4}aoG1)kllAj?5^Sp2{6TGqGqZ3&BS zcP-2*p55ociy6T7gW6pN;8vl4Pd6k>7$11;imnH&!4q258XtJAf}sEumoNp;^zG5z z3o4f2b!_7iP_YGCVConP9!~@vX9KBXQ5VUq1DAn_MKYiRR3MEuP&WzE04j%9z@S40 zAhMu=nHgZcp!yBcqytqc5Lr<34Wbuxv?oONAp--$CvZclh>?K-)FozN*amNI?PX+O zfS7j(-rTy*2-8u=#J~VKxOEE?0|VrEkV8xi3_rm`g^tV&;0tM37`&Jn7(m4Y3qvI6 z!acC;VP*z~Ent`4W`>#P$HKs{9V`~j0vo(*Vqsv|1s3avi!EkhVAu#216}_HX;f|p zU4{qt2dIvPEP{YGJuuG%PH^mg>Dhh52ek6xBXmRo$0iKu%4%G_obEl4Rup7t9cORn zxF=|73S>o4>w!`o&^U#z@1`a{tV&MWhtE>}L zOXJz0;iKXJ+o1t%E`lc(THlsdf$K)7Aoxt1URQBX&{83ApUc(wjSSMA!BZ!^^>rUjeuu&HgpMowFbC@ zbJzslR0SBiow6Y2`whcB)b@aN z^*wr913+yeSYIEMjRIhsl_28)&|SCSolB5SOU4JFV<%}7ocOo#FnIJ%1zow>ai&!06W&>IH*fPc?W5uW9!3J!?9f&q$jzWN zN1bz2R&X;gfQL0)R1{o|4}i}!04=SUHUSi<-99Ry<+sy1m>3vZZ+mn@)bqEtf{X^8 zl?!U>7#{#_K80~nZ}{{E zG424xgQxakP!rGEqxC?EnNP3Bf8&$BttU&=K!MA_z~I?^$XELiQuy+M!uO!Z{{x=Z zCyQP=9b*w>0G$ou$$Z$u_CGTNLvb-QdRu4iGbo)5M=)W z@JX9*I($?DAhFx}zeLCQBsk6bXdn0JJ`T!%2=yTM!dALHJH}$kkmlH2ufn(swA#b5 z+g-tviGhKEf7b)9Vy>MoYZw?_f>tfA za46y0sT9V*@Dg;=%L0-WPI;Zhzv}^aF+12DuVY&d zRI-B|@j7S)2TYqg|E>qZMK&<2d0|@h`FB0wglSb;p-=+VD!zgVrj=(U_)P4~oebrS z3~9~3I7)dPkGFw3VW1Y>Yn8O-UjlF;P)i*&)BH=K)B>ss)M|I+-|nvB*x3f^rWqe_ z?Cb*#hdUl`19izj2VFQG2lvas20~_=J-U0r#k(sY|Vz;Ff35@umwxDIA6@tJ)Kgt1TCm2=tSD<*^TsL3_{& zxMaZ#9Tl<5@(Va~bUSN+3J?W;0dI*;&v(jZ1NCwgKs_NBegWqI_|a@0 z@D_gryiMMGBq2U7`Y<}G+aeA+bPUo)B{BpBu!jOP1O;))f0$_^X4y=F#B{Wf!dhNuAK^?4d2YZor<8@js%a^|0T>Wo$8?Q zHon3 z;N`QWZXT^~OSnC{yFqvIcy#(QfLzGY-3(IR$p&(!IE3Soc>;8Vjt4VXrDrD}$kiO( z&7kG3o$OE-;XHNFvzs5%gaK`=cW~@J0vfb|o(I-l0zSq=0ld!Ta5KDE0iB@-S%D5Z zSqYNFK`U7xE6_pb9(RC8dqJzXA;}(e=L17E2QiYk=th9QoSMsV{R1()uP zARqg7zi?sR2=bCk_oMDc(3VDz?w2msANZR<%d0%PXM%#srMH5~vHQ3WWTm-B<_}Q7 zfQE>rJi8qPz|}wK^mN~D2FMJ{{|m1TAif6;iJbtqWx-nwT5p%|`E;N6={^jR2JJjO z4q<{#0j&}D=xhbs*|`_w8prN?pi>C=TS41ZT)MY{9PMd+ugu;f^Eb#9p4#`|bHpCq zQ$ac*2LgC7U-#%f<d^=>?Zq|64(} zyw-<1)A+z^b&t%`cr<`Q5I$Ory2DigRLg*m#yAY>=a_(d&7gx>jle9>II|&`1)9)- zY%Bm-4`I17GB6l}Wm6d$7$9PxT_=cRE}GwXpjHP4p!q0JV)5;M=-K@PrPuuq`!RZ; z4vM7Xj`!GQjc-BH28tKQma{O!W`PZ3gc$~EzI{IkZabAV z|70qygSv{psDyvlL+)aBu!~-&x16kG2fOBV%(sIaCG23Qybj>s^-#E&9qf+R&M-&t z7FqD`ddLaWtNrbuLJ3r}{I`QlFwMdsdzlzYpxRkLAq0yU5m>~qgO;B7s2CvCyB?15 zj&b007#ACT7_?d#G8ErUg72aN34<^Ls8^-HFTmi@+oJ-S4&fK@ z5b%KRqY`BF=-mPpby&bJ=qbQ2;IV;U&{2V3z~KPDpr--9fX4-XK}QFE0nnx?&j5Y_ z4t_z$1lZOq0f!I#f{q{f1snwU1v&WzJQVl^9R>IW91QpcJr(!`JRJB19S!&e90EML z9TNBjJwf~ck4}&Y{h)5XN3WZL2WSh}4$#C#>wyvvk7f&oQc;iQ3JHc%Nss0V4u(>8 zkLC&ih7w({Ab(3QNGVu?8!W-!Qq97^z%RhSFUSb;9mpTA72zY_jvNT40D>vu(Jkfy z>RE#37&`Z;Brt(`nEyRmFO`aV^u||sbYBLCU+V$K?&}`>E|)zz!wWpR&v(0Vbhd+J zJi6TkJUZJ!GteI0ZW11y?V!b)AQ=Www-(g+PUFu9Pak^pwy1#GLy z9n$#o4tju32`@On(0q_3jsM@l&;0ccADVx1mpy9!$yYAr(K`jK-=kNgYeFZNM{f|z z|AWmx`N}?nj{5AK1D15$!_UCL;KA>D*OlM(8K^BG>9~Vq7N{={9u$LasRBC?v`+=J z%f_|)yKCzc{ua=rxKA(W$WsQ7WNm0Ofj|6!PcLYUn1NrA9W?X+HU_#?3EE%e7jzfk z7j#$P7j!q^7j$>v7jzHs=yXqL{>xWh;@I5-8WLvkWIp@m;8}{@1ZSo#L+} zd^+V{2lETGHGn#Lg6#YPeV~2$zI~o4H>LMS}*L=ePS1AN(1bBk8XBoV0c&` z1Yf^$-lO|kDLYv5HPinKpbk7}y%_;>9RDBlXgFB@z|6#cjw$6CROz^_q zH7cN>1`Y3n;vPIY0ZMbAJ}@}P_ki;`yl84X0xAeVt0iI&d-To+oi_rCOlij*8*rTI ztmSHa()g0`N!MOSMriAylNoe|s~Us{J#nJ-K#3?)kbvhDz+U#~oe$aSc)L^%r!Bq3 z|6c3hvPcWXBG4+65-F@!y=Hdo{*Ekz6e*zX@ZjtMOjXq|07%K%kK&qEk{y28MT~poNg2qzI|Bx|v{0uBS3FFuVbqH=l`tp$=@yY9Cv_V^{ApQW2Jz9cYAIAba(mIuefdO*( zb_oju!xylt>fkdu)8XdLgPXUQ1-6)bCkq1u#JuBh^UlG|yU)VF@EvU4OSpMISr{08 zfyJ0vVLs<$WnlOV783=fTCgwW;j&t+3=GU*F%wn>22L=`4ldik%D}(|7VBbVVBi6> zX24|^!(~^oGBEIiWw*d(&#*EuJOjtf1y%-z=U~*ccdIfz^QyA$$#Hp>@GPd*UI-_bY(+1OM>o zehF$D$vN)W5{tFsa_m0$n#H5r6L#JYxDbG5O>nM+7VMDn$fNrPs2DT_^>JBzx=Z1@ zK%-_*U7pZoZ9AreW{*4Pf{v_t2|1|^G0fC&~hLL(6|fu&;-Z0!=P2b zkTyMNbs}WQ4YWJ~QmlZc#`l2R^q@(*&|*;G83#YM1axL2L>4qj?f@=+KqD9sG0?b0 z1Xu@X&>7Mn0IgTs4>oTX0|P@eSQa!j4v__|$AZY-W?*2j2b%}loevQM9g-3U*6|na z0?^%*hrs56cI-pUQ-@FZf$qzI$b#-}gO~?eYX%VuWMp7~m=_7x0lHb@DA+vElsv>d z(E3P-`#|d>A+nPhVeVT7T2Tqs0a_IaF%NVyB}B&oMh1oxU|CS6f|v)Ynjo^Ed*2|k zpxs3f_c1ZSbbxLtfS4x;UnwaMuZA?47#QNf)sP7j149^?WzWRG5CLX+F)=X2fLWnT z3=ENA7O0IM3ub`|BFL~^SU7wX$N-vxe7k>urlAykx?e#>Ji32^L^O^%nZWGP z&4<*o0ToYR4IcjwdvvY`6-ge=znO|ULF=0NdtGO~R016}3YF>RLv53T?SyEDOv-z7 zpG7GTB|Xr!Ac`lLSstD9QA(%zAV**^`7}8uqm)flH5sL3qN>TDA_^L>9^F(m8PtrU zjme-B2xwz6XsZ!zOa|R=LmQJpClS-eWYB77+L*imyg3Nea%7-#sspXQq>Y`!IOc0LD82syJ9(jSsxm2Cd^#Gd>BTq&>S2 zcxr#(-zLJ=a;YR7G-(f>gttCj^xE+l1A|XzJLvpx$8OgO(4=L8NB8;8_8W{04B&dA z^*~94NB4Y?3XowIAQ#Ddf(Er(50q%ZU8LY)eY$v~(=irChS%$0?m>2)M|VG{nt(`w zM?YTIfL1srK=utlD^t(o?w}qngJ&nKrRUiRZ|QklqJzyrSVafc-t7#M26eKKtZ z28Mbt3v?F(q;UsY;|OV-R5LIzK$>(j85kHK4LQ&mmJr#C3=9lSV7;KBbV$Pnv`hga z3)-Rrkp(SffHZ7C7tBG#Kyw?Ah7D+I8AJzYYgsGUJZ(l;!w|H>3nB};k`5vZI(i9W zUL5E=bg&N4YzV}>TzEqcv?iSG4BIh_B&h_v-G6yXgyZ6*eWKCrAM6U@J$ zMjOO^fpFOH~h#%LhseT3`y z&BDO23~U}JE6jaT@bMExR+ubkj0TckY~W(ftT6w2!*ztRGBB(Jo0kkX4|Gfc#P!v1 z*%nq<_)UY0&4rt{6mH&nRtAPOVDt9E%{vE|y$YAT2R9EiG6T`^9d6!#xDL>|ne|}v z#Mxl})nJ2-Q|YrYFhFE2*kJDSf{O*R!TcKu*RhO^fdMjmyM>K`0WxRH#m>M0*>df| z4wDUHht1i>vokQ*fxEO7?6C30E_McnT5y+aDmw#137EBjoq?eg%v#OPz)%HdZDVI( zCr;<(-|?;;EWh* za7GL@IKwI+k516SA4rp|(-~F)d2~8kAPjRrFhM&(!8hcI!zUKy;1i3W+q@vd44~V* zAY-qfT}ZFNp#(Y%^f8zPS~>g#%!*}TV0Z>*fer(G3TA;$e0~mQf%c%j0JA{ruU>&! zQy3T+UV>SWnR3unBdE)PJWbxB0_yA;zXk7G0Dlee;MiNE@(*gr@t40C zL9?>p*)2!Vp{4Z@8P84@6Cr8`6STql z|Ap705c9w-Y?odq=rSE2&>rUlrCj^}{Qv*|C1_zHG(UTGmUDP^vU~QrgO>S}B!ODX zmL8Vcr9vK-*%Dxqqm<3VGFzZT2_#bD;9;2!6EWczVCENO;1}=~;1~1|c&*~msR13X z_UQEBKrjUmOz^n^8ql=r+kGCij07~*2XVGXXB#LoK_TG{5kU&~5@(R>q&&KvCBTb` zUV&zZyM;Zw&;P&h@-FDWBk)j^Z})Mq?;-kMg7)hm@!|f?3-Ih@gY(>ArKM-58?3bS z>~w>bmY$t%u+q}A(+$yFcY~Fdo}F&6($cfj4OUuucDi{WtcWl^06IbnlypIJr{Ml7 zXwDQAo!vd)0t;!*6jY*t=S-sygOjOeH;)R$OQ1vrVRavWy%u!5F4*7T(&qJ2LHNo` zGkEz78vKO}1A`WWK+0#(y4PUvFea!3hV&Fb*L6T@chGem5LwU)SV*4%bV&z9477|3 z(tAK$2MStl69zU9w3r#vng%TzgO~@(mk?P{KLXPF0QDXqVxVOlkk$-n=^I1`XaYA9 zY#wN}6vRBxO)n5x&{{5ttS5Zf7BtWc5d(!C#Jo~?dmglp1TqH+YR^MzZE+^pY$RwY z3uMR~bU`d+$sOnz6-ZctHatLP&OjG;KrWZf18t56*FqEFvyq?`ED*hO;a7dEVPas= z1*-$K&aJ^LP@5cL${n~VV$87ig(@?w^* zz5Xl=3|e4T6bl1`HkbvPgVYDJ3RoBzbigdof)ztBtBr+$!3fNn2(kmrngg;I%v#RE zz+ehyU4_qreqw=z&>uJp)PjY?5olnW19VOkf+R);1~IUCpyD24UL)K* zP;m#5od`E?30%i&xOto5IzUHtOM=Zi10VOk3zvPu$iM)R1@+?~{spy2A!6+Caa(@) z$Smj>CK<4Kpi8YG?gNc5LtO8`#J~WN^@NWr$H2vs;pTx>%0YCLfevm0n+NL0Ld=^A zw+(c_lRQ{B+yKf+~yz-1ZX*!);V9)@YHv?`SXoVgmG&aCx zK_kqN_&5U>y9_rEG{Owg0cuZcgU$N_H;)xQ3JV%xhRBM-$89x0ZD+6!P-`2Kp3UJp zoItDbz_Oqfe~|b{gv(~YWeefvwZO%?;p5bk;W|KT{|v$At$~j#?}f`AVPRl^$ex9p z_W&;T9B$rwxQ<_-);rid4*0k&=-^sNXeh8UFqnX4HR0p7pmAl07`AccFjfWzbFhvS zxOt`UQQaE2Y%AP6&>BIAj`{F$;N@@~n^+kbtia|SfSY#_E(=;22$6jVHxIPV4Ks;p58ja2;7}3=9rn^D5xxb;4yQ zz-4E`&07N(+YC2vH(bX_HrNW*%WMn`h2Y*4=vd$kFzY2714Aa5^@WXrAqUL*&&I%z z1!jT9vva{LQFaD~JTOavoq?eM%+h9OV8{ovK)rO(fD;2`=`C6(475pA!T2_45mD>O zQkL%1kO5TG6+_^WNoWHA+)IJ48iI}nLB=pVy1^@lOh7w>m_54Xpi6&tp^T?O&h*=H z<=_AR9-Zq!#o)^spiL9#%YLAeVC%rcfbc=ovoPDh9Xuiz41q=kpg!>Eo)5AXi^*gy z7y?~tOdFFy^#N^62Az#a8Sc>3-uBugsCOxRO-T-EB z5`w0>`JlCSFO@(W9Ux~ydvt<@psJBg2M@=A);vHNq$D};&I(9l(W4t|0aZ+fy3(U_ zK4_ghvXSHj1!zCVOL6c@9?2D_!pA^tFEx5q(nH2`XALz>lI@J20YI2IxcYIH-I z)u5Y_A!4A5Pa(}}(5_ub#S6OR^a$9zCGckTR=9b47#J8JvPa?TZm+||?lHioKcB&M zfcAYI2b%{v<_*#e0o|Sju?@5q7a|L~`vB5RGGt_6fQW&%$3mJlpn+M44$!LHQ(*Hz zBTEqTGU3f&P*WQsTM2JgcQZ0DK*T1&o7J=6IzWx?vtaW!!JE~G;j*A3fgrM%;pRPM zWMF`Zy@8ttI_V$czJH7i3>U!WfhJfW?gL$!1(8)@g30Q_o57&9xDXwnbG0G<^@BHb zL1oNkuz6|lW^g%N7Ie55M7AAn-b^M228bBwSO|!DE8sdn+f%NA%>!*ufuxsHaM_DY z3=9z2>!70#!2`H2;bI>^tA@eLNkGkSh>t+W_}m1WCkJn~YQP)Gpk*BpSqpfx8g$SY zL@W^A3C!#{=Ev9y#tqh#0*=+1ZsXmbbuCkK=KDOyxA(v!octdY@Q~(8Eg!fwSvn!!<)gN zWgQS5G4N(^3S37X3j@Pbuz59b^FTW{A^x2Lmz@hY545ZUqGKD}ynS%zp z&_UhCGqH}5do))&fH$XBdoYv=c{EoCFqCqG#%~xvr;J7)Ha-BM@z3~!&QwzJNOsI< z{=ru+32k|G_kc<)1`p;_{~v-)JZya6a2jZ9Bw2gPK$<`^TuZA!+ZI44yLtbnjU*KI;r zpc6_UEYP?Jgaz724Pm7)Ffc$^plJ&UaAO&KXa?+fpp#@co>fF)Glh;MN1BEXKEA8{2|T`z;j(j}q;; z2ihvsoudNP25A|=re1G=cK?D7Km=W1YvIwm9W=2HI*=9Li5(u@d|;cQMzw=x06cnm z96Xp$@(Z+_2MyZu3%WA!3-p`^4TSRx`U-%=L8C4Fg02eP{U8nh`$2Ph{|`1lWbOnD zKyAf&Du+imAH+h?o)`5R+LM z85kgXL3d9=)TJ@PHUwtFPyQ%FIr#&23vS~ZP@Rjq_yu&bhDY};k8beN7fHt*tk^F{ zG5!zTiOYsv9<&45`u{XgT=*m(fM#ir?l$m{X6peDe%A}&gW-Iv5BTuA9{?R0q6gY= z&hF4rqhbKMT==+W^9#lj5&msFOf4r%cshJo48Ui99VmL^&{4x;V7U*pXxFC~e4d(* zih%?Fl;f_(C%dozp9V>02TIu?_jPcAX8)NzI+;Q3s~#1w7oo>J`Si|F0R@6*ceH|M zcQNSf!uZ3WrYB_kDJc6xLKWK??V!vIk&S0yU~mSP$e=6?iJM9W1_n2<7-)$r>V=uJK#Y-NW!QHU~%kZ3W*voQp%&GLNIw#MStutMLKQatP2_hYZl9-07lX z09p|NUD^T7mY_@tIs_gxA!^~#yBiW(|4Z>_N`B-_*$v8F%|BU6c|T9+hH*+hcQ=FL zn_qy9`5<`pP4fe0k6soB59R~>f-Q%^Iak0>fM2lZFgWK5xG8|dK{?l>v*$3VVdv4? z4JxbtH-jtyl~~e_J8H3)Sg)l#x;b5q4>?6Cf!8RGmOd2hd(RNJ;>e2@qLO zs~u80_`~x*=%^~l5&=+K9g_b+!{U(5x}YQ5AbQs^!16U{-yFo`!wj(e4_Y1#Q3qNc z4Jju;gVzvspe@tp;FcaJTp@OV$^?jxT6l@k1TQgq;UxxWQ8c8C+0F=C7yvqi7P2&9 z2WaI2YHNYB*YO{y5{U*K>cIg@WuQb@!u0=wM{hS|5C6#$PLFO*Xk7JngH~*S+5kv` z-Mc~A(xZ1WXeUP37H7W)kz0MULmd*vG;-IYuLLR*y8K^FTwPZj=0>tZ}?T`=_Xssb+ z{bvto%RM-tLB}&e)**myCW5d)C&@rqpku8ddeIwNtFZ6(ZLZE>ERl5Fu^PLyM|U?U zwjmyZITe4S3w4ptPVj;m#3CP1`h++YRAzBNnN9*?6KN6PfQ-H^HnrFiI$QAq%YS^#OW z8vstxh}Z>P#3BVAq6RHJgOq5X@efEg1YO+@Ng1GJe~|JMG}Q=6H=u=S5OtvT7=(qM z9@b!AMbuoK!32tdwb-Shi5ilU!QNCLEpc#x6EPxjATlo8f&V9xP#VJA0O}z@5#0T zdxmSL_Y_Bd0p|`M=weEKO&=8peoYq@1AfgIl>iWt03r(bHRph@T;kV+*a5N=v^pBJ zBU%BH9~zHf_K8sC6c3Lv+;TR~_0cy#XtWjD|6 z519V2W2xWP|gg1ov71#pi~o7@&&kpo8l$5kR*h`2u?$8?^-M%jz|6h8| zy5sTx|NonRJcit$2)f+B0;ZY21(a=fgLpppa-Zh1YL>& zjslPFYoLZ_0XQmJ50v_W4fD_jDeiPpu_!(C+8QDRY8t=R2bo|1-fixpVu5J_xKY@8 zz?0wgyho>xiUHJ*C7vGLuAn9LB@RB_9s-~UGWX~Xy#YGj8SJNvE|z;i-MSJ5kM6l3 z4}iiE>n27Q%i0SivL4;BCvYpV*b542uvwtfpl~V#dCcIY@W226L6sn+0|Po6)kzYR z3_;CINaY4PT^+JV1$1a0q+_y$fq`KmIJttFln`~GU44)f20?3Kmw;tKO+W~Xn+die z6ttraq7HQF2ShJu1uR4?9Ig(uRvsb-N(L_7Au0ww{OdzhG(d6T(;1?o0V-S>KwAUTi(s4%@cG=JT0wpRH{Cc1fc7`g2 zQn_vyl>(RU^S<53z~^*>E(?InAie|*6NB!G()&;)RVv}q?E_K>xlRD80<_!;G;R~n zoug9V$-n+x^Ls}A7SMUyU`sSztN^?0XZ}dApNubk=GQsq(fXFZ1+-||hkyS8&*Pxcg$90khMoWa z|F8Gxjb`!eKH$N>p5>ay!5m%t~$f^$yue-8d8(4JzCUibeVy)4Zhz5f3_dU^UhtUr`<0|MXawE>v{CDO!LtG1-2QRrJwl)LE2b6 zn%^^)-UPQfKshG@oN4$qYE%?JIVb)wYUc`)91+cL)WI@HqDBmsfkMo_29#YutrUJR zONEhvVGB5+gYJceWGmE!X#lDW6!i=;%78Pjf1nIdf5F{642V{~rL=MqY^8 z$mvgq*Iys&<3$HS8HGk+gfnD5K(2HI^|pTrNK2cLj$CEMQ=D< zM+q5}hQqa@OLPl}08nw@fTcLF$DZM##eoBMS=8d-m?QuG1DM4@J7^1D^KTCRCeTfl z@ZtbG#n9{b-vd-6FrW75WpPK82B32;Jh~5qDpLcG<_AC0`18SSHAsn&4=z%6f{F=H z@$evxKOelJJdMBJMMVMB?o>$Q&wB}p2#^Y%a8QOnlg9t(>?i($gEv0&7o50}#-D#Y zjlbaH4UmdkY5W$J_tN++Y!83pFE{{G0hYZDn(SZzsqzWuNaIhx1UD?rkw5QX8vmaQ zU{llh>qLymGdlee+^97Ex)38459W&=)))Aj?Ee4%52`;5Kn0P5EC2ojjyoIv|NoCT z5SV{IOSmKd`U3}laPaRxu>*8$jP(hApM#Fp2Oar+4ze_Kuoywhq${9gmH;Y~z^yq4 zkLDu*)Gw1jLqa#fbrR@mQ$KJ$1v*XK2FwEOOnwBGozKX?0AYDFzv1B5xa`w?k^k5w z2JivTpq80O>m~jc(EV=wdf?_mRefo#hxIA`mPL?lz8=~aJ)3{96uJ2JM*Q(;KFH#E z@HLa?|HFR0JfPkn;`;M$7p^~@j(@sY1UnhKUAR~}9a*|vxHvjNXFYogc!G|!zvF9t zg1;HG-O;<3r^{FSAXu?SuO|y=immjcNAnSm!>*v)cMkBkD1k1BZ~h@rRPWjA0IJWL zJbOLd6KoUPd4WrV1zSbv-<-L1ZKq2MP>%aoh z?85X1r2G#!$XzIeZjH$C1XhjnDhgaCSX#^vH2%sX^Km?i;Bi)et{Sj z17H62hrk_OYKwtSZ;S`G-(5kgw7@J-FC`Yt0?jzbfLWl~rD!k<>>>@%?gOsf2YvY0 z9{{@xv`PYE3{Gi?IXI;u7UGnS!*4glY2a&vgBckZLc<(GJeuDKfGRx$&+ZqXL8Jhm z?nj`BB@2)4kFMPpjsN>}=BPOMbe5=a`1BUC_;lK+`1RU=Hj*%Se!uV4%K~QlXdm?K zjo^S3ovy~W9h-l#@Hc^O)pqQ4`s3Ms<=_h@5AAFJ4}fkd=sw}nD+B6qFnIK`fa%Xp zpacwRnY(cP@#ysw_{=Zp_y#2E!}tM|iUdK)%ku+B#Dx)*u3Wf4NzC!fXMO=6Nw5+w zP(t(k;o1C>v2?ww^{pcQ*Cwv5mrBfy4|sN8@zB2J!F=4c`;@EpX^&o>w$G0I0xY1` zJ_{Epw}R~Z1J?Qs<|R+&U!KhOeV9M^G9UM7e#^+;sscV}+eXEw8x$Del^NYNDi)y9 z5!AyKaEytIJ?ztc5nK|1PTK^RM28qahYQ?>mOA`RAP@WYdVoqECeMQ}nLPg=^y`%Y z?a^TXH52*wyKw#U==J0P)rkDj2R`#h9{$X)b0DqL@f%3)2d4NBkoYf5@n0bEKbYbW zk52QozEP~<-OCf@t9_V%KR6tiFctp;=gRwsL1~1;qx+UmH|U-={+6Af>KRnhg0`!< z8ej6!J_RaO`I|tKi{O&={~>t6Dgr8Xz}|!38^XVhiGQ0HqepKL&%uWr+$Z?AIei8* z4n72V{UE~YC%O+Fe8J(=;llN~`{2Qs5{?}{f?p555CDmM;os);oqwCx53pALZBF07 z3>;=ScDM+BgPZZ=;0pne$ans2PQUoKdHn`!<=^J?6U@M4#!t8zzoBOQ;@{@Hb z66m%zcz7{`jV5FVD0JXmj86au)E^J`JtEKJ({;D==^u%z;1p z1b^hI&-~Hgdk8jwBsmVC3W7RaE{qpY1uuY9GCn{ReBf(+v1mS|lDoj)3>q~DS8|s< zwGV+RIsPWlnh{@6Md!`pt9{C=muI49^GgVBZ8gA_;0K=G;K-ODrCR~yvvWf26GIVhST zmEU7<p#c_wA|d`_!}gJ zULtfj{s9U9#ZdhhB>WFU_#e2OoCPXA)V+Iogh9o}HgNI5h@tjBxb%2%7_ZFw`#+!VMmBJRFAw~mGYDQi%0$Mu=xwQw}>^s8%s_8tM-$WqK3xQ`PYE(Qtx<7S8 zFSzY~>6v^GG&2jD06*@c0vaU&9hL)H+yJ6=K-*tHS80OgrH;F(n1E>h^))RDp8V^7 zfaZ~WKwb<0RhAAA-+)SVq!yH?^*@j9`*3gF^XWbe_10?-=FcA5zaido0Qo2Y5pl7Hh2W=&fF@ZXJ_S`bkog9X6JU!9K#LpsTR`UuLXU*xmuCPU3MmOW z6cV=Z1AJmlDR|+>>HkOzKah`I^yvm23R&V0nj}$yoChiCq5We5|27f!mP;k_7^gvk zMw+`pOZ~tr?LntHz677@h$aqF0+|_&KMWevg3NM&)^$Q=Qo#NJt#TBAu9Wx*_Y3G+ z50HP-nr$K(O22vhKkU(KB8}oHHL$17A$#f=(#bOrPn8ORX7NBy?FJ=9h=XT13ni#{xE1?FT}^7<|M?&{2C`dK}SJ9@y8qlw;VwiPRN1#KcE{N zwt?>~0EdiE_rdN%;HxM)85}z!SU?tWICgq)ICci`pspS=a@_IyB-W`M#~n|wOEgzo zFqCq1JBwI43zV>d*C4BOJF_(ZU@B2;{=r%z)6EQGi|zzrrPucY(O5WONF&G8i&;WzN9B0FkwUFW~ZMepAuiq5_(m15FSKGJ-Gu0o@z~y6^{d zW00UD_!j7d<1XMCYX;D2iUiQ*L7k2~-7PAh3aismpu0r{H2B%+DB{t5uv5^b(~-rc z)04xa`AEfKj3wIO%Y@)&J0=`=1TC*)Kn+Vt{6QuF39?cSNXm{r3|?kUOn`t&N>9(@ zpdtv=r7f{-*a@1nU?|b^Xg-)xq6fYOxb;AZBy8$ez_|FbjKh3VU=03s|~al=5`Di&(k~@V9{0r-QDw>-A9y0GE)i#+N|G z3WCPKJ4D6dHS7pj&;m3M zpKcD&yt0E&FXYZ{&+b#8>3;>s{})>il)89yLrnBw7WU|MVh0T)LD#p=Q2~uMdo&&a z1!MeSa5#f5+Cdcs9bbkast8{@rNO|!APQb}1e#ibWCGACW(P^o5Iv{~WdUY^ZuxQq zvsf7!7)rpiVc?}94xk8Zy;REC>jYX0=n#F_1GMcM6ptY)kf>-qP|5}Jy#mDH%|{%f z534XRFhHiO!KNtqc3%J+bdtXXwCEAs&H&A)HSE*^UEx}i?$LZufxiWGxj=Uy2dJSB zS|i}m?Wh0>L@^goiaYJmeE}Rhuv898ya}$xw_7ijM1qzjgt&GeHildt*1_!g|CGo7 z!;b$CyI9{UdWft=w>yvrRGva~%7b)D5!MM#+mI{;TA2f>2SMlRdVzxtv^EgJ0GWg}sqZU+sJv$^0I=z;M8XkI5Nj98Gu2)vMz>S4q&fMH~S8Ac!xVoNFu zP@ED~Qst;X)+K-{dk2s1U!L7Bz`4_-`y)81ftFZW64Cytg3bm11@_&Z)gEuD`f#PQy*lem`}I61jwg~5HlVB zU+6yeS`pidpzdR@IghpKGBPkQLYfn(S5i8}9|rAofaF2YL=B|c2jzW8wGUeA3Rx%u z%7+kH(1HfY!VZVva7R!P4yp@5jb_lC+)v-u1Em6vy$=688jpY^9OL7o4}&fU1MU5T zt$2V2-+6fOfi}wdbi1f1fObi)Q32g}=GlGTqjQT2=)5?e&MhjSv!;DIcZ0^-!MeKH zjlXp=ce}H6IYjiqGbo*Pho-E~sl;fqW zF5TZ=i#vi2-(YAxS;_$sE@8DS4=9!F&Sz=<%TyxP{Fk*vuv;I*<^r)VL09OMpDi-|ED#im;{6~P*gAxg7aawb=0b>dKv1U+_ z#^~Dmt;7dfsUVCs@ac8VKsHtoY$T}q@=@`CnC+s1F!mz@1H-XqP?gNczil$e;Fe3J z$_^HrK|-Zc-Oel?z2H^Xmdp+vhWt|xbTn%+GBCWBf(dJvFdt)Nw9IA!ISeGl=wM-3 zD(hge8N?|SX}MG?05gvLSTpG86h;S&%^d8deV`sr%cW9QaE$GcVPIhB zGy(7JfTRtNZb)+R>4u~cgH9^}ul#lh4R-`B_iKJrf!dez0NuX|Nyw1pORpfE z=u(be$A2$DhoN{RJIa8{i~>+sBEzRMMI`|=vKi6*gO$G>w9+2Bkp_8hobiF~6QEP- znLWEXe7axwf^Jau?bi3SKI_Bp^b*`K0xd>#><;4bO!i^{Ew%!63qkegaTgWP%1}_Z z5p-Y*h;{&V7mvHBfDZCw09_X4#=kzK$H1lgkuU%Hr=Z(J^T9n}r2e9h^)t`zJ09Iv zJ-W|(@Vg`R7k_&&zxL67TzdDl71)&SSDyUq--1llf*1mx^Y*ZQ=h1x^s@eDwqPzLq zL;H2kpt_SNF6&jms!EK%HtDr@Y3ZC7kzz%x}@`+=^F3?^HhSEsK zhFzdE%24X>+3N+`s!+-fZfd-)0^L8&3JSFMu;zoRPj`R>sBV+-=oa+tJ_l-o`*a@# z8Lt5v-%tRp!gK<+6hX@)QSE)L>4D~w*Wwr=tULaKhn-VYATw~F{#}7jcaBN~XlTF# zX=tDVyyP_YFuy#w^MH-ugO415wpBs)Yo;+UFxY_iW`dTnL0VCujV_RU3mWEyEExpl zOo&(z69a=gI4^^?KD&ZhpffW)z^r$m8)LyN(CL|wF$vI?W?QfwpfMCXFv|e61sTjT z0c{=zvq0ON9l9e3#GkBU7di)7EJ#;%OST;8>Fo2i$ z%V1aI((SF`V%ZI96PL(3?vTZ!s(Ug>Rkyc_i}l4ab;liY*wum*fDDBwcQwB4(;1`U z0Xp=O!=sxGw1U>dxBE4yO~UNct?!ZStYCb}r89PdZ};!xu2Vo|j&JwpIsv2~su`*is>9>^N8?LSZ6K>FkoCHO z^6Hz*)MMb8b7PS*yPPTvX0M!E2>59yuY*nP_5`$vyX*AAcV z1HPaYmAAo1dL(Cn#;dwRR5HN#6MqDa>{xmvGlNXQXFb%9-8HQf_}5o+c=E6R@4@^F z)QBhm8DZs-?94)}86_Yyia{mNHxK4dp513XwaO%ecH48Fj%Nz zH@M&^;qkOS;K}cP0xam$?W5x1!oRHW(UYZht>ln37`VPqPrQiUefrI2mktm-QA!~R36_y`dFXv;deg)wgTiB8&I+` z@Al^K=|0}=EYTed8qqxI(#;Ji#eF~>d*2P<%mFstqqla$Yj#idpF24FnI90`~#m+{uVTg0QD1p3%IQbDMk5PK*N|Izkybd zTYxXoMVMM~OZtr)YHt^MITKTKjAPI+D8koJ%)2 z1wuR$qN30p45^KLm}^uVe3(mAEPSkMR6NQg9e2#fo-uu`zk*~JV3+l1u2HdIC{gyc z{#q>O(;eI3YJAeC+qL7R%isV1AqBrOr23Ent)Kueij4rdmD#7$4ixOnKAq;E{z5Of zBX5~SSILH!MgEGSwavY>{+Qn2h3_`n?Kf>lVh06MB|Hn`~) z%)-C`5d$5H2GLu=!oV;YthW|^9~nR|eS$T~w*Ks~!sWyc+iXF$mn zqv!{9i!4C-#=zD1n`8Gea71}@v-x&E1C1gu`*bUMS|9Q0+@sQ=z`)?c@ASaMVlyaX zm#Vrz8}Kq7z1|AG-DfMkmvbLc=#%N%f#-tEH^!N1;H z16-DZc5-yz0QFoQJ(9UyI!ja{j-wff%b@O(o(TT+#h`NYyC?I9*UX;X-#{tV2~uv7 zV3JFBNlSza|N3GcU;g#qeVD&D>;?rE1AhzX0372>$6ZuF3l%|L0DBeYZ_wUS2H)-@ zFcWx zvxH;!C6{h)5B~M%Aq4`sWb;vh3{1WjGQI>F&n$2Ry8|41P!qvBczn9ggW}escWMDY z14F5=XZN+&9uNhLC322C{BfnJ7!?P{?%z;-p4~@4rg&-}_vt?7VSU_#-|dG_H>`JF z8U!uR^g!)Q&F)}Ou?MoQ+gSpJElhnjYO}AxY~R zM&!J%0OcIe%poXVi0=K=v>5QOujcXKU;hu3{ft4K)(FQvpjlGTyrQS|AMj)%XoB<( zsD*`?OVs{d`p~C41=L5V0reYlR5HN*28f?J%|LUf0U$5_FJboVGzX;#w4OxVVQzR| z0kj(%5=o$0IYpyv@D4W*pYC7K<+d6)BI#21Ay5m6#j~45#i#p|Z|mC<7T@kt22bl_ zKH!-8#@_-;fZg6apcnur1yDMO=nPQ-?ZG8#h8Gn5w*2dTS`@l}dP3tOeJ>;y7(A^1 zdUW6S?7r^Nea?g5{Vt-5{n>~4H+V)D)I&$ntOeE#St|w657mquiQw7bhoBub8K68J z;nNMCtS$jXXN*b#*xSCHCMuw421g~x>z$^#KC>4UcVhKK-t{tGBSOheJ>#-M|C_S+TTxwJ- zn?XG+&{p6^>}p)P!96vX?qCg9>r+K4jyswND+Dclt5NZA?Y(<%z9Mqj@>6g?SW)(@My1#3alpw?h!&wLF<_dnmVne1BhC!eu9yq|Ht;yBZ}(we?IWPRy@PAZ z$r1q{YZsLQAAWZb*$LX6+8v@&f#|mxcyu51us&7B?a_S-lmZPrx(`9>a0ZX=3nc=c z)`viSx>KIrmq6V&14N&!^?!+#Cv=9v5Y`jZ@a*;%0QFGE`a94C+67@~=Mus#QRJn@(_>1=d$9kETu|G@)!ptAs>?&NRb1|0|q>s^7)5cKR82X(FtVD5(Yt-6nUz`9p1@b1-j z&}OUV8kGcwk_1Sz1lpAYbpj1w{V{%*f1m~pQhT=*)c$~IC{YJBVY-idfVyM`ur8Um z0?0R@;796@r9k>)20qp)kp9?j?0FR0ANzw{78HU83?(X{{+PT^cZ`aME2wo7+VIj2 z-XT+hbjYMZ4WkT?Zr2XblqR!JrvfBncbbvi9|O(iK&nd6c~p?9G8f(!0xkW7$b$Oe zkp38GQx2peT?y~4ftoLKz-=MWLNQ^p-PMY|i2)K{h>cad zK}&bRLzxbt!H(d5r~{}UdH`2Hv;o`?ZDmkkU@*Q7no!%J0P2Z;^yu89vO$4?!MFQ^ zPv;Vq6$+p+%AcSP-~t8E-Dy550iZGfJUAHv8tgCt)r)eDJIb*KCdiuxpmg2oqT&Iq zo!CGoc(@wBg(T)~4OinQuAs*2TmF{w;2YZZsDRq?;E4gBW*p2;9nmP>U-YtV7~R5$@n%XQ@VO2yMv|;K)DKJDcErY%_;$z zRSq4yD^Uq(*bnV}f(~*6_dZd~0|x@kP+x6O?-Q$ML6xpZ1}L~4Ahm1v2_Nf&WgMXT z!oj8cV8ed!Fra7mg%W z49!9OEuf{Sl#LU0|>CuOSu9g5#;DRnYgXjQVx&Sd5bj2B@ zPO^sANxtwJD+E497zvlXf~b>PPnHNlm-du08y`3fTHRC12Cm;BXQZ_rC;_eU@c@m4 zA+7NN*B6IDr7TOY!1=a!< z0@aWP9=(wnpso4*pbEzT>=LjfXhog=!Qz*n`6Hp{bJwUSeD>hi`PurvR0yoN*Zm); zuLs)nt8v()8*-7PrB8Q&0%(fSz@uB(apwzWP~G7z;L?5E@&BdP1Eq4E)e2x;$DLIm zlAwjGAQyuD2$lky4t52oFJB5?M+)Ah0_p&RTn$-U3k?O(y0Gq_pm}nGl1z`@!VFOM zNbrE(9Ul%mn8nYd+fM*AtlBN+`2S+_KG4`PLy3h)_cl=b1*As@RNM)BfLeX62S5kQ z343(oT#D<_>keL8X5C!jz))i9(OqCcvKbBgLCbF#K-tHj#L=UBKWGIDs5M~w!`!kM3X&P}yJrIv&9? zM8&{^-|3u3CqJl33tHTau)`q?)P%DDpOXn*56T5{L-P@f_&Bf=4WP>kLD!ptRwE)D zQDO+PF#>EOe=BH>EqLWscZ^DehxLtO&{4L`9@;lNy4iiY0|b0qFO>>{g3(E7(7K@N1oZE$Y#IxcM=o z$Hf;O+=oFzCqDB>oa}Z{i2$$UjZrZG2_69LK94)`nO~6417aa0CxTX+Lj^$TULrIM zwxYxUv`_3NxHbF92Xsd3r4pu2CQ#yGcI*V5P*B3`*vSZ50RyTGK*camjYoX+VNjvrVEnDq2*fY|7lYuCC;^{h3|0ZNiyLB>i;4kg9Rw(SgDn#T zC2j{;N&)NW4N-xn?mUlfW@r$WTnAa90S_(kx*i2kx~%Z%J`D*fdvHboC!dp$Er;a z#brIZ8Nm_aaokA&H0J1W+(`jUcLr%dmv)zW|F$r;Cb;M>lA%ivU=SfDdTZWuOMXAmb;{3VH>8K~I5C z{DO|4s>lLd8VG;}PbDCkrST2u2rge(-_NJ}r*G>?{ua<(w4lX@pwnhOyCKVdLHVP| zquUo$ca~&=ix5bOzzNC$FG1VPjBkS?(E^kmH9%{!L5mPU@n`@N0rl=cGrgdT3z}v&gn(hf82RV2`r)fZIMEP4m7w&>m z2)KH*1?5Au1*o{Ix^D38Bb@s{3sAd{z0Lrw9O?~Gaq#IrjBH1VIVel(fs7XQ0JTM1 z4}i`pgRE`sKK2@VdN}I&aoxvWOE?~H1D%Nl>cM$*yJ~oLwt==VfU1Bk;9+;r+039Q z^z4q-@a)bu@a%pJE~E~FHeo;tiAC_y2++91#tH@o76#BcxsXu-(4hJzuow>`1H%(3 zkgOfN>hocQU6u<<3Wj1J+1YT}m5i`!Zb9uSNUoU(cHc=*{^B$~@PU7$mqYi7?t|dm z!{gK42$BXBMbMUA_aVo22M5n?4v%g}4bU;njE6nDPk8))0LloNpmYP<0R^gG!TqV` z-JlR*0JREcM}Yb;t+z`|K_M#x3t4r@3SUsE4BF1CfZQrT-Ux+Ia9E6if#Ctz^Pt14 zAZ-EAc`J~5-ys;Z!5TCJ0$#Zb3UJhEkOJekpt)%WU(nX;M;_fDyYISme|72p3)-K+ z?AfUa9)0w!C;nDrCgxRC} zZ}(mB!avaRQBal*@azU%0S^vd&_qQ*_YEKBub}yE(3wNcKi>1Vf{qwAzHJ$!Vo>7e z(fYQO-|_!(N6_JH?VvkOUNeJESnOu^==PWJ>2}}%?=}Tze`|jM{`T#lra*T%2k20X z?(?9P*Wgp{LF*P?yLoi`g9b@T*gQJ@G+xVjbh}%CFCgy&YdZh`f(PhM@a_|!(U4kj zP=U=a$?@n8bT98=8D{{x3qH=GM9jDQC^$yWdUSsU zN6KpnkM0jJ(Q^su&C4YX=*TfYK(Y1OP2IiHnT}Cu~R$5Tq*#JO&0@h70Yn zF)=Xol!J1$HGHnNmWhD@q7HQJ4urLY2{v;C%97x=R){0$cyzm&(Lxl-YONr`Lg|B`%Y^SLAnRFErxTdt*kKAWA|Bn&3XoQjC-VXDAdB+}(By&kArI>xWgH&OKmU91yMp$8ma=podoAqI z&Fs;A8nl43`#30jB>Wcu1#}`9Rf}73gHJ)|31$$6o4#nB6fd1>nd<@la=s z3Uc`cDkdczcl2GwnoK}>Rnl=sKMvV$XK+H%^RRR_C>8Orbhaqv^XQaED#SdFgEkj3 zfNBRw&k@w40JoDn*&(}hAUhWzaSvL1)CP`wE%-P9Xu}>v7PR!c6D*6kqXDu%&G-_y zL(R#+z)%Z5qypqCNZ}7^`+@R)DWsw4VI3$?2J4732Xc6Vi*Xx}DiZ(^sT0(?`PPxnEP z6W}#UyQ70gHz$?~1<7Uuh|NeM79f$%7!}yX56y2BzzG_9d?tSj=sZFn>qGoapw+cL zpdq#HKnYLg3m%ci})0GgW8KIdV5&4b_VtcUe^P^#m1 zKkm`#ECKROi3+H)(>~_|T94p%)}zx~!NdA|8J|b%Nw7*+kV*w`QyJW6EGY#gdINB? z7}Q5h!`4SU>tTJaSQDZN6d5HB5cQyf-2##}AV+S2JIsec7RrHAy!Jn6$z8(lVO=dy z&gsE?9luUc4-{Gmm8d|s!5(5@@ByDq3|e-ETuHWox2JTssDNq`pYDsWGnRGFmrfrQP}^6d(?>(YIQU%*F2!I57u_<$q7p!Wv;@ROfB__a=a;*SH5+kE1WJNU_wU(opi zfA|3~<3{VH5+TqC4KHZYu-8WgdVY6z57_ZY#Vp8ohe4)5%2rSgx&R)GF=k+3kOLod z1iB657B~liN-|NfENH)z4VZP7fq|h4+~By&054m+j|7K-wgxFMGcfRL7Tn<1tT^Gq zuj#M@H6R~=n-AcXv>!aWZ-M$D%$}g0?SIfB5Jpf_QT3@9u<(MU;zwj)q=vnkzb?ugCoC2^#fOa4d)A< z-H+qrq7Q?H@*u$u+I)HvTn2%zdr1QaCo6oBg9i!0398x?m_e;F@I=@RXuFLO)UIUq zfb_@>9r*$udzi1 z@Jo7rF5h#c0hM>p8%(GOGa3Pfc9KLBE%s$6uy)P zV?R{@s9&Pt)BOlkiW+!yzXVm4l8!r~zF=>IB)5WExUR%2EL^+4A9qm!-J8PT z*nR)FiwfxeQ_!@X3v}+#1GJ)&MXk~cx?)|{XzyQ8D zW+!NR(%1T5X{C2Bi|{~tZUyKFk;7PK4zWP~9o*d$;^C_}oz|1a$L`v3p`*U4D*gQYw`nsq>$ zK?5J)J_OqreC}?<5Z&kpLY}1!=3gGcqt-1M3BiS3t%!Koi5~ zz*z=#k|3lT{|Y{G@dG|`0ot4hu>-Wr527v!ejGb!svIKL%EZ8M7Hr-;l)ezSc@N&n z-whsmmvr25@jdpe)jJuK4Zt05a8m4b4*(tBAhZ)SCD;u<3pD_8N-*e9mIEa&9+n)X zLLQbL5@3=8)I0PLDA5Ipl$`Rg^ni)%18t%eWCGPppeqW!1VDWPs8JrBT+l%?k4`5J z1XBRPlt8f?q8HK(0}YsI7@veI^-)m(4=RGh4d9|KDi+2EKy57p@X)810K^lZJ<^c) z0rfN>aSLiELE;CrGdc?#qo9>B*~(bjO@L^C^neNy$fXIOD&7DzSJfM$Vgen=`N+t?&{)j^ zR@k{j1$0j-f4eIS1A|L>=2&2C}PB9jErI=OSzA ztrN`T55EoCGY-0Hm0vJ+Lucp~kKRxPe!>Q32h$zyNA-I`9iPEAVUjPT<#cZQvJhcJSz(2g*GBnyw4L z(csek1Jn!e1s@t+ssQSl+kgr&&>SUbtwV{LN4Kyi@*JgaH|lXgA3&px9FF@zBe$hCRA_!RZq* zaSuwr;6?ea#wU-pgC;&1LAAC8s6Y;JvFHa4)A6^cfzuXvRY)mouj9YhhR_uhCCach zj8f1A92TGj9FE|VY8?Mxay35r8ghRKXhiQmBk1tzGcKLJ3ddbRv&Ia5y)0`%9kAoB z;PbIS+XX?JI~gFWXj~0IRr_&Q@EKX4)j_NZNP3}qj=O?&eE)bHqP+XW4p5H_s>_Xk zeG03BM`s`CGD6qxFFyS1Kl)gID0c;qk2^;!i|WuZo$MWN5g(lKrw)3y34^s`B-PV#e6_(C;S8k*-SU6mkyugHUR~UG;ER^ zl$62uxIrrWIiL|qaF5vsGFy295=s!+y1)Pb8y|RC{`dcXaM%20>)-$XL7P1wJtj-| zWPk$$0|TT`1|5V7DU?B%V?&DUIQSV($?(A;Q0|0GDg0-I9YJFVuPQ-TBtVY8h-ZS` zlhO&7oet`{p_~OK*2)5#T-Xg4I|&zKVTGv^Vuh)Lc1)1Z z9Z~S;ZUmLd;J)$=@HMd?JuDj~m>3v}B^`Hc{(z%e_+P>f9Zd7+?gJU%(cK2pzwIj2bCBz%3a>q<{|Yfvn_*H1r{xWgNhhb}v9XE8ury-u{TyJD|<(l8!s> zV3+mi?uNK~G1!BkRzG+_3Fx9CQ1QoV{NJ9w-%u%Ujwv047B>@E2x8_;GzB5t5?L@)A}o@x-RhSKI6#m{M7~4c>rBw)BJ<6lm)az z$%FZ(^|>O&)&nJc9@ck@_PiF`4|4iz=yh)0C&1N5_YcqlF3=zVXoJ52$md+1opP^P z_k-Jd$1(i_x_=Ln^*~nyKr$w1ED4e^L8$>E3%VlUAUK79?zD%9WGPZYCxbYJxNehXFFvH1s6sV-;*{r4Llt(ScG zJr8+wPXwhbP})D`seQu7`j7{|-$73#6Ct)*U+~~}JLG{72MwNsdXbPNO^%>2g$~Ao zI&Y70CFf>Pa`x!%23ZQ4q%i|cC7Stk2WWuSuZV+ZazIr8I3t1<(lj3dkCR_?=|1lP zI)UvN=%|NaP(N85yjl-avGMKZ|BR)qF5Tx}^MN+LF}QS}w*KM5?{Wfsz11~Hf!una z6fITU0L?PPFHQ#As|GKSL8pj8;vIB_93*Bz=Y?v6V-|Fd93*DX!S6i=7kHp0917sZ zF6fL%P{{noRjPp-yMJ)VcDIAm0c^#I0c3O-eOVSwEY3z~I@ zj~7pEfIzDj)VLJjT0~vHlHKHz`!639{2>^XOQ60GsFetF7UWcE1j3|;6Ft>Mv`o#4?~oZ-`1UEtGMT;b7p7}Tx@#dNI) zxI=uKzXh~l1C(>!Jen;SN<}@ID77)rFkg8VJuA|6?y9NZUX z2K9wOOL4pfUT48Os?Lc1uQQ_m>nve>;58`Ug61FKYQZb2SwYQW0zF=4enD>m_^@6K zXjfkjYzzciFQQ-j134TMbVMp7m!~tpcF}^udMh{^fm*hZ9x&*jMo14BwB{Ys0|u?o zfXITD(?DdS;r(Ou`1fdja?CPG`vJTA;v$#J*1_sOtt9_vmDX2Ch%1GlvH>l{of# z{PXE_mOxG;KAp}6KAp}M9-Yn!P%R#v&KU@1flsG%1yYi^RLatQ@O3IE$yoYWGL#DW zSUO06NsdxBA4>;;5+#sG$t@pC2bjn?a5@2xneYpG3A|SE>12Z2fNP+{vHL*lffC2= z6E7cvMjl{(1Z__NuPKKJU{5e810jVD^MVb2eGxK2A+683%enS2h<1(2Nw#U$|3^H0&O;m1hYW*nnMzg z8zXFRB@mu?vKSc{AhOkPSR184tdqLF(XvDg~qx&rAU~SOw zziY1}Bc!GTEv_gPhp>@qO~|RNt+z|qyAQwQ1v{i2l(<2ud^;$)zx?s{|9{k$FC=** zTE3tY(jlEr(21&$BEPy%$RK4)yI)B^|bmBN%C1fEBXc?pN0q}@E z*w^8=A^w$wdN)#_6Xf0S1E96okk#2B?}5F>FAxH{65bK&tw0BU!N>s6McX$VJDqQM zbRPs=seOZAFcNC-Oi)mP?ww{}U^oma`@s|Kuq`IHJiv2ICCpiBX|WnL^Ei4I%qLC_+(D-mL!lSj_yWKBNno%p0m3VH0$8l$pBhU!P(siu4g(K zLA$vFd_jE*Pyz1I+X$Lj0MF5auif1NI&lnqwV^^unMd;h1<3eLcL4ZgX7D8l-5wI4 zKA{x&^y-VA-H4sR3gDg*Xx9~J6HDvuk{nm#ORbkmQYV0h>Ec~MXY7Lm%&~*nJUS~M-)pFX$EKkD`emfwEY-zf;DKI8p3h_E%gC+ z%0Mfd!KZeErUhV8%ijW?2JiOt01cD1NPq^qJ3$xl64-UY3mKLJHPquEgK>}@7yPZD zE7&}`y&>j7wplX5wqTqBjkAE49D_HkAZ@{b&QyV>>s-1ggW}JnJ4PkL)%q4_s~e;< z3=VMz(9#^&*4rfspu3u)L5o{Kz4eknSL@3q{>GO)yAOHtuRrMd{WECV{)S`sVaM;E zz^Be9+hWU^%nL|HD4rry*mfP)~v8_`%ztp!^*(K?^Wp z9MAw8cmfjS{q7?koe>-!of!fiodpu0rBw?U!Aq-Ncy_!~|5VXX{6STx95VXXn612nzoE@Ocr;ry;L6%behb*6( zqXL@80L^Fx@N33^R!OCRR!Oaau9DgUKJlJkvj=i9g60(H@~S=H6Vx)d93G2eQ%@vtRy=zXhDRK%2fmMHsBd1#dPod-QgLTh1um z^Wp@L&gu-0&g=?sx&x(QP`PXXI!qsQ5VKEr57;d}-7PAhv#CMH^@FYxDK!GkWgCII z0UDq|8fCaWpiB0^9chnFM?|mNQQ|fDeh<)bp0M7$B|3dUyw?3*Nz*3U2|P zV`N|m1GghVwo8P8&gyP{6977VuKU+MP)Y;&PSSCQ748ueP%Z)On38ndVU1l?w|hbB zfs#1T2rREh_hk?Gc9iD&0;W<7A!bY=7BnGHM^cOlbV1nxQ2sjY`2RZ83DKVuETCI|I$KnxfDe^C=g|#O z?$Ox-=^pkrgHtPb!$dDs4BYGPjDenWQc@0e&)3ITCqi67{YlVq^1&IPjX$O0-R&UH zdi+1?Vf})?iTD5i|KJ#gsdM~)y}7=C0d&Sz1!(nbL5UHhtm%gKT$_&sIL3oES$2bt zr|B#X0F~a(9vW*L@6h zYX#V62nT|e@`9^9@W3J$Z2ayulktC;>h9y9`#`}SLLHTc^vMvT(x5pA$bbZBD4}OLpLxBw8u0E%sS5i+h2YSJ{1XC&slvG9r22n7pkdc8w1k5@Fzd8OSBW#irl&>Lr!$5sJusYD{9*Fz8 zm>3u!?gQ;~fVl566KrG^bb1nGWEHYwGQy)9(i8)aXJ5mf_@POWQ0@x=)w~{{Ea<@B z0?KrVEcg^X3xc*OL$V-fhc+o$FbuIscoPspC zL0b<%b2;#}WZ=ZL6K~?eS?+?D2Y@=c{GiR5poVP%xJ`5zln#7UB061E0x-(qP8XFB zXff>34LX0d(?`X^_#`N{1JKg|xUYwl21*q{S|Y&F1S{2@QDY0q6{r&?kZ1$-Js`n{ zI;#W;T*Ryr=!Tf(;34u-cocySw}2D^pg|%?Aplxn4^an7sRE(kJ@SxY@DJdH0I=z@ z9k_z{8)#yL%m@$%1->9G@H4=H9~XPLn+;SGg8Ib?u;6!tRfC-_Dk(_8p8(qI09slJ z3jYN3@J|4VB8R^eDEte+;on&u;L+U%tARlCt}!Zb7oe8xbHM?LDA_?5?m!>NOY`&=NJ!gQyfb({3b>-`~bKa&H!3p4{C-d zfSTbMpk{ahs2T1N09t(yYKCL1w8zy9?_8q-y4w`A#2nN#1Fh1Y0PY4r+S#Dh+n{?Z z;O%Yjx^B>hSDX!Zv<5r=_Bm+zG=KO3&)!Z@oeXM&IPwcRLI<@yI~^f|+OD8eg23%$ zpUz+hSlb+F(j=I}rx&#KfdSEy2Td6>_;d!NO`ilC_;dz?rcVldI-?UjIIbis2ThSdn)&MRW|7v2c~t?fkW1dJBTpdF%wi{&-o(u8oa3{eLz z5ds7}Iz2c-gFSjPc6cPH4Aolc7uwA6`-#94AAx_P`?~hAW&=T4d^~QO)%jD z>Xe6oI_0qq{Fh-L6NVovlt+ zL?z=2sbfG@yenD_1Cjzwfy4V&{H-#e^{0@I4yY|w9q{rGXenv)Una7gdX$lYfmTih zbp=56p#!8oEDm@Hn%{J6{>x03Q>AF=R8VIc?o^-7;((W+W0)PA|FZD6f(G4)P5^A6 z#>VR++5~B-65NgOO29e5_<&Dmas+5m@i9=^fGnnejB|Mpq>u-%HUv$knu7*##eKR1 zLF=+gG+m8Pg6e|q3!v3k(hweKBm=s-we>(L8)SkJ8X`D4RzBUOCqRqi1sE9^d_jHh z_uwVswHJ)Pxq^nmK$#eFV6$hp4d^g@{ua;~rykAr4E!xyKs_#qH*n@SP|L0)9u~|V zSTh-<>DCKwg?GB9c!KT_faE9#^oAUGE*GgG$KL{KqCovfxI+c%&6kRJ{67XhKJWv7 zliL6P|M!E2sh~y>9UVTP=+J>hhaDsWJRs3w0E&GNkIw1<YfJ=OMp_568MN)aH#o$_6355E%665xuscY z0n|s%uz7WmmpnS113(ih0kF{#Q0VfvWP%bme1-wKuJ9y(%T(}kU)KQeVHXvk%aXbs zAp6y-!8$H_cAxlvq5Jqt&>ka??q+JGe@ME5jTwRh4z!%iryCY>3&XR|x6$|* z85kg~Fwp7nkRG2Ee5}kKK9T`iAOz8o&IlV1DT0r<)xbyG?!m`Hp2AIe1vdq>%?aWH z(E18UA5oSGHf{%+V1{(rLE8l&ZULP<0_k6ZG8v@zIfn`6=DkcXx14~R0=gLxVhZRI zGKeYG%&^fv2l%;_?#v7fTfkwE$_z85fEnhNDrVRS&n~#Sd+>8GpTf<11vgKNg@FOG zx)ZcP1~O&t20!x>bowR4A3^XlFPFk)*Ra6cw-GLTh6U!T>nt!=J!XNqN{baXW@^X^ z(`&{G8+Xcu%a*`pE8()|SYa-`!3ta3!oe*qo?T4#l;eeUU#{n~0l!Jlc6xd{04w%V09I*7_!T~ecp95xcBnQmonQ(RY;3mI- zoBW4^fdR6@h?$dt0kZ0dgA+DKeGZlJ2jxpi`1#{=>9Y` z`ZZcypnlCB74RWju#U_c6;Rpa(v8u1fi=2(AS%Iq8AVF^G9^s3>mYze89buuvx4mGS8Iy#Y$d z-L&k>c!K&eM|`Y5@HeTF+m{Id^<@lTeHkZ|zKjVd_C0*MOK%|eXNn_U%R`i5?Q?>Z z!`fp=eVP|6kUkA)>IXbYzM6%Bfqy$2=oX;vYiX?qO6=1nq&5H0FJVh-{t;8k?9qLZ zf4d`-NAp3ZwAKTqPx!Zk=n_dJHN5=WotQkD4}l~~pS$pHXL2<@ndaJhphO4KZ%uP- z{-Nj5>%|0G9mnr_%9G#qpbOa4-asaw?h_!jrMG-rPePB;^yGKF2ySE=pKLwAKjk3k zfKSjcDWb!`-$LJ>RxxOv2wP8U8u&9n!bO%_8o=5jZ(9-5^&}J>rV1q0uwMf8XA2P^*XiS6S@*@9sMo@G* z@XIqG#nqsRPf+|J;**XYKz&FD5HymHwB?SX00-wEaOQCW-*AB%-q-lIyD@n*9|LEP z0n0xF*5L*nS3td?1me1-MSnnB%32E#0n75NiUGuM$nWU zqz?$1)Pb0C6K)Eqj|wpb)JKKb%LeaB3c`Dm(oC?P0q7)5h$$^hu-+D^j|wpb)JKK% zp*O-!*#kEPG$IOd3+T#hh$;H;{v)W53NZ!LM}_o9BjNo~(50XdALYUOqwC=P(QEMj z=v}yZkKp}LC3t^ShXvLnv15Vt8bN1^K>DLz@E+$3xa@p*e-w1HG{k-T;r-E5@c!sE zcz;w5-s4n-_c%d)REXY0xNHVo7Su!TY0k;QdhVf|5NE?9q*j|H#|7*Es>SD9He{w$lQ!H^Srp1Jo^-kP;KI<l=~U0$LB+53)iZh?{GjwQHUjAOFLrTe%?H|X3uk8Wr1k;w)imqK=)d;EWZx}FE( zQp9>5&>5tVx(Bq|7$OT=*@NU#(BcLK#0GsM#~o{Mx$qD6F!ktmE&v^-BmmiZ06uUS ze6|62-S%ln5Q79f{y#un`2}$hV&xa;(0Yh-?cf0mI*1s_xdf7gByD3JbbB1=;OpKs zDjpyH|KG7g0LMM%psToCjcqe~T^)=rH)({4FXV z24tTH=v=42uQNca2UZ9$Ft}LEKElYrP$CX$rx}3zaG--j3|zVoygUf%__SUsRdBJG z4LT3L6q}+sAVuAbp54Ep8$C)jL9@i5rW?q7kM3TOJK?AFH68)^2Xu~0bnIc!6{+AI zAqJqs5gb5V@FDaTj^N9RcDsUZ9e%>!Vga(FbBoFb0nq(-Yg871={+hl1Q-}REO&$V zKeUv97d+IcIQS&zs92<>Ar7ja`~oz-1)A*x%}IEIZwdz;ywBhPz9~EfeDi_zA<%L+ z2hbICOrQ~t-fr-QA%6Empi9I-dr=*`uY;5rfG&LisVe|&)jIB?QUanu9x!m?Umvn2 z0dzkDc%MK2`pFMKHZxBKxey$-E}-4r{H^}rX+6+=JKbQ%fp)SucxqqrVFc^&Wd7;V z36=nz>f-5<+zeW2=W2Z0r87n)0d&AT$jLCL!rTYCj1J@!n3K9)S`zrzH-mOZfp*3C zFn<6o7~}-sz5!aQ3|f3;;?v!H12iSt+YJt+Zm?6qTX>E)gB%Z9T7v9y&@Ki~&|Tq6 z|NZ~}vK{2TWYG0+y*?@lKE12KVb;sj2%4P^fQ%)8#udTW=YfOVxBCyw{XW|N33-8k zJ!AI?@EKtq-#_|T|M2Mk;K}dy9kjXEMa96k^=*l(qeZJWC;^*z_WFWuvnw(7=nmun zZ4=Y-=?=00owTe7J+jOIbYz(?_$n;W0FA+G4$to6kSMkO;KA<>I{UIOb50J~>KzCkVD&YeicoYDRi<$ra{|EJ%K=}f6C=fWGbVKt8 z<8IKBK#+$cKuaQ+JvuF1L1*)MwEhR5E#<=Ra=@|sz)P<`u;aWu{`?1buORok$-}Sq z25m8b9LEm2?*qaDZTW{B5ew=CEdk$tHkk=_Z2LrJ7;8T>0|P`BU)?3?xPul|ltU<} z2J(O}$>ndE3re_<0+GK3v``a#b54v(2CNVhfD~eoRjnTJv4HhjU7_@uM8?<}P4RqO! zBY4jm=t>Cio;9ED6OjFB#+Q713mHMV7Jc8EC;$4BKFkMwtdIKgyMKJm?9=_(r@P+6 zqx-XO>;Do%Pta+RkaOHsK@Lpt1mA@LIu;vz%WpU6gbnKt#jGCPpIR-{C{V@@xvN4Pl*c@oufeXyVE5<*@9FXA z?FW|tttU&gq4&d;D1mO-E0KX6B#3dpp5y-upmq7sBa1+bh(W;(Ig$%>n^G)flPV|% zk=hWFjyq`HRB#9ewFtp?uAMC9>~;JHIGn_nRl$5dy^Y|lMBSbdj{h&hCIKv+6H47N$5u2!ql1Dz-HrmFg^1GN6)_h;cSK+= zOgau;E&wTqHiN^ucQ@D~&^jd0^*RmvK*b?=*Ofs@2`CAA`1F=#__ls4N%Vx?$`=8< zmCxU^Tg(S^ej50!LPz+ue4gFwK&yB`Nfg_Wy=VVlKpfe76lpXY6p6&?^JQ3& zY*52K&=HjkpjNX1_?AG>xDRM`FE;1kxjJb!sOANwZ41!OTyu}s|Dc-%-NDBXYr>8n z1~qsbz#B{zqM-{NP)|TcQ2@FW5i&;$Iv5BtGYdLD4>B(gnz@9W^$QwXf}HD{2|p9K z9DXM7dbrqLxY&2N7%TiJ!|p>K2OlVOpY-59co@_G=)UOEDFrGdnjbNiu!HvKG;}gG zKV&Ll8Mg8Ke(19{}A(2i;74sgwh9bsuQ|@+ojZ z4>~qq5W?y{0orB_-ckY%5e@L|E)ZXWuG@q7QX6qT80e26S~O#28RZ z9dhgiIIATfAF%7965$CtX6q0rOC%eg^y~yz>XD#QO~IoZkuwxv4hJ_=K$T(yytUbU zBq926w~LBPw~va0$H9jR9^5B9I=Mmn6?|0CltD_IZVYjd!yyM-nlLahXoAO!KwSrj z7eIS`AYK4n>IA6?D;XFVAS}>9+z=L44|;T;0{O8wLmcG2p#! z4)KTKr^*F@cMBZ`^_v605e?yIgZ5Q|Q+PQ%sempsg(MY_>%iy8fk*j{yQmm|>LJ(e z8!*>8Ha})`Y<|Gx*nPmY`?BM~NAjNCKS2(^gyir8Acwnx4z~lj9dvRq#Jym*!w>iY zyA13oa9IT5La!Kdh(8Ppf5;&^X>f;voQ&?!ArtD5BT+zJfRrnPHq;>w#TM!g;0x>^ zN5>j~rgA_D*!Y`g_el@S+V+x7MTk9{8j`&}mGd zmQ@DmIMo8kajM`+I*;zZpk|kXhxTvCQL2AI$4Y@Lft;rL*QNV7Bq?=6wxNQyzQYex zJ&!n0b=zw(@Eu_22daJm)$0gl^6?%5K2A|_0PTebg(bK{0BXlsfEr&3 zpjIujN2dfh3_RkZ4};EXhWHiH=$VGn9B}MD=-Yk6r~49UfC16g0B!Ryz729At4sHZ z*L*uc!z`dfO%J;8yMWf^dv;&;L~2R!L*o*9+U*Tci4mg$x?@!1FsMj|tcV1SjzR)F z6w?u);?}YGCu1qoYd%-wlVBUI5!PMu>^=>#r1d~4&h`(eN`Tk}>i$FQ0u2~KA{SmJ zLT>T`w^jm;Z+muvuYYw%9R2Bl+-AX6M8i+s1n={Qp8g3Jj8TE#4z3V?Sf2qlYXhpL zAZ`Lxe93Vqs6`0(tPq3+ZtN*ULrU1z+a(;JQ34MS@cFFG zM--wDgZ4f{{0_Qb0b-9KBAh#OU{{$r!n1veN&zU(|MUc1QwbjWfaLlA{4JpI15i%@ z+-nX1=lB05Q9hk1DiM$r?*ST)DN#}I&_3+ZE28abeFT)!3m_@|h)eeYn6tY<`^K4X zw4N+s_prWSbQ~im`*f$ML?CD4fbL`PU4A*B12jv(`&A7E5if7w`x?t8zl-rIWz%OUPXYiDY)|q&!!g8YzpxaDAXZ70-Z<= z@ez0oFp!CXAv7!;>0TeS26%uAsCc>vI+g`=Z3gJ#(M}%~4e+Vk;KPJIGlEuc&H@aXn>0AhP|d%bY%cK8Bfdp5sfEIkAo zCsY8t&DHvT5!Y)QkiX46x_^82ve^0dvNXANUw75M?9nUo%cGmiqc@1dv75oS*MS9Y z#0!KGp8pT}GJp4E{^DzWmcPjybSsZLXrR;yWT>fU_i^9u^B(-`5Bv7AI3cA_h_U?J zLFO{0b+SZyHos&neGWDk8e;oEhj@U81-gAykni0|0A(W3WuTyY%Rxm3q`L&VUn3xD{5!=L#D93@b>BB)#e=*8n2;8O4>I7OWV?Wqh0-P6b5$S>$103sB? zClIY?U|{&nFW@P`uYZug9keHsUmx6);BN<=6vHpT2+qHb{DKUh_ys%#Ko|LW2y{D2 zICeTnbUSi5b~-5VuV*~?fT{Zfs6j8}(d`7jZ$izZTL}4r5FeBaLOePl*KLAtF;@WJ ztpMtL$44LT4pwm70othT(#`JyZODU0F`!3>#Ye*%AsX?~haEx9aPWzKIetOLP7xKv zZQr1K6j{JmeuJa(2I#Itjw&vY8K8Qmgr(C(h37NB0C=^nNB2Q~0Tva0jZ^%BjvD+T zr#|rudRlzu7mRcOpTJiPcRpwsjMJm>AZV9sDQZlYV2XpI9E&(O?j1wHRZ=6;<>Nlx zzkEOk6Q3*KS9V{)eTaB_NIfv+*~6L03=`@&?_P?kfPg zsK!;I+nJ-&RiU#~hJQWt!3RvBVy^XniK0ihJ2(w-d31|AcAxow;kA-SXQ{?+&}A|& zL93!5m7#C<5u^b)k4{(66=0&P$QF92>P zfZ8$+pgYdNlW+>4nGprZ5n!NF9PIyY@TJP2mLI5P48GIL8Z@W|y3-5nTKJt}hyg;> z>##wSET!yVU%d7L&7gorKYY56c|tGv0@+XkzFLLEp}tZL(Dl{MsDgokg#mO(0OTI@4)~bgH2AG!prR0R5Bfv6?0fhqA?O4yh#e~M zQ9@A05%lN<-+T^gv9o^W7xVy^kR=$xkZgk?EC4D%`CB%E<~}=PpbHBihY*2$#n8MT zawWPGs3=-v)Wf}jw`yt~K)Pm$LRI$yByI4Bd9*m-oc)zL|!YRd(rSG`{2tTE`DcAZZg^_}4pv<~TH5m=85q z@-TdM;um0PC^7uZAL%IY*^yt6Wisft;Lncy0xXk2f+8RlJPn`uBRwTP^GBTc?8Gm~ z)9_j!Bqsq<#sgC3DT0s#>jxVPQUf*ArJM2pAxG;A{7qH=|NrlHlrg^a|G@V`1;_3K z9tR&V`7qz;43zO1=fkqK18G=$9cx4%+e+63N0BQzEI_@|d0vhmx)G$cf96`r_OFHg2 zhg}wOd$mV*_X}t?r@PsLhk-%cMaAH?B&a3&TG+$- zV<`tj7-X^y59pd0P-5>cQE`BdaVf+fMqPH3SPUvS5X){rU0}#M7SLiF%@U9-;usy! zIoe0T)xSe1q=g?2y3Ec1w4mey(t;Auc{Pw(V^C{@!Nd57M`tT&!G^2xZCB9I>o0t| zAAygAJz2`iAO7vN5ag=$$ed0W6_d~W0wpRYK9-RZ{4Ft{Z3&<|dBKZHBGXGvz}rxL zR1AzSfeVmsP@whts95mtdL`iqnz>+xh`Xp5xEkLEnfIC%w7>ymEci$ZaLc@cnSp^{ z{~v!_K4=k0a(fNc=yC{<+ZbHB zU0OU`_}8~LfJ{hkf5ODT0J4tw^%OCn^%V18fYxqwZczcPV{z^N3~CBlfQ~Q+Z~X_| z)Z*BE!uS$o(L(1Il>?wPD&IeXDpLo3c?X8>=^*=EyU#gxe|OaW)_u;y`b2j>$XERC zA6)s@pK$5E=F5Dx`6VNNi$AC{^^&n9rTHaei3^BOY5vK`-wLV%K&R67`ZoCTuRp}E zanG~+A~-N#{swns4tZda0xjq8>BcGrIw%9&C)@+k?%91BWUy!R4;KEWS)kJmdSe?r zdSjb>wQu_N2K@2VzUg`J6_aOgAdBb!Ltec+pw2P_c$AKRztbO37bT59?*M=FfzSMr z$3FAx97uEQbP@i-zu)N_x&&1G2f8>&!w>%bPQTD4I$eZ+fICi8K+C{9txxbbgDzxt zJ`i6vqWjX53G7P| zh%dJ}{`2VdWI?mavBQP?7ymZL-)Q1c;Xi1?P8}|ifB3gK{zVh-aN+&~_S_7J=PnfM zc=Wn9_-LO1MX3k#DUbh$ocOn~aH8vD^t8TEblb7}noqB51ONKVE}-M}&U-Rn0jCS_ zjf^hL-#`OA4vwHX3kFx~@BB@9;4SEoR0e+n-b`XGX z>ywfYe*J%CTE4AsOI5quL7Bm~`?yPYKPWqSc3=6-FIb|Y;n{r#v;qojbwhm`L#cf8 zevkszQZX0)_4`4(JiD)WcAs(WJ`Pos&Hy_1W`Y0%L#ar2h>8j55HlO3VghX;8R&p5 zaKY7h1XP|m#)JExv4=s|&_UL%Xu{X6fV$`^;KFb&0|Ntu1sZ>50QZ?dd&uL#{Ugw< zYaEyb+PfJGW-+laFkA)e^@Ou(Sr{0yz_N>27#JeKtRpNilP|NtX8*u zcr?BNZ>#S{l(w!6#FS*A@`f?bA{KF8v(&*Y|7ot+!b`;2g7k! z&=v=V=J`FDMgw?0_P+#SZ@sC}XPmyh*%Pky(baAiLn`FDMn_hGiPh?6bh z1>bGx2|X9rBiYZv_>xPvpNmKLao6sHov{m!yMp>{3_iUgpgTYrj=Qb^``UF4#H|}Z zjy&$V1w^^=uXhn$;Q9RnsGQGeJy7b^?Z#nzsWW!RaaT~8%5dEE07UZGAy|cq`ZsaGtO}&flU93bF1Q6%Wtez6D&MTQ!e@x@zDPT^xM7 z&v(0k?g0X?OSAwjOtS#luK?B#IyUMJsC%01<^Y=iws7fobLot|aNP9@I1*iNKpb=j z;-Ci*2R#8%-65?PU}46;{!sUc?l2DiT_5Gaf$Yk^{-6u%IUs4HTD-+83-(lrS@gflfg$^h|DR z;RLmad{i7H^AFI(iid|~jEY4mC%Cl_I+@(1J4eOjrR3lL|2@0eKz$|_kM4*9 z=m}Qf^%EY@^H{*AC3$w+fp@=xDnpO%`T{Tqlq@`Y!54v+N;~d2Yl}6HzUK4j<^$hM z4rvWr#ygbhf|~+|J(v&pbn_esP5+2^bo(6VK(^oU|Hc1@t>Ybvu6wk81DSsZhxwcy z-Fy(EK?@sv8Q{ww8jpZV8^q{bbnIanc&P|lO%7>5f+kZT&C7WBTKhuy`g72goCV;< zCuk%YvP>E@007Yon))dQ%YqKwfyjbRIEBc94m&CX%kG4mypj=i6O}a+1H&8eioq%- z*y_M-%nS@QU>%2<85p(;gPOJ9;k%LkF*7g-fn}NDdy9lw7#J+TCA1j}14AWPHXOd+ zD2@fDBN?tEkA;E36s)5LxvWNNC*OGg|NjmRK^zOOLHElW9{}G>^uh(yJih7D{R3J~ zU%_6Qy0$(6mD5+T%YrIXNyi=6u**VA@$1-SeJpneurV-zOuvC$1!y=+K@gNQeHn}| zfzN{zWMFUvm(k#o!WFbud<8S;VDH-=y_*x*7#Jo%n)%TJ%|Dp8;@#F4 z|Nj5)?v7vsEvEm^-vTNeJd)iVT!^d2_}BZicJQzFcQHQT(tYuR3;)J=35;3{Br1Yd zjg>HWhl8pym+n)())#&G-A+LwC0@iM+26sX`+)H!7ep=P*?kaNL4nI!A7=LB*ea@S zpWX!^$AH#TgHQDq@#qEzt}Cd$2-z0>S^(DG1C4KhCt{3mJ9Z!Q=mZC@Pp2H%``zv? zF2q%E-65?zU~YHhUw^Ip1iUtJL5_DNL?*&l3ZT+Lj)}=G{1?XyPa7_i(guA)|wHbI{fb0iV z`#+%Z;M42M0KO#@5*MJQ?rflGQE6%0*Hne)4A{p05mz81uj-W3)>(?JZQ)hqIVJl z14BJnb~;?|O1NIobP=Qi0IiIG=sgXWy~M!4&;-_d9j^BWd{}iUBdk*3U}j*r1}?CL zm|<1M6c(5mXw&Fg8Bj$5s{Ss5#zJ|FPk!Lv=q12E^mC!i!LqTS*iJmM{M+A@a){{+g$M61L|HvasX%* z93=OE&Siz9aZnE%GM)*Vb%aa=Jc6feG5D}PD9u1dJJ-R*K>Y=Xj*IZ75-a?cb7A

iw>W_s1^cllZJ%yq-&XMH~`QlljrGV^lUyLfKUUTzTWO)eZ5$&`@Ey}Dd>zjLYqjF2Y812|50m*cbd#V1L)oy zKHbeA$AXp}Yc~I6E*0?U^)3K;4N@S#7U^~T2hJCzTnN!_Z;oEa|6pPMRxYsjn?Z>S zWQqzlQ}{v4l8_8A{${xuoI~2qKmrUjC=D{7=ObExf@U*3y1PN9LjrUoC_po?1t@F< zSvQkoCqp-+fz!*=)Xmc5*y+Fm6A)?aW@&Wnbl`vqz*O)+f)wN|k6w{RaFA|61S!~5 z-(DnxU_pA!8se8G6YzXPiHZd%d*y(ZuEc=ySdEGYXe)(d++onlTSzVf?a+keBGBR{ zNG@`L*A$>J?pAP3ff(Zk%^}?c*9vuv3=B8GEKn}G1!nDMWMH@sWoinnFqA}iG*^ReBzD}9giBQ_L#Z5SKu*%7 zyBpM8_Uyju(hY8Sdv>1zmyxdBXTdY8SHKt2NW4sDW?(Qr3F-krHY$AtpP=RnYWH^T zQ335H0iCc0n$Uu!9LMgzF5N#Jwf}*~ZzRE6EN4Ff<@M$V+zbpZ);IW@K~sh<-IKwM zDec2P$=#s#mGzHK-|LRum-yX|cly3}>Hgu>%LD2zGk^vIx{n=qy$$LV9Cy7Bt}I<2 zLwfVi!BwT}YY@f1K7{xBaaYh9BZlLy-y!OLL)85TDe&N5f5EZ)&~aB#Uyy-+y$kPq z59Vtwogpd;-6uTx*Pr%a?!Lhd?Zde+?glyAr5o&efT(F5TeXjbrzv?q&}*1_qzxZcuN@QTuSG@B2<)n4e(*hSl$IPlxcn#}}6H zFoFiD3-5Ig=E=`}_Z335X+%Pl93= z60Z;ky=F2#>Cs(!-j(^gtMym@rpJFkTQ2uNu8jh1wFFI)bVDv%^XT?vhc_P@kATVq z(C9Si*cwo|0$Ck}TDCz-7)05Y1uywP12O5~lCKQjMg|Q+L6#JN+Q|Okk_fa^08(Os zHu6GryoDc+dY*}a0iuo*KA0lF%)kIqCkj8>#gZAeSfLNDZYo^eEV#OTaCNfqArw$6 z1l(poI!?i{`=e*~3&hrcNyi(;r|6tYZtVE5wvcogul5)MT4=F1GKz7+T;HN zFnt*H%mj$LKue$@L4w;|pi+7;x(ig73`Tc>I+BCYU7)IZ0NoWD9Of9}2wuQ&0CW~y z;~UU2V&v|8E9jJJmu|={r{KhIG97E%%CXn|AL!@{&{68H#!vSBV_;wa4RN-Eri5KA z7lWEEpd}li-hC%{pa?Qb1llkU>0ESzM~yssceikWPR~&1Zv_o}TOTWvblhQw-BL)~ z$Z|5MKU1pe(c3%$G{4dMzXW{YdwlDql4zIiL*OM(kgGsmdpGZ%!NtJ9SSkXlL2W^^ z_TA^9>nL99`G9uH{4Y^!y;P#)+I_{h^>&FA;=b4pcF+H(J^mke{13iz_Mv01JFH`J znSp`9#d0yIwE^-ZXwC#4oq1lq1fBQW-NVVi(CN+N*!-WV zM8L86KXVBeXr(uQ>nV^lcw`5(ic`}?#Q?M%9UAX0Di+<%Ag!Pqn;bepyELFN+3BL< z(G5;4phYZ@1k>rFV&l=<3`!&*_29eA0=k<)vD)dP64TubS^&eZ>Dtig+R)t$T4Tm9 z*a~VX9B&03V+YQ0nl35{{DQ6yojxiq{DQ6lE}bbV8T^8-Ai)BD!B)^3Rer%%P+J~k zM*+V8*aUt}*9o1jGd#MRL96yXI$b+Fx|>0#H+posLdW%!1^8P8!6Qx9KE0bk6N?^= z$3Y`e;Oi{DGcYi?S}X(YBQMGH>@5SY#!T>lZXOSZZ65dY0B;`m=mu{dcd=Xs@=}R~ zNB1_+s2?cKNLs<^3EMXAVwvDjV(QUdU_r7Ot`^HctNKeUJiE7%xntbeqc>ClwD`cl zqwyFhNWiT`$l>{rv5Z;)P}+0=IZhZBC~6+vVjkV+kw?~$mn!;z)`cG^}4QwIYrV?Aw))hmL4H6#RZs5gr${yWfo}h)5-N#>ornewN zcD^VJ%Da!hv|(XjIM#fYn}LB5DdW!sCu~r@2PbUT1)Z)7AbGyibw#%`55K1ChECTF z9=*+=4DZqDx}zIBr_$-V1TrYl>AD7*i(I#O^frU?HovCp0gq1C1KrJ_T-@n;2A-xr zbh>_kr)kg?9miWi^N^4MgyR$cN7wt}xe5^Mzp zKfk8y1Ac*4kTLw4t_+>79EgPd1Cg*9Ad>^A2^%~YHJY$N%?gihP{P*m==EKInXZq3 zR{_<6*W$W>Qk^(7UGukifu%@E+o0(&NZJMkKR9jsE`TR({#M1m|NkFr2A%uQ=-B+9 z5nT2%^S3sF2F*GbgC-w+Iv0bc8o?!VJakGCv;-e?6MTuEi^W1v$;96RT4D`4TLg5X z)xXzzumeUEe7Xa`$Nq>zkNwdA9sA=5T2##dI*ONd$K(J1|AS_>K$r5kbT@-XyMuYU zyFs-C*r6Vf9=T8V2Om)59X!G6*?0hy99=q>f{u7N-U=GI1*PcYt)L4uKrPDStvZYh z44}67@m5gb18V&qZ#7{A%}g*bbZ_YeY2;tO8B|Yt@UQ={1GG`h!}?MFZ7# zn@T~E3yMBSSLhuB0|R8s6E}S59+X(Uz()muR`^5of{q{B3f8*_-bKI62-6EX&joVT zATJXG14QptX4o2b&`=p<%0Q9@CZ^89zyJ}8f{THs)*)h`)mctp7l7^nflNz)&Rm5| zGQ478U|0xVg(1NTGf#~bX5KnBnAjdRnAj({7y~;@%$Oaf&Jiva!Op+{v9}j4JDZ(> z0b=h_c9^|+91INe!9%s)oG?=&IbkM)8q1LBy#sLB+nfvxMqnM!IAP|obHQ|2bHPmU zgo`K&;oE&4eO+~TNQ(!yRU4B*MLF`y z4bXx#(2^g}LJg34pza6g;5*o&4NvBe9*kgJKH7)Cj)l1t=0cdWFr47h;sIU2;R9Kt z06IF$hxrF+dKYrw9cX_R=+00*7syHu@W3N@;e!WsnU_a*GpNAvfEeJ@xf$esPwOxI zO&k9G|NjzFKdn)z07XdeYEaz!3CH(3pc9areW=vP8uJa>5-aIP@3&162UffbU+S0?HAfL+(IJltHx> zC|h`R_kuDmQtbrF;EwU}(Xod`7#J8JOCLZ}Igm2m2|m2)$-uw>DdR!oevncevg*G5HK=YmBffxVnaA zl$X_@CP*^4!wcG(8@0l zaFfYJ#RuGAa#0BYHxQ91ZZZ-r*n@AD5gQX?kzysqZf4QWcPKS?jN8e z3O<8UQ*&`2#L!Fs_HJ810>B*7-avqA;~0|R8CCur>tL>6@GIYck$ zj1$O0Pte*Qh%6$pgYG|u9PbEPmjcNEpsgqnF(dd&h4w&!!=O1+XgdF}|Ns9T0*=r* zIuGp!D0MM|1GvKT=>7>RRt#Lb?}1K;b#U#z=F=IXV&T|*&$0U(Xx*i<k4+DO#80hT!1L4$gr^w@m^6q62)uw?+HVGv6^O42=&+d*|5 z*n$(sUC%%)xB$)ou2&!y+<;hc2SmAayR<@1Jq2x70u88vEw})(zyg%JJRBkC;0A!{ zJt`573=FR|!2Oz+OsotHU|+u$2UB3ryBdE3ZM=jW$O`ExmqV6z?gU*g^Nqh1)H3Y$ z=Kyaz;$dN60Bttn1M@&L9-zevCtSL>PXM*2zPYx3D_M)&)Bv9g8~~a}>jae8NxZ@6Q;CKGeS)x(`I`R;7e~(W$*Z~FI$2@vN8635b;&6Zr zsBHz=LFUuj%U}SiwZZ#VK?X|G9LQHXL_tfy5A~AH;Cn6}0*lv{3;RHOE~+ z>(0PQpaGr)K;@H5XX%XNu5%z77eJzR2`FkkzkdKN30wiv?bChWxGU&JK89|e-Wg#1 z{OkKc{XtOoHUqSbu-8Y$!v)kZ^HEU%`-b1?fJbjXxLoZ8E%pZuHiA2;;E3(M0g6{p z%ZlUQ|NoHq1=aQ*9-t2HfADPd37_6puxERxGAMu+)}IIK2Pw+{g@XVCLw5*~R+6an~;p9yqcc`JE1ce4PObX%ENlBOp2hT2ytrv_9Zp4=#Ef`PU!! zV7>r~6c3ORM5OSyeE$3YKPY*CQwn%{;^W_7krG$t_b%4&_?r%aXR$+6EMDII4QgeD zs91o*6s!Xrs^@?I|L@Yh9hC1tmw|obZ`tts|9?<)!w(N}>^=rcVFC;c+Q%HbZ#i0n zrZV{5ZggLUrZaX>4OrpQeHavjpzV;|@Z_M>?V?hFR3IGSZwdJQ|3C8wQ0`y$(hAd6 zu;?}eOF_!M?!%zr3UF!Iy+j36I(l^Xf@%+>1|g`Hag2)xjaGoh8Xzq~&@?NgMHs^X zyT1f(&_<93!u4P&_24)plMwbQ1oi>3%IB#fMz8?2dRLj zCqUbk_yye^I^{u94xlOT1b#vP0DeLD1drw;0rB8jWyo*>=-SBUHxc~dC;0_EP8@dt zU2V$1FW_*3U(n-5XTS|m>t4a9)8PTXpvMcJPLCJ-f(|DxE3U0ON#*EfP{W0%fSh2yTE@i~U$t{RY{7`zs@(?uo1Be@Zj{f@gDfOH*q1ux14 zH7^`ox^r3;T=>_o1XZ}*j-ZvlAn^bY;Q=B-K#jwc&KMOJ5HF|s2V=RnM|US^v7lpb z7^6owBeamK)B#oftp`e2e7XxzwiJ1GLl&JtyPPlA@iH)U--8s}uxc4p3xhUYtl$RM z!u&1w*r5y~HU-R)Dj7#R51w}a|){`D>@5!N4yxm>yrK;)SnyRUVIsFZ+? z3Fmk0^d0Xi}ov?2kV+y4hZYW{^%z)ZJH^4O{s0{?IVLu#q{Q}YW0}`!& zKpLSnECWcJPxp!At}Gyy2Xu#bw@d2>xH+H#7}S>Pb)Dg2eZrC7=^*H|7uN|sy}l0J zE-F6XOH?92U51M86CRzuGdy~ICx9v^1!xTn&Yz%}KTw4m48BtVq}SDfxpam{?^FkH zm3P7eG?UVOzOzIn0j$BJyC1YV)~DBX0VsnNAfif}8MINYv;(||wmU|pz|s0nv4CUu zF-sQ}5B@0!K)LQUOE+kG(MKi3rTcMbj7os@!BSI4>-*&rj@@7R*KY^yeB)maE}$K? zzcYV+t>n?o?%90~H0#RZ(d_|Cf3MXXt?w6ccYk)&{@(hnH1D-Gc6Bb@r(HU0R1|iB zLKy6R$L?<~mL)1K{H>sCtDwGt94O~%d;nB%G5!xo0M*F+9XuQi3?Prd+zjhVSsyGF z?4A#C>wHjU;>f@LF4QPcpu|AZQm=0VC@C2@YTpOZ8lXC(n-R1%R>86R`u~6km+l9k zG|meOIFOr4av*KO?i2j$`$5J#@~=PY$iM!q_Brbl#s16(v=4f8^IIS0ce?@VD6o2V zpL@;X(d}8_(tQqeAR%nO9o)06w@V~Fx_ui!O~S)q?{^fcxyKs|SG4<6)cP#C4C1UP>GYJH<9 z?lm`3VFNV?GtGC3{{8>|wITMj2bwx!00+7wbOkFYq+GgFR02Q;*ZWwOPJmoS3UBB6 zI(Q_*n(&>aH;%i~u3$L;Em*pJdLac0wCN6RwS(JiF4hM?%{Ew(;yMFXq;y~7Umv0p z0ZuM6JbGOxfRlm;bBIa>*tZ~Gg4*kAfB*mQ0V&sAjZcF1?E8RABUm!{9}oe_X7@l{ zsS*_*<`R{Xm!J#Ru!rZ%)u81v5CeUBU6*+DhMqu_KJ2Uv4BfFytUs5EAjMpFjEc+4 zf}j8Y7qh!`KhwVVS_x}y!P$KSBF*gD{mi5Ly5kN|UEEe5WDs+JF}2P$IiVaLaSR)_=4iX`bG(+CYSEhpf(mvFR1Q-=yg#69kLh!%IYpE6&~H^ zzkln7tj{4Tj;uhv&F;_*;Nrj))O_g%)tTKnDk+Z4XZY7MS)br{{rIw-3DhNn)er|j zVUU4{qfF5H8_*_iSL-9#YX;Zu2cRQJ0<2wBLP{-Nt&f*WcDI8{0RHvh-ZH2xa@0QV z$b8^+1X?O_?LGnZ^=ma(>*GZnj@<_wwU56xz^M*gDY$^D2XGL9QfP)__c3d*O~p>2 zYO(A4|NpJG`CFU5gX0xc^MMXQ#i%Jj2UW zRH%WSnN1-JPD4ftL4Oh@k)Djh>qynljKpCdeh=`;D859I1m6xCeFpvVyr`xr` zqx(Fl{D((9$V<@d83QV-;kgrBLX%@(J7`^2cR$FBj@=je*G~tx)IY(9m-2^-*C&iUJq#dX^4; zMnr+`(*3*JMa2OWg{t6T15lw0%AhGcOXA|z##pb-)UFK2)? zvV&VNgj&!!B&Y?=SVIp}d}+eOzyRJ&`BDqS0!_ug1fBXp$a9X+<04*y_9cJ<>?P>@ z5XkN|1o0JWyAl5Y`lX$p#wKa!2g11Fhl)*Tm5C*I*4sP=BR2L?s1m z9=LH2+HDCv^R(MXCB@PD4E9pNrTZbcK#+5^K3|>yNm_^&(pg9C^N!4CUURrwKQD3u zak`K4uSYI5tWOkcA(h$CWCR+Jhydk0go`>$A)NwHrT{r}0%$cFXk#&dCun7lN4LBO z=y=4{;C(UC1z-+%2UBl#259;L|FH@DNXI6)SQa~!nIVQ9LBoyU;}Z_Uj!!s@etd%C z|6?xJ#STSRKsG5k?!a+?0<@NRt?bbaJ9HY7gkK9nJq~T;W`MRa^@6*r#*obtKD~2P zK>b?J?r00o?qUbe?rIOu?#Hkr7h(^C4)KDFHGnn^LB<+Do1FW>2X=v$jzG>h&}U>| zI0~N828|(FfmwGM85kfe(8?{yP&#O-K4f@(HZudmRIn+alM-ftS)f${JHclr>;TQ` zgT)T9Ffc&GuCTyPUQmaRPH?ip?67BJU|0gy;mHO&c_EMu<|=b`*my=iI|IW!u)4$S zFuf`qFjgA}1H&Y+>;w*&DbqP%rX1p6U|0o~J;MQW)pHKm*vbx028Oj@*$12q3>(0# z_na_w8@XWig6=1Ugc|66Qb?#h;)0E`D00L6q0J3*fe|;%1)y{~8*DNw4@{Pa2PSLC z!@vNMwS&vL!ewK57#Nm=^=83k3*oYzJTRY6gUil^%dUj8uEOnj1XuS8F001Nz_1x? z#|>Usm^$#m#-`l)V5eF5@G&qz#-?KU7@+50wDU1AYy<0E1=q0=u45Zq$6h{|e^2ni zPQo}37yHP^0KL5gys%#Zu@Isq4C@?AH+YvcXkLZaqr2OHhk*eyUVzv^TnduVfJ=Z+ zA5@42pVb7JTmsF^LdM(>o9wI^U~UV6vp{>h<(eyb7)m6%9R>cMu>H%xz);T9Tq(j( z!UkU9b=dg8VRUzkW8aw3P@T?DY6zP6?j`P&I>;ss;#?0pD_#Yhi$R-uAS}=b8TkGT zP?&cA@@PB=+QeC^>9`}$4r|DQD=5&29{d=1PNV{{^+1V^E$CM3Qh^=!z@>1E3REF9 z0w71{gQ|4Ukt`m)-JngSkWJp8hBJrb4hK6122gvC6QTq89DHyKuk}DFXayc9*MM|r zI_@yU;Z#r!iqEMUV5gSAkJ*PBUm^fm1_wIr3>3cchr63W`4Pz|MflLH1_Lbl>%w=% zfOdvLj(^F4-vk0WQ5bU2IjE|E9Q6WPRSh`?9dz0=L>9Ew7Lou#E8`&t%Yd#9hh!|! z`a+1w?2NE`b3hkJLiB=ygBf)7OgAIsf-=xV1>`zb&+Z@4yEqS&)EZxM+`$1lIt1Eq z03Dl|0oeiszFa}dr~5ec?p09qfo5XB;o$NAflv4O=6{T(vY`J?1iVw# zr@Og;je!AV1d4vJBv=D96_r3wQ#}j{MaXzC=x84k@a{3t$#xJHD2wreZo_LmP{Qib z?N9*9QJ@)gZct8XJz2`>(#Zri@-Sq?(lMu#2`SUv0;#n(z63JI415GsU=H}Kx~K!- zDF4JC3Em#dAAZvKz$gBw6QB7b!DnrM<`?7!`8@h?r!d%j2nV$PX(RY_2hi?3daeugg4ubv&T8+r; z(J2R72EzfmmVnu@L&EqHXeG7+B+9_<1?`gObnJEe_YySd;@QpT(d`J1IDSxT2sA7O zUX}e?4K(bj09w&n%H!MJ21-Vb-Pd1p`F6K~;>QtsFn#NR(h87`fgl?Ve0p8~gVs(e zd-P6G0WA*&-GHEdoqrn-Q_IN`-VP3r|A#&PAMmukQS|y4i!}qt&E_7*4}dZoc+k+} z|A7V@c81dLuVq1U(jL7%5OW}ECV2cmU?Ia;`op7>4b*n&frJON@d7&NuLBf5jYmKM z2Wqr{PH-24pK%ZeKd}H*en8TPB_phQFg^f^V|L?9ANV&03V<^KB>i_ga(Hy#Y<|Ji z{DZNKqkAW4UF~bO?wz2`6QJNVfD|_T;U`-UfX=ygQ33C2({2{vVPN>=!4JCK9l|*A zi9hbdC;o_T2_6Op{_q3c$Bj>d6dd@(A9>;vzW_KTrSa>ZC=&m~A9Di4D(3Y7t;__i zd2a9ko$V5%V)4m?U*{lbJOXq>@L^Edp${&aeHa)Rg1`%SKvzcR!&4NNU5xN8j3uBe zt4dixv6cdwCHTQyss-Y3`}BGibYBCf8Bhc}gN_Y=JQuJ-~4SYrybiyN~4-KQITCQ5L3 zBMTE~x06G$hX?b4&M5~uK)V9Kf*#BVTsm70@*o774=92Kc@B1Wg0ywJsAzaFpLFbO zIVHlxz~K152_yOK}oT5i;4$m1w-c&70|W<@T%yyd<+brVzdKvwh5vbz zUBaW=S;M2#UBRQ<*}$XIUBd%%Uv)`}Pj5SDPXl=1#IyT^M{g&{IDP>JkKP6_OVF`_ zU%+Dmzo6#?egTIC{DO`P_ys&R@C$lw;1_T>z%S@{fM3Al0>7Z=1%3h0%$(x`egTgU z{DPhzJV3WctbNVwYJ9+_vmI38cyu>|`d^@)bCyqUdj%T<1I%W~Bohto2bFRjz1zV% zp0+tlbhxMTZ*!LHa8Kjk<}B6Wp3c9`S-QhLu zKGzo#*`T7(v-MJmt4DV;D6&D}ApqWecFqGjg!fw0)%cQc_np>DCGzMS=-9v;=#IfQ z&~-P1+72GQ+d+F{I@>{8xVpi&DtESn=C8V)D>~aj9kg!enojV6sNKOe$SwdyGo>y7 zPamO3%)_8XFIrf(SgBo<~Iwb zd=et;*$EoODOHB>kZlw2gq>N~ee7j9Xf1Q+8Wqq0m``uM2k1OJC7c#J_Lly837Xe+ zH3nT91@a%ne9ul-2GAw(@Jr;txfbL({?;F$HH*FF0idH4m2nyk4!W11wZiCTdqBbs zo6(aQLB*3tZ!aiGI_?1NR03NJDg!`Cp%c7O51M3JK`{wRGOeI{xj`AA6?DZ&=TuO> zZM{?y;L+U-OE4m!BnvSXo@@=kXTEF&Sw&>(b>`R!nkDTn7J#^|vlVo!MeDb2XP(Yh z&|pLNF@6`&JZP_vN=9ev0!G*@J)|sw=BQ2=l>lf4bWw@uhGaj`Mn-4QT^}i(Au0(_ z*TXWL5ZLuTpz}!~L#MBeT#YaJ^v0-Uv|cJvgOo?0^Gl==dsiJhm_7fW@%(=nn)e=b zJ9B{ARRN$I7#w_h!6Dh{ngMpbNB8AU*Bp=T^E*KP<9GScSz6%uA9VW1ht>lnQJ&qt z(2l4lY*+^=&4Ct-fRkJI9u-h}2dyXuS3KR&il@65Tyw#iN{vTAHJc-Nk!80q#7z+A zz!t87(pd=?jQ4s@L$x#mf2%F1N-gGS{Z=a8?a$MCvP6J?y*JN}%f3kh-ye2=I{1#Q zn;G0vVk#5%?cJgR+GFIn7v!OrjG)okcF@rxpvFx1wa)FJlQ3GpmGX2?2TitgP6zR! zQ(>rjkGF0B&4Dm5FdT2)!pHzJl;LRW2{hkvy!8kpXbA@c zgG=`wuvXA1&EQFQ{`KHHybNGh2XlFLi#dWfIvRQ$ZwDQg0&*P8*Whh6FBgCcAkZQk z2H$SvU63A~U=5(M@LV^<@t~vp`oR`;gUfPo#_4Vcd8`xc^=@WY`i!AtW_fzFhi4sx{zI1E5`xO9W-5wJ;6UxF(4|DMe?Dh>># zX&~Bzp)?Le2QZX|HCr>6x`4))d)*6My8FS+>&Ani$ad*==YbqF51MH-=mszJ@Bm%j z1vzRSG~ord1~f)p>IzC24DId$FF_~t_;$Cbfc9T{_O?J$WA_vlkXnykfAF$D4IjvD z3*c2gAPEMLAtn5pwIy=%vSK}Z3P{O51QQR4N>_IHWn0?pcUMn-RB{z1fZAKf^6vR1uF-I9MoR0 z7N2f&P;JcW+x`7Di*I*X0VIIIa^3x)B~2dPH^4lQ8Fx)uYI5wp04`bI%s|tp#fBwbRTl$Uw^REMMVR2ev)H1w}txm>3wq%NAhCA0x<6(-3I73KWk}yTBoXB@KbrsDb4m2kVoS zhCu7spw_r_gI5{jPZjN;emY!(KM#J@;5kaTYVh18xoHSCix16o4f{cZHaKDfBX}G3 z8-n)IL0y6tcUV+|7SR!JwlPU&gLm~oT>{GA{H|Yppp#2z#oZh1#hvkim&xD%{|EQ^ zUuJ&){~vU2D`e8hoq>VD0z6g+8moYe-+^vn+Bh9B2m06(s~6K)FVb^yp2?tB)QTRdOw=1PhKgQFaCfOE8O@1ICi(fNfRPMM1=Vo9~0gDyE#d^6J7}UXH3*ch=xEUC< zz+$)IVxPGg7<9p6zu{t_kwgQqm^u&44mTbK24k>TC|s58;19?7}9p-$nFtCS<#qlvPK*AswF4hAYWCOc&He75MA1n+`!o^Tuh7~76z*PFnjI!Vd=_^AC|7X`C;h_x(5t$sM8F{&|wDX?joRp>I%p1Z~@<5e+f|2S;4W}p9fqg zgC^AKxfvKdyV-qu7lY>Te7dzk4Q&U3mWog^XL}*A0*(}&7%V4GX4(`fF1&jDgv5tL=oXb;yHF7eQD0c zz~I8a-L;K>yN^nhWA_2rpuS6|YZw3egZzT76J|C)VrM?o9imcm@CP&h_7nW;Po;Ib zwtzg~(dpaZ*?r!n+d%~ESjX;u&@_rq?`qJpBbRQ_5^4+n?XE4YCwGB5)txmeG5oF< zK|3r0K=WWAKY)_T`a`b9C&Ak+GCD(496*5`;K;xJfN%Fv@UDSg&jWQ!bsc7A~E! zHsBNXHNJoB_EB*#zV!WLcZiCG^#%Tx;|!p+^PxI0b)B^u$6djOKn(yNudm_4zy2U7 zh+O#B9|EnDi~vUxLFXemc7oF` zI9Ycyz{fm<*%=sIyWL;~QkZ~muOF;H@)ZOoqtgyn#(46(o&jYC18_kzJ%AB(4PvFNXSY8rg@U6L9NUijFaP`h z|8<(<|LGZ^j!yRl&~Pl5Zzo6@uPS-#D zg04sS1zoT33%Wi5o2)=rNA3lU^7pEO#tR)S>jd~)e=#sHd~;pG#NYA?ygZN(oX)@- zuhTkRRI0#k1g%Kz08I+qW<@l=3(4Lo|3yXGv3ZOL5CN7YWEC2SRud8<%Gk~tbfy%h3fMi~$?gB0N za0D%(V@QJ;@!EeEXs_FA!(IQuS~wUOkR*+EftJrX?m!il*~QNWG9J{4L>R;daf72} zod|zxH6#oKT)JJ?lq7b$uGr}y&%j`Pkl*RxPKGRjc2TJTSH0jmc>O_0T?>kc3P@$^$iMyssI~=HwEW;GDWGez0UbO5$`8o(AJ`ch zU}x}mf)3=MOEz=tz6u-Vf$dLuSq`38La7?vAXTFqPdBJ)bc0omVQ5t&xSaO{jgf_k zAhlomAnliC(2^^c?iZj2OY2F{F)E;f2Ru*Go;W05$t69QoJ#34q#r36A{h&ww^hb^3s| z!SR7Z8ComSF_^*i2`r#N6$ZHQe=XqJxeioKx^^xD1rg||vLz~@20Umg2h@D`=prHb3Gc5fO-^=RV~r6hbMrt3n45GU;6?&@(VJq06HrJvN#d65D_xQ0lLmC3f#|r z3tye~g@J(qa!0{W`06y!F<=l=gcunZQo)Ne#om@E&X#D45V1R^%2sbSJK;r;C zV7;pFWw?6qWw`M?uvJc=ae(Py_f_yPFw6k6YIqnJW`bD_JPZsF*%lrKhFM@S&@z?T zU{((g1H&9JYXT1g!(1?H3J(LrJTS`vv~Dc~bl^{p$_J0eH=v1Q7pXy+I<(aN>#JmP}+oEFGj~s9~FV_iyoFXCrUqf zbhCBe@;LaI*@G1{OIM=m)9t_kS|-Qs(JkQ7ecJK=h1aSco%=vFBV?l0mkqYo+y}Hv z*Q5IcWNy);vkjyOY%S=f0+;UZj+Qkl0{m^Ds|evowsjvzImf~SGOx+Mtq0VKV*u5y z1uZ8_ggbbcJpLc>X#U|)bjP8iMy232i$jME6X-aiULTcm$AiD+9d}uR8imb||2cM_ zb8Y=r$@KjuXc(n@H)z)fbi>Y$vuDnn`Tp?WE9PF7Nz4bdPwWT>oj(4Pli%+!)4}dj zjvWl1{||sJe=cEZy;OR^xAjsfJIKQF*S?O;zyH^YI38or1uOZ*2$L~(>;RuARqpx! zgs1g^Vr8TaGW^>Z9Qn6#I(9I6{y*s0!QuJ;gs=69qURpneV{x5jWWWaS-3sAAwg?t!_40TTDju*|Cp40J6lvhb-v^A78OvX57O?@E#}I<{sZ*D(-Jf2)oCRn9^FEY z;8}TduoGUc2X~6jd35drjRk=dE+pBAvA~iI>hZ}Q-8aBV26U?}Oa`nCv=7t)T5@=F z&I1_-ZoMJ((hq?1mK&C=1?sTJNjP@8iGVVaZtJBIO_%QL{PGOGt#3=@Q1TUc`O8_v z@|U}484HweVHwM(`vItPZRi2Ny%sTKbQ9d$J_mIw*b5M!gHAwl><)uuL4I%s^n>NU zkLdYN7EAt<_2}FO>I=VCfaElcd_&U1N z|1UrsSI-2C7F1IqvWcMHV|NbdAg}L^mbC)>ZJ&!NG{zo8piPL>EEW#79F z9km;fa;lcfj?LgK?8tP` zrTbI|Bj~#M=2wj1JbVU}huIuDYInT$0B2#LV~o0B1-}?eA>sxO9hM%wt~)&bpY*Uk zSgeS!kAEAZBmXu5NU_i%=<)xgkM+T#msoOd=my7~pd5uO^MWGQaOMTA^#Ety zQUU&^Es(tH*?k5j=em-WaZ3eUJHaP@J05Svk!w?6xz-h)Yg0YD&wxuDP|4u|oi@di zx_3bef%A}L4XPSndw_1u?%W3&iHC@S7W2L~f+SH`PKNLxdA!6ECJI*X(K!#K7u2O# zqXMc@K`Z0It%hz$tHGn&_XF}-LZCV`&M^kGS{`gQSUcD#aAJME9dXZ(8@zwu3tDYn z3U2el`v)m-S{GO^Yd|pf-Lp3j+g0M?Nb9 zLmAk-diVjvpcN#LV}h5#XJPr^bFZNO21IWt8v}zs*xpPwm?;O?U~{VH*kCRYgC9Vw z0zaNO7~ThoW@li?0=vqQ1Li6PPMCSdoD2-qsBb?m~- z27?2_5veW*tul1%c2VI0H;KWgU4gh@-}*Z+xdxrVs_lGXsmmCkiVtZ}adZ0u%`$P&@0wFBbdZ0uM;dz*9W|!`Z zuANiBc6oHys2F(m&I8vF-8m|tz1pP)IG0L;ww5J;7pkBnEzEV(pwa45QAp_vT>%c7 zKwZYbz~I>nX&|)TE}4!)Bl>>9(n8N(aB1t&db^a{aYuk11H;SM|NsBH8lN;i=?L0P zSnt>y$Oy5|qZ4d9h9Nqh-D1#XjIN;bx6ga@Hi3%M?n$5|2^tI20G&DlnE(a*6vdO; z@KW0dexfSqjx0#2&CLi~Bk~u{0+p1IaUjqrIAo{cAtu;}2IyKE$Z8wVHHVNDF8`Qd zD_oS|YjF(VYjHp;dLXN4(wP|;tidBarEu9A_)48FW(I}_VA(aGH9%k%WbQr0F%&ZU z{s44PN8=k%d51ju(8|C8x})T$Z}$Ta@NP!XwjD{w9bba6mrT8@!F`I(Eh-Y6pl0-hif z6!--kK?WG`3wVO`JMarQg0zF!2_BtJ1>KGnolX_}+Z{o(?P;A(HQ@a@j@|8`BCgZb z$FaK|l+im~101{CL5-(Q*AVck0@sLcH<3=)7-&d#x+XvavC}n$9}Eiz1L~_o+|=v< z;eZ{}?7_pp0CGsP4}3efM`tr=)3*m?wl4#c^Po8$IwJ?V{^n!}uLpSDfJf(UP;1Mh z8@wGEG_wWTZwgvL&mjPBMcpuvXT$u{6UVqlXy zn?t}H#{U5dKE2JL%}}7xgjA2-R?q>hzTGE$v`=_+pXcA+;tLW;Yduin%)fn#?;<7! zhBU`c7LPR0?jX-z7Eh1ngG_0iQ&>SV9*sXiDXQp1nq#NMYjKbN2Vo*?U=h|d$4(oM zZtzAAkKSHTADLf(5j0f}N;2KxeIFi>ZD5Z4f^3eR?gIP*jtjcM_c%H73;GFw=F|BF zJU2knsGu7tQGsfCKTxW=;K(oN21-^BKxzZ{1w221L=rrk-!*{j;TQA+r%jOApu`DM z1x}qHRUnfbKq4TM0ze`llM+B8U|xb}ryD4VI(E8&(`cGwr&|pqY#lqn$)Ov(d&IF5 zoHo1J;Iq_jET9zQqQc|bTcQHVmaPjwePUR~bWsuT=$#5Gg+Mt;z!x-L2+5nBt)PQ) zeR_LA>cQ1&XX^@P28O|vO+md5$8N}QL}x4L5MK}voL5^nfISZxqUdbh0_M4>aCEkU zvLh(BwC-VHfR%xrt)RoCVVSoTbfh#a|F)iBfo{HmOmPP{+cB5ALGutaqkz_#5Y9nJ z$1r$w?gnK~$L`}V%fUw$wq9TX@6-ZqmjWGZ(h53z+W4eLCwR^Y<}1*au})|~0QEJr zB!K!8M^OMe<^fU^@VA0?+M^T)&34Q#-5;psC8QNch!O-e;s9Fk0SYcIaN!`}*;&g# zs8Dh2KK3#KlwP}i8Nj+Sx?8`1mI3rmw%`HPXTA)b%>l^e320j z2@^Z6@&qIUD?JF6C!o_YJ$idVhXjN2d8dmC52QeO1BzAT0>woITB3k9Hi7cC0C-q; zP!%bluyurv?sd2RUli+!6I7#K>8y4k=RqZrEtJS_KtX75Vbk2Ql9V=}_a zol;&8%en%PIB5EX5fsUgvpm2H_Mk01=(vzeuao%y3E(rJK&KgYLZ(~57t@2%inI@C zjn!n(7Ieq|0T!?cWzeCn$V=OdJuG);@Gvlx$a-{xPSon2TmY&F0~9YK;onhw?fKG;nI*(F+a*=)Q&H&7hewQ0eE< z$>h;FTLG2_3|bFV@PURw4LmxVK|`XixgpxY?(RO`-VB;BYu^o;ECY?KKpKXigPB2% z5o7oyEvVt?7!T@~f)8hwh0nmc!duy(efp5GkwEy!322lAvI_n+BLjmjc>KVJ3AWxG zG+GF0M}rn3L0Zb|nHU(9!83k);A0sJSYdiW$23D$qldG>OaX0ahL}>$25XJ)gRk*s zVP{}a1Dhwv4jUJE13KUcJX8CbgMncsn01E}wo?2ZoCR8)eE@v&03UqjQWQROsl~;> zuoNuY!3CR#1f6st0GTli1)p}&o1=2mqwx)>S&iIocNI7O=F{oA-J>&fxo`K&&eHY1 z-5)%F#cxf=L%JdQ^Ea!?~4GP1y;;t0BB7&Nlrq5>LPa8Ut`Ex4$F8W5n} zpxn(rSxPy29jCusgk}DYA3A?0=-6D#!C1<*13W7S+Pe*#KkR1cbY1V+4c@a`!tBy* z(d+ncH)#9;Jkex*u#D5EyLS1@F7Rn|2fHtVcKbs26?=5HLvqxCQW>9K&~YFPkdzK> zcDi&wcnR7f<^ej2KJ>U__j$)1pqZHe6To|450nT&bvf>t^Z)<Ztgyv`!6)0MH?_q|WAFSAdtD=)Zn173$x!UHw( zCFqE7{_Vbupu;`*x4ZKCboYZC%fJ30=-M_W237{});Y*Z8^`YJ2ce5veVNlbUAaBF z**ksNJ$m~=jobedK-+O!k=+X#@O%l{cka>a3)!{Czde-Ex3^ZpvD1~ur~7udE3;!~ zsDNXqtB7yw{}T1qOC`!K-Pb^q4=6irLD$53!mo*a019i*?z4{Fp(3E=&tQ)^LWXgD zdcny894(IB?C>^U3HF!))xe-B0C<2Tf(Mt`J$gfryL8vCcidqDnk?%+|9?URXbcD0 zci`AyVq##RT5N0uU5y2f4X`J`5z_1W-v@HW9!Ze`9vug*w8o4Oe{k;uB|^Y+K0M&q z2or!!|3K$ducb`@4HKcn2eZfj(}<~`2Oiz7%Q035b+&=Ji6AM+eTLoFx;b2XJs5i( z|2bM8D0lEocID|T{qNH0%IwnV%YNLI8$7}0$`77ja}@?r-65_29r@RThrS&7*M|x; zA2I;(LH#@UB)=1AsSrVve8494f!qXMMAUo`WDASl2Pz~{>;qMbj{m{a@F4rzQCIim6 zzTL;6K?-W%LpH{OQjJSDVi-jd9Q4yc;qK8139)YQ6_t+7KbT6{JbHONU-LS4w}Je^ ze9)tp$HC*^Ll&3re;&-IJUaV9tzu|o@VA`>WoYot5%AeKaB10D`oGha9THEVwCMsa zTL~q~?&IKe>Cr3W;L+UB9}-YLy(|u%2OqHbFduwf;n)qH{_=q+1m~tu zW>7%7bjGrS0@IWEBs7b9bb?KBh3EhUm18$JA2~9gIF6cqUBH($C-XrcX3$|UpdOz~_diEVa8iccFbc}jkf?C!3}ptnp|kWq)P6KOAVox&fFn36 zrhzgzG&VqfMTrfNP0-i?$#sS>gJJ{BEMW%429ybk4G635w}ZqX zI|CpQ!Ylv^WmoHckVtX<|Np=7f!FqsDjs!m*wMngL>ZK^A?@3O0^jcQ(1i`)=z>io zf^Jah_7#Uu;&sna0bRS{(Rc*ZfP&48fF|@1cY=VH`#}b6qv6B6poP_tVP4R!ijYBD z&>+Aqa6=GupgW|&sL2RxFa|L)Fx&;pPG@9bNCr1lK?lA*0E>a@y2oIa7!z!{nm2q} z4YX`N8?0k7ylD#>N`3~`0cr(6Ojc!vnQROi@CD0)Cf#0wSMEF~pK!qDflnW%v-%1qd+k z3wQ{C8np(Xev1X@P!R)uK_3+dkM0~5kJo}e-H)NY9uLS70^pnNKugR)Cq=1)uhG1+ z6LdWB{|g?VG0g*zYt+;D^T9{hftJ%6fUfEU4G?ypMoi_W@#h`%=yecC<1aYC(0q_3 zjsM@l&;0ccADVx1mpy9!$yY7~8n!m@=oRS#)jFMA9=$;<{|`3*+ZDjGPva3# zEJC|&vC)S?r`etM?7rgBeeVC&*9Rf9;77pIL$8m5ZeWCLVg?-}mj#}L0A0?P17u5J41A{r3HItEnK_AQlo!$Yt2NrynD`@@#e7g*!8w8rYK~7Yz z{I14NIzyMcbf)%ucK`J0Or7rA{Q}y+fFDC9>A2&1Al9xB=)_=2#~nAY%QpXJF6HQT z{{3TnEJy2sN*4ZIFQh?xWcYWz5p4d= z;==Fx0CZ%!1L*JxNyiKAr#IIwXX2l7$kq73{{V&8 zTt2<7(>=Sd{twUqtL?r49&8h6yh`#*dNgQazINp`CGX` z<6Ygp)1m7y!E+iVDxhmJTzehG{|8949;g(Ab_5|hZZb1~cEf~&HpD#l=yYZ07j*q@ z*$z5=xisAaw86)9J=8Ria|B$C-?kp82=M6kT@PBt7XaEb5&&vRG5!yzXgyG&?qZpv z5>TQ5QsB`Wdj7cUc^*(HaoqJXsEGhFV*)7mfaSr%A)s?y-a=#9r!z;zgTHk-NJqEt zbdU`mP{Sb`aw^KfVdK;5dfxZ|)Ls1H2Ru4m@AC_~o(CBp0NMOd;pEZH2MU1h*!w=+ zhe6w^Ky#k~7A~NBQYs|*1(^8-U7zy{cnk0g`hNe!FW{{3iC@r_-J{p{yyO3ApnHG8 zc7e?laO^(v{f6}I=Et*KTc5aer+~WpQ;xoN1Z_a-4N(Eb?Ee4ce@Cv5eGR8YdYw1Dc9Bmozp=p>RtI=Pq=jUM=&uk zfEE{Y_k+d;TThlMSoVYJ_EPTF+a;R4JhPckzLsdcUCLs7>9th$>6F*pP!2Eu`qR(} z_k*w4AZB(SGQQ-IdBDMmpTXn%XOB+T^Po*{{H>r09~_>c&mFrDx^^G&;9u{09<(79 zG(f=sS{$MQ+Gx@%(*W8`#(dkS`!s0mN8x{fgslQYXGf{KN8@h?ZyyH!{)M1|rS*1+ zDbzp@=G&+S`E;KK*#J6r#-p1NTsgpuDcNZ&$c{pn+4^e8O?i zT4n~v2787Q3D0gn*vfFbkiI9{6F|Cevl?mR_LA&O6kxtw%d2H@ulv= z-%C^iz`No=cYpNsyPp&UWecoeb3r5(8_3O8BS*;E?g1 zk4s`P4ha_(2N&zZMfu<&#;4c!xnuVgP>Kd`b!&X||NsATP)JMwtt$Y#q4hw8B`7sQ z&FgFjd9pX^FUXNl_w|E>pz!(|V-?IGvZmN6*Cn{{L@_C2g^QZg)gaTkpXqZ-F*~f>Oc%0EN~A zm7?9~s!sm-|G(RHvhk(w+d;1Ej_rp8TyyPmMxWmGp!Q04=yIRl<)C&==lmEZ&`OZA zKHY4-{4Qrap-VGCRV$)YuLoNUs?A)_!z#1Z0~G}x-M;62dR@1pRb)397-74a6hXBZSXK?b2MiR%kWQvO zBLl;2u&fJQHVm#e7Opo5E(_{DuL938)G)$k8$jzIPJ&GVt?WDlX1#;&F8d1KUB<=4 zzyP|xnT0`}3ASSjlo*zP&HKQ_z_1X^0@asKz^1%~cO#it7#N;|#SB?sJFALV85q35 z&G?zD3=I5W)*4or9jt5&46ndC>eyf%#cnoOM{xlg1H)UeEa(KC4`9{~`2Ml?Yzz!b z!EUi*XJGgYmd$5}>8)aCVE6_W>wt?*VP|0Y2^L!f7hB8D!0-nwwi_;XjGckuKUnMv zTuh9Efq@C!k=1}-QDMfxz`zO?bApR`b1*P)fW<;M7#O(0tR%Q>2L}TKA6RS>T<JkOdC}^n+Mn*`1sW44{IL zg@Kuifx!{nnbPKBU{D3C^FYiTfzN*~jJy0qM+UczTnnUMr{=rke9&{dy!hezDua|-j zGM}RY^0`Oj5s>rZ56i(bAZUB}Yw%)SE%;QSAp--0BsiL@7#JAT!M#a`;4skrs|Ju$ zsXe+s`hvRnmq5b=AeTGx3o~uoCQjhJS?3hN*p{a;UXsB z{sJSUyWlDCTE(N25!L|d4CFvC1rSULP_e85tqpy;&w=`hh$DGDAO`@)Lx%2Poifk~ zG~pnZ343%qgT}i{0d1bSPZm>*9Ocg1Pxz+S)lEBkU3}2`MMB0 zKx^-f!Lr*J85mT-F1X6bz+ej&1MQ2^1LqmgnnFP*0e(Rz1%5#%(3(OAenF=Ik4~oq zP{Wkdqxm2Q=(INmu&)m5!&5X!mJehOx)RXPks8=OaC!yJMEG>y@$8Hi2=i!s13K!H zKm4R;XElc-zo7R7;{%?@ok6=Y|9f^ifAHw7}*y zB(moE1hCfn2+&FV*tDKUPM;_z+rsyW%z^A5X*>c7*SN!=RsbaIK{t0nZWEc!09y~R zA6{%7gWqg&8eVK&gR28|Rw1bf)RusxBG8@>h*&6Sa1@-1jyr;eAs9Tm4|;S8x^y~n z90nD0;MD@S?#A%!{^{HO0W?tuKA4}s@5ilLu7d zEdsOQ_wVqxPXq~AMvCyaw6ifVboPOdJ?)(~0kmqj+fe|z2Dh^fbdITKZ|4lqY*DwP zNM{@Ps8W6bM+wkcKSzE6PYvjDTt^G&a$HXj=yF`g2kOE|Bu9pEncxkSa13!3au2%qLX|7WOXqhGI(p=EGT*vNC z(3o6jALw*c$L>bZa9w8`=-|}Wlcg$--JT+#9mCz1Ukf?%ukQp66CjNMOap7`b^pH- zbQCauE2xp@3mU6U2Q75!_5cTCG??uGyT*vW9kfB;(K14Wzr}$S6pWxlKs|b={Q$3b zc0dFp=wMIJ-VR7GI*4?(eF3T97jS?DqX#S)9bm!e0SiV4STK6Pg3$pMj2*NI#tuj@ zf)3ku>~4SrBj`{9NHBUJ2V)0%FoHGpy8i>GMNli&qjQZ)1L&TJULO^O?$?gZ|5+e+ zwK(ukJ!*XE|50!$&x(+6@%%WHNY$PFz} zoe=YVdfO`4K_?xRI)EDnp!oxzUg+H_-KTxEPk448;FkyOuN7fzxy0Y%%gDgcA>i@< zAZV*_<4@2TF-4DFf~pP=OL_iQP@V@H1ezCu4XJjY_URP`4H)`rpYYYb;L{5_=Yhdj z`-W$)0~6$YbVvSeA&mb(7Q6v1kq`I>5?}%gfbM(f2>9>BzfFV#q~H)pfdGgr0OEp% z+B*V3I|xOXLHy|;=>R5==7X#r%?DULpl+P^612$-?6f}c(Y=s~47^SQxdG&;lIRYE zgG#&+jw!M1fH|Z@qa%P3S=P?#;2FLE}u!)nGpr)Uvr5t}N_``X!~`7krOpq@wb zVJ37zNss@BL0XaG<~8i%9JDKskgqz5J}d-ZZ1{-@HoK|A%)kJ->SzWt149Y8s|6b0 zgRnrG${{Rc76yhL;GTvpoaF^)-G<+_$O+oi2yT9XF21@8uBgjd85jz|CfC7Ppp!2l zV!Uh&3=nlnYzz#$!FGV=VIeHg9XAjbs2+x>+YZ-p2F`j2XMKgUc-Udf2_3>5!#tYb zWFXI(xu{rxMw%0RyFY?@ClMaqH+?!&R6HEJU$`1S0iBxe;M4sJblWDgYp0@P_m|eU z{4L<^vY_q#-IG;#K;!2nc^=8lpcX*)U+sUOIVy$YE-IkuEYRKIpldroG$@ckv;k;E z(s36R6A<0))1tt?z8lm^u)fak_6u~s_;Jtye*Ejtc``#b{A%t7*~0uCW?Lz{OXp&c zkoDIxX~!MYJ+byrUo(}&b?*jM${x(%)$owxm=}WQ%oM#$BQL)axuUL znUEBg-tKM&8^H+P@6y=}v6jDy34HlpjY@z|ca4ezXsv4sXs#p&bcj-nN&@KW0A|Nd zGtdB`1$^A8`ACLiT-;&MDdmv4QP7MiWTyQdyjl2^fq`KnxaSJWwvgc@&@9Fya4G@? zD&&4p(D2V%@HSJ2VB|HT*e2U6jNkfng2&fFK$kN8^yu9PT72vZ+JySaqx+^y_gk0l zzu*FL1*p(H@PU70Hh7?*buwtc+WHTFGiVc=NAG5kK96oa4^TSz2s)3kdv*$Vv(Oor z?#ZBNbksiYliUqT435^P9lL)YcToY2MKkcb{RACp1u7Da4}i{v04-DNbW!o}>g56L zOJ@M}nH{??bvJ_+M1u|t)IJP4HUgANK*vUaX#VvfJfN}R6i_L9+(iYn&=-`XazJJs zcToWyAkE;xzy5+__o3r1DkTs-E<6by%vW7Nhun9caO^(r!N2~5Co^QA(1rQG=5CM& z_*)Y}o$~H((7H{?DsAvsrGoKWk50YL)gVW`d<8{1#N#7=ZGQhHv*V zkM3(ACTL>ve}DvNE}p;jGB}icR16%y9c1Kh0c~;f=w|Ld3+nf?cyxOdxPm4Cz=bkm zWV!nU{a{GX+0hhwK6s0Zx;_7BL< zFNOd8|L@q%*4Yf=xwf7xQ2;F)M{*jpkB@K{*npR}|Nj5q_#bRIN8^79&?Ja1bP4*d z4q;e6YCTYLp}CrarPLLoYw6$r|Bc_ioc9-WQv|aIk~J=<3wXdaH|)}{?iv*fm+lv@ z#X*}@Egbj$|NsC0Yc4Ry)%s7do=3ACLx~17Jdh*Kqf^i0I5;sefJX3H!CD-9LTzN+J0a)Ry-Lm*>Yo>tDg8IOskR$gx*(ObiU}U@=g7 zgosUHf*rek0<^LREPIIwHh>8_tm7y+KRYnPN@t($JK>I?(Kk>VAGL@M@a&9!;oJSw zvorgFPxmF`x4xap7koRjPk=HoXviEiSl1b%q5;msrE-otA`-Edi9Vp^AfWRpA4F2IUseiW$(5UpM$N zKc8M62Vdrs-j+FM1^8Q|L756tHyB^)2H)w|nWJKH+(iY{_yOfz2T+M{+(pF&nuk3= znHE%Nfava$77PCMn?dD@bGOS`j_=>Pe>ip@(f;YEecbv7zw;0N_2)oG9P+O})_t-2 zoHujGSq9LOK~LsWkVFJ_lJO-*uzOvY|AUs|f$p?}7-k9H=ulefdEDhJX#ST0;suXh z5eE?$gZ;+sg=23toXb85C5G|0jZO*a40Bfp)Y&%rn09 z|3K$zP>6ls4C+mGf;U^ZTK_52bllN_Jswbl_;n=Y3fWg;7J!gy&7x}XMffviV2clQs_@DhthcP4BK zbQh@D_2@ne4%*k8`@va}srxTzk_D6iK-oM4w4TVo2eg?SwAQM^qkD}CsFUf@Jx2vJ za0@E+m|Z$8x{rhMUjS161EqV%`1r#D@CF*{3=Cv92x11N9A0IDMqGY_vsg2HBqfj$ zc78wTEDSSnX8RAHSz%*hU|4ufd#B?4SfH|FD6)) z1uaVQ=v||d0GbGEe6s+&NCjMwdl(<_=xl9ZW?<<415oY`#=s)_q9|jVfO7*>uv_!YUk10 z4Z8P^Uw{!bvI`pi(*bWgYX;p|1RL3LQBi?zX$1|YfCh6wTUtS5IiO8vprIUo0mlQN zksN*j&kLY|9DV`E2cU5regRIJu_prISk5RL)ApciNe z2Q&%_8o~+S7xV%R;Us`YMfnB2&~{aWhHko>K`SF)f)*urH`jo68})XBuO-Cr1L)={ z?0$fT1;{Htz2M*)SRa8l&VbKn_fb*kbhU6bKG5yV(|sLuVNW-BUuLJPPB(bFW~Zw` zH+XMmr>jXfc$;>os}27)@V@&_R|o!W;7#|Pt}guBz&q|cT|K~g7*tF>1+6;o+@b>7 zA=><(sf6FN`Z(ur(fpHmXCy z9Xu>k1D@V#Z2(WgpYQ>VJe}c}X8`S52Z@1Z$UH&2)|-%btt%n#S_jD>cA2_%DtLCE zf2{!;tAcK2=S+hN3a7btf|izS25nmK=h7$qpa1Uv;`iKN>Rbf7{5@fph#R7Ozk00!h( z2MsjBVjVO%35s=N^jHUty%3CYK96p2H#Li(_A{0(GDd5iLj=*bgFoR>Sj>Z z26a)lsDSH+?ma4?y1}O#Qqy>J_kt=aq?#C1k;O;H9!A^>4La!rvSPswzAWAuUe|)U zhLGKu7vXo{fJPN0K*J3N;FCm}e=(Qx^*a3q9jp=nnwN_^4C*L3fVRbX82<;&*??AM zfm#P_pgz%SX3#ZzkQq)y?*nw>Ib>%G_(ls&kTxFB#p9+LZ89JdeIE zL}&!v{2=I}q5yIqXp{mxI>E2uqN0$-Uk?&*y$!l+#L^XO0~&nyFK8VkXuekgv?AOG zvB+nwMZmDL8^bS zT;MZk=8vP>MTMi?F~|78VaRNyL$F8l8v}k#@N(Uo{NdmD_0N^7e&&w@k2=FH^#05r z1vwguKl~)VZilmH!!|ASk^l>()Fz3zWO9YqI- zEL0e@JBk-947N)Fq^(57!lSp)0<_#(+HuEO>=)*~<^v5^gWAcEIZLp4)vyZ?FU-Z7 z$iR1-wmS=WbX!B0q#NT{g3(;>z)Tpz$tY6b4W2N-~6g;hXX$u2=g zAwX*z8Wm_P zdBA6ryGvA1rBdOYgdF&EYau)#gM1B{pq&L*2U-FJxe^O>ToojH9fr%Ef*<$|8cT=B z@-s3poCjwI6L<&1nh|!0HE7`nM4dMy1A{48T_}9nKsHO~$XHH518KMl0G8DckRxg)r<^b)is*rOZnj+da7-#*<29^LOj zfS<*ZRqCVKyfd4l<)$~y`^SY+jqsPF&kPGhBS~I{-F#s)-f}F1iO7;a{*=-CA3=ozvXpRdkmdOYcV`gGt zCbQ~pvNB8;e%iyx8eLkeQ6r*AQDP!Zo zi!vc89aNq}Dj{`v-ZcX)y9DPZkfo5y50ua$U2UmQtVL_LGsk|=AO`53*v=9agXSMh z<=*f%JLCU=0MM1rl?I45g(oa`Kr6}abD;9a08v+h7v{i@zCfg{Qcq~ifE$Hm8Vfl| z18gCvNfDz0t)4;4RdI(*5(5K+5x60d36I`-1_lPmh$Cp>lPOsC09+Qd>;xj~0E#>V zMC5^Pm4Pk%f}E8Nj;(HY4p68&fWt9I#RGImWB{mO&;S(-3gCi47~FXTpM&7jy&p6< z2CV~ccYn5=59()?xlVxY*M*I$@k6)lb|3TUHF5Af_>9Gq`5JUg4bybaOoFon*c;9Nc*h{_>Qo!u5fU{DA1&{SUPG|L|+s-Jq=i-G^U`>;~ny?!&LScZ1gLbsv7s z1j^DiDhi;nG7V7727s2fd4SRrs5Uc*C+^Y;(2zEz7<6DQBo%?yQbO{VGCY69GcYhf zhRr}d7SPQDEDWF-Dai4&-xy%WyZ!~Sz?U0<7Ja-2r!3H(Scp13MwmKNMh1puV0A5w z3=FHlEYP|2E5WQDMh1rEU>2y>fQW&XHA7gSN(&+jT3iPa163re!8$6|pT;1VolA;b8 z!<(c`C6bOic;;a(H$jJ*F@RcT-Jp9OKpiApO;XD`hf-?~%Q}xzQxD6!fKq)(d(;Bd z9uM8+H$vN;1zuMooCC5&5xa9BW=4R_WCfe)(d!)ms&1qmcaYl# z1?5F>(BW=*g7Pf5W{0;t;gbWHQd#geBdEOzX{VLJa~G(K2WffEhpPisyiVZjI6-Sm zAT2=9He5&^1+_OJn-H$RO#$UDh%6{~xq|hAj@N+b1x;u|WI^jcAua%2Is=gf9d7_} zK_9#|x)k0T1?3-zj*alPB`6QT+LoX`9eCr*O%G_x5_W8j4k&V350tVPp9Ec~j-}KE z9oYIG(q#knr$OCElpZ;#f=20)A1Gl5^;7Zox4|7m&6}Mjqh&25P03q10Zm1vA~}LA_E?t572@8r%{=ZpIh&!-zaPHDLrWI(Ab z=v=8@@S28m{{swK4^+lO*PFK%OG(`YDoDOWPf=4G<+M{!G0S^O1J2*PP=>?pwHR7TVgYFuK zBy7+T7?36aXv7CnM1bbnEWj0dE<8IS8j_&>Io4oV&`c+!x<3MMW`g!JL5i0)`2HhM z)7uWL15}hiblii>f>z5xbSN>wnjXGP3=EE79Z~Q;Mh<-M5~vBP5DMw!-UJo@l8!r0 zCt$6>Kt0&E(6fbXV0i?zBQ3R)8BVQC6#8kh1yTj-En z2dY6FpiI#AQIAgWe17L<9?>gOp?r za9Pk8JfvX*I$;tb8^Zt_W&@RTkYP4Zr4_VDMpvoCyG84R!1v*X$qJt0K$WjI!g90|$5Z)|v109G07E5JhV1T%#70v=R z-ymY34im(E^Wi!U!)-eSACf!|w+(b!J*50+VS)`7?ch%mthEajLO7$B}UXM*i) zwS(($hUUw4UCQrXvGG6+Y@-W9a*IHKZpml%%j)a0@RE^+9*ZbJVHFgpAis$1~yl7Fqe9J z^g0)S#!s9YDr6W+Y&?1c6g;{aJi04>fR@qefE+3eyW2?)e76(M4O89Dpu_z@4Z93* z1qp8Vg09E}4F~sbUckk`;L&&-G@tGP+BO5)wcW5!8`K#n$@1wf%K(i9Cwg|ffj3M> z_;h;+fM#s{LDxETANTC`6L9>0(Xn|SXj+S*#KN=-SAg2m@bi2d09g2H& zpGDr*brfkYp+~P9Y*(2BcxzaO1<7VK?9&B1+rp!J8~9wH)^8=)9B~fW5yy}mf!xSI zgb)YlZ0gYv0#}zF-Ma%otEIY2K+~)xpyR}HR5CyjZ4BC03b}p^blw&CY(>ysQz4IT zoJrH8d-n})&@DCpOUTsj0NS9`N}l$H>J)|&OOI}Det||%^5+-m1dVTkHcBgirc@2U zr53cWA_%IwEkIe4-J?51MdAMi(4`xo`$u7&S5Tu6)Y1!xJ`B2{&>p;Q6*S=tDNZBc zGgz_kaRktQAc!nz9t=_?fyN~ug)L~)ETj?xtyhOs1E4L7kQN}QvV$C={2jh|5md23 z#6TC{LRg?(P!N_Kyh?I~A8_CWuaXuq!me@xjod*tK7!5@f^2*Y11(PnS1>(HFmp}?$laIrmbvG;JXzi=_oE)R&k-k^#Etha^*W^Wf=nh{j69J&IyY)Y4s19^7F;)eTelEx_|EGgy`XKcHsI}e>y1^Zk zUAuRK4ioo)NWo^(2{RR;nw;ev_%}bm*>$9Z_vOy3qzs1Q>?YYQMHDQK)0V#-9g zI?!kVq%_i{4s_o=B)mXVG7uJMNE%WI zfEK+#GCXt(p#~^a9iV##K|L?fp1}yjo}5VDnSA!w-71+;VH3uxOIXxSR5 z%NCF}p}QM2M%H?=M7;Z`_A%=d{B8%ePcVPzgbJ4$xO6TCseqUR-n&;Sle0oOpMP3h1m(&`ugNkLDv9puK;fAp}V2u?}8RZ-wvp1C3rm zN(#`SV~`zxpgC8_jz6F7J7M4(+#2630FPRN2Z6v_4O~<*JUgQw7=QEZ%)a5<{nE3u z`hrLIP2bLB&>kV*&f){0L%cgfR1Cm9XV6L{@Cbb|s9Vh63Rk06!XrTMTppz1)gXa9LS0GA3 zi3VkF8-L5LfB*mQ0}bGT#$qeFkAW7{g0{hUbnk&~d0C?}0US|tR6v_sK$kzWxPmVx z@=?h^UT@35zyLZ|H|{Xh4W%62t3i3@HCy*;$OS}Tr~33ZgJK4B&H*EIdts?y=W0+? zbZ!QD?4|lY(8fGA&>@d4?*Bn-(B?e;7He=Sg)LhuC_vkAhnP+R*T&r+UTXgT{~xha z8q|q}?0*Bz*hA_((78#F{ilJTW$NHDV9-uD$imfB_#i!1_qF}OKDrOyz69OZ_7|@b z_`Wt!{)O#plSbOtCc?wO;M2Pq+?wp%0zS&B8$9@lx~~nig3lwl8I<-x``SRd$k^8g z%2%*`ZP!5MBht<`NB;HKUAm8hHn{PxKjz7N$(z~dECVEdzzzcKY6IDeWmg+WxU>*y zR~u;b7T2yekQ{9N4QN*zsBQtPJ@|;llliLS|B0Y`g+W8apm`ohU_f@Xfvowy88k`J zIT>UMbVr*$&K+$a4bZ?v+R=tAigiaD$ZR}2+Ccijr5WaqHpgMKqYZT5$R_Yo=Gly} z`LUnyt~3J^0|O&?>YEil8VkC$8IoCB;5*u8GBGgx183G{@ENr)@EvWSv06}p2)R)U zlGs3%Mz4>G1^=#B5|BmX5OEi9mqp_+=&UKoYH)SlDzh3)9Cs-Ni#PrYn&klUzj|10iY5Y+KKJ!N& z{mdV6@Nn}_CdiR0E}39SkeOif_``31=8t*p`27XCxh~oVz$zRwPk>aXIr8Tn0Ga>& zCCmnN9clbg4`8n04?pmkKjz?P{>TTP`6C{t@#mfV%>V4Tf!~Ed~4ug&n;4Eff;9vk9K?h>7Fo4#}2ZEQQ$1yN4fW$b!=P*LVKqo?h z#8? z;wdzAukmY`s33)~NAnSh!|YH`a4>{1Ffc$o0XmHniUyvydpojpu3_T)1Za(PP>&5u}Bc_YHZ#eg|a33~4a99`~ zcHU4H3j=6*5+v+E+s;5N4hGObABe@m09xJ$VR>|)^7s#$QeyxekZcDY3ptCQ!5e&h_+3AM z3l`AeJamlr1L#gECD1M(X3uUrpYF3zJ>Y=@*!~_5(6I}k@ssWx6${W@Ie1zeGy$C8QoVH*bW;K$ zpMf$faXxcmU|@&_yFD20Gti<lJ(>G%pdm@D&nJ3@R>jE;Aj50yZrh`%9;5!9`NfQ z2GKXb+9Dr*=8wGjnLp;pXa1PapZVjCfx65Api~qA+GqqClyitb3|h;#9bEN-riB^6 zMLGD?6_4gO6{u%5By{gl0WGX{?Y;=w@(x-l;o5zo`&IWx$L>px-6vgoS@wg2$D{j1 z>w!`(@H&i_pp(8p7ovb>lU+bZ`NgPMbYBG>vuV(M4m4X<;0n6puS7+|rL#mu!L`?q z5o`#kRR$W$1K%nI?i40FJAjmcPD$|rO+vxWT!Edqf_|h*w@-@)|9Wp1{`KdL54iNQ zaDH&%-&icteWLpU|E^E+jSm?Z7#bkGTk+}($KtS^+aID=GiUu+MS0CkYTvo$^{3Gg0J^O1`9 zxY+2!ppy(C2X26>r}^M?3>rpzB*MVJ!T_2DhjchVtrXA*QCLHz3`0o(XdPm2AY@0M zM|Z>zPy z?f2;Z{F=j~`!gu2;D_L9gATz3E&u+1;k7tq#0)gI1rqV-J^>N%0AGJr$_2h{ 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πp`DD)%875Ofd>q)r2!$O_4Tpb0aGENE+`Gq`>L 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[IF2f{_X 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
;kpf5W1wHy1;5s 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

#y zsSd+HNHW46ZpdQW z{D@r|Q#)o*WArj1u0m*y&n%8dtYt2UFH6ipIma7^Ji>tT{G#mCqIk%1Iiy^QCXUd6 z<{8j7IM4>f#DdIXaAy?dgj!-0V;T((2jUFItrXK(XzU;xjO1ZFPDMVF0H!RnI38{g z=$OuoqWrx4QsllpL6ry>LJL~Z@f47VhzFnJisU++DiDUi>_b!@#qr>+P1uTdViY5c zE=bOeFSP)TJ(OA?ISN%6k_*9wA*uu<_hONN=m2MHY&x(=U~^Kjp;>%|KgNI#lD}p$ifFg)VXnsSt4q_4kML4~dUr>^nn^}djR&5lk<3*THPXC5!6dlvEUpap!h?3JDmAE8;PXL{f;?NSGI)2?)&`XX0QZWGDVhxSO||?WGRR) zc!i8C1<{373cK?_MIV9CDn?TSu^yfYVb-Img1Cf`Du`JazQk%4Ayv3NNFW};9z;?D zaTHdkBdNmUK_pcWv#>fHNfpE_LaMNO5L`49h(UBUxDCQ^6KaYF8-%0=)gYu229zjp zR!w-+;WrS;W=M!ZU59QUk~(|_qGe1}Q_xEnl)?wE_wcGjbq}UeB&VZz5wA-8hN5{B z-6V9678{vB`WCqB2<%EwU4f(qm!GgJ!DO=BJw(t6hs%I^hT0`=)x+6)p_8YinCaN7>uq4Vm+eV!>$VA5<;pV zW}*8M-A0I6gjC`7AW|ejjDi*j&=di-4oMBfQCOXhqzYmZAyp8wusR*XEJCVKJ&0Tm z8JXbDg?Q9K90&IT9+haG#iJ5pCfqA{R6@)oMkRiaV}v6#cG0ayQU`G)PLCt0ggBBI zl@K#=dI!TyVpQVuI9hp)>RVW!0Efe2dT~nQ^$bim4j-V`6sR=`zNCs*CB#YK@pDiy zidQWp)`(LJF&8}2Or*KQsYQ)DOy^=G2xzj!s}kZ^JRZX^l{mE!bMbf#!(8Ij;tvE^ zLc-y5SR%&h1(<$33J7=%W&%#%peJTfCxB4z5p**hMh6q(5O{{eXAS{H5R>4U51&Z{ z6rsirl0g`;4UHk}N+1rx=4uRc2q=P>gw53$CJ|7C)ob7u8_w1aB-+r`;5G=uNvMGY zHV8=#c7sr6UO?p!G>D5q64-+SECngy&;tQ11<{3Q148`;G7zE*s}xq}fm=od0t{UZ z#Cr6IMpp%K2_aPwv(SBs-7G??aC;Cbk|54PjYzO{NNOOC!s>J+RS=U1se+h=)#(^! z5mJTfLFD!T)^QT3C-JDmZy<*Ia2SZB4xfRrk{GKg=%p}f9f{9-cvYgh2U97M(;?9b zbsS!m_zgw#Cb~)J9tEX60@V$6C1?o?yCR6E;OPpxB8W-wDi*sUh)Dz#K}>>IwfIaT zpa^0TcufPS%*1CB0Ywm#zzeMinM6Pl#3b;dD?%m_P=uO-kUWPgJz`e^aS*oH#W06} zB8W-YVi&_C0*W9eVT)Z1lL#n+n1n5MF-#($2x1bp*u^l3fFg)V*kTvMBm#=CCIR#o z1til!%Oq$TKvx5C61>L8p$cLWAyp8w&_fg5Mu=I2R6)!_4^Ql75mE&)3q3@!n?*~9EIUQG*u8s5mE&)3&ZJH%_5`h#3vEyz1T$q3QiVIidQAX zNpKhARSOAW;?zRSh5LmFbBR-n8h4n+Vk8J?vc;PC70%a z&Mqr1O#VS2&saY1&;x|W)V^aF$+BQ z4GJT?W)V^aF$;W4JwdYwsUj5c;1ZZXz=PF59EB$sz^Wi75mE&)3!ZE-0}IJ4LaGoR z%)sA#z%GrZ9n&e`R0r`^Zel#x5FCmiz5@FlpE8J91eHNd1N)w^X#|xK^d?e>Y7P!XkbnZW3o?jnKHyUZF%8@94nsUDpk{y@2YAB+j|xPjCV{dzIM#7>S#YXAGXzI*0`U;2 zXAQCmha!ldz-12DIvmO%W)V~dF%4Xp5H^jVGJ@VjEpu?#2v!7f7&wjLQwH%SL1hrr zz^RR}X#|y_coUo;P>KPFn;_AMDT}HZS0;z*#*~Km#1Ix0xU&`>6;Lz4ZPcWKG~8z3 zQGxJAUVce(2KK5J9uj!eAPg!4sIm~V{-C~Gg5QX2$+Od6~eHLOkxbfs|sOQ zdSYf?Y7u6VfCm#U1qdU`Qj?9-;&W2-kk5id7Q~_yX-oyK5_$9$BAAj{T!NtzB8YBg zUVc$-Voqii^1YT2Ly%<9H6<727bAD$AsV2f2*t=p=Oh*sRY1q{ zAl)*GVFEZ;;57rB=5eTiL=-+15O;vfBfMtdQvoppTngbe1D^_r8Q^LSuNnAMAR;BP zq$D-36mo%hYDGbQUTR)RCdz;aP8A44Qt}H5a#D-p3o=WRGmv5wO&p;C)cQ(Eh1>^* z?1{AeqWI#1M34(It5S=Qj3QbGBzVBJBhh*wkx!Z)h&|v$N1{EX=|Kb};7~*u+#Ju#Scnk`cry=2jR|z75ax(K$6N}=DQX!XK6vZc|WR(_|#Fu2| zrWS#wtmE@i%fVM+AVnG}n($avoLT}lIUd{aFuJ?2D8^$n?u3isb_}I>jLprgh({gU zz%UN31dlOb{}$wf2kF7*91}7WhXzPu09S+$p~Ix1f+iPgK~dFdXhiXoYwh+^!`yx)GWo ziJ45z5SvMgEp(g7)J$?vlbDUb6&S)2Xwy40FFhW1_%5hP3hBPZ$3uc0$qFLX6SM%_ zV*{UOPP83ZwBWS{)S)HL2dFCX8VWt}m?%f1tA!*ga6=1AFrur5q+eoHL(B#j-9(#B ztZIna;N}j|W)rI#Vm3IJ5^XlIsv%~B^C8h@6RR3xHn^KhwAsX}hL{arok6tO#Hz-d zxM2k{QK=h4FDhu8pAlCG5N!yC zQ=n4+WorqN} z#Mi{Bg_sL2U5PZ8IJFRS!6hq^<`SnCVlKE;CDL5t)MAgcq|(fs6woCpX=ypB@#uL5 z*#THpLL7^c=CP`UL>h5wA?9L41RisVQwuQ{BO>saOPpGWxfl_F$6VsnVvjV)JX~=` zDI(J1Q7$e(jyGJ2Ax_4~L%5VfVvlI$5YsUt1;6P;DBOriC2$bt6Bjrj^&|ui z%BFCFDF>t);&|db3sMgWdg9eX%qPyX2=j?oPki8j`>hb?6OnSjsv(Xi%Clhg5YvfQ z4>6x8&mx&myn3Po2dT|TAl1M&7Gl(iSjqyJsl=&;1O_psHOyS%)Zz<--2A+JP`QrV^!;U{q!1 z5f@W1br5Ib^ASuXB%X*-2{99&e^AXNMkS`lA-$54)S`IkNE)6_1yXTFoHmFTP_hqk z8X=)XhDL~uDA|A%8_CcJu@NPUkYXbl8X-2KBq364Bts*7p zYVjEi8@7ROq%4jnF!F$~h@f6lErvGtU^N2PoPfx1M39K{4Sx59PeEnx8KgxHCrpunpWVkbE| z5doW$Sds`TsmoG}kjm`LVx*NxMX80QnMEkaW{{+dkY%7uoST^fTJ)5gh@4!o+k&nf zVLWDJL#rV0EJs>Ver`NgBk|@5VvRsJ13Gezr~!$$o^-7UYtg(03MTBuC?SP7jl`B1 zu@Bv%SwT=EBoBbAE>g8ZG67jyA+~~>Ipo<&mR5+Z;BjB_Y$Z!8#8&V;40*Par4?c; zc!q{NTglQ&QUV5@4wP6DpP!dgiBt{{;aU`p5RVd@3Q@F@mVi;TLTm-Mzesg3#8$Gj zLTn{A?V~stVk=o%A+{2m_OaMXmR5+Z#HM{Lwvwflqy!ABGvdoLOERF9D6~cg%?**3 zs0kVd2@qlvIzi(g34&_IL0m^{swUEPR5K3ZI$~2lk*=efaS+!Ln@fmv9o3A3xQ^J| zM5ODeW*m7Lmh?IY?k|W|lB|Wcj*H2t>d>@8;sV@DBBI(s(+tV+WNL=k4DRueYcrXe zNeXIsG^J%0p=?bg!qMn@NwOFo)?`?Wt`}i(Q4-|1tD@Azl=y-YTfCQUJ)P#tn#L|?^{P?8Ow6s*v zPOP+e$nXHv>ioQTP=ZHFJ4C7{!2(bx9UO&_mLn;4VQ9r+EqL%DwE~yXaA`;cfMX5C zpKt|8(TZIG!~}3`U{?S!0gnQR3E=h?J`?aLfS3So6X7!fj{+RQ2wkI!J0Ou1KpX+i zYAB9CQUVDIyhNudTL5Mic#q4y*M>B8!4d>tpnmzuy2UggA@Wp z>w(w?{Ge+W7L!JjAM#+{AdCDiChLP*NUG*cgNw>;@(05rn9UaD+3eB8W-gsvp^7sEQya5l{p%2_rhNnM6Pl#3YOe z!DbQxMc9KIRGZ>Y(LR3K3ppAgQnr;RBU}Scwq^P+1%y1eJy81-D9(0~aa_(ThtKq8B3s zQS{=Hh3LfyLlnKZWU&VnD1YJzCx{ewZJ->9LmNa2yEafB#Gwr$h0q3W2Nc08eMtU; zglukNJXTq}nn8IGpLUQuP6MD>5tn|bG*0cv$r#h|5Lrmnf|Dbf=OFSByK&1y^n()) z9{srGA^O3I2akT-@(}&tOo~T8Zh4%6hmyE4Lk}tqu^yb~(5#0lfP^0&1rQUk1rUk} zcoaZPz!pR(Cg4#3F#%i^O=Y8OnK6 z$RZeukUDz^bznhErQq4W+|-gp(0CH87(vp7CXdAc&@e4FjSz7h8sdvn3rkVYR718D zq!&dAVPoP!L$u&MjPNt}@R*3A8kgZ{euqRAhM{PxPz)pRvLqGF zfjAYR7zG&xLJeq`7$k`sszBTZ6NR`JT@<3$2&-CjQ4|M&ip=<;M0lGFVi`gdVz2?! zV1zit8JOY_^)M^Z)nkf7)Enb4A5$En-UNqwOmR%l;!06qw<1eItU~c3vNVa927rpp_>z1??nF)R zSfo+3L-H9$8buPuq8#BNRLdZ`kYyo34U1+>c}Ps*mWSwvWk_85am(X%JUBKWE`Y=q zy7^!Q_?-Y&0x<)ZE5J%1rofUUR*N8};8lX(JD{`yF$9NqKnfrZ!Rc0z5{Mail|W3v z=~jd(c$J`d2d#(zolk`@2I3(cN>Gd`wE*P?q%DjPtw_QUx5LsLk_5!xSR^1iU;%=m z1B(QTlR%g0mgIvxgqmHD#UWO}>_(PBbrrG%Pqy_K2#ZqRWMhe%AvXt zRSu#L<_=Ujh(7FcD2_x=0L74qf#`>XKMo}*#(-y?LGxXxWfPJx#I-PAB1u53!Xg3D z0rLZf4lEMbodoWHL2Q9|2yPTu28SlJ<~Bl4Ibvrjx^Zwl7`_1w$l&k~NDAUQ3?G4{ za5yiu0wf91hwcoB91e|Wy(a7?peZ88DBR9LF$|;(mvNxZCyw+J4{qzgr`XVa43@$O;s7_}qe|5YaG3>q8=?3pBG)wBWS{o@}st3o)~ZT^`Y+2LtWw!>n0>a z5iN0$E+WlG)r4tPZfb6R5qt$dlE+{YxU_)tEQTW>y1;TcHKL?LEa?nt2AVS5#)0A+ zk1-Hw{MwOH7y*+|R1$3{N*cr!Xy};-MH7lupmA%&@&=?R#%L(xS4o_q7}YvXL%}ME zGqe~n3lB+jI1L4>#55FC$&nBS_?44jJlK!OQ3!T8PUFGKNiZJlTjGreD<{QxLo@Wk z1Hb!0w<)6+eE4;MItXYbDN!0o@rAJwiN1hnz_bEd*1(qv5zI@3G>~KkIB{S^HXbX$ z8c4DNoIpsi0;~aUMOsc`GI%u=qF6){!c-Ptl$w)Rf!On!QUq6ykiw!3t+0nV7@-+W z0U;w0mJu)tp%B$b=da$K#NNjyXl8SPjG+ zOI)4;tAUtUIM6_OgD=?Td_$i{)xVKER=g<}f^m<&!$;B7AP_&fzM0$m*z1F^PhklWpefWoPi zSYr`I9CCnRH5R0lNMn%-2SUbzm0~d#XUiPi(7_SMSltZ@Ig%^@d6X0jKcSO=;ld8rj8@yVsB#TluoCGjOi ziOKMNAJFuPRR!FT+ydj2)Z}>39=w!P$i6m2A%RsASu5HCnA`$ms0fClct`}Hmgvz>_5)KE$t*I73lJA3^@W zX((7FrlF8h3+t#JMmYdBn~(;QtN=R-62?ejg2xK5223kJB_;`ez^|MH-n(^h0jOs$Z54YYa_dU{I< z$_25Iy0$1DyD~_=122HauMiThL@0!q2wsj$jEO`jgqR3k7jyz&9f1CFaCK_pHaqCzlqNL3f|z=f-1PM*<6Y$mA;M96*>79L7L`50uwR zQj79Xu8T%73sW7!K=hT~xry-zAxvf9_9jwkPO5kOlO1SaCr$N2hoS=bdVfGA9gv2K1}B$^kJ97 z;yFlFiY=~SG7!s9JqMG6I1;-YL?3t`20Zjp^q9{H&F((If;aog;Q$M=RnDTfGz~*#h10eFK27t>zSS^5NJDMCq zBe>8*z9|o^5>%ccnSf6bA)~;pJEF`(Qc0AdpbhMaWdxlGHWsFqkin2F5>J$SQPe{6 z8#wP{4=ohckhDdtYKYn3@_}fxiB*j!Kp-O`1j7PF8JcmaMUXZYD8FL6lN{_F9EuP| zfd_#>=@Kcfk;Jel1lL8_R6`{oAqozExIs`Uq^Locg6IO5x9Cz3U09_cy1=C^c3oJd zaCr(*spTfdqnZbjf>?&dV<1V0BXLMVbYk%VLMIMMh)yiNKRI>9{`M5v?c#32dM3GSib(uqS7EqE}hTZn^U=>S6- zO*^Q{0Ih+Bk4d42F%|`AM&Pa^!9K%~M$?`GI(s}Z1>Ih_Fs5>lldvd<2xBV8Z~$ru z;jhHOUPwW%WI-W{VLPS*bax=DfVdRR8JG$XMu1C1%=#GQI3%awRYk}!aD@miI|!PH ztduBY%i~Ef7_OL*(U5#bq|vB~@fZ!OxCn(UsyZ|SVU-ui5W>AmbX5q$K!cHJRUASH zQyEHSm75q3QwtS`lsVuw0GbRWDPog>=mD41SoL6&f#?Ajd|35hlfmH`L}h{Q9gqyf zB1~t4@ zXbFct>~au&;4|29=)*3DCGsHo4XG%B8wituScV#TFgZvhV3&jFLyaI*ec0t7`cNYX zRUdXah(6Q^Le+;|4mD69#RXb;zyu*yfSWF$V1fxlLIFbWy$H7~s+BR(?^w;w>J zU{?kSH+XR3RtO0bA{0VQgaScI~3F0DXEWt%F6oVoFEkR-#2mysPwk8s~ zB_QwM7zjZ!0ut7kwt{rSXEh4{G%gbSBtXm|8*xLrQz%3`S9l$6!b~OwddeWst-TPWl*u zkD?GMkKk7bF%ewP;#UYUkqCtl6Tzh+F(wkB5G@d4O>0mx#+Ez4w&GBPFsisDsVu)J zDYF3Mov`r69V%osvy1~qzYmdxFW-2Bg8C1svu^8XSNBNMMxFIEO6(7 zpjm`eLCgYoI0%|WNEO5^OBIv^ha>C>se+hgh?ZP%7)DST)HFg74v9)bLdviuR#4Ld zS34XLuvk<%iJuz42LjznpNLL7$@?-*)u83bu^;_N>l z0uN*&cE#9?hBitGITM>QJjOxn$691K;lg&S~*&v!`hF~HX^}xCZ;+}1Hm~XJ~1aJKRK}^ zGd~Z$FaUlc1k$oCe2TFc4K7O%_n3gnBd`$05(#k7r50h_e1f3^65NQmCq@r?dLc#+ z#2!RE5u*oU4{3TJ_JEthM27*y9@6wc>;bpANVJDEJy;S!K~XBGkVY@oKp_g3!lo_1 zxCEy@xE!WNkRHNw8%S9P5;TYm56fgoN+2PQR|&)vl*q^K61++vrhxlXuxP?=3SK1; zQ&18Jc2n>w!3up0tiLkUv?30&ktCOng?I|4p{&ViXOy2?w0=uq%L=04~6>D}b1QM*(&(K%xt6Ng=Wg z$O^C<0f`{^%AI07hGAC<@f*18K(-mXVu(M9R17g0TqF={GLecQCSwFQJ~tDo7&DNu zSLV5i@i0H3E5I}Yk&#eK3YZCK@(|yFs|_3~AklzN1;h*-5ryOmd@3Mj;D{0=Gw`W^ zn1PYCATfnx20j(o0~B05Aen#^BcSj^NF%hDCzhpxdP5jJAJD)ZQlUve9T5g%bfq9M z23azW-3=I8uvk-^nU{)uwkfiC2svCD!JFnV9Rk(}mcybE6f#Jo-0|@s)!2dxheAmF zgHs4RRBS~9*An`q?_(+SmcaK}IrJJHG^rV}dQ&`c*(!Bgk(7mPLQE_4h%868uZULwzmyw!S5TBD+nO};0kvzme40&_|Fe=jW zjLZ_`Di>l7E@hD51Q#{<6+#jL5egwDf@@A5Idauf3s(^HG$3o;8*b3k32y!?{Pv`om!@(w&uL)A!zm5InjIAPC0 z?E~o|#j>=_yp;IdOyn|vuw4jEBwLl4mtO=rGpD#TIXShsIKPObKm}Wj#RyWIQIwjS zUxr+*5%w=k3#5<&H;#zc1Sthc)I^GhGV_v)Qgc)DO5%%B(?|+VG@YbanvzPcrD!@y zu{5nHm9&V1Xn;f;xS}Q;Z4fPxNFzxL#1?S1OqwkuX@S@Ru2V^~g(NKyTfj9eX||B0 zg_Ni-N-ZwUA+1D&X~APnW^sH;2B<*`S_hVzmzb1;IENiuwTfLmJ`3_NS{UFNBz)O4 z6Q>>$ErLwrkZKi77m1dYCuZV}9&EmX=pn-*@L6#%heGly*66|#k{~-#wBxZJxl&Im zE{V^{M;u;?%|7hvNwA zF-r}+`XJ>Ej^Y-tPDtdFqZ48$j>4A+JIT?BCt$(F0(4Tn2y`r8T4p-F_`XT%<&~-uLpK!YjT^}UHk**J7AC8oo zSdthI?cHFvk92(y`*5UEg7%TF4`LtTW(Icuk*<%(!~&@$KpBd(q=~7INDDzlVLbRu z>(q+W>@Hr!F7=mfTekG zZIC=fs7U}$|46zZ9wS_HqU(d$M!G(TeMAO7k~<;xk**J7ACXajVIS%GAodZiIkAT) z>H3IGET9`7$gTUZ>Lt?RWhXg0iA+GyCMK2> zQt`SJLmM9Jic-@vi%U|A;z0=>Rv}?aplAw-GBOpkaytcVG*OuVVFz}d#8?Vhno5qP zICSE%6clFh#U(|liMf!*6RCw7UcE$GoLLMX6i6(IPt7CThKAaTMI(_`f)}xZZ!1B$ z765yqgxZFxhe(S+B`;_Q036VuC9*A7D`QlxCKu!PJ&w~HHE~)0@=cjb4Nbj z%mGWR*v)_h3h~j5-4s$23wBc=ZXrIJv6}*M3#Fz&+(LXb<8uq8rjU?WNNIs0C3h5U zL|O+bJ3$@#_~Oi}R8Z0c-x7s?93C{x1GN^Xc2cbe7mVN~62w;+aLZ9O6KQo?W=>8# z$m_(G*GL*jwSwsIgxgU}c+?yk1-Xd@#QFfN6p|+hmla^ekZee#Vu;CjI=z@KCQ>oP zWWuEZlADQC3^5r`PXyD=L@FjSjg;mgIhs(D1*({!(fOrC$*J+sU6_PZ2byvsjVH?8 z5Tn6LAwi8VUXT<+5+0F?AtvLC7Yvh$RE!7;@CXQKo*#Ql54$wPVc-?KcoaZP z0EYuU6Ywa2m;m-KJ`?aLKmp|i3{3Z1=_-iJTy$4a)j}@nH9+9 zBHca*It~cixH#pdE>&7XK@H9A5(u)!cGQcBGnUDkJ zQ3s#!sKIIwSbafJK4^U$Qivjz1Yi{qTft0him@7JbVs8m&K_$CAB0mIU_zkFFzj1@o1|}z|KM#iBAtsixNvf7NI9r zgrP8boCbiVPH}68$l}zTmRVGcybKA=M^I^;+6hz<2wz|*grrh%d4pyrhEhl#AWA93 zRB$;&oT)@9Me{y1nn6c3pocq(6q+_r13o!1IU_YC9uh7ECCH0;!S2Pb2I4EE?3r2; zkIN)Nsvu?|0u#H9=s}KM6>bkgvkZP8A}fM83#;Ril|kG^P#MHDte(U)ji53#ZUjiw#5O2Rg%40aA)r3hoe zJxl1O4;=cC50=5tjRY!x6~eG2q71{U3Sn4Yeo1l$5gsL= z4q;$HYGO7K=HXX{FfJ#*I3qQOsHh;M5@BdYCb5PRREaP&B{e6pk_ev@PzT8_;JigZ zB~mUVpb}yxI9Cx+2{Drxl?acg=A}dSu%)ENmx2sPjW5UqrK-%d_@Y$so-yPpBjS`} zH$Jl%Vl?)}^2jD*Rf*Hk;)2AI%*33`D#+oKnCtM-jK-xNy9J7G#*^l zA(c+Zwh+>U-73t&FFqb}-$4!zd+_RjBph&JM0OiqJ)jhjs6v_^h&|wBN1{EX=^;MQ zit|fRCOQc@3#uOC5u!W~)j>jVL3KdvAj;!V9S}Q6(E+go+>j&E=MXzc(E+go++QHc z4pMXwAK~dm`K8Ds;e?z7Rgc{Q{FygC9;z7!(Zta;fEUH2fo?HJZXIJ;umUlMkaDCn zN1O(TH^E7cI1LaRz_WeCX@J;3f(G0{1U|Eyh_FFZ2XQjEctCeDno5XUiBSnL6I>h+ zWhOByp&m~u0^gmRR9v1|5MP{_Tabg;iH=q_#EnZaH@h`Pz~>JLQTM>2*oJ0 zftC1TL$mn!+~UNPg5+FSrh}M=UnOBfGjkJ(Fc7Q?#V~MwfE+r9(U*m|47&J~^W_FB{aKPeDxiLY#|D9@PNwTA{T3qVmKdc+(WB z7gY+IHc(+yT#{G>ALT>Qi6o1v8Jq;ta?y1mgi)14PoYJPbN6<585L8UqT}_~P7x9ME+kaPQH(R6@*z2L(YhiBXBg zXYA^*7?_xxnL><# z*wvvL2v2~?`304r6`)8HdgwVCMF}M6;E{pP90H0UCc(oNpGgE1K}<4&Rsx9Jj^c1g zR1#1GG0B)1lL#n632x}VM^NU1p39G#>9NVTE5x4u!cH6oMZR1S$4cs3)}d=fN3Y{0CLa5@Q6?~tGYVgqJH zMZg9UG(c>C7aYWf5(ye0Ho%J!Vr?Kn1H=Y+0YIz`BxoRzR7>-+^YY8{Al*bl2^CE- z#PPUO9h!1T5+YhT#B|y)-zD<=>vMCDtkA{3+0R)azsHgG`{rQjh2e8UY$awr<( z!6OxDkp&gQqA)(G5^*{x#6b`fpc2@$lp31lfs1LxsmdriQRJ~2U~Gin0AzWr2AG-P zHvm~4n*nLaHbBxIiWgAiu^EsLI^Cr-4}By89MGVe3_2}^Pb)NpvDgkX9I67ZAt-^3 z&ny&$_>CkGIB@5cA}hpeBuaSVb0vyGyhaw66oIBNOY-B>GAmM3AiF1$O5tbcp~NTL z$+&eviWGPWgc_63?jxvcirqHS^+D`|mq{erN4h?|feEWyu$3F|cDqO2S$h+IaTE1hLJ&|2=T=wDVh1G?m(AA(Fm^D!E-|JDm>oJ)5jIn2;8c0 z7zT-Q;*7+umN>Ek;Kpn@d7TrsInNEk&MQcgh9T-q6Ebla2P?XhZuo3FmWkFF%oPgxGF6wEn_O$P2{0!N0EROSnz@zbO#x#9;{LjUGU->Qx{e#6z74H3`iq*`A{*) zneiZ(p}G~fDu@$cH5zVpkOG9TI*56&>YR{ygw;XJgM}U;^9ZYhmOe7*7?mgq3Djl)4+nr|-5_xSsSHpgAkKg}7)1)A2dflB7tBdmbYYc3aUOcTkY;Q| zL^B7W3gQG@sSKeG;tRs+Am+i`O~`eG)j`a|Rh^@|kFYw3d9aQ#A@>nhM>sN|Yuw_& z3(g?H2uVO#Ek{ueaV*|IM^O(kop|*S^Klgi;K0RVKJn@a2M%)U08gkultG+}t2_of z7ordn4n!z~n20yPkWC~)A;d&nr6GnJiBJeJ5mtU+O*F`EBtjv?M7((q*+e1~LQKS) z`;bi}LLuR#gWNj6lXM`;AP&TvA|MKhNIDRO5EJo+Hbf!BL?RSIOvD@7$R-k@5MmR^K2Qd#8u7u1ZtPWxxu9S-IU&87j=HW`E=;jet zM>w%yX`Ns%a=@0OsD?NeS6c>CJtWeJR}V2CZ=|BQ2Vy?)>Inx9a*c~8R3OSA&c&Oi zAqpYxBtjv?M7$nCHjxO05EJpHYh)9NPzW&*Z^}kCkqCu^qXoIf#S<+MWe^AAE#e>w zA<;sFLWqfYLmSydA{0VQ#2ebkCK90#Vj|wqMmCWMg@mI8rKZIbFHm(5C*lons7fMY z2C5QbCf*Q-s)U$Hj7o@^c*7jUOkz|*%)}e&C}t9)5+#Bl>zR=zZxZuT;*)doi&H_< zIH-fagwzsiFwzNVkl4p#Fjy_I27}vJkO(7aFjy_z;Iy2?g?pAWD7)`g3W^np(=|{ z%PB3+h)*s`MqX?WR*ou#rmZM7CqFSIKF1JpY7t~BV-C_Xdx#M@l|j6U2nn1D(Sr=9 zLWqf=rUU`2Atn-`5Mm-I?-FGq5el&fBKX`Vw780g`V%S#aS9^pQDq@M!X=AgX<|xz zX=Yvta`y%1K_n@NmB_(_Bnhz*ha^NNs2s&&EJP;`No-EV?NX==R_ma0SZ#yKLG&TV zS|aEeFQjn6E{DyLpzuUqj)UfLkQ6R$nE4Le2LYJ~l7hGrOGtwxA>P9w3DJoqq!BuC zNaAuTdVvBd%t0=-fGujn;ug3r>~c7r3D<|kl@>@VWRSu-Gp_`$E2RiS0?l9G6&XpH zB^ZS|k_5zch^&Pq1qpntQV?ATcVpwMFl=!^T z+$7}P)JSnyim3|Cu;j#?P2IFK?lA;CSXXTX@}lq20gO?a`0_YYH?;tX<|-%JlNd$ z;^f4f#G?4B)S`TpJNsbqNuC*GI|SW}#9I$5lMt;8QuIP1nLNFa{EsLaNVk_fz2pRP zajJgVfVT@#Yj0S9!7?^} zwP*(8cYkRf^!6}xJBZdsf_0?WfssqFdm7zHT&nQ95_E}jaY-Wb)-|k=kED)(fn}*h zNr@$yNXIo{HxQwYfPvsGR_MM(izLXdFN}~wSCvzcOvoUx5;PZqgBEm3c|6!F;I-ly z=OlrpAU#N2O0gMRT$)7CJPb8x2En`uH42v(;nHZ@!HsN~9q5|TB+;~jPDe`4NX*Mi z&4HY~2^lzp2_+U6XQt=nreY>95;US&3H2;ExWOTZ?qVzoASD|{>5gdzUL_DyFlt*& zQ}8N*n1WFQV48wg3B(kPrWU3tc$Gj*!KfuLO~I=KVhTnbfN2U|C1}wMPIs6_z$yX^ zX^0~bA&;Q|y{&|y0Ad0n)G-u5Ou(Z6%?sdS15~j=QzTlkfSzs%Dn~OOl!f54AMr(r zdFiR~`DyXVrA0-lc_m0i6}m+vXhgFT93=3KL0EGiRwX!$ArK%iqcD_VGY)$8Tzpa` zc$Ep*ZP*9GaHxbtKY9X!oM?yLRN~Y^%tcQ%_{=3vEw(@?$wzoTK0c|av?Mh?Ex#xk zeoqNX_Q9hXhvA6@1x5K4nYo~w1rT`?wD1}A-~g~(JS_eQ=|Zy%np?nTfTJ-Ul!w5E z;fOj&#)SEebp2=!09%C?IGK5gMV0Zzsfk6&8SrhD=oyOyjU-x0s!g!Y38FqDHdm z=}{boRUMk^z!Sy=MVV!wG9f-Mu_Ut$qqM=U2GyYa^mNd{>LuVCAmfwrQ!2r`%h3W0 zrz$LlfxQa4PZAo3@rh}myFHQ<3rb3hQsWB}L05esZ2|`e5ZD4@brNAIHus|&kKS#` zPft%Oj)!z53Nk8-Gm{hHXU#+72fGps_kfH6XAk_wpeey>OnN?S#s(CmnBD?eg{}tG zAkb!Ju-Eg`)6w*!N0=~q7;%=~3a;$}=Vv;O` zWf&4;7iJg9+Q^Dcm~AA+BFt|@Wotwnz-%K~8^mu!Wo%5_NY+Mt63NX39lS?Ya{yB- z#FIqD1g2(4jv!Mr#Ac#m1((faY9=eF!TA{CVIp!Ql8eC_Nw*S~Y)P;TmOPKFV({yRWHxejL+r+# zUWl=qT;1daIJkI#cp6s*$KhzOPBJWorE5~GgB2sVt7B}AgxN%@E^=ZKW*2F(2lE&1 zJdV#_r0RnB3wMsjXBVluh))=ZUIMZ05+rwG(+u$@QCS$9c1UI*TRX&dqLKk_+sW2W zX0U_vGQ{IVWJ?rxgSC=lEiA{7WFaiW5S677?u6MzvNp0}6J{HUu?X`UQP~>PZzOAj z_>HKHjcFUn+K5jgu$~ba#Q~;Ph$o4P2~5q996_dLh|NUB3ND+;)J#@TgYz-O!$jmr zBo~7l4zra`Mi#+m2nn9XXAC42 z5fu#hjDfg@sN9Fo7>H{qHip7z0_PHlQ;10MSp5Sw0um_jB?uT}Sx9Xo+=f8hL7^cK zmk^b$2)TqpLnw(FSn)tsq>xk~VFm)sZZf?Ovz^SO4YQqW?T`dOR2_xe?__I-*iKY4 z1h?&EYsa7Dz=vp(Il4hWFT~q8Qw#y!kc>~RZiwAD%XOmcCRaCk0S+!QA)dyO!Eren ztdk5&VY!MF>tGp~w0MEpM5-=wVi9H+X|adC&I-MXGg$~S}utI>OGyt=QL`{%jBq`ou zHj$`__@KjVLz7Vq;4=i`X`+%HK4Ty`m11Kct|2Np5_S#6#!whd;L;BgFhrzyto{KT z0SOeMk{E77Anu^h5Qt02k7clTATFWM5K5v3mTAd~6p{)g%;6Aurj!Dxdq1Y87W2akX^Z;Ex$D9f#?BM#@O{>lRjEfS3T!Aja|NRzOU^qX69t5U0nNW52X_yAo2h7ZscKumyVZA>d5Cg4$k?uE>}lGME7%#uo|YcT== zQvtdWMX6|KTtZ_QE)H=u=vobQ8HjVS$w2ggt~tZ52b&DK!%~tgad;}9lSDu** zk}<&e_)e==&{792b&C5hZ!Pmy@Lh`*m{sSRt@pR8Tmye*nI<& z!|pnmEW}snE`-TK^co_SFJSk;q#>GdN@I6&dJzuyf+Wzj6epIY#uud)rxumvXJTqZ zmqyoKlv-SpUxd#DEGp0qft2AGj)ncAp_?4*=<7w3?8 zPfrvFAq1g@L-QL#9O4>73~{J>XkNomk0Flgl;X;~k_yfHZ~C z!WBgZRTKD7YjE=!O%JjJvKH_$5a6~lc=vlU=J6k(@(0w+!m0?>DCp4yC|1YAHjg9C z6oHM*&jTNpf~gp^SsaJrc*KFIka8JhI7mHM0>v?qtOPl$8yq0`k%4I6BRp@kEM5&|ZmX$PNz0BQpiYuWFTl2;ot^;lJ-Qr<#$rP= z&Q{Ou?%J%{#@Z zXiX1LMO}iHxWHjsk{@4Ol2}v%I!F$3BoS%~#io>ic7$Se0)KECh{4bp!mS4 z2*oIHfS}q75yBkVfrz7=PzMo$D1`(xw7mdv6(MtpRE*-__{8MoQqaXyC8_a6rFnUo zdFe=J<|XHsqK5-f8c?mkHqM7p{9qlv1FJ#}3Eb)+X$%$!xYa?-gSMvPajS!vM_3)i zJZKAxpm~JVLCk};x(J#_SRKSXXoHxbd4$zL%!73(356eFb%Ya3F=!MQ5*^T58e5VA ztAaQVRvKb;99SL1G{Wj2=D`XsLgo=x2Qd$CqC#>XVRaDm@FprG^9ZYhn1?q}A(=;5 z9mG7mi3-U)!s?)jB`qgEvBcOYzBI2OF*zGnDWi)*6({HC7J!ca1YN6Bo?ny#9}UAI zfuhzz~KihZzY{i`znU zS*Yfm{QT_F0>k*S#GKMpxEIkyp^9NPm6T_uq~xT=qfHs$l!ip7feKCqkg&m{0Ahj( zArtT@fS6!P$OJqJASRd*G69bQv|udH%uC5Hhb2Q;;J`#7))~XCgNZ}J3R4`S9uWel z>M_O99Fvq;Qj}VjT2u@_coOCyWErR?a7PJzTxW4+6S6j|{QW8;x}oEXBG%3*#(QVRDRIHIA(BB?Vrf{Q`TgWQ`5u^*-oRSc>S zbb2`QC36TqBvBivAVeV91z7~5#t1_VvIsnT(*s6tRxn3SKN zjXcqpScDwV_*6h#1S;fHQt_LDUk$_@Llt~>LBb5b8i+YYM3{qL4a6K!flSC>_|@PF zWKhn6B_<3%LnN_l#i0qd=mb3fH@9J zke7gDArXs97UE1S!HlFAmn;qkr=)_%3Ug8+b2A00iSS_{boXOb3N<#RD6u>~Co>OT zSt1EQTn*|~ps7I-f~W%Z70^_n2tlobTmX~`&8V=5MHhuCh7PDgWnqfZMWKqJQ(`5l z1;+82C8M4i%oN0UTRT%F>dWB@-PEHhfkZrS5SZ)5D((Qq(Kv&;523& zpNXVDJ}1QtqzsaJP#lRx6(poBR0yeqm_}F~#5_wP%_FQ1=Gi=Ob1fd^XIP|VqKH8i zrsT(`<>cohCKTa(h|7#r&{ZIdK-8FFs6iHis4>S-gDe73V}YRtSp@3fvizc?%wlNb z1s`ghi5RNKAqg?wNCk&9B(88uLv$PC*NsyetFs}k0MK+Uyud(p8;SxvM!@10n{Id$ z1uB`AnU`6NHlc=93gTe{n1`|H!yyUL2@gqZI&nxsbizXsn@${(5S=FYT#G{zHCziy za|`0rGK-2!Q0E+>L4+v{)t*@#U!IYl13K>>)R>Jg0FC`4#yD|GL$yO1!jJ+Lo(s_g zp-Q1H0aYc4dw0k8+=Ch@nBu4rpP7dd?GPbU zWzaiR((;QSS3MMz7H6a+mL#H;6}Xk58V8=Hfb{1e1tm1aA&EmZfVw5%2?vmB=(0(O z7Hl$*uz+-3u_0f`?h5)d7r#yeIWSR~M$1UZHim&?#(A=ZJ~?ojK{-?c@CmN;32Fii1n?ixSgQ z9qbM5?c?Kf@{<#D;=%LtpbHrCO48!?ub-ag*hA>P2uDZtOe(aXid!NA_c(9qt*(t!agW$Nwk zJ;h@7dLTob#(CwaQAia^mFrv3!8g8`+Io0`3E?JI|liA`XMx!`Z~F| zy8AeLJGce~`3J#t7<#%o_Iy!?0dt)$Z1R_I0L@|g+1rbFc0?f4s zOJ{@lV3|x1s{}-3fCzgdxE;=(4u0M)j=qi_eqcKaK=M8y0&JT*h-GgICRLoBAhe4g zn1+dZf_W;=V7d&%$p;Z&J5xX`dsDb|#?Jm;j((o5&S1+7;NmXs4nF%x5vrf!QIs}z}w#s;({cQRznCohmz zu&cpVLkzK}M$o!>J9xSHJGnc#IDxIp1=$1+sdx}83q&9+28)5cRsoU#`wyIcz&euQ zV)3w`Gca>=3h?sq_w{!OcMNt6@PteFI`}wx`MUYJ`#ZQ~7NsVa1m~CLrNE_4o&5ql z{d|309l|s7e0|~KJ`P^)zP_$50d5Y?`9-PrrjDg4nfY*O(*Rd*f5!k%Ux(s6WO0jt z054y6XJ223M38!W6=zQqdlhFdxJF}tXK%*<_W*YX|KMP_sE>o2myeU9k7s~GKxs~K zDyn5B-cGI_jxH_^jy}PTa6KmOUhW=#&W>&lUXIS*aA7ld*8qRV0B>zn1WxUtAmf9zpJ;erzgUq;>z5l{2Z+I_&B&a`3Cqq`Z{5; z2d6$mXMYDbPk(oh0Pg^BQU{j|E}%3EPSM~p2%M?G=@={q@}#39DH#nG8iuZ(4t_44 zo_>yw{$ShkKo)^>5;)g@EdrMrU>4X)a3(^?f)#_c*_(hgL);6uz{TGoz}wZ`+0oq# zYym_Xq!8?MtlqH%N!zQqfJ-cd`QSi6*q#Wt!^z*l-_OIt(ci@tYzNrEhzt)dEWoyb zi+!+x;9?c*YOqgWAqcS&!b2*+T^&4~ynWq#oLs=>gZ0nDx%*R#B zz`f$)?cnU=;_2+>1F5n|_X@bC0{aXcUSNNh!OeH|c5n&scXD@i_9fOUSPcYu9+4zc z;pRKLJ2<=ef~p2@g!w@r)sXrXR99 zo&6jUe#D;~5MhX5ffEKedcdhD4{pAjw}X?ji@T?XJ5t?>6q%qPKni;h*B&LyfYWCV zNF1D7z>NuTjf}`LE{+bK?g0VL?ygAL5|KT?=?CFJL^TS@8blT~a38riI=H)gdIUIo zLs}+?as=#3FadEO$kAX~aQlud+YFrC9Nb-;1H8T6d>s5jeSGXq19B28L03H5o4TZy zWhTRutAUfNgQvHrvzNQGH&m%Jdq4o+@ve!i~$kc1@9wZLwNWj{4?ha1A{vJ-AUPx&>9ONaiM@WhgkS?&}!HE^xS_TQCk$ z2X`MgS1(T=P-=Jcfi%GERowjSO@m8{QWJCSO^Z^C@=HrVHM_CBX;FR-$V-qejH|zc zkE4%^m$xgZ8$n37X(DJN94yQY-2EMV{JaA^0=z(#ABmdXoE>~zUEIAK-9aTOv6?~s zHNX6l%(TqpL{N9nIWsk{B&;MU2ilwgHLO9MGEbKPqzC|~18_Bl8j56>qOc$^boO@e z^$YNDar1&yo#14LD5JpD4>%4fiFvqfE}jmq{%)?mj$WYHa&!dS<_Jm>;HnbrIdEbD zTM70xI8TAaz(pk@A7DBNW{rWXvxBRXr=yFr3&6{VIGWu}&;+MDKPRv>hH zJ2-oKdHaF-xArQyb%Tb(>`jYHGfNVa5RF$?HwO=Y7dJmAaN&(xH^T0eRCx6aa=DMM zo3pQQ1QXKCo-?BL<(5#Z+RisVcD-U4T5uoAde2oxOf zICF7!aCh}^_4ji^DjvWg2`0dXf>R3Cc07@dJ(z6JMryI9$O^3Pj_A*ti2FS#WHC%TRF1 zi3lSXM+av&KYt%D%sLCXc!3mcNvXx)kb=3@z}elw)x{~m-P;S)xJM0PKkzlx_NHmz zu||8-y!;YS;aE@r*X`=z;O*w_Y&B?z)^7at!_M`s^UN<_6A+3DDI zyLdYI2l)FsdJ!nOuif5i%QDSatu|2pm*N~X=n2=H zR+^WR2wM7z~+4VPlN8?M>S(;>jkE5O?kGex6X z>z-N?oRL_Br~_Qw9Ne6oJ$;=#L6srN$=S`pHz2^v-Ny|yqD-o8XLkor4_7xY4`2L^ z4D6Y~#oNK%&C%P{$q#=c09?#MhA~Z(5$z*qKL-ytZ!bS5Pt5Q}&c`^k8@YHpdwY8N zfy!uSw_+Aoy0|-d zyLfpz`Fev~1ueqeK;G$<7w z;ozJKZoQ=<<~IynoE`ify**r99Pzh$z-t-oO~EVW;G-G_E-nr}PCm}A-ri*Cc5-lc z_4M%bbHg9M#U=R#Fsl&_R~Ki806#ZBCod=b^&V&;M+r^78O?a>!0C z%1g~LHnKNKL7Izja&ho+ar5@}_V95COD)Pws|-smD$dN$^UO=j4~HiaCszk&Pd68T zFIO*+vf$Jb=lr~sOi&NsH?cSyrViA|baC?waCbs(y@MN>;3glqwN?Z#D!>C_;IbhY zB!*UKfP2fJ2!{;)!|X6{@po|cc6Rn~_l8ssNb^>ZQ57TsXK+squGQ7w!QIKp(cc?9 zD(L2eq!Oal6Gw~McP0N7S&AxMeOz~v+&3%j~HxVpMIgQft%eNQBZ zK%9mo07=*gr@1>gdAhkfdV7KrG1O^fhBw4jpx8iZ*t&T51-STmLCOatmq46`9BN>< z!JKB`P;O6e*>Eug{ ztK!{LOPt}Za&vKT@$&U_b@u@U7=dH}wjWt*fS0$6kH06Rq{RpeETslqv%8yvhli)H zkE<7?kjKy*oSK}Umtt?4SW=RjTTlXGz%{%1I{5l|xca(*CxHk!Iv$kp;aXii9bBDU z-Q7IBA?51PLKHSkri*oy4r+{6NV)1*?w#EG}JgSVTX zx4)Akj^F~#Tf3ANs9M9~ zdE~G!&CAJ8&PHm3xjQ@fc{(|IJNe%vx76M;OW)d)xpo*-PzsM9f$oO6OiK^)T{vUk^JE8>fq|^Nlc$ng*M5Zni#nJ-6=054}>B1&tNqyuV^fP1LUuArtIv?2l5 z2;iz2(Ub=_^pPgbh?=8;v`Bm$Tz&lfyu7>|Qi?%KP0*Sn&JOP0p6)JAt{%wiQ9z{` zQs_9jgSw?2P642T9Ic+X`@T^7L~D7s<%s1s>Z1w+_Iw<%m`csAh%q@EslDqTm5cP{IPq;u_lm zb;Eo;9bNn!JwfFm)HZN64~`D7K5zpI+^hnNfei*n8{vr#L;`XK%^CW+`ndQw1V;vk zxcb@~IhPg{rRISeeI-TtIl-wV_D0V6c_l@NhPa8Fqmz%9i=TsEVlE;b8wPm0J9;?# zgItVMKDzoj_&K_Id3XjuatL^F5u`1FoWD`JqevOd#mT|n$H~pl#Rac+XkC;F>F^-@ z7vSLJ;^pq-1s;)f^FgxQIlnX~1v<6|86!+A08K&^A;wccgWJwtUcO$Q&~ZA@x+qMi zLnbibQ|xfdUHu*W-Q0WwTzo-6f?_!_K6mzXaCUb0^7MfXZQ!%L06HrSx7;Pb!Nu3b z*~i}#QZys^9NBX4LA&4y4$u)!aNTZh4!)iNE&+b9t`>H?6H7`GlQTeJUQk+s6lu<$ z4le#4UY?Gy5e)3wp`B~cY<4QrLNpgA2Tu=YUw7DKBtkQEK_;jiM)m}#y9sx?i?f5L zucx!4H#qB{ggO!04P9IvJX}28-5s4lU3+NGBWWHSp4D+oOu$|06yV_G<>MXT=?`ga zAw^MnVp*zxX$iQZfD{&>3M>t>#LEO`f}yLkgO9VjySs}UXwc5l5!}B5m)~F(xMTuP zI^rC`0T)@|IdO1T4P2%mjU_od_yz{MQcPw+_q&{76hH~#>4PmC#Ma4>+&LvUDtHIrA3Svos;c)NMKJ2{kR z=H=#M)MB2V&HRS9h-fcPBTv{(z#? zg2bX!kJQAJR75l1$-}|L$-~Rv)!hSTQfMA%V>G5xKL=kAKOc_(w*Z(*aH2$VlZ&fs zK!B&8GfX}xwKx@#gq)llyj(rqef^!h!3sR{umz8^gPWU+yN{2b8$uCA=ztp9?rx60 zz7XGe=Ai_RuY-@Dv#+O*yBE|5w6Jk?@bhwXbo29rxdTVgxI4J`dis0&d%&E6D`cD; z9lTr|9i5y5ATf`W8{nbi>)__=?eFg64Kpklw5=IQ+S$v|$Jy7<1?py$urqXVba3@@ zcJ%Uh0#zW;vIyML05{aYIUAf~!HFA@)(MqBpr!{T!#X-5=Ibp39NqkV9sRr=ax?QX z!K2-9hdTK?__=v|I|ukchVDS?Y;jhE;CUl^cqs)M7;y9U^LF-i@p3RWvNui2EU`BQ zpG{+Lnwy`O51#r&T2bum;o$G)=VGPQCXasoR|Z1Drn7V zfR~HEpQ{Tz%3+j$fbkBg2>5TkXmrl3(*vEc6D%d_i=Ir&r7KwyVWN@ zzW`LTf&7?~3T z#o5il-6O!m(;p*JTq}}uN{chWt3mQnEr*4ZfwPB$hnv5LzZu|dlS z5!!tm+#LNp{9PO&T@O6klR$meB74(fq`sGVfS;3lfTy#I1GL?QFxbh#)7#z2)5Qrg zaDv_7qSW+E(23jjrUm&$C5VF7!q>&oFTl;i-2tOTSg!8u?cn3@;}_uT1zN0t99O7K5!f;uaKc3R%F)5q&)eD07ra^sm+s{Jyu8%p z5~!z;vXG0fgNLubtB=1UdZ0Mw*_);o73CM%o8~7cgPQIs_NL_-sd@IMNVTN5gRhsn zm#42kXrc;xo}jdr1dSYddxFO39C9*~vJ#WCQ7cM+2Okd?Z)Z<;f0(?P391SwCkIbA zM=w_=7k>wk{`h2YNfHlgU&8~^3AAP;Ai&?r#{)$X_`E`dDt8ATH}`-5cW*xwRe6cI zsqvtbeGn?W9bA2Vz5KjAT_Gw#htxn5Jp8;_glc~WFV6rkH!l}=glb6o1{;e-hm%8q zvx}#9fHy<~=-k|l(vlSLRshICMbIKoFMk(D{{U}Ch$8TgL+}o$c+go}2VuuyhYM!pYIW#mmvfBLEgK zDXAE0{2bie-Q2yPmOxW4!UPuwPp1G^Uq3G|h#epo#KUqfTv>pFm%oRrhod7jLcyv) z5d~M_TucMQX19&V5%V;?$=A4~;JwTh`K*Lp#S_pMW-O$6?A;3An$;Ab{w$ITKJgEz| z6};mJJRt*aR)8x!a9s%w0k9ZY3ApM6bHQN&7DE)omY%-8zW(0cz7F|0spvCN0S?|C z{?2|bUfvGQ`JT{a3{|PFh=q#*4zBLL{%*dYeKz@_dGOg>czhZ-Ie}Ik_<8&J`#U)2 zLxy)e^NLFn^N`j=`#bo0It6%x#wwiiLlTR#eN%JYkWvNM6lYIQKPOighk&B|g0RG* z%*4DB=bY3;c)S`oIXZZ_I=Ke8xVSh3mlS2@rMng7=emb_y1@768TdH3`v&+pJ9~OM zloqEJq5BxLINQ_P+YdCz>z-QTmS2>bUIZEw3kM&S22a6GP7bafj_yv*o~{n=sUf?+a_2=IaO<^Zy&gQP}j$Ym#ZmRD)LnmQwE1vyQ{*$p0P&dv`0E`CmK9JEL@!ed_6q;+#FyH6Ii%`@}Z}b zmy=5Xq&A^)HUt^(4y`Z$7CT|j+?W)=9<1S(hs>a#j|f_9-J z`UGfJfr?2`oI!Tef(MSkZ3%ENBN7pcA3@6%!Fv1&Xc1RF{{BAh?%*Yu*sY>8u0XrYphX{KRS4){5!enh7hi_} ze=lEue^6zm0(Ag5VSpQ*;MO5H4T1@9s}J100`K?$%Yq}q9~3&^1PhHvc)U3~I(WLd zIyw3|f(DHo9l?5F84G&81ZY{R3i=c;I5*g^*S0ggKD#7Hww-PiIFr7kB7pRx<8{z%4@W6EOc5rg{cMS0I0X2r8v4zt%a5RGR8aQUaF$fMUa5EmvMfek8 zhp&U1r@OzKKX|Da*>+%f6=8ups88wX=#IPskF?l9SwaC$31HuZK+lE%9i|C7d=^qs zrxwMRl^`Ei0$MNR8sP310BYyKk_R~A!a(r>?(Tpi4!nUDkung01yV+!_Ca{p)xpQz z!`syddFT*q8@5Ajz=IuN%fT@PW)ZR%;%jIY56DT(OAP{TCoP8OaaRX7H+N?jAMkD& zXpABf2RP`!gB0L23Qo6R%Mo0{E`SWgfi|SMdip!L1$aaH0$_i-fr1NcEZ95XOb0d_ zoDs1lMvw;F;R_F1cUK307awPTC-53{vOI>8lE9TdxO9OpH8phcbnx|Y4RCV=@6Di* z9o`Ne0iI62{;o)E5z-=vpg&x_9sGU0JiXnKwk1$t1tgR}@dO{SGIVox@bmWabOB9D zfMmdtPh~q?T^(H9y?i}A!Q=Rjj^G*qXW9bS*I-$2F$j)dM9KpvKQI?*px@KM-OJ6_ z&mEdqsBOL{C^>n1ID^)PQzMMPo&);<5f5Ij4n97vu1?Orpt%mplPnH9+#UUW+cS zfeA!f1Q)%CLJ>Sj2Oh@;OF*I;lyVS5VTLaL4jyi9Ue3tP9sK5l+vwmt0VcpM0vFTZ z+74Vyr^AX_0}D?-*8q2K4-W^weCS*mT+ZLY&ojW?8MJx-@(t($KBn<%gw*3;pP*Rkbm!67jmxr65o1eF%r$az$QEp~2co4%h zB{eTI6!uPfcwYN+uhyWH^A2mVp2|G5t3*99sHa;Jp%#)0vrMoOET8U;^FS>?(OQ} z37TpL?MFga0~*J7_I36Eoqq@Ej^u+@A%J%XgO>3lBGSyu-v_in*~7uH#NHHX#L)n> z568*h!xwa}KnwB=^Gi!WG-QGpZmzF`o4<#Tn~RHwgJ)hzYEe;XK?!og zaRcRd7e{XwH>8wMnupba{=S~hULcdOq?yb-d(%>+TA26%e<`#Lxk~6t~J0Rz}wl&%g4tTlFA_IH77GK8)3hbql2fvmxqUsi!UUV zWP(mi1?6+((D8Bbadh*`dhJ-_DUS?q_ zBAA^59DJRfeH?uQJRE}aLB~debAA!Htg|=G&jS@OdG?mYU^%$MoItx}ojqMWeL?Y4 zT#}gw)c{QgprdiX$6O(mVF3;SF5X@~{*JDo*}0PZ<*}>*Lrzvc4$jU2 z?k;Z5F5rLy&DSH>7na^GzJ4x#exSMwwiFDWtNb0jojiTq{d`>=90NQ-IS;hB4m7h2 zSK{RE;O6J%?&0L?i>QKOJED{G^U^XAm8p}5gPVu1i=UrY0Nk(OU_nf1B4tk(@KlGt zdw?6NV(`LlTdJRQPNd-QoZ11HdKGG`wjZy#TjDLqIV0~$sc!Qm9( z;O6M$=N90FIERDcO)weIwiO>|Uw=<8YlGJ7I#oUe)j7DG;^yw)=j7|^=<9_vlnWlOfb_mVeg#_wb`;TWgvWrFzk`2( zrd}KHYaqAVfG$pkmYYzw#FrS6#^OeBo5#h? zIRGhBqxI^+@j{71p@9Uxu>^|~K`w*15p>5jwqW$}baHm~1@8!l1|v92gOfh^jB!d_ z2MSPv!I)cM3~^#{NfPun3rvUlJGlG!`aAl8mg_^^2~Ka|G!0J4;MoCi?19TL(wz;@ ztFE9k;eA|u9FbCy_9zYpCwFkh0hjk+7GkkBD7QQL1-Q5dARk?bRxyEniiivfLf_EG z-`U;Y&(RTa4jX8;-o-z_-3N636l{hFe{}#JZv)2x*h_@m1B-hDV<%5%7hex=C(s5K zxTuMnmzTGjkFSRVC_}-8jeS6aq0Vl84!H%!Fi}HuA15DIFCS>hR1V4m;1ER=pO)UB z6DgeCAUl=86&bj;1uKIu#WOT_@$?Mv4e&r(Kmtx$2&-H`n|s{dJY1n`y}(9+a|75Y z#MBNrw!qN?4iiM<0X(|~mH@NBdf`Jbpba|#p5C5bkWH7+n9}_&c};xcYcIxg&)UMa~B$ zZumfop{u)ttB;Sbs~hqOI@C1Z*TL1*)y>V>4QXPI+~7ee0pSP6fHq)ydpLRe`yyWm zLS;MLoE_X9-94RrXcSH!&JG@ae$H+l$fq+=F)z5gJ2*SLdbmJNRe=@@2=l?I7O6l3 zClU~WD31`y6e)SY)2X|sgRifzua^t*k|`>h@9OK|?poISz06Un=Y$rk%oU6ehg(WXLJ3BbLIXZj0gO7v4 zhy!p@0XBfl^3L7Y!N<$V-`T|vY1I|D^$K<`IC{ZF5x4+CxE#{jA#wvEEXEB$TgL;O z{9XNBeNe_LAQyjtrdL2g;Oq=q7h7zv;_Pg10v2#9wl_gqAMWf3I>S1^$gM3$?&0R;0UAt$Y*Gi`LxMP!0o0QOpS+WnS_Eq0LhB$GcL&gs<(@w7NF@~M zDGTH`aK8&&k06#_z`_eOx9{!m?;jB0gw!A=!yc4E5FD}a=>yQ>5jR(VCpS;z6`p-h2+d2aC`e5%0gXCA0va?54H^@I3{yfn zrp|7Nkvmsk2iE`}p8zK}7nJl<0A8f*>lp9l?-U;r8Q>Zp@aZk1=C;FO2IfCF#d0#`TS@P*G+h#olp5O#vi0-K76Fit(g24Q*dlO*AVQMN*mL)GaCY}~_JgiA z0+&9B`VC<`IJbc#4^cZIyb7+xz>2|6gI{?Cy5GUY(b3o68M-zQTs`bsFV57mB!QqBz@PO5U z{RuV@tOFJ;h=DtJ9&&egZ~>i|=LtS+9Z!;iTC=Cq6$=_ z_&5gm`?~wN<8&r;DiE{^0p?6YPhSU5M{g%*H}FAhG$?{x9o$^JeOytlC_)cxP^5tG zKm;ph3aB{FK!A^rFOqjCe2;b)Z?K=h#R53Dg7rZX8b}FP0+D6G zNggZ)HX5u0(PwjWcJOs_cXtCVhXHA(#C)(jz)`9|>h9_czWD%Q(;;yKiKf)NlmgJa6j}x0;_Bey=H=+<4YeGc4A6$Rz+w zL}dk13W_^;%6Ik#jSskbJ3*HVgZ+ds7QZdvqyP>IL?}Qj5V!^I-VOmyo-UyEYv9_K z>J~WpJGgmxIJ&w(OK@-v0nRhv=Nh?HAw+yD}Sb~?h<=%oQXeB3-8K=axj0WOdp47JU7cXe>}^YU}{^M(u%fzuAS2m-fmz;zSY z)riyxmLRq%H|J(lCq`>*d5@|Lo|h4938xT0$iM&!3SwV;}@(D9Ks0i zfLm;c1PF>N)RqTADcFPHJO!Kc2lXCcOGdD?Ufn!F%iLXE1Dqh6PtlB`I1t^u9h{v! z9XCOpr&_&a!lHiUUYuQ3KEC2(>CXDo2+flbF*ZW32K zJ34rIIXn9XfNww})b_*Kc0zXqsI!)v7@wM#o|%`59E|ax9FUg+Sqgwn8g$@J0dzdi z(8<%mCBVne&BGC?83NuV4fZ-XB*29kc&`bV3*N?pl0Kmk2fF6k$Hm_Re8{k)BRG!0 z+Q2Mui4V@+V3Wad31)$V9PB-C!y0TdSTR@@QAarYJNUY}`nkD+Z#shpB>r>(D@YKd z9VjUS9DlIZ8KfHuPaV#_4j%qKF3#TIgZ-ekfk*ei4g_aSuphuBF6kD59D+Dv+04b+ z)!om}!^OeC-o((@-UQxvFfeg;^a^lw3J7pW%SAd|*wES6!Pmvp+usL#WIwe11$GfB zEo_i8U?BjbAt3>hfjiL9+tI<<*Wb_I*9%g?!PJ7&2Esycyn(|89GBqehKCI}EkJl+ zb>L)C1+oV**5&T$;Ns=s;^u_BZyDT*0H-~0S^;YYCk`+d^}GPEB-mcC25@NsR*T#; z_H^)bbocXgMovlK6bSC~f!z-dN$_DxXt@C#-(V9Vwqq1C@Fe8n>;PJP$8|(#e zV1t7TOLT+#EU1+?EY-khY$?sz-yy)s(ZkOhx!(dd3v3a%lm-)E7l7RYNmH0bJt%3x ztb)-HE8*ei?C;>@;o zE#RT&?(X32C4-N6;KbI#cZY1W;}c7R5Rz5V{V?v4(= ze$LKrpu@pni_xfSg@H)`XtSM*yNg3gVhLjNyPLOzySu-umm~7p6lz=T=i=ra0J=c1 zIJW?P3bBESyN|zffQOTtLuMY*wilBC(6z3h3(yjiGvNci1}5%~F76(lKAsNArO3C` znfQ9RyL=aio ze;3eQ?4a$12-Pkg{+=FQ0d5X?`AGL|n0Pq`xHvm{1UTfBAY17a;OZU#I)cG7*bT`_ zXIFn`H)n502Ty1J0EF4jE*{>#KJGrCg9?2S!U6ssUhV;opb3K@Btu+X9KBs#e7zk! zoqdsno&0^Aecb%q9Xy?bTo8tMd3w0|`hn)?-2H+O!fw7!{;q!B9uA%X$g163{at>0DLxQ|vszIxk{QcZqocx?2-AZtI0?zW_c_?scgRM#g7sudsAhm%tu|TZ4#eBQKW# zcZZ@RxJOOByaGIZ{h%vQz@Y<|G%#}a^>K4@@^dJMiy4~wdVzL?LI<>Mg_YR9NFO79;$mez$~z5z-=tB1Hs7&(sBkxF*u!oBLF>PIxyHLrX&`WfVY^2 zfVWlKJ2^Vo+ou$PHb{VWwZ|7E=f9fB4`H6>xI^wOm2m4rlJ~=IHI>6yO8y zof_Gj7+W|nq!fV`wa3S&dlvgdIs1hKgU|FYNlihP0Ud#gOTxquA!DCe>>LmZx&;Jr z404KtJvde%)_`XFxu zfi71CUEK+e24urQ20)C2#sp|o#Kqmy#~FNakE0{FA_V6~aODWjYT$f{7Ck8S2zVO| z{A6T94_5~dHz!9I(0K>om<1ow30|%T&cCqsIk+B0%iv(E!LbWd1*5^GHCPMyayQT> zJxh2|>*@-+-Pgs*(UbBj5}fj2wt_803=TNEI{5i{xdpiUL8fYu90k(`E|^f$7tAmi z4KAy}J_Y+8CD($EHU$S2{2~I-@o*mQt^scT$o&FD8V5TO99!UG6C7JO<~eYU4#C3_ zbnCjSkH4R%KV(k@*neP0f{g`_9Dp4Twhv3{09y=6D+UNt{T)0&yM=t5A;Zxmn~EjR zqNO<}e}@2HUw0RGKgeD(VyyrVd4N3#akRY(tk?vlT!a@u3uiqXT|C{OW1AG15Ah5* zxuAK%+26t4(bLt-4|-%NxM_qxqG7Ja6|*2;pe8%ySaEl9_j7ZF6sg4e2xcR+oJJ`K z%s|$`o4JNgz78&)0q%Z&K9r{>w4ieGckponZTw--H`v`0aSTi{9f@Q&`!h#A{(t;->XV95FzV1#= zo=9T~;GzPYcEKzX%?5cFSF-hV2=Mgra&!XU4+tF%2D=|zjv~ARb3e9(ik6x@JRLl} z99^6}q0KL_ao|z`TpxnXL>rm~hdDSD(ImiO2Oa>1Um65jRO{*K>F(;{1!?nwGd4K> z5XOMTz$G}?I&cCYGSuKP;pFMyY>jdRwKQB;M2(sG`oG8KJ05%Y8 zG;kAA zkOPB#PC>FWsN0a4oSB+eQjF2wf^3aWjR&8=omw0ppO#sXnu5{-bM|%cbn$R=a&v_= zT_BxuPzC_o40Z_EN#Jfjb)Ze2 zp3W{FuF#XW!N~<2OJK{u830UxZ3KH1yz&{G*uexi(Sqk^!0I5*gGY&*vxA3=x2wA! za^V0jK)~jMO#tT}aB>7^Kd=+P1lUfn5-Vc5Sd(V57ky z26h+N7_bC5$ASk4z+A8dEEKUt3S#iU+1J6{)x+1%89KoL)=!>2kR}pJ`YJ;7!;*`V zjg4Sk9%%m=)Y9+{@Nt5!0ksET5da;)09QbWst8i{gQ6JjFL1qx+GYorg5dTHcnv?& zVl8h6Hz!|LFLw_}Sq?5Zz<~%Rz!?f07ckSYl})fL3Zs#-D#&4owCC#V;Oy@1>*ntR zX{CXSF0fy~u?$X6U;>=Rz$~ycz|ja+0!|WO0vwm%IEQcW0j<~c3~+aK^?=TJft`;Q z{b1jK?F5^U3vwYiUJ-U8SYX@XW4fSOcYjYe4_6--Na+p^EwF3A1lVY>so>NC3o>j8 z4N;uCdOJ9|IfHg@BExf*g9C0RmPiH{u3(*D*MRdbVm86W)xp)(&)vz# zAF|4Y+F|43?BL_*@9zXXX`I@|gD#qJ_i^=ghW1s#NeA2k2Zswdgiu1AvV`jB;Ns}z z4;nlHuUrP(25z=OLKT!~z$F&y7$w-#;8XxP00Nvo!0rQ=cHpFeXe)q5!adyG-JFmY z%ur$pB2^f)utUHpAM8SKngM4MurtBI2o?jY17{AfVp!rs1TDPS zclUSj_4jb{bb~Gw1FHvX2eU}C24z?tQF^#JI`}&U1o(p{lt2+caim}kFLwuL&`L8u z52R^JA|nNn2*GIp(li9Q6dahr@W66$ckuQ0b@y{Z-c?6kI}FTReY|{KyMGl^10oouN;O6Jy z3N4TcTLo4DZq0(z9XKt5S(vtf8~`2=hSr+?4xT>#j!xdLkir>k5?*hCT@N-KVGX!? z1&e|8gIS0(IzS_HzW(msp01DvCs++$JHW{wXGVY&6JS%|Q-p@#ZEr5F&aR$FNswYo zAfAP%K{wE)a4z2dzC>FBb|tvb1N#Y_Ucm_)97bRWoSa=9{d}O4BVZHo zdIXWsz$FpbJTL+7GhrqHxOZGV9h^P90zmhIfeUPK4T#Nkh$;gdAYj9x3&6qY2{vMe z(p*FAJ#qn^z~klY=?fia0(%LYTftcg>>Y5a0M0z%REE_72!DXCU-ESKa6vv)A8(Km z?E<(RF3t|#-d=uQuwh@Y33%-Q7iZw00u$iK!0JJ`9Zud3-j1%Wu$TuM0FDlD0s@yJ z;A8_15^xBC!wMF|&?XajSP`YE$!%o0UZl z49P{wuvU0Ze!5Y-33#RgXK+DLlbu*h9)p@D`#_kjudS1=|L; z7tFN>OM}A>ECcp6*lpnMmp!7-=?pr!#nZ)R)F*x$UTyTvA zU;6+$Lo2}D+r!z<1G2vg5{%#ggLlkON*L0k815Yxe+OSj*8p!X&;+pxbkd9D)B<(@ zI6;9^5IA7V9T@B(N3?yw?mK_|+) zxI00Qpahr4sEz{1A~?>#9I$a6+zfV93lXODzJO(v<`& zIw4IlQ0edM>g?+82I>cW zjwSZN@c_=DMLE!WPjyA=512&;a`a zq66%9aOhw&AK?|SEFw!Fg_D!FgO6)~hj##UCjgq8!0rTF01kPuiEwv<%XD}O1gA$t z%5(O1@b`1~a(D59I2{}+sAT~-L?JB;P!xjQhp-K?}AYtOJzrAUVhtl#;wW zeFC8EK_=E5HQUV6X$gc7rVg+YL4(&4D2qaObtxZj1lsv z5r+^@NlH#KPck$$GD|fzKynW>!XUwdNDpbLW{C!tsVPZm7D<-K{)Yx2hWa!ELlg7F zR49S4Hw~9LmPUqYNv0+yCPpcSh;)#a1{OC%Gas6qp`wU%WMW_dR*!BDOdQi3LmcXj zP^^Ht-^9QehqwtYaZ7`=6nOlY7?@ka9c_T@?nJOK!k(lwWCIc6$sqIK>Cwa>1*{K7 zz||R=85#G3Ak<+`2PTH8-~yDA~+V|`PdX3>IixBG;^>5n14ZGg&gilPyv{H5+vB+@sN}TQ2>|6Q63~g zq8ns17{k;LTr^A>H1v~k&`@QN@V79)MWZM~q-=Hx#!ZV6T!(cQG zjHZFnG%%V5M$^D(8W>FjqiJ9?4UDFN(KIlc21e7sXc`zz1EXnRG!2ZV0a~U3d;552 zhj;@6XTJbHCr1xw2YVGqM+XL|uwj6=yQ7D*zmJ2xinAArl##oykDHT|pF?>Ps<@X+ zfV)ExnxLPXkDr^rpM#%2ieh6YPiGfj4{s-j^29P!8P5O@FJ~tY4~IzCU{o0&4|hLD zXE#5G+yY}%8GmPQ#{l;LcL)DqR6Qn6u8uA~0q(vIo_=nqPB3xu_i^@d^K*CbbPjSs zG0(&)z|}p#$H&#dGuSNzRm$1XE5Ow$AiyClx5VBQP0q!`+t7tI~60nYAT-i`qdL7r}I zsJh%7oqW7p{2crebJ5ImbM<%ib$1DH@C*s^M$_cw*0`=lbDPqgVm@ z;2D4(P;UM{egR${ehxuyW+te*+#OxqJv@Cp9g<5^(F4le)yv<-%h}H%Cow4pO`Dgy zho7^fn}e65Gny0Kef*sRJe=GdGV_W{P<45@d3gEyIlDN7JBGQUN_ja3xHvm{1UTfB zRG{hd^7L@^^>cN0@O1YJLe=H%FML(SDJ(7bRQQFA3tYz zUx&n$5_BmKXD=ULUr&dWV)StG^>BCh_V;&nNJ>R_pP!4HcYu?hr$cdW0h)mU&YnJQ zjsgA-iOHEMs16Kp_7CuO_I7pf@eD>wGyz`zK7KClE)FS)C5dRd{5`zf0~~!FJRO5j zb(uQ*1$g@T`no!VXXg2$rfXAge;+q@R~JVI7gwiHcN8N{eVtrf-F=+B9bAKg{87W# z)Ysk3)y>z@!@*v~&BtEF*{#@K#m&#&4Aq1HS8spE08d|s;=Gj1d=xn|H>UtE4}V{O zhj7PW#{f@MNq5%(f5!lCF9%1*QVg9QUf$l$p1y7l2KFXq_9n)LsCqs896ek?ncu+P z#L(E@1Wnr0&E3=A!^PFXxwN<>KNnRmD11GfoIM;IOYBWk^H5{P+{M+`)6?6>8Dz1c z5r)m?E?&N#?w%h04xV{si8*NTYVPXp=;iI<=HQ%IP*PfinzPN_9o@aXe7#)3MjP0h zm>8ir%-q}A-^0_*Kfoc}G04x;4>hLDeLQ?TJl&ie{POKhGxE_gm$|REqmQqXi)L$x=+&&fT&)7ix#8H=keT%7`ZJv{u}90GC@ zE0YqFvr#l#xCXd*dV6@eI=G}JrsU+KOMCjc2Dp2Bcz~UqT2z!@gsR=w#nCUo&BNWH z0ITT%0baiD&c415iI61X>}i5g*;zU{`8hi}dIdQ67o_F|``D{^_`0H2F_z9w9^P)= z?oJNnnR&Un_9iJgsCm`W#oNQfKfv9|AuBOCJ3g%_6*cZGJ$-$B{k^??9rANhjg7Dv z;O*k;=i=w*1NLZ2YFTD7nhD=xO;OPnpn3UAS# zM;UEiDx_{Wv)~c)R(zdb|3$!ZW04ZfbFHB3kG=IXSp`IJ!GI zd%8Nfr9}I0O{s7lb7iWhUm8IOn7$qPfw@!NbGVGr--~)gdS~J+L&js4^@ur!>{I z0=3$5a&qwSb$54mb#g~|Cb1~J6cj+{j`VbM^m27_@rT4qa!zJyUP*j@K`LtAc5-s? za`klg^>^}yhNF9GiC<~1OKMp$x{2Q2p8nnePR;Rq>fK+Ocx#InRpP(uyP0WJ=nP64jIeqLS>rxxTFmBbgNre_wHpywnP z2XAjbH-9H57YEP0g3=Ou!~D_`5N(QCY&*F)_&E7{__~2g8hg{?g2d!hd)#jDadGqZ z_V(~`2um%>OsfooBo)uRwES??kacnJ3vlrb2yhEPON42q#pnU%;^6P><>Bh@?Fdc; zkO0G;3S1ogUHqK<-2>blpoN@i9;oywE=erHD0y5RoSb~zoP9jJK=lbU`I#nz@_$Ya zx+7d2oITxK{JmVgKz<8OEpg7zOUVSKSl`5A)SA-C)xpEn%hlP%-P6G@wWK`1DBIq& zG!I9_xq`YA&K?0Cu+#`D!y&Ft%`GTFE6-gWJUzX={d|2LL4})Jeo<9sELleC$nOZ56B)fUAS2pOcrbx4S!}-pWnPtF$)-mouhmnK`LwrHHG8 zkDtG*x38zCgHL{9N@iZVy=ie}Zc=`Zy)meR0ct~{wjZ5b9sIl;9o_u=U?o&QQEEYA zQL0C3VhVa1b#rj?@^NwWc5!nEN-fSWEdnJwoMGtZ;NlhF;^*$_jc`m(W>QgNQ6)~- zxH-7Gdj+^Vx%t3S6<+7KyEq4UdxNq8I4I#wB&=cS=HTJw;ppk<@8#f@UzD3zVh@SX z;?e?8zC=%3ZVny+ps;lHgyh}y)ROola1|I|oLPmI(cK(8{hVFA{hiz(ia`d(BXpo< zGdBk>e-}sp0B=W#VvxGxL{L>6UxZP$yE{1hxOjT_`THT~hs->C(}JS>^rF;av?So} z;Nt7)@9pmaE1W~~3UJ4$yMwE@hnuUPucw2dnZ0FFW{JINaAta5YKpz7XI@EaI(iat zcX0D_bN6ua^+i+@(B>AXWKYXXNA;__gNM7Hucu>xi-WNdaTfTvxd#Ned;38G2U@!4 zCFZ7L33qn~Ung&8KQA{w2jA3Oa3)QJRW!MY1qJAl>h2KW=H=lO;O2r{%edy1=7MUv zRJ7{L!@%j zPz$CcKRG`Knj~?QN*)egeoo##K8{}K8gQgx4+n2wcYhy84_^nD)U?FXoDu@%jfaE3 zx1+PWlb@@D1vIuo^NR5$2u}w$Z$EEmUl%V>1e=0WP+CqtD0k)N=jGd*VreORI(YcG zx(9%It%!0YB|o(o>iyJ;OtcP`lc$51M}V`RzlRG_(F2caum-e@;OXG+2^|@p5qTbnzban+*3`p{5^@Ep#hl`7k zzmJy}a*lykTG*Or-VRP)&VHW$0Z#B-P*Pbyz_H#AuD-rre%`Q-PH}2Ud_hrua%ypL zd~#w=4o1h<$=kul*U{6%$;Tl$KRG+K#NL#!Ej|uTF3!&WzD}O-rgJd3+_g6i0u{W_ zIx@xH)ZWrw#l;^KGR|%(=uK)L2UjQG0DnhcCzN!Olb@JEu)6heaCLU}^zwJ{hC2aT z8G$T^w&x-C7Na_&7}FVU0RgU#ZoWtzfsDkwywsdxaQzA~Oa201jQ0Zh()2uaA?Hhp#VE(eCN$>S}N52Fm~7#z#PYW*%Dq z#n-{b-7&z?)x{Z0=n-lk_&KX585p?4F*-ppg;usSOqjP3h}y%vnOcu zECoDrftI@c9Nc|D-8c_#2gj5YaBXUumYAEFQ;9!w`8jyFIeWT#`ho^tic2!{Al)R> zM3^B(sfDHBfI|r~KL<~DM`tH5XKyT-30E!T=iuw=;TPcRi5g&tTE*TJls(Xg(wzJq zoSa-;Jlz6ZQ3ATSB){laDiUJwr^e_&az8IQ#f``}m?*32Cpw z9Ern9FV6rkH!l}=NVN|t0FYW+I0^xO2R}D&Z|49XfAAnFWIWu>&)yWd{D#;LX-A+J z-vOW&t#^Q@KV*~@I(iE-p**oH)xWePyeJbi*aA*MUiPMGka_{F5(sc`b9C}^3-Cg% zIKcfI0zn<%;Op${3&`5I|mjp1ox;q>MvJOaTr7F5X@~{*JClt%35Q zd{CnwoLf!PK=le1tm#N7wA+;OXt|mp@KN;o#2iVk`GiU_D+sn(v&kZG=A!i9t zmM=yGz6r8sUvCd*XK(iau;plRjxyAR8tn)pTpgT1T~ts%3o-%&Ap<})GqgB2MH!TV zl*OjVCV2QeI{EtfI>HMwkQo7>k!4fdMj#vD?eFdC=j4;a00W@{v?BeADi6Q9h1t?o0XY6!Ptq(1~Oq25w`rRE|U7P~ky}?Z%Hy_yS z2S~qPVo7FMsy!%cp!EQewR?KFx_No{LP`~6?a0<6r?!+-gmw=H7iTXoUoTHri0dIU zLLkeLbwf)f=#al@3W|QO01p>uNZ!O{dr@jhQD!R0E1sYl7@jpe96a3oJ^bCEj>ldk zf(8{q{YOyy%HA|NF$dXp4=*<#XIJ+C2O|?`7XZCSXlk$G>=}}oo>2m-Cj*c*`@1>% zy1Dy$fxV91@Qm*iHaCUb0^7Mf^9w}>L1|59H0!6=zzlWEn zqYtE9!J!|$Tt>Lx)4|o(!^_*z4K%5bns9jI7(=%gf!@ z&)46O&x>6{M00q1W5N2Q*LY>*a(e$9|t!_KM#KwN6;b# zHy`W)4~}(6rD|^q8pA-=@9pK|;ppw?fl{F2D9w>Q?c)&O^@OJX^@Fv-2KL=OPtedm9CpgG(WfYhnAO!`&%YF{-K5njFo<3OO zx}+#IF&ES&D?*fgMfo|Y$Y~YS1oZaubMgfBuTawpa$*G+Qjk8dX>v{`vi)A39^Q_Q z?v6wSc{#Gq08ftqM=w|^$5A}OJf2#XnpcACa(@T!09RLMXFvSKD^C3Z4&H7~UY-Gd zkX%kE;6de6Y8l-5E{+b)ZhrngUcPvvEiE%I#ojb2wYUV-2|>~B>F5#QPV9X z;OTKwPb3jY-vHTaZx?TOf6zcJXi^n!!gF{?>?TwsEi;7b7Km+b2Mfo|wsU`MC&iQ#D zHnOdrzMjsG-k#7bMyjWs9sC`=JzQKI@h3EBc@Fk+ULvyf0e)_NPF_yXFvk&N;E=XA z#n29#Wbtu!_4X#ub{7XfUk_(DS9koy6E53b9X$P=Ts)i|(c0Jfxw)AopgJ3}!of7a z+1K7Qtq956ZlD=dPhTfb{0SAgNLO3iq1!hm$7y+D?O zr(!^jD0|byJbTkpM4-Dl_yz=cxr2M?D!7Y9T(-MA_&K`VC&Od+4;KX?}7tsH8}-H!aUd z%>$L?h=B8T@b~m~^!0J_L5c1B0?f#UXfDY>w6k6O90FY3{GENA+)#95O)D_%2)q3q zoV}f$J>0z^J%5ZyLlS_Eayok=vc(B%2_o7i5Pc>Y?1s3Z$lotFwcfqj!L-kE172oPp9AmKe`0$pDqgh^im73K29$ z0{M|Z8#|#u>sYNA~h+uPdaCLQac5-)uw3;x24M_kR3CMZ_+`XOs z{CylD(T$@fLNt>>4Mv1^HwO=Y7dJmAcW7e6p&b$GpuCFE?&09#;TPcIM@)o6oQ|T` z$H&vr*$3L0#bGrl3L#TmC^_8K!@=9l-OJn49e;YkrQOrP)ydV}&C?qaZb(54Nvg$FMUAP>*eX~>F?|BOODI^9ef;p zT)ezpF(WM)qi1BAn3R~8f*5>p^>^@hbMp;w@r5Q<>_r<+f4ez5__(^bdpW{d%19}W zuQ;(9Cv#w;e-)kC|Lwtiz96IbMWy9@bmZcgS1_6*JL@ViN&d)il`*D z2%#M`)8Xmp=k5q8T1c|m-@zxq$-~>p3zD-*wA$Ut!QIct!`;Ib(ssbG8ad2L^T2C@ zO%dY`?oJNA{*KOmjxIP{j;uX55j0g)nuaKr+?^c!{e4_~JW;1QkPC3gJUOIufJkNT z&JKQ_PLAGAemHU_vb|WG?d;(1;p_xjoCdB!@%kEzv)vp#JUo4UT)iOCjiEg_H8~$N z;E)IzxU)9}Ent90x4WliSmjX~`}Sho%oZRjm8gysGYK7QT-9sypEl!9zI;gZ-r zz`@zi)7jA%S2_dP>rz^flUiYK2w#^0+DU~N&vg%QaB=bSa`wdHeHZY$%aqLGVo27*%fZ{*&Ck;vG~JHY8wD?bE-eQ4ZBWW7cmR0#JNUS`y88Nf z_>)sGdiy#!J2|_!_(B^$7-i+270C$-_GUGL;10Y62ePhYVGNA_aBc+{eMy$Is8p%gZ697_`t4 zb3wk3gPWI+lcSGkfI~oOPH`%@4nkQP=HuY*8{p&Y?CI%HTAW&hZB@UIgQt_bo1?EU zWHq2?9%zRFnjv29zP_$50d5Y?`JmZ1Z1(uOIs1CM`8Y$?yqD%>7M7we-}7;CaCY@} z@&>I?1$Cpb1~Y-Q>f_?z;|N+?hHC@?6x#&M0M&-xo}jLoLr!K=7HG2v##RYm2WKxw zA7@`b7x0=i&%7XvCE~sgZoc0B?mphIC4j-8g|;Z+=Ih|*@8RPHTDAmQ=2uiyT7bS1 z*Vnp9GpN4 zabQa^kW;#!gR@6KfSXqU(sl*VGFlXa{2W|dT>}C<{hYye`InYp2C<)mo4cF4mkVUE z0&J!n)hbUvM|Uq@KR<`Wl(NKvOl%3<&%rCe+t1U<$=ks*Cnq%>G#gf&nu@w|+0Vh- z(cRN6z}*w(9!$^sIrzGGd3kyGIyq#g7UiYpV7JZJ!_UV9w7eU2bpz5a7C#4nCr>Y5 z4;L2)|I!kB@EV|^#5}a^9DWY|{(jC*ZayAJt3W{oGI9v`J2<<0x;pxLctN&VnwEff z-hkF8pel25_ILO8a(4$MB2XIhPOU`Wo8j-^=Huew@96Ff32}H&8Pz0jCr=-DKVMe| z&~7)#>fSVP`Gls--P_mQ%gY~9AV6aeZE>c*gO7)cx3j0aKP)ernP3)k{tmvLP66Ja zt)$NRA&JG=zNxux=)0Hv9sC@9+}&Ng+z??39?(I}D*g_BPM)3t0RaIH0g0fo4_x8p z=NaJc3`(A`?PSogHe^o*IJmlaySfJWLz5_IjWJ{$2bwB(Uw=1W&{n64|_n31Mfuz?*m6s zY3U6b*>QFQO)@w-rh(+ZTkF8e!JCvpLU4nf+#Ouq{rueh!2Lu=N3bJHK}x_{<3TKl zF7Uo>$S@)(z(5N`9UVa}EJsK1rZSL9WMlnY{hi!Ad_j$Ska}k)kOGKNmLMj0J*A4H zBRF2b-Ud4n9G+k?aDfj}i7?gE!NtYh)5qBd)M$5f1e*rl;tF;#c&in7aUsN6_9`ym zowsmDgS`L_Tr4gIb+KfJ>5K9eOw@s4t6>?`oIJtFj2$CA7m$L z*kC4hCr^g}PaiKwC-6KYnZbf9$h{rhTz&lgJpCbA4V?BeL9PNj)EUGA`vq6*LVN%Y z8Z0jNcJOv|b@lOwG{8JuFl z+QHt!l{>J+3b@k-UaN|3lZT5BXm12)u#h@d;ff-E2XB8*SAQ2bq*MS7M6h2-iX!kP z23SdnCBgYS_&9+!19^ak)u`huZ~+evMf3pl@$+(Xf=s(Yts>P|kQx#-&0tM{pmPNr zU0fgu5S&>_waNvY?oh45lAZh={QW#U9Q~nHBDgjI7op&S6Wm?_Cw;+a$XUOxm4Jt9)N2Ph7}>WIs`pjFNujxL^V;8{0{?1AJhuzzqQbZ18gPd8U5M?XhM z3kV!!aCbuVqa+HMx*8n%q-~a>fq<+ z1~1rmun@zRm0|GD4<}DA&~UJ$Be)p=-oxz%Qa1q3adz?b_j80+SzzaYQx>E;1m_of6*`8FuaBpz zn}-*qT?|f{pp9IhpoJ8H;79-`6L5k-#00pC1}7b`7&zacL@`7^YEpqT0Z?+HyMv#v zvzI@#iw6!6a6=JX=hHoYJRMv-KnJ`)`;lPhfRhV22*80c0B-X31syr#2k9zj^2E?mIsz*P{qF$b0f`vNQr=EA%JjVf^JgXSfqcB!+cgO8_^v$HRF zmKU0bz~wXe%rS8B4l{{vDc94%&)db(*U&THT@8k|@Y-Bf=euLx*Gt8`TMzhxr2^;1C`=nm!K9A-~a=6<-xfXOn@^g zI2D2;8(eIHgBa`yunw>oxZ(x}J6IN+WFbW%N?XL+A;87S)6X5+odbssxaLn-FLeJF4@9bDgoRf2~- zz!G3Hz%1}UG?)Oz3Zhx<>)`I{;p^w@3TfbjLmwPS;AjCCSm3w;OMt5xv|10Az_1NK zgUtY&298>UX-NH9lz8)X@O1HTb8>Tq)Gy#}AlQ1aA>fDxbHN6~Y^Gbe?(5*?=Irg^ z4r!@?$J1bLfchRB{NU7zn&iQW4V;w0E(ZrD*dt&sfm1e^3l1i*ELa^ld4nau2B62B zuZyR*zYla`2W&c6KR5}2bs<6x=1w|>mA_knpQj^uCn$982<#@XPr$h!oP)uh9RSw_ zxca#}x;ugvEIK-Z9RUt3aIhoNDOdt;s)r01pkzs~8DP`E(Sa}x+^hx16KYKQJ2?3T zxVQ%RLB_cdg#kF2z{Y_?4XhQ+rF($+JGcgT`nZ9PN(Q-|c1awZgRmEM-~tY0JEHXR zcX0FcclQVYE#FZ=Za09F8o06m=OM6r!D0gta_)|vu3mltkg;uW@@s3isf+LLfm8-u)fR~HEpQ{T}yB{20;5-2?*TF4YaMA&n@GwWytA=%Pba3@@ zcJ%Uhg0$SgX&Ov`-3d;0VAp|@9@xbL62mTz4({%r9s$nYkRfbv1cGx1*oj~QoKL`F zU|EzzK*#X(aB=nmpOFYE{J=p8?t_8@5bQ8;8t<$1|7!yAT|@;59$savMy569qWQg4N}MJPTF~=Axt#XsZ?_1A==Kn8hK;S?&P= z&hFqnJJ8|~oD#u-hAP z2FElw;J~tA0;~k=FL>4mj|@O~kd7N!s~}t)9RfVPJ-r}1B%whGo-_yB299xXpbm6O zbars{^mlR#@J1?uz(D{GE^vB8q%Lq^B1R}6p+vum#>E-b(RKCrbAsd>aP|f_A;1J8 zqYs8gqYLPqXO93kXIDu71ngM+P6UT6Sjhl{tf#Z1n~OWNfCoDkXEGSz_J)hIgJ*!d ztE&gJUy3u^gIh@8EDlb>;KU3r0Kj<=!2%bg;1(Y^OCq#^b%1jyEJEglr}CnM}eIPCcw2OSS{Ek2q%I|D@0KYZtQ`50hS=zbv};%{!Y*- zerg9Bcw!6e3orrp3Xu*2?M?P`ck=OvEIt?jm!VXTSZYRB2RBz&XICd@r0zXu>j0b-5eW#KcEH6X!o}dEgy6#Bj&8NOtAo3v zkFTer8&ZJ@b~ZQ%feCQo2zDaa?cn?Yu1mpU1K?s07f*M0M`xrS1~~VE0~+jNoP{Vj z=MRL7-CYCR{C$wZ7*XDV-2_h2;6^7nK@5C4_H^?0b@KtATL5iH;Sbb-cd@refQOeO zc;`IS#b{v+u2{gO1~`F(LmQlg!9~XaqzWGw{{VN;u@>;M0z6_0P7B~pGMGR(aiGK4 z-ND&4z{$IdSA@sFwHR1Gw&;Py47N@c zxVHeFwgH79e0P z>DV7|ckp)c@^td`hD~dqP@i;Lt`SL2!aV1UXn1oEgD^4~`Jj%!o*-;A9QvB4P{SW0a(Y zZ8#BYdiM49b@y{}L>g-bCk(KQz^ApaPk4CRIszbUI9nI0EE4Vv!A<Ry+W1_jK|M@N)&9j||;Xhj0!!8G!>D>`w5+Iyj)g zu>y86xE=?~g8c*Y0CMLXTuy>z5hjCmfXxFJR^R{u%YxN`xu|*5)4|it$J5yhd{hZE zfe@L%5d|aI25@46xs#3w+}Fo7z|9eSlp@sq;B*X5AK*X*_oKi8g9ro!7q#R87n%6S zslfq@D7#@k##U^D2cgl!+s`Y&I{>s*93%lw34_MHSjuj12Pb!b#{e%M(D6Ue@C8>h z;IIToJUG3A0~FyAa6$%$J~+)`1{d8Dh_{2Yqj!Ld8~Cs)M@NLiz%B+m5gh5@^a4)9 zU*ft7$m09@`Mf&m<#h@1^i9N;VkUoQY&Jpiuvu%r-g2hep= z&Tc-Ck$;@IoQS{%SD|3Hg7W~_Ct%Nl#lX=B_BPlj;AjN<1ngP3)4?rZ2oFm$#M{Be z%`d>+33&(@90*{$!8U?T2ipj?8*Dl_*uZupA_N@d5R2&8!1i|V2=H|B^>>BTGGMnO zf)<=Uz<~$$0N5*F4|9h= zfkPVX7qBw0H^6ZNwjAsim<#BbF@0UVTs+-9k$N^@pA41u3Fs^sCr{9w?ci(Y!EOhy zodOpT;M|283*f8_jtX!D;L32|L08nW0-+RKx8pYmp#)sSU@6pnL3gTndHA{_b#uT8 z5^OM-0GD;(gaJ+>hyom3CW4z>UOx}u1~<}37l@gV&D{k2z-2PF>E6_;5f!+8lva}hY7gM z0J{X#T0xBX_&WIe`+IqN`9oYixY9m2If83e@QPzpm-{=o`g%Eg`XcX$1J_32zyuTE zt{ONKgChc5P$B{Xkrlud7FY*(p#a@8fWL!>o12$2@*pfYHG?A;On^fI>_TuHf=ef` zpTGn-*1+isoWj8A3S~%*ZkfQ}!PgOVrnnPi1sJ%UfLeBfeFAnhIMsr^1NIm=62L_s zSQl6cSPU!+KIa822G$0a02>9{bqa|&6*ows3vQUfi(Zr&Gi;`#rz}5LSI`Z%NG%C) zD+ufoZ2G~@0Vh6i*n{&lI8wmz0oDd_EYw}}iVs&u2Ny>#e{UBjqzVEpp@98^8b{zh zE5yOzL1J*YgEJ1;k6{0R32+9ZcXk1J%Ei^m)zbrMi7O(0z^x{5RtNhG>`qkgfyKcQ z1&$+d5(F2L;G_l70QNUnArYNOSO{P%pm9{)u8t1g9`5dLPCk%@h+vmc5;OQJ4mu_` zZ+{IZ`K==4zNj!+u0(Jn{E^z(;rzLP92G055i~~+Z_>u_N%TWfei=S3RVIRSg@^N`$7JNSLv?K4$kiGzHa_LNZA7%;9&h=lfVQxtiUXAT>%au zuo7^M111nwfa@c$EZB=M@6ae^p?JpA-O1g@9ceKG{=AM@xdwJFIJ^-400%SJ3*fkc z1RrfnWn|BI2Ke~+_&^77!Ipz#0UQC~L=JWuBte5y5ZJxo1Ok=;Ck90F2PZACM-XCQ z7FZov8`uDFAb^V)u%Y1Kz#oMk;0Ong6NDmrz{lC$-QC3v(pm&N32Z%>1-1%o0k|Lo zdk-uIwjOLL*h$#Bu3!VeO28=+<~VGH0XSu1GaPG^7sc_ej(*O5(EYn$hk|Vc8whp) zB;dgT1$H<%UchR=0Sab;-3Jb2G)IC{5!hY0;}oaS;D72IXU|h zoefY+EwH^{AA^ktm&xEl1k42|BS>9N<$4dr2QHrO9*_%$pq)JMU>dl64z4A!I2l`e z1FQj@>cHU)jt_9ifx{3MDKsycKo?Uw1-Q8SLt2U8G!70;a8dy$5O8|I)*b;XK_o7) z>%n4RFMzpVqrfb%DX?|J;E2Ru0KyNh0VhbzMld2yTpe88y?i}A!I#QFYddht16vOE z5!iCDtH8kymIa4BIL#nr!Aihfu+I?{FDyPN&&i-#4t_0+p{uKdi?@qsfH!nJ80=cG zyTJhob_Liy10Me9DG@pRJ-ofWd>kPa_2BgZ`d}uq54`+5{N0^FcdM#6I)c3b&XwTs zCs3S%(+oI?gX0aHw!mDpGy_h@U|F!y;CzE#S%UKg*ch-hIOf3#0o@X0xBIwzc{@3| zLHep-6Tpoiurc8D4K5SGW`Ro?u&2NVf|mh+^8q-egIx_)2X-dd0E8IKJLm}kl%Qex z8b*UtE7)MLU%@uQ^?O5gK+Y~g@kxM_dw?(anq6otfZYHN0k9ch$AJw8n+x_GSPZNL z5oTalgT=u91arYgfmvW3FsFh8jCw_pzoWN{lOuEx8C*AkYbk`iU+kOE2|dXW98h@cI&i)L8w6&79SBZI;2=QCM&RHEI{+LmU>3~#G)!IrE^fX~KFIrp z!F~d}2JCEXDQnO)#1RqW4!Q%y-O(F)SrFJ0;1mK5ajK-~tV z!EOUr8!!dPH4H9Wz|I9*0QNW70&v;|*Q#JH#2=u_ya?eLZ_q{e&Tbyi)6WscfO8c% zLxEXf&2R@nhPzQxKiD#`1Hj>m==FhJ40Z*=>tOeQn>kr2PDPZ%!p$iKK^6Nh22yk-^ zaQ5*eX~4jDbh=6-Mr0f#3zfrBFe!9|S6!)lOWhCu=Vf@M-5$l9h}_U z{Cr)ZJC(uGU|Yc~uoiGAf&Bz_BG`-I3<`D_SP9qzVE=%f4OR@61ses<6bM4jix!O;o!2H3K}n0b92{QcZqocy3C#e+izoCLsS z065&hi5Dye_7Avh0e4uzy#$2!zF?m_>Ei~vs}HdR2!Fyx)L#e|A_~AM9vn;HpaSO`uzBF*2$ltB6tFB<8!YXU zSd$@gfWL#Em%E#zGxC`=;9Lr}66_YRMT0d5xH&p_xVm`z`+6enlL8k6;4%bDAqp<+ zz&QgPmf-LO2Q4^M!D3*~f|E8PKY%5`N)YWS1Pk2ILPRN=1CZ>1hrFAkgTGTifWJR< zBpB>0utIROgKYx46192)XEU(VARRA6G$EV{){B~T$!qX|TN4Nyz+6Q0&CS`t*)71? z#R+<&2RNO8RfAbzSAhKj?hS*(3fwFPhb|(laF#`|w2iZ`2A&ZDTLZ3hu>=&>A`TG+ z&JG?f-mdO`$eY5zi)6rtfSs&U_r1NuxhXs z5H(;|AvA(n;7A2W5m*aY46F~L1?)qx5#acR2qKw-2y<@-cXxkRFGmlgMe*Pq5B4ZH z7{GRctpT$TVyKA}s|xUB8rYp+|AEsEm<4t$IH15>NC5yYH;}4VP}2`=CuV{|_`ufz zbf=iJGx$beXvd4%J^+Uz*c;$rL2@lPjUZ$Yi3}ViV84L_3>+f}Z@``J?&#p_=j`m} zkF@uV+O9`91)N#IK_3DNTd-%4nu6fi0!IS4lmI&%oQe=8q$6DJ>frC<>+S%hwuxy1O*2JxIP1i86td;QW#QHAUoaL$Jy7((Ff@) z18O@R;b5#u1QFHXoR5@5KuHU6-YhuM5G`eQPX`w-4;MEl|lOM6hDi_@RbRz{Y}Y0ke=o4DJCB zM+bL*S7#R=52S^iIQLtC8)o2Q8e9Z|39$KK{~(&1UF;qDXQ z?e6UlT}=lL7}WR%hXFWTz-bR0nc&a>#{gIZm;fgPusW~~NYukKB+7U+SUsvOIHM2Z zWUSH%UpPCsd4uk0_duGs2PZ&;d%z}O%ZsS)z^Ve=4gjYQuqoig1U4Bg1}4B_aP#qZa)kss*jTU{aP)$k^Wd-mrxS31fU^#2>Hzx{o=U*;E3m0| zu(QG8g(&X9W`WIzsRG*w?%06~d2kN~HA=BL3}trS!_~pv-^JV04H5_7=mi@Kwur7C z0hi0TJc7+(=pONKb8>X?b%UJN0t+K_nz)dK=>l5|)&jB&R2!p)FSrl}rynp2TwH>a z4qUkkK-~a@N9oTSipn_$=EO7QjlK|%?aPkLd4RHDa8wHji zoc|!rCUE$Jm-fPnZRmZfDEF!&hKD>s_t3fcx&(MY5*j!f@DAC4-46B{m<4t$*k|B2 zAGmx0yBI76R)_F1q^trLu3$m1LNE)g5A0n;5drf7)}bYM9C(5@i#hvvcsgNSKMRfs zup`0Nf*k~Q8fwx9OM+d3a1+F}U|)i@fmvXqzy^T*3sHkjH+~<%o$uxD;N|4y>j%51 z0vrK|*ajE0;PyP&31ExCE<|uqJpxt%mIP;Xu$RCN2OA77LBX;*(c4-P_bg$E8eaKOV-r879MLU>?x;2=Xvz32z_;;A6P)}oaMU^9`T3R1qRKr#bZ zg9~y@fTs7{ygVV^0ZY>?7lRE3rysDF!Jz_nC?u(1%M9RD0Zxlxry)gzvxA4fySJ~4 zD`eXNSQ@MvoY=q#au6hctZB&E!Q0K(&BqUVqS{b$x-00QH5WG@Pozi<2k_v6KWBN6j`7)4mb^g32-6>=VWlV2i!aX=WeheU;?ZJoVLMAz?l%7z`=HzFIN|5Kj>H?g78RFwJixcA$%Mvql5|eUL<5N=85=(PR81OlPAwIq+H8ID* z*Z>0Hd=oUj1s1-cfdO27GDIFmB8yuXSQsMl4GoM8OyK-fhz@ii0QW8Sm#KbVkJOzoLWN2xOurJLtCCNC&93gL(Y+-Dgir||YSfnKxA@P$9 zlTDM6_$kS0rbzCE_!A+Y2m(m%NisDsNi#;sqec`$JS8bP$vnx>)W|H=)Bwpn&lOce46vF56UQ{i5Qlmr6f0ovG%+y7A#Q?8+|nQ|1s)G328m!dBg{)m zL)M58PXVig5pZ!sGh;&|gd9Q~&0NEjGzfsiGeRBqcr-Cg1t%c5IwOcb5qyZ92!0wk zg5i8)LnDw|5qwiqR6d$JjB&WbIMo#75V$!e=3tK@_{m_M2!2{BNCBJ=wiPKofK5Z@ zCmI@9AoG(`%ngwEW`^cz$te7!6cm11T3QOSd|FysDiR-L7_xchhDPQ{<)ewYd7_~) zvU*D+i!>yDA~@FI`OXv^$_V)+P~ae$p9Be3cz#SugD8N7M-n8OKv4n4Fmb3huo#Fy zh-1$W$p+ZdX|e$*ogv~9ZjKqqA7FP^kQwGXv1_lNuIB2Lcb5Oy9 zgwgp>Wsn#J1tbKc^U;*Sq|s?4WgsbB7_Q6$luvPDs4`d~j*EsXLro!A%6d@LqZiTW ze5h&3pwLeL~ zsc?2?US>&ra%pODMrvwFd`VGaa&~-XNorAINq!MbJy=ayeo<0pG05||sd*(BYVuMm z(9MBq2TP%9he_oZq~;;B;}i2zK>fOu_=4oz__U(L+|=UuG-D%zYKjfb2&+LeCoMAv z9t`h~6RH%quZmK05-Uj7nJsCEz&ocz)G z;4}rY8ms~meNZ*Ii8(p>$xseB|A3r_9)6I_3o-$x5=iL7m4MSd5k}!uhh`qc(RdO) z+-5u~5oY4{Ai4}f40QG8||q6h$GR+w~7YF>It26pMv%oG%pks=4g zj8CnAS_$TXk~TUYi<0=%3UK}eIRLH?n+RGZf~Fo>7Mt#(%o0#NiKYuA8eg1RSelww zk_itnBxj&0zzj2@)Z%gwlGQksfa+7Yt09>iIaDDcXyFPKM5$S@XetN!4wpER+lWwt zBVcg40K;mq01i9R<#1UB@=iQ9D|1UR$_}^?B8MS_;CT#ZagQj3a4JCV;2D$_y31E@IrwiO*2YCuzA5IB`@6fdr8=B#?s@TvBw~4qyA->qy2)BvG zMyMthCBlj)P(mrmhjw#7o`VV$!!0T{LaXAS0tnB+1Q1#vEdoSHgAz|kems&EEX`Sv z?InrXsqy*cd8tLk8JPv~`Dw`g4~T*yP;Ce;S~9CrmZ+hiC(V1R>VKTePqW7+n~V_%LMP@c@wlMMQ35d}2;!dR}TuJh;wB;zOIc zAd!NCoXYsJ{G8I<)cCZ-QcNtttggHz$>*rKJ|dgL@d@Vja|00J{W5x+Fd|F9kz5IX^EgGd&)w z7Er=POf-XgOT`!lfD~bE>w#R3>;RBLtgSwDg;?DJ5{ysIFQ~+DKyG3Ih8SA$g+)Fs zJ`sC>6s4wto4W)(4pT^!=V6M83ILE|kbki{8@qSY&c#9@p@%)l>f+Rrc<=}c zHc4m+gk1`o+X&l|Fi;I!ld%216P|2cHxayqLycAgJlT?sqgkdL04BbYM7~BL92ZwDSDI~iJ z(y*8gPMM$~5SVug(%@M)sUR&Lr_u2UlM%xx(1N2FR4ZlXLAuGs@o7c*x$!Bf#mPmP z1)y=w+{AdIwB%;yURQG8Kqabj*k z4rrJNy6}`%z}7u6hOzBK@P~zD*+E?qgEpz&x3?h^Gb6; z!{eZ3N@bbJsgQ&L)dO)ANH{(-FD)PJA`B@E9Xa{w$SOhZM&TF7=j7)XB!PzJi%Sa% z@{3ARA+CoS4NC3#X~n?1(o1JD-m}K z2pqkjjtDk22vZ>rz+)UjDXdf?(qvfUh-l@IRua+5Adx&Orn)JT8h$ieM#BenBO)xgB2)k_T0qxXUBlvdGySO%*7Qq*fH9CYPk9#OIfm z6qJ@gQYWNj#9}K*M`j*K9a24k#Z;(a_!SkU7M5lfrKZF~OhhV^vDggDifEQ$FI&+R zf!Z8Ji6yC6jD<n^mM8`dO{Rk40l8s|ECmW8T&*^AbznXscZ0c5 z8$ib3YR@67LkSnCUTD(=Ss9i#E3!1W9fg`6F{DxETXGY@t+;rojo3p8-iU$?m4l}F zKssR~=lQwuWoh6-7^(s?TR6lg9AyC%@22Bh4;&;ywO z#;y$_hs!n`29_t5VK)stN`NVqlbDPlh#B+X0DuT%4;-jmZhT*k2rMT@B_#&h!hTem~jQtg3ZrRact&*R~lf1ADSW9 z$`)v%1D7ip1qW0Tsn7t4Wag#EgIZRgZV*~)8`iu8xfGNvATG+xD@iTNNlh$EMeAFo zq=MUh7>0mF!Py(qT!R<`7lk(dpt?coaubU|ZU6Md%)I!NBsAqvNysP^np|-Sth#|X zB_lB}B_|c`J#g8YoS2gX@-LEBP)jvFF(oBFFR>)EEEQ73BGnHN!w?|=b~=a)Z977p zo>?4UT#^WDn?eOaH67H+nEpYM#PAQ;C?rP~m*j)TM4;v(#bOR*Z40P)%K?qtgEi$8 zSLT8lr3E>u74bz$@IGY3~LR3S?I!p57k>U_kbAbEqxN0r90&u!Rq$jvIuBr^I02Evxm%=&VS_xAG z5`l%YTcNM2$txVZ-w z1J9lpq!vN@moT-dd7$+lDX?4#Zij%>Ljous*$AjmK~ZLYQD#YHd}dxsY6Z-k;*!L? zl*FPGSnoF;+WyMOEY3*H!5qkei-RmfY9hnMVId9|z~0J*OCwTZMnRe(dXot(hHe!| z9o!la2Zt3PDHP)|hNUt=H9-16Lv6_jipPyNhpBN8LC^?yVsfqAmqg0euC&&m4d5|v%bhfbSfpm_L zk_lE-m>n*x%J6r&Ftor@2!;gq=)_P4s#ahfFPxHyt`~+9L}(LY6LW$pPLBoc%XAIJGD~B@@)JOa^Bks8msEaVoefgYv;;1zPBU#nAnV$ao-~ z&{Zu+DGDlr6yY!tL}bH65D^U(K}0fi?F+KOko7Xi217-V42Fsz84M9YG8nOj2Ab)R z7SW)IAQ=qNg_H-Nx)26K*1#Y+7$Sh=UZ?=VRHy*LwGaWQnW>4{7{yuvSP+z;kV-X( zAgoY>@UWL@5IIDVhLmSuagQVfk9v?8MixR5L{FeOTgG9lRnDT_dv(Ckr^6pt-y6eWS?ZXwyCC`;^xkH`w3Qwu=r z=5rH4CV;9m%w{}vS~#@`oaYI&-%%A~G~7|8Fjgb=}=iV*Uk`ViJC z#}q|0#}RVKVOCt4Qv#pC2ko`UO^h#2P6Ul^L6!=^;J5m8m4wG>e$akUo_s$g-2Dgtjiq6s6KiwFr&$_6(&z_VrGo+v^RoYv7t zTM^Po;agIaSWp06u~l4Bl$=-s>D7YRx!}<~5DPQ|398tNON#Q4mELh7Cs^f;(&%XVG^(r9gsMP4W88o`wlk#hFu2Iq=w6ZN4dc~_<9ED4m*&O z(u$GRIl$KepeYC04tEv86p%2IWe5q7BeAFl1wIzxlGGgV5F?tU@KO~dj%*^bG01x1 z8bEU@_!2Bk9xbWDgmEQOkUT7T!r1VHi6Q_A6u1x~m4bvoUO`KrAYqWNKpk^X>jRu} zK_LZ_K%~j?#IjV-FdL|Sjy$&t7e{g(L?L#&Aab}Ygvpk~gUb6%SQiFv38-vL0~Nin zHC-q&ko75`5)xc=p~%5{I&eWGuO%15b4)p8EVH;I2|7CkO|D1+kcmie;Dg5?u}Fco zW5ySh7H5EBnE_J)G*XZx(sD|RGq7p_C#L*d&|XqxlR(0$MMe2V=z^g984s=dkt_hI z2S;suT26jq35G7PlZ!L*i||Q7M&-c+bt&j3fJ+ZF3!xE$-8B$N&^#R$_n=7PvlgZh ztJ6^=ahU=xNkE~7>?62fd~s?rc;yz7*Wi-q@eh{3j9joVw&VcH2FSL8_>d(Y7^0AA z9wbK;mlUNY=Efrm45(*ObYiH1F3~G1&Vw9O#h|Z%3JlGR4K0if4J<5C#SNfba{~(_ z6EHi0i;;nWfyac2L5Y=tfpHX$hQMeDjE2By2#kinXb6mkz-S1Jh5(5nU~eDq>=18Y z;OrOR=j7<&>;OG=6(VdH;O*|{;p`7OVjXg3KI|k|10#1|A2%l_KZkPgX|xD&FP8v! zhaxmVKQ|vgH-A3|Kk#AiaK*+>p3W}59^Ot4<%!_Kk`XeV0Uln?P97c(k*?qa$q_O> z9`1gQ&Tf7Vxdq_EVi7X_&fbmz?g8!&{=ukvOq^UDU3>!EeH}dg+)$lh;^gn+?BnL= z?%?Sh1it7DZk~x#fUA3ekB_T^XRsUib}odJv!hplt5ZOLLt1VL*ws^5bhY}ifW*jV}OgZqep;4PDurtE-z0HS6@F@X9rJrKkyOi2m`&HTs<6J zTpS#Ig2A^3A<1}ndwY8NIQW(3fDdLy$oRN;`1m=y`#L11l%Pv_ID7f{`g%H~6r+ce zuZO$4H|Vy`q*QeG`MJ1x2RQk8Iuz#?pcxq847wpUz~3P;ITL(*Il>tM&i(=3&fcyL zKAyp7i6+3y-^b6z-NgYkR)QK#0sbCd?g5Uz4xWxdsJcv@{Q^Awe0^OV!ZY)HQPZ`l zx4(~@yQ_<%gNv(Es5^?0roK)tuI@h0-VUxoLH?-WYwGLn=IZ9_2s#tm&BtEF*{#@K z#m&#&4Aq1HS8spE08d|s;=Ghh@bT(!Z<@I|1$cS*`}#YCI|e%jfN$?WNV>ZQ_&Wx8 zdpS5dg0?cEm}=(XFefTU~gh(Z(?kSChh0w;R?$92KFX~#`Y#?(w=Vap8g&# zt`5$n#U=URiwYbVprxr9D11GfoIM;IOYBWk^H5{P+{M+`)6?6>8Dz1c5r)m?E?&N# z?x1VCJ@deu!BE^~?&|L7$oekp1u zGWT}>G(9ICvpi=$tFn}@qY0anuk0=#_P zoqc^B5+O;%+0z7cRSJq3mQGH7&W?^=0S^8Jsd>RZ_9`B}uBcUvrL&WVx0|=SlS6rC zUT&_vNlFfCUbS@b_VDlzaCdUZ0&SK9t=mMk!_(K-*WcUQ*C9VA)z}D&0p2dYelC7~ zK46bR)*7Rl;O*}2?i=9i->lnOd-0DP+mJjR_I9bBB9 zd_BBeJP@&J>H=P_jpAsKLN7-bj{r|sNWi3|CMK6;mL;N9f=-~Dn4Me$TwGinf=fVq z!rY4Tb3tdkqb6}DM+Z-TFAonN7heaz{1SW9%-n*URM1dvDrz=#a&+)=addQY4sZpv zK2kmN98*%9z!P@p4)k{ObMcXIZ0b#PBDaY-%C zF3B$l&&*572j9qs2pcB{Hz#L5Z$E#32j_g}qSVBaRL?xf(gswkogCbpJw5%LTwNRj zit-D>5{ohu^Gcj^QWL=k`Xig@;o<5T;O^_{5R{r8SejZ?8J3t+n(A7CT5UNwIe7TG zyF0r&xg$K2Sd?B03Ltd5J>49=T%BC}A@P!&lL=a-4H{TQSLo&H>F(?AIWw$hX7|6PwxP4h)Y2Wyc9?RKn(zA2M@=90DmVR4~Unbfe)TL#8Bzx=jP|_=;;uUT9lhv z3=SgGl+?UTOzS;8odY}_-609yzqABbOgTGvySTaex_LN4%9_NIM0->C4lz^*xHxz^ z1-Sb9d3iycT997^T5kkdI*P8;+uP60-^t0v!4tG)(%uj<H1)wFu zw9;bq0CREhclPpd_4jrJCjv--VNV4v4*o8FPX6uzZVu2w&NL5H`he#U(9LyqaB}i- zbN2D@0@WwreU^FlrpbwU$*DOxn2Mb}-CX>=T)jYk3r;O@&d*B$?`-!?EC%1+jPRVR zgNLh^tFw!{r-NT=NqK%zw!LX-9*&4}1$8H!Jpw#nsWHE((jMaK)ZBs+wDKHu@rt*% zpRcbYsBm-3FG@`>0;OJT<(sR6r?;oGm%FpKgI}nRkG(0Zt%9`zaCPwXbMo@_c6W!= zTe*pOmG-9Ka>f)iM}$_2xH|aw`MY}idU`te%NNgE|*i&ub) zpS!C!!ZA6SNkxf8l{j7F=HTk?72xjV<^xMrc%9?!;vC@Z?dAgwN_Z0qYgoEDczAg@ zdV2bMIkD7o`@X zB>{H_7hg|*Z+{P1;T)P*fICLr9bCOV++6*9Jsk|q>@AZrOYBX9!RIO2n|kJzq^6@M z0e8?u$$3qCg2-ZT+b(d2>_t)R7Q+#LekygZx&++2`r8CTfR z5NOqxhl7ighnK&ry9X>a;tfm>2R9F27e7C*0Mv30v=kvTF(OA5#>lqerhq)`>7R~XdNskPX{lL0B1jc4;Q4O2Oia64QLs`)4|`#(aFcz2g&dF z3kXjKe|H}rA6FM|2e-sbP-SCUl5cOC3~q$ln}XNMLvBMuq;F3Le_u~$FBcaNZ0-VO z)6!zJvcl6Lz{SVa2UKans>A#ua8nO_T7bQ2QfV<-1>@!5hJ935S)=%lnM&l zlvHSF;fQ-L2X}u^XW7f!0dj#W=-zo3KYLSW4@W+*%eeVK;=N!0Hf6p zUJf2EEeDPdcD9GqO7o&9~CJmF2}V9>&zRD07PP@f7~ zN2b`Dg6lp|9UN0qz_qDqT4HWy4y?6@BXjvVc(^%xx_bJ8240FwGC@1A^YiRY6Jdsc z7KMQW4kgI^96a3}ot?a#y|H8_T(yv&gRiTHUx2SCYJeeX6?;?AxDfhKnv=hSlas59 zr(1w4NC&h_CW;zQfmvon-4WD`8)Wzd3!qt_<*j4cJqOZhr9XNn2*oE8y=0ng*#CP{;9|0vy~No&4Mayih9+ zaQ}utPzN~pIy?I~`UZHw%P~Z++Z2?f^78ZSEsGaQMXR%;gR7snv!AaQ=zdOI;Rfxf+na(Kv&Cp3=j`a<>*wU;?c$A^Dq)o+ zsDG7OWN%uM0qXXnYj$$*^mcdhbOGP)i_3o4&Plif;I*zZXavIB%ge>j4JDl+X9>uF zIXq{WAZzyZ_HcIgb`Nl{S8?+}i*uBrF3^qFh-gO`;p*T7>Y{@BS&$JJ2pIsXnW4qG zDaxP>q%1Z?Ho?Q+(aG1(*AZTjfy@X1jVznuHUilIZ+~xBM`ss5R09%=i%T+!@=Mb* zkSYPF^&lHy7}+FWmjFk1KYv862vXz>sY~olgYrvDK!rJ|Y{f7L#Ta*g4{t|bRAYkR zC*|RHk3CZ0xH*8PZk%1bTp%$7oxK2MOXQ554yyH`<(Fx4KEf&P4z4av0q)-5rjMHs zZ1w}B-w%9LhCL{2mf5jAj^?;LrW#-kiTgPihi#E4;N=h-o#~lQEEw1W@=fgy{RXt28KvtODf)Q`lUMX)&^H ze=je0Uq4@e2gi~U&^QrTDX0&DoK}!ZCWKyZ2OrQpv9FgCIMHB_Hbk650uQ0x$HC3f z&%@uv5wu7FSHOc~9a5>csyN{cz zm!}VC0S8)KgV!m8x@1L&vacvVClxuZf|`KdUVcuVp#BxAe&oakE~FrRVAJHBOl14L zJUzS}9o-#?3i5JfodKR60ghgK?Aj*svk)uVZBZc?yjC5etvGyvIR$|Lz>s%P0e|U zh;r4%$-&dZ+1K42>T5)$0Gn+#Ms2l%XMPYacXIIeadPu>ae-tr93>;7fB-uHq21ZR z)!X0M+1(iuXt<&bm*bs5gVuhoJ}y2E!I8lsuDOXci;Y)6Ne5j@}+FE{^yU8niqIdpQq&3%!AhvqONNo1c@H6Ew_m z#27fF?M*SXgC<#goL#-W$+O+X!Oz#j+0E4*fANINc2@^aeErC;;^yrGX?5W( z86bmS2)&*TPA+bKegPhkWKN=9{{VkqM=t`E7_Q**c5rud^mYX;fC3e0xbiiwO31~> z!O7Lr+sW4v;&f#FLEy8-!QrC60wj-OR)X>FsU^+`ce^?} zxH@?{x;VQ)vI%l}L3TKz9?H$EKx%rpIy<;IdIz}rIC>(*87OgMiSf*m3{a_zsQN*x z5J6)^&i-WT_VIOd_Vx3`->rjm%s?@gT2xYr2sT#-S64S@CwC`Es|h36kOZKSfUGyb z-P_sE-^URW-8c$GL^B!GU_@wlbMWwYar1L>hbA^0+7Y1+%Bu+N9u6)ZegQ6i#6&p6 z=_q=Ad^{bUeW0CL99Dy(5L{HGqU3N_4+n2IcQ0>Gcl_xEmv&DFS0`6@H&1U!xFH2G zB&i~&IdJa=v?Unfcy9-1PcLsjKR5g_4#}m(rI{t56aEm|{T!U#ynKB80zAk`tbPuD zj;>xFo&k7UY-o+_G=%N`4(?7)j{e@Rkmd)5-;vw7@!+Ko$a=jzy*>SX{e8)Cxxa&t zqmPT1w<~6(1!MG#OcRq5^HLClFRuO${%&r*0WQAK#EQLW!|87~X9piw7k4j5SW6ix z#Syk0G_ULB>*?z50||A!p@x>$-CP_3yu4j}{5>JjsSTFb;9>6O=HTlY;1b~H z0_nlyXrU*Tlq4pD%e8{i5_{7mgnmy4A2&BAcW*xzBI69D>Eq@Jn%NHUcEpS`Pw-ks zXYdj=V^Dz(oDgR7g1yT89PW=9y+YR|k((2awcRR~Y} zIyiZ{x%jzx;;4o32f43?S#Su39Irw-4`1$+!LE0|3YclZB zBcO_?B((^k9W>M7>FDR~2q{`fvfAIlC&0}ziaUq^RmcRy!HnSwj6VWYXAZYVsNxr5s0 zUhaPWj*tqFRPDYFZmt17KAz5=@I~#$p!Q%qsDY3SIcf*J<%O`^-@(VvJHR8r3zAZh zEhk(Oy9YQp`*}J$`r=AwAbVX(L8m<18^YIRfTm~>6l|xcT|``#>iH$S}gk!Pz;$ z-Nnt>1w6SAn}kK4N%V1W_V@C3^6(CTOeKN0nt;dnAw$)mNI{)9_i=Fb@$>WY@^VNi z1}${NT#)bM;O6DyFIX*59 z&aU20-k=q#pl&qQU?z}OeOw%T96@W#aE%~&pxV&e6Vx?x$jMB~N=(ki3|L$|XJ0=T@R~Hwydcmie<*?C>)__=?eFg64O;>j3_9K&U5USkj~i&&5@?xUQBi3@ z3EJv3Uk49oZzp#zKS<#FmzH3JzORFakH1HNpPM6SWxaDwesQXID(ccDUk4vYFJCu5 zcYov+4`^=j@o)|B^z`?2a4O2r1`QGvWEP;tkgtP}pR=#0kGmJt*I>)gjq!K&@NxEX zac}}H#DOivKu+m?4$d9{0d8IaNZS=a%V<#y@^f%;bqxsc^m7K=Z$D2bCvOMOoSf8j&}$HT?j+0)%0mKV)TFmt27gRiGk zfH!C>sdIivVsW-_YOY&RDr(~Ockpxcad&s|azlhEct8g=tN1(kIeB^p1Ox;)1SEpS zK5&JXpJ#x(Gbnk&wv%DmA`sx<>f-I{8sHC2qM$X#kaZl$K@s5K>hA0B<_p@Ymmdng z)E?T4M^)wS;pXJ)=;Q<6Sch$ab%2AHzlW=bqa$=*2FN<_vG^#dIl#f&!{6D@#mn2l zIUn95NOi41Q|0I4;qL72?dsrJjAtuVfJ1<{vzM2Tk1z82_?*nVY?RfYhK4@=&hGwx z;C6%~$|_$&V`qOaM?X(j&}f*WqXEbnAPlR64Nbkg0z7^FoIs5rM@R6Ac<=^pR7JjC zpiLp(peDJaBX~;_rXq6}PtO3~0O&^YT#x|;AOgHo8^nOyY3}3XG& zTH`@1h%QqQ6Echl3NX+DQAbBm3(L_Fyr~Q%jclx+tG|<*hcBpc4^r>!1X2Jo$`Zu1 zS8?$N(cpLidmHRTaCm~nz{VRAJ8$8R273VTY{Dc{_spj*gCCW5JFAuNVhA2F%50Hh9}BRX5U zy+Hj^M@MjkfK3ED8Pvi6`4hZ91w4+W0t;EV7YI1r-_y;*)yD-A>0qaWqYq3V0uwcC zz@d*CHkgUs$I`Cm{emlY zAwB>H4HlPsJ9s;~y88G-@+mkJz!3;eHQ=q)-~9`hmPiFbcdKd|U%OyaT{%IUF4k2?v}q!C4fX zf57es`x9a(EQb-az}LaW$<^7%%Nde9Nsb3Bxy%CMGekqe(8<@q#WTR&&(DYQ*an9t zmVD;x;NcqJ=;89)^uT@32$-~75v^N4YSV$eKa7B^7gSWq@tG|mIQYrukBG@k^ zMGoIM;}Jl(*vZWP%A$y;E1a3pkR zM+Z+gS0_h5M@S0@9At2JLiD2~V9;;?$V1>H2Cw&_O&D;go&k~u`xhJ&;9>w?r69G> z4MBDX_y%}6ftN%g?*^=jY`X;O+;S(Simq*mtlH!O@>O~)f>ej#{9>;{ z$I$Wh@pN_b@Pf39!6_58kqgvbgA{?_NB}1jaDqX^1h|R@CmpaDINzW|F+@LVQh_u9 zP;#QXgP*Unmp`;DX9b7y>2fRT0kznV5lM6Tqz=1IUZu0g89XaC% z=_-SB1URO^1USsW85bM{;A9OhT)^tURS>u_2bKl<0xS#W!n^{FDsbw9<|U+dsk5hp zkEfHfvoCm-7n+B_vt?Gl^~~*VDnz+r`n>(E}1F;E3`8Wod9#i`1{6V{YK z9Rto1;LHIIC2*j_!kcbM+S|d+$=B7(-2>7e2j^dK%!3JV(g7!lfpD0+kE^dUv^EFl zHESw zl_=Ov;Evn?ILy~Cz{AB2I?e`m7$PdcB^Ef{4S>V^{oK9WK}WuUN^!7DP>Tp~fPuU6 z;M@u(z!?>s3c-;LE;hkI4E6+A2UrYTaf5>$EDKJukfIQ!E#mDE;Ns-z=ML@8fkOve zc7nBnxdW2}K_f%n0X|OPg_xkm8KnISb`jWt;5H4sU>=6!YLd6u6rX zuJ6Gr!NVS439uPp7I+{UOn_nq(X94$aCi0a^>cQGH1NTp4-O=7w15jNaNK|;z|{;| ztp`hB*ao1%W`IotM=ioMr2Z^Qy!kqKx_G!bxw%5>7jQQaY(3Zza72T-V1r>c)2&?h zb?|a?_V#dxv{b<3X)rfHeGd+PaB4+O^5DbHXW=VoP@x-5TOQhCmqAe-z~t;(-FKA6gqbVb`#hq;M@<+ z!C=o0fa?NW{oEbh9YG5g9UZ}r00$O0*b(UzEP*%GLk0{`vLx6Huxa4vK$r$@)`H^+ zH75NXocsb@Tm$?d<6MZs031wUqg5ZJw7u>lAP;O^t=@8}06 zEfRHf1kYK)908qpM=4yv5k~vU)!!k&%f;W%)di{D4-PMIo&cBY;Fc{o>3~akn4{@c z!@4*+xOzD|digs+T5jMp4JN?u1gAQ%>%d75?BW55VHZaScXv;Z0B3K=5H>gh!MOwM zL@)u)CtxwKEJ`AvV|aSFID3K5NCXvr;GhKeLBRnCb{IJF!9@#LY(T1F7tkdEE*`Fq zkU0pjyTI*aaQK1a8BBm(2o7EFnjdhv4JN>e0-R*Q>T*Gz1uF(~QPK#sRg01V!MzF0 z;t=F4_kaLrckrGaXmJQmiQqs(m<_fHyy6MPDmq59pQodrbAUgj6%QL811CXn@eg(u z*p=Ym0uLI4V;UTAU|BE$Rs!}HJnMr;1|U30$BnF25H5}m0iNETUXUG<(4YiQnuBcv z$2d4p2RbD>JGgrKJGljTBb7klAOHs!I6Wd#7dS8xBNUKOqF+Vh;tcBOy88P$LGlea zdxM)0U;>fR2ScOL1$54{M}V8NE2Mt{b}W7;f+6feTV_iw~S75!%2yz&R8aA^6*q zF3t|#-d=uQu&GvXFoM%2(&P+E8yB3Tz)l1c;93)`7VHv)6Tzhwq9_J8_Q1XXOAzfk zA4h+GC+HMEwSx^ju?6-8m;if)NQZ&;Ci}TN`S?Q?9}IxYP^w2PHKVJ8o2#p{tCKTQ z_Z~dM0gef9@&daW>~(O_InbHJ)xpip-Pr|n%L%A;08WaC1O!ey;9?TtVsKJIaA9#r zw_4rR!QIiv*VEArslWs~8=Ql{1h{YnI}z-5aQ*<-rC_lEaIuGrr@OnOGg1!&oO{6m z4R$fkLKK|y2g1eft^scTK1gAVDDS{-0;gzjqZ6DU20k5oI(hrL`GC(YfHtJ?2kOAP z*xMt(!^;u8a~|qqv@ix&EZ|ZDoWQ}M4Nk(~qGJG3g^!DWfIH||3wT)p9x(-{1#l-B zOdy;%&|&QE;OrXUjxr6g7!eiiC46Gkp z^uS^UTc--#TL4enfWijn365Qz$G6u6-R_8nL+xK#spFgU^>JS^3lyMw2vr@N~Qc&jWl$>I+QaM*zj z1-lmPP>At#><_p*c)NIcI{A7-rkcQc37iVRVFV5oa4g~v-vMy8ucxDnpCkBOF=%Mx zui3yo18}T@lMgtR42ZLxJRRLVA+0oUXd{v!I6)wS94rgYjNrfrM+jE00$44MfU{Z>EPt->gWgCvjTP)UZ;c8EjSs23m0(8023&k z1-dmjJsn*A-CTVg!N*8L^C5Vk4P2xlic_$&!NoH;`G8X@*x6vOfFoc4!rsH#&)vt# z8!{Av$T(mY*u~&H2d*fX&ufD;qUopem#zCNx2ZjRui6rt`1r(ddIz|;fe)*4bVN7|>|(GJ!I2J5FW@u` zmH@NB9RzS$2=)ay=D~FqSP3`;z~v4i7{KufE<(Xd;E4mArQquYz^ezq^&XZK;_U#s zPRiNM2Qu=HGnW$)*x)J@>=1As0Q&^&S+E#58o}NM`ve?~V4r|J3wJuW1q|U~X@+<^ zxVZTRxH};a0fPepY&Y0Ou<2kM!FGd92L~J2ZbXEDgB)TJ9UIu*4juuXPQL!GkXi=p zc0|yE(+4>4z#ag51?&L?m%IW59I9ZygN=iEo5)hc8+7?+fRhtscOcl!;0Yjb^nsm= z>MC$ZgZ%@uiy0aa8?L64+ z;I&iWA_APdP-6j{mBCQ~jsRR44m{|JT2>&Gg6nqt1|gJyix@10x-aNX6)z88SEOzZ zI6;C91{2`24xBK+Nd!@VgUdv4lM5^XRsvQ`_XOnY;O6P>@8%CaG?VPA3G8poj7Ya^ z;OpS+;cKk;6MeZ5ikp!p~3YDI6Z;W4Ok4EA`pR(uPugcBnKSF*i1tdec&(w zml714jb5 z$OG#FD*=mvWx?mXfW^Stz!G4iV7pEsF{k1NDRjXNGkDR9GGm6#bo7+v=jsZ&!4|0{ z0d56>U4l(N*g4?D2M&91o(4w>I6lDIAdZE)i(c{J>geF&=;iP2;)GN|pd}Qre^BEH z+-HS27(7S}4tH?I0s9f`A20#VVD!!|AWyltI=Om!AT4o4#1FXD1kUPUpMl+p>OHVH zIHJID1WtnBLK2*`AR55_1}h|@6A23eYy~uqs@v7k!P~>#-Ob4dvJesM5=vqQU&TSk zD0~*fg-=U|YdTzyS-k6>LAqzwj#E)!D(>-QCyC-v=psfCC(?A8Zns0EZQr1+FW= zAp}+eu5rKw!U}MG1eOJR5#}8lr7RTBc)B~e`?w=5X274<5i8fg&IN}T!XMyZ273V< zH;~|?ZK;gx8P5P8A0Hp+ATHQ)a4di$0G!CdPJ<+9a0&vu7o0%AGT_93NdDlY1@;I+ z49o(n18V~t01gCj5d$_9931$g&;uOd;BkUbWDoc_ySux)xItQrU?+jC2eZIdfh_~?==M<*v| zU!t=CYN-Xb7wluO@!&EUT!?_V;A8};%c)%Nq4>bX)7=Ad!4R~Q2OdlVx6i?~Bo-%Q zYj1!xfKwefoWbz{4mofb!Xky{B@^gkN~Zu9SAR$=5uC=sp$SeZ-~<9rFWA~6U?qse z1$I4H4D1Ck7i<)m1vUk?ZWtVq_zOVT;U=(PI+L< z!9D_84t5nd*uk>kum`6Zge+JIm<#qfqT+?S0vy@!COJ5>fHNN0NHhzv=44PU2fr4^ z(ACw!#oNU*z#BRq40bKp7;r#>T>*B_fQLVNN<iPg z69T#=$f57!?&a;|J(G1e*meWx$>S8wg$o0L};Cln!<^SRL4z zU;_|hFz=uz1W=VI{~~I6zo*6 z6ToR1>@P@yMoGwE4PfVjCBX?9lFY#Q0&D=-bKvv{4im6KMD_zqfR%vl00#_$1ujrv z{zp_F2={wBID2>n_YVbI!4td zAh-hw4q|X{!V?2HG%0BZB0Jv8+tuAI0J`58HrWi0T-444IPrp`2b^TV@eLLOM>@=H z9#FSIX>bYyR~s+|$TbWuTM#)A>~F9I;Is>_Rl!_DA}d08#v63ey|bGK^z?ItG2mPU z&QM?$STo!~kl}8W)DN}{>_c#_LiGB;E(W^-;dQWkz|9=61b$n=O2DRo;}ym6E&={d z?ylgwcc8fel4C$k6>yk?3kjaIk~z1UnU627$S#2@PE8g53uWCveDuHGuVlqXy04F$bR3gN{@c|~l`oXybtOOC;1T2GJo@D6i?cne06X53wJ%J7Ea&TD!u0*gj z_`og#yB_QhuoJ*e1t$QoJ0MvMlr+GK!Oj9JhNKkuk-R8*pRmo~BnY+&Y(4zKQA1Z> z2PZc-KVMhqPGzt(*j6wLtOXoOU_XJK2=*d4gMu9fRs!|_*gs%rgB62i!A5~I1wt0A z1k43H3+^+p!9>&%h_vtP;Ogcd;O>cWdLh_aa6E&(0k&)~W?o+he?K=DCqL*(@!*gF zCjoF701h{B;suL={R1vrz#Ud_F9G2_uwFz<4IC{5!xyXxY&d=k!KF3WNTlIOe+Oqj zS4U4jr=ePO`a5`f`nZAa>O(96!k@4a^%sJLhyrkm2gedPsKEIKY#ul{f@Q%O1uP5J z221-S)?|nr;P2q)Z0s{<2YZD3ij9WdV#m6MUZ;OFh->Eh{3lhzM74}qf;>{_rx!D$pM2F^8zd;yk4 zD?7ju1NJ0XG5q33LpNs!e@9U2fu30kUa4ab$*$lfi(nhTi5Q%6z$KAASO#GxSP-lR ztQu?uL=D(g2#sJCI8wn;1l9r;1M7om0s9ba1UP;nf=K2d!ra@z-QC~S%h3aAQ9L-u zgFOlk2C!XVYrrgo7-}NLsscQj26iXdf8ewOW`P|G4k$1eQUHL<4W#N7)bs<}iJ71f zKJaw_-6`hm489Q<+VP^c55S=a_69gukX#E+BM2EpA_GSW*l*wf1IGx$8*t~lJ39FK zIXk=gBkg^ow(Aj20cTcl(1(D+7VH_MrXV=Bz>xqhCBO~`ry_(2=?Is*I{3TzIQu(6 zSBil3frA5V0odz^h6o>|6owQP$WHh6arSj` z^g%kyfZ9$+I2daZK}0n;=OZN%P|`x2Hw%t5L`&J-)4|2d!^O=Bd95=z+kis}93o&X z;GhC?QPT-n5*$)s4dB)aSS`Nf0CpnSFmRaz)(K9W;C29*02hy7gAj&;wSg5QdBEGj z+1k&M$ln2cxrZD026^a=8QAyO+z56FI9R~$2CD-*5v&+BeyHISu(4oUz$~N? zgL}Zk(ZSu{)!D_z18HF=&ixkPh8eh+1{Z-~0&G6mKZxcgSOV2Q;BpD96|5GV{16U; z#|JpvAv|zAV08+z>%Bc39bJ8)yDG8R3l3j!@c|~lR)XCENiaC2!HF51cfpPZn+zrp z{sk)mn+g^Km$Kk|1dd{G84OkePJ3Wk#2A~0ql0&VkBgVHH)Po+I6n=(#ttNqprj#k zxcdZnyLDGHo~@{wH*I{4Pv-4`%fH>*kfpF z!0rcUa!}g;H-n1I>3H~rxNh| z3T)~f>}+s&A&PsjSzz;Fs=zjaJ9gkg9^8XLjZ$n5Lz$iTaCLC^ck%XggTw(idcnqm zEuyPOz~wS7k6?2cx<@?RoE%+z-5{s6z`_WfCN5-Qy1>?gwSX)G)yAmd3oeAg=?BaL z7nk6q16QsBxnlr&Z6Ne+El6BLs$&RS#Tnd|MYRt(z4`k)x%)Xo+t*-w1}K`qtvOuL zgw0`~pg{P<)4|ir(Z$&ldW#!4i-8k5H~_(62R0lWs9;$z3!FXCB*3`|oczIA1Dt-q zMu8;==RZiZ2^{|5rM<9X8+xBA%Dt+H;UQ1ZJ#;R`1V+U4XYumNEILeyZ>jo(Lb=X<$3csY6b z`oZq007n2Kw!sB0xIGVc0@z}(3lUsYkAPKxCBYdT>?N?n!3Kj%P_Qgm9mMsBAOhP4 zE-S%N0j}pDYQTaJzr&sG?dag-@9g5{3N56-HVi(egS`jN%7_UuZ$}4bUw=P;UoXh4 z2CRexdjV0=gM$!U;ei7V9PqGI=?u=R5FS_^ILMGvFZzMKcq&M+wP@u5*i59Tf|Re& z>%YMoT##b|G`;8M&{eZm;4i&INAxQ;WW&o!Oa9RXA4Jjg=9X$Nq zy?tF=A=?hX(qPr##0E}~gCO~1O+(HO-fq5bK7P;>)rOMOT|oz}xw!dwB8{>Pkx=(` zaB*_-_3?rp&j~J=P>VpM$O5%-z-b6ffDTQ#21&OWah^g6lLa>q{Zh$)Hygx()db-4W2fH|iIJ)@<`8tNg2ZuU^ zL2mYA87n3R(mpOTuESejG9fX@jG@$p5e zi8&6&1`q(}o1pP6u<#8H4B+aMA@VR1S=_?F!VrmXXkcVu0_UefbfEJgqDbba8ki

AMEIv8pYLOe0eFvSE(U$Sv(nxQFNJ`JJ)!8f$Dw6sh_ z;wPn;CnNEVOe|88k`VGHCWcAoDMRsYL;kVnVOQ6W|3rx?0;wgVyI6uFf=hwOob8%d($k94AYWKO-xLTQVbFC zn+6d#Lo)}OETN)^^kHIPfYltBIHoy3RoSCfQuWN85#G45$dqVqlsZEI03=c8A1Gs;6v<0 z@YBE%4Cfmg8iCx3;G3GF^3mL3jKdwqsiq)@z|Ao+2YU>`PX_Bm@Y7O33gCROtw`ws zY#K5@(a^vGnV+0uZh*u$Gc-?2M&T!=pzzbu(o&G+)6&vXk@z6Pkj*nUG%`mjA5F~7 z6Ag`#)ms`_q#^MW!LbIi9@x4 z#Xtl?9D9C9Ho%@vlMO)W3=x-bbId^g0K2;)H`f7Vg@J*AAugIBJ|0OKtP%pJC?stl zUm_`k^6`RkW!EmkW!EmkWwQ9khee;AV?V`cNxG-6Ldb*Qe%+o4Gau$(J*Dn1_q$o)ffj2 zQ-+8qa72J}5|{-c;L1#r64MgXj8n1GaAlAx(>N774O5n8U}RyKW^RmwMkq_PNJ=p= zPBXwt!<89YrWq!s7^h*U;mVA`)tzw~b{ekC%*@Or#oRazI}KL`i3Ve6=0d08$`Z|u zQ_M|`)3DQUWvJ0$3@Se$5dbFP%Ai>vENEb03`(I;Hi$wf16N?Mip?0a!UUx^^!g8- z4>iriz{teFEIADq4O50zyO^Ny;mS~bYGR1WhbaS<6{!XWCOBxAGEi1CFfcH|K|__9 zg9;ubjLwHDgTyE(AR!o?kERSJjZPye14-e+aAg*te2No8mB9*eTr^x6Y6`(p)`OxR zy@*EVLrqHtg?^fW0WKP*3>x~$IB2LcSOh=<*T4Xs&yZ|knOKsao0%M+oS$2eSder>j7bNG#rxhjUrWVJi85(gbMuQT zQS?BBi1H#HrRbi-rxeYbP^GwiRg{{OSdp57;%eNgQDoxL^cJNi;z~y7Qb<0+kV5#N zI5jmpz9b)!G-0kROD!tS%!emrC>K;LWagzaq!i^A7{@1N=B30#OaP5>Kt+=ibCOGQ z5=&AcQU&>$c_qbAIcP2gX--cqi3ic3+yT!R@yYq6c_mQAFcsh|gCT=m4=7thwS$=8 zNAn}fxazM70=7EA08idefgUUlmL@BAs@hPdv`6;Q8q7kAL zB${Sy1WK838$n`FqstS^;uCWc%R%yx$Uqi>=s*zxCwP!ZQe{bMF{(DW5JDe92vcLR znF&Km5sF!*;DQ=e2vcJzTqBClK-ypea7}4uCa8{t3Lu;a6F_LmEr|!^n6&(&+(b|o z0vnuPT9lj|pITU&m;-e+Gz)@cLCTX$i;DA$;xqFQMF1$X!lZLj^U_N)uuGR_rl6RN z6gePfd};;MN-z(Uw9)xkl*Ff2fb%EF0dRfTM9?Y`H1)``*mM_VmVoL>G+iLk_~O*U z($u_?On8VPIRi}rW|$GB7MFvNtj4JXRG-3K4awZdp$ZW}3sM?aM+11hs!dMcjB>GnOllccEE)YISe5L&to`?dqg3GQwhRg+)7X_ z0_7MyMLZ}KfQ`bZ4xWzit3-1gP7{j_&4@LTNWT;t8=;yAO{};pOSr%Bs6_J*K9z8H zqE&U!RE{PZp9Cv5&?OK_0E-MhUEl^g$W!S0a7rM2hpwgA&mYs2(cF4qJ>q!=)#D^ham%x2Z#(PB61Vs6LT`t^HNjd!F4_o zAKJ_Xi4+v%RK}O(=alBA#-}AFm*f|LBMpZXk`oE4AkG|cLM5rpY zz@-Qjqd08A;{Z^a#-R&pRViq?IjJ-)Ewv~f+`|AD>!7v**d-{^CGn|wDHy`Z`FUxX z>EIR-h8!s2A|{%_y`^HTimNVB_1s$Lq)-nmu@{?1Gi;>%Vxry;m$)Z%a>YUWP6jO9`BskQG8~Lli9tO<*y|;7ojCT1jeAd{Js~ zVs1eWXqX7(ri{eAywn^}AeE&Sf#lH=0BF1!RRJs(FlDfZ8>Tu)ngr!7m|SjRL3~n9 zVqP}7U~+z5Nn&PRaXd_AL4I*2XyFj1LP((oQwK5;RM5re7r__zAo&+04e55H34+oc z)C_bhKvK|kLNMo`D*=TKc8`=L=468FjpUrnf_QKgK*yOu4#>|d0S{)QRwE$KgM?G_ zN^?QO;TOl}2})cu8c8Z3xtw8TRPN5GN=mEb}v z5qAp+9KE292sSkcQy~t(V;n*$tW+Y>WLV>fXyuSr64A;b%_U-$LmD%9RYMz4_|*{M zNNAG^uQ^E0K?E>7E{aQvU?os~K_#@g9bXQT2UVK5%Ol*f$k`lC6)2CSRurTrm!zh| z=a-fgl$JnJC!}P=Vk<~TW*$f#QaypiRH$M26&0lxmSz^Ero=-`L@JZ9*bK{xXqI6w zThSDO+8jlRC8=19g-OL1C6;7@O9?c+i2MtdCh?$co2X_uro`*g7KiqVPubkv?FAoWdOQSpt&gQnn05pka7b< z4`c!uyEcd%F57SzSe{sh-8Aqh0j5+=VlsvxX3T>F03wV%aG-Lz1;#i`i--6Imn=4C zV>1jTi9(~SEUg&3qhJzPf)FHy!+cOY;?RM^4ISLHO)Lhr{nHaO^WsyI(3C?ZA)`=ea>XUE z>IUMJjKsW@oK(2?z-4Q4Vonaozerj^E!FtMl$7|q#FEUiR7eqvR6jrrLxcd>=^!q& z?Fe;xW^sIRNg}Lm3Kay^bWkT_`Ugo8!#`l7kQ`ZDk`EpeftrgHi#d?BEui8p2Q+dI z)|69RnG0r=7UZN>#1|#O`;;kg5ri43d7w$vqWJjyJjg^mc+&$&Ke7yDxDZ1MEy7_s zKmmko0*D_U4^BOKi8&ZDplJlGf{+XXGqnKG>4gbldIRE6)QUB=47_S5FS7(}3z%1& znGSObn2%%!08T=p5WrRsxq(wP;h}<3g>`p zB}@@W@E}w{if6bwETsfkE!H9eEQ1JyG+1hajnzPX4<6qJ5C4L!0p(awh5{8Jd5O8; z<{nrKJbPY{S_JK1!qlebf!2Sdz;Y$H9RgAh37~jnBcMVBMVa|UnI)C+nRzLx6)X=`aBDyu99Dp&P>jbImdXGPEnymO1kdJR1vrg2LK-~Ez#NtW31J$JY&MQzDUe2l z!@-#yTUC)+42%AFXmS9>Y9_9Jer8F2VmvsZw+f^VZViZo!wQfTit!kE zJqJr(2a6#YZ-gU5g4LlIk6uva6hJ0Nkb?`m5MEAxGI*32 z!b4YEng^55%g-#v7{7oB!u$l`VR0Tr0+gx2L**re|$f5{KkVO&xKoUh*R+JQw zhwnlLB}J?f=vtEVb8|CG&@F(9qALcavf|Pt=sXo9At2d;DvgvXiju&MZG=|Ppg&HH zpsEOxO+ayg)cZl=LVIse5%ALE)S~#5Oi;rz8JvBfQbno7so<&%$_JMfXrTiZL-#8p z85#7%GBfFhm5&V8j|4 zXr@D2M1v-RWH3Y*QXYWnLKqBL1B2vXhyap%p#lg~p#likLIj{@rY2@%6l(=wK~RE1 zD%Bu@utE*O!(OIAOo={SqMcCSt&G=AQ=c1K~@Cv0wUcbvJ6I+ zfMypAaadNtlqf06gk%$>ECOXhvqw=rvk}puRLs3#ZA}fGS zEdZ^X&rJlG0IJe3oAJVz7aa;>?`Xykzu&zT$#RaKi?4KnA$=gCY>0mtUEgnggDog-ak> zLd7LX&;e9Xvc}a$L{){>Qbd)+)m}uXg2ff82)ylxCX8q>8dR&hyDa$*UjR|{h2f=Bm2EYJ)jsA4NFDau1ul9vpVfvpnE zO^k;!p>viXZJ^Ow_=q5g0~+3hNx(*QK;j@acvc(iJJ|Reb{R;M8ZHMOlvUs z>_ARRD@I!90AB}yrW|BD+*JruK*C6tAtXSK#G)P)_*jHXQggsVjA)j^OI46KvWdvX zAnS!|0L`i3ORz9`w4@3X#+67x^04FyW5W|BiU1@~;6jL03K9Z&1ucDogh9Rnb<9Dn z4{*u_g%n5vktWL%%ThtZY@qfz^4uz19LaSMh1l(a$l2sbk^?9kAlnM!LzZ}8 zh(e}$kQ`B5Qk0sQ8;>Y3pq@q1iJ=C%M6awk4{}fyLvChXW+G^5rCxqgmcD|5p_#Fv zg|VT5g{1-tFf=eQGc!|AFf=!^urxPNP*6zVN@=?$VR*zjG5NRv!^|KCFwjdWN=+^) zO9gWn7#OB7Ffg<{WQK7eG?xH_UP@|GX}U@b^2CFf3AG zVVJIpUmeKald3EXE7b6-OJHDNxTVIzV5JUG2MTd){w-o)V2Ds>VK@a<$AlwXK<>Mz z&cbk617aSo@JnN0U|`i`VaV2msKaG%DFXvTrzQ)-Kd3rf_UbS&FbHX}FxY89%wxpi zK9GAnv{)E?(bXjt7sJv3h&F&217gB)1D5n6%fP@84mAs=5L@`EFfcHb5TQ<=7Id7sKdgbri0(VVGIlm22eHl%(G`;U}z&koj3yn z!%-sCf&6iY2z8<$N9jWR0AhkLw)Eo4z`!7?%fb)^Rf{WrlNcBnQgm4ujuBAD%fP^J z0jdUMCkSJ+7o_ek5$Ztk@k*D4;XPC>F866LFfjboWnoa%gZLSjx^M;t25qPsklR2Q zoBKfNw226HnhXpK>xfVXD(|oBu`oQ=hqw>qFKqUL%2QDT7KSg-f&rg8HA5B#RwMl8 zf&6Q3#KO=ARR>ay&0bJ>y3L4%VYM+x1p@;vdqMuVYs|v%(geRcQ2a`pLezl57=*Fe ztIoi{ph1K>P&w{z%EGV}suov1pUcR=aM_fFp}-tsH;BY$FDRU6n6ogHTYyyH_HO|L z1H(!S76wU6h&o*L0?0gFOBMzmD~LK=;RkZJ9aIe{Oh6c$`#|a(iBJcMHy0}w21{%F z_JY!LwlxdGbf`L9`NM~afnlLF3&UGmhNik)q}s7CoVJ6g1GyKMKVCxBfcy%= z*wlf_t2acbD`a3`V6Q%(LmyNvu5baVn_~y2xGGsRF3~8LLJCFMF$p!atDZi@wuuX<1_nPO)NwE{FjP9SFf>Bd;_|N%0|P@h zR1L^Z5XNRNsNUL3ggO}p1_pU2!v2tCU|_H&LY))?149TA>Okh@5uuKqfq|i&2z8)z z+DC*ska?4dP{+o=z%Yvlb)fdk0wUCb?A=a;I#BrSBSIY)0|UbiBGhp*FfcqKLLJDw z*F>lTnfH|lb;1k`4F8BwcaMRALC~3n;k`2?4T4B)={c5}fkE7bg`vb9q=JC~SN`y2 zWnidtXJI()15pP`7ud|}V`5%+p(?GI50BC)CSXJla5?9al$6bMp*yS_Zc$iQF{ z$ina~2%-*``@}#J!4Nf|ybHqE?A^k^z#taF!f+}Cq86kLn>t=*28JskEDR6BA?o-+ zOWpLwN)X!`>K(I$Ythl$n9yeGCi3lmv)6J{;yrvobI!B(gAAr$N-=GVeGe z14C{a3xi1pL>(yoVDrZsMh1qy3>F5fEQmU89QICPVqjR2#lm2f3sHy5-YiB2hMZg$ zhHH5cbs!R(y;B((82;t4Fib80slc7Cb}}$9%q?JH2rI;|4%B~3C}d%%BcN^x2LnS# zAq#^}6~sJHyAhlF{xLH!tgB*SSW^d42P&7asXNce!0@7ug<*RGL>;d50;*>YLe+rM z9tdMI4-~E^iBJbB*Uk~4PKAkq;c5d5!_Ov&`&4nn?+qpfhU^v=2IqGC>OlSYtsN{3 zw>u!}aQPP$UQeNFKz;>bZ2kq6z|;w`6J!<$V>3^lfq_Al2z49S7#LhS zSs1?bL(H_o5iX!~Wig3`A#4&v9WMX+F)%PBOk!bJM?l>%1_p*5lUNw)CPT~vwHvYd zqm_|?Va{Y02F0lmb-40nE)xSo|5O%+Khq)VK=l|l^A>;_t}|H}7R-jI6T;zNZ%{j8 z4hzHlIS_TY+@}!Fz_51?3&Y*t5OrP5*vAt=`OQ(DmEoy6C}lG+xZtq&F9QQZuNEsq zmo`KluJY=K9s|P@ZB~XmQ4n>3jM(j+q0hj;mchy}Z#qO>5)O4oSQ!|Kma;OKt%j%z z!QnnnMh1qub*v1>*F)6dijVgU3=F0lSQ%6{LezosJ+}DZVP#+_*~rS!unnTl0EfLG z*@PXe45>RnEZpTJDBkm-YCw4dgt6JXgo%Nna|bJf?rw;AxayfHObiS=ce671?!&JR zWZunvtPHgW@v8$3L~|ZuWhg%cQ3oQixvvV;|2@RYpnMplf`I{7`pafuU~oLl%CH8i z4pbguGf$V1f#K6(RtEW_5c6=UOJrhTNIlBRaO^ll9f-tc-Umhoh9AdS8D5+OsbFBh zWge*6qIZgwq5d319f-tc9;lq$2vq}$GZ4n64rI^vbF2*eplWfIL!j_F0aXJs3xu(m z2XfEpbF2&(plWfM2kJ-Nf~oOk#X2_n>i#sQRwP$$8_ zz@SHjI#9jmcAk|X8>$wUe`hc-FqEEWW#GL4@e3&2vH8P-k%1xj0xLuJMTk0(d$Fkl zg$wf~RtB+45Ouih-3rS8mslCDLe+uN1vc{{7#SD@FS9aaUV)g0s~iXA5811%3>{Y? z>OiG5HuG8-7#KD{)j(nmdwH7A!N6d0jg?{XJ&1X@+D#3h`u9F7!_xZ@bs!R(z4I9v z7_Q%EWhi?HQo+E0%e*)23=BUWvNAk*2~lSY3MVY>Y%eAT2Ddk?3=`f#)Pd$Nu-SW- zfq`Mudsc?}4-j>@>^;EAz`*#CmEqtoh`J~o_HuAAFu49>W$K(`IB~c*n`cAju6;2g=Xb)G0GDFqm_*F+}k~)Zy~)5k>}v8a_4# z2LXsW5ghLO!^FVAA;`wSA_7qdYDZzSSCNTl-U?eRUl^KieDQx28Li2HimvJh&l@#@ivoz zfnmQ68$*OHL>;blwU&W_AxD>uK~fLDx()0M47z%340lZ->TtD-I~W-l6fM{oR$4;T z;c7>mV`N}3wPIsn26+fH_k+V9l8g)tS8doB-q}LT1CiL`Et7$PLCcPfVX_@a1@3VH z6VUvY9UH?>s5)Hb-vb5)21R=|21W<`<}GDnU@&%IW4Pf2QHQI2I-QY$!N{47;iL;h z9jLy;<_`^K1_oVMHil3Sh&o*P1Jr(e4ON3Lor3bwXCl;r>a!n2r~}QD{3k-44l@IT znkO4WkPpNk(m3K5v;ZQ(myKbwFGL-#bib5=f#ISr8$+2NL>g6V`G>e05K0&eW}UFz~CLo#!wOjQHRSsP(G{+Vq@4wK%D{u1H*wJHiicT z)P*rJFuV$4V{iz8*o!N@faWWfgs?H}gsQ{kK2ZPaa0nX%dnm*_T;&0%e3uSoW3Ym% z!(|@G6c?x(eCZFQ&Wi|jpmtIKk?LZIPv=9)+_p#708Yfk^b?8dwNoQwM5aMH8V8G>)W^z{cR0 z0I?UBfBQk}a}wAXo{%DQrH+oQz7bbxo;K&14DEw8^c#3 z)LEyoFTz4?gPzBN@lS!IA%f2!b$eq7>Ws~Yh`0#sLf(yU@C-| z2b$l*mOnuCs#FmhgJTgy9WHxUYB4Z)7qKyXnFCP=8YjhO-U|)}hFf#l7!ECisKeFH zdCbPZ@M;+w!`ZbEb-3&`Wny5szLt%F2Q)wm8gs=_pI37-Feq$bW0<`YVjeDgZP*zY z4)0`R_d!85s5)W@Au34^f9J-7jZlV8}er#vpJN zq7Ik6tZWPnQdij+t5Njp5A`h&o*URbppg5P8bRVD<*04wt>< zObiSTZ`c^Nzk{g5<=+l|28NgK*cb}`LDUK0h>un#1_r_ZYz)&FK_P;BoUomdfnfnu z4Zd>xGXn#IIy1y9eCjqZF)&E6urr)ugQ&&j-%X4R3|F9P@R?`J$iUFX!On1)6Jj2& z{QI7jf#D%1JHrKj{OU@X85rI{)!?&t8WRIUvM@WtPf>_@xZ-0L69a>`7(2sJ35Yse z{++|fz;GU_2A{pwj0_CfQtS-Nq#@?v^6w&E28JWj>TpS{A23=C{e>Pw+A%tb>! zT=lOsGXq0k4m-oje26++;j#@hE>*zJ@VXG94p(~l#=yY9Sp-poFa7y4FfjZtVrTFv zhN#6Azt&6)3}H|;_{>|xz`!uEl%2u23}POx^ml=gfg!bwox!vMq7IioZg4U%1Xr*# zXg5LB;R?TI(7Z+yJHxeRh&o*UxXr-8@SvHUVRs8e9jXmw5{r z85pcv*%|J%LDX^LDA(E<7#PgjA!_i2-#i8ehQ00V4AC7BwYb6s6t0yW>bPuYh zX9$O?!&Pp8=A*J_u`{I4g_tLZqdW)z&4p#1?0*cm=9gxCurvAGX49%jCXogsD+ zNCobE1RD2FU&PMPLqHv<9-RhNgD+hYU|@(} z$OI8ln!Aez2uekUIO->+!1tt<&1S zo}FPGXu=b;XA?*H0-B$Buz{VyY!k#hT=hAq++DYco#6;n9jY1JF3>Ts5 zaJlaU0|UeRo$L(TcR}pM6(8Rj85r1ivorkL15t;oUjb^bHSA?)klP1Qhb#SoHe5&T zV`tb8Rfo&Jpmxq>s2Y6v9OUkA``8)&LDk|47f`*xv7ep6XFtS!xa_TGVPHtu&(3iF zFhm`$^peBGzz}|fo#F5?h&o*PSAda$;q@_ghTq2_>TtQw29$F8>NJF)#>RWM@#g3{eMa4`M4fdRQ44?pi=)EGw|I3nS{Gu1(_!S zRfEqwa5+YVI*@IuM5qIe>+2Aq4%Gi}CPE#^AF)KJ1Leb1BGiHWTSSDq*-Q)!<#*T_ zgzkfU&cJ{x-Rm+8?kp+_FF;K;4=?YE}kM% z-321lfy$k0M5qJB=Pe@Cfzrz}BGtWq%+Bxusuowd21@t8pla}iivt4#gUAzhhL9(a zaK=?$f!2d%JYi>OgQ~+7zo7bK$rE;l;{?=!`b|%vYVf%a6mP$XPzMS>hNtWdtWP2S zz!ff_{yslc4L*A}g4U%yWoLK|RSSx5Y~|@A1_lP^VDwQl{LIc^ z{sp2A6z~+{|ZruD_lVJ)N-gAeEtB{GyA`?Gn|8}#pRC)p#6hi*%>mv zLF~olKG68fl5gw`{@)?$KqNMQfa=?+-`N>fK-J;0cROg^>vwjB%pV|=aL3zjP&xF2 zox$lRes!S!Tf$FvhIXhrT=wcPGB8a1$3w&F>x>)V}+>0HEt2d$iPs*#=&ro9ik3bd3BSCf#DH52g7bIh&o*6&17a^Naf~W z_{RrP2P#Lfg)=CA4fr`2Ui0Hum(9q)AS=MZ&>;v>2Wl^1Gw&ZG0|S>32Lp!)L>(v} zU{lA&$iOg9go9y;C`28u_+VvZV0a+P!SGZJzdDe4isBp$>Js?Xfwp2NOK>nOk%6cK zmDkwZ2MVt;Sq_G3S%^AZ=?^si*DA}wpeKi49caI}g&YS%76Empp!0L&I2cwEPzRdF z*bG&JuN(rkUk(zXZV9NJBMi4ONG$-UpetQ;CD&I8+@j z^Af}u7%nPtFc?}w>}AHWzic@J1B1Ua2g5gKh&o*L49H$N7Y+s;7l=As_JY)zxo|MJ z5Kspi@APxwV7LfXhb#P=K!SEHT4wrvH<8}YtIT&m`Am-sx2Z~>J4-SS|P<6QC7u0Uw0ab%9-Gjy* z9}=MsRNgUpLfnSWJkWTtAQ9?9LFYUXp$=qkD-r5I_D&%}9mw9zM5qJJ2XJ_CFl2i{ z{ERD{LHh?gy*LVwJ^4F_b>wkL$WUigPI>i9j^EVmAeK|HTc4LCKCh0O+OBX zqCkjwxa>7xWMJ44$ieU;2%-*GIt7il|ADH(XD?`cNHLg$AvGAkd7%AEl~6VK%mc;S zwqOp1t5CJL!UeR>@IF)xKJ!55zx*ab9Vp$)hd}&*&pgn$vI7z7K;wydAsh@#p=xo3 z^IL8PhMgfC49gNAcH83^j|CmSD3i>=ke3Wmhs!*WKbn#`7#2g-;R-)cx>}pe!4Q@L zF%MV0HI;#ZVNwbQgL*1N9jm z9fd8QgVw=2WpXeiLDk{17nELdGC3F)6QOQxCI`b40_s5azRTob;LC!z50|~5aFNL3 zU@#+~4iqj9SsV;!q3Upjb2kG6!`CbhhK6j2y|~nY(#wi$4hGvC{OUmKxKndD82%Gb z2O76f&*fn7%7vJR%fFy~6Ct@A43c>eb-3K81zHE6$H9;dRfo$wM+OFl(mW1^Z3NVT z#<>pUaWF80Hk9I7UmVTAz#yK_!B9j%9q8DXwtNnT8^sWRfXY{F=?`>{msJ@D!|F1K zI$Y%js6Dj5jDx|k9KSkHJ3F+TgW(JTb)fdpgK`drtrhsq1GR@vR&X$UgsQ_8&YnLF%llI2ahK39B=%=3w|qK;2qK1_tdK4u%`G z5c5Fo6m0PUQkPf9!64m0Slz}34u-fU!s@;>aWE`wA*{~4m4o3)8)0=-?Hmm1o%q#F zWMp92+sVP;*9}pJYrYnwZc{f0gL5xob<2A>7%ckntGfZ3x9{g*D476Jhb#Yr#u?^J z;9&SQ5x+W6`cRt0!Js|~q7GNOa%W~>Fr37}P%#yv4%fH<=p4CUQ#lv{ra{! zXuM!I2g9~K5c6=QtJ$FQrS@lK> z;8{&F#I_NQHL*_6OMB*Y&#B7hs(d9 z{PyQK2Sffzh&o*TP|!Mx-jf^*l4l_5K=TdQ(p3s01B3WE4uTdzUXfySw@g)?Zr+wnR?4L*BcgZ6!1=U{NY z0a1&~-dfQ9nHwAoFK*&j2dXaxZgDWYxCK#%D?UK$+do0o;B%ip69a?pZ4QQ-yAbnm zrDsrk`~g%AKJ!4PFyG@~;Jyc9;a;Zz8fOr_$H5Q@Rfj8`g7RzbJr0He` z4hEa25Hmsk$Cmy;^HF(EIT&U=gQ&xmt~x>I(LUo~@P3Y8od;;Y_;U`1-B5M7!Wrb> zqt7`Q9urUpTKE62l@ zpz|!%-b2j7=e}$v28OpE2&+qEW?+c;%)zkp8$>NGe>`DiU^x4YgJI(jh&o*L3@Bfo z|G~i^{1d;rdyEVWhCewN_=bA^tZ9!)9(!O^$p2zAqyKgFO!?Lk}-R z9j^2TI@fd>R1LoH`@q1!;Kj$uV8stH4_CSZ)ywmsYVer{Qn!>yb!&)Jx0wiaAb0N~ zQr!_E)tw_k9mu~oh*Wob|Qk@Es>a>YeXGEkrOCr@d5UI|cNOgWhstYAjT?~=xl8IE8MWnhyBGiG(g9;+m zH4&i>lumnyR5zJOb+d?6w~$D6D~MFLo=A1uh*YP(1KXHBF!CnD8( z5}^*%4+|hdT@L8pDnU+$Q&6?I+JB&a=oLXuhVM{yxZ1m*^|stXgwqS?el=Ah)Pd}6 z72;%A0ac63UeJ2hJy13H+7Y06oaaKE3=zT*H{$A7faZ0(ggF_EMIh>Mh08ex1_oOZ zP6lpKh&oU{z}6lIjlTqnax%<;s>9_E&^c5aL^&Dk#USS4QU_Xp6)eWd@ExiSSHA+Z z&Q(;Llfh9OVjeDap!5`*F&ol7jdb)K12h{y^(I%AjiS4kRg24gpn0VNX-!5FgZ>JBYB9KxY8A9d^b;?lfha6q7GO20y-}@Qh}49TM?oTmwBLZuH#TO`1}DH zZ+c3EI*@y~lpuEEGY_<0h?fX;AouwYp$?RfvWQRz+9$l82zAvA3=C4r#Q9??5$Zto z(G?=pf%1{O3MYe(3dC+)=^i9&233PEoFf?+80=Lz8D>D$;;P@mm>3w=K-J(g52TJs z9b`L974~yELFqI?lQ?y%T7=bs#=SpjaWY8gL)3!O8Mbm4G(Tvj&&iN$08xi4{6Om& z$_+RfRE!|%aD~e+(7J0QP6i`mh&oVth|ONmd8N~!YG5IQO&!RTJ4C2k%E-X*)R>c@ z%M_%8fdQAjp!j$PRfEr7P<*&pK+M9Y4pjdNT5>YTTSC<03O~?3W=%^@h6z>>b-2bw zLF>aFK-CbkSKJz67Cv>Lb+4+{oD5Y^wYcIfh?Rk%&zh6rfdj-$TGdB zyK(tL9pn!ePKHcZh&o*Ucm>+e2~~s7A0Ty$-5_S+Q&-2xz|iZ?$-wOiQH#r7Q27__ z$;prbRfo$TpzK}b$;q%N2x1WR1Ga&Zj+K&U;Uly4OQG?IkTF^b@nVbxN zp=xo(FK9gpdln~yd=|t^T0Wd)PdIdT4i%G zL_^i#vbP3wj#oA(!^0eid7$2Bsp2y}0HPLE)+eRfEqw8%72OpCV3%j$(*;xW*Yk=VFfg30 z;AB`<2~mfu90HwlCs)PEAObqrmw^Gi8xos8K89w)dRN!7GsKLU(VA;pXkT?mV4%hj0pnJ~FPvT^_JsqMBln=1k`v!D=@k~yJ zzq29gaHWH*j0_C*b2u47=0Viq@-OHfqPBUQ3~ciu>Ol1vHhX`A!f!q&gZu)BIuMCX z9q1m#$_1PZa~FYB;NB;^kePwu{31?N z!pU%E8%PE2bxWZ0fv;`jWYF3PQHRTYpm<*dRfDg5Uc$n_uzn{eL-0O`d7$%eu(_|9 ziGgAD0Zsq+E{Bb-3*H01a5(z>f=ULA<8SOTv!hwn5e5a$hea1H*w2oD9C7 zAa>)rkIIyhfkE^$Cxh1)h&m97%^!Ce85ov);bdt122#Pm0J^6Fn>tWAS^b@p;q!Nh zI$Z7pmG6I{YVd^%==>|@ADj%Qe?ZIw>BD9(XdesvPfmuUpAdDR=*FgQ9RmZyil3Ye zVZR{iKqNMGpmV%7{NiNz2UUm5US&oG2CLtk46%PeCgEN`1ajZYKb#CMekaxqvlf?7l?h@VDF0!z7u2p+XXawC zVTP!~m99YQ+?cr-qKQzK%FM-3MTEK*W-f-AM5tTL%*C*SfI3h;cnGQnU${I5?RQ~; z_!XbJ0?<7JY=qT;#=`_S2&>CwU|`_lTspM@1Xr^ zTwDw}+z@rR`YE7xT?024Ljn(eb)a&rgolfvgMd1ICI*H%JX{Q``5@-ua^HCd28NS- zTnsb#@v8&n-);O{41xj>b-4OHp!{Jez{L;-RfjA5KY(r3l1*xbp8i1_p-9B3ujyMe(a+U}IpA7UN=&mVu}P%`aoim+P1r7%FAC z7}OOZ>TvY~K>fGPid+mr$`Ez9$|2Cb84=1{4EI&=s{`$4S5@U=c%ceWhpT*cU}a!X zQR8Ar(T1qQ)lU4u!oX0Z!^Mzg2vG-`cf#gh(7aHS5f{TUBZxX&@e4YCV}lVFgOD*q z9j^2T5;uUV!I#fL>Wqm{2Re7bj0kn0^~Lc-r~{ezo(OfIeX|lKTnw8`ApXS_E}(S3 z2dW02e?j-w-!$Q3$TNeOhpXHG#b=Wl7sFhrI(+snhpNG6FDRUk5TOp_kK;tB1C7%> zG2>!jGKcsBm-|5XUh|l9F@#z`)Zt3cO`vn0EVvkit?;Yc&cwjrZ^gyHW(!e=D__nB z?YFn(VlcFWsKb>nLFYu5+i@`zJ3`cf#+9(;5754CPA4vgX-*JzxXQ^-3=9nWoVXY^ zI78IovKO>(_8U|UzIe-EU|`te!o{HC3Na6ty`Xb7d|bI0)IyVn_+VZ*MRo14CW_7sJ6oh&o*D&a;dR z44(tJ7!CwO)PcsgvBd{y{Axo87sKvQh&m97O&zHJCLhMdpdSWOfxG<-QfC>)#o$SV zy5KM_hGZht<%V%F)DoetJ&cQCIuYs?hH)`$BtqTpFfNAEM5wzO#>McI2zBqnxETHu zp^h`0i$O9Re>ht)GB7BIb1}q5K-A$XH$d&q)(9?!nNW4O@;RsxXJ^0Mh1oz(OeA2V<75qxeui7eGC@^UmRg|W^r5$q49*()y8u% z%t^qn4itVz61W)tCE`~H3O}_ZE(WV4h&o*12f9bcFNup`QYu6pDBoktN1%JFex-3S z@Ml2O;R+X!I`a%J2A@pA>Pj-X7&@~EtJ|2x#c(W}u)25ITnx;)gw<*1axpmM;a6wR z%)pS5$HlO`5TXuWxbPQqF{qS4)Zq&kpAs&H#8Uj~K=IpI%EfTF48J;1{Jt*ZV)zeL zhbw+T<_VW`G5A&xHm|gTi=h*$4wreL`g|@(O(lNwK=b<=m0S$-pz3hd`yh35;z@XO1#n9XcQ3q;= zVp9hi7hTxM#o*cmQHQIZ2ASvI#Ko`%st%WVHK6^;ONh6jTkq`qvY* z&zcBzp#9j(nz22qDAenIOt7q@XS9EPgH6(68;iq5uiF+{XO%){jmka=b8 zTny8o>hPJjpq-0By92*@pm}G{4lafgs5)Hcf!1f&b#O8GbmBKJoq>TNtCNf2A^~-v z@zeL6Tnx5d_{{^I0~FoG#c-E^IxhwWhTmOW46fby%>(&2xtoij52_BAe?j{+XLNHh z`1Ig6FO7+TA*+Xr;X)sNb)fT9-}P}ZeC~&+!xf*T!iE`}{ob-3!83rq|QcNTLotXT#!3HN#R#%v4>D$BVT!q-C7f%^5>>^;W9z>vC* zi$QxcL>*|I0XB6Z%nS_mTeui5Z-=PE)ous5`|u8k8hqhr&cVQNd~)L=C=h0kwAxUvn`Syn(32HBSd>2XBI^ z!Dk+*e%wcdx;W4{<{K^s!?zH7ah0c_{N)c-gU?>jct#Ns>OlKl8sBm;XuZR4?;g;- zZtu7lSl&a_fzlbacmv(%r2|!i&wZ9c3=AFbxfl*If>r=9Fo5b4Z01ejU|`T^=Vmy} z4^ao2C&H#~1{(u|z7RLVaS4bzP`Qas9q7KmQYmf*F&T(DT=5IKdUb{jH^U8Oh&s@? z7dG?GvokQTsB$x0)`6%4)pyv`u`)3*29+S$XkL`Fff4H z3)s|w*1_+zf~dh4&Y*M8?^$s(q}f2!;#&Xmf{B4)n+-R^JbQ>bT# zxIon5YHzG(U|_i5!p%_cieDY*o{QbC+zfhd5OuiH6=*+Gm>V}k6I30pa%cl114EA+ zH-n7_#5~YB_t?VEm5G7jmj^e4y*ET1h{UE2bU)`>Z*GQPP<6Q6cbb8LLCJ@k;jItI zB;4y?beI?zjD5Ko;{74&K0hrP<6Qc0lH6paR@hqN;t$kT=fiS+*Txln_*fcL>;J9z~;VUP&h|&GvvlV z)Zt3cp!PF+95;h)97G*1e}K-7ijL!E_!&^avg6Sx^P5+UkvnRkVOfx#`2 zn?WiGq7GMjJ^c@k|57w7) zGX$4I?8W5|(ER?)a&CtF3Wz#TI>VNKLFz77a5Jo|#IFu?jw^2!H$zJmL>(^of%+9Q zs<;`>5Kspy#~wh{;0qVfJnY9RZie7$h`qS-5on)ge>FG5DX2PJ=^1pd;EQT*2Fn`! z=7GxX(i(1tc~Et@%ma<{@2TNtI0IFOD;9_z(7mc3Yq=T3>mc^xG7oevpea-hzIX%mQ!?tf8BRde z;xZ4^9(q>C%@A3S-(FBYFNdnZXD_IJTU5`@P}Tr34_7(`wL`j~YVeuo$H>6238bzG zVjiyi23m*g*2K-w+YC_$BC+K+khkJGG8(O&;+S@=XaJR2= zLFaC@aWhzT;8zDaKf9rWn?bY_q7GMlfWpPNlbazLst#9r0oAvuo!kuN1k{1rVU3;K z3`e2raK#57BLl}jo=7C6T{s8TB z_%?}~Vcleq3I+yHxsFX8==_a{DclT4ra;tz`s>)#f%^MVQ@I&xr$W@>N`Hx<`?scY zGrWMR!{uMlIcf#Kz1ZU&}h5c6=QKTth51FD9QKlTx!4%99_zKokeVL8NJTf3o%)n5(mzzQ6AVeLmd=$XMz_95cHv`iVh&o*93Y4A|j&L(HK-J;O=b&*$ zt)tuwUPmG3;qouYytt#>4Evz!aG3|{2V@-MX4rWQVjeF4g65eHL)GAm4^Vr&{5Utm zrsMd{1EqsK$GI6k6Ho_gzx+MU&ERqZzj>g0e*BHtg8DPIM5qIun^Hi8I?(wg z>rZhrXq|@GjVqmk+JAPZxfxo{K-A$1KhVC^MQ6Afs?S2y;p+c_#y7T~{U4y6tjf-GYXTZk5 zaOxU2!>>E|)q(6ibeEgK{vJdfE_?M^7#IrgaWgD<1W^YnH?i5v!@|H2_n4a@>p3?# z8Q@X}lGS*@&0zcj#KOIW8YqXKw%l1H-IW+zhXvYC-PB7S5n? zACA}D3^uPpCNnVLYHxtbp_JF$3=;^b1Fieo@tT|A>>G%Ap!x}$y`XV)*|*#b!Jv!L zKz%+O;TOorz!3YEn_=BMhK>f?q z58MoWP<6P%1=RoD_JNz>$VZ5IxaTub65VS7x6F0+?&k%K>{yMhs z1GU>{e1WLJmkvPfnhQjza|hik_=TH6;XA}kT=s(6S69AsGdzN-!{rZ7CI*H#-?574!hk=hB zq83+tI5ILYNU-xTL~=mX;mU8IcF9Vp8hrMG>XipXr~|dzKXdRfcyL1O#g(ptSr`~% zIC&V>^Fq|&s;3p07#P0t@-T=BK-A%CA8cS}U28+wD0bh3=c!7EJPhH|AO{wWy54Q3TbbsCsMIMG;C5SpuJ%LNzekC4;Da!cOf#z$EDf2K) zRDr0&mHspt85pjE)Tlw!;c_2n{5e34hv6wy9j;<(C zx}j?DrF&2~AJ*hyP}73=16RB$GBPl@Xz?(lYU5Yu#K6GNtj)u46siuF`=l8e7|v_+ zFuc-%n1?HVe@QbisOj=BD0o8Dfy~F2KS09bK;>Y{lV7_LX~Fc_sm z)ZtoB2-?0JlE%Z3Q~*&2TJMR?ynN7lvqBz*?S&9^xZ+Khk%8fUArAvjF@ANR^}8m; zJPb`xb-3~ysNR|eRf8{nLE|VZiBz|X2z8+JaheEqpndP>ig_4LltBCoYJXr0KT!Kc zsT86HpFcqQGl)=^&B(w|Sjxk2rwn2yNFO$PLG2B*avlbka)>%y=@isX^eg9KXs&>$ z!&RQ9g7&jl@G$6ALe$}kH;_7~N*;#g1k{1%k9Jq`Fj!VW%){kAP(RAKiie>Rst%XE zAa%V}JPd~ksGH2dz;L~ahasyPzrCPwq!OqaeDMYvuiH(8Iz1K!2A&!o2CD{$y`c1s zE#5%&6$5JtIOL zXg)c-nXtVeb$Tttsbg&=tPWJJ-62w)cN=l$?IJ=Q=sX|IcEaX?)XgMP9cKq&^FZTQ zl^r|`-=J!7rI&-C3b>Pp;oxM5nV@kSZ0QuF?(GyFhIRAts{_rG+*-iH5U~iN4phEk zGY_P$Y!MH`m&JtD2`%Aan6MPTItfMwhILDM7>+H2sKZtNg48iB=V6dqL0Dbb3Lb{M zmH5?_voSENTFJwpxB;RLbRPpY|AN%1ZscK@vz@TI1v_{c#P{P@2PzMw5AZN39Du09 z6~7>LS_gO-^a-c~-E(URRfDfQ0L}ku9E8|ONZl+V)$tr6Y#wNSx{gS7N{5Lv?-&v4 zK>0H32x0R;>TVIK&g&>)^FZrGcOB(n*l-M@7FYa&)g9wu*l--bIxj{BhF!;b7+6k1 z)ZxmP_dx5ZL26FnS2vS^fkEyR55sn-I$Z5Ukh=S)co-^9L(Idau91O(VZmu02E{Xk z)p?%bVK_xVT`L0v!>2Pm4CQAb=7I77wtP8_fq`M=Ssn)aa}ag7!UeQ`G8U=^U%Cg) zYjmIEVcMTmL0+E<|d;A^NFeEtQ++wY4!3^kV^=HZLqC6{;@R4?OK2da*IFY_>jL)GE( zFX;U7#LGMkC$2!u!{rZwCz<@bVF1b)O&cF!(*euWlwI z14G0U9)^!kA?k3YdyqQGXFLp>o)cDg>p2fY@k{*bKDQRJPdAcAnwBzE+BPzZ+IA2*ldmaYi zj}UeE$^-R}JPduG2&-H4iHCvn3x0Js85tO4zwj`8`wCHq%fBFXa^H9uzJDjIPW}fE z!;hc%)q&P|DExw`!56P`~2hFCGTJ-w?ZTr9UAi28M{=JPcWXA?k3I z2OxDD{_-#!`cGIL2LmsI6ccDf6Zbk~kZb}IFGDU=9WM8Q_WPDY)!++1&^m&dM5qI~ zek~E|K;t<(nRppgSU@htJwFXn=flFwu$`5#y8EoW3|{Q`)q&3OFJk9q@Zx}|!xb(d zbs-$Q3_6^I)tPbfGVpQXS7*q;z#sutgD;#x>O6^5S4D(6(7K1!M5?<=q`Ln^r~{SH zn%uk$)41`63+Np61>C$0PkA8fK2Hn%}L4udzk|e~xxZ)SI{^|o%4L<*Z`i%}!5VP>9JITPn5I}^wjSLJ7 zlG23D1MTZLCC$rVCIeB6D_?@*!%K#jAqA=qSGa)6gFL7jeEtBP)3IBIm*EdoEw1)o zIOyI2Szd-}S%~{^g>wo61H(*NUWUH})PcsEq~&-Sy5;bj2Ra9Gr5rEA0(t!EKODrI2@c9>%&kqry4m3Y_$%2=`+!A6Y zuKEphzDThpFT+NtI#9S{b6*7LJS|IJ26Zd^=7HAXC0p?_Oopn%Wu68D1H(ouUWS`c zb-2LhJ>859Vp1C^6nP&N3{3#k4mCqf-4 zT#gf=4z%uu$qwQNeD;FEMZ}JmK@zGKmw!R)s+6H>@Rfxmp!4BY`SCKW_lKy(Rlb1s|GxI;WjG!HQ3oQig)`{}OzLLCPK0|RFWFN0tR#C^E( zIjB7@4ON5BUeLY-TO!nf<_$85PzUOF&ko^bI0jXV%YAd07#J=<)!=iVE&~HYPB<^a z%y9hv1+_00L)G9j4|HzXwQybrqX>w3xXKsM_)tIuFGC|#9WHLDk{2cTE&8 z19vpUJY4EP{c};M8hqglN`FB_r~}P&mJp#1WbYy()Pc&4`_a4%_AwBD;0hPeI7n;^ zFT(`_>OkrK9aIfI{~l#vU{HwVWq2A3u@_glq0hj;@Dr*ApLuDF3=A4^ybOKu5c5F! z9$WcW$jHEOFP@jdKM|r1)UU^-?sy_EgGDlab)a=n8Okjk7N+wu^bk-7 zDzB!c^D-PHpbpeuJq=ZZFWw$9Ffja2=Vf@10r3Z}@B^(cQOo3IIF$)ehbvqxSQ!{T zW%4p;+{@=>SX2U0CkjfB0s#qnC8-r9D!HI*62Q)mU}az^DdlB2 zQ-xn$2P*@^ylP$swMK|K(D)NJdqMNHJWad|MokcPxZ(|zk8GNF8S3LV z19mVDe}ML(Eh9o5sNC4o#LMs#suq{~K1Km&8)XmGVrUzmkuKo<@{Fi$@ybR}hA?k3&8z{WKLDk^17c{;h*atBS zpSri8^UV8r8T9%gYH_&_H1C(t&&yyj0iq69{QhHQVDOp1%iuK)q7Kwf#umSzbM045 z=VgeS0a1s`JkWS{(F|UOR;W5$@e5j?-9Lku;Ti#Tpz|3XfYi){*o(_NP=4c@$;;qO zKpm+4=R1>^VGUFrE`NZ+^#D{2zWA6W&A@PUCND$$R*3sR?M7_j2Rh%8c@Hnc+&vI= zxak2db%Wq40O9q2w>=DoZO>iZ$)fyT42*$bLC?cUGJuobEfm%a6j3=Ajt^D=lG zgqQ~+v6;7!iGktNL0*RJBOn#H?}G&0V{-HeF9Y*2h&s@&Q*7pe_AwVi)!++f(7mFw zh)@UGPqLW^b)a#nBgc3d{zKK`N>`wHd(Pv$3?(NZ{s5J8*!%%HcX`qYUIy8d5Oui1 z8FX)z;YnVG?@)EP$_-HcCVvWI7QS!+)t6pGs9V9vzz}zemx1jJ#7tcNU}9!qP&vcP zpmrX=y1fhx3@+z+85l1>)Zy|k=)Pmo3%m^X38(|z7ybDHFGKM~hF)}bL zy~xXue;J|R4p#^xR@9i?%wBR zc>WM#FRt>cpMinF`w=h0tw#`bxcs}1n}OloBVL9z?;z@M?UU5__`(IWKgI12FT>V95Vg3%1(c8W|KVk* z`3q5pE1gbZW?-20mzP105tLGJ@5=|>EA7L`$B+$Ghb#O*^Ema4d<;*})m5YR0` z)es85d8~X4tDtJ}h2IHQK87dg>ae(viH(nej}5e1+t`3WNX>5E9Md<3# z{0lN~0vjL08mKya`F9%|AHyN2I$Zt*h2I0H8baashK-Nm2UIOC^FaASj2#jtgv`@p z=VP#fs>Nj<$iETnd<-Q}b@=>S$Ii#l1yzU3JdpcVK-Ca(-vM?$hBHvLxXc6P-&asI zgv|TJ&d0#U0SOyi=7H)D6{s3Q=IL*o3YQOM1S*!N+h1T^(9_2F1rO4n77UPPA~rVx9pfAA=3LIyCb@@e#$z$B>1t z4vTpmoO}#Z(A8lvZxbgU!y$BaSj>CC$;a>pT^*Wvp!ntDg7}qC{0edLF~~vH;))MY z{RcHY6zLvz|F_d167O5Jdl4^LDdj4Zyz@w z!zrj*eCEC2=41GSt`1B1N%8P8sPPaEKNlW8h6r?ZSj;Qq;bUk*SBDnAp!~LghmT+=W9?kg#7!6pO4`kR4uOZ0OUR% z0Z5n-GEYW;k3j>f7MFRT_;3^8V~9dmhsD1Y0(=ZD=<3k?3o>t!03X9PbahzFyCT5H z@Bm#Mnt34q{uAJ1;1h)S8SW1(=IIFXF<79hLo*K)A7M~6gu*XLkdGk`supf9nt34q z_6YJZEI?O>#eMq(`4~>2tHWa63qd}HKj`YPm?tH~$Dk&J7A|P!fx^!Ps)mq%LxlJk z5};~v`4?1Q)(P=3OhZ?P#eG|Z_!thLt3z`iDE#gT@iBZtSBJ$s0bxD{8DaeX1(|0h z%*Wt|t`3WNIl_Dl73k{F+y@H3X~KLA%h1(fG4F^lAHxN7b!g^+!tb3h9|M~R#2@(D z;{qal3^Gu4xZ(rUKe7_xV+cc6hsAv*B76)D=<3ki2lDSc5k7`>=<2YTcSeMd;Rd=o zH1k01-EUAegwo$X5k3YkQAoJp@-Ha=YCzQxGS5VmkHG<|7MFP-|HeVp5Hc@Ml#ihb zsuq`dp!l63%Ezz)T^*M2J15G=a0^`>TKIw5FF!>27$n5d!Uct{wyT;7+j!gaiu?yc}Y+;gv`s6;A5zQs>NsC45%7H<}H!nW7q&yi_1Jv ze4LZuV|a$H4omp4Nb)fVNa7DaP$;aS?t`3WN36gvaIq2%p;sX@FU6OnZv(VLH zF>i+?AHxxJb!g^+{QF3fkKq@(IxOZ%NbxbKNa6P{$bC*yd<;S8>adtsAjQW}gRTzE zeIWB@LDdk7-&In44BMb;am6pFJh&po$M6AN9TxZTN%JvCNfY+3g)|?72f8{e=A}vV zF%+Sz!{XlwP&I`7yFi+cVGUF*KL4JQ=3{tadu%PL_{h7rHt$^FaCIhAbb$3v_i@ z%wv<|V-S)<^9P!Fpz_KkvW>CZ!+k0AuA7FT)!nO6i=L&&^3c|L|N zs9JpHt$?Z_WZo8eK86EOwYba!rRRI{dat`3WNXB7AtZlJ3}GY{n7Zwh=2LW*eqz+#?(A|Hbdx;ixT zK98@j7_ae&kK#7kb0bLyy|JEt-F?6A;!(!eFs2W24-J!(Ca0IFrpMM`I@iF{ESBJ%YGRk}m z8p>$lg2jDq%6trA=<2YTSE9_v(15NE%{)+fFb}GRkbhSx^D%6Ls>S7BQ2lmAnUCQO zR2{zh?VBw9wU|nFlJbJXHA@V$juLF|SIMkD(1+9h!L{_bpN7 zW7vYO4vTr0RQVY0p{v7U-XB#y1|BuS{?$_BV=zNkhsC@Qs2W1)FGY=yp#Z8DSNa3R zZ=V_;!zOffSp0iIjgR3Dx;iZG`=!Rmz@<*uzZ&X%3?}I6u$UJFRYS$FKoi9TxwdQ|Dv2g{}^Z`+lhNF>q)Q_OF@-AA=FPIyCb@%FQ|NJ)8J#62UUl!d|9Ev$FK#e4xf3KH24@^p{v6ZejJ*73?iCn;ezI0(0G%P zCLco(x;iZ86=?D?)S#=md<<96)uEXO3cpX9d<YBK87B2by&<>rNzgv3tb%+^KNMIF+4$6hs8W5 zZHQk9r9UBUJ_b3cT3qP`R3F)B^D#uAtHa{HGHpJFCUkXJ+_ylRk6{D4IxOa$)8=Ei zg{}^Zc|V|P2>F*ohmS!-2NE{;{A;Ac$KZ#q4vYJ8bodx5(A8ma-!vUQhGppLu$Xs5 zhmYX`x;iZ8y@RSDxKJT^$zp zO)}zRScI+)i+Kl(_!!Qht3xvn)c$(~RYSNj<$bB79HH6HYV9dub2dWmIdAp!$2$^@tn2+HcR4qR9 z-ayq5GVcpWoeAOikb|lrWS*J{AA=E8Ek5@JK-Ca3FT#Y6AqA=ymwBM}c#{bq!y>3U zeEo$rCVUJ#pz3g$2TCv3plS%Y?~w@~!#k*2T;_q=<2d?#sx$lZ8AHy4Tby&>fGUH!8qaYt=VORKSBJ&CGIKtLCUkXZ?gNG20&_lw4N!IX((^8JK89mZb-23>i?hxZ(p8er-@Sgv^^~ z$;Yq?surJlCoK6G9zfOMOE0f1`53-I)!{Qw#EOqW$BJgrTcL3qMf$E3x8Z z=s;J8B|er}@iA;dSBJ&C3s!s#Ptet2F^|cbkAcq`%^zsyf%2PXWX;F04_zG=^X^#lF}y)nhs8WD8$Jdx8~pwS#fJ%04Wabs zVZ+A|0#%DEy>KuvFcd-65HhdIhL52QsurJlOQ329nYYGNjvm<5c2ON2R?>*P__8{yT^f#;R3okEbe>fz{l_lT^*YHK=C2r z$j6}Jh!!qb%yV<(V+cZ5hh`oq{S`pf5b|$}BOk*As9Ie91-Wk>R1G2X_Brw~oPw&w zWgf_VFC6(8{-CSF;$JBzJ_a=>v~WT5FUWl^PJ9d@=<2YTSLDRUP=~G#%{d4e097{0lla zY93S#A^)Cp=3}@8Rg25Np#J3#XFdiA7c_rhai5tBAA=LRIyCoz;y1yCkD&lv9TxNY zT=*Dfp{qkP4^$rPfT|(n-vcgu3}>KfarqaNo?p4}G5kYUhsAv|u6zs{u4v(c=01>r z-CX$?!qC-WF|WjxkD&ow9h!L{|IUM|A>`j>u6zueplb2?_kt@Q!xMCMSlq|t#>c?t zM%cePZhQCi&d2ZpT^$zl_&oR+q&)EZ7Ze{B9()WwP<8m?Bg})3AqlDuUwkxp@G(q5SBJ%Y zn>_d!_Mxjo^Dn5pzXMX^iRKS1=E-^TF=(NyLo*K)A0D233=!z+u$Wio$;Z%yt`3WN z3q1K4HlVA+V%|AVK89Q9>d?#s#m5g%J_a5yh=1|LhnN>1gA!C7uJ{0jpMw`4LkhY& zEbeRa;$!GTSBK_4Q24F!;$zr@t`3WNx4ifmo}sHlGY>TW!r~3_E1~jFz?+Xj2C5d9 ze?j$|l{X(l6uLSr?yKadu%!k3R>3%WWi=3VmTV|a$H4vTp#etZl9erWzcGY^#C^!)f3?9kO=F)zlCk0AqH z9h!Nd_-KQwArv2detZnGpladajK#biP&I_iJK)F1a0aRtmwBN0c;&~(@DE)b7XQll z^D$`n;}1WOd2arE3{mLnu$Wij&&SY$t`5z=AoCXa^D%5gSBJ&CEB<^8575=2nFosB zfBt+7d;w_wz+#?G03U+|x;ixTK=B&}RYNE~;sW>>vY={l#Rtf|4yYPJ=1mCTW0(U~ zi_1Jv`0WbdV>pMd4vT-^1n@EZKv#$6UyylXfqV>FfrP`)Ban|F1YI4Pc_9B5LDdlQ zZ&e^4LmN~rF8_keTLM)>$hac_#M-U%_NDx}Mp!pYM zo>34VgB!X!Eas&I@i7#jt3xvnOL?GEjB6%mca43aW;X`<#OL82q4WahV4y4|0O}7;2#E z@VT!on2%u+R2@F^HU#rA9Du6BXWpq`K89;hb-2s}h2NK8J_e2u!r>hQTQEQF6C391gCc?}_a3=^R0@R>I&gpXkvR2@F^j)d?rT!E^?XWqRK zK89CNb@e0X zRfo^KoG?Cy8mKya=Cy_KF-(H0!(|>Qe{6uNAygh53FBk909A|2JW%=a4yuNbdEdhL z7?{HGhabp11*jTA=4piUF_=Kr;&WdRR1G2XqQdzY(x7Vbnb!hUL&&_Ia6X0^P_?+s z1I5R-a6X1p=<2Z4Z!g067`~vZL#y9F=~*a(k3lH{Eu68K=Mcfi;DfFX%{)+iWI@#s z@^4WDA445fEiV6p%$oyML&&@(5qu0AplWfM2MWJ)5qu1f(A8n_FGD0B15YGcxS;tL zWS&+eAA=pbIxObJMDj6YpsPbO4-_A5k$eo((A8lvZ%ZT}!vS=4Sj@W@$;a>xT^$zl zc%t|iB%<*97vw&(C_V-^s5*S*OF$GKLkv_MuJ{0%R~5y_&;?b8&%7y7d<+Yq>TsC{ zN-z7MY6zv5V^Mqzm!N8KnFkt=_z=a%z!MD#7kus$i{@icf~v!19w@yyMDsC(psT|Y zA4So840Y)0(BcDUB9@Oq1*#SvE@$W!!C4n zXzl}xv=VM4gSBK_b zPaJ8fserrT^$zl zQWE$W3eeS|`4==k-Un4fD1K)p@G&fds>S7BPT&5_!u0})nRd8TnZmU7P>k#_kqmo zNa16cgRTyXdAm~h7>=QwXfMbUp?Rs9JpGvs*eJLln9?EbgmF=VNF=SBK_4Q2Z`R=VRD}t`3WN7t;9{?x3qf zGY{n7Ur;qr@8Z0FhAD%OfiD9RHn{u?iVvL(J_Z+bby(b&l)=Z4hprCIeIWCCGWZyl zpsT}T-k}UWhI8oZ(98q1H{N9MF>qv}`2&l2YMFctM(FC$%mamA08|a3@QcXgV@QFj z#TR}}nS2bh(A8ma-;PW^h9l_e(A)n~z}! zx;nJ@0GW3!n~&iYx;iZ8apdqZh~z-R2ABIl;b)Y?$KZsn4vTpSIeZK`=<3ki2MWI~ zs2W1yHz|jYVIEX1F86`V+XGcY$h;#td<++$YVn!(4yuNbdEavQ7?^VLhabp&3Q#qK z%+tu_V=#fL#pOOw{08OnF{DA&;cKrIadvClE=p|0bLyy^Va3@G3-NE zhsC@*d3+2n(AA-t2Z|52d_D%Le29PX#fM5hAAOnt7o3U@PEb5G#QA7w!)%=9v`mF*u;B!(v`s0Utvi zx;iZ8^%U?i%s^L%#k_3=d<@5+>hQ(Kg#tc?J5Y6Se_%21R{5X-s64QPsv%S!xE1m-1VPo}G7psg3JUocTF}*D3BN^!d<^T*)nW1PnL<8>JLu}L znD?uYkAbZS5-zyh2lB59R1G2j>J;%YSU}a{G7se6up&N&EOd2P+}BaW$1nw59Txv? zD&k`}gsu*Yc@K*C7~Y_(Lo*NLU#?P%$5a98@hn|JoGuF$AEi!{WZYVm^i{ zbaiO%1LfZt#e57a(A8lv?^rP(!zFZeXy$?Z`vIzkkbi#^^D(fLK*9!>e?jIcLDdj4 zPpgEF!3?SvpLrorHH6HIDdA(tfU3o3UK>;mA@ll5_!wqE)#5S_)PCLpRYS8DDuNP|C+3167C5JgZVZh9GoxSkg;DDIY@(x;nJ<0&?H1 zQa*-F=<2YTccGMz;SRbwH1j~|`By0)16LWuzi@wGF;An6kHG|89TxL~%J>+P(A8lv zuc3^Op$An;@gBesUF7rVB%aC$Dh61QMeCe;MoR6Umst%WVpz?l6IUmCgbahzb<61c% z!y|NcXyFG+FANoY3<4DpKf~?CVxC?FAA=RTIxOZzRPZrmK-J;%Z&3vwLmgBd++H;E zK~qumP$TmwBM@J6FNS@Cd37pZnfa@G<;=s>5fVSS25WRwd!^^Qh!w z2tike=3kKeiYoaS+R)WuiH{|fd<+}V)uEXOir;gUd<^%{)nPI3PbD7%M-`esu$ZS- z#m8WVt`3WNAys?~3Fzw3%mc-59aIgW_-(7=W0(Y0iz_}r=52tgA!Oc;Dn5oIP__8X zdjwTO$h=ood<@^9YH^tdieHgxNEj0`PotWT!33%nmwBM_F9@oJka=;{d<Pz@i098?`H^FZaZO${GI z2vi+D^Wtjw7_y-1aG3`xpF3*!7?z-`!;+p4)$lQ#Lsy5Eo)$uV*Lsy4p9>~92>i8JW zpsT}T-m5x3hHvQV(98qHhe$migGxQbzi@wGG0&-hHSjU)LRW|8K2Z9*(ZI*>1zjB$^Mo4t z7~~r9`xlg6Y#R9(e9+ZlF)yo;kD&}*9h&<<=1pnjV_1T&4vTq*8u=K`p{qkP4^)4= zfvO=CzdsuJ7+9L{`xj)M5>yQ#^Yohd7_6Xbam6pF{)lMeW2k|u!NI=!$G7pqL%$oTa{Ls~5 ziQk-NK86Z(b!hPmN-xuz`52a=tHWa6k!C)I3+U?5%mcL#-Zk?vh_w**uR;qSgAP<3 zGXn!;9u|vvUM+kKMNoD4!mp-5gAu690#TTpfQ z%zM(#$M6BF4xf2^9efOW9fbXB(ZR>y0#%34yrd33h9;;weD3S%;A5BpRfo^KZ5@0J z*P!a~nfIWBkKqke9X|88I{6s1ItlyNq?3=q0jds{d7$z%4yuMw{gKzn$4~`Ti_1Jv zeK`ZFhLCwnI{6qjK-JNj+Bf`lC*^LV=W7$l%-ahV75 zuNhPgA@iKN_!#`4YH^tda$gQq4I%SNy7(9xplWfM2Xfy$s2W1%t?J@q*alUL%REqh zex-|#;SIVvEcGu}Hy?voHzcfanFmULCf$4tF6ip8n3vSe$B>7v4z2zLxv!_2k6{T^ z9lrdwuA7fx7gQZC_vtV&Fx=?oV_@k)^9L69DfRF%=%K4ab04U^;nTy%P=c-wi+PiJ z_!#D)t3xvn6n=Yp_!!QhtHWa6s~$dvZ|Lfd?#sr)Q`d zLg}xhmye+Vsuov#fXtf*RYSf>V&gQ~@6o(WV9A@gkd_!vB(YH^tdN-t?pHH6G7>*Hf+f~v)39;m;%0IG(Nd0YDU z7!E+y;xZ2uzxSYO2$}b+kB{LKR4qR91p0}K4~c$01{J7UT;_qo&k3rAko$uA`55A$ zYH^tdDz9q#`5304tHY9iH}&%|>_bU^UY6$sPYa$O$ahV5lUl&vjA@e3pNj`)vd{V46o4DVKI+m3Lk^W6iC?Mav!LCF`B~1;00BOul*M?g^wWtst%WVVE<0x zW0-`l4vYIXOyOhLgRTynf2Z&GR4qR9n5IF( zn2>o~)A$&~plb1%X9872$UK{Ad<-5?wfM|SgQ_89Ufwi5hAOC9eCEx7sv%_Ff@ypV zYoKcJnRg1RhLCxertvY{gQ~@49w79?x_>28rpAxWi|j8B`4+^W3KMF$6)? z;xZ3Zz7$O7W9UFvhb8|mo6g6u30)mpc>pr+!gM}{7wGD+n8!AQk3nb#VgDM;;A3z= zSBJ&CxEXv5S?KD}+y^QTI-qI@h2Ml3d<=7-YH@`h$h=)pHH6GNG=q=f98@hn^WH$! z5HjxzNZm~Q;RkY`98?V<^VDYYF&IJB;&LCT{s@4oA!J_4Og@GJs9JpH^+DATGH=mL zK8AHrwYba!`S%P|4I%Tc%;aNu09A|6ynj$Ngv?``#m68ti*S4xK-Ca3&teuIg9}tG zF86`rBMGX8ka=aZ_!yd?YVn!30IG(Nd0S@jF&u!Z#bq97KH%OgK8Al#b@ah8D0UyI3 zbaiO{1(_$ckdHxcA;iCMd$E}3vyhJ=0$m-Nd7$_xTgb=IhOQ2ac}o`ZF>F9rhsC^e z3;7uCp{v7U-k*hh3>=Hl{DEd3$iHff_!z9v)nPF&Vi6xh3c5Np^FaP>TExdN30)l) z^ENEvW7vbP4$VB!e9Emwd<;yBA^yOZUU(MsF-Snw!Q&0hJW&2MTg=Dc2UUm9yokko z3@K1`xXc6PkEX?Z42#g!Ve#*Q#e58B(AA;24-|f{7V|OuLRW{yJc%WI3@S??;eyM3 zpz$Urs2W1;7r!NZ3{g{%8%lH_A(A8lv zuV5J;Lk+q*wEO`IzgbWDd7$#eVmTi}1iCsb;a9etkD&=&9a{K-%KHV&`53mKtHWa6rR97K z_t4d$nFosBKg;|LuRJ)lf{)=ER2|$OXy$?9$BBf~v)39;kh_ z0IG(Nc{^6~F&u%a#bq8SJwJk~A!OdC)qD*9plWfM2MRx#HGB*PYtX_OOaAa%!^aSX zt`03eK<=wp!^hBpt`3WN%hvEQY(iIuW**3W7uN7GJV957#XP39d<=YR(fomC9>{$< zP&I_Y&t@$jg9lVCzVJ(1%g0cKt`3X)W~}97Sc0w&i~A0(1XYXAzX9v`7*f#HVR2v6IzEOzbaiO%1I6!}b$kp5(A8lv z@7_8-hF9q7u$afOo{vFhJ;cv&e_%1sYCRu=8@f6)^FZk(Wj!B53A#Eg=1p49$1o3F z9h!Nd@_7$b4WalrvYwCO0#q&BA86)*+NbZ<^D%I3Anad_4SWnH=<3kS1Nk>-10O>Y zx;iZWZP>ua(1WfH%{-8QS3%Vf^6#b%d<^@bYH|4&=-tt$Yj?P_?+s1ErTRs2W1%rETS7D1xfRXWj&;8banR z*viMS2C5d9d7$#;6jTi%^Db@WW4H%Zi_1JveEiwU#~`x}5{CHVLv0%$gAr66F7rU? zIba(fLkhY&Ea|Ul8y`a-x;nJ<2a1n1+xQsvpsT}T-mPtX4A0Qjp_vCtFD%>n7-Y5+ z;ot3i3`S6Oc>TMbk0AwJ9TxXBZRca?Lsy69KCpkc^D*o}SBJ&CTif{W^UQYeF*u>CLo*LlpC|0#W2k|u!?G`8tDSrdZs_W;n3n=oLnyzM?Bru;fU3pi zUr>EH52}Wcd8>BvF>HgX#bq8SJzv?$$M6PS9hUIp+Qr8pwhJv>u=v+x7axNQx;iZ8 zCGFy4$U|3$#k?M<8bbb^vx|>m1yn6A|ANxXv0Z!&x6svLao>+!d<-nR3Hw)RHy?u$ zx;iZ81?=Wyh(T9}=3mhKQPplfhDA_y`0~e^-Fyr?pz3h>7gXL~gQ_7Ee)o3sF}#AR z#bq8S{c-Gpgb5+@ME3A8C_vTXG7nT9*zMtCh=Qua=iih)d<+Fpb-2s}#c$spK88i; z>afJefjxW-XVBH5#Rtf|S9|yv{-LYGVxG)iJ_e1wkg&n!K2Z7MwwI3~3|$=-^Gf#e zF*Km7LvtU17}lYy!(!f&_ z%YL+QL31C-JSC_aLgtz6=VNe!s>S6#Q2t2R&&N;$RfjKswC(3(m;_aa&%6!$`54YX z)!{Sm+I~KUM^JUR%mbxAh69i=A>?0y1AGiJP__8Xvx2H2WS-XnK87%;T3qIV+NUK4 z_!y=@)#3B+yaRj;tDx#|nFmULC!lHwx$nXOK88C`wYba!rI%j^_!#&OLc$QA`y>wX zF{nV*;W7{8J}0OeLhkcA$j1-{Rg23!Q2Hx5$j2}Nst%v~W*y{XSO!&x%REr|a^xT% z!yR;WSjvN62l*J-4ne{NmwBM{r*epo!313$7W0A*@iD}qt3%7bp!&Dw5Ff)5s5*T9 zU3Z9&VHZ>#F86`r;|5d>q4;=kh>zh7R4p#^KNjadu1+sQ;eyLN zP+>I z;W7_Y{yl=KA>_VaC-@lHPT~(gQ2$K@s)mqx1}FI#Y@llKxi9J@A43tkIxP8P!bv`c zIq2%Jq~~2H`54ZjtHWa6o0EJDKhV{onFlKW#7^-ssGUL!7cAzvoZ@2$Kv##wyu4F< z40Y)0u$VXJ6d%J1baiOvfy#qpr}!9dLDk_)FHcVKF?@ik!xbN({Kj{hk3s7+VgGuZ z=3@v!SBK_4Q2Hx6&BxG%t`3WTSDfZ!*n+MO%{)+iTsqCi@CaQU7V{X+@GUU52_Ad{I0mb$FK#e z4xf3KF7PqDLsy3-zwun;W01HA2^U=Ef%1phMLq^MbahzFOS#C$P=Kxutvmpwmp-T( zLg6>-A|Jyts9Id^1C8e#xyZ-x1YI2#_c2}KW8k}l-@hR9bT08R_@Jx9VqVrIK87-M zb!h$trROP7HH7>-=Mo>o3aDCK{sraVW0&|CZlSBg;=Ui3_!w9&Nj?-~v^P&wWWyHH6H|y28g$233p8JW%{jfvO>7 z-jXYP3>%oKSNRx3plWfM2TCtSP&I_i z^Sa8%5C&C?%REr{l|a=HGOy(-AHxKwT3qIV+_&y3AHy+pby)J-ldF6TAJEmI3Xp=Gk21WAH#%hh`oqe$$|82>Cbf8XrRyR4v?IH1k0DW5zW;hBfHw zu(xBKQbe)eu4_zIac_9D#K-CcPZ^(5%h6JcueEzMw z&c`qbT^$zxZn)0Jum@con)^Why>*?B;T5_%Eaq|C;A0TEffg=k=7HkF2&#sVf8B2I zF$6)?;`4984L*hzbahzVx9A2R!#Z?zXzl~~_sk7GhCAr$u$cGj1|I|4O~U?FfvO?o zUyGZ33@%W$`23r6laHYcT^$zpO}WX(umD{h7XR+M$;WUGT^$zl-rVG4_<^nti+N(V zAYnqtziPMm7>uB5arqY%9|2G`gv?90#mA5XRg2HOE~pwp=FPgr$FK~l7MFRT^n3)W zhLCwTZt*cZfvUx49@A|;2C>_aFodha5+5eF`4}9~)nSQ`xZ8XTdFbk}nAdZgk6{M7 zIyCb@{@n&uL&(3UZu2o*gQ|u51B-cIZu2p4+#&2=wL5$aM(FC$%mb&FJA4cY=<2Ze zx9$!fLl?R_Eat6%sv+dx9e4N`jzHDo@-L|VeRPM9;TO6(Ebf!I%g3N{7cE?{xX zA43qjIxOZD+~s4aL05-n9;iN=1yw`Hzl-kjF|32C#pPd+d1s(%2$^@~E+4}Ks9Id+ zf#UZcR1G2X`0nvBNZlhGeil$Qgv|4}$Hx!?Rg2GkMNl<_%xk*G$Iu5=i_g3@P&I_i z+jEbP;RIAIKJ%VI)eti8+dV!8ru&4$PXVfika-68`50`VYVo-*3aW;Xd0F@Q7|Nh( zahV4yucqARV_1Q%4om(xcAt;o61qCH`~gaTA3$mzpoI$-^W+}zF=(Ny!(yHXR1G2j zMm*qSNP()w=ijCWd<@gj)nRepmIr(c2hi1Fao@cMd<^f<)nPG@=OG`1#6z@j!D5~n zR1G2jx;^A$2!g7`=ih>dd<-q<>ae(P(L+9lb?EA_xbMtEK88E!>advi>meTl+atpM zRe`D@b@>S7uN67`)KcVR2u^6F!C#baiO%1C>{k zp71d&LRW{yyaP}87|x)pLo*NL-&asIgyQ$p6F!E2P__8{EAy0(!Qd$*4B_gqxX_HAgsu+FJWzaGfT|(n-zQJ`7(PJN!u^58JiccT zzY;P}=@}n`9#kzp^L(Ic2$`4gjE^A)suq`dpz+f#s2W1%O?$@2un4LamwBM|2M3<< zF+4$6hb2Ckp7SyAJ%@x1F7rU~tMi^D(5Mt3!(qkbhgCY6$sv!gD@` zIZ(B@+z0aSuIGFV_t4d0ao?Zkd<+~f2>Vy<1s{VMx;iZ8g}mTnNI+MI#lLk>HH7@z z^@5LK8dNPl|89A~$8ZB(9TxX}d%?%R^pdcD6<+c&7@(`eVxHeiK87fCb!h$t<=+aZ z8bbc;-4A-D)ahV57&tITw2${$7ijP6y75?x8xlivE zAA=LRIxOLr@QRNi2VEUn_<`Kl^@@*S7P>kt=IwaJ$8ZE)9TxK*LDdlQ@26LM4F8~N zafKhqzcR1+7z|#cg$ow<`Mu_2h(cF~#eEg8`4~FT)nPGj*=s(AP3Y>dn0EoHhLC?B zyyjzg167O5zo7OI*Bd?tr8k8A>+pt;!3SL(7WZYn;bW*mSBJ&FGv4qqEJ0U?#k@mM zHH7?o=?x#lJ*Zk-{spDKKTtJ<%;R~>#~|?*EnKj;&kU-Dka=!z`51zrYVoI9L zJs(2=R4qRL=Dp`*XhK(q#eECj^D(SJSBJ&Fr{421+(K7}#k?Qy`50I};PLVY+Ep&BQ z%=__?kAdYAVgD*Y)e!Qp)+as&GpJg8{tfxW$B==p4vYKRKJhV3LRW{yzZ*XBF&scw zhsC^mpZFMFp{qkP4-~%~pCMsF$iD)g`50uNYVrBk>N6jMAG$g$?#ubi$54T;4$Xa_ z`fb{0K89uJ>adu1XJ?jj(@>zVR{Gp{v8SEuci;FJn7$MCuflge1|4*DSls9JosS_3T^$zxR($7UXhBzpW*#X0ErO~cSEuQ@{8a9-*tl;y#Aod<;Cl3Hw*;Hy?u)x;iZ8 zMf~PtNI_SJ=3h|#+XPiZ$iH2``52}_)#CCmD7|d?&Bt&8T^$zpJ^Rha@CjWVn)^Wc zSKtpHgTf!QaKU1p-5)*%FLZTi=7Id10aZiDzXgBz7;2zuarqZi9?XKOA!OdBKYR@P zplWfM2MWJCP&I_i`|yX4;SW?TF7rU{llsfYVDy)8_yzpsV~9alhZcUI_^A5J$IykY z4oiHj_{+zz1zjDQc_8;)f~q0p-&cS67`{Q(;`6V_KRyPXe}w((^^cDs3|$?X`#}CJ z`Nzl5g02pWe;57ZV_1i-4$VA}f6x5mV|W5phj0JFyMKHPzo6=H`4^O4B>wX;==>+b zzyJ9d!qC;BxesJs$$vhE4s>-`{JZQwAHybeb!g^+_G?`D&&TivT^$zlxES~u#2EO& z$pDvsLE||l4Ezic=<2YTSH{54(1flI&3&NwUBJN4umN2i7W2+A@H5;(SBGXE$bCN; z_!(pv(fonMJS#?i1~+tdXy$?3m%_-;P=c-wi+Ph6`5ES+tHWa69!7qKGwABDnD>g2 zpWz$2IxOaiF!3{}FcJ2z6B9p!AG$g$=H)Q)Gt{7~!{WYKO#BSX(AA-t2TIRJnD`lP zK-J;PZ;zPx8QwwF;YxoX^LUu~85Eca``3<{pTP@V9h&<<<4qaN{0trF>ah5C88bh_ zCUkXZ=7G}71!jJR2k7dsnD>vFpMi@7%^zsyfyyfl7Jdc?s5*S%=f%R$5C&C;EBrwI zEn(qjn1Zeji~BaQ@H6Z~SBK_4kbm#6@H4zYSBJ$sE>?a9F;@Kk1+@=MplZMc5d#B* zgMfj99v1U#Sos+|plWgX7nEMoSos;M(A8lvZw4zr!xD6LSp0j4m7n1fx;iZ8ePHEh z_=Bzv%{-8QrPv^0Ldd^LZ2Sy*P_?-H3ra6OP&I_iOJU<@D1fTPWgaN}`k-nEnYW0I zpJ5$TEiUsw?mNTA&+r6Y9hUH8V&`YzV~2zdF7rU?PluhK!3JF&7W1Om`5DsC)uF`) z$bBtPHH7>-hn=5c1yn6A_kqki#?H@h4_zG=_x)k#XW-x<>|Zqweg-piby&;`;oxUT zKv##wzjYk^4AY?M@RctMIQSXXK-J;$FDSj7;^1evhprBb`~Gn7GjMR?_b=TYALF&%nin<_|39X>jo~ zn4qgeGY^!WgShw^lF-#*F|UD(pP>g`9h!L{_pRdMXSjr}4vTpoxcC|VpsPbO59B^6 zZhi(WZo>Zc;O1utL05;xydrLXh9-1%SlqXOo1b9~x;iZ8o#N(axP`6`i+Mk|`59Pv z(ENeLJS84}1|xKJSj-FH;b(|JSBGXEs642Gsv%UqOyc2ZmN~3c5Nh{%zvpXXry$ zhh`qA{9D7v&u{>$4qy3vijSY+8dM!F|ANf>!pG0R!B5z~YW(~RM(FC$+y{z}0DgXk z40Lr^{M*LQ&oBvH9h!Nd@Y}%8&u{=;9TxNM@$)mhLRW{yJPrYV1_=Se{xuWeXK+GS zhh`qAKb-(oLn!^#2=Fs>K-J=k4^Vr28B`4+^NtDdGhBkI#b@3Js2W1%2?+8t$Oxi^ z3z~mH{b?(x8bap93Gy>!LDk}NAE>?C0aZiDya|H*40E7rahV57&$|Tq8P1`r!;)U! z2=X)hKv#$6Ur>4$6XIvk6G96YEav$L@iRoAtHWYmnGioiAG$g$=B*LpXV`(R4vTr$ zplS$(-#a0GhF?&%xWW&V-z0<~ekEj{hA=;a2~;gE^Fa9{NSL1?4_zG=|Mm#;Gt59& zhvr|9`?d-5GaN%#hsC@n!u$*$(A8lvk57a+|H_H*GiX88;`6VE2tPv#R2{zZIZuS2 zp$e)FmwBM{JVS(^VGX)EEdD(u!q0FGT^$zxei7kkU=fA*8E!8Y^OQvS8T8QAp_vDY zUmsC^h771WeEuyGVw}jGw^>T^*WvAom48)ey=bDPsH#1yHrP{0k~i`^5MeHbK?l3%@;L{0t|c>hPKO zOpKp_OPsKOHN^QDOwiS#`4<#^LE`)jN$BdZ#7BcTKSK|?IyCb@{#_-`&u|W^4xfK- zi1RZ%fvUsjUnU8D1~m!7{&kVyX9z%7hvq(zfAb{x8S2p0Ve#)A34VqZ=<3kS1ErT^ zP&I_o%Pk3hhG$T4VNb)nNK-J5TMK-J+g4-|e}QpAOyoD@HU7E~=h^E{wx z2>CZgik~3^surJlZBR9Y%$p^}&#(-t7MFRT_T&*MeugJdb@;;XofJRAFQ__P=7GXb zLYkjJM;a1_aCKPnhnF-zLm0X`wEO|eZzayx;iZW-6X@$ zun%1wnt7n`yCcKT@CIES7W24d`5DAy(fomC9w_`wWce99(A8lvFHM%8p$J_ant34i zO_1ehSb(k$i+THG`58{3t3xvnhQ%cn;bubkQ{#hg5uXej-Md_T^$zp z<;n3gRH3WG;=UPj{0wW*)nPI3lpH_9HFR}o=7GZRiyS`#i#(b?u$ZSL&(ENTt`5yS zQ26=C^D`u%tHWYmojgB77rHt$^FZ!fA^!^RCJBGdx09hh`oqe=sQUGe{^v z{EIJsl@$0H^q}hSv3XZVD! z4$VAJ_z5WTGbkuR{0sL77W3>B`5C;>)uEXOa$klbKSK?=IxObRQsie?hOQ3HJdpd2 zDDpF0L05;xyibb!4FAy8p_vDApNta3uY~G110{Y28>m`%xS*K_%D+)c{0vo4b@1gJV(=7HR|PKlr45V|@n{(Ydt&+rCa9h&<<;m4)S&mg4?@iW|BEaq7#^E0@h zt3xvns0v}CPCHVbKe|QeufoLb@K`_R>4 zG4GB#Kf?=jb!g^+@-LeP#IJ@1@$hA+(TD~#k@b-{0tmAX#PMm50u~3plS%khn@~UgB4UQuJ{0% z7Xej6$h-s{euf;VT3qIV!mmq*pJ5hM9lrW^i4H%*2ByHmGdzN- z#bq8SJu~P+!kCbGJi7c05>U0c%mca44625Zd3L(|3|>&RxXc5k=L}tbh8lErSklWZ zU4DjT=<3kY3&^}9y8H|`(A8lv@0%_^1Ct&kZ1A~HL64uo1YI2#^MdsF8RF2@p}7x~ zUTXCC8G4}V@THe&di)HFpz3hB4-_8k<7MFP-_f3GRA!ObReSU@|P_?+s z1BKrqs2W1%ozv%MxCK><%REqe`2kf!$UF`Meg+W({P6+GA4X6$gv@g@;AaSes>S6# zkbeuHY6zKEW5CbQ0ac63JWzZrgQ_89-Z}$*hFws#xXc5&?*>#2A@iOX@H2dXs>Nj< zs6E7I$j_i=h!)OR$^#ceeue;aby&)SJVSnlI&^hd%$sA#&#(er9h!Nd_&sLG&u|N> z4qy55#E_ri15_O@|ANfpGva4ZG9v6>2P1w4A9QtS?gNEimJvTg6S_Jq{#{_i&#(qv z9h!Nd{Bg>NpWzm|IxOb>Fyd!mF-G$T7W0&h`5BDR)nPF&z?h#Q23;MRd7$)M1yw^R z{WTf$GxR~#;){ z&!7NRhs!)rdbTs=X9z=AhsD1oru+;I=<2Zecb+Lf!#Z?zSj;CnDH}MpsPbO50sw6plS%kN1PcyLl#sm++H;EK;?4>R1G2XrkL?F zEP$%TWgf`C`^@+muA!^L;@>YIHRfpHg62L@dX_WiXV61ehs8V}bAE;hbaiOvf%03K zIX^=eR2{zbH^rQvVF6SfF8_l1m;2258E&Dg!{WXl=KKsS7Wn-OGEd2ZpTQ1Y9TxLq zEch8R(AA;&7ZiSN7W@p;(A8lvZ;J&#!vS=4Sj@X;!O!pxT^$zlcr5uDBrNg!7vw%O zs2W1)&&`sbAqc7#SA2l-M*&m~A@eFM`59WEYH^tdN-v8n`5CsMtHa{oE0+8W575>@iXK>)#CCmDE;+V@iVMISBJ%Y z$E^4nE}^SKa~~*vKY-L&6ZWs1H9vzEx;ixTK>qcBsv+dx5Nm#h1gKhE{som+b=Le0 z)6msn@$VLEeue|+>d@Q=^6x!seuj7G>adu{W5drNVS^SfSj;nnsv+cGHyeJ2AgEeg z{srd`s2W1%RoL(|v_RG3G7se6MNl<_%-dwc&#(`w7MFP-_uYZ2A!ObY8-9ilP_?+s z1G$gS781sU%#*X_XV8MG#bqAIeI8IXgv<-DNj<$bD5%HH6ITvgK!(233pC zye&{Qgv>i)%g=BDsurJl@1SZ3nfK3@pMlE`64toP1BIUkR1G2X4D9$BY@llKnHL3B zL&&@&JAQ^ds9JpH^+44SGH;3;hCS;zF13!ZtR4qR9Y@ligndjob&kz7ri_g3~ zs2W1%l{xS;G(pwkG7r=~Sm40Vum`FRU;Fu(13$whs5*S+eQ@As;Bkb6AwKiO9QhfP zpz83M=itcC5Cc_*&%888eug5bI$Y*~)|X6hhPIo;l$4n0ab_3eMwII40%v>_{{5Z;%8U`Rfo^KZBG0QhoI{4nfCys z#+k5xxt#eK#GvZ%nP=k6&kzDthtGX+&io8nP<6P>1FgUBaOP)N167C5ylu|>42PiV z@R|3(nV*5hg|L75T=*HJpz83MXW_!n5Cc_*&wXhw{0v1Y6z7FGhFx? zmO$0wGw%>o4I%SRx$rYwgQ~@6-WRAELgxK(;b-7*g@iLc^VFbf2$`qn%Fkd0Rg2HO z2&fuD<|VlDGvq+k;xZ3Zzje9tGt5I*ho!%;$CaPq1iCu3{sPFnXRiDVztGiTF;Bvc zpFzb9fB1pwb0;@`h9GoxSj;PM<7cQrSBK_4ka@G*_!(BAtHWa62{(R*E9mOb%mek` zKDqHTaJfVLfiHiExbrh8K-J-k4?A~$h9GoxSln0O&d*STt`5z8p!#E$J3qrNbahzF zyW!5y@C02Qnt7n`WAfl<5b}Wd7w!)%<{5bKGuWW3!(v{P2R}m=x;iZ8b$IYIOhH$N zW*#X0ZSvq}I0jXRFFr1K@H5ah5Ch9^J65_ENF=7HklkS9OGC3JOI%=_TU&+rFb9TxMXy!aWkya@Z(!;7CG1YI4P zd7$ts^5SRcf~vz8A5*;e85Tg*;fs%bUi=K#(A8n_?-!67Z!~|Pxlf0IfkDojpTP@V z9TxL4y!jbQ(AA-t2a1nL-uw)U(A8lv?|?Ty!x?mSSj>Cn&Cl=)T^$zlBz*W8RDAIJ z7c_q51XV+*{_ykRXNZET#T6f*{8r(^&(MRe4vYI%`S3GrLsy69UyylMeE1n&psT}T z9-A*egOD$N|ANw=fiFLU2f8{e=B4@aGZdk#LvtUv{Q^}(DEwyl@-r-fs>Kz4AoC7E z)eti8lrKNSHK0$J z2$@&l$Inm$Rg2HOSx_~E%vT{z&eg-?JI$Y*~ z+!q5?L&$w8f&2^wP_?+s1JxgWP&I_iTNKF8unwvgpLu7XY6zM4AdsKo4OA^I^FZYb zR}duZ2$?4p#Lu7xRg2F&7pNLS=7j|DGbBLO;xZ2uA9YYQgv@IT;%AryRg23!Q2o0h zh@asER2{zbaw&+P;T}{SF7rU~`zMH>K_D0shH!OQ>T|tdeg-Rab!hcD$h?SPeuf-$ zby&>n3g%~+hOQ3HJW%{@3Fc=wg02pWd5?nm8Q!6*!(tv!2tR{N2x0$Ph43@Dp{qkP z4-~&CP&I_&Hz$Ojp#rKF9?n?In+8=w$h>(W{0yt0YH^tdYM-8fsv%_Fl@NZ02T--R z%mc;mzYu-~p-{r%XAsKIV1up>&A*`W=crJAhB9<@SmJj|C_lpjbaiOvf!wz*l%L@e zR2{zjaVM0Y;RRG3KL4_X@iRz;5%#Y|7(asxx;ixXf$on=3gc&(gsu*Ye>a5jGweZE zhh`qgzqi8p8Q!6*!(tv!I6s3#IGR7u%md{&vv7U}H*|Ga%u5O9XDC2dhsC_UaDIk) z=<2YTwCv9P=u}yi+K|w_!;J) ztHa{HT@m~Yr_j}5G4DkLKf@Pvb!g^++6O|B{0v5s5P#rH&o+_#3?5K*xY9GoytGJu zhBl}=eCADvTsC{sxPCW_!)|z>hQU*CW@b-1F8<6dCQ{s8TLWd;WO_<6hFfis5)Hcf%5Mss2W1$ zGg~x2gHSXiTyU8O%D)CsHH6G_h~{VTfvUx29;p7vf~p~8URg9hLlaajF7rV3&XP#6HKZ9NjS~z3LzdkYi3=!z+ z(DEzx;ixTK=FGihM(aPx;iZ8F~ssS@Wi6|1B-cDvHT2H z=<2YT7ZJNj<$iIG2HH6FyOXO!rf~v)39w>erplS%2HzARqVGdL+ zF7rU~y9=s@ka_14`5A6O)#5S_RQ~;dsv%?^OA}MfZcw$j+y_d3DNr?p%qvOeXJ~+`#bqAIzw@AK2${DnnV;bhR4p#^ zK=Jzks)mqxUqI?o@P{ABJh>Ep1|xKJSn7{}6n=&nbahzD=c*KbhAwnd@Q=@^4%k zKSLh6IxPO}N#kdjfvyhCJW&0y4XTEafA^*FGn|5|#pPd+c`u-92$}aGji2EUR4p#^ zK>1fH9TIkg%ri>oXRw2+#bqAIeKAlqgv?7x=VvH@s>Nj3*5fU3o1 z9>~9SP&I_io0P%NFb}F0pLu(rY6zKkA%mac4pc2J^FaRn1yw`HJfTc}2DwZ~SmQDe z6dyKFHH6Ib$mC}TfvUx29w_}4LDdj4uPKwCp%1DSmw6!nu7Ro{WZscXeufKBwYba! zx$hlR4I%UXW%4s{W#JD$PNJCeLR{nw9*OJB0Fa=#57V|b` z@iXj0SBJ&CJ5V)*{QD-0pWz2oEw1nb`ByBPpFt}dEnKj;&m)_kAp~6=7WWlp^D{J| ztHWa6f^2?D@|hzC;#WfE3FPoI$UxQNG7scF zs~mm?KXi3i{F{@*&rpG`4vT-MpPz;%BfxSBJ&C zup)kjBy@FX`4{BAh9Z83Iq2%Jn76BlpWzs~IyCb@?t4B@b z+y^o*1geIRc`;@D3>i?h_{?jAsv%@vUl~8cET~$1=Iwy0A!OcxGJb|LP_?+s1I5QH zs2W1%F_rT(@Rj3_4^aBkfvO>7oY!=}nb%jv&oB$B7N2=LplS%2ccO});R;kOF7rU~`w6Oska_>A z_!+pWAz_WnJW%{9Y6!WHqlTYBqy~TZfy^_4sv%^aQw=|ZA5<+a_kqg4oEmNj^!^S(9kGcYwm{DI4Tp!}iG$j@Ma zt`3WNevSMLQRwQ>+y^qRqLH7W16>^!^OiO8Gi*Xvhh`oq{4O-|GdzK+!xta#8u=N1 zLDk{%FUUNJCVmE;CbV$D;y$k?eugk~b!hJMVPIe=Y2s%%fUXXUdH0(58D62QLo*K) zejLsG3=++R-DlR!&)|fv4vTpS&HM}n=<2YzudkV(VHUbNH1j~=x1*V#;S5wAzWBJ- z%+K%$st#X#FtqS9h_n#)uTcv>gB`j$H1~nxBc_F)VFkK6EdD*#!q0FCT^*Wvp!oOz zQqxMwu$br3%Fhsit`3X)%3Apun$XpunForG1+DxHTcGOj#mBx@euh&} zb-3aqfPsPGMJqppMH^xNhPClCB%!NAa~~-D8rt|7CZMas;@@>`{0zI$)uEXOs^4xv z)etK0AGGl^yn(94TAGqj+qLvtS}J{EQIGh9MfhsC@Po%{@c(AA-t2MRx_E`A2BE{K2O{=j0M zM;AXs2)a5f<`s4EGc=*A!(!foE`EkJ=<3kS1BKtIE`Ek*P<8m?<3kre!yl+RxIfU$ z1GTTDy7?KLx(WL?p_`u}2VEVSd7$%iy1MxpwxO%T;@>OX{0tA!)uEXOijRNY{0w|O zgx#mp!_Q!Wt`3WNVLki|Y3S;(xUZ#$pJ4*JIyCb@<=;A}8bamYwjO?lLr}H2;saEF zJbhPJj zqL-iH3{)LH^RD&sGdzN-!(|>Q{22NmVM55iB7OV}3Q)E9%(Ls`X9z-9hb6rf^zk#) zpsPcR4^VoZ)yL1U3SAu*^G@{fGh9Jehh`qAzWmh3&%o6W@iV^g6Y1w?P=Kn#mwBN2Xj4Bw!zHLXeEz-D&(H7zst%WV zp!C8vfuBKY0wnz4>afIz#RPr^7j$)K@c|0IqzU{CMd<3Vm^WbpKf@e!b!g^++_!52 zKf^h6by&=MGl8Gs2f8{m^FZlEY$C+3gyKVQB0qx_R4qJQ(98q*Hv+1Lka-ys`58)} zYH^td8jqL+RYSZW2GkDX2Pp`QyqYeuf87 zb-2s}rI&w`_!-0|qlF6=|C&tZXK+AQhvr{U_{B};XUIcWhsC^}$@~m6(AA-t2Xf!G z$@~oGpz84X_r_#?h9^*Uxcm!BFHBST8N{X#;om9z3=Zh((A)+Q~4Qu(AA-t2l8*$ zRDOm!bahzVH)kq8!wPhDXy$?Zdu%E{!!>kuSj_tZQZo(BA86)*(u>?Qeg-qBI(+HH zVH!V!4^$nl^a3(3YZ^a86I2~O^LnQ7Gt7Xh!(|@GzuTZ{2$cuNrtvdef~v)39wZbEEbfK$5GY{n771Q|{cA%@n zV&1js{0xuK)nPG@VFo{gzzo9v)tkZ3V1=#@%{)+f8Zm>Pp#-W9U;Nh1;AiN9s>2r_ zD`xOB>_JzD#lN>^@H0F^SBK_4Q24RTN!Y(eGx-_p(AA-t2a4aAnfwd|=<2Yz zuWu$l!z^@lXy$?3w__$h!wGbCSj>Ailb_)ex;ixTK>1Bz7R0ZF@`uJOeg+e$T3qn~ zir=7F{0wac_# z*KB?UvDuKY!DpVyY<>n0bahzFOPkHlP=u}yEj~c*n=qT7VF|iAEan}W&ChTST^*Wv zp!D))Hb283bahzFlbXZNpf(4;e?jKC%;9GUL05;xyrMb$40Y)0(A)>|@0>aO3>(nZ zVKMLA9Dasd=<3kS1GWEtK-CaRFAQ_}8F=R6_badu%WiCI%0d#d}=7Gi!?#<q=GpvHD!(|>QK2FTzXSjo|4omp`n#a$;HXjl$ zxXc5^hsu0@1`~93Sj-EW&(9Ett`03eK<=xV&(AOcT^$zl*3IW<*oCeR%{-9%Za~!# zijNob`5C@I)#CCmD1QhofP@Jl^Q0E=GpIq;;xo?$s)mqxJ`4C6BA{yVnO6o?L&&_k z1^f(MP__8XTLD!=$h-{;_!;&<)#5S_R3F`fsv%_FvjzMNpP*`SnFopwfrZ4yhr~jD z1{J7UT;_q?=LA(l$bEhb`5B_1YH^tdO3xJw`5Ai9)nQ4`s}}MzY(rOvmYzZ8U0KM_ z@B&>O7W3E^@iPc5f`kn&_kr?!?*mB95;T8cF;8v@KZ6#!IxOaSEa7K}Kv##w zys{advCu#BIf2VEVSd7$`R z1yw^RJ~l1mXV?c-3-<>W^X@>^5Hjz{GJb{+P__8X<6901J3{7(E$3%Yf~v)39;m!> zfT|&6Uc_>Kh7_n;eC9Pl)etgo)^dJ^Wl*)a%mc;m5vUqM=3QCN&+q`M7N2?lplS%2 z$G3u?L23mgtnry=0aZiDJdYLp3?Wdp_{=MUsv%@v(+YluKB!t;=7I9t8mJmV<{enU z&u|8+7MFRT_;>|XL&&^eEBG1MR^ksoko#1iY6zKUv67#`1*#U8`#|O;t>kAYLsy5T zJeabQpJ4&II<))?^6$Qt{0!&N)nPI3%}RcTAL#1P%mcYkY!yF))+)4c!D61rDt?9# zbaiOvf!tTLil3ngT^$zl7OdiDSc9$(%{)-~cM7V8P<&ik#m{gLsuov#fZC^jplS%2 z$FZ89L1Z;rxS+WY6n;jl`5D~M)nV~(%4&Xw0(5m)%-ZU#psPbO59HrN>-ZUNp{v7U-j8+s3@q!>{DEd3C_a?d^D`KstHWYmzF3O}t4{0vS| zb@7{G~KSLL~IxOy6v4Njq3%WWq|ANxXr49TH&(PIjF^^>G&u$YxgCDv&Eav5G;%BHpSBK_4PMcgTiL~{sqm?*lp%#h(cF~#k`8m{0uGV>d^cP3cp30`588$ ztHWa6h0Xj7chJ>gG4I!Aeg>{BX#T)rp2ik_1`~93Xy$?9BWMdhLl#sWzW6BF!q3nE zRfjJ==566;*o3YQi~BBY;b*vmt`5z8p!oQ;g`YueD`EedY~^QgKv#!m9wLN zp{v8AK0(#N{efm4C_V&s@-wLHB*MQt`5FAs)uEXO3csA4{0ued>ah8D zCqKh7baiOvf&IIapWzNv9bW(LAEw-TVwHyNU4cZhi(obaiO%1ErUo z-TVv<=<2ZeciwJ(hE?e5(98q*_rz{~h6m{Cu$cF6H$MZ{9yEWTnFopwjXnGf7U=4* zm>0H(pCJid9TxK%_V6=IKv##wymfo{8Frzo!(!fzJ^TzW(A8lvk8LkMgV0|5{so1f z!Crm_52!kP=_P0{KSLZ;9lrEZvzMP?0=haZ?pwE)pJ5leIyC=++;?LyKf?!fby&>f z+sDr!wGY34LGf#`kDtKeKSLV2IxOb39Oh@3fUXXUdFu}IGwefGhsC@*hxr*^psPbO z57d8SJHpSPbOhpGeCb8!2tR`bR2{DL0x~b`2tPv+R2@F^YL4(TbU@YNG7prVmmT3} z*auaI&%6^y_!+K1)!{M^l>R;);b&kw3h^&o9hUG@Im*vqfUXWL{6OyWJIc?HhOQ2a zc`Zlz8782sLo*MQ{?;AkXV{0X4vTqrj`B0SKv##wJho%}3}VL!``6?cKZ66hIyCb@ z{*61v&rk+chc7-Fj`1_}K-Iy+8O=OUe5^Xg&#(_&9X9_S<7aq*t`5ySQ2Jv#&d;E9 zoUnf#j`K74psPbO4-_9+$N3qm(A8ma-;Cq@3`@|}VKMK}aejtN=<2YT_u)7{!yj~Y zXy$?9L+S)SgV70yfAPhK%?W-652!kP@sW0dpP>p}9TxY^IKj`b1YI4P`#|w==mbB* zJ#=+g%=>eKpMm2f#2>ia2Z|51ll%;3=<2YT7jlxHApu<-n)^WJ)t%&L=tEbB#k@5q z`5AVgt3xvn6d%{1Y6!JA-ks!U_ytvq%fBG=Bu+uXgphd#r}!Ccplb1%7X?*A$h@Lc z{0wzawYba!&9BaZsv%_Fl2iN)8=z`&nForGbEo(j?m^YzOV2M(@iTmZs>5X-sC*GR z&Cj5A8WM(Zby(up_x;iZJapnv^!wqzGXyFGk@7o!E2ClP^FofHS#XOC({0t`O z>ads>be5kX4P6};^IFdGGfY5Nhh`oq{jGzlAryZ5&hj&yf~tl41I;{;e_x#CXZVAz z4vYJw&hay-okI&3H1k05>jG6n$bA9l_!(lLYVoKs2qAG$g$?pt$?pJ4~OIyCoz z>Z5Dt_!&N-tHWZRz+ z&(E+Asuo}P-8s+C@Bv*N7WeU8;AfD!fZxBM^k;E_pTPrN9TxM_F7PuHp{qmlFDO1H zK-CcP@0ttz3_GA|@%i@}R1G2XUR~g4_y$#r&peTf{0tfw(ZU6bf88$fGX$ZlL-Q{v z{0c7eGc=&9!(!gNi~J0$(AA-t2g<)EplS&D_s&Irh8Iw^`25RuiJw905@G*ZT;gYN zL05<7K9GNtF7Y!Ip{v8<-wBua8Rnp?!(!eps2W24J#~qn;TlveF8_k+%P&whgv|SM ziJyVvGFrHxxepW{YM1#LtkBhA@o&Useufluby&=6y3Efo4P6};^R`^(XE=ba4$VA} zfA2xn5c2P{%lr(VplWf2AIQG~SNIuJuAqer7WX+_;b-teSBK_4ka;;*_!%0|)nPGj z-W7g^Rp{!ln0ErIhLC^nT;XSU0ac63zo7b!?JC5tgv=AV%FiGNRg23!Pdn77~>Kf?xeby&k#^FZUf zKd$jJNL(lEU$g7{3{L3k(98qHN5XY}h7xpjSll=1IzPiabaiOvfzrz!s2W1)@5*(4 zh6hl!@OVQr59GdoP&I_i6S~3AAa?^TT+qw|r578h8banp+~8+OfvUyjK9G4$P&I_i zn|FhsVHH#@KJ!jM)eti8$qjyn4^XwZ%malV-%UuE5He5cCO?B7R4p#^K=JEylb<04 zT^*M6(sYxbp$}ahT6}==+nSsF413VkVKML4O@4-F=<3kS1D&tOa*H_sn%v@NaDb}C z;%8Wat`5z8HVh05$DnEm#Rt=Eeg?kV5WnDZA1HoxZu2uZ zpsT~;zPQ``3|Z*v(A)=#-wvo6Lhf5|o1b9~R4p#|f$H;9P&I_iyL6kM;T}{iF7rV7 z?GIE9A@d~e@H42~!5@C0_;rG+A!J_C9e##9s9Id^1EuF4s2W1%&AG$RumY+UmwBN0 zJ$8qm;TF0&Eb;r}4nG6SUHtw9#jny`eg-Raby&=cxXaIwg02oNy@1@;beErD8oD|x z=54vl&u{=;9TxNM-Q{Qagsu*Yc>?$N8D#F^_b(_utnTqMc%iGqVqV5Qeuffsb!hGb znK$VkKf@w)by& z0VGTanP>EXpTQ2Q7MFRT{1F3HL&&_62mA~TP_?+s1G#VB1Ac~GP<8m)&qp5cGhBeG z!(|>Qe&0RdXJC2=@iSZfqsmW*#X0MM2dN zir=(H{0v1_TcLsy3uejxL5p7Jxa zpsT}T-lC`c4C~Oads>^o*Y& z4qY9Zd7$=R%`<+6IZ$=@;$zt}euhm@b?|UTGY{n73s5zL(%*w;{0wiPYVn!J^&Ao= zgv^tA&d;C*Rg23!Q2FNqRYS(`7MFRT@LK~_ zL&&^6&-ocnK-J;R-#n-qLjK+Kf}dd@R4p#|f&6>t1wX?VbahzVC-jn^LGC4C|JuCdXYfH+ zhsC_Cm;4N6=<3k?3v%C-m;4NC(A8lv@6=0vhHL2R(98q1uf9Ol5DGtmSNsezukia9 zl%B1iY6zL<^opOs52_Yde1P`PafIb#%q3t5_EM~;&;+(euj1E>adu1<~2XV z4Rm#A=7I9tx7Yj(TyN0)fyF$HH~b7H=<3kS1I0(s8-9i)bahzFYk0%Y(1WfHi+QV{ zY6!*0t~dM)$DnF)#V^RePu}n|{D7*%SHCg6p164!FeGYH=8GN8> zak&qaKeFEPGxR~#;d9@NxBLuCpz3g$2dd8xz2#?k1XYL6yf<(88Gb<3;W7{8U$J-m z3|jBd!UaqGdc5Oj2tike7Ji`gQuL0Wp$T0b7V{Ro<7Zfdt`5ySko!)(<7c>st`3WN zf8OykaJ)zJ2by^x_o=<-XD~xohsC^*_xubA=<3kS1C^(BP&Eut;2>b&pohi0w)gxD zlb~vG#RsT-+3=p9;S5wAzWBZNo}b|nR2?q!K;g&mfuBL-16sIXai7r#eg->qb!h$t zgn`oPaH4P70Yc_8;~fvSN99|NxNJMn>^;R;kOF8_k!;}cX3A@hEH z;Add_h!!qr?gO2kpTP#I7MJ@#`8Vn#KSLE%9lrQ&`N+>O0jds{d7$uH z2USDJeTP2sGn|8}#b@3dkeW|u;esW8!8%+F8(Rf{h^ra{#ZGH=yqeuiyOwYba!x$nwneug*b>ac_#*B5>Uu`g)hg63aP z_?dj+XK+DRhsC_4FZ>L7=<3kS1G%r~3qQjWbahzFJM@L0;T*a;H1k0D?adc{2A;1F zf8fh+Vqf_gl%VQxg&!#WIeg`32tike#eGFz`5Efa)uFi$6n=BQ@-wVKSBJ&CQ(yTR zuA!?#GY{mxFJJi?IKDyr3-<>W^VGiaGZ>+(Lo*Ll9|e5lXUKu7!xw&K-}o7tpz7fE zqL~MZ-v!_J8Frwn!{WYc-}o6Gp{qkP4-|e3-}xB?z7zJZ-gkZmD|B^e=7HQ7@tvQc z09_px_w{|}XPAYq4$VA}`*wWiXE=eb4vTruzVkDDLRW`o9;iMN_yO@Nq4GfH2S0-b zR4uOf0L8D{4}OLus5*S{oAZO8p#rK7mwBM@oA!gBVI8_UEdD+7gP-9Bx;ixfg3|N1 zAN&klKOugG+l$3Kji3AsCg|$W%mc+o&`*AbBy@FH%xn0`&(MRe4vTrKe)2QyLRW{y zyc<9H8J?i4Lo*K)A56da8RUKu_OHz^eg+S8b!g^++CynjHH6Yj-Y3p^A`N#XIKMOi_1Jv{GNiUA!OdYU;GTOplWfM2Z~>g-;l5)WS+!teg+k&T72d? zLDdj4FX%TvLmX5sKJ#jzY6zLv^P8Vx22?FB^FZlk+i!k`OX%vbq`wcp`5FG8t3!)l zQ20sx;b+kL0|`U8y;#ii_`}Z-g02qDJWzR11XV-Gzg>U$8Kyzi!tF&f59GcrP&I_i zJMxF0;Q~}GF7rU)_wElr1J_@|{?+))&tQVC4$Xa_^c?h;pCJuh9hUHG`OD8R0bL!M zc_9C;`^(R;4_zG=^X~lRXLy0G4vTqg|M(fi{t@=C$v=Ju2Xu8<%!`AnArv2Z|M(fI zplb2O$Bcjc3|r9EVe#*!fBX#h(A8ma-=BZ{3_Sk{`&a8fKZ6;%IyCb@={W?dhLC@A z{_`_bK-J>%FDQPeLDdj4Z`pr-hD}hlxXc5!cQ5?sXZV1w4omp)F$ge7F@Rjaz<|p< zkgNrR0D}j*IxOa;F$gdep{qj+KahD7plS&DcL9R{!y2esTp{v7U-U4O; zhBfHwu$Xs>S%Bddx;iZ8{a_YgU||8d0DpQ`f~p}DA4V(!40cepxZ(qp-(pw<7;4bf zVR7Fq76FE3=<2Ze_Xvvs!xeOOSj_vxBEawuT^*Wvp#G5zDt8h4vYKh*aR55(AA;24^&>QU=v{2gRTyXdAHaE7@nc4 zLo*NLQx;eoIplWf&2PnV2V;5lH;~?x`9S#8o3v_i@+!w|nz>tQn4vT+VI0P6bpsPbO z50pRFLDdlQ?=cPmhD%Vjxcm$9z>tNm4vTpm`~nP9(A8lvZxd7vA^#rY7hpICRg25Np!D*FUx0x} z0OV4nbdSY-S^@$LX6Wj$xGzLNfFT849TxMN1OyoR(AA-t2WmgBfvO?o-yH%13`d}9 zarqY%zmEh27=EFv!{R;(K>-F8L9}o|b00|7Nl<_x2wfc(^9lq77;4bfVKHwOR1G2j zt`Zbr*alUL%fF!V`HG+b!v}PASlq`aB)}jg1o9D5IzV$DD8E?<2{8DetHWYmmXH8L z8M-<&^FX#u5fWfng02pWd544q7|x-q!(!eWApwRz=<2YTCnYSvpe77*0e=6wK-Cb6 zj|gD_h7_n;T=4-4zb0V;hH2>Pu()rFumHmWbahz#drw$^;T^gmI&lGp zedy}2n0H59fZ+wYIyCb@{$-N@nF{kQp79q60R}awT3r4Gndc%Qzz~714vYKBBm@|m z(AA;259G215&{ex(A8lv@0^4H!!2}mXy$?9;|Ej?%*VLKHv}XF7-S?tF2G&hgUqvn zsv%@vkfZ=Z98@j7@T-v&V3>fe4vT-+NeVFRLRW|8Uy$o>ND45#Kv##wJT@r-1|cbs z4&43)#jk;s0D}v0E_$lWCR$Z(AA;24`fP(i~vIix;iZ8 zEt3&o*o3YQi+LBIY6$uFfs6pd8>m`*{^gPtU{I0;xfH*D9b^RJ{xy;lV6a11hsAv{ zasmtm=<2Zew@*%hVHUbNH1j~N+94;vZ~|Q&7W1CT2{3#@SBJ$s0eJxi1$mIk_~X}3 zUVy<1T^*Wvp#9Dn@&XJKpz83gH=89dz_1Le4p)4D%sV14!0-mD4xf47gQ~;jKG6EB8mJmV>90jWfMEhuEk5(sLDdj4Z<~Su!y%|z zT;_q&^8*C|hA-&qu%s6uMF9pmMf~9hlC@D3VDLd#hsC@sMFEB~baiO)0Wxn2R1G2j zE>RR<*Z@_F%fF!h`8h=ahF9q7u(*#yNq|8_3FHE#bdSY-MoIztHk z4$VAJdg+3yA>`jlN&*b?plWgXSA~IrVULmk!vl16SlstdNq~V%8NYu)^_zyW0D}X% zIxObJDGM-Up{qmlFUYnIWdVj6=<2YTw@q1q;SjnyH1k02d!Q`9@B>{P7W2eZ1Q?W5 zKrX-^zYZz_3<2osu$Y&pBEV3Et`3X)Wtir)M!J}3NTbb)#5S_lz(Ta3NUPe zs>4@4?^6|EI0aRQ%RG>OUx3u8LHrC?hb4aH)C3r`(AA;EFUS-RH35bQbahzFD^n9- zXhK(qW**3W3!rKUh2IV}0fr+`wQzr+nFlIg9zoR*GVhg|0K+$^T3qIV>JJfh0R|0q zkk66w5f=ZtsS7X!p{ql4AIQH2>H-WM=<2YTw@h7tVH3JKH1j~Nx}Yw=@Bm#M7W4k8 z3ovkLfOH`F1B-bY8UhR!=<2YT7p5V=kc6%d%{)+iG(go5ijNKr0fs41wfN#=6I2Z$ z^Nwi46D%9 zp_vCtFDIaC2>JJct^mUus9Ie91%)4%9>|q2({SfsF+BkWC8%0l=7GvT2dEmDiMaAd zjGh2P22?FB^FaP>gQ_89-aI`4hE-6txXc69zbEtr81A5}!xA6A^aL2#^dVt`%RG>* zioO7Y3A#Eg<^|~sFvOv&!xFzW`T`77pz83I2lMm=7*;{m;c}k?0|UbeeE|jz1CUGc z=U+7g0R|&t8h4vT;535pV<^CI1YI2# z^Bx%rFuX%ohh`qgr#wai3^GO_7XJ9PG7?~LLsy5zyc8n=h7xpjSllACYLgw`u3oy)rs>NsC4yYPJ<{dB=U^oL+i_1Jv$i0H9A!Ob!V*v&>6Oaz1 zbcGhbp!}u+RYS-;0}}xT8>m`b?gPa~6jTi%^YTmt7^Nj<$RsF&AJ^F$b}b$_*^$IhhME_@S#qGY^zra-eDm z`M1PefT01Z7MFiP=FNkuA!Ob%a{-1;P_?+s1C=iqplS%2cgI|S;RRGJF7rU4#%2K` zVW#0ue_|E_3`$V7_{?*Fs)3n^GyPczFvLLB;xZ4^eyM`0A!Obp3jv0CP__8X+hZZX za0Oi*mh|$;LV)2Px;nJ<0tz`9O92KQOArfx`t!0BUYO6JTgSSBJ&F^Xvo| zR-vmy^DoF%C+q|mZlJ5fV%|490R|>}kPf7LiDn+izY6vO3^wTMu$UKRFTjw7t`5yS zP<*sN)ewr09(w_X8Bn#j!VhHLHmDjx=Iyf=U^oR;i_1Kae_ue=5HgR&L4ZNP0e|>` z$^$*98bao|IS4QWLDk}NA1MD8K-Ca3Z-RpW!yKquT;_qw`&|wK4Cm0*VTs>24gw56 z(AA;EFUWmjjsgs3j%eY6#k>$l0fq#0b!g^+OsR7eVCX|vhsC@#jsgrj(A8lv@0z0k z!!vYsSj=N_5?~N;0=WQp{s779LDdk74?8CT1}~^uT=4;#ugGu`VCX?thsAxXoCFxQ zp{qmlFDU(8aS~v7gRTyXd0fr{3}Vh8AK~|}iL(HM3%WWi<|R1`Fyx`DLvtUNjNj<$iK6oY6zLP$VGr*9aJqY^FaNhGf*{z%)8+t z!0-gB7MFP-_c6H=7k*r>0t{kMwYba!g`bJ50D}j*IxOiW%~gP*2wfdodI6a?!Bv1^ z3A#Eg<{fesU^s`a4$VA}Pv5u-F#JJRhs8W8HvtAUH;@k8`3)2wE>Jau;v>LKfFTB| z7FYOz;-daR4qR9q&$d=4Nj<$iID1HH6HY<{`ka2&xvBd7$_>098ZCyfYpG3^$-^ahV6oAKyF# z7`QwkVF*`;rF_xw6ksqxSBF+!fy@i?6kte0SBJ&C7Eb|&3Fzvun77VTfZ-6jIxOZr z@DyNpgRTzEJWzaac|rV2DEx%H1Q_I?YT@C6W**2S8>kvW=J|LDFhoGr;xZ2ueq~+) z3|;8zu=sa{mjJ^SbaiO%1DSWpOMu}Sx;iZ8v3LtG2zY~hguDI#$?ACvFxa81!(v{H zw*W&1x;ixXf%03Mw*bRDbahzF+v6?3Z~|Q&nt34iJ%g$t6d&Kb1sIroKss>y7ZiR9 zP&I_iGw~5%aDb}C6~CbNU!0EsLlwF@EdHJ0Bfzi(T^$zx9`X@jxP-0_i+LY>1Q`CH zt3xvnNjBEY~D3NjhLe-%On7d?#s)o)B;0t{+l z5dY%K9|mCp3^q`8xWW(Q-zcaWLh+FoCcsbyRg2HO8BjHZ%v%#Cz_0_V7N2?7!UPyz zp{v6ZejMQf3?ku>aKUGuQMdqu8@f6y=B0!SFchGxLyHfPZGGVa4D-;{VKHw{xB$Zm zbahzFdloLh@C{uZ7V|_R1Q-+|KrTQ^_gKucgQ_7EAAS)63{gs>5X-C_OVp3NVO8qJ;~V@H2@NU~oWJhZcSy z^Wq`}7>dxa&F^?fyfPp6(uS` z(A8ma-?dl)hDYe?(A)NSBJ&CS8)Oi-_X^enFsQvNW1`pN<4^#KYpF!1sMF$)nPF&CtiS|23;K% z_sxnIU|5E(4vTq5;sqG4psT}T-lup0hJWbl(98qXM=}Wl3?>PL{Tq}Zzz~P74$VA} zDK!ZK3_a-Tu()qkf&jxdbaiOvf%-F7plS%^j|T|?3~!)napezC|AQ+L zmqY;ujwFbm;p(u&hgy;VgAuwqwDadu%0;&cUBe=#l zb|eWf9D%BZ`vc89Q2F-=s)mqx|B?h4xROCWLdxf8=7H)rjbs6a0CaU&{F|37z)*#* z4$VA}Z8MSu7*?RG!(!gCWC4at=<3kS1LcnoP&I`7`zKj|fg=Uv0^I%u$*Mus5Him! zMS#Hxsuov#fYNh9iU30eR2{zb+>|1~&<9nA%REr~U`>hu!v%D8SiI^W{u`)0)93w&< z$llW!>ag2;8ABbWy+I5N3^y^<1z@-jRGw;QfI<=GTTuOrX)g-{14A%|I!t@n85kJC ziBJbBC*v^GVcH7{=PV3$nD%lrFfbHgsKahvHHJD&^Eeq87#cCuVVVaDmo5x-nC5}p z*N34F(>zdmnTeqe(>##6c|@oK#m8bI)NwH|Fs#B*hv`0$x-CSg1KGO|LmhU193?^> zD1V&7P>1P0Q3eKvOGKyxm6OkjPzNgim@^6I4`~Jl25}|KnZ4l_PL z`SK}-I!yEQ85kHO3((z%sZO1Nfx&|abw&&f4EaQ=TS$aDQ21TOP>1PXRZuxzNZ22s z^3;Y1b)f!68HPFmjCL3(J(pvs!wf%Ad9{!Tb)fR#A`$A`85kJ8W2nOn7mzx;BJ^;< zR0qm$tr+Ss{SgJqzZmK;&8q~(M=@c0BS7aU5uwhMfq~%#hB{35fz-*CpxcY74iwHo zM5qI$mkJ`(f%5MrBGug|LLI2R!Ba}uA7%^;3|2&_lLWQ1iBM7D35$fz17#M`Bi1TkChB{3DdN43B%qKz}sD67+ zggQ3{1_teFboXJ}3o7pmiBJa`Puzi_4l_Q)85kHY5TVYIfq~&UhB{351v4-(Xw(q) z2S{Bt5$aMI7#PkGp)Q7jfx)nB1T$h_-Br~~Co<2u6jg6iLi80s+N12p7y z5sKaz0sGL-4L^lsp9mu~EiBJc! z_Y)E7)-W(IgftPh7o?7%nXo!gKY0y?I?QkZh2LW$)Pc%V;TFR7g2ts1G1Ot&E5yLS zkVAwzF$M;PIwI78+7VNTP$$d4z_6AGb)fe1DI(N?+Bsi{PzP!sXtkn;A7;3K#^GWx z)M1WuF)=VOBod*{mw|yH8ABardIq(JHejg3bRVeRe@KKnP`^x}jj(?~>B=2L9j5z0 z?UJb&>ae?SFA?fO85kJ;V5q}xZ(uuNe}KZTk_dI6bg+sDb)bIo9U|1pGcYg+bP#qQ zsDG}Hp$^l(N(>APg&68E!`TMZo+LsYXk6+%hB{1pLF2&EorL`XDmPS#PzP$SnG>N7 zG;iQVggQ_=F^mXxpmDuqBGiG}FBL?n1KHb8q`Emos1syhU|30nI#4+8B0?Rgygx&P zI#4)YCqf;l{rs2+b)b6TI}z$YDT=oXMB<;10=Z9?NOhV-r~}ojrbMU%rTbJO)PctD z^NCOgYCrc9p$=3&FC{`9sK0-P2z4Oyei5Ngn}LBrv72ywfaYg>h)|~pYBv#~4mAH( zN`yL)`|5~L2WlUzBtji%y})H6)Pc&=&qSyLjTh_ppobr3c?wF;l^E(U%W*yi28LE5 z)Pc&4$wa6Fl^aWlPzTDt+lWvn#lXODh6r__{PBzkbs&GR^rD9!X1IXLaZw`GX%V50 zpMinFo(OfI`0yh_9moeUM5qJRA2~#*1J#$6M5qJ#qlXA}pzxbbggQ|9w}A+CpmU;k zVW`7Q_n>ypFCx@|#y4#GK%t0SFJPJna$f_6I?VI}3YRV-)y*VA9VnfyAVM7|z3e1H z9mu@nM5qIW$PFUYf$IGiM5qJR=bwpC2XY^KKgf3E_`nQjP-M5qJxYZ)gIHZPKafx(Xmb)bIeaw62_FfcHD zB|;sj9hNv5-F=wh44R**BtjkN-i&2Lr~|cME)k&)R9^ihLLJBlJX1g<{(K2)=g1JD z4wOG^Fw|kDE6_Tv1`KtW;~=1RVjG4!%y0&kSA9gN1FgH6Nu;_hM5;SaggVf;)GH#? zfyyEFsf6PLw2n*yLmg)Lf!0;Z5TOpVE>|8y9cDO#)HxHWE}ckq-9)NePlP&9Kj1PE z>OkXqcZg7D%fP@OG>ve4faZVwiBJdX2ec5O4wR1$V5q~)M<8`9)6w0BsjdXHj)Dkv zAouOTP=}cgKOkpFVis}!@FPMU$RABas7qmBVAz784l})g=7l6?6Lud+T>ug4K;hRxggVf= zuR|E>Fx>}A_abx9-G`|TG_Mvxgt}=A3=9j1P`3!Qj&UwwdqL`M5TR}f0|P_VJi_KJ zU|?X7n@?CBX#8Lw5$Ztclw$$9d6@YKG(YV^ggQ{U+eCyqP`kaG2z8)(a3&GzK;sds zh)@TzcMlQjK>d$XM5qJVdz}b%pmyR*BGoZ2Bpe^0{+1*W>OlU`B|;sj|7%5rI#Bqz z5UDPR2z4NzCJ>WNSXT4z2PLmg(j2{c|Z3qu`d{s6`AS|Zee%sWbi zI#7T01`+B&<1bH$PzQ3~Pa@QT>=j%DiWU6*08qM;B|;s@JS`&BfyxatBGiG>fjtrG zKtAvzLLI0d5J!YMka?9vr~{P;okXYuh2Lx<)PedvD~M1BO7|OxPzUNC9U(#;Xuk10 z5$ZtwnRi5}1DVIY7!->5(-mmGOp8c$jzp*fjTc7{p$_DaG9uK0#;GS0p$=4UttCPo zD81|>LLI2wy+DLIQ2BC?2z8)z^^pj5pz-^kM5qI$KmH|z;}>L}G7;)P<(efC>Ok?~ zM}#`ix{(Yb)PeFxBN6IA^<@tc>Ok=}jRuo1O9cUfd zZX(oy%sW7YI#4=2f}swxy$dS;&JdvvWZpF*)Pcr9?h&C5lPjn5}^)Mzda^G9jJa2UQamuK=qp#5$c*4 z7#L(Q)M5G;R3FVFLLF%Q-fjcB`!LM|ty7qQp$;?rK;uZ8h)@S=Z|oyN9cVo47!m3~ z^H%pT)M5GqG!GH9k+6S3^QO}<)M5G;)NbEJggQ|F_Z<=HK>aO_P3Z2!v=^jKjROk${6e84t?43k}IvEBAh7Clh1DSV*2z4OyUJ;=V)c;`HOgQ{N@uo+FI#9SI z5upy0PA3qdu7ZJqVFiXd%ybHBXDe(W><^H=VMM3{m4A6er~}2v3L?~j=9!KWp$^pU zWZ6pCAE5X(AVM9;eLh60%OXM@s9)PbggQ`pzljKSpz`z)hC0mn1%->rHp1ZoQkOua zx*j6bf&9CJ2z8)!WREb^Vfq6!kFBzuus^`*ln8a8`OrKJb(r>o^6w4|b(rNO$bD~! zPzMSZxgCW40cvk}V5q}%A4pvX5$Ztm@h33UVfq7f4hzFhboXJ}8^FN8kVk|%Q2DYC zLmj5QAa!4ePzUOFYwbdJA7(!aj{80s+n3tIQUyoa#8pmNt6Lmj4hp#0m3p$^j@p!{(fLmj4hp!UmO zBGiHMulQcV{s4`iCK90z6u%cS)M2_0RKENtLLF$FUwj|B`!LM|wf|I!PzS1yJc&>T z@<%!m>Ok>3l?Zj9^6D}X>Ok$GzeK15^?OwJ6Al;9c)K?d>Ol2YA`$99>An|39cDTO zrN65f>M-LCl&;=msKYc5q%Qsdx_>d%f%4ILBGm~VBy1k29112v9mv1aG1OuD7nBY* z5}^)MAKk`Khv{EXc_n=a-5;3tt^%DGiJ=bDJWzW1i=ocJK`*Je7%{`lzyKQeM&^Uq z4FU!S(9h)rsS`eo?moae@d9zz{=_c;@(&YcK#AoqD=sKf5QU<`HG-4{-zx@aQR#S^J6nMif%M5@as zQe8fg>WYa}S5Bn5Y9iIu6RED5NOkQ*s_Q0FT|W`(KM-pEsgovBoiY*XK>pCiP>1OckiGgC>ae@doCtLwd+jmQVcH8)=S-wJ zcOun!6RFOhNOi$PstYGV9VncmG1Os(Gbmh=G1OrXzjPwiWfQ3`pGbAZM5-$%Qe8EX z>gtJ9*G#0kb|Tev6REDBNOhBmR5zVSb+d_7H=js#i-}aXoJe)6iBz|qNOhZuPzTCy z+cDH(<~NYK{Y0uePNcf?M5?<^ggQ`pa34b*W_bXr*&bu4!|V@&)O{vW9rIC;YEVxQ zf*k~~nY9mEH>Gln*5Aj)&(YU4SV=)YF{dQ8C@--jH7_wY)ha$dvp6w6u`Dq&Cow4} z)fSB(pOTuESejF!0IKUgg3hI9U|=w0U<7RqVF|mnfuZ4`^pU9tFGsC8 zczNsLga1$5KKQ=#%fYkX*$%P%6g~toq`_X?RncKrNteS{{nHNqXE+@D3-uf)*oQeX zFf=$yADQZSIckmL<*kPu|DU++_`dUtB?Gj^V3Y~k#rXzOes?&Rz#>h4@5?c+R2Gtha0eVB7dY_#+7 zrUd7U>rJt=BuRmEYvN zgl((y^bgyeo9^y%jyt>8S@pmH=gV6UJ149==6qu1NoTedXPkvso_Bt==8|*A=Bv(6 z_uO#)f8w_Dx$F0w171FKuK)YQIZWh*^CjKa&Tl>6IWNli=={F(i}U%l-<@r){Bkz= z``0;g3WH0eHj~TD!^|$Cv8*mU<_h|#6WT&H6XQ+v_u&x$P9r3 z%n-=PgbEoL@k7S{imXDcOso)71fiw~Ff&3Se%%a=N{kAOFf(9!1es8w023-?U_yl; zGnoG$`nT}k#DCn3%42<`)KL2sCCgU@~GfVpL{iW@P!F{7?DcqrXC|2mcDP z2*S;P>md;4jsFV&1+%!Y1hJ^H1TqIQi!zHc2e3G^urjkUo3I$OgffOOx-yzFIx(v- zJ2E*ki7<(PoXX??@*}eWvmuiqlQ5Go)PIU#|FQfJ{wMtJ;$N8G(ES7RC)|x_anSV7 zlqK;W$VCBgH#xJIu&}Zivp6w3F^4j_GKDaPFa|TaFsd*+GK1XYz{~=6ks-4%vjHd~Jz_DY%$jr$4|HxnEzlZ)T{4?1Cn8ldcnAw;ESXfzt z7*!da8BG`;{<-+aky!*9ss_wXj4F)Aj1G*T*hWsvtp7j%UHmurFZ17ne>VLQ0;gvM ze0dlxuFF}=Skqazsr9o{Vo1$_*e0- z&|gTdg{5ndKN(mUSa7AE#((K7r7YnrX)Gly?kr&}sm!U&#mvRb<}9HsAm4!EMx8l@ z*^Rl7S)AGMAISX$jO>gd%&yEX%n$!2F(om9{Ez^3dm=L%Gbkm3Qz1C*!O|Tl<*6`% z@~1I0?XiH$97bkFw*N=Jn|?F-y7)`d7soG#U%+XQMSumK_Xv~^jDMf}cK*%$d(*Gx zU(UZ6e|`FS>F1`O6Mr`SO#12cQ}L(J&yPPI{W$bv(GOX+#vg@05`P5#ILvm4O^0nO zo8ymzY%*+yKX}*_e+d5A%x2BT_+t~>$M0Tj55Ie|UHq=is?EBQbt9`Zt2FBdHg2{| zwk2$f*{s;6vPH5jVqL_l#j3^n<&W{7qCfvx{;~XJdB^gHlyDb{$S~6Im43p z`vptj?|SBX=C#agnQyV2X4%7%&+?7=8}lp1myF*TpELF`e`UVOJeN71xsLf1^KRxZ z%>K*=f7vsCX8O!@gSnS^4PzeTCnkNSiN8KDJ!3q{IEVQu^Dd@cOxKxfnV&E{Vd`Pd zWq!=`m}xfCY^I&etC&AB$}{pa@-wbxievI)I>B^}$&N{nQIGKv(*vf5jB<>8Ojns= znT|8nFkNBV!PL#<%e0bd7Sl1N9LCFxwoKcZE-_wW)MdQLSj||?c$6uIX(rQhCL2Z@ z#ww;trY($H7%wpLGOl3kVm!j=!?=wxn=zVk24g4V0Y-1eWsF&j(-}J$moi2%PGf9m ztY9o>OlMrkD#d#68yD-wZtGWj1i1w(2}Ku(Va1sF^tiiF@@2M zv6!`pwUDKdMVv*PB@|RXu_m*cv4V1P5~~_(FslSJ1U9fVuyC+&uq3i1vWT&Wv4F}h zHWoIPjbE^p{Z1?@EXFL3%#O^aj4q5QWj?qL0_R$I9n1DV_4n1ESALlM*z(=@`{Qqu zzXg92`u6GT#jl6HF8r#*3TkP9@-IB^g7O3lBMYR4WngCd`{>W1Ka2h}{we&E_$Tm> z;~&F6ihl(EF#h@Y`{D0PzxS}^vu*sn@OM1hbGACRQ*3M4cC$_V{e{h+?K7J_TQ6H4 z+b1@Cw#MHd+2q+yvdv+8%C?JbHCr6pb+%fzCv1LfC)j$}9)J}|vt z>|^}Oc$0B1;|<1VjQorb82K0ze+B;XWp(^z_)GDZEvqi8;IE6U+gYnwWmy@2#jvhm z?P5K`>chH?HJkP0&%>-Xtjk%eShH9k{?uX7VL8OQmGwAd4dWHY9gN+KD;Z}o9%BTh z+e?fW7>_dYGR|Z?z{tb6fiar(AnOd)PF5M#Ev(+G%UCzFPG_}dv1X}c?O@%+x|CI$ zMVloGR1UC6vv9LcW6fk;!s^A^&RW5`nAM8Kie)NGB+DWeEtZ8WQY?W#+E_AJrm%Rj zOlGlUas1KB62Y>7k&CgMaRTEcmNJ%f7EKmOW=ZCWESxMYEFR$Ux0%HPTn0BWH!*84 zYcMx5H-b~3J2?HNvV?)_n-rEJW_55`9Lg-t{P9~dGpH;!V+NJRNz7`@iOdVX1v4iw zvVrRnQ)U18eMbgV;aGT)b-(Rc;|8D&IgY`RWJ?l5teAfTW5crifp7jgM z7Z!gOe-?Wddlr5cewNRyOaAIJL*PHg_l$oT?=${kJkR)>aX;gFaQf+IRQmhr&pVcj ze_pfPVLABc70Vfxjei#YS;I2%&r_D$%(t1}Ffsmp%lMY@9OGTad5rTI*D=jyy2rSW z@hszBa7ug5c#3fj<8{VQEFV}tGJj;2XO?GX{loG*ou?oMhR>x|%hPwU+e>%M%tq))TBfEIllb zSst@oW4XqX%es@b@s}KnFH7OC?JSAE?3nGCAF%MTTxE%6InGkUa)o6F%WTF~jNOd0 z7^@k18CS9#W65E;%yN_^hUEhD1?CkjT`V(Mj<962EN3~)e3*IS&x6c7%#AESXA7I|X z?9IH4Ig8nv$(pJ0$0jCirj1OX5?7i@nrQ+Oy(ubUd-*x70gylR!obTr!p^M zTEz76s}$SAubyldzm~Cq>bZri3t1Piaj{Kivt(;!i(s3?3M!d3S<~4juuWv0$STPy z$;!#r!sfvi&eqIk!Is9>#HPWj!CJ!R&eq7<$ST1q!OFqP!P>x<%2o_+Cz`W{v8AvT zu@t!MfLs_U8NGxam|GyP!x&Row_&-9Jy8&f`0KGRpG zc&0B*{!E{l?3qCIwmg$O(?@20=7WDH{|){t{PzRnroZpNW$POzP-_Cza=62MmiZO5 z?Qn+qCG$MyTg+>jPc!dfe!<)aZXG-aw+=4;KKOgn?>yFtzvr+%1*h4@-?gmZ6w6xp z`!VY^)?8LQ)}5@gSy!>@u|8y#W4+26%X*x(hV=^T4%TkgHH^C%Z!q>UK49Wwy1;mW z5!9a8#dw|Z3F9M1U)GhZvsjO@=CEF7wPn4;s>=#1myfbS%H^5xk~#4gtYkjKs>8aK zHJbGxD=*^#Mp&5}_;WL>D=DHc#UTn;aZmoT<7g34l884PMgpq9U&Hbf)1B`m=* z@jIy0Eo5o@Zq97ZTny^VFsm~+Fmf=0%3M&37*qy>+QW#_SB&}NH&E->1zZM$+rZ2V zzk$kKXJ!-T07h0uNU00yqvC4QgZhI_iw<7Yk@4x_go1tl?!Bvvn>#nxcI{am7A75L z&<1?)P>2G9188)Rp@m@!!z~6C#vH~~j8aS;Os|+27}8j_u%bdEc2o#90*PQ`VrF6E z;NoRrWn<^y;X_yC0E=CgLE%&*%e>Of&Ug?sl(to0<4C z(|tDfCX(LvjW5FORfLo6J0Y1 z^BC-%#G)L!(-`e54zb#=-^AhW!i-7aSQFejH?A z2ykX#h;n9VIN{82fbj^!2L@LLhF>lW3#Pg>87d~Z zL-ZeTWMJSq#K7R?%)k)m%+RpbnZZHu2m=F?D+9w97Y2_mSB4$ik1`y1=gQ!))Ro~s zvpd5Lh2sn+F#RhW85krEF)+9|GcY7NGcZhcW^hnE!cf5E%CO*#3&V$6SB4jdjxs#> z~1B)92Lyjwh!)rGNQT^i$Dlq*WjtmSYhZq>lof#NXoEaEgof!@|9bwqO z=*p0A(}kfR(3N4wjiU?-Ty6{n=B^A(cik8y&5kofO@P|p;K;z>a)^P!0IFZonSmkX z2*U&hSB8eOE(`~(T^TGMA7wbh=f<$$p9{m2b8ZZ5w#OTi`$5<99bjNcaAaTzImE!A z33dNHCx!#5M;I9XxiCD~@4`@^=E{)q_9(*^AvXqxb{B>>2izELIv;Ov?1SnLaAaV} zIK;r9=*+;7;LN}Hn=b>5OifQ_st_`0B#Iu)u}kz%LgD7lvaD5fW|;4V6b2 zx|X^z$ORr}sOyI6S8!xt=s3i{AneS*5bn&tz~I23(07F4!Fv~mgkBehhRZGtYgmpk z$Vj;{EMPgxpfkgbp)342!~ITo1_p-%3=9H}3=ESFF)(mDL(<@jgA5MSk1!l~>B7)Z z^I{0g8h|3<(R4 zFep5BVQ6r3VR+Et!Z3s97{d-}H-;6xM;UTz+!*dAA7{{N1I6C~28IU?3=FFdF);jc zVqma!W?)!$h@oNS5rzl1T^Jn1T^Jg?T^M)-jxo%Uc4N4(^(aF?vKzy`%;ODWElB#e z9b#a3<;1{X=*+-yyE3tnyvnfb>V z?l-zKFf2I0z;M8Uf#J|028Mf13=GQ73=IDcF*xi#!r*Ymg@M5m6rauvM?{Y??2vS0 zC}2It@W;rF;ZDhM2CjN{hJXLvP%s0-anO~XaLmHNBOoFnqo846;ouR#fW{So%S0p; zG%&zb1|$?TEMS1if_VoR1QZM$K+FjXHXOL{08(zbtYKhi*v-JePz&MPu(0Sbv+FQ{ zrpCY`EX)Ed916@l8cd+AV+^ZTtupZ|Z8tg17H*>IDP(fq?1k}nza7S%9UaCSc1IZJ zv+5XgD6$wo*?ZCGe#J5)MUEz;*_nPuSyz;eSeO`%I!>N3)ahDe*x1lu`0=;5;s0lf zh6)Z0h6kpeHh4UBp#g_NgTc8ZZ-e4oMFS=Q27@@y)B4Vl3-uW}8uVA%cE9x__ zFzEZ6oz_$GTBygs)S#zi?5#J~T~UvLkwLH9@U(8U+d|z2h6ddO2Hv_mT@`ha-F)_p zkI9i9B@<3+s3id98ATy+@=Hlkz<>MD%5ENn-Mu@=pAR2^` zr1<#J)bcSfp_#?Nz)+N#mZmrHyx`Jn8n;;vGBA`UmZg4Pb6K$GBEtdyzS~`Cxg`oH z{KVuEWPVCwNg^_z4GcKAc=*6^ucl{W<6>pTzyk(gAyC5ciDCi)2C!l_4An@&LSj-1 zYI z2}uL(nt=p2L>CJSSR)4q7Z(=~4-X$7pOBD{goK2goScS+hLDgD0|yN7@bCx-2#AP? zNJvP?$jGRusDOM1#!^yJQ0pM(f!qQ%5@Mc|l$3&kf|iyR$UOY^Dkv!E=;-k9@SvE- z0X7V~`{3q5tVUBRCMG5)Cud<{fiMrEj*X2C?q!I#q@<*roSe|rad2?JJuW3BrKF^! zrKJUOA0+DFKE>`h1_n@qU}0fFHU@k6!Oepki6!tD7@%nbt_ay)MBw39hlpGT2B?48 z*dW1zGx*Td;S4@Bb;$lk2|m!Asli{DaD&e*rUoC(B@EbDSy>^Notc}9fq|W!9pqPL z4h~iZ4o)^6Hcn1XHclQkejx@1HhvxkHXcqkHX%+1PEIx+NY{dgO-KkF-V8jPoNW9& zpp#f3`=&r=jDRp`NdPhi-A#`VGeA}nFvHRqBC@Hg; zf#DegDmG?hV3-N=FsMfb3J7KfhBOe(1Q7?xBkN&+ss*_O)Q|Z9y4#A8fuVzufx$mL z9dxxY1H=FS|3UJg{c|AN;Q#;s5>T=KXaHmtXblPIEEmwaPBiKN=<=Y752Owhlb~)Q zHz+?bFff45oCWp2K&yX1i%>vyCg_|^&>4%8pz~iC7#Kk3H-gS&Q3c&M&cMI`y0-+h zmlt%;EaFkE3`V31*E zV8~%+U^vXoz@Wmyz|hCSz`(@Hz|hRfz#zcJz_6N)fgy~YfkB9af#CrM1H)lX28O*{ z3=9{z85sWaFfjP=F)-}nXJGIVWME(yW?*hSfzSBEbL zzBqh8@ZI6tfo~2U4}5g^aNvW(=L4S|J{|bPu!>GSk#?Zyk$I!&k z#xRRv9>XMtX$)5xt}|R@xXf^u;XcDnhT9BB8ICg?WH`)lmf<|ZNruxa{_XhJ@o&bz z7yo|zJMr(vKaGDD|2Y0h{LA=P@z3L5#J?5)cl_`8KjZ(4|3Chp_=OaV+Am?toQV7|a?z%0OAz#PEb($vz{($&(}GO1-+%dD1pEsI)~wXAAc z*RrW)Tg$GNeJuxD4!0a_Io@)z<#fy0mh&waTQ0X;ZMoiZv*mWn-In_-4_O|uJZ5>q z@|5Kn%X5|&EH7DJvAkw^!}6Bp9m{)`4@W+@d_3~e<`2XPli~k?~|M<_qP*Yn|SyNh5 zSd&|mS(93mSQA?lSrb|lSmRscS>sycSYuaXQ)5+QQDatPQe#wOP@`9)Q=?U*QKMF) zQlnI(@JH^C>>rsw(to7>NdA%dBmPJ1kLVwfKf-^6{s{gN_``3*zlYC;Zx62x?;ai- zo;}<)+3-!!E;qhiwj944Vww9o9LlF|0DIcUb1I#IVS)++m)> zoWtzHti#O1{D$ca(;B88rW__8CLJaorZY*R6c{8J1Q zU4EQ@rflzpOv|ceOb;wmGc~U^ zG`+=aYZ~X{X=OCtP|Y8NM=RFG|(SjNA>)O3v7r#R+8!nW@f6p(d_t!J!VfnpD<%&zGjy2`mveZ z?oVcm92m`CFXJ@-I77(%q`Zu|oU59-t+1iF+*BL${A(WOiynuXe?OUQ-Z{OUT4WsEU}5dF$3nC2gvEl^s}?s?A6vZ8`DF3(3xlOw7^fwlvyf%r5oyboEB=??2>Gm!C7R<_^82B#i`SBuI?nu>b0{ilTRuAgmnde%~_8?hU#-e2BrwVC6DRR_matN9Ne zS+URmXce=P!TJI#r*+(0L2Ld@Y3r6IRqMab2G;32tgRDYyIcFd46%;gpJY9!y3o3Q zT7&hjj~&)OuTQjg51MW5n6=beU2B8&qdB{+h0Yzf7C&{xT4CNJYtx92*2|h1Y?j~U zu$gg4(5A^++J-Sm)h0y1z$UTL+UCRtcbliHLTp~tB-vasDYQ9X&|ov|WQUE_+KDy? zRA$>8wOne``fa_<)70HIO_Pt??Cic`vnBeGO{L&Ro8Pevww+r!Z0}DOv}NLxw(V6^ zwcU9~-!|9F+SZ}W-F9M5h;6G?lC9Fa0$VNb2HX0D9kv}!6Ky@d&$5l+Tx$F2(0bb+ zdb@3zBahqW`d+b(k$hx(@ze*~XbT3r@?H)*<6J?z2@j;~)IX}&>8;eaYZtb*J7w)| z=c^ZDXZ|D6u4Q?FUB7CBU2$cH-J|e{c6UzBvio^siCu30dOPVKyX>aQ9k(;)zhWnT z{h^(0%Llt-LJamrsT}r`odoT*H%r-{JD_5}u~grF(;X}OR7Q7u*$=_?zqcmZ|4t}? zj9;_!amq*wiCWm38LB92=@yjNRpw+TrF!_gIfevB#Xflc?&Y7~pT6F>f9>Lt!>7(p zm_Bc2Pj^%6iuKDDZrQwR@9_gC&)vRr_0s5G-Fl07V zFw{1bHxw}hFoZV5H$*XbH~2BQFgP|GZaBtphT&wx4Tif7*BdS|d|>$6@W0^~!|R53 z3{MyyHY{LR+OWQ16~pd^eGFR|Ha1Ldn8q-NVP-=ILvKTSLlZ-DV;f@+V`t+G#<`8t z8z(VtVBFfczi}7i>c(}9OBfe6K45&>_`dNK3A0x;FYZdND>f#xaI41~wKqmNC{aRyJlZ<~F7`CNU~Esxj&?YBpLh+BTXu8Zinm ziZ;qON-=Ub@-eb7GBz?dvoZ59b2dvb%QlNQ3o#oon>O1wTQO@l>oKb^D>f%Er#9y| zXE9ed*D;qc7d8hshcU-6M>cyf`!>5bJ24+%KH7Y~`4sc*=6lRnm@hVeZvMvnhxupo z3+A`Y&zm1HFK%AOyoPyY^A6^{&D)zdF;8Hg+C0B`7ISxVA9D+HV{-#jYg2zy7t`#f zc}!E7CN^zu+QzhpX=l?4rnODWn-(!WZhFS_hUsO~52n9O-YCHq|#(F=aR9F{LmiHYqTvHt9EMFf-9zWs|DVn} zyXS@Oh1xge1#Kn=a@`Fiy;*G6g&nnA&DNwYBkinw)RsZ7Na3K?`MG^U6%GDk3zzXo zi|sxuFZ{ZTamhDt*4hgk9H-8n;H!^zF!Lj1$_5j5ScQ2vZQibkSt%A{>Po( z@4hDFuKjVnAm`5|1?@+vwy&P;kzVPC`RdvYi+)?J6Mgo3)tLi3mRH}cTzo9SY<6Mo z_xUTLw@i|EE1f1KX4J8k`*Tl;?uN$028FFNCv?T?^?FBsU(FHXu$5(;Prv=W zwD0n`&pB|`S+TaMdRl^Wy}uhn(U0hZWq-NbGQ7mxb0!N| zQo#L|GZ*Mxj?E}hQSzh6`K?)Jgc3m=zVOL)HigYqkh zw~yY*?p^v})q<2S#ck?8kIa7dm-FwA&V3Imd()3uHC(v;yX9*4o>@uLYUl0VU^kg( z?f>Z;1vac_<1Ji$U)yL|hS}#uxoKPW+^R0!!5nM0b&JFIjgWFj05pcTios%bA*B34 z@TpY9kZlaaeF*KK0va?12NMCg_wWDz|6%GtV|Bm&|Njr7LFz#BRiJq*(45tQSOx~A zFb0N>2u22maz+M$jZ6#-@0b`G>=+n6X)`c5$TKh~B(X9u++k$^*#qitfy@Ef0W%+F zF9|fP$cE9#T0vqUj4TFagVqy3nfNJW{m5*PE*Kw1gT}By$I*dCYmvl2N?UM3NDdj}lS5T^`0EO=dtfw3wpwHzSPaBwU|>LY510*73&K!&29SDWK8y_# zhpBi z7(lZhj0_&k3=E(-5JrXoX2@(5BSQo;14AcRM*=egLn@e+!OQ^m0V6{LGh`71BSQx> z14A2Fb_O#8LmZg3fSCd8XGVq<%nS^mBMliDHZU_VRDxx9Ff%YTgINcd85nxOtP{)( z3=_eu3+xOGQ^Bko>M0~Y(i&cLt`%mNJ#g6^GVWC-A3U|0thi{M~j z*a&7Na4;}z1G6$X7#Q||Sp^&n;80>@sNjIi?JzPl@Iz+H7#TYF85rJ!)h*y>VE77V z?cir%*binM;Adbs2WFk%XJEJqW?kTCV0a8>-QZ_ncmrlV;Add?0cIHpFfi-{vn&J{ z7(iQz85tY|7#NO%#XJNU7*2y(0RjvRcfqU(0S0i~F)}0wFfhCXi)DajY8XK&XMzX= z11Fd@Lxh2W2h7?a!oVN|X6+DRU=Rhf4u~)?NPt-L(4z^n@*3=FDZ)(a5^1`ROl zg9rnIHkkE8gn>a1%<|}rQQ`3D%ux~W=qyo@@aU{jQE&|NXny11(LF~6lukXnKYDh* z@ag{K(R#bYz@yul!=v>;36Dp&@J2{XzZ9Pz;=+Q0g(S6?W|Ap>juT>#N`*a^S zz67_Z``BwN9}a;hCs0ueNlu_b@G&?*KnKS{ zlG6@c$%#;afYJmg0igs4NRXriH1Gj(l-K|P6%3Hl$pDm%6KGyEMf!A~23zXWeaxf# zs%Q5(%2P}RJjFPGQj7;E#RPzgp$L!WBL(8>kWE(S6y&`nU(b(~s`+U_EIQz^Xwt z8C*T6-FR0RR7-*GdwVMYV&SZt2<9D75dtb#P^xv1AW7xQpvpVP!I6bC??B_}he!7< zkJd||#!VNf=?xBVsw5atr=SAt2GC+8NP-Eca)OcZ=xzhKntxk>iHG%d4}Pa#9^IFr zsRmz~0Tnne!D+@J7+MEIeBONmln|LcI+=|R?0WzI|9_9xll(4+Kxs7kFleRL8$M7< z1GQVhYslV$x!sMR8m*J5yAhOr9XlDi8$o>*$4*A$1BX3YFO{)yKXn3@qEam8R{O8epMB}hW z<3Uj5l{k7d?=%4Q4NJ^HVZ{!%^R>K3^G?t}6GMqW^Ue;?NKYA5h83)!`3GYuyYT_A zNzEtzgK5x__|@P-0d&}YJ(vaR5Y~cOAj?^L9seEX4?kdh;FAZx)}c@Qk&U3<*C&3# zMvz+$!^7baI9ObbZ~Js#1Z5Crmrf~AX~yi@DFJGzF}rq3`nFywWp(Uz`U{HV5{_=L zQJqX6Wt`oe2c#Gn96LEcin$!SyN-aloQ~ax5Bqc<1i1y8D15p*K|$rw-3baP&+daB z+6O(XFO)y@U_NDhz|;DqXJ-dU2AmxET@D@YcC6@R>UOLFr!$ZYp>!r-4@+lAT8$4l z9tRIs9{$8H0QUSR{y4A?j8A^z7XYn9WJu%JKUgIGi9hBbh*iw%0ZQf!pwU5(?h~Ip z_;n6=Fdy{jWHLT*7ja8yh3LbeMuma#e~(Tpkl*=w-T#4F z0N{if4ND3>y)G&i#+ShH>(T9_V$ph_`?v?c%Xx^>PHsp(0M$SX!0FVZyAc!s5GQza zH-ZBa>>hYT?t&=9k@&z?{Xg;jhBPE4ffiY+f_0D`^<4bhI|Ud~;vUqIb?gMkJt!?H zf-*f5s1y+Q=wyOr18$G*PEc9|l?Tw`qV-aV99*pkl3EUrZqEuNg_Lw@K{NUobstC& zTipi|ADW%A%ivfgqDu=cR&jR9KtTWsFO)tANRXuP8u&iQ6>xyyY)c|WNJdJ4T*VzA zpwgq8$-^2n*TYcC>(TAR;bF}RTDeyu>4A`tMUp5L@a*L9=w|#MzyYnLJVD#A7#Mai zIWRPUt1D1@0@P(@VF0zRAuP}cDyT)v!T`E=4#EQ0_K5fZm83-W2|>vclvYvNwID%~ z((0gU*Ion163%ulwqPP=V9$UF%fKE;5L-xr#D``nxD_0F*h;}2pvJ%l{)3Jj{|_AI z=k?^^mv>;`mu~@8_Y5xGjvS8N2ON6?{(s=#7|2oj&av0w|6y>#gcc8oo~`d6e!a5BRp8B{*8e5?VDl_?fD9;6 za_Imwc7eJkAmes`CbKOaIrv>5!w0WfT27WogJfJFMwjxrSnLGpErqniAzcjQp`GR< z3XbuPaq)+l85tNV!7XXfc>Gy#FCmtZfdRs*V`N~c0Ly|C5+dt@BM{HHC@BAdiZzrf z7$itiW*zt{cpErX5LL+56&PXvna2V8&#nsp* zILZV{p`c_7%6_2EIe792BuG-S9aOdP4sc*$tBoPuK;nlGWWa?3D9s6g3IYje8St|E zmq+(a*VY3i4j!;s5T9;vubR!b+cV=ev#aq*pI&goY!|5H^6WkdE)XH5k_BkM89F=R z(d{V!D(-(Uma??|FXd|f!Bpzl{GYMJ2GkYf^XcXU_0>r=we>%LE2!J%V6hWa8}PS) z_SQA;Jix@jz{uYMnnVRnGa5J;eFNDd<7#};_`nAj{*9iXGQN}tRIY;E>#&W{qxCI+ zi#{WyR0o-Roq>U&H=GeH4@xu+7CS&?JAca`(8`<+Fb5>wT*1N2-wK+Xb%2!W{H-88 zFK;n0Ff{*XyMC=;_tdbIgS538y3=Gj=bzO`wy~h|C7~;TUm*HZ+;5rPMP%1NM z@}+I7NCBMtu(gVe4;=1xv;Ykg**JDGAjWz?`HKUTASoKCm*C&t1{&fZsbYy{U|@h$ zEXD^A<805sUVx9*JO^`09D4zcO2LQSAS1Bg0s=Jb1{sHi4ZXmJCs7AtLGwzWaTcV3 zSkOo$H+UEZGUkLdbo;V-=M7M+xP%`n2=3p3hPb*rFEB8G7o;$Bp8y#M9k6m`U|^UJ zPJeKZo&;^a0y8{gzI|V@_lOElNKpuyVOhUZ}Dz`wx)gXs?bWa4i&7)HQR1*?b9cTen3MrvM zdxaU!dVz9cL!~r>OZO#@Zbyw$4&UShPMsnRhdq-ox^#+6@aXpB;NRw=qS3(uo^m#D z>13I3xR-^q`{Kn{#us{7I2#{@hK7b3U+6w{@Fi0(3urA3gYgL{qw%3J2%PA?*nRNe z1D1oYSd1?me8|%Hh@YRI-}qwpq3%-$UvM0J$YFf&;3E#z{|x^bj1L_=AaL0DfUEJz zG*Icx1tu9wnKv*VPHSf}I&c_XaC3p=Au>o3KKvWGKKSr&gh+TG$++-u0BP9(k!b^& zmDVoEzx~kRcERpLhdtT_J-QDaZWH9+cIfbDenB4NSfp*4D7Zrie`~og20*62F z9|S4o=jC+i=5*~o=%RhdvHKvz9d8}G4<0r?V0zyAlH&Jq<37yk7pT#XNS zBwzDQzUgA&EXCgfTA#!pe$u1)7Yl#SK?Vi}zh0BMKmPy!@6~GpY8f#2Xn*(6{_fKo z^vCnyD<+>_e-6+8hdg@oSp1-=mtP)K=P7vi^6-7;kKp0p*Esmukw3!m$7e@=LB}7T z`6IjqJi46~KJ!QTaInB+|A1xxz-2v{k9~GR(#{ByXGD@diX!dE0+M%R@oYXQ09NGL z{ECskwfO)4|321-_hx*mHgx2Zvq96Pj|6Guit-<e;e!Vsj8+%pSoqAa~J(5rO_v)B?_OkSOSpO(z^|gLlT;O5-x180-`e|`m z_c2v=28M=O_WviEPyG0QK>Gv8$sa&|&_3^x{LM%EzelgXK-vU;0mlzsy)2xL&4)Og z_yu?jJbPLAJ(~}3@JF2Dk2t|E==p(PfDtSM6XEn|KB(YheXQoNPdCVU-5`f{*QhA? zCSUTgzQo`Aj{($fJH#J;(o_48NAnLB{+{&=pwWa=uoU2{eF~NcJeyy#`1HDScpiMo z8Fqmd*>xY{cRAGA2x`fJGiicjyko3mOnmI&<~=H)I-S9zbB+pV z8Xwf+i2!BU0DjF96$gIJ8WjWnJ~tLnl->8}{>mTzpI@HAvAgvL8v_Hs#`oqQob`dN z2TFMzJ6pf7F)%=ef*hOoz5$)^QNrceu=fd=rQ_KRSyI*f<9~^~V|OpefXfdY8Nu3m z9XT93!Dcr9kf=N0*gX|wSoa~-|I7>wzWn=3Tnt=Wzg6gZcDtx>IPq`eF}VE7Q4?&2 zV<%W`rM%;DumPa>aBM#J|23ay_Z66>F5Mp-8){Uf8A_C0t+%K+a4;~`NILESox0)C z3ko3yk6!RZ9=JSnZT-gIF@>3d!KHhS3aDMweb%LW3OHOHyN^5auRrg|zy7cz^C{-@ z)+g#jUAr&4XkT^ZUw_?&`MOIdOd*SktMLIx{`JRvlRtUP0IyYawQy09;%`Z11SR(# z6;M0Dv-uwje-G$L5zpSRKOP5PF?sazw1Pb0@&Awyw4?)h8&r&$aCmmJfNLLKevMjq0`vc4iB={Oso_aRFV=O)VvJxD~bC3dAl97SI zw>RvsPxlKSXc+U$gJwk;JiB>rgBov=5Gz5Mm*X>k#BopvZv=(#Myw$W(If@e#%C%fw>d6#^wXy+RE4Z5Py#lsEqGk zqXJq{4qBtA087}u*2ii+y?R9$JPtmB1U|%f5@2_N%2!Zzhu?1=&4*aPp?|2m1zfB+ zY9H!^uv!oB_aFQH|39d#0u^4J@KUQ2QWElOl&Cm>3dR6XCp7}J!WdK@@oUtmfTq3? zB^IniDdmEdGNqD^&3n-bJIBseNNM+a8K?`g0$i;6GBPl%1GDlO85m9mfLhmOj0_AP zz^rP}(LM076{O|P3f+yM!phb7n+JG|rTK?My?g6{QVy5Sji6#`f{W#5P;pnn>0+@N zRN9qjdO(|5pu$e3yBnna@&m?Zkm_DXj?QL~0H~xp>e1~FUh5(2W4YacgMpz`r1`{u z(5RyWXeQqS)TMHaJKS8)!r|C`td7USvK^#~f9iqPY@h`U5Q1+>DR0n{}BGa4U(Isl;VLHD79FIf&g zU@<;%@DWSnLvRn^RQGgHQRxEeA2=R-#c}WfhYP5Oz~R{V2-F#HF}~<{@Q^_BFUC48 zpYHi!r^!1obf3ET)2)|>(KGp=tEEVDIlF83LG6pLnYu4F|6;6v=weYWUCQUt-EP6b zz~GsD!lj#~!=u}uqZ=G7p2-*ZxA6#CAFPqM{7(B|C)i7n5OeH4@Nx|U1A|NFW^e$W z`2P~r>H(QoBI&qe0UHAYmeSlK*`39;`x>Y{YS4P1&cmZSpTnd3gHQKe-|lms-*5YL zA9dv4#?kQeKWK{5U1JxhO@8^&|5M77L80krDZ*Id@A?0LnD#r`n^83=FTpsrD2~szn;HXg>enqx(}sl_Y~l_svp+ z=G`F2GJsv%*$r|ie=9p90|SKJeH`ROsAIt^lp$#zRK2^l9w=q&Yy`Qlc{eDXGn8;P z>;|QC1`-l_H#jh%HM$4?{u(!f*8dgi9^Gyn{M&deF2B+Q=aNpavPzlb;Isg$2Aj|S z2QAaT2Fp_2KN{+!8A=qb_kofos1^sM1uXI2`oB^W>dsP5sLM;mk;4Hg*1+Beg#vut zBxrXEB-lZ_Iv~XcXeAq@*Z>9HTW|z`maHg%iw%e1P;l=^1GRV5{enOIWb1)SZgA5} zBR(z`(m`_Rj!`K9caUmS6pSzVBwyoS|H7sFmnXPK1R7xjHSz;IdyD>fBp-F}WswH8 z_aj`pA3J`(<8kmE6R4S%;0Eh0L2ER}?!(`2c>F)+(yPPJE3y?7felo<=40f z>LZCTI`^_@cqE_T*Ej^KlGsU$xF2$!9&8LCvts z9^l5pb(oX+BSbhnk`G~bg(LH2NDJW=W9d0?1KbDHGXbqJ07nbx6gfx>9JF+R1GN0C zx6+{b#Q&WPpcXLy{sU>BS35RTNHdghx^~Y1CBWA#uH6wVJO0-*Ft}LXaN+mA;c9%z zwe>cC-$@1r2G8ya4%hBeX%ie9Y%IC>+m|sgFhKO@d3HB|%1ftio(|ApO)m>4s8FWO zc+G1L&u#||r*4)GTaZVJl_qqb02gyX-M4&O5AgR*f(_<5?ot6w#dhCx0Z-{VgSw90 zCojGLYk%1RAA!1saYz_<+Os0;CEuzUbI}>fj-P%dgT7 zK4MC9>J*9g{D0Wj`bJF?sPc?H3|d~>3$FAXf z|HB^t4|rN1srle&eWz5{v3WlzWiWv1Byi6OvxfmrdoM$n7#KV{*Qh9fW->arsDN53 z9?72@Z0r<@Bpr9SgVG$PUdM)dX$JmnJa(Sl?i!_>$5}w_DTdelkVP||-R>M69FCoG zF8teA?7)%iX?>wwV<+gu3(!i1v^3aS1+XJ7Klp#L(`AZ-$Nxi~))(qt_hvKtbgoeW z4bM7Szv1tl402oYQOCWYS`pT4_UToz@agnX(dc$jQGlE0$iGcSrSUiDEJ*$qS4IYg z=HCiMa*jJfvHJtGrAx!3`*7=R{+29828IdEH7yPdrCk3{d;CA)(R!f%JxC^ik%7UL ze;bR6NAhQv?vgDIj-a;9ahEON();^)aCn!pId+OLz7}=r6j=cZVz&+!4N&{pz^PMbg(Jv)p8t=4&Zcd|-vl-@xr4IE?QqYoP&czpy^ z)X4{dTEL+81!RLjH3O_w3_AYu47hr02W>(HS8v@4Q+=q*u6@$F2O@a@c2a5aAG+gW_Wr`H71?e1l11+7wo?}Ge!mm?_QRA=qQ9wuZbggO^qY}J{G&yqx>zPOl5o$ z(lh{7EFRq=zd^o8@JQ}af%q*)B?XkF6g-*_GWm47s91P1pY`dEQE~A2eiPKZaR8+x zP;KeMe8{8wpbzsY5A8!9+NV62KX@>|^z3!|5A9o^0c>c4;a0C+?# zg2e#D<^)yFpf&`L257JhBFOOBnLols0z9+^78mdUjhIF7NPKqY7lcVjfQHXJnqM%M zt^zqIpgTv!z|;Cvtt!Yjp3GM~dRe+ayq|*<>16uMPnO<{G?z{%ay93GG=qi` zNOUzw8yl#Bb1x`xe0o`&!2Se z5EX^SLktWKdwQD?fy2{!2^uy>yDy|Ub_#%^r1=07C|dS{BC)$h#l*AuT|;w@iiSe- zaRra&92JWOkLF_ypsWYVub$0^9Xy+lDuA-4XY-o|aE<|IAfL_{6%QX!xwik$|Ns0N zTU0>1(|x+PsDN7Apd!u#Jd(3S#Q{_mb#GBI0Mk7x4?qLSh$0SDU^vFd$3`Cp4WvTK zwkZq@45z@0?LjN~Af+5=sTHUl#=>CA0xNewg$QIdg-2(JiUO#e5DHr#2bzEL?f&7@ z{mP^JCV%U1&|Z|c#@{RpS@`>ofLc4oPdvJRxf*|Ks0P)`>?QIx|4S7ecTC1!b^Q>CHb-ef1DDT?gu~UqG3zdkL<_P8@v1;nCd;YG5`#1ohO7 zPaQlc;MmE-;@NzJ<^M5IUTQwZVqMI_-{boK|9|V%pcV^%%YFt12FD%CvAg^wXsf-e z@ktlUPO$1e&=!4H;{%X+7!BWE2NuWw2OYb882-@FOFj<&%mn4(cfdwN#TQgE&5r z@m!DYZcrdNf>%+13uKREuwrQF9B1hOA8S)B%}~P8-3-$Cnz`GV#nt$mPx1p$1(FFm z>G`c^_jix)_dtmw0Mv-$-{vjdathX667HflcI zvq9n0edyv3w_X+w&*T%XmLlDt5t|d*7hW@UUjUETKm*G&`J;=a$Ocfm_9*rw0?Gm< z;*QLJ){+<=k zvMK^JZjt~}kpXJKgIg`2$%@0EbrF!>H)uEh4R9kOodMQ}0L>>tWMvo`7;b}Q72yq# ztBec`lfX@k=Zp*tF5m`DG`vxg#Kgb=H4ok>v0#QZS?rk^818}1b76+r=F@%0v$I$M zJUtWU81C7dz0#xc4XD+O+^cZq?=G!(1+`Ut+daE~_#}UF+ygqo0W`U{k%fW5vol)2 zw=-J;rLEzae6zE(8I;P>5N&l(!@{>8(ynmt)v*UBWtZ;Qdgopibw}&N-RB(n{m!_8 zh7lh6f{dTQ1a483L0c3ay(W%M{QFqMnjf$=|6t>9{Rk??E_pWpVd3wY&%nUo)y;C< zx7SDIuSYM>j5JWi$@!UIAP_vM${*o!wI4K#dUZ0W8U?WheL!phPw=!VNPaq4el}Qs zI;8jQa&`V^{)mvPi$Rn1S7(3b7x3f(4gG`F%m>ZKf;27%Yh3-AUl5|w<`qkjk zWb2FkJq7>&|M#@M!r!}+5#&yu>mI!xUXu&*wMVZvlTWWVqi=5v*!LZNy*wSC`2~EgP6st}K*8Yn1Kj8VITtii0+Qhu z@C4P+K48~^gGT@~n+qBw2m4zPCNm%6kIOY{z4!S{@k>yf8#F`O3OdmQ;%NS!61Wjq91fk)_{=W=o&k5cIvEy9AgB3&qCpTO$S(kz zFLgnV92Q9AMEG0<#Rg0Y8V?Zn*J?sS49V>v^O4*>8RmA+|A&07ukrV^{r&&HyR;da zR#iYL^n_>gPZs_jd2l`M+wR*N_}4S}=sr-R#G{wT5tQ^n4U?&$=!7;*T!?769fLO9 zrcO?C=>++X9KF-iTsj>|Y`DQKo(<9rN){yA4ARC1N+%x82RS^gFF_h^-KEW*))#AI zJi1-m9l`Bc&*ZZn%%^;MSv=6gr`tzGq46NN;ns*~xbe201UK9q`8^JIpGb4;nLqOQXGi|HL;O)6_%)7!%US;Llb`sbPJpId9r*=)?EZj7 zK|0Pm@`oSbj|NGDbe;$Ii0nZ6Pknaek37jA^%1KfktaX%3;ILM0WE^*t>*yQ;MjbG z#j*J~6Id@u#ShQsw~YL)@&Et-cj4CvQBeSmR~CR`1vEM4)4c>dy$Nb8g9d?GztzQn z{B(9dXp4d?fAmSnbbK0r-f<+a9mMW6enD=qzd&B{=#2(B=s@#vme2fx&VN9Iv86je zb94!y8My!#ehn8D2N!+~AMos41$gun6!8ndjcw45tQzp(0_cJePp}wh%HamM2HwQL zz;GYTddR@Q01^8I7b|CkiGdcBLezP5AMxmnknrfNfNbX~=>BPZ`{I9}?oS}ErSTUW zU;y=^^FRekXMq5}X2A=7&59c?{F)9YT=+FTb~OKI;%@?NA4ucR2d5~H&I}G0e$9Xt z-N!*kAwieI{QG}^Ujy9X@BZ$>uK`{W@R`5vfD69{XqJaT`@?4!{$~d~yWgep=biq{ zUw1r>zu+W88vnl&pZT91OyiF@mgdMGcOZ>F62yo+2oBIRP}%{dg@b91{E>&?V$Fw{ zKJg3sGI%r}<*>e2vj!5wpurr_fTQ(&{$9|jw4b0u5b%@_Vt`%dk$gCfKkxr%{%8L^ zdrjJqS_^6Xd51sqKRfV=KjPRY{;tAJ-WAm4@`o@y-#n9N`Ys0w1Q`MwT5T+W5>9|ehdr@ zs^GZKV_;y21G7M0a8{B>|D0CmkEEKrZH5}aB#Fr^|<9|eWE10`5#k#nq%`1w)%wTe@vj3>ueFM zvxJ~F6KID)_Zo=zy0?H&xN_}2*O{ZD;M;w?`#Nj}3Y;09fkrS2KudQ(1z&tz>|u{& zh*s7z z_i9DEuX*zCKjNr;*`xb+_o=kct37&Uf+0(|KqYd>zyJS1vr!os$pEyN{v^1!02-kd z0jFHhvS!FoC+OIoi{MlbI*$%gIfBwK;-oMI7trD_C5#jOwuy1b}i--07nu{+%)wNGDq^dE# zZGDd4=c7mRyVh@YNubdk(6q8=_jjM}Enw5S@A`rkZR&v2v}f`Gk6s%FP=gMsbn@&L zF>~!c;?d3L$$Zs=`8>qN-ZBo4=C_Qc4?znb3|zV)dB!98wukloS~bt)1D@SqJea@v z^qPQ{>)iM0KIX}M4t4~>|HB}sK8Hj`caBO0te65H-xlu}2VU+5KE(}G3$QSNwl`b= z#{($f&VyNYjIc%uG)785C*RemD8RC;NB2+9l)`x2yz&WuA zUMvyiX{SyechCO^e5?=GfKTJ&01X2OfKr6QVbF{PB*TC=GJz{($K9aA){Rg4_6D&y zg4VE0fNJ*!M}Fti%m<857+-4q2|9$sk>B|;XoB^m@r6cEO9QN+mqp0yKmOPf(@C-xC2k)&{f^ z@))>maOpnCzwMCc!50!f#s>~Qwt90^96UPb zf|>{(om(O2c+glt%z#LxzN6&Mcn2{*0dd>rZ$be8}w4>&Wk6eXyL%V+Ux>GU)6dkLCv-z~zWbcPNL4_5)DE z#KAZDfbmJ6&R7ZWUXjDTtq1B9oO)UQf;tpF$tR#?VW-GxPv(mrpgD#UH6MMtw}Kkh z#{YejU%9qksx$RTP8IO!t~GFKurX&S74q!n`S06(#Pi?-CV0_v+|&AR&3&KlR0WXT zF5R^(F5R&V9^J=0I$bp!n}0L$w;Hi9FgP~FaB(Pz}V@_(CI1wnpOf2^1veY11Mr4>yA8_H-n-Vw7AEs^*||i zCwO5Rc)|&~PKKNnJ}%v0V;s8=fLHB$@b3?CGibe3p$T2%Bj9-X1!R$r3&cW{RX(TC zR{2~2ukzvb=xzgz8G0n2>K5r}{`H@~C5#octinY_z^B_+!lgTw#UuHWXE&!uH_HXj zP8Pn`EFf{?ZyuJV8vK2rjp2^gzxjJ-fqRjRuAue9j@=jd*I)PS=3#SW7E$5f#$xE% z%X1vG&QZdLe?O0hYwO7hFHkLP;nC~L;M47D06HTs^CBn!K~;-=`9k-h<_C-ye|3g}Vg}?skN?O1ANQ~>)!^@m z1&wYsAL9Tyvq$&e|Nk#RcL?(Ly+Dre+n`x+<8Nsb96=u7Uw_<@nMGx$X``+5De_zlZD3|Nk!wm>3w0FL`u^ zDnJ@fu=sK{KH$>n%Hay??wWwqob>4Q74T@i#NVn0nyC2Z+xnKjPa4!H>8=$>o8Z)7 zW5&SW`WzG{&=UR!C>_HJg597b3@Qj9tNg$@n7V1g9a;vsgVy;GQwG5MS=|>vD_u%> zp({m^%77q7Xqxco_LcBRzU0x(^ATDA@PpRATYu&6VFHbrbh~nRcAp28$sXOI3XpQy zRRgq=7c^RQ88mxmeVo4+w9&@+KRDiWP@3Y|&0_A+ z%>&wiz+&jp%X0`^W_a-L7x8GlRAJ}X?W4ls+I_;Oo6UuP{Yj6^OQ4W;W#&2~FSTISL1D*)<@bcZT*cb_P%Dzx+K_VDHqjcyzmJ zfYdntKLGNQ=l^3MUxCtukM#%s9$irD3sQ-J#u7nFK=PnO;%a@Izvt3lP`?FS$$9)g z1WI2X&A&MKdzOGkXnLY(G6*8bVHgNy{Q49ZZD`+9eY?0 zbk{1lPp`|!z;G4ZmYxf5OSdsGFhJH(eqn+wzuw5qz_1Cdu8ajH`-BB1yNDGg+re2kh!87@QWB1F8f5E$=dRZJmXAHUWZ*yT_0u_q_ zf@#fr96=Md45jk;Hpe)`9tN$WsBi?Yu>o}jEI@lnJ9AVdJbFQ6Tns*$FKpNt7##Wc zGx{*E1W!+QmVhdx5|si_8Is}3zip}ss1D~}f5FwV)dI9vrY{P-+N(sxz_WL%J7`r( zXN`&mX!jNeXedAdw2&kMI(pB)jmwdLJBw#p>jD0bSD><=fBPO^P^x#~-~Ygoe_Jcm z^an1TH7WtF*7x~aKnt`SyU)0GpLf(g<*I$U`=G1wftlSOx(^zE(*88l@%zv28~+cq zp6spu&+qx8`|wQTL)wQN`PU!R{@`L=qmtmr@Auxt`YwNOA}DhuUxv0w9r?GN@=5*+ z+5phu*y{}H_D8Zz^GUt|9_pOt3mT@<0_o^J?x}qkvYiWbfVpRHFq3EZdr$4xp1mT| zJeuFKcpQAofe9$)dEjM<_iN6(bl0c^fKwP~f1FFVi%JG~VFu{Lo$f23eEt6r zI2D0YgsU|q`SiR1=iC|<4p<`WP5~7pH7XjQK9T`weXImH{y?h;yRSNGUvB=zQRL^D ze8@BT^k;qno@V}ICm9+aK{x)LYCiSj|Do=q+J`)nFF5iGu=F`LALihXJm|r*w?J(Dl{Bp>y(zEE50*?j=CeyI5eON}eYlI|0rwM5M?Sv(HD zWcti6Akysd|DZ>&6BGZjOAMa9ENvdWB3-cbE#UCOqt`)zKk^X2pu-Cf(2ly-AXDW% zlTU%%+6|f=bmWg@nFdK_plJ*13pFo6p)UZ*m)$Wc7NFjJ>|xNY1)wz}EDYhG%^aW` z%^A+YSzb&G3=r80IO_wP1q zC;r}0P<7myqN3s0>&D2x?KEf|w}DSD=ss!&pX3w1$&Wy()1?=*iYk(2J2<&)_q9I5 z-wPUD@a#U~seKTVvp_p>eY?MS_J(u#YJc?V73uP9e#hc@@Ew!q|6~5WJR4n@kGfhP z2IZ{7-N#-tbsz8Lk#J!?%D)Y?_ff#5l>wBle}Hnz50E-eSM9?ty&`^Z!9#>oYayLE~;3p!5ztM+S0m38*%Tk2_q;0E_Ij zgC{xow;w#rf3o}FVGsV39v}+Wfiw!nx4|bde{kvk)cvygr$U*e=# zkmqj)uR?;X&h732t(Ro*U_SN#VRu{&|N8ibZnp~l_3kaqH@e+Q_}9C4fEWe*>)m@m zj2!;;?h`(j2Qm)?h8PS2>$i%OF)be{`KxFK#TzX z_3mpx3?KgW?i)Z15B~M;TR;pK{`KxVKnw@|_3nE>3>*IS?gu~&3;y-)M?ee{{`KxB zKnw%^_3mdt3?2UU?iWA|4gU4+S3nFE{`KxRKnw-`_3n2-3>p6Q?hil=3I6r&Pe2S2 z{`KxJKnwx?_3m##3?BaV?jJx54*vD-Uzl%n`?2t^_y56sv)lg<|9U>=o1m2)7LMKL zKqVGrgVW{Tp!4>j=j=Mh9R`g7fI8>{hC#K@eek&ppey3-z=Ji846tP%prZ#x!9%*B z#VBIp3=GT+OBfj#%)qP_j0_B>VAdK&1_l!_YXhiN4rXm(WMD7?vp@xyA(*v?k%7Sg z%sRlxz@QIi9bsf(&;zqhFfuUcf>~!685nfHtP6|`4BBAU6-EXIEiel-prZ+9-C<;4 z&;YX@FfuTxgIP})85q>SEKrwH70i0W$iSciW_@5}U{D6LzA!Q{D1li&7#SE8!K^=^ z1vFq5=msx&FpGtWfk6(;;$UK6kOi}NK-b)ZSprN94ANkh2onQ?6qqH!#K0g4X2~!y zFi3z|3QPDm3G`&IWxXl87}o*7+@FZH@X^5c8ZObcdy^hpk{ z@kn-V@Zn#7!6Vtf!Xw$a#e;wS2_NQ*9?AYC9?8xf2yTH#vU3lDo8yt}JORPY@JM!^ zg5aijBs4g3IHP z?EC@2;|7f z9zXCU3FtZ}NJ#>^rU_D#SinmX&@?HeBnf3;U@!;gga{@EhHx+|hKYe849rSkVqgdb zvr?EC7(&3T3?>GKU@$9(iGd*q%mNJ<1cF&5ObiSGU>2ys;}2%lFflOrfmsbq3=F@;0t*b#tx?Lg9l$oG(P!V@;Ut`mJBp@DqdT0j!w)iN}`cseN zE-D&~Ow25x-43j5>>O#wT~rL1n2x!q7%?^c=Bkr)+_Cg6)~1UG|GvZg+e%n8__w*6 zSbr$tXg=}(G0_72Q`4e&IgTRfU2PXhpq3`oZ{cs9>d7M@a={QdzlRXwhK^|pcBgxqobUx zAP3F|8Nt8JS?Al073`q7d7UR9Verfjw08xzz@c zje4bn+Sc8ZL3e5SKzir=+b4tW*z)W?<#F((1^@O#X$N0$@NYZdVSFO(;3E#t?n532 zUl_RZZ*Kvec+bGU?L?Z3@gYb4?FZ5v4?f^PsD`N2aOB@U1+LQ3_yX9(M;Pi9h%gau zR2sTDCm@zdK->v83X992Dh1#wd%%uMb2UEU$iMwyn&ZKTP`5!xfQGJ=qd$>Rtfe$0W%%>x1GQq zKG4WG-V93gpul|zYG3+vcY~tC1sWBO2VXIJUVaH$3jtR5|3vc-cK-Ha|NyTspxe7610lpzH)L1b2Z}fFnkLOWAjv z{{R2~YwmI5zFZSjXF?Wff|hVXnlqp|=Ol2)2($(XvaaO`0|UctAqEB(24O}9hV5XM zC1{lvxGM$Pxx)w60ooP{(E&=BkOt5qMg|7Z!cArdP&Ws{0(C|pEO927tUa8S31>}) zv$n!nADI{!ASVB0VqiEe1?r?JF~dypfwMqm8pICJ4V4hqDY)zxI7^NNrq>D1>S2M| zF@=SJ;R@J}{czbgaF!%1Os^Z9RSah>fV0lRSzq8R1vUnTZQzhhW`m7%7qBrfTn5`# z!p6XG3CyZsV_>)lX4SAUFkAq$8rT>Z&VyMkYzz$Nz^o2728Od>Ru3Bk!x=DZ0viLv z9WZMO8w0~_Flz=I1H&yaYYrO&!%Z*?bhgF~Flz}L1H*MNYX!(vVAdKo28OF(7UVEn zk8b$=Jl2Pij+q0U^{)_tNQ{tYrqb{1x-qWQak88T7&q*t^Z4S zJ(68O!=2|nx_^IO4eCHMASnRdiL(V^ZfA&!0BB7h=;SQW%8BMT4zQ)NuEzge`M14v z?0(UG$fx_GWA}f@?ys&bCrivcyYKMJGx+pE&d)+z=+=6mB&Qo9`2Tn(vq$q`7VCdS z*J1ZYLVBE_5qtwj{%vPny3ctWe8}R`eZYhH6zIB29~BMH?sK4FdRXhnk$>uON6=9d zkUKO%W0;`PE6`jDXc?jLf!9w!odbw}K(|eTT1hMnpyfoD!F~iC!siItp4RvV)IdUR zVzz*{Zi7~1fij)ZZAboXKV7?jxPW%AeFS+_djcq^T^vE{86j8Jf!c4NMX=p2DlYun zPX9aX*vZo8V0FLf8c3xB_^@CP==F9WwV)+6pl$1*{dfG^a#S1~8vgS0w>$$Kwfpa= zN9(szNyi<0Pq8-TKqk8E0xd{GHgPp*kxq?@3j==-X#Nef*~LdC0I7v+blaije~G#S z|F*Yj6Zp3s1{o93ebuA;oNq5n11Jm_S`U<@|2qtF_I>_+E-Egdjkp1z7OjD6_fgPU zY8ntPyYO#255G6m_+;w={;8m25WCl?fc)dp-2)Clc;VW31Y|lSg5u-Ad#7(3A9#Hp zbR!gGmAelE0|O+oK+9YqMIUIDJEX7#ouViLF8V+-t&pM*G~Ec%0lH!qvMwI9%oQT$ z&d9(3iRcVQShfde7f>5r0MrGL0Br?F*`#OyKEVHnXZKA=M7LhzZ{cTRVAu<4HM{hd z|M$>-57Mpx9e{^M(q`=K?biRG7~G0oviU!FbaWebS*XEn*kvIGcVL%<8r+FPwob>P zQ~){A1cDbxxaojSD`?Sa9 zx8UB2)@hILS3Q*1fgN=DEwbA|$FTZz_km@aPyKgcEYkrGi^ix_cwBx3*6pZu&g1(f zPvwgqmtQ${pS%2&`zlyTj*0>(LOm}(>^|hv2{xknze2G{_XUuuSKa5EA2EKv^#9Q1 zN8E>;I$3r*{y*+&{g=Oq6Lx9iK-CXu2poLgH&Pps%cJ>7gkub-00PYzJGk&~+XG&1>)7z0sYuZH z0BBzbsJ4VFDpB!(8wNV<-LKbVIl@5xmgS5L44`?a02lskkmKMS8~$>Xu^S(F*$x^> z?OX$1vu=C|G_TA6s@g6;?LPMXYiA4C8;;gT_?tj$2SE#ML5n21e>iIYNrRsS;%ao# zr313Q*tO+tEmL#628_0C7RvGeS1yXn*TDEi2pz9+VYma<=Vgh z|DoyPdAgqhvF5v=)zDM&BkHZKSXdx}cBG8B`#3CmKm_;t274)FPxWHTZ8{dFzYCik_ zxQhxXI2bx}RD2xyw|xXRi#~ymb3O?w@*`hh&DNlt3tC$T&ACz7RW9J`NqpYrKk0zNtM z^3$DvK%3c>4}wnP0fo-@tDRH8W_GRtAMOI3KL%|V0!?pw^zyWKx2S-E$dUONsJ#OU z#*F443jA&V)EF2%tbg)1vv@Nwxb!A5f>Lz=BD4B*cY|u8eUNG}SjSQO3pAce*gt#n z?>+b$bdzH#n`5u1!)tDkz)n!VQ2Rvp!R8+h#XOxZDlV?Z|6dj}GcY)UO>x!!DG-%7M%kca5=5AY7?8kkB6KW*2os?L5w85(dmwHkX50><(h)R( zbO;m{kTFW+aXW|##JC-(7WC-`kJs%0jn`=(1dj%RN^HmO9?0el=Chy?x z0F9S=7@q*gzyr_-9n{mt7eI?W4!$tp-v*HcMGt5^6{kuKqE#vop%QEj$j%cGcR)iM zER5<-un>|;XlR3l@vDT6`QTOQ2pOKjq9&~qJm&1uxf_(Q{knZ_8-Nx|dGeq1=>(6` zxprUgbk=w+ja>C{1%t*^!C4P9!tG)Sp1vTM(3SZLs8mgOSp+)GSo@mi<(ICYUhgNzUZ?-fKjc8` z9Ckm)TI`XNX+677fr9AZOIW6bjB0}i+rcT6sKIu)O7!dtN^%er!GrB^b=ZgRK?B}k zB}5FiL)2lK11*D~$!IjwLJNWsl4)JKuYoeH9CD@&^#?8U4%UIznJ&!Nz`Zrk?u!mu z=R7XI^5{P2$bGFdL?yxV@(AHKJ7l>aqtDSAjDDwf`yQ3-T|mjcX^}Kr^MzoXnpEHLVX%B zTAzZZ)nSw1(2@n(EF+>|fhMET`gF8DW%VGkK8>KfKIL%f{_!#gRGsbzb#+~}e?b-NBgH9#wTczERmTSq&l(DoQ|rx^DtFi3Y8F`6`*TL+@`i#NAo>zjgl zqM(r^{C!iG?pL0dA40}yp?ynRs>C<&i>eae7!ph+IDsH_DxqNy7KT)pxKu*Jo(PrDf)TGu%z6t*@1fcy>cp3P6W_U$%kH2!W3HK6LFSb;OtW zEbQ5R3U&SfG(!OyrUeiDf`f{vfnT^v^sI+A@C#Rmne{*_L3JEh2@wOo5OtX5K#LD( zG8)ao2T`*dWFBS&WnnBM!QPfAJy%!6NHA#W&gEC0ppoD!pm~`d@VqT(r?-RS<>$~k z*QMJ>#l`XR1INn`FFyl~41;ds1Fc4K>^`LIqLSdkeFDOYaN$1S*nP-R`KSx`50KCy z?hlUL2b&)-I$nI~(p}Qx;n;lu%zNQ#{L!U51tf4BEb!jd_yOo7HP9*=m+qVvkM3ix z#_un_Ykt7!(p}Ty;bQ#HvH6EWnWEzk92@Llx{WV6_7?na{wL4h{t;BsKu%Y8?C#kJ zn!X0tC?3h7H6F?C4Ibc~cD@xJ$?o8tb{?RecD^MZ$?o8tb`WlXN3uJ3ryYcwk?aoMX$Rp(cqF@nciKU?As)%@ z;GK35Zh%L!J9wuZgzMvx><->(2jO~nB)fxm+CjK39?9<->(2jOyfB)fxm+CjK19?9;YopvX{T>n2F$*v5beRv+s7hSr0AZrV{PkLPb<;Z-k zQyiRfGzq30m`>wM(4!r|iRdLG1A}9C59km?22bX5{~vlJhqrhnyVgKbkAH_pvTFs@ zZjWUD9*<<#5(IaGN3v@Hf;+_{*)<2jo#Bz}nt|ZX@kn+}L2ws%B)cXcxJx{eU1Jd3 z6&}g15eV)Yk7U;n1b2f+vTFc>yTv2f)d#`d;gRg>f#B}(NOpBWa1VGSyE-7aM?8{U zZ4lfO9?7m22<{n=WLFae_ku^Vs{w+0#Ut5O2f@AJk?g90;NI~_c2z-eA9y6YDj>K| zJd#~y5Zo6Y$*vLz?i-I}R}lpFgGaKf0D}9)BiWS)!TsTp?8<@Qf|3ghoXd}tcpRaL z=1ONc3(2VlXL1205m<5or4OX!0!kl9$pw@?kdg~1eIO+lQ2IbhE}-;*lw3gR11Y(H z(g#v<0i_S5sXbfYJw2asj0eq~roh zA4tgsls=G>3n+acB^OZoKuRv4^nsLIKnNiI0rbn;H#b>#S*A+(FN5+$f*T<>7gfRm5WDmI4Fg{ zQVS?~Af*;i@<2*0pyYv+T0qGIDYbx-2U2PQB@d+30!ki8sRfigkWvdMc_5`0Q1U=Z zEuiFqlv+T^11YtDk_S?10VNNl)B;K#NT~&sJdjcgD0v{I7Etm)N-dz|fs|T6$pb01 zfRYDNY5^q=q|^dR9!RMLlsu493n+OYr4~@~KuRs3l1{rq2e=D}aq~MH>wva*T3xh924#2ld||BaryUKOia);~%4Al!I=J65GIs zs18{JA1IUb^N@x=x^XUgi+EW&I=28Ci-C;JKu0v7LujBOR!9yaX6yr^k~S+n9IQRnJFeD^^poezgok_r@ z5*pe_^8>h4LYpsmRbtL0z)T}|CgF$*B^4@U6*+MgYQboQIuusDjaH~bqC(|SB(g#+ zpwhxO&_U{su!V0Z)AgerRM4~`Xp#?77>_n|32yTOS8~I$J}r?YvpyYN@`ihoA9+Xz z^#CumhCJl=y`+7-KV| zYu`Xe7*2t#Y{NJ93sHd>`yE~T20O_Q`z9^ODkscdFlLVxQzd4P6{d#V9;=WTSvBnF z+BeXkEjaM;SF~Uyqif$7(qNS-zHH;Is@ko$k|D3veK$lrV zZ+@{8g*prKhi-OA=tD#OYiBtN z$-xdiXb6({L03IFclRKc)^wM5K!P0<+TdUZg*G_YL7@!}c2H=8gB=vw;9v)ZHaOTp zp$!gpP-ugL9TeK&UHU4t7vzgM%Fu+TdUZ zg*G_YL7@!}c2H=8gB=vw;9v)ZHaOTpp$!gpP-ugL9TeK&Um+lmmgqL!6{{MIB-UB|? zvm0`RmTUL>9iZd2T$Eq9TEFLSZs7)fN1k^pp>5NL%E z!9#jL=VF1jNHq}bFLGnsD-{7+V+}bE3%Y6++?K$xR|<4oEOC3KAS%(fS3t*LkoHPJ z)ZttjfV5W%q7sLRNTWIsbvR9gHh4f|un?8RjKM-w;_E0vR1n!w1YP|DT9gdBkrA>e znSVRp)3KnYLpSl`Yh|LU#MjD1Rf(^a2~!D98F+_cajAp`I@kcn{tjF!Fx!8akajJ*eP`ad_dL(=uW9C;IpxQj_#C#4JClC z$w11$kPZkj85p8+bh9G^q6dmEA47r*b8rS4_DH!CmrBfhj7ueEKE_munU7&=h|0%U zdW(wSyRhMFhp_h+uY-0|z3)D+?;}K?iIycru>=?L!&e#RT8P z;XBNBF+n#m!FDjc%mrP}_stQSXmE@GIQBx53{uY%<;KU+em|amKQzceeg1Eq=8pf5 zdNdzraj{+lZjKl8fj33Ioc-nhf8ztMS;6-@fUasd0XqD-8*&D+2kLpbhtohOLWA~t zjN;J{7!85Z5Eu=C(GVC70TM#M4t~*wEBx{RFZlHUDR5cP#p*8L>j4Vjvg;Ta7>ZU}n&PU(6K-XVt-3pu0jL>Mp^>zQ9?aYrFcu zcYAunuj2yk>VfE(4;MQLXMKjVWZ{=^xx-o6aMpYl1_sDgA3IqX7(lLPVK8N7V1QhV zV8hD5;0X4)BP;Cg=V|bZxemZtFW@Y0HrPd9#&A|FoYf9zt%S3#voSDC2m6hI9i~?c z&I*CE>fo#uaMmR_>kpizzyUKQgahWcc^nYGu`+0J!e!yC8aQhioOKS)`T=Jtal!Pu zz*$9H3=9jvuAdDTI|^sLhqENPVS1h6tXw#2I-Io|&U()cb3F$S%=N82u$bxLfyK;3 z9$3sgBzzbtpz**67Rx_Nn7S6g3XZ?h;)cF_~R)YN&zy~w03eH*rXPtwye!y8u z{4gCpa8?1FwS*t$w|D$7zf}pqbS#0h&cIpU;4C>om^u$Qs|e1T0%si(WMJ3?cKvI( zn5Ym;uRWZV31>})v-ZMS&*3a?VVGVgVVLWqg<-BgCk%_3tHQ9Dxg`vX8GR9$-bgsB z5zblyXPtqwzQI`vqAcClHa8{xW%#?Px*h)C-8eI0C34}vaxkA;%ftNZB+tMAxsBUh9_HUvd6-Lcp(;$>GgX+n z@2W79In)>!EWtWlHDK{}Mg!){Z*Z2JCQQr&&MJVjX2DrU;H+nGmWUPuLloE!TP>I= z>2TIWIBPeY^%TzH*M{k^gtKDdtSQ>CuwSbUbG?=h%;$zWuvBNJ1M_*e4$Qo*aMoQo zi$NDAs{v;P!C5tM);u`tj4lI1GT3il;bQW7FjG9?tYSE8E}V57&iV*viR#1j`sl;_ zHccPqH(3LitUH{Q4`!}gU_5X}uuCF$R#Y~eiEN0q`VKH;Z7-k2X35=x&X9dDpm2lQ#IO{B& z^&ZZWHDzEZ1^dm-6lPuloHYy1Is#|CgR>;eU^<-OtTZ@lt{Kd4&&^iIO`vrWoiX; zy`L4#^#`qBF>}HS7Bgq9U@@a)4YSt=&MJYkro&nL;jEW%maq*>uPL0BY{S6N4)$9Q zTx=Vh^#IP|vW4k2fwSV^tPVJ94V?AZ7UnlyJDA@V*ul!WgLW|UUc*_!_AoJ9I4c#- z>W8yt*~3gZ02g}%XYo70WW^mA7$$)I=HLJ`ISbC30%w6v@qqL&UckkK9AP?a;H(5V zYq}%MZyOw8ep7aW#haEBEZ+2;VDXmh1anol6U^kTaMoQoi@_Nts{v;P!CA4+FjJc0 zVoTwyOK{m+&I}AQ!9HhpftjZZXNALAjd0d#IO{5$^&iesc7>S|!cF zxp3B0IBP$g^%BnF_JirQ@PnD`;K#tA2DUd7E;|{{+6!krhqDCzVWwEaS*dVVC!BS_ z9~K`s{b4=|41k3}L;x%$#s@4oYeqlbqB&s*$5ZA z3}-O}!DKmt7#Q@yJ~s@4nH&vgwZd8J;jEi*7IQF6hc28I2xoN!!+g#e0`spx2rSM; zLty^Z4uQGFID~=00_+yMFqjV4FqjUnFqn?GFqn?iFb0N5u#N}e3=Gr2t-dGW3=C7j ztQX-73{$|YH{lEnlfkSH;S3Cuz^pIf3=9*&tRLYF3=_btKj91v{a_YD1Ovl-FpDLE zfngq)#Sy{4Fc-|?iC|!u17-l_42!{H77+{#i@+?K2nL3QV3tD! z1H(oz%O!$=VFQ@u5y8N)9?bHIU|?7WW(7nrFsuc$LLwL#)__?N5ey8g!K|1F28LB& zRzd^=!!9r@C4zxrCzzEH!N9Ns%*u&iVAu|36+|#FYy-1OA{ZF9f>{+23=CVqteOZ0 zhRtABLj(iEVKA#Df`Q=>nAH)%z;F=E>WN@rH~?l%h+tsY4`xk?U|`q>X3dCTVAu<0 z&52-O*aK!Qh+tqi17URzM^J!!0l?B$9#QCYTiw$-r;}%!-L*V7LxuB}6hXfO_!E3@MQe3}WEAA|sN4 zK@`l&iDY090kaAs85o4Ytdd9u1|cx3B9ehY5X`EHWMB{gvl=2982G`gmPiH$J}|2z zl7WF2%<73`VBi6>CPXqYaD!P>A{iLCz^oaO3=EuL)|^NN1`aT5K_mkMJD9a3l7WE@ z%vur2z`zP-t%+n{U;(o>L^3cigIQZ585o$rtR0aI42)pbo=64;1~BVDBm={Lu+NV~ zGBErDvra@ZF#H9x&O|aW`~kBrL^3e^2D7e2GBErCvu;E(F#H6w?nE*$`~b5aL^3c~ zfK7f9$-rO^X1$1HU@!x--b6Amn1Wd!A{iJ=z^pHk3=GC#){jUA1|u-*Pb33_A(+Jw z#lT98nAmdSDh$6a#}Um?aR!z@P(Wi9|6lXoFc2Q49=PV3tf21A``* zr4YrypaEv7L@_X^gIO9;3=C>umQEA{gDRM15XHct0%n;+F)%2DSr$h9#ISoa$uHE6a#}Sm=zGkz#s!=g+wthNP}4sQ49=HU{*{N z1A`=(l@P_i5D8|bL@_W#fLR$)3=H95R!$THLl~G<5XHa{3TBl=F))OHSrt(X48dSl zO%ww|5SY~v#lR2q-;@ zgAJH3=HvL)`utthBz?m zOB4e`ESU8pih&^p%=#0>zz_{)F+?*kWP@2O(F_b(U=~L-0|RJ8kC}ldnt>q$EG7`m zz>p4Ri9|Cnq=8uy(F_c!V3tfY149a!r4Y@)Py%MDL^CiHgIOBU3=BnJmQFMSLm`-D z5Y50)0A`s)Gce?XSr*X@40&LdO*8{TE|}#I&A?C#X1PQ&Fw}rq9?=X8)nJxSGy_8w zm=zGsz)%Thg+wzjRDf9#(F_dbU{*{t149{@l@QIq&<19uL^Ck7f>{~S3=Az`R!%en zLo=9F5Y52Q1ZI^)GcYuQSryR?3=LpbO*8{TJ($%H&A`wPX0=2!F!X_09nlO7yP zGy_8qm^C4qfuS4Bni9>x&;@4Ah-P5u1heKuGca_3Sqq{W7-oQ3OQIPVrh{24q8S*b zfmv&!85pL5SsS7m7^Z+(TcQ~lK%=hA3_GG37$$+m_Czx|0d3=A8ez_13)vWa0} zSPf=5#4s?d0<&CV7#LQ9SspPA3@gAapBM&)xC3T2#4s@22D4gX7#MDWSsgJ93^&27o)`v(8(`Li7zT#xVAhlv28L^3 z){GbihO1!KoEQd%D`3`w7zT#RVAhft28K&u)`}PghKpdGBA7s zvvgt^7(Rko2C)nbAHXb=SO$joV3tKJ1H(Ho%O;kA;Vqcu5X->u2F!AaWng#>W_iRi zFuVe@d}0|GUV>Qxu?!3^z^ss128QQgRzxfV18ByDnIR^Yf#E4wEFqSGK@}V>DX|O; zDqvPdECYiwn3WUDz@P+X6~r|xG z3=A@0R!1xY187!IxB@PJt-Vi_2?!K^c}3=CXg)`eIG22L>RN-P5d2bgsumVtpC%(@fH zz`zD(J&0vs&;zrc#4<4Gf>|$O85nfHtT(X?4BBAUhgb#%Eimg#ECYiknDrx;fk6Yz z`V-5*pblm+#4#|KgIO$b3=C#q7DpTdgDIHB6UV?{0%i%sF)$c|St4-^3`SsvQy zA($l-$G~6!W+}umFhqb^Dsc=9;b4|V90Nlbn57fPzz_;%8N@L#gn(HlaSRN>V3tK3 z149s)WfRB15C~>D#4#`gfLSha3=IBYmPZ@|gCCgX6UV^d3uXnxF);XmSs`%@4BlW? zL>vQy7nl_j$H3qTW+lWiFnEAjDRB%8?qF6%90P+Jn3WU9z~BmI6~r+xfaU|48A{?9 z7@Wak6>$siXv*yGxFj#_F3*s0UD#5HJaSRLK&_QWwT6oFX>;ushT!K@>33=9Qe)`>Uo!IJ&0pq$ON;V#4#{rfLSl%7#PyQtT%BC z3~6B2hd2g?R50sH90NlNnDrx$fgu^p`V+^%kOXEi#4|7?f>|u_3=9ci7Dqe-Lp+$p z6VJd92WAPxGcd%0St9Wa3^8DqL_7mSG?*n5&%h7`W`Q=5Nd$+5JBB!hdo;f>aBM#F z|G0}v0%)0zOJ|BoiX;EFpN`!hJi2dsw4N-LblhS70_#2O9?g4HW`J~o)L39w7r86`i;M3IXh^VTLvTK>MvLAuO7YN zGt{>K~uk;0Ndlvz(cLl8oR^Vq~U;%Xqp{rp*D}o{GfHUFi zfR{7C=EOiJpg`8kdBIozf~q6P4Ek)i*bzAE4V)zgU+HTPXC=T{y-W-YHQ>?at#Gk> za27j!rLO^;6$xj74r+tAU^QIq9Gt}pU;QfwUH!|-Fp-4;>faeGuvyZ%EU-Dii!3nz z{)Dd~R)(+s^@g+Z;4EzGg-^4>O!){`Ck$T^?7#+-^Qby@#_T z;p>2%;jCOZYa*O=f*m$X`kx(UTM7pQLkrlp3=Rf{W-u#CSAtBz;F({Sbr@81H)M`>mCCG!*MXnqx*xblY|aN*aSQ1KtM&;C~TAy@uw2S6p91?Yqx<%2H#nlUN`j@>6*_%%~h64D&` z^B=hIYv!l~fS50QdU?R@QkTvW6^rH*|3MulP*=sqhxzQ~2OuRkd^&wp9KK%w1r~_^ z0Yv;rbLG!Jk>j4M^PGW!;WK}J zmO%4Mme2h8wGTj|t&&V~JPuPe%UM zB@7G(TC@#p#eN#jp?kj9_q`Xh}$=}{VgUg#H) z{5Obv(uXwuq))Gt)A;ke;Cip6@h6=Esr&;{c?{Wrhai!2AOd8-sWkqi53iL#cJhax z1c|~GgRF%r=1)42#-H@jvHKj@^?gv+dxKpMDxN{EmjJn5B#l2WO8{gJ-1%?P_>*3M zoc|(?Kj{_7QBOef|DetXdFt2eRFLx#n&Iw$15yfd65NEBAQ7<- zX|AyNaRC|a!k={Nbvnok2<>n$+yNN|@&eq9n;?;MFf&f2fy@B=AxZ(s4`AgWyP?XV z7M=9y+@c}?>Vx&#f;(HCdsILh*L^ydsN4YUW$lD)cujNRFSsHNBJYC9$I_nGFZr7< z%QG-I{y*T-*`lJr$iToa4=P?5py$niyVjsXXB@SEKzr##9X|uQMMLFfF46%sD1Gu@ z*!tvwATL19odNfxkebo3J~^lz?GN%J?shb^PYyct7ork>r<@2Ap`CJqCL*=Is82$1P$n}@)pm6kLJ`cW*8-7g>sNV|;ZEy@g z&Silb3pqL&8Yv)U(2yoVC3FCU2$j&VCPF1Nw24p&?O73_6197d2z2E0F;M&G$ZDV+ zbeGP}9s&#ue%(H|4IF!&89n(=`gC?X2!JZJYn{#-ucbY@PlM_@hSmf8Esiqe_0B(Z zmZ+$J@8ez!K57P%d10r`bf0&zzFscr*?rLC@(agq=wUP9Ho(hD@WoWuJukm>?EdK3 z{mZe}>3{PNxiU$|9lM`nZN89`bv?UJfokM~FF}b3G@OQXDK|Jl;XXzbqEZ7Mv>=t} znfQQ*@d=1J1$Yhysl&{2AeC^Vh&Y%O>^R)T3p6o-y6F&=1D?Q z(4mo_I&>$fjqIRx-n086xOaY@`&ws+ih<|lhv51Y+97w;{sHQQlTxj6xO6{wSq!@D z5qwyjtM(((-YQqtz;?nuk!yjJ~ydgG(A78?M*(1vM(rAXIT82H|3>sbn4c=iHV!~FT-s2>xLj8qt<>LoZ zyQ%EmKVG&VEzIJVM_rONdQBB*p&3#)b#%oC|Mn@6Kp>{m3R6jVUXxM`dfQ)kE zQVA^~@v6ie6NZ@v9TUczXI-%@+R|b}sc4BEBVvXu+Umzv(Vla$zJ_`J_*8I3dks7q zJi2xZSML=Poj3=@ps5MeK!&IsUAr~9c8h@lJi!B6QG_x|d<|`s_#AAM_#F3D*zyO@ z%Ma7|^HNj{K&uWwQ`a#nDrt`VWiPsWR16py7Z{QC^harq{I{QiR9;Er&pQtly_LrQ=?17`cj=y@qQMASZ}9ptbYwb>KmSM?|84LA zNoyPzEjf?e#@<@Jp2L|{xB_M|$O5?Y1IF`m=aKbN*|KEu; z{=9>q_zNzAI2Y6SA6)>cIgrM0#eb9~jsM?O5bGLC8h`$aH2(Zg9?dTpOLxBH0!>af z|KcdG0~?QKz9WDBX{aR@4(B1375_n& zG#Aj45dOS_AjxxS{QoYd@#mjQ<1e`72eRZm$Vs<8@fTbMIq7E_|G&#b1qFX=)&Kwh zKl9g@GI%r}74YbUtc&rm&QWnF7W3%^&-A+*e|s7H0Cary%MNfO&qYPSrF)Kw1b79_ zZ^!26e_XA9^Y^B4f@+gBDxk$NuDuD2utoS^(%|6>tzlqu8QsA;1iN*RYi53c=Oe%) zFgTas|HIZD15HEwK&JaWdchq@a2<=~nij=IdO@BT3>r>?1T6B(8HgTei3!b`_*6o}o(Pqwxfr{dsM#1<4Keu` zIv9#N|NjHD_7r}zj0^KM(1j8T(8+%s&AM(E6%EJk6Uv7!Kj;1dZEkfR2erdM*UG%_ zE@`poZUMK)jK3TIZ~mcBri$~@KBz*lt^ZFn|B&Zz2d$y=fZPv?a!p8WgCqa?+L~_H z7Dw<6l|Q;&J3y=w<{#azJs?&Ah&2Jk$^o&afLIwI)(j9U1;m;IVkLlB3qY(G5NipD z6#-(c0I@DV!Z&dL_n-JAeI1# z^#R1<0kOV-SR5eM4-kt5#QFnbF);stHo6`EAHV#c`Cn%(s71yCYmvEF?*X^B_`CVwB-Y`@n#M?Z0pKmcG%Vx&|&eAJpzB>n?KayN5uQXSw(QxEI8{3 zob?XQ5`Z5EZ^y~N&C7?0Rnaf$yepRy>^531@AFv+lxK?C=BM zwc)IIKA7t(_+YO8%m<5^Uwp8b`OgR2R1^X~7`_h9S_WsGgtI=vS<>+1;9cRYbU16O z00YBRu;2E<#a_T!Lh$3@ZQ!glIBNo&wF}O=2WJTh!Tgpa1oPV~_!03U@FU`F;H)$_ zs}IiF0cSmevsmBb>XaVII9}YS|sc5;2g5@yd+-p0#f+~QEM^wL4~9PjXMKUQ#Nmg)+rwFza2Dv`M@XLD3Ks(%cDEet zH_$O~5LwVsX%H6Z&?*QEbRZIh1v=0N!U7$717U%Vymx3Px?*JD|g0nye!$YqBm@du0umSA118~__ zaF&P+?9fg-I4c9rngnOm#=cr%cN`E8XfEJtmWg~i)8Sy;Til7;zPNDgMN z4V;w%XSKsw>*1`MaMlw!m?>QHFjI8ktVsA_@rm*b3_HO-?}E#2fwS(xSsV&5Q;guO z7&xm9&RPX$Jyd}Ce5oP>gHjM^L*_O(%TXETzDdfka1l^}i5aTEY>R@kn&7O}aMoow z>o1(8rV7*R4ri6X52UYFWnchZKfuhe7%qDT&iV>x$*I9jfwKzWteJ4u9ysf>8qB{u z>M$R*sKa8qTOAhD6VzcbeMBAR-#6+o+r;2U;@iPliEvgYoV6Lw+NS|Cd<9im;h$= z=rS<$gIN=F85sJ&tSPz-483613|$6>xnR~DT?U3ZVAcX%28P*S))HL?hFM_N3S9<< znPAo$T?Piw)fvnT8*~{Mrh~<{=rS-&1G9GMGB7Lyv-apRFf0YL4(Kv4ECI8Q=rS-Y z2D47+GB7Lxvp`o0ECjPI=rS-Y0JE;>GBC^svu@}zFsui&?&vZwtOK(i=rS;@1+$*$ zGBB(GvtH;jFsuf%-smzgtOBz>=rS;@1hc;AGBB(Fvwr9@Fzf)c{^&9=YzMO#^cWbn zfmtki3=CVrEDk*ehAm(gj~)X9s2*Tu5YS^_*aQ|6(PLoP2xdv>F)$nivt;xb7!H70 z3VI9-`@t*~JqCt-V3vj+1H)c0OGl4^VGo#PpvS zfdO>s3NynTJqCu?V6g>y3=FTptR;F33@^c~6?zN|FTkuddJGKD!K@8>3=Ge}tSx#B z3{SzV9eNB5Pr$4_dJGJY!K?#%3=EIJtRs313=hGq6M75`55TN5dJGKr!K@2<3=H?c ztSfp940pk-8+r^3cfhPWdJGJ=!K?>*3=FrxtS5R53^&277kUf~H^8hndJGKL!K@E@ z3=G%6tS@>D3|GM{9c;f}Cf!l0dvplPxI&_ogcwjZEv!mh?|LED6z&;54+Z4&nA zz6stW4BIvg-6j0VgUQL%7r{>f7&=eVO9`@U_TZWk2`(B5MY(6kGG_$3$qZRbI=MvuDpfTvs> zyH8$z1`>GX(&?k3^8H$8iHZh@e+xwXN^|AUKLjE``#$r&fp!*uV`%;l-B{e~5878; zi)CN23xB>VWMlDEw2j5?p!t^b;GMa|>?=MCnG(dgulO%SJ_)ih7j<7TXp#}JulOi< zDiXRe8N5~aENI>lWB_DeF7m!&xMKLeVvs{X`-*=$cAo{i9{aXjw0*@OUEpoepzXb& z$x>IOeZ{W)c_4c(rn&GZ9RLxBAlq^gJB#5ycmr}ONHN?D@Lu7wpl!JzVNmoW9RP33 z%@Tla%LR#pm4obtDu-^%J&4~EXnTv{o&fFd1#k34-CGQi2ki?6c>-f^G29d2y~Q9` z!OZ|~76y9)WCqw1C%~S7?=6Na2iXl(4)w%I&?aKgmL}+oBWU;V9q{hqr(p86w5Rn8 z{^rH<3=FQlj*Os9#Gv_U(A+I_iUT||2$}#VV+XOq=nmpTj{MsXq&XgZz=5(h1v)kk znwNlVF(5RX2paH)sB|>OKAlM64q{w0i3p1cO(kNg#5I?QsS@8z1jO6K%tYW)2@P$e znFw4eF=rw$RbtLWz|=rzB3wFGgO+$=S>EZ|eHF9~5oJxb1O+>Y6~MckAXADh;FTq? zS;f&E#PBRPx`Q}vH0vG&FGHXp>tb7(4LT7XR5e1YO6U$^Pz4H|L>%2g3|dqPJ=g;0 zBt3Mk4x(1TvHAx(^*p+R_yS}qezbZ;+m48>dfiE#9mJqajas*k?jSZkF`8vZ59Z+C zc7n>)Dw`m-4NssPh^1N`-9e1exg4_f>F5q(aB)KC?kY(;h_k4&gBX;TF>2G%9mM?G zr$B}hhDrl?bO-T3WLUhr5wZ1Cw}Lm9Agy!-AISmAgjg%o(H+EC$83?RRmkFK!nqWp z4rj*~TF`?=fgvhK_Z^PNeTS}Cb`UdCWd|`RkHafk>^q2iM|Thp|9vL#$tTb;7N8?N zk>)}mMGxlu3VcuAp)|DJBVf(o0af@w6ljngER4F(1T2K45?Y3Wh4HI|4)zhD5?YE9 zp%Qa;2h;DEeOZ_q=@{$$>)Cw~e)|Jt=P+nV1Z?N9f+K8? zaEyvUcMJGXb@1+A=sw{G(7h)v-9F&0#d%ktll-9L#7}ik0WVwtO^6@-e=3dt6X;Cz zJkUnt8)^LckbS|9{CS{R=ZoMYupRmHFM&2FUjpr9Nps}CeJ_pw_2V@Dd|A*^{WSjD zM_~JiFQoDR6NL!AzXF;khV8h8&M@#p0l? z<)9+l)%fkp8py_AJ}yw|*#h2Dn&W|~1ZO{-*%zNm z)clK2<-j!3CShy_#@|Q-?E?liy|8QqzWBcR0b_Sbi-YlZ=mOH_{|cbv`J0|$J)YnA zQm^}e@b2G_pyMBU!Ob(r?jGPmg1f;p+0_TZ-Qt<->Ve?y@Jx1fL2&nYCc8QyxCcCw zU2PEDBc92w76|SM&tz8<1ow<*va11td%-i=RR_Vn;+gEKf#BZoOmbT@kI`o48hxzK@CID_GHlMKKwm8i0#Qsz&E{swkLyDQ-iiA ze*kSyegN8@d;zpQ`2c8p@&?fMFk@@98?ekGQ zf{MN4p3=9nJ;9I|z!f*YW2;bj)6~4bY8oqD270y}=7;TBlj4W%(Ua$g?O7{D8T#N(0N7)F@ih;A*;H(XB)-5>eADm?b z-{0H>-`~sy-=k~-XT`u-ZE)5)IO_(S#RT7@tO92Ri7+rof?Zz?7h4Kvorkl2!dc4j z9n8LPRymwC8_qfh-{1TkzP~w93>GsP@cqrX@cqq);Cq?hz*!>j-OA>0Ry>>qx~LJd z<9IV%>?)kaEWyB_1ooSb1Z+2E7@XArXRU&>uE1IU;4BSEm|h<^3v|)|WPc=RXBgzX z0|)s2=0y0u~?7e25qq49>8U};Cqx! z;H)?}s{_v31ZUlWvlwJyzBGsLZw`R(Z$1Uz-+U3izxf(`e=`?+XR;A|w{i@e)dFWN zhqKPZSwG<{7WfWkE%+W~A2_QLF54*2z+eLQ`6{^V6*%i3oTUNZw;Tj#)xcTH;H*<{ z7Na5q10+^w!*?|wS7czA4&Lc}2QKzm5w`1NF-Mfm<^Z#XL-&RV7gi+xG>o@WL4o@Z6~ zo@Xogo@Xa*28J-OTR?kz|AK30(B9rZU>0a^?{6>*w72&cm<8I~`xDFp?d|;mW`Xwh zeh0HadwaiuS)jeWY+#c?dwW^IEYRLw7BCC6x0e~r0`2W(0<%DSdl|tj(B57KFzbmf z1H*r?d7!<${9rNA-d;X13$(YF7t8|f?d1WpKzn<+!7R|;UM?^Tw6~WN%mVH0?!?d|mei-Gp`dV^V@ zy}e#w7HDs;Czu7=+v@>lf%f*ggIS=xy>4I@Xm778nDs-Cfgu#k`lH9d5CUc~=rb?` zgIO&43=BbF7Kc6qLm-&NqtCz)0A>m3GcfprSt9xj41Qpiggyg98JH!b&%jU$W+~`1 zFqD8J?#DQ5g z`V0)QU{-@Z149g$)uPY95DjK^=rb@xfmuEJ3=ENA)&zY9h6pfgiarBFIGEKLq9WkY z>7&9C4Bcnl{KmqSf7?&j?iby+pm$rhzs5TH^h!>2PvMZvZ8Eq}`wW(Ee(*=Q3$7israwzzaJ0bi=!y#{@JMz;45|6ccqF?ahSdBOJd)iILu&pi9?5QqAvJ#uk7PH* zkea`aN3t7YNX_5CBiRixq~>qpk?e*TQuDX)NOnUEsrlP@B)cJo)chShlHCwPYW^-B z$!>@tHGdC}WH-c+n!k@nvKwMZ%|E~+*$pwI<{#pb?1mUp^N;XIc0&xQ`Nw!9yCH_u z{1ZHq-4H`+{wW^GZipc@{|t|0H^h*de~w488)8V!zrZ8e4KbwVU*eJMh8R-wukc7_ zgGQiZ_Zg4NzZk=7UZyiLFc{y4CY4C+!>_&2grW~VWEVWOjdJv2{2%`HaW&m;75wYj znQwHvv+%EXD*-V$_}9A?fEYad>)mod3<3W2ZW$nk2>*Jw6c9s#f4y4*h#|wj-Yo{i zP~czh76D?Y@UM3Z0WmcA*SiIP7&`px-F!d{1OD}H9w3GZ|9UqU5W|9hy_*AwVZ*=P z%?8A9;9u`%0b;oDuXi&6F+BL!yBUBOKK$$5bU=&%{`GDeAVvuPdN&miBZ7aun*xXt z!@u552E<6MhX9VHx>}1f`2_5 z^9{%DGnaoe#@BSmsAM?)KjzVVoW<4pYcVgZ(0Hl-2Y#et3h2W68Wj!D9rXsF+v*EE znvYn3PDON#jXpd&4#}VmAD1&?U|vVjZ?3|?T_Ft}_1T(+Enfx!hV z3o0%lV_er57#Ow)flh&9WQ6tWIT&H51o1M$MqHE_Vf`g7Mh1pXaLuB_4AWuE4AWu3 z4AbEaA1w@GW?)za)^UV|fng<>b%KR~VFj3VhJ}G)Ihb{Ug@Iuin01APfnh0_b%TY0 zVF{RZhlPP*F_`s$g@IuanDvB(fngz-^@4?gVF8%+hJ}G)KA81^g@IunnDvE)fnhF~ z^@D|hVGfw}hlPP*Hkif0%D^xS%wl0>V3-MJaj-Hl%mA}^SQ!|mgINNs3=Gr2ED=@) zhN)nd1S6fjGMm4N|t0va=e0xJW z09FQu<6u?@D+9wZFe`$Uf#E2a6~oHFa0JXsU}a!93}&UUGB6whvocs27!HD2Ijjr} z2f(ZXRtARsU{(n$1H(QrtAdq*0d%?>GeZq41H&G$SOY5q!)`FEg_VI}7ns$-%D}J_ z%<5rfVAug>O<-kU*bZh*VP#<024>A*WnkC}X3b$`VAuj?EnsC}0FAXUGb~|cVAuo} zTfxe}uo27xoqM?f%-X=pz_1?7+QQ1funx@H!OFm}7R=hi%D}J&%sRlzz_1$3^5~3F z;qd6pQ4t6WcMS7req-R9{K2#Pqeu5GkM5VCc8sLs4&%328$%wgw@W1*cbH(8_3W)< z^5{P6aqu~d2lH=_?(47?3cK-vm%AAl82GmZ3-NFB7VdTWvEpwjn`84o#uBcEU!0}v zuC3onnf@Q--{#I}eX#ff|2A>$6CR!A9Icm1BAZ_^mN+!OVlPo>{v}bmg@1dnfJ^s5 z7yfPjVB0!NR20A#y$-`IWet_`=sxSyeGZ~roqv0^fKRWBN<#NVkM6Tz!PWyM0yA+g02Rv-9?eG#9OL5S4(q@R6=QgTWC1U7Pz$7ZaFK&3 zka8Fp7;b@!oU05B3^%~64-5njf)4eN@Wk(R@T9`f#_i4#>9#2VXFQlyugpI2iv=>)dT2z`)Q6zI3ZM zoCB(-B&nfB#o&Lb5lDx{E;euoz;1Ofac!tkv9K%g#$s8CCe%cbMuTo=9ni!81H?** zB~Tkm=NTXH=&n(5fH)2s$Z)#oYk3Dcc*=K*xQHJ47Y;UU;}l z^dw;CtcW>M+-IfK-B(5rCBtv49t%4$~ayRkzUf381C;5S7Fn$O2J`??4ua z3it&NkhI{_-RvO1z_5dzfq_B$VDk@l{`P24ctZl(vAd-i6p)_GhyOnShdS>2;i1Mu z_k-iQ;{g#&u(cleR)wINiEB+4vYCWeg}}^2R|ySuurOpfFfNrG{M$e=j#nk-(p{Ko zh&8()13JN}9wjau558pfy!^tk`=B=ixuP1EnW$^+@I@fKqQ#{XT;PCwi5w@O)K3YPftRhI5|s~JHEF=h z)>sBuD;HFvLT07bGr(q5uESZs;4DzP7^2RH5vHyT&YA&d9fP-@&oMGE{1yYvqkf0W zDl)m-~7Dv}}giZjFX#==|Nsmu%vEaD(L`rxuV;H*b*77u(@-VDx4 zfU~;btaWe}sI|@vHW}1fX9BZ8t#w8)3)EU?0JA`?_5WaBf?DhUz${Q}{V$jWYOVhP zvp}tN9a+0=3q;z${Q}ofFIgwbnVnEKqBm9n1o?*4e-;P-~qP%z6n*9H2&v zBR{B9wywBk8UB4{|^p31c!z>hB=1sC~#n40N2YN+7CRs z!3(TBjE{J9wx~EbFfjOZ`lx7lc7FsN7O3Fc{llX(M8&|T`xBxu;>Ezo0KJyMqn8(C zJ;b!`YzvRhJt`SsLzbu{fax_V5e^It9<6WrTR{i=`ScbG>|=0XVDN4I#^2(^$iU#) z?ZyFeCZ}h&SogXA7ha2dcDo69v>xDZQD$Ud@a<;v=)Pur^8W>o?jK-LP#2AVo3{z5 z&Ea5tX$OM<1B1u+kDyjg_XV&^e7awNRn7q2D+XV(&A-jr#HahIFY{AR{`E&Z4nAP= zus&9%;?rwm=G%SFv-`SF_c4#|^I#Le+B+FQuKVuM{R5=cqc=pw!KM3v2mkt`zWnQt zdi3(Ndmeni1XhpTJ)YffJW!p~TK*q&T~3XP0?0_?OCZBMpocViXn*kN<+1STJ_a@t zG&G>$(_Nxs@Nzq-LDu@eM9jDKTZy1&w;MQKIXt_?Ji5<=0tV7nGd|$c4GC`0EexRG z2i?K|iVn~a1SoDjjL$&KZae}?1fcN-@VX~Z$n-jc2Aht8!o|1sK*=iKZVwLM){`aN zp4|c--6tIXUwkd%+wCC$66^KtX7K2~V0_Z?|3z4|l>Ptz-=jO&!l(OyM>oVl9^Ef| zTmP4^b$c5)cJhN9&(6Os*u=3@%%k;N3A0D19zx~um-hevgU)kjxGD@v#}W(-4D#S9 z%m8=+7S6!H;N%aIje!?n&lngOss%w}poL+dz>UKQMh1pEU{fj?VX~_k85r(@#qKgP zFjRnburo0*Tmy^cF~QW$V1mga@{0n_{Gvcie(@xbU%-YeQAq&PYg8f>p!uZ@+zbNc z7X@g3F~*-?M6l+Uf8f3tG5G~_WgOxXHBbsg$}h|ZK-onBS9U3>_w2@#Ros1<4}g;% zkvXNb(We_VmoOjj(Eb3*qP;v8V3UwC$TpHP2q>JpA>j?qAfWK>?on|7(MTCYfq?;Q z24Ow`${(xo<&Qor`2&>mDas!XWap16aB5cvRleZ-0lGmAl0S+W85kh4bKv;{R9!>z z#}81?6r4Zem|*HUu;dR4>k|(G`2$>!jMgXJr$_4(`qU?t;QWHCK7q&*u1_H9kn0nV z<~I(=4K5cIgX1nL3XDw5Ed1MU9(PeuVP#|INIUMLqQS&;%tb|uso^JAouuQArPzny zJoxt==HKSRq5zs9G=Nv`%|{%f4|BrnPbYZ&3EH@j1nvNUZV-eFho>;W>OkmFVTp=B zIB4+6v-^h!a;uNy9oFF!pKfmt-`10*JTBed0sPyY&3yRRpWxr_9pTA*(xcb$zX!j| zL2zyF)5+)2`X5y5vwL*Ms2G5XZwpYjGybp|+=ZY!Ss^t%XxJa(LXYM*8mOTr0Nu@3 zYHNHF7Zg1%3g?2mFE# z5BLQAF(Scvk z!NH^3UBIK0*`wQC!=uxggMYibLt3Y^S+~1Er?Ub7Hg}T_XA}Ny?xr2irXJny79O3> z7TxX=oz4!R=rMrAJ!rrWa(Rp@ymbc(a!B0Uz*B}Z0|P@FIPH4C+kO!Yur?j2tp+|- z-J|(U1!`i60M+rJodLexFF`ZzHyA*jiVvXqcn^>6SCIJOZxLYvwYo0xw{SCo8e0eW zTbP(Yjk^?;0#F=$5Cm~*R5HL#572D=1#l#Us5p3ZYP%Z$_vtL=aBTj=3w2H@7e_fI3VJ|F3#9|6t*71??sFXm<8s>2^_Zu<_(?-OIqh z&`_h|=)&L1%?M^X8S}R?fR2>k0Upck4FQd2p934fR+{a>=nP8wLHsAXe|8_d_+FKP zp`M}fIRit3J@*fYKs^Hk_mAd>^%viB|NQ2n5-3o@+^FzK}ThUf!|s!T^ofiRh zv(GRvFm$`9n1C~_i;4v}*Se@!g0rm)D1kd$@Naif(Ms!dc5vX|cD(rm<8kKzP)XbA z90DrbP5A{t5z`x@lHl2WhF`!(#R7EyKmxy@i%Nx0XO2n+NP!1Ptbkw8w}W5Mb;kc| z;M4@logg20bo(m66G!6_P)LDV-;S}4F>%qchg~e!f;MQBDEss_uK;JA>;R8m?+D{d z;0)P%poG)KV)bqT28L1=;{&f(@^9O{OMro)yLk&}zQftzxT^rDnmz6+0;ytMC776w zxk@oPH2fz}!yf0~=EEw$zpWW$c!#s;j{pA|7(gM>;cfEL5}d}tT~}#%3R%R!!0;BF z8qDD-B$JVW;U-vYD}2^@4&(fFnSR5~FI)wF^_ z-=ll)1@JNr7ZnGe?zJ~S-No)tzTFRxJA&@aX7KF(;Bmb11Ssi(V!r$5aTgT_|CJl! zJ-V+ScToYAtqh)^0?I{2!Kc?p#m1-C)c_Wppjo`VjNpV}p#CdxCG2*E_O8OR%1kcFd zsskz1Eb2YF&zCy5^g3Sz$(umrS@>IdAo3<|rIHObDyBZALJc)4W{#!Y4K*s}&ipN) zz}Te+Dz3ZFzvkQpYFWHy>b?vq%|PWCf6GabZ1Q1{xdtHL!V0tVa7&hhGRck37!?~( zl?xwK0!^Ix^KWxeG3MXqqhiFrEkwnz)T!ZDda13|sZvXu;8IhMUKbSySkQyR8B~lZ zfCAeCIU|;sHvecSb?S~$v3b3sBSggj;=m3c6+@7_j6J%~cetn+L0rUGD*6BL%Usa? zA1*5J2;y(e`3EX=J-Q(g<>PAEvdS|&)Z7L>2z<6;l*W&-)VH$=t2rTZ`_7XDxJ zus&S62jnlt(ycDN&i}hFgGQM^#r-muUT08Lft&*i{?dsJH7X`)rCpGqZw3W@B7bYe z|Ns9X!QTpUBxq0wG=2uP0bGxT{s-MS>!RY|$bZ7I`-5ZmfrIbl;f2>T#)B^;K;@U- z|Ns9X<(CR*?5x*C#RDbS_*-}&p%CKO>23guiU`LJcM}jJ(y_zc6janFcy_vji!iS= z$4+-p8FsAs1Eb?{cTngsICi>+__jVNF$5P<#s@rlLmNP4ScGTy*-qC6P+{fK>DmF3 z&FFNUz%S^#f?v>ehvWY%p3Uwa#-6?X7X&~fh3+06y`csk%?CDmcD94EUAL=)V`uvT z0R{$mdV#gj1eo{*yaYUYCxLPazo26Szkt&MenHO(`~qGZ_yrvo@C!H{;1~4Vz%Sr+ zfnU(^0Kb6K1Aalz3;Y6JANU19Z8pyj9?h@8Z8avx2@DH3?FKC zf(|ubDpB_6b^;IILPwcBx}88{wKpc^c(=2Fhov(| zv83aU*Lak4dvkbbpYX8$>A~;*6V#~@_2?G%=|1kke8B@+uX-?63wS`fImdDKbo#;V zSx|WdY3+jgXOOlqXv}jGST+>i+O1`PjcS6|bAWs&5$+h~2<~aP8vpm~egSSge01#o z@6mb^)Tub|39CPSTfc#14`P=Ejn%h++616LbO4ohF5PXA3h1tf_I)4g!~EdZscY+j z620!W5+;xx(<(p(i}nFfeZmYGg@~<=K7w z^)3T=ZvwQGtPmW&pjr?T`k+2BB=kXHUILblf`>k+QiqJ09fzB8j)8%p9IOu1L4=rc zmw|!dKR9oJW-lN*Jh~70bf5I-KCuhbW&-CTeg+1Hc3#l1H7Jykl4|!a$L=7T=ghssTSI-LbT^Bkx> za1ZNW<&qwq-U8sv4+?VR{yCTrTEJ1F0`0Mb`f8A{0&PSfP$MRDnYX6uOWQ zf_gXyv`D)|MFP|jQvfebQgAi?=GcA1vHKgSTL#Klpb)r?J+ZXD1%<#J9Fn(74Lp*= z6E)z`)S_n-R215{b zweNxKeu-UWcRx7QB_H;%K2hx2da{I*`J+qcc93A}?RrVa9bd5P1E&py^Y~j*{{R1P z{k3MoYc|dKAl=AOlLNY=t3<^Cw5G-boF(E8o59niEdv7sBz1#g0ut)Ma9L2AhQvuc zJjRL`7#RM6Q*9$W{ereZL*jT10|P@XI2Jd;b)1Kr2U<|s0G7QCmt}`Ha`_k;7@EPd z!th2e=n5uC9zjbB;G)d2`vYj$)KneF)T;2KA$T zx-Y^ShbKI{e;#KB&3`)nzXa-=l?Z{-8_33Pcuwi|Q3>Eb*?sWf3wagB4z})tAjesN z+cq8SjSoRRwN7rNs0F*#r~6K5Fr*{k*_++r(f9^*uoY_0(W84Ss7K@hGI8z!@Sw&^ z(5?p!-|hz<-Myeug8iU@6p&bgM|bZAP(!cN2Q(7U3+f4i#3MlCXCW#U#+Q6Mi%)FV6wT{|7v}S^Pafg9t4C9^Eef9ALJK zKL`JI7k`1YgC{uBI$it)T&!I?iq5`d1h>Qgaqu@CVur@UP8Nix)`I+u+f$%b8JO`< z!3^>$x~Bp`Ms?PxRQUGt*nvIL8_v?r-VKTm&+daB+J`)vU$A&sALQ>*11k-k;K6*+ zv-_k=w~$BkAr=qqQ!bsMGdvEyWb(8=#oxrp%)kJu2|7a;xPk)0{NMlopk@tXsuO1F z4;KEO%b=xyTp$s!QHQ|x>;pA&m@hj1Kj3P8sOZAWFMt34cWnFz8j9g>+5{dke8S(d zk{J{p|G?>TE=UJ*c$^0nL0uC+0e(T>8TWwmcTFQ`hs7;4>S(*!=szSga4#Qr!NQpJ`awAFWH+PGIsh3xbSc5 z0F5Miu!6eQE*-ui9-Xcdt}Q1^GSk4le$VdHpayXBPZj>2d!Pdy`L{VJdv;&;Xg;9g zp?$%-m&d{5;6o-4>x=w7prCVYIZ&dOh6rzu?h78x2Y5WXxxm(QfzARfedWTx%~!wwc0cy#(s z@MJ!LqGkqMjR;7Mi$|yL02~A*(HpuQc|i1*Zbt!+=7S<2_6`s2 zi`@r2I(-j#SYP1pb^8rkngK4Uph0;4{{_%$kv-spF@3tXfDhe48-fE>Ignw3=-9*H zumg>fxOBR9fCJv8`?zEGHAwks(R!ei(}jQA|ChO-@v7bs6=?bRGWOs9|6skn3=9ll zeU9DNT)L0L_3^h_{rmqPtk)2twj|XN8b6WXB_%iuN-NNK04NWkEDr*Y5`bE`C}sO! zkh?(f1B#oMroaCGcLgm#+WGhYe{8OD<=^(-h2Q1>%ejByLC4>65@ZNC1-{$^W`?K) zfI28XDjpu4T%Z&i(H){<&?yLFCirw3zMKgzYCshmeheC$WMv;9z$OVFy8H%T4hWh) zcn{t}0BXy90JG}YU~`2R*clkUfyJ({GcbGvvu?06Fnj{D?yxg3dFnkBI-mo(;`~b6{?LgSvBclKF8T-s9|F&RLpI&DVkKS(3NHJ{CQqTjs z@~HJdiFosmhLY=;4Kh%J&H&org16E@Gis2wt~z`c3e>ZKw4EIoV1u_l3=9nS!0lv* z;4o;XrTYW&H0cxUdqN>S?x)x#L4)!hyFjfTg!@Z5A$rE*&l^9u6&UOVzMh!r#&j+E4`=TJvyh_`&~L&7t8hV`(Tv zEodwT;r^0j7nnNv5;n(%AOA~r!Rn5=sCY1dnvM)eip@X+ryd^Nr}@`k14rg-uI_6{ z@?y>Z8B4hSpLgv3{t`592#&wkOdy}us04tfmm)xIfAIDKu%AIEc|jr+G`TDS?tp;y zltCgC)ai%Ff~FOH!Lo>HMNkI`vbO-V0uaJ_2j5#D#R%Jj;0|Yj7OX>dD1i1@UW6qO z(2OLcOT)kIhYP4v1KZD0gFPX*8ovcab^vIfaD-#`SD(%jl?2z;|NJdx>S}&Ed_If$I z<_7V1+JjcwpXfdax-PRRni)2d-yN;t*!UYX=g8mu3)BdLE=LBh4+m|72n0_sOa`si z2Q5;Dt>r%CaqxwOhw%x}{s+*;2T&eiNW;0e0J`iOwBj0~5`EVLbPob_(Kit$LKl4# zG!eSwn+Ox3OTIz-A0TcdX8!|3CBFR+5EbzK4=+Kh6R~W601f3jcK3kheHlEN&-{N# zVBZ7OSR(d4psK{T?*UaMzI_icmB>i~bgnuyw86rVMf;eYcFInbE{pf%Z8mQ|yiG)-i_^z1&x zzYWK}ByeaDwJ!;-5IwiDna4mvRw6fR&6YR-Uc zj2X?BX!+8m`#30HN+C4}JYBk-bzpTJ^YQNU9+w~eKjzU1sr)*fbsVj~^EbWt0pD?7 zqLKhwiUMvXz$Ou5`nFkj;0cU-Jvqa##W1N^67|Ov->1?>zR5)utob?jU5{B=I zv4ykJ;jC^rYdSTdaxZE*clk=z^pCo3=Fkk7HD%~4VVQwKeHOlI>64rPz7ckVP{~d1hYUV zX;y$)XV@7SI>0PYSE(J$0(F(zz${Q#sTIrub(LDcEKpae8O#E8m72gTP*jCM=v|*Qp7~Fwf z5^8WK4%s>#2T(820o3LV0C!VL1rYOr{4JnU7+kvDbasH+y`I_!e7etjSpO)u^5~uh zYVbPtvRHUDpZ?$JrUNPrUW2#vI%=Kt_^|># z`GHR-SVQxFg<_Fj|Nq^GFTdzM-~52_`-T4pEeqy7t%9| ziH|#szh?xRI)e0!To@P_)WA6#G%XD&$v{0Lh-@MQ1A`P;HVr<9QUNao>lqjrWWlna zMl3|{G`K8itPD~{f+kfVvYX+uyWl48hnsv6E(=<{2{9Qo(F)NEDoY_JAeO+VovJ~T zO<)&*7MDTPO@hmUmM!f2p!T02c3DuT*#fi%2Xc@>>j6-KSM1{^dW2ovmJ2PXp1 z_(A+(P|%=+XEFw8jx-B1;~;c-HlNN9bA4?hh`Vwa-1eZytAj z-4VgaaNPC1z(yv9-hrs^4)P2P$6bFn*nz13N?{<1`9Lg)VrQrYQQR(TKooz& zZV)BRP!2LhoPmSq|8ZAo0TBt<(umSGh&d8L&B(ouWbrK9$YOGoVqmyX(#;86|GjFcj13SSG`3qU&4N~v0=K{uVzD&|w-Ue0p6^c_{O8FfcGclsA85bmHG; zW9ZR++M(h59JmdjfgTtBZMCO-dR~PGd*Y%9Y!51uGjV}D#-aB@mbvW>h#Y34FG<4=+e8LTC zl#9_fxKU=Hm8$&PQqTDGx`M7nJ_b6A3ZesS5XeLi;}eb`gCGa}^KUa@^yohAh-An| z@b)#=6EG99lib0XGg#V9x*!AUE^v08KZib-D?>{0mx} z-teEbT-TxDKND!()EVp=9eaKM?QmdXU;x*=4h{d=N-aHl>jc0DhJdHM5NZEEXwZDS zI4E%Wx19owEd4)})+z4BzfH&RB`D81H2h~T^#*GPEpA2AegHh5u$|qp`+y_=whNG9 zDv)9D7%;w&=J@|WnoB1;$V{Wx_6`mI!Hd3b<8=Iw9iRbmaOz0A{D^=1DgN~*(mMS> zQ)Riw9UJ~|mkT)ZZ#&ZM)B3#AkLRT~s9D_bhljs41vF^ZTgC%j`h=t51YMEUj=gEo zT&?2(D)kXTbm#B?|J~k@wh(wW=?6!VH2=0{bI|^QPPS%e9S)C!i$Sdmj+a|NGcVvK z&VEpe(EjP7{U6d$!`06DkFTBM(R}Vds3`?&tTEXng?C@x9Kou9(nuH8#u4fpIxt?G+=6Vt|2+6?EdVs$*_51(-FH3&@{|{Q1 z16e@~YPdsMi=cWG(w1BaZ}IJhw+9a~Ffasz+k>FBl91LSXclpo5NN@p7$XA%s8VNP z(1lN+_%kvvfa($!2GB+c$dpS5Tx>a9-F>(iXb=RVPLK(vSAz*=GU%2`h%9J@GsI-j zLRE;ppan({*_Cj;N8oxv3qT;UkKkfI;Odl_85jd zhrtQ>0fArPvVWKv7$U*4jPPL-O%?`*ZD5l#;H*vXffmqOn;5W;eQ+HjtT5Z8Sz(7H zf{x;Ygt!r0){>QhK^&~tjukdY13E+x(rH=vS zBj}Jiu$w*DU|}B%XEm|G+}Ftlb6+1D%moYJvdh_E?pp(w1)V|$aUVbY$`Ubl1_p@x zq(Ln3B6MB2tO+~JeW2@3AYl;C4(rq{0?ku1fYRS<-lN?SARg-3h9SeY+pH@NWa{K6U(m+*SK~>$ej2 z&z}5y55ATF?bc;;?Dceb%?%QOPX~flj|J=n9mCc9!=aeNv6JCt71Gt0DDx0}%nS_O z=<|c%$`^J_8hEcQc!>KE2mEv>*i<2C3Jx@?K-@GTL?!wp1m-j$L>=ZN1XLx`G$BMK z4iljhS46lGI&lS>CWM$s(;Lg{L2VnzVQJk*wGUb!g&mXz4r<5l9?;TV22bXb1Sbq3 zj)fh6hHqj5RVBWO2~?H%CJbRJ!HEMj83H{v3>xlWVaT)yE|t)5CqgCWgdwJxplTP? z&e@J}vomzU5OH3bU$+lr&d`(pq)%rzXeJzbTe%`)Zi2t%8+ch`Hz*&vbO-A|aw!ox zkogB_O1^p0pa1_IJ8e|DPq;8&@VxvGdJU$8XZJx5(5YB{i36127v zdKKnJ$L?Q_y-u+C#NE%aj&PvlV>VDe2A}Z;J^c_=^Mj7h@qy)Ia0vsRfX08^A!vO+ z15wk8aFyu!4>T!;G_43%hnb^6DnZkVU?oHxcL-63X%4hxfTkx0kU`Pb}mmfZDXbEQ}!&$v>)^<4SA)LhxpJ>#Fv(n%#QqW{!0@x1FWFhE!FlL4e z>tF0!p4R zePL%{NCUHeurn~Ef?0pq85mN)ECvn+hGZ~{g@b`13CwZ`4g<}91R#&rgE!y)@&KJs z14^aPO);Q@ilJLXy3hZ=4B8z6-ameffq}sheguUEXmbQelCAWyORocLBe4Nk14rq3 zmtF@B&?H@Nh>8VROrUgkw-4x`{16olpKccw1JDj!3s3$N9{i_3TWZoe{g@8E5J>A} z1Fe@n_(DS!w710L;46VnHbKw%|`-2D+jpY%VtI4`}{!- zbp=qcDR^}M0!`gGfcxYg-8bRga?lEwu2X6G(WH}P3vF>webx= zn{X_Sxid0+JIKi2a+ZOC!T4=&se^~*YEYA+R0C-xJ!qlxX>Dg6<`+=&OOiaAcY~BM zlstwWlLK8QQ4$YX4w%*agRzvQgZ=g76$&LhD)PtJ75*LQaA$hmyn>@dM1}nrJ4c5* zlMM@hTmJw5|BtydF&tz64_av%qXOP)7JnGT$$_2V(+wB)XuVVdUSYV;9<;*nL-(=& z2N<1oz)PQ)k3!dGbhtBuL-zF^(18h%es(njtXI_!T_C~A;1C=R+1c8C3v>u&_fP1A z_fzacq`r_bwrAL7A=7Wqu}e1p7647Zy}&LDo!7%LZVNFO*M#?P@TDxcCcNuG6W%2% z9^eUY(C{v7qauF`=!i>~Zg3T<`egTZkQ2d^ z-4Ne^Cc7P450u#Xbhm?~x=%C4>tMRtQ~86(<#*ku!L!}px=(w8CcD8JK#m91tI(P3 z5=a_!jE|3|+f24TxB{ZbO!ilJMks*KRhPqOvTHyr@bE$(To!buDr8V*7F_l)d?p*T zNF7`lfsO_Ffml_KBQ--a5sqoZekh!3O6q44p zUxIU(_E!qi`}zMq-QUsEJ9yp$oZcbx9a zjIg0DS9n^?LrRO`jv=5GOupR@z)Rtqe>s54-Q(EH-RA!a<&us&PT-I|{s8}k8l*bF zl~^G+(BmqYt3d@bXahwjtaOGiM}kB$Xo8*#wC%0i8D2!Ybf5RJK2dH9t!zQdk-E=! zI_rRnYM<_Zp3rpQsCClw`*k1XU!IrWJ9eK07t-L#u$OBY7#QGXGo=fWu$Il}X%*xR zXKZcl&2x#^NRZ`uc1k`*)!7&D-rqTr0C!m$0kQ{`%=1K=Fi^#)q@M)D) z_}v&;@JSa?(+M&q13KOXvLdS;E(=PR1|%?KcscKsHlQ^tlbZx z{VXl)_X9%nf;M(p=rR@^?6Qvh+vGr9Vg+!Q*s~jQ7Jci1QbCX7plK(D9jE{Q|Nj!S zY7Dd;P=SBj;cn3KA5iUUfLH)x!`O1ELV$nUA&*XW;{&c8b}#=kFfj0MJEZc!=0GWj z;||b9%Gbdlfe$thKmwpOVy~S*0zYg%fCNCx-(DMn1pe6k011HhF1}U(3H-D90}=q8 zoB5i*;nzR@R?tyMj-VCb3?7}3wSJDh9*m$qy2Eae%^sJ3?*r{b=ihdy`yJ@8Lkkb( z8?N0iKt~LCBp(C&*oXN5nx{*$__u}VDEvR>$iMA4*qhCd?4h3Y_463|&Okn{*z@M{6Cq(DbuLQ)*)Y$}K>D3@4+ zWkH)KA+qz~n03vb;Qzb@ChHm5+0y&mlDtr2#V2%LB5Bc z`P3N$I^ZZr1+s{^dm`vEDdV>u-8Wk=@wb9DjQVs>1Wh18ZxgU!Yw)D5q@ zrCvg&NgF{y3k}kppu$D_RP#^xj0^Yy2>x1UHm_>jYqe|rlgw2cpXcAxM#_|kxX8$=SK8jng1qE#vop%UyMkewGH?vMbv z6|3#&Dg}sE3HCn$GadQ2oxmPGovT5~ygOK9KWHCuT4ysTNrUs1_4Q(Lk8YpK2E9&< z(58Q`sb@EffJY~jM{m#%kLH(*rSH4XgAQ%(4moYm z>(AKftpOdsgm!1Z=azswFdmS3;LdEFm!RAa)>JJ4swcURJL`Z}0vaE9UF6aHp98dc zBo}*s)}uREVF!41=5deCY>9&pn8Bk4pc6x!b+k@+G@tknp85q1eS3Vr;Guj0=A6r~ z91p%=c7fcs2i?aC?wEj%NHG9SL?IW8X#{7WK)VDWGeV#(9*`Ll(E4Kt3v`GSgatbB z4#N5kzZyyvK8FNa&jpb!go}Z;T0l%*#>l|H3!Y;*510K3XDKtmHi-GcS>Fz|s{;A#n6BYAY+1gH3JPYvty#Y!IFWaG%#>8t^X3&_4i zNNj*l_ySivWxSw~bWjZf5kpD+;9T=x__#CZvM$hVJ06g1pbpLkpnHL!c>!z#G(#ZU zKupev!k059VB`sKn(96dPUAj@4SL;?dB{fx1-L%7t9dJ!w%2@{|Vj**6pbS zTK{Z)pWUwDH2>x((n6eN1W^ylyGZJlJepr~fK(QP#;Hoc zM|K!=gAUS#PScd4Mm;zVks>dTfq`KwcwOB*c-slI=pNEy0;M~JQ1I-W18TG0f`40N zlt=eR&;%XmOqt^@Dmst@uU%9Oz;{X*F*W=qaN_l0u zGygVc9fCJ=219P<^wd7Z-^u`5sxldrK0pm9&^a6MH7elh9J+wT6P)ZVz-=AS;v!Hx z2Xrz8q)jw>Gbbo4APq~TH-FHZU6eJZpv@;po9g8n$Ve@yUF!mk2FHW1m_0AQbnHHaa!)7dQYUDy z0_FNtM4#dWXersj7obyHAc7A#K+7E=slWwvnj@_D0ab}OwB?YlvZ zUr_a+0J@3*>A0g(cJN5YYyLFr8Wn{dppH1d>x=HApiX=NXbc&ASB3?=C*K{TQsL2j z!~t|*JE+2<1cMG9hOFiX9oqrvvxB?aAAGQw8RfF^<gfmZ25YGI%^0)L8K7c1~zLP^vQl$H{E^NK-Fj;FH;2%XnBHFBkM+{@~JCo#3JU z1Jq^O`I^mx`MYLy0(d9~lodQY7&n93OyCh0(5>u{!QZ&UpcWeBMh#Fxh8)TUax8e~ zwg6}`y99XPQ^EKf_*nFty>$*AmK#Bh!%`LKIjEq+`@pMbK7g!XXgyG(h;#J}H)QpU znDK$v`XIl9SGw#6uXOp*eH^jU<^Kt0*f}JQoow)i9rQeH*d6GOafhwp6Re;e*PxYe ztPD^Opq`B#0z2fr8#GhI09)=4zTBLD+bfUmi6Bq?KkB0Wwe>%tlZjl7pZN5G&DjSU z76C0dZi-|E-%tR$&I9=nux}I`0tWIDajW|wD$&;hLs#ZPs}fM<1W|``4KJb|nF3LX z!$fEmLWGIX`U5oB1~CzHunp=;MCAdu8+EV^xv2#)3(?dn1=s7~CRX<`?St0G_}ife zfPv=m7(AKJgGQ$gd3NKz_#A2^5eI;ws>HXRA5|s3_53iE@Fq9T_58S0LPHvC0A%Gq zE|t)b#;X!@JwMDe=z4ya&c&c^1l9weqb%m1fO-HJBo7kKXP}JLycXvPV4yRO z$vOe-D=1TtnGfFyVBp{&YNcfy@s>o;p^-R*clj# zz;=|dGcXi_SrzOI3WD#~?#b2+ZzjBWPk86iwE;PkM7IRMlOHLQ_#G}wlE?7 zZGOVNPCr)sEoE)~%UHtM@QbsQt@Ujw)Bl6~+ngD#5ArvIj$`8ACaitJ8`@{LS1vBxlzlbyzS`F#~J3tP6>4vNdZaq+v z3OXIw!G^yi2BhQP0nji&_jwQg^%p#v53xdBS}OWF1SE}Wpq2~&Ht;=U{M*9>U@FyL zYk{~ZDnS#&uZ2PTD_{EWP;3pgYF4}%wdBT zWI<#>cQeI<`@W!if*`V>nebcS-ttWb1_nrP`8|B(7PLJ8A_h9d48pQ!WMH@g)&Xkm zLB!G+VR}*11oVy^ct60i`zClz{3r1IH~6NfPF%P1rl=(NLJouL!mh;A`a9^{mTv5_ z9@d{hi!^(1$ac4XU|?YI>|{{^t#&{Rii1}1_V$BDe_`e2d*zZG-}yhrzRkR}f# zd5>Oy56~^EpjB#M4IZ7_K{RA~1o`%-Zqy>&_!3B~;|{n+XvdC!TezuDFX%!xWJi<; zA|*>{a4*zT`@YA;*PhJxJ*@AQOz`Ob{L&G8=)Av&Pj5TO383XL+}-{G{M+5lJowk2 z0Nwr0?Agfy4VZP<1IDw{1acDFOY8st|69OUR)D&TPX3@>TcG3BAZaHWE(^LsI2D|- zK<8~iWGmsapsOD;z_Ot8wji?8;j*B4f^4wtV)%&6X$A&{1K>3F8qWFyzrNHIe&ai6 zj~JvY4H~wGbfq`IR~T()WMF`p47#uqVlOy-LMLKCo6JF>BZhUqyqL?})?GCMZxKsa_eLHbKXu-Up{mu(v_8oshfw__sTnf$P4T zX{`rJIQX~!@L>M`nsq;D4FK3zC@0`S?<4cD{$8vKDs-R+;v1g?-RLvrAn0-==&^WE zmHgYBP58qP@Ne@r{md@_-g@iP-3%^}_}8B>KH$N>-rM4{2fxRSEdEDbHJlJ*u$gsQYm+L zF!;`RP_+-bGv0&wln37hxPSxP7mwj{LP?c#~$51U<>%ePrC4L z^ELt9fTazNLH_Uqpr(N-$h{{VyAOKuuLs5FArSw-Xa30JpZViXcvxRAQ}D3<=+XVs zgWvt5hxR#-i!VKx&v|rT^sqixoZ-=Z?R7F}iHPz5$G?vEb) zPA@&Q4|(#h2b&Kz+ZbwkCrqSEES3}94P!-v;IR5RDL}L zR9Qh1F6bagNMZ&Z5aa_+%vTv07@mO>^B+bSOPmQ-9f1z&ffOv&OfcDrObiT=%mFG8 zBtRp}3h?VKjoW+d&vb%E--BHkr#nT?$e_{GseL<@;ue-K>D-}j0ixL6Q z6~S9yOLZ3~xO9K*%uev=_O|d~{^w);vy|1t`e#W1$eSLJ1OvJt1#X@?XlX6zR$fSP z1s38NsWm{WLLqGk(5fm( z8v=Ch5~So40ktN;$-x(X5jH3ta1dGzue@U=AYDCX?`_5YBg z^~IvspiAsc4mk2}cPS7^0~t}v#K4ed?QF*1UJTOT9cb}KkN`Trvol2{ z0<_dO0VLG&7L@h2Ih#W26%$0g0?EFxdgT2_8yit1?7gI?4|&OZ!VA=!QKaQDIPix*!sK4M^~XHaEeXt3u#1Qn=fVBkK~{ILGw3+{u5L1jJ% zD6@BhhUP)H%_M{8MJ9t<+0exduw@UP-J?sSp~KA!vHn_G8u(SB{cBys>Ga(f|&-Lj6xhP1&I$L2BkrR&XA=FpaCdQ z`;?iX20qjbI#B{52HM{TVS$FCAS_Avurg@pCqxWX5kpv@J>(FRK}+;LiGgmV0IlkS z$RZAgk%td3d%{_usuH3OboC{KbsR4H8qVTjW?=XQHd%m~f#D~Z1zNcA1I&_OW?=XZ zX2~!!Fnj~E6qp$pzJggQ%nS@)z$^`B28PdI7HBSB349BErz3|4==%9iM*)v+C(yc5 z36E|jk4{F9?t_P6TWdh$37~6U9XVhkAmxw~p24e8yAK`)b=VWY8&5zT_Czqtoq>TN z3C!{UH7sU>j<^K(i9uJqf;z^KW1vAR3>X<0N_jlGJ3-4}z?DmLwF6_R2i(3I&~B97 zpi7WFESo`NWT1PeqrYQq*?3r*mvVY^`$NV#L0jCxLzV^}y}<$?p9_M03hF3(bPIWO zp9bB{dqg4nup|Qm0~fgK4yqtp!8aR&7922u#bV)I^ArXKhN<9jh)j6byqtl70iuI{ z`$5nS2+(wo3;(tQ{M!!s7$1Q8!-;=8i$j|6!NZ_qvq90#!r=J-gbV-nlaAox12nJg z*nPkU610xpEuir$22bV#{~tK=Z$IeKjdq?GND#WVtOqnE0$M$A0&=hmXwM5o8kD1< zGl~asC@UlLqem+j*SRT#QfhZv(9? zJecNq@FDaRN>J*7%v^)^M~QfY=E2b`O@tcI>^2Kn4d#qANR=c1b`fvFv)U})xbMS* zrVdyB?L6LC_H$uW^kDmNE-zv6PBT7q7_@s46a_2{uB``33|zZU`1FE~@nx9MebA#f zi1FYH2}pi+`tM_Wp!?v#2OQ4bJWa+YJV9GuZg(F%%+0{Sz{Us~pz1z&@F53W19Ab- z&C+0e@G!_+h)PHst`l~NXZI(^?i>8uemXY)0G|!g@)&Cg=hA%`>NwCw7ANSg9PJz3 zhkd(yKv(dB{J`G=+P3c4{DQHh9!!RTNnOY0AB_C1pdM%UY0x^w|Cb;1dP^uDyZnNG z+X>Ls2aq%0_XvVoFJLAA89+(|@F@Au09L|*TM1~_=j8`Db^A-HsgK z!GWnDBfB{qyB%0QIr0lQaPpt%KEQvn`y}{83hoo1_yt{5I34-7IdF8isDRW7cDkqt zdUW!rIP&lF5bZvA@Fm;97wioW8C4iNd{o3bTvS9s%9TBv52$pwsEGS?x~NEa^zt}( z_Of(%b~)K_d$?CHc-O} zbjkXG5(AfRPS@6xB^n;xj^In~<$aO`Jem)%c=hrKcy@YlIQDw}ckJWi~u!w3J}wZ4K*qO{-BBDV9)_-L7-jYfu$x5ztT(ftxlC{+XR=Y z`*fc^&hEc*Lp*q3vj%j0+%=Exv#>O(3mS{t@mjwlL?r++E7akm5(si#ut)c~4i}Xm zu&WqLIsP-ho?v|7H7nG0pn1pc7!`yQK`p@F;Lfu{aA+88)G7jXKaKJfK-`* z1~>ztm&kTF%N%F+Pfrg%?xF%JQ5c|;r!L*>zP+A|p!+ipJ^&T{{M$ea-yQ#hR=#($ zOmQ?m(8=!6{Eo5oDrmS}#-le_mVaBZ48o<*_3cie>rY&b4=}nixL7#Llo_N!2I0iJ zLDxF?NXR<=KXC8`v@Lb;5r<I(XDNruADbVbVQJ$_h_T|*g|5a2kjCyHV@eL7Jq8}2%oYGz{{UGI z1v(|*Fw(eR>w%Js$VQbM2F>o{m{VWw(H#z*%n66hl3(E8?jPa7e9EWS@xKqh%R!HB ze+wV&L!SRZ(pmckDj?^4q`v z|3Q5sNSz9r?J5K(7SOaOB(Vg-CqSzh7#RM8^EhZ(2ZRNhO8*BI16^bQVS(;&gvf&W ze2}3sP?Hxj489mX_`QV@HhlJi5qY*l0F+!Ht2{zM7iBegG`?v7rws56q=)elk51Qu z&JYzFP`d2&QSm^Oi5}hM0-&R=OGYU9xj%5S|cDi)w(#soZ#2U^4vaNI@3 z0#tT+bVH^JKnMGQM@_3hR>Y{7yk6K5qGA9qSPelAGlmqbM*l-VcanqJYoLoR{~vw{ znoR0;Q86*TeZ;ola+lk3zg!{o?(yYaX5e+CBr_E-P@|L=C!uyj#LC`oR;&EEo^ zOKbcMS{qPi1nNv9FC7Ty01f$rW_3Lp-6cS3JQ`m^D6o$5b08fMFLhu34^iF)I!%!= zUgBjPc$&^%z@v9NC`)>DUvud`{9hd8A(!sMp4JCSle$AxEIQ|dGIi_i5=#%r;(J8= zfuW`mhkK@R`Beu*6{3p9DCTKw?6=MfeZM66j00r zfYO6UZzrf{&M&~kFW@EM(K`vu6Lf6g7jRm@FX%aeU%+bvzo6p+egUTg{DPhv_yxQ! z@C!N~;1_Uuz%S@|fnUJu1HYgni2VWV-nIM!Or7(=g_)cr>J)16_r3%cJ|J zYwLkh4UcY50guLCpcv$D16@Q2j%;wel~j3j#!H|^ZI`7 zHAEpaS(He7Fvd%O%OX$#3te9VHU+lb7b*sF2D&>k~a$$5w!pp0z*vIET`*b0jP(Ch@Lig1WO?9+YTryDdo;n59V<$xpy zZmoxbI*ee4fezI$0kc3g?4Wg`@!)r&5!i{~!T6?P9l&_7?fFR4{8qvz^)YU1g(RCbnig%t^r8`pjsca^Z=~KqxAr2;1XsUxDV$L z4Nh~P!Hd>F(@v019H_kr>BKoPFfed~9SS<{9x~<#nhb`d(o*AWBl-;7O_(JT5?9l7cjyz(pWDi-6CE`T<%*)_Ssp zvW!xr0?jC(w1y|6bjPSffJ#eH|Ii`+unq$Q10>6sF)%PdvJB{aEl8FDH83Gr1~d>4 z$ugjf0?9II^$`%K!m3uJ(&{jwk_^-{X#|&K z`3wvUI7>46Is<26Mqg(@$}?y}0<~@?fY+l#5|nSJH!MMQdf$L1BuJV9jaoB!b~^8% zPf7x<&48pN(7JDEN@{-N0ji-3K-IAUsOhHxA{1P^PoOmUKn-U6Iv|+_lE6TXM1&5| z-OJ&Qp&q?!R2ts=`@f?>gb{MXpNIAXeA_>qTmZ%n$&url->BiOVAx3#{Ydfx2RNzfK={LDG*^~@M!&3QphjhC%`Wtmd2ld zFpaqto?4cdLRB zBSWX_3y)sc4?dl_2Yfn1Pxy4YUhwE`0Cx`s82JS}1z=rAL5Bu@0mlXWf*uq21w1$K z3py;|7jQhlFX*v>U%>MMzo5ebegRMi(c=QYfaeE(L5BzYf*v0{nqM)NuH}cA#V^Rf zFX$oAxz_-+?Eua;aAWwmst|xqY zT@UzlUwX~r(e2ye(d(j;;oE(}r`vS~XweX8Py{3v0LpO|9=+gX2x1$6L=!+P4UcZu z2_S|7IP^e61GAVI7(nq<;nJO>65z`G)T6t!0aU(MfX*i<04D;JxOD73;@kSIgv+!0 zdiM#(|JPm%dw@nEf|wW>d_gA;TrfWQ|FTE-4G)m%Hel~g1toNk?h{~3`2}1Tc=UGf z7X;bsyTPOR;Q@X@X917SZUNBU+=AW;&;-EW3fhR}((R)X;nC}Qz_t76E@sf`MCOCO z%%GzR(#{@{n-eCcxWACv6(y@0q0O{C!T!3`!LGk4HfM3Al1HYgr zJiZwC1sOfM9Rxg(S5ANzGC-FGcyu;ffL3pHHiPO_pU!4bjr*Dv;?QmvP}u=quMP7? z3ur5(Pxn$#g=GcAEgSi_kv1w*szdC;}KBl?-&cdCA_oR1!`6E z-VKbP6)S=K0<9dNa-jP(zo4_nCw_rm2_Z&?PyB-3khV+fffCT{NofOUF(!1uCTJZg zIE+ETl>!=nMU>B%J-QFSjQ-HZ$j@4&2o@NG@IK;tN2 z9jBNW7#P7U8_)_Ou)2-R3=FJbu@um8z+hGp3rrp88Y##K@gEijhEDJZF(driQ*rpE zsB)|f3_W03Wwl_6L{F5)~DX*8lu1r!_$fthT6dfX!Q?0=hZfr*n-8xJ|CP zMdbmga|GRJ!mqhUx^hc5h@Ju0ANkT#*YHi4m36fv8} z-=e0;!0=K+6LkD@Y}D}W8KP3)Vg1vk`+^U@`$Nz?T14|NCjQn>Y77h>-L3`v z+kB(EdQB#Hbhm=4NYIFWhF`DF1n@HP=9e7&EuOLr49zbY`CEPqGcbT^;TBC!23V_$ zzeR_MfuW(+RD!=<33Rsv|F+Of*WS1v{M%eB9h(pR;MV}{C}Z&LKI6)K&GX<(CKu)# zpk7Ck4|tBySNo_ZXsz}|q|2ew9QpGOdGvZJe0Jo|=V9>Zbr4A7|8xQ*e3(D_=4bwV zj}QEjcR*tN(HHqO4)aG|;TJi?FM10yo*M}%@^|?4@_;H?24Cwl{LRJc3=E##M?AF; zf@W{|o1_>S7`%E#dO?}Uv-^*y_D^5X{nD5Sa>x5WZ`eR z#SDtIqrScA93I-oe0u9RJUSsm!7kmOJi51lcT{=wI{xuE_=3r?`-R8<1E6B40<=iI z!lRc3wB?imBw~S-zFfQG|2TG^bL^IP?5<~V>JG8{;nwY9_s6Z9$Ih)g#E!+W+x>^* z<;RyFUw+qS7`;Q)Du$F1APP5|T|5yx(K4zm0s1F=8??RQ2+^@fH>^%Xa2}@Ajg5j zKm`;WAlZ}r(O^%ysAzx`r19s0eE?RZ15*SFN{A%}AVnaikBW(Fcf5>acf5jQcfAV8 zLlPhl$$&hh0P>KEW4F5mSst79iJSjUX40DyZ8XK&r6CkPk=^G=@B&o&)(9 zlo&c4lF=HaRRc`Mdb!a0mzZym;(p<6PPMQ+&loQf_f0F=nYH}B5q!Q z{0CA9x91h^O{p zNM#2)$HlLgXD+<5`|aBs%;*8G>_o0ZD?4B2v#`oeW;bYx6}D^LvD-!Rk8f{~LiZ`( zUQcj+cLGuTfeQqu8;;$MZ!SORMyiP*5_dql!Sx!3o(GQIjvuh;c>>Y{uGld2ym0Jx z{DMu-8;~Awm4~6{gJZWNN}UIB8MMxm`~UyHuk{`NW*>P522db@sylF4e0A(*G4$>A zWPwz7DCUByyZesajwtorvCp9T?mmm>|HHo4cZv?Y{3(ajPy!7OKpINTtPBkN8lZKN z46eO?KYY8dcpiMk1lsx6Iu+F02Ax#`nvP84&xbai&iZJd19zRy`F0-y)tnx^LEz?* z4CiP5d`^#EPXTa@Y;(;_E1<1c#1$RB-yKl&tpI2v-}!|_(cx#i(cdx1kIBNLFUGL{Casnix44gDRnt$ z@`ARdKqt5Q_69O~cHi;TzUkW=$>`Din#I3YDYbX@}uSljLes~FMyWtIci_H`0L^ikVime4rmj! zBfp>%NZ=5%z#oWVEb@-s!Xz2Rl*XTjViF6)Bo}!W$L?SjlFV}AKsSsBW*CQKcQ6MI z!!W%k0Lo>K+82C#qXjPhLO1k_NAqh&{?@(VwHvTT+H4t6suk(;(S8TA)4iKT%duO? z7t~6_9kM^bhQk}Ipu~J0*)~uzhBw<*yfl|#U~v3@(ifC2LH(077yf+kD5!7uT_5dx zh=d8blK`GRo%r)rBtP@#OQNOE2WkBIXFyR4Nvrus()f!$Gp0H6-#(be|M~`~&N`UJ z|L=4f|1WSWA1d(a2B^6oeHvdvZT9OGfhN=|{LOXJ(1dyjQVvdMWMJ^^jba2h_vHJO8=-2$WpA4>?|b(ENbW zmHB{U_emG-15UjxOfK3WQPc*oBfp^Y50LxefejJ?r&`Bu5jmG`X3`C0#xM_@c0o>L zap`tu!EYj{F$-$MqBw{H!$@!n1{ulW((TNF$H>bMe0!r6T(l2)^hOIfV*36AhSAW1 zcj>?X|3MYM3TUZ^hOhM%{^oO%pu{XQ4VsvpyG68JyP17^qe=A>C`DgKu^5!3ud{gm zKjdqDrD)O16iHCZI0Nc^fchN9=(Ll2-0*`C^8L9z~@rin}U58vKW z4xestK6CAk`{CFf_s6l@&au0U#ko5~?}vN0i{2miZXP}N?hri|aErsS`*8CE#>>wy zKLe#2a909c!hyRJ;KXy@mHDJA^Chr47w!Yy$D1E8x^SQ9J_e>iWufrp2aej8FFyih z7^hwqX8z~{mmj!jA9lP9YP*5!G8Yx3f)Oh62dbVQs{Xv=<%h6F8;c53*g(~@K-G&u z)n9YG{0OGrMFpHd4nj&^s0JR81{4tikg1?T)uTB_#eosrIs+Nu+gm08X)Au=Z{7Fr z|Noc4pjCpP4QZg2p}xK496sIue6|1j_NH?9biV{;IM?nNl^>4XF)Du?yKPh)yGv9+ zfouZ`WE)T*+qid!*boY2PzYasL^y;m{&3X33@H<#A$;)%D1c0&$d zkm~b}7k|Q3vw;Evn!&)K<%S%(Al26#FaCn5cEbo8;LPTOJSY+;2FeA9L6L>v@{#z899G`@RyuWu$p3N0 zUOt{fDjzTYaJ>BBB9_(|av=#0GAD2$>C`PE?~1jMB*s)G^muRrmy{sKvbbU|DTy)G z2^?X_@#_RGEjka+S&n;lzXetP9@_8xdU^W5!+j#V5bksA4$*`T_&NRo^?G$tKJ!PO2Nl?k{DK~!4*YSjo)3_*KQUN$6Hy)83xN_D3#dAF?iSJL zKJD1e;M?oK0`&)q*&uWIBO!iw<`?w2&I0NTv0R7Nxcgsz0?i9oVs1?IMDre z`2pB_4_%lKyD*;z@eYCVI(k>#MMZ)?`Xt!5hfv0PWI%NeNXKcAvm8Nt%@jZiKtkX` z5Ik%I8TaGQLnwj_`#JLGfqVq=ppOb<+>bx+JlrJIVZU$*TGF8kGY(6FdS4juICY@CYEbXmd&9&x5J~kN;tfH=i{A zJZQXmfPw<#OOUrfR)MSp2NYreFkAyRwg(yv>^=({4g~p11LRX3kWURjJ~e?1`aJ?S zAum4%rO*eC%!e*Rlc@{yVUT^GvUxPFsLr{(Ejby8x5@sMXn<{!hVpBut%@VZf8*M7t)Y%{sU^sICh@|RRSkK z(?BP&)C2J1%?n%yI^6)7@WHX$8C(Yvp$%LKIz0es`{LN`{Dnx{z_p;$3y`)Sj@`~b zpxRJe2r9ZjwUr~kpcg#lIzekeP%wi-7#wz%+zbppy(05`wBLbRSdQH%9bt_u$L0)9{&$}THh(!_wqFtQX|R<-iWe*Oq&wbh>Am>%* z!r1$AAqN8kXcjfov-=8Y$juehpnApR2%Ss~QE>oIq=K4N9?id4_?upXTU0432A;j{ z9H7Qig-5R+hi~^^k6tJ61gekrKhR?E3g|4Vg=_Z*573p(paqrixzrXF(7l?-^8>Ek zp+6kE&$)KT{&DP%WpM1SWpVCy(f8|h3+MoZ+JkSH~>;80Aig0u|z;#(FQMGfF|=(pym`fb_LxeK!p*=PY`Dc z`oY^;;CVcLK{rTC3pzi?FX#tpW;ycbfv52J1>GR6Ea>Dazn~wii3Rc`)D}n^i(k;s z#I-wA!nHeA#<4qA!Lhqm1>`~rBH~E~Iw%D8oC4f)Dxl5}$U7iU@<*T421ktrx~FVF zN8m(+~61VI|I@H zDJ{-H{dWOmA;^p?uHB(8T)Si6ICjT=aO|%A0`lJry#C{lJ^)FIZ$Q2R`|ksY1y74# z_@hsPya)1IiCm`{YAWwltTMuI}*piD*x`fDR1^QE~9;?g3{}AL~>6&7dn*x+RDSsO)+>O9;{JddtTHN&-8CvKPTekQKR~T60jwbl zZb2Tp`~V!XAhnQ?1+fqz3*uo3Sx0_BcWBT$@(cQ74O@@|G;l%6v4t*32_$$yDzJqw zNW~XWvF#3RvBHaOP$=POU%^7yqgMu8mV?^}qTo{12eo}=0&ZW)z$RS5Z7jrut55eg zNL_i?SNp36XdR}^Zlv`o&fOu#;Q3a^ZdhyU1gIecN`~kaCL~8e(;}kFUUxud zz}s6yX$04pUQa+8;VmwrG=i&4uQwo#@HQ8wMo_K=Wmk|JL3Jj0frJ;d&gA+JoeBVl z=wWc+iGWV!W$*>9zyej5MvmPlkQ!f@7Jv)~HN8M(y(7OM%Y9Hl_}piKHox|~1f5x6 zd>gz_7j$ERPp=4gbx_&_r$0XY>rWqi!Q|8Hn(5hmkm>&cm+l+> zy*&NBHeel|-2tGJCIlS$w{iIPIyg8opL6PF5rRy8eCB_4;FBYNsC#fSNzi}rorUY>T(ZU>O(SfHL0AYj8EkPW4$ zK&FENy7>?bC<>caA_k-9sDM^ldTRga_E9PDWWM9WxD>R8)02Pw85hme1lUqc(EbAr z-(H)3$k`LE|M^>wT>Jmugrx%#<6{paE|~=F356W(TLwRK4s`hW zZ19@dCit0i=NT9nWWXm!-v=G(0cL^DCNcrDxEL829)Q=(f=@{8(>!c`5-6v6)IzD-r zn7KSmY^D}$buW0g4rmLEKyVoJjCRmTNziRH;2PkfZ}&s~7JX0`r4qEVQx&wAV4Gv6 zYi|H-c-OTz;s+?qL5)Ed&^ZT=mA>6)L4gjcx?PxWfYy3~7J5ebb|3NRKIf}_%o8;3 zdvOA&UZn2w&a?c@j2sLMpP>iigK9L`GEZJcP$%ZDXKxsbr}j;sUN;WV(gd$wkuJ~f zKcJ-vp4$KXds$AvmL|wMFm$_!LwhUOmUkWmY=j-pyi#u)@S*fFS9W)c=w9*`D#CN z>Sj^GzNYiwXD5C^mUG>xYdXQAF6SU?I?on?j`!7YjB^B?&mI?jn15SQrf2ur&-?=5 z?F^30A3Qp9R1)~NWmOtq0uA>%GQR+2a0SrORFHArY2dq>*MOTWp1qD7uq5QuTlvRV z`wOU_!U3AY;_z%f%HauGl>7g=z!;tfv5|r6mIL3Ws~QiwTc=w~vVc$S)#}-Jlb`AY<1kt5XU1 z1-zEi6?rKq*pII0D>+Yr+OdxOg1(@h26&(t+|vO057gBFjT*z&ar%P#8ek=$00f1Y zE2yghUKHtT0*V(IkY5x)eo+DWMFQj(8IWHTKz;#@=&Ha*bV>0Gcn}x7jMEi7hKpFm zi3m}|B2HKE7%r>}6^T%SSifFer+)KrG9;Y5q0&-{__;0BG^g2NKrqyq&vXvp>ysI&xk4na`@ z8nOkefkz2w$QBeOa3!DtTd)#%lz^MR$TofX%pVDf`wyPrW#7C0{QnQiV&G-d86Mp{ zDjwY-DgvOK2rAyeW3!-BgFtmI$ZNj6H7Xpw+HW93v8+Db4?UU>aQHBPfCxawW*>ou zWI=P(j@|O0#n$zp`Fnj(#?%L8Onvw65Pk4UYsknf=4xx)VdaMrR&E$!<%bbgZs=j< zhY?n8=wamt4=cA1p3QF<`CHZgqlXn}jjIA;TKz14GiaxeNB17^LSE1AqaNLJAf+3u zQep+oM1oGZ4`=b!zUb5I%;DMmiUm|D`F7s{&6xUV-v?Dnp8pT|_wr11gl#zJJ_p%v z0G^`(kI_RT5?UX1pF8Ho(*Ysf{pofL30DHXC5- zK(CA-Zu;U0x`PcIkiOPui(S2XMf!cTpF4K5h=3|0P_&~O1*(Qlqg6wPK$VlrX;?LM z2sBOwI>Z2}E&`n;3~5z?&ia6~pzgw3Y3WR`mK#!gC^XD56nbm12LHCgOrP#g{M#}s z9hpCXl0GOeI5NM0Rc^*7Jr2HN^6mZ!YPuGHcGf6(bc(1z#@Y^pYqcqmB+%;!9&QtW zEN`h%5r7T2`D$PE_PPoE_ih&}P{y%x?+&p7 zXBqBwh};PJ3pcX^ie_7aL`#yEJdl03ZjmK%wuAfr9LW%N)CO; z0=&TWQ2|$TE-C^}-60mBT?!VUT?!VUT?!T=sMDI*cPxMs2WZR*Qqe-Q0FwFOO3paEIZd~27YiM?ZXXK+#HI!4#+l2H zV6g@*ogh7RcpdAaf*A4fQGpHl9KcWo9_WFDK4kcZKN?dJqyPtd^9?wPKs(4m$sWVF z4-liics9RdD z03E$?5Hv6O3S9Rc^f(BemxOLkC^=tHd?yT^N@6+7!2sG@ehpL% z_6D;cD#81pO3;`2sE_t-XdQ?&JO--Lz#TL)szXp!3fgjjw4?)4DN(UH1iA0W6kGevV|dUF-JPkZ#134jLnp{3Gi{>X!$`2}5Xbi08zvY_;= z`2}4;`&i6DNe8r09$N8$)j#NV0|}$62eqTjK|58@)xYR=0|}$62eqQiLCFSP{fBNh zl;x6OOTU0lEaY$112;y&rv-z;#rkrwn{O}6G+*s=&fP4Gpru;iiA5A6L1Uwr9J?J* zcCLVj30y9*c>F))X??lqphtHMXft++ih*NXJou^vbMUwm=+-8A@Yohgs|hiTqyQgA zdgRf4(YO00e~UYO7|9ksjD(ctK*LCoBEkvN$-h}aU}5Q1$etKH1WxU$C3W{^ty6*YX9=>^<#nTo?^M+3E4T| zsr?PqR)@}fr%iC`4&eirQlL#jphbfOC%xenq62sgA3W)8NW`Q!LLYbt-{T3$Y9c1R z5&FO*_#SUS`tVMA9|BdP;F1i~odngN9?$`N(Ci6#1%Dtr1A{*}34&HkXb?5W4RMGQ zzo5%i22iu!=PC+^8f*&WaP5xdaqNy1aO|!G&2TGX z%y2_|2=W|!c3TlNyAAOjc&9$tdj~+?gH3irmc)XG89*&h&|n6nNe*(luMD(tjot!x zRRGl{U?uSOH^{%BR0nFj!i<8q(|vV7CV~Bnt_Ivp_cZ~zTf((FP{Or4QpT}6Qo*si zQU!h165>U8B+7uMe?b$$ilB*Z(2|q`F4`wRk(kDxcM9Z9(5`Jz%>j-@Na_W7(E?<| zVX*scKn`={&%^Md16bMxIz|N2a1!c8kl)c-{l1`0Qn1Jb83pQ~psTq8GSL;ZnnF>- zwL4J5wL4PBu{+YhvAfcQfKNfe2luRj3#hI@@+p7xNswnvu=o_wVE4U)%ct<>y6+RH z5|~dtn-4mGG8{_!fH&NI!4tRe>2O4<1y9$yLWitk!R_@NP#z>+zgKi&;NNF9#I8Zs5Sg3==uRslW5pa6}CDcIe_ynI`5$NXOEBwuUpmtXG9&mZ;*?k077kk3mVW3lye7o=X z^tyBSYTp7?g`VKuo}jAmuIItmOrF~JKvkjV|HJ;hBD0{2z?`~6xKZju(CS@K&g({< z((Z;V>WG8~6;f>|39Ajyf_4Cd3s>lxnvtq1A$*3q~LVTcGnD zAq_Lc^kX~rfvs@($d&@}BU_+Btpa!-7(Tf56Lcy^^Fa>8FxGSMFqRMKh%6Ts4p5#0 zbsbQKvINMgQhDH2DyaH_R66{5r=VjbCmp+8Sg_Zr{)mw+cU*Hf{*ZAk&{+ftpiIOL z8CF6cG4Thl8H3cR?l|gHe{h}ZF5uc7h+e1i;I317K%UYD)ipfmb*hUBXw(AaKirk7 zJEBtc$5pAiD}X8%knjAFDpenl=_f(S7}k<=hu5n9plSiMHVGq7xr1v}e^9N;Lt?GU z1FuziP-|5el>|_=3i2rSYSkT4t@`7tR^7qXs=teG?|x9X0aP}h1jj0l2t}$_-Eq{b z{@{Am9aOLKkXWzsfC3O!uks+(t5Co4M}yNXmWmZ+?Gv=t1i23-qQG?lI6{#sR!D?G zYE~bW1QZS6ssXG4TwP#R68_*y0<;neG!zyJu3C|Y!r&!9w~vYdcqk0Cm&%9Op|J2D zplKk`Xf>!99Nu*+q$dliR8a@V3VfhL9KP1q_?!EoRV*m6f>uSqs#Z|L#jls;Dr8*j zp0D;TpI(0!NcGAy(X;zLsCxBfKI)-;A6~mUFu=~~pty zmbIWY6F%05`J1OQf|tvHhdDgD4|{4KfEf8hSoscXb-R6oTCgr-KTwf zt66-tPx$xRw0kzcWby5O243K!{n+#WLH}Ns4!C=ryF+ZiBW#Y{?tehz+ui3OGXUrf zJByR=nGo7X!F6b+x-J^njU~Offg;=d~xh{|3aLm7a&cb1&cO6Ku3LH z)dX@GxLqdb3RYAsHw-(?5q9Rxm$#<`@CZ}Bk0r^ ztj29WcD7HZt)3vS#o&4Ip2YhCsNB0)+>ONo4R(|9YYkazo z`D!2c=)UgRecreGB4~R8{KOg#0w>mhP6Gj-SaX={6Kh^EmV!>Kf%a+6@i*IWfzvzq z&=PQg3q838blaFAoEi*bJca z3EaC~Wd4wHfDL$v75M-ghyZw(yL&f_G6@IRfOR4rU<2kuwzh+uMbZH_5WS#QEvPF6 z9s~z1(FN~k2N_1}0X7hGa2;R+k%cZp107%ko*r7p0U9h6>G#zB4hmA&ZWbdF&#nQl zMq>euZ^K;=8b^fgTt8Q|;-wP@=$y+r;N+=q={(1)ai_4sJMj^_raX>3$DxIB383?^Wpm zw;Xt8B29{b0^hkiMCXrdw~Gq46K+l+o!|i4CXG5};)Z;71891}4V|cwaTrq>URmd4s&n0WuNW@is6MFF)|; zEdw2I16sv}dcFEE}~wLA8QBY1~bErVlsDb~eYptTKz zPrNzfO4*4w;IahyJQj!m@_8(v&KLB&8?Yp5igM%^L^+QIr1~1UK$l=E01BA^if zc(8-?!_Q*@9d*OO2M>u@7 zKl^n50PUtj%a2&Xo5T}_sB-uX*!w8Q3qeJYgBx_94fOaMuq?{)LSPXW70^itpo339 z#|wefLl3}#%7QZ}XahYc5J6p47Zvm%cTs_dIYN;LC|4?gLLIV_@ep|5feUEuA|xNU zbgu!gKJbAKXo7~NDnR{N&Vij@>jz@33f@Akt(4;#N$KZfUQg`&C)g8K2VlJd;Eza`q73ufYz7Hx^ox4S( zTw!~&L1%5DoY02k6;N2CAG&i8)Cgw*?@WdJ1=LAA16xip7t|~-@Mu0_09jMv7!w~C zdl+;!BV-&F)G&vP!`3mtHfDehn1)Q1TQI}Ko-s2pK*YG%V9O5)H`WzE=@r!Q^yoeW zE7X0vZ-UBo*X|dfqMgI1`!>h-N-9V)Jp}5_OF(9DASQm{ zZ(Rtg0a8I@J)npIostQfOaUz;1+Sn6)wrJBCq1>#_;kPU>3#^gW*8J7uHErJ9Kokl z+dFobgX&t0#bfyETI!Yk_$yjaNse6dRJ#Jm~Ax8rfX%cw45I!^kX&*t1;&AK^ha3!yX%zCoz+eXm zfLl+XbAjto&IJZbLR&>g|NZ~(16p+J!wl+hf;abp-0j}YqJ%uEf@vM-tYwrFfg$Z7 z&!9ssPW;%6;=rXu=yE1^<|0J1-wLb?7$Xs2H-TGB@Dda( z!~-*v!?oKL-1^6ECg{lE6cxyzN-p}C3fLsnraxE+n#|{cl6iNIN`{a1x#ARm@Fv!O zAbXv=MZ}RR1?)Cs8&?4h!=sL?pd|Lipw>hIXn{MZP5`Zk1T1K`nm@IZYqe54*!!$8OBeY)YxX$HQJ2$#20c-h(;fpar#onj1cdIWycLdKDg7-{;4kJZ5t&>0RK=)agk3h}> zr&N%QAoqX{kpk@j23OV~KY$LA0`CC^AEyRV0y;$sypIC}?>_YI4H%L@~H+f@;EEm{0ovsd`Pyu-k6eyr90E$zP&%q~3fqUe>KHw09 zF1_WCKG1#E7rdJlWg;4+=K|RNDf7E^FWCcG}3{dD!~gtAs)Q}S+E3-NstdfJ^{t(6SzkofK-6a zBlUqSy9Sjfpt)+WlR@iyz(2P_s28TtVRn@-iq4!HZ`>eg_3EEU80xfXM;4b2xbt`OnEocq{r9PE_uCaFR z4zYw(s+R8EK9($?EC^b52hDt-;x7S~)f~HB*nfb^EA;#Z&1a5~JsgP26to-_6k(tY z2P$l!d&j}$7Ig18G}9raH_+B`=uyguP6%2N4ZB?x6trvH1+;72#{yJ^T5w>TiVYeaC0M0G^2Q->uymgVSr6Jd4)Fx2NCB1CVBdl~ z1nz@?&q_G}Qt6`tTW||5ctGVeT$Kh$Gq}p}fovp)mOUtnz)NXC`^fnrl`B*?bQ?LS zCI#&v_pt!gq!tpc-DoFc6ZRl90zm~P#G~+<)kg(UwL&+OLp%)*WsobuAq}rwp z_zTzW_&1K-@gE$!>%YK~EIhTs8iC-Gzd>iVg904X0>aV&0PQa4&jXh%(9PwiG3!AW zl?NarpfL-sY_TbV)C! zD*;|ns{$Hv(1r~C5wpJ>X;Cdw<*E*}S|O+*1}?u* z7SmROCRpI7tsH~zQxSn|GDliW3%c_GQoHi>c)%CadTQT+)~=4-A)t%yKsUQWFM6PT zr3$hW+QkIbWdy2kXr&6$1npshG@(?0pv(oz@Ms-Oio4pKP zHhayp`xx4?*<;`V4gP!?!qwe#UA9SBXiC*Lw0j-w>Z&?SexrHs7C8*^INDJs> zb@-B5f?D2yw17@khcB2tkE1EaFX#(tDf0`0w(7lvUMdXAwcr~7z{_Rb{d;-Ze6{a6 zcZ+a?mTkk9%c2{K>{n3gI|NRBEQg?rWe&e=xg{Ox08czBlqcC zqOt>gb@~?Q73zDycZGvbg9qO>E`@yCcpyk?cZiC@OFKT$bWttn%5l)-6ZFdQ9tH*m z*p=g;Q6BJ>Oy{&UsIIqq2c7Prg67mq*k;NN$gf7=!okg?E<$3a)OSYLGM zz5%;;hW!;oE)3g@0RUr7QDU(1J%-=BvJ~w@b1;x-a{7ANSM- zl@Z`6w9`n7tw0t3!O#5ppz#{SZR3zV-D&(qj|3ovMbSfnH2&<1Y5WC;ZhYo1IC0|> zf5F8YpZV)Um_B$kzho@k4vt5!UKa4VwVu{T_?u_(fEMK4_Ush_rFLKKf4<#Ee6$aA zGl35F09~Q*z_I((!50E)PMt1Nf1E%EJY9YO;yZS_2>%0@KbKzyfbMVLN4hx2mHDU# z_|ojNpy4Ee~&o4iAy!^QPEV%LY5Hy_e$Pu)4 z@!$)AgYN|nz7}u<<5vQX2VV*}f<#{lq&arFn7l}H?F=z_mFC!4WAY}=wbRGsU7Ay8 zjM|4Zw@w$ePid~5F(zNq96LiyzCn)O$%Y=7U*Q5e;=u=WfOUyV0;t5u0JV_{T)IJd zpgTq-!lm0sMWY*(u0W~T1A3F0C+K>2eb7w}-JqcE^VPlw4(cmD+86od8DOCexpV>) zy6B+|;{O93N>+OLO|6eW*R_HYWYGgqVmy?_UvL09F*@B9X79A^7puH$52aP1BK4-OFU{h;uBYas!0+ZS|6C?r4_K#QqA zJM!m?fWiZ5>JNN#Ey&y8hCFC>bQ(YGW>EgyhhP_j!WL44@3a8r-9w-&Y(bgyMjC(7 zV^Fx9PU9~)h#W4C{PiK+kZ@^4370S6#o@m};o_@se6(+Yq79y3K;h%qE$H6O z!UqZ;(Bzvh@`RpaH`1bAP(FD8&J8aFKtp~ZEwz|2 zGB7yyy8iEUf#&04&`Lz`6%9Tr7NGF}58v*0pkv83e6`<$n;pkNxx>S!`vJc^cr$`) zcR1R5A(Vp_W8kA~pz9bwm(XFqk@7KU*--a6@cP`_IBul8;n)j0>e^BJ5-2McT z0Il2tEx&=PaezIO4KBMAL)y!i9t566SA1W-dO&Al^32OLg1;Be9bhm#ICoOGaF zPtd_E;ED-SZ2EvU7lBLC92FgK=?Y3o8K5p!0!n#$AH0_Qwr?*FXoV#>XF$tSQ261` z4@j#m9WVX>uW$uL_6q?}7`S)3pcEe)UgonRRqCJMl{)BDzqv@d&fANJ8c?Ad)1vVIO!CLI0@-Yn-t|_W+m9 z5ETn>N@szjbkE*$)nkLs{rb#34mON0JZ1dFq2X6d;(;8IoIw}pgG>dm!EfE?mi1D-<37DS&K|w4nXvrr0lAHWiGLgT+Ehp8vu@oiQlL8a;17W`*G@-B z)AokP|Kpy`hgm$W{}inQCm|2mn3?t!*qsc_f4a{hE@puALcx7tk6syXNGG`U5`XK= zkDxPtLY(^FBHO8*;FtvFhDL}0A0fXS!HvJfdP8Gf;J-q17wS=5hDWw z0@^6<%6tgahX5@WuK=B>nB>`g%d`8p zC#ZdV%v1ZHNB1dMyn@ONNWYXnpNAb;J$nl|e6^qXf>y$R0M%dJb$?*pR5#G=>TcaWdZ2Eqo>O;-9$GimvHLu@ z0s~cBmmiVcO$F6ZAJSYqT}-faQ**$jM2SiSsFVO-tz7~2yNe2VtqZ7L0_Rr`>vP4n zzS{3Rds+H?wU2pde+2tSz@yiJ1$;tt_xXdbKy_w_#vjM-5QaaOAHn#L;JpG0I|}C! zps=WLWxfG2s{m9Kf=aV)lTCgjLt(1B;Im-t&i9WT&Kb^*8)jZsMe?HlT>Q9&*+OH>4WyTABq ze*}-pKz1^KF2)31M2>PVrVhB21Xov&ksH6DI_%&p&@o`z;MT8mw~G!U@<2Rzg8_@$5x1nsDNrY zEQKg&ypsVGi&3DMC;%0rp4t~dg=lApN&z$~VNDo}m^_ablLgS2RQLy~by=V>368pW zc=5^sE;1qIB`DCrb)5qLw#rP8?pKhi?gwZGjwAC2&<-45P=N|Qe#he%)>#Ws5flMx zfdzQ9zAef2>^|q&eaTb%BxohH_C?SfBBD_QI(!GzcLN>zz{3P>gn_0IkC9gJfXDJT zdGzXlcg=WMzbHKHC31wO=4rJ0Ryl#>M$T z?gXudm*{}+7R9Mqry&1rqYJ#NllA7TXwwkDKdTMQa=(Z1)`E7Inp{m)bT1*m-s z2`?u1ZXPS&-XKuo1w|#O0oZ-QmHA}zLq-?wlc15YUJ(vg?GxZNJfMC(sL|ot=_3Um zgOGxR-DYSJ00}$zUb*h0pfm^SF(iPd3l)4iT~q`>NwCEOJcjQ3zmrD=oboEbBO=i2 zSGv!;bla$SXrJ=!ehkvW;iLV?qx+>#Zx#ny_`2|KbI5cA4-VJ%QLni1G4c$IH(^H&b4I*nPVBA)_nvNw6rgk6g6DEimWK5YQ;Gh&V{(Iw*KR zB5#~}MdUyt_h2F)oO?ysT(ysZf*7<#2Yk2vBTyv?3RX}<05phl@D+GeodYzg?xF*# z9elu}#UbF);u!E~afr?r{?MFyGvA9z(MHR?V|$(x4fK?(3HyH$P&;5oFLR z5gb~s%(uI*H9ul>;lAbA9isCEl2NU_=*vlj4sC^AIlnxnH|9$Z%csLz2dkG!O1e*sMQg`pf3?v`WkotQ@ z{#Iu2!e8ha6QD8=G(lMa>ZnzK%3<(ex&l1Ebf>5!_*fq;E&^RV3Ci_8+Bbc)e|l=a z@aq+6L`#3J-Jtv^Ldit#oj$^#0w_cnl8M%#WFpWEC8Xz=%>Wzv z03AaJ8T#<(KH}5qq2SW#AmPy&AQ0*ZTH6Y$3oJZ3BRG6OcN`Vm@aR4T9+>Wa;L%xe z!K3??PiMgi&=|D<_}~Fka5VrqZo{$H;Xk;$d6K^+9$Z~O&S&@Rb>-;RQSt452s)Fw z(+6~o*jJzK7mobf{3}7tCA1@EK%Gn%P$!c|$Gtm7hXs`RKpP4kJ9fjSu|Qfu#q#9` z%$K+?fHMPV9;^E-Y13GG;E5T~*duIWMh`qO0}_Ev%;aX%cpl|B^+~p7aV(8gpp^lAb|y%vqB9lkO)d(5jl&6JD5OKDSs<3ILm?7-hkHDyL6|3 z#x6j60O6;*lpv-BKusVGP-Dmd)HX`+09|+q>ihe^nkdle9AE9Tp3FymwXcF&LKPm` zFOf2%Yc~%gD5NS~y5ap0{%!u5pyY*8ot;6;j-a9!GMwYu>BIC7)Yhy3b(okSd2+c& z^AQVBqrfo+GIoHyaqH3j%Cq|;s8Q?M4Q|wWbl>vm{t4PvzVbKLMlE>$#I^Mse=9dQ z8~Uh3fOq9igfuTexu3zO`=F2ZG2iZsKHAqjy07_k-{s%t4rzi3AT_}b@NaX6HNmL2 zHUTsTFZ2Pl3bC{ksU6JUTnKWn_DPRkmX#jbA3e11!aIVXG6B>e7GVR8oPir){-C7W zeF9n&pfXkYUM%^n`} z0d1-O3JVSqK~TF?(zn-B0K9w}5+1NN7AQ=xx3S1so8Zh}AHw(nv=XMY0V!OHV?p&q zFAp?Kz(a7L@Nn!FbnRv_1hq&(D-ryOTZ!P_=^_cL8eAm*cr?FeEZqvq88~_kHt=2p z=yY+&&Q=e2uOSCsc~mpPsu58Bfec0B8W8#n9~OTJ85YMjAXK9Q9}vp&=|1k+eGfJu zbQ5zx=rAaTL>NG$WX#ZR8F*|QbgUj}GrY)yL5IL;@w^ZCRN3zy+MmGT4jvFXjXZ;h zJfrIj>WhK8)r^qQ!HeK(7d$o&>bOG3#8HNUUNN~cqYMLusBnN6&)j4HO=h~N6!><3 z1dj#zXn*kS{tFrh(*EJmTfhN!4`@KhvHJ{sO4qg9#|$*3Yv$M;Vg|bF)(mNU=2f;G_N02bASMdGxYOL{x4r-3*|StZsO|2M?MvUjQ|2L5*Lh&Jc}1;GrK#h@Yq7 z(9a)OjKGF|e6&v>#>zpx5CNaw3=YuHPxnI~?GLbyJftmi-W7aeUMz!ScP$I*a5-oU z2WcdT+-P#{bb$``K!$lB13X{AgFuksAIQKDWXK0PZXQp{kdFv-$md$I4>Zyq`e>i@ z(1t`B%)j6`JMY*n!U!#^(c%m=dJc{=1!$asa|sm&d2Ztw)CJjjy)zKiOZr}jn9 z?t{MC2YtG)B31Tj{CUT)Ef|3uEJ0*x0$vNZ6}bfW00mPo%Svz(zl&ras7^cQh?KlR z!(xc!4XVfffX2f>1 zPw-k3SI{b5(AcL9m;qnktk289&`@a#Uf&E_f(&YsocM{gN#e-A&E3@azvFJyHO)6% zI%8A}JX*i;w_N4{t!ZAP0-iVST%rP+F9t1`UIAU(ynum$!LhkMfuU5?v->=Fz8EyD zRKvr-@G_H!fx(e~TRpYbGP`ugs2DhQ`zLsGzXL61c7d&CcI4mg9-)2ErL#mu#l!kz z_YV(#_m`lOJ;D*Zp4o{Tw5HF!0MuNJ^6Ayt;o02|?&o!a8mPT0JHX==;04V;IT#o~ zCrGzEVuLPdW~`JG|){=kd@5&htl|y4yN&EUHr^n z@A%*|e|_KwkLFj5r3XBEMIhIzTVLgGzQo1A;M>bG#aH_cER#ETvuJ?kL|{voJ6SG6 zE|TkXxeU5G&cpg@(e0Nrx!`U;i*Wl(u-iL*R2)DnM-;$`Y#V6Sqx*cvNZ`}3fRB>^;5lL1=%p#WNgsRHU*Sa?7$%<%w))Cx|dkaA!^RAj`3S0*RY`Zr@H z1_nn+8qfguo6myRytUruZ)FC}RDrsR7>7a~^#C2Ra~5>awmh=^pd1J}8p?@3Uj#IU z4PS2yk^(jI2{dZ?|DA@685TVRtv!Csn8u%fFpWRya2kKs>CgQ2PN3xD^#NS?P4t5< z$o8>5%ijz-`NIzyO1{ijeYKyaO#o$)O4Rd+Kti}qcXH|sInCnK$#UBB|3M$?vqcA9 z+Ho*2IQ~BkKD>DvQd8cy`-rbLawtINUqGIu%>SU-SS$mlpfOXN;n0Z^4&CgalRTi; zD?!2m-oXKRz7pjyGmr>slm0Z?aY1gKE+~Pr`=us3sL^;7G~*1KP%Ln5z0Kdp1|AUg z{@>}Nq5z)s{|{a~_!gu>!?QO;g~L<(4X7in;L+>N;n985r~4Hoemr`k6(H+l5ksJ$ zbAo<=hBW`Ub^Cx0Aqvrj4#hma{2aPK_8Dkc4^(7<*7zdEVnBlnj@_^`e~&jmV07U= z(0vR{pKydSzyl1IA2@1X?sUH4)XTyS8g)6|>HNT{mxT|+KG*5|!nv14%vJju$awH6 z8K5P^pasJkj4qwQUpj;DxO6%{aq0Aa)9L(yztsRVy7CCrdIMD%jG)n#-fYmN^`5;k zp!FJ{rPH9$t^{2M>e3zkqr3V~x3NoiF^gMwi1rV+ZXa#XY7=dz?htJjkKSnTI0}Ye zA%zWig!cky?e`<_ILZO$OWhEko`8Dr0K})<7o5Q(DX!X=J6%B@vVnL*vkSLs{937{reNVhPE^Gi%Vzd9hXkuCoY|?Z#qGnt3cP$TSA+17SOWE1+;83 zM#TVJXn~6_aA^iE-(V*og7fER7L0TWiGLE(BYbf$dZOI+ataGlp>_dLu^oj}Y~h(c z-Dg47ccm-yWl#y{$ow0$syE0NRG*#p)jsOkeblr2B53{%T%myqFi>v~TZIN%&I`(o zS*OzYlMcZuHBU&Tw$~3j+ykEQ`O6IQHDoT@m-(!(_EYfG1@t66=WY=m(DWO0Kbm7V zxS!|L8FCm@3>^kCt@@<_S>tb7cMv%3lthHK3LJ2l!h+wJE4} z^VNO>KJn8LbfxZt?)T98+OfMFd^L_^cl{qw1t;&?9S-UyAXRVR12aHJWI*dT&?zye zL8DNhBX7=Me&ou07_@=^67wAna~6=-!_H^|xDE-DVtv{a&E;e$L|c7O>z@SM9v6p0D2Hsof`5kzX(i9a=Y!IXfi&=qt^yBRLcNa%J16xt;E(7w70`O z(}REgG2iZMKD|86@cPpevhY{6FT=e4NF@ z`fJf9NW%@f=+#5}t0&`nP?y4k`LAR5IS>BzXB{>36Fi`MFG0%m?q|@_*ENuBm)(#qi$^!K3)8&?ybTk&OEg0fwU|@hOdIjC+ z0$KE$2Ooedg)e%of)Bu*hc9}SfiHShhA(;rT_6Qn9J>O(=yeSGXL-Z z&u<;zZvpN11m&9o(84YS5AAoLGtWG_Klt_*ayWL^{qgP1RdDQ9v3fXe0_rn#m zmesYJMb8nmmesM_4O9&Xz*kP50p)J!%1KAi9&pD_*Bh>#p?4fRYah6F`aW^&jD6wS z>H5aCGxmdHXXqEuN}kfmpah-)atU~eY6a*JHJ|Pr(8LG}I8%98A1yWojfsHP>G<@r zG=gra76ETl0on2hH0ahTa?G(i;Lqhpj-4ULSUmn8@UT8wbQ81~NCMQFK&(dv2bTx~ zEXg3ofJ4I^L8A`MZzLc~tKiE~!Onv&NA(5GD7t{QdY%HePEUdwYM`738e0PS;4V8mH||E#)8^ydZ2cj9;n?0StU$x zO{!z3i^>hx&JdM5j-53s4_rHaRGv6?#;Ck-?Q~Ij!^T2h66P1BmgNyp_Qhm^}%9mP0gcK!uou zNB2c=W_JTJZ9WS~C3N6$Hr5J^b zWA_2jDz6sMT7S^$F8+2}yLGehfmTCy3xcM(Ks#CBOF&&aS&lh&`oK%3 zE1+;9p=1h$mQ4zvvdO3W6>O%2P{9Ol)PokxhqAzj`$2^~M%bT&HRGTwK0&I%VGm-! z!oIWaMyKBwmrlPsopBFbI^CYQbjH2t4143!83w)-yR;ST3sCt4-kuQQ(G6P0nW7>9 zDw-rfnJodf8N}22Xt4u00>DKRPb0$p?%gc>pd(=rfqxFP`sRf8DVI)`W6-eov_4vN z&7=8<0;qg)jD?m^*eA0=D>Z$(KS5V&dLDeo1YN1=*?rTa`xR&edBJzABgmk99RYG> zfN$%!l2D)S!=Bxje6$aGbc0Ucg!B!Az=NF_odM|jOsv@$JZKdKS=hGAtCwXfX!Aa} za`frtY4p&(2}+Hw-7J!zt_>_0;43j*J6R4nf^spma=Zo#SR8Yy_=o3xK!YlQ*<0R>Kcs1yDde_j0P3>qAC?&N_D4juv(rl6G-;D!lk87j!{j=d2`%PlYQw}5&q zumJJtKIf}_9z4~2-M9M|B0NBY4P2zJvg8MC(tr(^f>Iuck2X|@6e{4g5Z!*z`DY){ zNU|jO=ysXupbPOpJNrEi;-)o4a(Fc!UJiknM(WE|+ zizd5yRN#vy9XmsBIQ2r;OFDLjK7cQkbnFa$fpwK6Xxxy4(Y4d{i)*Lv9oNp#C$62b zZyY;AKk&DzfkqD5K?8=M93%wlQ*=Yapf^W_!?XLmXY)}G@U1PsK)1G_q+d{QbjSYa zuKm+p%HYyn3R@2u178oxqXVsipP?;>eB2FLL3-PT`zClvDQNc;cxdsGEAvU{a>!HI zmP1N|mO9>c?DW0i)C*k+>DcM}z^Rvq42*I?zIY0c>j=VW+V9SYIuU1r9OvIFGu>DFIj9H)B@g#* z78$(h*RdOOMWb7%&t*_5z6?#p+dvg9==62OdTP*uCiLZuHSiTF7|R)9^}Yq@#9mP8 z=nC3r2)P@{r~4LYkt6tcr-B=xrHwwF2`49BzKV2wEl! zI>`t$%8fdR-p!-pO5DYHpiw~hnH-LtZlJ+s$Z;Hwoo=9Ij-XX}APZ22(LvcE?2BV( z*d56Dkl!1}PB+*y=FoWr*_jRVw=2OTnW&Zqk$#K-jtSgwt7?+!5m z?G-fnuH9Vln#~t;F(GD7aP9Ot&f?f9a@^zp0Z;40Mcbh32jd)LA?pVP zKz%(4*v-@)-Jd|)UqDT)?iZl$5~x=QxsK-g53Efr&|yZPvJTYLf?Y=ajlZQ2WpeE> z_|U9N;CpAzAzF(3+YlGeh+wxh8)uHDd-jm_ih$R zM|j79f15Y91%sgWgJWmNAr{9@og$KAj#h)*N+_(qlbnkRI&*I$4avnV6dcJ5cC>R_-4O|aU zSOkDu{GdCjP2gjc*6^)G4)8I`WcVUM(4A+H73Sc^E~xb&09qs%3fg)H-)8g@+{^#O z-vV0y21>U4?Vw>!7tpR+{Js2W@D|Dq=Mt^9xW3QUD%f1a)E&o&8GCwpf(T{yNb7L2ns{Z}(TvUT}5u1$?^3 zE2P8^+Jyn_cgCpv0S`9HsJM2AsDLJvKuf||)SN*}!kxQa)P8`=Ur+ z3+Tib_>9tVmrlP2oo+W=I^AA$hJEO)yVDs5>T`lRoL@le{U8HkkikZXXF&I|fSL;& zuzse8^|4|**t{cb!VOgaGPrlM@St?^!6RYJ7hF4Cj)6+NW8i#wtmrx@aKJr$*yc{q zcGBh}8jwK6vAGjg-{b1%gVJ?@Z}&w|P=gXBWcej%2phDa8`0bM1l?oN3GVH`^6h>I z?(0AI>Hf(t&*0cy4-N15KaSn!ptCigQ+Gl2oDJwmPEfDk<_EOx3p!2@G<$azHhXs( zY4(nASWkd=?h8P}JrUqwhL+kmyZFYwy*y36+P^@pF;KrAv?(86I6?#51r*?&BB1aF z_3Cec^LRYyq7VYTdeDydSD>REKtqrR-!eHe{{ZD(PzBWe3Dm1sz+UBm9325V6$899 zF93W&)G5%OV9*gKMPw>Db^0}=SKsVGtW&?0%+ZT+b`#pMDpqng@615-0=zHqasR z3=G{pP2jNx=2QP48Xq{k zVDb}~{0Jt$gUPR8@*S9b4kn*~$!B2lH<)}1Ccl8m$6)dUn0y2#AArewVDcfDybmUC zgUMT9@+O$P0Vc15$*W-UC78SmCNG1@OJMRMn7jZc&x6TxVDc=8Y(DY-@B~os3~9{B z#Kg?P#LC9T&cVsWtk9FDN9;B_b*&rZ289!7eE!txqnHVV9MYS5Q<^ zR#9b_Qxg$XR8$mFXV=hFXV+ra)@RfgWM|jW)zjkfk70^dcw%SAPZ)_U}RuW0khsPGB8MiSsxe~805gLFN_QfGGNvZ zMg|6PFzXLIK0p$AcRr1|Hoj!L#7j?@L8N-edqZ z^uRl(V5*vTf)0#fC=~)}&H>Fd#i+pKI!jb6KvVbsO9eeTH-ZcTVTPBFLF4Aul@=cS z?v)lE)_=z(=62_Z?z7#&3EJni4_O~9 z6Ypi=LRSYmskph?fw7d=qx-r?_i2zJ46psaHG}588A}C`M7uw<9w@bj$S{>kcr;gA zFqB~^kb)>+DV0LgTEYpn`?aJ;_f?oxtp`fEj4#1>-N!(6vITm8fsU$rBm~NKPeErW z3xim{85kH|fyF?x_AkLKGe!o6r(hN+i`58%)WtC}FhE!yttU&^j)QU>1L%kpw&P4Z zpaYXy50tPw9%qneU|=}x(F4p5Z?W`R~~H}ir_(`R5{UUz-9c+lb+NDg&lU|_fdj{yPDQO^pX z4YwSjVd3B{x{#T60ng-@9^D6bg6Fx~{lcS@N5yfE185g~uL!s~1PW%P?t(}6 z2Up|&j{MtNK`voFz~8bM+^62k#LU3JFArLLE(cmj+7b#{NZR~ckiQwUmC&)*`~PlG zqP+ar)%eo?)1XateV{YfJd)ixT#ZlqbRXzG;d$@}vm^6iA7=LEhwPp19MG*_R#)Q#uO0ZeF*)u4In43$50C$cnh*YP zQ9b~1nhc~b?RfctM{f|LEBA@!mmDtq+nhiSeD(dNF)9(z zj;lxW5dp~j9Qew=!w$h7&2Iug-OvQ^whz#TyY6402>}n!?hByRD-J&0FG2gx48VES z)%cQ6FL5(T z$+P(wlM5(gzXab{TB2eBsx300vaZIrL48mIP`?>GwvqtKgUdm)`n{8#AbHTSq3Qrb zNtq-6HdjU!HXF85c5oT_Iuqir10LNMT)Izy(slCzCQ!mgBx@Gq1FxMNyHA1>_2nP@ z+qgXbA5uQ(*z5VL^<0o|^{zpWb-L{Jkz z9ar!PH3iluid8*&CxcRqN3Vz%#68ED7(9A|n4pUPAGAJE^Twn3NI>*q9R>yldvG~u z#=yWJ32Id`eag|G3ydONrZRwT zsABePKFs2H@fUxq4`_|}|6`uk-}!q4z{A@)pu`Am%7MqN9l+&8O#ESU1_lPuY%&Xj z1L#ycPiBwqU#{I39e0451&#+_u{k#XVlHKMH9q-T+_Cu=V=15G!Iw;q%@27Tn-8+Q zW_Ilc9dsE9+B|<4oMw5Tt(2Fbwa|{=4s(twKH+ij70bcbOwEs(9r?E%_Bi;0x%mO3M=y(m2lEM!?1TIs zy`aNG89bN|fn3MGjfZ2$0R{$!UJ*`y*Nd%}_+2hQt!(~fQ7qm3n+eL@32IU~?%)8g zTy#13TB7+^LXogz_lf4;j1bp3^1Ge@UrP|9BH(I$!jXU5MMwT^C;0s@x^#f+@Q&%A z^c{Z~bixu7I9aZ|?hV6bafLcagYMb@t82YfSMSlw3JO;b z?SnqOjusxxFFX*V>z=(H0>0fRAe9^FoOgj7jtBe#A{_hzijMpOlKdKH`85vlAGJ+f6T+r{E?u!J%-QxF=t))H62d4@M|3S?7|-jZtH&L zj|5jppI!JPj=S({dhGbjAM?Y7Uo&8Z3%_Q>3>SV)6%`k-aS;et}9JkghAA`6IzML4cIs`0UCbbLKO@ zV4=WgSN@0xpI!I`99cd)@<+S?sXfiFaSNpE3BLx&2jH^hGk?q@kb)Z^iGz;(8b{!+ zhxyTkKjsj>#?{aKG1vJuzI^77`Tm(d;t!H!+HJ#C576T&#LkgHB#mKd4C+}NFLB(#6I2U__xP`&d2DiQPJ@1 zz7A>=DKJNJcyyltw}d>pr>KAw`gWi3=|0vOqoUy1eGV$)qGABrLiGQ*NAocS&*tOm zo{h&CI2jlmt^d}{1I>zqcCmu4d;}Q)ntldl7)SnXZQK$J3@ZdmA@19W3T%3nHU&cjZgAxmZ&&<<`)3nzQy3v zs|qSpe6^qZboM~*YVB>~lwe@+Yv0@NZ+-4eDujpMVs(2Vb&)7UF=4L571bn3^9j_lkHjU*O;7`mgyH6aSQh-)>0% zZ+^~n`FTSv14D_a3;#CP|Gg~#eVAR}dG<~>kYiwAKF}NgA5^3_9(=;Yz~ISz(zBPR zAM7Kj<*m2tvOIeIEI`+-^g0UobRTrw32NU%ia;h%5y-?Zpy0?aAVIhYaNG$BZ5QqHJ3xnIH2-FH;deOztzUeY4}cYV z^!ni|R6*GpXQArT4T(f(=zyC42A+)vLFvG!+eO8~v+-C1=t=@eqJu|y;~P*K^k_b+ z0Gg@cZ#De)|3AM*iHZX#MS}KMgBn)gNyCO$6qFlcF(JGeQu9+bbqEPl|W7nlVa zU-1I792prH>LF!3BdqcO4bXwiWMTLP+D!vi$H>IM01@k8VqgdYi>+p2U&_!S2fM$iK}|2zJQre_Ob`5AzAf|Hq*b<f_f&`LiIP__v4ixb(_+Iv#w?1U1Gp`LIv#Vo)M>y!^_C`GG6{HWw8} z$L>Rp{M&d`W;%ZV0b(~FX6Xn$1>(8zuRl2R`%g#yZ9Xdh9lH;8gq}nf{s$I5;n{qI z1xdD6jqT$i&``okpDyWE504Gcr{%zqx zt}U1NTRIsS7bz?s=`{U{NkpqVmu6(?8eG|6ITP zbN$-j`i-f<^*d98>ksAz*PqM{uD_TYTz@lpbi3Yn={^U$lMh@_fR4@rjUs^-xq+I2 z{M#IuL4_#)Hg`T3ewULj+9wcP~XyB(6jj%3-bx!NAOr%E-Xr3eKmdObiTn zz_s*zCI$v`M$q8@e>ls48MaRdRC9BHr}#lt3Z(i*9is|x<=>vc<M#ZJq^M@n~$CO=>=Dguo&mx=JD5sf13hm zlDX4G#lodCMMcGve}4;T8y164uZ=4xl6{%~x$$q?VhCb`hD97e2W+)~2L2uQfJ%K= z?PsukM*Q0tJpUi~%r78(@r9*}iUQ~|Cl`MI7hs=xG#_U1=$xYhDi0xDT~L1C0cz2D z9DE_|$iLslz4d>k6{5NA2s&i!gyZE$F5Slu{%3YNAnHEv(+h5^9{kC%7gEqXa$yGDhy&RP-+do+VBTZ?=HL9K0id=1FF;elmme|T zaAiJMAqLv{%M3o%!SnxN7w!v=%`Z7BUqi>&9lH;KZuC9)Lmq5Z^Mil?K?7PRAlEn_ zd@XtKg*<3`1HZ)Dk8wC&e&p24!sx<$qCyV7bkz}Ccx(TH9CqoV z{T3F?bqX$^vr{2S85+PF-~qf0e9!R`a8ubMd5#LGW$vhb716gm#sS(Z3Cad8-JhW2 zUu(d7CLJ4UH+HW7wzhd4^*x(|UfLGuIVgRhviPk_As zfaUTF=0jj5-KUx#vb!*!biDk?v-`AX@Ir^Z#+ji|_bbb~7+AeC8JjW$gRab=&XwB_$NvYv z9danaBK61d|3OEPnW6|YOS0kSeLJL3B8p)k8`wMnZ04~z{yzxH6Y@yu3tRknG#?Xq zdHCP||DYXf9H3+m>RO;T>_9tnA+x}z7#J8p!&=M?pbg2Ot*6Wk_ZS!$K)d*u8CpT} zkKm#nG&|`5F8+m>7#R3K;~5Nw%&@uSJZ6~KbT~_q1=c9^WMN^#ueLgO1yZ|XC@KqL=nAc0@w^k;s7LKVnZ!nM!*0+paahV$S71W*qeGF0FK9pnIy z7l3;3fd(+Qf%?Ojz(WY2?({W~WTXwOOXb3^nb5(n0V)PTWj2Egzh;h#fD6B-iHa+~ z#$!-_9lXo~G!k$hq*c>}KO8&;;L5M@6(suVvm?KN0LbVMF8tw;wxz~#*w}+3zsBp& z`~u(t+?8MB7)aemkhX&$)&oa=jT@g``6EGN2xmZDc<`_SsNXLDvDT44@)oG??+Ci8 z*^xgItnDCZe1Jdl{Ad1%8=v_juYGpq7i8eqIQ*GE^8RQ3NYLQNtwoya5^e9XtrZ zA93L`zhD;VN>+gg4p8j6@JBv#YOcYrlMU9<<|&a z0rCdOuLYnX4-*xT#6?$r4euQwJ2^h{N8AE=jK{P4F?dWP0-UBmgJ6(!2HJgX4i>9} zr#R42GZ0x&(FN)K9A#0Gc!y7KiAsPYzh?CcPwgAv8ADI)AFlkG&NFhta0~%-yQK&$WF4+pg@ zd>cU0UKh{}x&c;|fiNhv1*9JjjIfNstUusI1x=JbHg`2aTu zG~4T<62q@?2xioT&-?;hpZP)Vj{ptN2?YHCn-T#^xBM}OKl2OvPJt#u7nKl4{_vZh z`2}NVfV*R$a0>vlKJyE>sDymx7w}Pu0RCw_qt12BtUAVvbp;F0j` zW#M<>7hv>cKFqIikYD3Bf8-G-et{AV75>Oy{E?^lBk%A>{NUF(>c}5?fIsFYzs7m~ z$iMuN=lBIB9r+_a@W&kFkN5|gwq*njM)M#0FU>D#<@) zeCCgM_nBW%%cJ=fizlc*+UnVSRK>ITpo(YnArH``k%njUYZY*Q0Bs&pVB~LQ{R^4~ z)93-Wb6of}TEI7c^K0x;0gd)YX_j|dY3Lm;@amV>ioSy1|A zps6nb&}^*)Xa}nT^;XA)faHCI&p|NkE(0IsrK_%%0xx+Wk2aG%kIU$aJ~0K^4X(BKgaSAKyY z5l4Q3q$iI20#PiV9Qg&DK=Teq9Qg%`zPRv*gWCz8K;>KlX!604U%=r7zb5DuSbjmL zCm{76KR!9~3wnLwKlbE5zs4DU%@`F2{$sa8`86)P@N0&s1b~|)prKs@{$t-(eddoi z_nAND3#c4%;g5i~MLvV7co)!|4`@CEGE3sfukjMpaya>!KjNh$xE&C4+Liyr(g{Rg z1`$U-^T)jU44MvcHH9!?qB&ZD<32N2E zoCgK3BY)%-kn~}YbsiwqkDyk98ZD0ef=L|Ul<(Oa@B(DD2ABs+fB#Q=HXi_&8m+ha z`!7Svh#Hjw7f^u!8kch6*H{A{mv`aUSOV_+xbSO0I!iA68dJc9hzq|)54aQo7b~D* z1XQemiV=`0;Nqk42&jmNKCI5bzyN8SfCiu;jgBw|1_nsOCY^zS0n#J^4RJvlHlV@- z(j)<$A^>U5fR4(BG-9+sJG=rxvzE2+p7;-;lg~QjlOEllnos@r?S9c2qT=A&+Xfn;>I_k_ap}GUx)&GJ`Mv4ddb^~;rTZ&?_(>Q3 zZQ`IKURy7fxbn+`=D`J68u;}NR!Nsig3_2v0}F`9TgnZZ!}PR1RCDLG40wjU`}FtM z;C?jogqG(TVj4LW&rU-Re%%?os&LyYr+_RxF4E0^XY5ujm0N65&3 z^kGo{4^qKQ0j&W8$L>N#SQP@AU($Hg{ELOZ2Q)L#>-6W~3nrglN0$EwT)Ho~^g<3( zLv&R^!R!Ib-U0zEkQy3v&ksND%!WCzmb@dGs34QX7sSf8pj@=QMHsD06+ zm!}allqJCF(F>YAJ@}bF5@IdCpeLx6*7%2mfdM=iee1AG_bKR%3ux+Cp23m$AzVlP zNS3D0{E^`C+vEHi2M>dm(tyTJSr|N;-*AA&N(Fp+XM&QlNAfMuDy#03z0QAI57fDO zYM=J)KK%d0!52*b4}d~f1ia%2X`DuY!--!&gb~yli#Ws|a}nIOa^x2j@N7OH0G@}x z-F?|n`;v?GW&Ylk3=9m-zc~1tKy#42&i{Rr4}s?1IzflGblRwR_p&s5_S$rN_VRRk z_nI_%SRX8B^|1a|oZa|{oq?gDmi_;!=2JiZ9|9e`a8mn_NAd+v?bDvU&H`S&EDfL$ zJ5U5VHXq{fY(BujFA%~AicL@#KjtF8pbO&%e!+kbkR=NoptTAS2l+t~EFAoT0v^o= z6nv}?)$9eWijwdEUC`5g+Bf+UsIeg6V||go7j%TBNB2Qb?L!{TKUnyC_JJEw2R(b8 zS$wq*dG)eP@N9m;;@N!>bmWVt_GQoi2mE_Qnn2Eihu3F*0gpeQ`2|9l{(#dAsNRgZ z2x=`efrL3e^9u$*WCTFF#Ng5_Ody>spZNs^!1@GYm_SX$!=L$MK<#xG5KjP9H3&vP z61hMK6R4s%^qC)OJlJUP^dqP_2}z7T))#AyeYH<}_p(g()IJ4T+zLwU;MfF>jDR&e z@(Z#sf-LrEKEMK6kpPX-nS zhFV_79iV}iUXin)LD*g%q3%P?5A2&CI%pqEO4mASxNPW6L!K0gh8(Yf({uaUxK%B^KX+e zXuVx3@7sL@w456v1?G4{=k;G+Wnf@vJy|KkasVO zxQF#o{+<$WH>*UY!lnBuH28d=cg;9?H|M1xub z%s$;WRSvisKXEm_4J@CCEu|0B&07?Bns zI52^xWgjs2%6Ky0Fg^ep=RV+Sd_en#XR0+t`Lb)LH;0S$`8pQkx3A6kw=wMn#oy(>pcRuZ{vSltY?q&c zmH@kO9|f&X4WGx?%xZ-@#z$VBH}7D31Ela8Iv93aDu4>Uhyw?1CS z(;cJY;o5xxy5Nn;aX-i!$IJg=3yyy{GM{QaStk!q&5oC!gDiCAJ`P!PTm`c673erd zhwnE*ra4}I@5=q-bvVdY*IpkL4o_xbN9Kbdn;os4K}qZFYf}^tf!18VaAZE&da_P* z0v`W>rik8xnl{Y)K#RIm4(tbA@@I3Pl*RGja~4qWBCq6aKEUJ4%nVM{&KyviFTK`B zUCgU|q4^*Sz9qbAj-6qkHRSx;54kX30NLTw4M`551?Al*Kvo>!M7PBZ-Io6cVXZ4d zmT{$7U*~VvVh80@XOPYC)$7d%I6$_6aw(FBtkJD%Jz1iFw5A@PeU2Sre2%*~K|_kk z&K!>4Tp2mQwz=@TUITg0mzmj-`H-tIIN5;w2#LQ-uhIMmiogqygd&R1K2TutK-Wd4 zIUaWt6ay_3@<{&dk$p_ugZZFmCmSe)J-ekrV?*85Jl)`B`=IHQ-T+1)=EJU?!5l8u z*ZKQpK^r_rA=4klaMR{+7y81 zKakm=CCU=~@(lQlckBq`2d!JkVZ{=A57~X0ng1UE*#%FHcIc7Ud=TUZ65>w)6pmu- z{~a6Z_?b#$92=kW^MlS*VA%!@=Z0Slr817k+{8fJpFsy5Gk7o`1ZfgvQ1|R)d#&lw z%K{VL37UQP>|}#1vj!yx$760na{R6b9Xs3vK~dvseA2Vq8XP;th}h|k;PPQU?${a4 zVSNw0sRB#*F+qoA@r7TnGqX?fY0rZ{nY&N>FrPp8h}G5j5-7kxF>|aAymHyrk$)Q_ zd|!m;|AXMA%fDMs)=7g0K^!kX@a^?vbmhL#{F1|!f14w?e1h(YION*lCh2N?3FH-@ z?h~No^^>{z0JASMHz;{`Lh}{aA{U$%DS@g#SbipGpA=#HcwCJSxOTWnzib2TYoXA8 z(l0@qC_ypI4_=1{@+;Oj<427%Bh0kOP9VMaI{DZd69DjU? z5e`49#+SI`F*k|-pk6nZV~3kCh{4+MNM1$Wu_H{>vBOOSTuzpxg2EN0zQLBk{V)R+ zl);fEQSs%6*Zjtpc7Qfira4-=@$gSRDxMN3{+`$(-D*T`-+p)t<-n03bf@AX=#u7eo8v@jVcq#GY|9{7B zZ=U88|3PCLj?BRvF1;)-9j!0$`@iTu3hIp^4;CD5KJgzztc00?0d##K3&R~2*evlo z76t~;zA$D6R#pZEWAMTu2hi3G@CHK9?jPU~!F9>~`S;2?%sMg7_lcE?hqxI~^pPx?LuM7&6_CAWEUzh3k)F zr-KSe9K_J*b_7v6p3Mggz^7tPbhN%*Ywp^8*}IqLrswxtj@_5FFGHNb0&xQ9fX;~^ zO)L<-pk6f(xJ!S-qxm?qb1#duZ!b@y3;#B7Xtln9ZY+ubb)f@5+fY3~apT~tecZGA zx@Yr)Kc3xZKpP1i;~it-UxLD@o{<4*Lm|?6ji9K3joK-6JMs8*f6{j1@a_KL(%r}) z!N4%H`4PKM_e5?{$Td(jTdmWKIoDB!m;~DA2j~& z+Ipb-xNG-AM}GeUKFJ3?W_Wa;_F%pM?t?jYgN`gcI@2ThT=OG#(7X`yAy4M>2Y)bw z1{j#dz>0c#^gKJ|LH+^z4D82*gAbVabB~u z-ma7InBmde5AvQz^C1DB|eVbFZ2CHO_(>A0@KRy*xso!H6lK^D7v79T`EKIpDR9uhU(7 zy%<5KLtlK!uW{88w5$>NJ}c73#^CEGZ-CY}9(Me8h_Qr?U*j^r#$Enx;vS&IoRK#_ z^G99;Cxh1#V8;qUq`*SZpyA)f{{OI3gN-$VSFepVXcJ}kVgA++&~?rVpt-~h(79X* zpg1Z(^i<Gk~V* zyulsYXhsGG$eQRR_|h3r)N{BR-}XH2$nyXH|NoB189|f>=-!8muB`{^;`zf*@^9ld zKH$-MvczEmD3Dp2Kts~70qQ2?0csEHgEes6nKkV`U zuy3zOldJWOnk695avTOb15|tpfRemHcVmha0|S5f0dTqjxgh*o_b1Qpo1Wc2K6~(M zedumfkz!x~F%Nv^kNd!{|FK-samPy@Mg|YajsR#27aaaz{Rcpb&w&aakI(#Z=Rix# z6+ZJvaDVpT*E!jJ5F~q`8?+k*G^@|;!N?AB80gdh14vG}?*+;!Aq)%*hrm16BjJZf zfy*h-vQI}yW@tMPx(5fK23fe&N}DuDcmzyl3}S zm+k|;{OgZ<@UOoB+CcAd@DXGaJt!2Ce7ZqbLqgPg^oFPyxO5-z;9q~#hkyNX&t9H3 zkPfI*-g#R8DGesx)vZDQ|Nnoj~01nw@ybE=xC#dWwVA914F5dM|Zb{ z6axcDu>i#F$D2U{psLHGyL*KlsJ{hv6lk>@C}KfMg+Y$vgE@}Xqg&|z1<&qd9 zm_0i6K;rNpw&JQ*<*|h3}s+h(7_(9-~$y*m|&AC4x!Re+}5Jt__$NqBpNMhuN zCq{UQ>jW)vFO?{JfSO6I2TGuYtw*<$0I1aA_UUHw=)P!t(&PUF;{%Wr1r$6$=XiI5 z^6CG{pj70+*jxe1sm(_eK&Rfts3093;@SNJbk$_{O=t=0%Zasw_2_Os0W!j}`G6e* zL$Rde4nO<~ySqUOJ+x1FSpW3kcmD~xB~iwsd-4uD1_qz*;~vZxJfI`{9*n!eMuYBq z1mDyG+L_;cL?QmL8a(5JnqaQrjBf^5 z>$bsR3tDR14lV&uD`o|cqr0 zUo~L>Du68@?a$V4rJyrwKq3E{-GgyAhyki}4Zu5jkraX!TtE^MXj^?1*dw4V4v>Td zb|I+Mpa8jZ+@tZ=|NsB%%lW}(fvp1j$)g(_9VSS>QrTgah+o-Z~9^L&QUEPO46S$lno%6w(*gZPI zc7S~e^)HcW9eh%Vf@AjqRzVPWj<vlc10{05w8BfaK&sSzg=+G-h!fp4YpD zJ-W~T50>zNoYe$A*9yt#!=T+A&}`d!sYD4}>UBDDfQ!3MM*(n~qSH~rqnpX2lhLF5 zAneQ~*!fBzwU8ND50G*=Qv#$`i-Cb50UREnJJl1xEKr9d3Csc&fXv1R4!2$^kpLah zB-}Z%!w$Od-lwy%!H$99uOiJCcAtWt{o%;Jjl=W*0p)|eUeFmPQP8+8=&&8oSvH_aIMBE)=&+p^j@$HI7V z48E-g_*>XOE1f&nsGMM6U~uWyg6$cyj|6(o{ap}!t^z1bE>@4SS>?{{}`4xOflGlG%{_v9?-S1raH4ge%zbxZ)>CO5N zb}DFARKu~?m(lnVw3!cDX9IO5e~St;$XCz6%dQb~g&y6_6Cf2Bs2~JYU@|zGX5Em= zQX8z^v(xQ|XQ!TrBqwpbe8(|V`kL6FEJIXMbI!4sGSYU;UHrx;A_nqkAO0YV|?^s;{&f} z86SB40JK5`($UIcU|?7P?n;3!S*QkAxS%8isc=Du;vEGyPC!SHK`LC(q)jeZ-6Tc^ zh8!^KJp3BGYmBhbgxjDq9KogRLM8?Vh>o>PFdfgB7#JXSyk&yv_{7A(0I{QtnSlY~ zmU(d2K4u1nda!M$nHd;r!7O9MBJ1Wi8lWzy7{36MNB2Q~0WSf5K}LQ-PXX|OdazFJ zaTZX!f&rA0IiOda{JyqkHlKP+86l8IttrZk_?+cCH4Af^;!3G87v_bwGQ;yBHW4q2p4Z z4EzsVbv1(rFph(@gXBHByFrx%%)su+AbHUJM$8wXBO$0As@4OgydIXTL3;RGK&O~` zFoJ6hkH*~~F;H`xza4Zks)yxjkWru@I*&bwJSCxcwndVs$bRG740;%}V>UO)sH z!+``SG~7L~hO3V?*lVRG&~Wwa-2n-*>(CH}SPcqX@R&p=Byc--gS$HX431fe&KgcRAGUqhiw8ogl@)(CwpQ z(b)~Eg*#VgNHH*UHs?q&Fudk$?gl4DPzR$1bUt5>3VcL5Anq_IQ$fliP`3<{y+G|o zNLl0v&wQY@-3!5`A!wcfBD)$s;{e)t2I=#I&fbNXm(9q)umr3FbmA^V2dMmn$o4Wa zFf0elf=W_|ENDIU6mWUW%*4Q;1TH_#nHU%#CWCS*cm^c^yjuj+*aM}nUpP_^v<3$$ z`Hfu(S}q0M?-CGy*rglX<%cA@?#ZB@j8FG@7j4LR0B8~k+@}YXl#nwlx-YaIDD_O6 z;F0X3VgNb`4s=MY2V*x#C%P$6v%62bXdeQb;K2+w4V;!h29@mOM~nJh@TlJfjrtv+ zvsd_CE_A!7m~{K77<4v+qu)ivq7&Q*JKhY6eg*~xhR(^LB;b*J(1Q`|{O&`bt_{?q zq{RFxa7-a$9<+iI5>ud=PY4SfKcFrVc+6h`KG5RZdH@s&+1Lm3jSqCPdvs3*wHrLF zi#UpOESo`1hcfvI9^Ktw(OwY;56hB^9OXQn?Eeo~gQbfef!hrr-yjb`pbnty1Unir zfZ_qJCQp`7dN^$hxW@@PoVFNT`+=%n5zuI!2)MfjI^PyDLjbOD4uj6VhD;?m1jBCs z^X>lN)BOut)$o9izy;rA7k${Hc_+A)Rq_hd*mUsdUJ2@KN}B z2O8WeQSkuH(FGX)@BR*}UkNu(TMv|Qbsz9x{^9Ze*y|Ip7F_Frl6Y7vuO!r?^=&B^ zObSQi4t_8(sBs79v>qrC_ig=O!tB{;4jQ~?_UyC+#~0{~O3*-%2WTu3Jgti47tlo> zkaiDfiy5SjZi83qpqW&NEa-qQ208#$gx!ZH|mS-6tKp4|pDY z!F=(BWAg*X<_FCGPk1C>@CKg?2s#|Zk;&uW3l@*;Q~Vyxu!YV1+gKQx4}gz3gdEM+ zVD-N=7j&R2=*YqYDj#e3J0KJ|FIm1{Y<|e-`2C{e0xF76~lkP)2RI00vSP}20Fr&4eY7#+J=8ZN)!-*)Qq3-AetAXl(3G+6#GH3R7bIRzBWoFE1$ zmKDAoVk?n`#vwS8`5GSbH2mZ#L5nd^kpS692^!Wi@a+ER3EDdeIsqG0{^)~BA}!-f zjyo>c!6tUPT~rc$y3c`zr9kZ^kM7H`qfsH5F$Q!ZSB{DYC|iQA*D;7cY|OyG09h~% zI*g_OQpR;cN72D2x*HhZ_U!%uD)2qPBWjoUTR><3K*I*KCCvkz_drD<^aQ)s1Em?D z0Wx=(^I*;Z-((Ee37Q3C097IiF!}Bq%|93+qsUmR!SM|q9FW7-sSq4FpfT4H6>yk1z!E2{mXmbcp^u|m*!myTKrp}|+wG!KaI6`0 zLLq}kCwP|7qjNQ=j`Zl<3>v@(jlFX63kdKFLXmyZ7z}O*Qiiit`J`3wSi|jsR8LCH5Z8yFEaxQZA3?-2ot0i8icG zY&}qF2JIYxa-T3LdGmR6yMq@AuzGZh|GxlAgfS}Mnhvsn$fNm)L;T@xaEq-IJk1Un zA?gIrNQ29r*Jt$Lt!~i%Ur3wW3f|@Qfy;tQy=rhYf;zvDHZOT_e8NdPRbf-^H{I@=E9M@ygX(;l7R0YX^5?F5g7 z!PaUV2RAlB<+=w*WsVA@p^US@0r?k_|2!EO7>dB|3ujL0v;mA zKQVY1-0%YRVxSX>kisiRMF4b9-3|eJSmB7iVuQh+fdMqG6aYE|v-_tf=#EC{fIx|d z@g-2fW`I(-wx~pawDkI@82EIb1tlj1U(iVZWpIeK9w-s=={^S5=Fxo}ECvaL9u=@v z(3w}C-Z?5DM|pNfD|mKiYj}1)b_88=1{$k?EDiv*)gfb6pcN~SuquIv3#jaE1cwl) zm2?fvLd5Uz^>Y*0r>K5b2;Tt;T5wontjPHQ8aC;^1@4+$^zA+fiWx}SY&}p4InsxxWn13=7FnN5OV@bYBD8fh|LU zVhMEUu1Dh$P`&~kFA5sV41&;gDDa zHK`G?I2t~P@PVWsMEF1!7=f0yfny0HD=IiJF!+L&9=rm#AW!nQfKK}KXx<50z`?)| zz9$=$4lF>6=Wc^a{zA|qA_LF>DQIV7cRy&28mQ-{09r+)z`)?~{UfLh?mq9MecIFd zpbx*>3D53}5Zk-KGqmauO`w@R1<;}D{OeEq@UK7S*~`-hQO11F!}@N~5>OQYvd9C} zw49>?asn)sft-yrw*c}V&IL1|Er^gMF`yyH3UFa{p8>W43sgWurl`TqUC5y*pi3@3 zpsY`M$c=qThUIEdK!Ms#k8r4H{l?z{@)l@#z#wfx_ipf5ko7-)_kW;Hxupwa4OTZ~ z@DMy^nG0k9Mh7w7~9Y1h5tQj)52Ac&CfzJXUMjJQ~ZLAVb&;_Wiwx9!` zx@%MnJU|od@VWbL(D+C55e3J%!yes7Uq1ykB_IQg*qTlO@TL=}c>x*D1f3xcX*z+H zC`0DrLG1^KDWDTCHh{;GzQP+|plM*p@C4`#ap)96>q+o*11M4$_`z%ZK^;a01_nq9 zK%XJ8#x)w&ycjfE`_H2j5+R+C!0iN&MR$N(!H}g7_@_liZvh0|YdCrf;NZIjP)izg zgfHk8Ky5G!w1G_r%mUp4s0(I+mbmMISw4&m4EkUeXg1aW%nD&-U@!!;A{ZGMjKD0= zEr3yA7U&j0V=xOe6JY{ofo=gb1+zf60GfeWpg9S1Fbi}Gpaqx(x&_b@%mUp4Xa#10 zM>$EYKn2Hegl&@F&oU>4{Wz(6nybPHerm<8${Mu1tMnG#Ph3v?K8B$x%d1uzKA z0^I@_24wZ%3$$3+4a@@F0_X>3fo=f| z1+zf60D6O2pj!a_!7R|GHD53bbPJ#dm<7585E37tY7Vqo$hZ3f=$`xTo8T2WpzZsf z%|AGb@;!SU{(JPYIC=JZ{P*bP>GrU`0XhQYRBhBc5xgAjow&+PsImy zhm^qkNWei48YomSz73i(Ydu+_;$b-xR7I6YyBb5*(|K4Qhb*WAFGmHhJ_I#;tnZ_X zg0d{6E&~mA=)>nJR6Rgzt-ziA=AEDk8-^0z=AEE990sImcjE)#Q+^=h_S*1qdjrI{ zJ!r)q*s;)MpOBRVG1%)#kM4GGqr>_}sQ@TfFn~eBi95!^=dZ3gWNdnZ)GVrjT z57G%*5S5CT6fMz=&O_60t4Tn%rs04U)zk*H|NjmPR!{Ku1`WsNI zupYaTX2`-E(6oC4b`?I5X*c8lpx~-->Anuy0OM8gYMPrtWhcO>HhE1{T&?ekX2`(d3Mn9A87lCfBSdP zrUcNsACFFOd(@+IHz;B~I>Ad3KpPD}!y*nHyFnv|X^z(aialNVw?A|Ie};eiF=*S< z(fWu7zw31mOK^*{MA*X;JdRa{y&-Cn_WxL#qxH9!S|*Q;v5Z2Q6a)xf--u3FJVK{~dh5&Dib|l?u?|$?>t# zhd~!bK#~V&tqWub5HyGlNgnp_b}VQO&K_{$0j-3A$R@&NGZ+{c_Jd_XXYfO0LDxWK zgZm;?jIgCipsEehjs>k5hvahf7ON}v}B)DvdsjzxfLqEQN#~sEfZD90H(_f`kC5{E7!%MW_H92JXJ; zVc7`E>!2M#;AQ0?HH3Bm;o4^bb|`3u5i)-O8i$0=8em+h%7*Vs)p=ldgC;0JuAy|+ z8Zyd{n6+-b#NSs2S}F^=O(@)>@y!O%I#i^?e_K=-Kq8=Zv!L~zzd+sZ3m}nRT^7(% zdd)2=3z!%fdW-&o)>ckn0_}6S>B6tsqSC;`z+imag`W5j!FQ8 zu|x$l?dHO-xkkkRETRG0^8ix2MMVK30P4Yk1oo&1KmPoM#`V8W4KfYXs*U}3}W|NlLkUwSz53uu7uAmi7# zz^`!=bP-t0%g_7*UM&0(SNM;eWZ;hh9s26@hyU0C2L4D8o5`d35D$b4I{leH;xK>A zEq={L2GBZ7jidZAZ}=l`@<$xuk9^6m8QH+E$;hv{5|qb5JBuMF!R3Q`uf0Ah0*<{w zERfCQ{2G@08ifL&E5Tg&VEObzamA5e<2HXpqJay4OrU`)f5aWg0iPOIK%UV!&mZy6g+Jnm3xCWR7ygK= z{4wVo`6KW0YaDmvk2v7Sukp}@Kjt`p#6w4Zjgv0?5s&#}F7s+W}t&d^UDU%4s`z3Goa;L zy^ybmo5coy&rGk}9Of{{Ps6u+>C z2fs!qEOz*X8^CK$K@uI{Ri&(;owgtwOV@$U!UQ#Q`8B|&LiebE`V1h`B2IzM+69{r zG7f5n@C1+nFG1UvK}|&5?g5>G?)ZTp;vPX%_Xxt>BS@rsK&G{*fE^QYiXUVg)C@te zdzufkcs3svu)fdVbLtQHxL|m8f@d^%7KCL=4OlkSfMr|_SXS16WN*+SVNjL_t=0vV z0-zx?aN*FsM&$&kkm&A#6dy?A<~Km%jYmM`O#ETQC@g4i0_dDT7KTZn<95M~SkT;+ zGnfUMyK)1wtU)CVnB@#Q?-R@d-NESzW|cEBFl2#Qpc6vz=@@EW}z__*@XE zeIw!1>7$|$j&zl=f(Pi>&u_h6e?Z50c0X|Geg#_71zJDp+FQpmp~3z?e=DfF#J??` zk$>A^@N_*di1E+n5r69;Mh1q4$87v9po=v4x0N$F@NYW?Qp4Y}oRNV6%;T});cuA< zI{u|wLCs#h~FYSI`YNpbI7;YWZ70OB*44(0T;^7SN!$kvrpxfBdbW)++zD za3;`V74X873de5;K7dAm8Nka=kXN3>!o2C)ebGhxg5$RXOeI1vuY!HY?*hKT)~Q!S zz^Rvo%dyvwh53Y|_1StQkK{uh-RC`+k9#IxgdKhWTEFDl{nNEmM8&cDmE-?|&BvHL z{~vCB%ijW8$Nrk7^=*lqb1#dzqxI2xQAf~D=A(|<*I=$_Jy06v+WM`O)z$iMiH{?6 zVYFkf?;pqgpt{ZR|1ppM$6c&H^7rt7iXG6!#>YWj90o_o=p*Wkt^#N=7Ubv$&|3Qp zmre`E?qjd7fmX@cf}2{^j0_BRU=}EWSb|xg3(69}EO18+X>A8`ax{MH3OaTHd}~qn z*Y3YAy}2x&$>%(n&w-b#2!j>@fzJm7EfjU+ce(qTsr#-Ybh458unY4C$6hxUN9!Z} z{h&h6Gx=JBjnV(oV*YJDMvVO14uaBV2`7jFT8R?#?I2qT=-yQqBXGQeW!*tB3|?|% zYvjlH?cl#sQ~qs!p!G(e)ki#~>fa82=)T&0mVe42PzvCma?tuzsnGx9&HuUhdjkId z{||1zz2-4K@S3CbzXSjLqXYsml!1W(5^kWgPxV1fDTX4p9slbY7!H5t7XY6G$*+H~ zh;0W$!~g$>Kl4X`m3-#cImo~LMB2fZ{At<;!H1r{19yZ$Gjnp_azK-Tfx#0j=Fxow z<*eYwH=t1+e$9dvKHZP_H7jQLfX>L!bm;Kx{^`Q6*#SDD+l60q0_bFJ7kYP@4yIW=XI6e;?4*31F{y zbhfB~hGM!K!R?jK#t;0U^JPqeKzoir<2D|>I-u>f436E`eR@qa9lP703cv6(Fmy9I zb~gUt2c0Iv=GfWz2PD+k!otAd*xC3&03_T7<$V!gVCZ&a@#sDW(gBuoV*zOqfKIQu zb{}`_zV3MN0jp>74WHh2(8!KQFHanJ)E{hlJE)%V>^=h)@$BV^fr>IhO#odM2y*f9 z78TGy5@^!RtCywMh6Qxf;!&?&p58OyCZ~&vf+zD?kKXnd{Gfg2J}LpAKmd(1dG@xQ z04r%x0U6}jef;|kpX3X^%$FRqkIVaXhNu+yBp>kZWpM$`wj22O%Gi7NvM7U29qx2d zaq!f>;+TD0(O3H&s86Nf(RduR-^26!O^@Ugp4#USodCbo&9C=LEUCo1xR~k8?LmPbZsWH;YQA8;f%{OJ8T(4^YY6&DPllS`+5j&BE8& z25Lq+cDw!QYy+Lx0WtuT82AO|GkgXmF@g0A-~Rvq%r5}CAjkuBF|0s=%4ZjTfdCy4 zSz+**U!cMS#Eh_T;TPy<_zu>!oZOzyv^TDv*EzOaL?i z1`^PK34nI?fCOwjnh!YeYrFuryam`nsQ~Qz$ea9;pes5wd%%0^K>qsl|381s4OjkS z-x&BKeN-Iy1=>L^JbP28pj;@ zBfs-U9_Np^#jkONKk}*zf5bDe1(BfB>kqi_M}n@lILse$6V#~ZkGTMH{b!J&px}wP z#V;@&6bk&2Au101v7qY=82IDCR2qNuX?_8D{>V9yAdI*H(h3U2NEa0Y{s^#1prC7djIGE4!g02~rfe?x2ohr|>WP#ia#6f;g z7a`(~BS;3ti9X#SDhVFV?*;z%fXfTd<|7_y{LzO!o8NhOG`|x_fM-JmMMv->f;aS#_9eHgS63AAm3 zg#mPUek{29*JWg2fUxZ0tROh+Itv2>XiGZ_!#x%T2GG%CEDTkwFtJ8fm>B5%dX8Z5 zV4(x3X`A8EeG_zSQvqoF&cdhrlWX@w&*T@bttabbJ-UB6c7N%<3TqE}G#&>vBEiRm zy#yW13mTc)4;sq!>^{(a-J|<-+60f@22cZnUx3l07ueZ!~w9QZWl>#$Rj zJsOXJ1}S`)e|t6`;{e?>TY4KbE}H=J1h{Ai-R4pOst+t2oBx0>c_{#`B+V!_^60+0 z6I4-o9DKy#p?%qt`2u`kznev>li8y;nge_U_DAr-z1Zl(2cNQeBp>zcnyh6ac;G(fze0pbh|5N~LJI70)(`-4aGd&bhWFiR{zS1N$h#zzJQhBz<_bW&3T z=rS~yZqU6xG2pueIv99B!!Drvi()!JApue~fq|ES!EraJZ3NmuI{{Qaz;6251!_dW z?;PWg1oyT-^9z6nUp~X`+Jaq*0lEgukzb&JftvwjT|@%|Gw6gc$bCs23|wH*m=4gP z-!KahW=DXoxDaRnB`lCL8W>oS^>V=UvVlZlr+h>1Y5NSfmmOI$;vz_bU;k-dp(qJ*2qTbJVlMFOUoF-K`9jd~;6oNi{_U4NlMkjjc8WNG+O%js;@^G|qzbOWiGhJ( z2e>H_HqEAnVO2panbt-DVHH&|N_l*E2>W@0K41b1`0 zKSKLVpI$RNc7Jv3p3VZgm%RHlSg}hl4;#o(0~hP_C5n!{o-Eeq>xDplP|zMQ$m+n> z1EuVsE&2wpSzTLCmL`H0!SP6e4)!_^Hp92K9b_b^+3o{cxdJu08+6}P>&X%h$6g*r z>+^Lwj-4zjpryjSAb%n)F9L-JqWXxAJ?z+h6m-WiWVeMQ^Fbfx%b+H)hhsOFV`o3e zw~pOsL8dwKZ*yW+Ibd^uf9k>i2OT?oR9Ia3w>g3Km;^C-9DK#p>7!!M{D>KJh3CN+ zETG*N-!FW>2-?Tu*gXZjC!_fxp8)BCZiYDt+6?0a+XupYz_HUsMZmMy ziN%qBn-?Rfd2R9SAfr<+%h?D2|NrlFQE_qXKFaTM2-LcFZ2rN_-wK+T_65y5O-8O3uH~B#8Nq+x}9?7RXd#8hsUu3=rT6dTL zj@WJ&6$dbHrcd|r=11(zhaiVXdN7BmSbz^lIRRGZ*nJk1J|aN-024rd3+QFBKKOvy zx2Tnr43J3!Z4 zf(`FJ?$g@}3IhK1he2!F0$jS!gKlIv_>k3y`JiVvBn`TD`>3#Z^tLw0f_hpmpo1=^ zLJR_3lL=}ZD0uevgGyZn%JL=Xd_&ic^`KPz^4*{R|9xA(m1=`VCP4dkxWM~%Uf%fg|38*purCjQ zn`^D0eJ`Hfr$81#c7I_DH%HI_$zRYIE#kNgP$=m7*21Y&&vu|WM8hfg3DsB7c!1;ks~g-3LLJTDqukbi1f<@So^(QQ-jJUdeG7eEl5-&i>EZ$IeMeX;omBY%rN=vMC^%=|5&a~GQr z`~nHKH-Qeo10A7yiN7TgG^^44OQ6WB89YD9RO9FYUb6H7TyTGO;?HMsOyjozHGdfx zK0EQ(vp9kTT^t2aL|q&OKsPA)SRW`l?PGn2zZukqOq<{Y@_yRE6C9wPucPPx13uP= zie7m%AF(*>xMSsc28NfQp+U#yA1o!Fj?F(fO1K?&OaKXkt~~)QMgXnO*$X-sM;_E$ zVff_4FTipT95M=^kWuj9KjGEMau90r1F*?3eV{9uA>||@v4He9f-W#piRgAwiQqrs z)XCBa)%^&p+p+lt6MqZnif6~>7mWNZ3qWID%|94RWF2=@oCnp|;I=k2dO=!1>unie zmV!FQ9?icviacQf>HrDQA0VfIf{vvTCFEEdL4q!gXyMri3QrD1c!DM+(k3`{voxZ( z`5njshrx@OKtblnzYVng)``ingUPYOi^;b)h#A~leFjzsab!up2PnopU=Fm0IPe4L z#y2wKyI2M?pMxj9LCH%jkb!}f0d(x22{>DU%4rBI4SozwE+YfOQ}FS&pmwK} z109qD2{^FhKH%s& zkv|_fJv;K(vowBoqABFAZd9Ps5X;#U~uH$#_0$u;~YB}9XmXke0x2i=^3<^ru9-u2{@ioE$84Y;1G$~W0a1W} zJmdfpba6l`KtLXH!090en1>wT9s)I{Ax;LZZiHkU(BdNq3*;VUq?>wN__r~FBBg`T zrNfcQ0~RMQ4#Qoa0WJd3T@SjIFrTFX)%BoTsPkPK&|KdDay>k`6iYyg7sqa%25@p| zfV%wMVN{nxf*9m-2+K41fJf^=&|w2yj{N%=txtds8}MRu53Fy$o9iTus3?acT2JxYLSX+rc6c^OK z6#y+7Rq$x8^k4uj{q4S4%GF${!N3nb{NAJ6(*WG(=yuld=oa_LK2hMo?CtSCoWrBj z*#LCXa;LKe=xjH>UU%?G3(%FdAmyE)m9E(z3Ossw>_D14dRfdptPhq+I_`LS0ljVq zTjIf3Yys-X8hCV8D|j4t1|5FS02=K8ZQ=nhI0hZt0*U&1_|iqt>Vi7(-e%B(8c10T z@|Xl@o?HQRSQmIzGwALDkM2VjQv5CJz$ZgHFnU_wDCY3^f4ISdq4d2+>uvrP&^&y% zqXu{*lt*`^1<1po{+ESMZ!x0>v!90-$P>(d2KYVV!&nBsa@E446}(}+{vuZY`ha$g zA%@nwj~`=^XYc^^=t1pd2hhZ!2WSruXvb2F3izA?(D@6^M-<`?gNEoJbrI;)0f_&r z;Tw-Y?GuPBs43h94xL4CS4r?*A7w}wY|wS`CaMNr(DdoTxkcrXVWcvznz7`Mkk9z#BdCkh^optYzF zZ-5SshD0MMDzXn0crZJ99QJ5_W8u+#(6{wuiHr|uEsk&Z2_MigMS%hy-JFp018VDf zbb}AGv+($@0LndXLZCy@LH?9Ly3fy}H%DcIN8=mN5GC>H-mYLTfY!0|Wmy7ZoM`Z8a*&9^J-1-Ngb2pL1~k0EsHXSqfm5N9#Ax+FQc5 zfDX*R=EM8|V#zmls3jh~JoWtBLR6IhJ8OWaLAWn~twCZdK-h;onh&sh^s=1rV089) zxs{!P!6Q4+p}>PV5VD8*C1^pI%&I-rnb z=WnfIhlFG)$A3qSmpPaQxic{^IDR`WP-5L+l~`)lP{o*BYS2){lv%3PP{o{5ssb`d zg1^-eYEa2`G?%bJ4B#lqX|PHzO>d}TEG^8twaTO0OTeS~C5y-RhaLxCv4BQ0L06}NRvm)E0JP_!=w3!0)OjT zR!}ME0y+TUyXXI7P7O8&45i;dM*A>-^XN7{=Ash7Kq!$STks#6NEP^717T)E8ZRFI zk2ly@FqD4x?DiG#Wd7&TcyPtv|NlLh&wDU>d;E9Sc&Px=fRZSEx*vF2y9)5PvVx@k zJ3GAm!vaeAOwB)hN`)G#7(GjQ8mgFrOW7K#m_tg`n}6i+w;p6+U}&gf%;ay~4rVbG z^S7=Avp_Mon1zACr87lE!^bj7fWK`G3j+hxecjfs#^1=u`mjuy2zHu}ih>I$3o3)M zr4opx*!(lGRK_NyRHETmNvW`7!;hR&-s3JR8q5p~3@_PP7#Ny=dX~!A_<B^Ul}F)B(fAU7&{9DK&%!TkZ` zR8=Il3Y6XLqN3pV?YKjUnhWTP*yf)(r82Jk+YUK4|41wqa^&B3x%pR8DW?Pfw)2kG zCrgZ7TmJKRf;MA8mFMxdg2pbOTJlTFAX-XFb3s~~f2NkEcr^cG;co(63GZTAEKw=| zD$IJ_{(y7qDo|knvQ!joAwS46E=TK=rL4V9^ItO=A80+`xD#Z-4p7s?r~8--zboiW zG>`0HQ0W#7DcxQNc=GRh&QTm^lT#Yy2#SXg$A;e(rGAc9ze+tln*WLLH_3z6sUz9{ z25i3;XoLu47U(Px(Bu)+M4#>t9@fYBTVH~*J1Av>W}-osa2D&>c$R87Hv9@MRdQ_j z?N%z|(C|N`RLpS)XpR@O)(JXE^>P(x;EaFQb1t|RXRsE3u$BO@78cNp7qG)Xx7Z+Q0Zm_m9L8U)Y2#U{>e%qhy;R<@;kRF@q(j61 zpi*JS9SjX94tre7y+P^);p#(>)O$nJGoh%LcKmjn zu|&cJbQuk(%v6BHI=CqVx^IN}nUD3iG8xAm?=FD~PUN95h#Ko}#WEh<-U1%o|2(z- zLGKW_4sKzTIQuX^cldUUu|y1{0<>9&!KM2UL{8oF{}Es7Q~~~`lHdRTzm@>IL&&Gw zNdPiK3NgL=foJy-k8WQF_%vhp92HQN<o^3L^u9ADAV|#K7PWX0Js3r32@e04hDuWu*pX`V6HmD!N70>?DLBpFjwhw!qho(A|EXQ?IIn3942Au zC{Zf%-%+Fcng?Sb_>w+Y7o@XB#lj=I5>(JtD!6pAw|?Vq0rf6=o&JM*S_Yl&9Nmr@ zpx%;$Pj?}>>(u=Y-0@@fHt_6a(Zk@Fs4BA%-kp-pbY2dH~Ev|ydI)FEYxf);c?Y`mBeG@dL&jH(T#=niJ`9})4#gGVY zF_eH?42dQ8nt!B~YBp3c=E8(sEP|Lzj)R0j?F)$NWU%Uzk_|4FfuQQiQNy$QkPl-d z*h8RmJZn@8AXyzA8OPZ{y?>WZe;)pAOdakVpy~S#cTWCoPD~x{T%h@V&+f||?%Xb& z@z9-Wo#3O#KtZgIJ&0Yp-32_l!zDn4$A95&e*quR1xuhqHLMSovVcMwyxaPSLiAzK zbt4|&^mh^7nFH-_nF9{9yYOD8Z?DIHA7)Pv-_`>@{4SmzKFp_lIz2tG&EJ4#5?L5P zoi+|o(t;dh2;^Q(0`UlFsh2(2BZvW4&=MkuEa)u6pI}+gBm*Q6dJ(r+c9U_p z5a^;2Nyi;N*gXlljV9EiyBp;0*8g?#9=$A<9@gNXDU*c^6Cn>RfYyV9FQO@W3|83* z4omQm87NhtDutR;z5^T(5JjE4K{i3u!bhJVe#aIvXiT z-OV6bk4_Gc&dnf4Km|ZK2ebs1!-E-YB`A0qJQ#O_tVg&SBwEbw(b)`QcyvRlZqRf# z_(mRZ9>ciShm_p*2Q9a)0WGrtm%I-N<-0EMVWddgg*;rlKl*}BEqvtD{nN4eCu6Cg zNAnR6SfdU+rtH}Kj|salGj?GX?7|#n(ulEc3|ov(cJ_d;iuDC;%DMs?o9&EIDFAKK zWdN1_6^`1+Ks$;-{jP%#m_gNm1HWd8ioqv-0r19O&}p@hZng1S)a|4R%E3>x4?5uhD= zkU|JFxX23L`3>692PuF-R~!k1f#Ml-Nrh|oLtoIbzaPO7%m+F$0JQlm4jj9nTw37R z{D-O3*7!C!>_MCRyAOlr&f$}e{M%Wa{vS^3Wb^1PW9mNaX??Wl4a`3QV6zT`Zq_Mv zgBsxo?x7w+=-=kW$iI!rqc@28;6sUnKLygbPo#D7#CiNb>}h?s=mDl#ETzJzf?&rw zHvi!$llALmaRqIz0}V{asDQ#0;s&r1s{|Bd;iaR*cj4Oj9IMp~dALH=h{_+2~ z$N!^_)_;nQgN~-C=!T@7{|CVr`FS4vZ_s@(&85@M1GMeX`Y0%RDnN+^nwE+hq07!e zMR9ix{Dfs>&mC50U|`S(rw35xFa)!{!`D@Tx?+&@5YEWJ07(y^LyjQn0kkevAvoMI z)Dct^TR3(f1ZVnR9?(qh>;YO)xE2&^pyeN+lZHSsc>uEQG;IR^HWp!z-g=hi0}USk z4$i{D)j)HlL|8j%$NdTV-0IE$gv@aZd#0=gS(HWwm0!lOnX%l?9 zAG>sa>i*n%pv29y`~8IGf8f-z?H*C7IH2_FaT{XgKR|r6)B*58K7P{d=Rw42%ILw8DZ;J;d)e?*z z-9JEQ^`&5+%kpUcFTvmX4z!kFo9iw9ZN9e;KIGs&ahwe_nR)Ob2mdzLo7^Y(x4GT` zi}P=Dz0>K(!N1M-ZrZ_z9BE*wdug41Jp9{y?<0gBrFHu8@o)2e4Ab=x#&`f@JnQtm z@R?u0^#cDk-{)Y#&-{XJ0{q)tpMrV(+gzW(7%w{gMEJM)zC>8?Dy`E`f`6OuYlP6d zv`#-M{%yYRVY=SJ7;nG~n0+8?WI!jjbo>559wPx4{LM!KK!=HfrpzG&BcOr{l21X$ zGB<*&@<4e10CYeZ5@!&q;aqy7>|2EfRkRcx6R8oXsR(UXAUcpL)%zK{o|{fnHF<3nXs9zpZyK=wO!K?aM$L`nI)#mWD!I!@sR{ zAL!oYPX7e{ZM~qY+7S|<{#aV4e-i(;UQqr;NPt$(rgi$K@Nerq4AKnC8?8q`yiWf# z{%yTSK}?85TBm;ocqurj{OWer@a?T*^ZvKlh#67J!_3Q9#D%_skR{6E-W!^Kegz0*GcbQ>EuC3W|L(<$s& zhsGnIbnF-(9~T`9T2R#K?$PNU;L&~T^;r;c0d&~|B&UE5D~IG1(CjcIr+{{;KuRxA z7c&st=mqUffv`XWj1U&+06&P{d`1Qa$e>OM{ED??_;qSoOfXr{!4MFWJK?e*m-d2l z=u{@y@Y!YfCB*RZv-ynya=rwunC}MF+@SLFC;0w^bDrHV9lsy=3p#G31+>u0aR;b8 z{D0k}`G)|1D`=YDrPtwy2k2(%5ETWmI0t_#=zv?$6p0%DHXjvrczSnHQAJ{_AhDH^ z*h)xjMI^QYl{Qlk5yN2j+y^8@xyX9Lax|2k5oW`rZjH~(khZw&%Xx^DBm z%)ia`61W0zJkAa}If#Fo>qRghRCvyS)O;7f0^J84LB(aavxP^miKj>R36F!%n42H6 zmvDo+s?7&Lt7j}wN+}jIa0vw}sqmLljKvndo#g`1d02Do?sn{U=KvjpHW@T?+_?n2 z1J~8~2}mf6k%3_Xbfo|^zvB!o2~c0GH}nQ*Z0wo`^AC^a9}@hnBA_M0+gxvX_wuCp z^s<20w>$nn0K!I~7sj{)W=xcRraK7%&^FzbGp**yH)Twj5zd$5iC z+gxA57%#vKm^uOeZN6{$w}rmv-{$)6;3E$13m^hi*}MTWKz2KJhH!|uTBmdHH?98n z|3B!wwh5pR10CJAMFo@~Jdj%k%|`;DrJqOl@z-}i#C<^q1_nrC2b}~DDY!t#m_r5^ zo#D*`(D7^#SE%)ek-S5+#TJH+F7U2hI zExm$A_a|S_hTP*G-Pe7(UxEfm7+MdMfUeC2t?>t)-VG9S;os(Z$Op2Hmw#L6LF3!} z+k6l3Z*$%6`29Gj3@sJ0Nh`H$_?cU337S?lacuaZP^#zH{GXA(6;xe%fO@as^?VFY z{M&dqEPPn^m-2zuFE;-WsCn~p1p@;^x9fF}Zr}6WH$0ecmN>vW63`*_?(?u)zs11U zegD7KdZ5G!dNX+Gt=Amgm%&2%P-8rp5BheW^JHXm?LH3jxJUPOPw4dyARjS+vYhJw z|NlY5lXFx+LLS||{P5Od;}K98!UkPH#UIqCC6XT9r=9q>v2a?r@a``?09u3G{8OOj z&1+B)Yy<_t8c+}{2M57INVVe88+zXZwC>_6sCp^w_GDz^-{yLsf1B?EXbZ#hxQhy? z0s>{lhoE(~D*W4gA3-HSxh4&?0g#D*o9{Dlc0g`B!<*nQL26j|xB0$;se$Aj{%x+1 z_Ba1F*ViC5pyvG>gyC<&hJzaT@1TO9DU;gs0$_1I&rTN=2@s*c-}aY*fx)HMfeCb+ zPjBdU$NwN7G4Z!<{rCUBCnH;L1So&*;NRxD9cmKT2m$^!&<=tf|Nb*Dq&Zr$sFdcU zHUAXjZ>{_H|G!JGGYiDH?~ebkI)X0$1ua45Z%qUpQ?SkV2mdzL@B9L;f1xJwZ*%<( zHdTm!o9`c(Pk+G}Kfw%;FGU&qoxi-GptLPu85gAxgU3)?@?hO6zMZHtU-j{MtW zR0Nt23w4K{hom-b&<#rvm3Kg^Q@TUXcOUl9zTn9y)*X7i^%AHF&j>D2pyqA+`~QEp z?{!Z`KCnG6L2Ft-d$J%!2FJ-1&}!7N#(C_;Y}UYtdT<0iYrI2#@9?2Jzq($PmxE!zW`v zS5HAoL{P&KQX($Lb$EU_^j>$r3fkGx3oOE0|%|RlGU=E0t0A_Xib2#?;{{fB4fF>h*9shT~ z>~!Y=EvN;z>rWJTfTmKJy*qY&Wn2+%pC4xj=o{;(xHNkg5dK>xD!K zXjBtY(UdbVFhC-#242Cvf$ROu0Bf0phSwo_L6%$IrK~Q6;!(9l} z1nO`X1{KwiVU%ZSo&KOvj^`ld5TO@oo&I7N10Vj#10QLf{!)&;{%=4j545DZ*YUqg z_s33m8Kk7=)k|zU)d#ecyR%#Yn)Kdc4=bN;cLk4L*9#uq!2-VB=RCSmQl9GtNWzox z=yX>Amkhoi;5BFW92HP)?$LZC0i62cVh@83?}8K#sHfgSQX=BiTTsIZlA1tE03az5 zG-d-yYoKv5h-@RgF$KDe79tB;{{oQ(9nv=$oDxAtu|s4*$Eib7;xl;P1ytHXC&Qp+ zkAiRaNAQJFkQ4~M2mA4T>;s6~oeMmAs}(%>*E?5$ifjjugAbTMS8GN4bh~qarn$I5 z>+-u#IR3xzTEwT@UjVc~+Rmq&-J|=0@kz)37d;?1nrM4;2UqxXAApV2cvw4ElvsjH z5`K@&e9^IdOx!4bN6MISN(fnV9 zzjY>fajh#O|2AJHXl)TH2N_&v;64F5<{Y$Q0V41pB(O&XG@%V1Xm@4h-{#8()^!}T z=$!$gj0HTV$_y6g-{#5*8G+}5@i|}&b{K=V(+|}6=7aHhU<_^;Ll82^E(GHXz!>~6 zhA3prT@1z-fiZ+(49QME8UAg)QZT**j3EwYbozk?g=P7-`6_|Bk$;=-C$J#@HrEd@24p@3stnYK`O3e|_Zv+13ykp@%z!Eraj{NQ;cwal znhEKwz25Dn0XpOhl&C?&Euhp6>S2R+e1f(lfhKaDH9(uTAp5k4i?d=2kIp*SRxNex z`Ol@>Ps5|v^}0{Dn+2$~g5FfQ{+byw6QmArp+Nigao_=OMB4;(h%uxl1P$*%+9n>L zq8hyW8njCrB3l7(C4(+1hSZbd@DXY``1rLl6KsS!6fPSBUlyAPmpu(%_m12Rlu=QDB147_AR8a5K8SHSRI>w{h(E6!^}z z+oeLFwghM!Uw4iQ_$F^q6T(>lwCqOIryIQL&H~h@fXI3Lf8f#W%mI=;?$KN=!BBF* zqg&Xs`y{9dWB_WQfNyD&2i4=^;FX})J-W|XdvqUce!%Y0d?3Sv`5@$uZIIWH76;z) z=>7=WcjwA4&j1-M01aL={{H{}e|?z-Vr+^VF*YRy31gInZIIEbXP`ct0eH*+)R~4Z z^aDj12b#4XL3{E+*EF8@X#54XD+6v9EE16|fZ7flG&2A#9)WEL2Wy189W>5SvJX{# z`5lNf_{tP;gn=&Dg|AEV`2WE8fCXcTphvGKqlfh&&}Pj84VDb0@396M(t66|D_Gky z9?jJp450OvDLCZ2y(Pely{>_iA7pJLXlVv$`Jc!C2ly9jf$oifbkni@E08PnQKmzZ7Fb8OpX!K#wZexi3pc{A~ZETQ> zA!P^ZO&1W?fm{rc1*c~Ne$5gU1%8c>2q*U8a01MU{dnX*^9z=!cn}C~kViZq6EpvV zIY29@6hK2g(cmow5Z{3O0PziI-4w*Vs69N0ZxB7aRQPBDXe10G3o2P5dO@p9Svhm#&5y=HcG^gYPdkJTV?*#&9;)^A2UNKck>U%QoiOo9;Q-`<1Q*DpaOp-=q53Q3^Q0p10?hR zMDq`M{&qjmHl`Vl{M$ffE$Ce79#Hq1!ISyK|A*afD!n`oF1;#eL9JjH<}?k^>TFKX zs+V48me)?iWKoY^@Lm{rs&VYT0=hn$`3JNZ{{b?R6=WQDcNqMFNKkXC^>!&toCn^8 zWkyjifJ0mahqwd|aTy%q@D&tj?oq*^UIR(o)%d@QWt|Ft8|a7;#G)!tTgkuIq~57p znP+x)HCYzz#&&Wtak*cccb_kaedK^E|NG#`fyjC(Nu zczuQnDXBM%15(B!C$HWxPHci)*aW$;3G!eQ8r*(%3crqV@u0aEBl!X@k-C+`# zf{^kZ6p>uL&K$4RJ$l0w9Cv`~eYknxtfu7%St$%%_vX`GX5j%^nE6ft6gwdOETB3? zvOCNINuwiTlPRJ^hXf^jSsX&b;kA%QcUXiYcmpw#DIgnIdz~Tr5^zZ*;gU$fC6R_p zA_FD?I!)3?CBSh9XuqZhtPX9xUCIs?d2K{o9EQM*0JVLXe{{PAfZ8A~zyALRAFSf? z`~QEB?l2G3vi+sXumAs>>v$OXTcv*e|L@YB2F||lpjPkzm%!j64jL;S%s(L2J*+tF zb_40*Z+Qf=syj>q&4xoi|NjU3;Fybw0=Vr@pb4h{DR3d)Z~;w!dHx5l?REg2Fb>)T z>1zFgzvHGTs24Sx%0m#*tZ9&_x zORQ}f7#JB!v~3v}m>5bqVTw!~cT``++O>Nv2~#5gW3WRRj@@jaSOQHGy7VS6!onAv zneB)PUszRi?SC)_B=Nxl*Rk8ogZYPVcOAIx#O=ZS57aCHwa$n!*QfiKXE$iSwg>YM zaPov}B07z}P6Z{ZWXI+@7N*h|$8I-{)&nIWF5M46z64jKf)Lkxf)fQS)4*kU98p`y zp4~@0T5p%|dv=EjAkP*t{{S_?N?f2C8DE=&-3oR!D0LF;U`V3-@5oODAT*B z7<9X+Xn^AA5C2Vku)=F}h>8Xz!XO0@#864c9e6dN zc87dG=UIC&gR-ov@mqd*2G8uT1+K>5Jefasi*=uBe#yw+@|Kl>q4}jff6FUo5W|7L z6%1`|vnVFJhrgnzr62v}blXh##sw*{0XKLy7f zprc?Q$&Y`#n;1<^m!PTXQnWL@xlV?GzikufyqxAbNW&8QPNGe4H%<5fS_!^I1vDNCnxeD-ZHesO0=^LzG<6AD zmI=Di+klaQ!2xvskEG*{A2=cbl<$27kR}vCW5My!v4{CV=j|}y28?V>Y;0`IY!JW# zq2MGdXjL0zp%iEV9^?T2(+ms@>%|xtm>DiJFfeQYvq0$vGI}k{$iM&@y_SG4yHjRl zV3-e{)+spa5uEi4&JtpRnc@X!Wx!c& zaMlty>kypv0L}uHe2^jjF!(fQEHlhUN$_dTTxJG_MPMJbfG!dM`v|n;0-|>vGi;*Z zFr0M*&Uy=HF|okZiNjg`@Y96CSYY->v%n@_(^wc7R)FoTWr6v3Dx7r+&Uyi7@w3A0 z&;?yv0CuT2oRtY@ErXv^1Uh^V5`&vqVfOB2Wnfqew)YBL#~V0{m5qTR4s5ap5M z*A&k3g0phjVLoqyvzEd2Zh^DTvcp{e2Cj~k112U1XPLuU0dQ6(oK?pG+fq9Zezww5 z4wyey!OvED!~t`a1SgE8#tHM03tTLf6BZgdoG?=gIboq8!v&L7=7Py;aKU7axfmGM zfy2s^3uZFtP8UcHO^55qf$J#eVqmxiKHmKd7X!mFu->a&3=D_Ctb1Gx3&^Tnr2+!7Nd328OF(mI^n_RodJP3~#|=2HY@LS#mQlTmp+ZaWgQS2eZ7n85quh zSv7FI&D<~_b-?v5o)dFgw)w zV0IYsF)(Zdht*=Zx>ay>>*4Bl@i8#$0;~HCSI5K;Gmo7gW}Y-Z+%~wZI$Sm!E*l4z zO@_;s!)5E>vdwVW&2ZUWaM}HE*~@U*TX5O?a9Iukn5*~&U~U!>fVtTfF6#rA4TQ_4 z!ew*dvW0Nj#clZ zD_ph*E;|t}`yDR(Pn3b-12~SD#b722iNTg+sfaN!Yy-F4G{hJfHiB6?VhjwMz$^nX z28PXGmWdbx!xk{hLX3f7E0|>?#=x*0%mS?l0`Ck`03Y1+5qz=-sCx)nFJ$4-eaQH> zg%oH_jsv5I^?lHqoWtM))P1@SLxvZv?-%=n&eCx3=zi~^{kp`~qq&ZQ;h2kx0l4Fc zbEwb{ys8DZgd&W?qxmgk>0!vKFvudV*Z3;EUnw zZa|AUSb80q5Q{7zjTNXk(&7t9b2Y6KbD>T?z9AJ$#~lUu6>-5Ts29+6@Jf1)aqPkxhaJ1}K0avRM39fzR!f z_uDKWU3^fF5bQTf7Ab*_1AzDqbXYvZ=ZM4NL1*irudr$);JGGz@bF_;% za3%!x(ZFkbK&M2&?(gz}T=D2?eA2V|2Md1_Xitl8Z@^#B=20$3{{1Y@Y5aKy_@fVi zuI>8FFUaV`zs-e<5sQo?3s{N?n-m9FirKUIAPY8W9*^dO0>0L#iaLF*PZX;;@^51S zE!t!8{C^Oe@;|=TCyH)?H|!{Z548f_r3N}nl!d|g0BBQ-f)8YgPuc_*{%t%0?)=+% zywY4C*TR6V?s$0^G<^%v;nRH)bPtFrXtksP^u`YjXsCc@55SJ`?G1uDhR6BfLjlK5 zFR&XPgYWc#m;=(UczGq!DK-&nfbSQGJ&|B+y^`k zKH%`+J_x>i6UO9%3JwM*cr?G!07ar0zW|d*_d$LEF9Ci*Mt(u?eGnRlJ)m)OoCUP< zi~+RAkpn8mFTkP#;tN3eAT}rh`2~DbP|N_8$EM)EsTl(UgBh3wT0{wIi?J~Hf~K&# zK#SixV^lc6n`=So5p+cCEsyS>p3Ofv_?tji*?IOl{P*Z(Y4Ys#`0vrnoGl=AN=6g08gayYu2a;r19s2?)PGF;n!qQapBhhCr9u+E$G6v zE`E(`{2FKZV=wR@I>E#ra`7{N-E;oXv;1Kf`6I45@<(3y%pZN|6Mw;p8=v_LF5ckR zIGe^_aO(!Y#gY-D@3%GO%Ak4vI2G{_Y?i7Cx&*p<1KGrAro3j7^ z|L?-D!J@*iQKKRNUfk(xeVV@+bUfH+egUw{J-ZLXiZV~_gRrXyK?jZSH-Sz$^JqTA z;nVBR;@f@Mzn8_sSNo!8^D7q5gRhvpdPTZC{~z-2<(ZfU_Wx&ofutY&`bWxG`86)Q zX6M(q_?bWU3g}Yio!~33`6DlX=GQp{x%AoN4@mhRqLu&q%rBrstZELB*(|tKdo&*s z_>AftCeP*r9G=au82MXGz;`sBF17&OpQh4{+ZbdcoH}{@!PhLFE;<2P?hRT=ZxDaj zi-Ccm1AHx3ECT~W1ele@z`)Q1W`VZiP6V?I85tOOz$_a^28JwfsqD@OyLHjG`+`sR zEpR0S9*zf{6}O6kf#DPM-biTeQ&i&9>%`&N8}tWsaj?h#Lq5GcQ$fR&pi8kGet@sP zj5_d%Kl0Egew_oIj38l;H~dj2Kk-MN`oynuu+xzRBUIUILy6Uod(8Ki~zu7YbC7z}B^cZk2QV;M?n|06qqiUyuW!VQ_iUkFPvo;co(+2MH}t4q=ujpwmaw`14^o4O+B#G(Y$QE?c1GN*aGXi%K_- z3b=gX*8mqU{PGN-^5hh_V90xz1}X_aWd)>ENrMyzY5Wn_()c5_>jPY_B!kXVLM>9_7{RF#=Fw050^sWaJ+%*hg5FgR zDN;b2%0amgXOVIWTBPvHgHm_WhcuMz_lZCD$|wGai{Jv~6My98Pe=vICs6rAy!wBi z_yv@RRt~z5G>HR`a*yUi3TQ4uDpL%gWr`)}l0}u%c+5dI1F28}U)=;PR6r~3Aq7Y% z=tLE8u>o3hp9p4EGB7aofmxvC;{0HiJtG4{JvjGyF)}bngT-Ja3TPq$oDV^F0)Q$^ zP&LZO$iM*3kx`(;?b*w6*t0j_zelggL=Wo|<*c68mx~?vH6U@^eE^i8Cx9-eJ)OoM zc`%Lt*?~0vYmCnP0xqWoK&b{v)R6= zggmq?c*R({8Psl;{RH*`s4xKO0heMr2RcC|*omUYhg}#L7z)542fFbDQkH>wv7r0l z__s6e0v~%~eW;Y5f19Hu|29tvSbNX|+%P&>!tMcWw6)2_6QW0oVdQL|7l>3~~S2lY=Hq};yaJ-#u=c+ zJe^F&2XL!#tnh#+&_3nUeaQ3s4S4gK$$~|&RM_MHAXSsEQ4)kIHpTd*}KH|(r#RGhukT>Y69xu?DB%Yu>#T}qi#CpMZ zTsQv=Diyc!EER6}rR5F3(n^a!a=9Mh>&+yN zJA)=J83;KKblJH_Z>R*sb-fiV9?d^^_*+4TDtLCE1J_5}S`TP214J&^Y*8wLu$vx4FvkZ}XM+IQWnQazv#p zf+>SwN+Xz32&N>0DFJ1AH2)0XZ@K_V!JvJ^#S+j{G;r=4762*j4dsBlt+Q4Flx+E1 zL38)UC!q=3iGN!QXlodQg$pl7X))|fsTVKbgY*;dKPcQmiN^tM3Fuf}Jy0rf0-b8( z4Boa2;yS`v4q%pxWwt}9Zu3u-VqMUsrAt&m3Cg3}R|9Eg8KHbjX%QJkKOD{mn2mY47pl!_EwV*`j(R`f4r@NY<@1V(! z!?XMRe?+p|0@{4B&6UrEe_Jj;IGm~_JP$tQ@Zo;Jzs;2w!Q??Oxe-h*1d|iN zr8ewNQiM9mv-#Ky{%yYep552LVF1q*ywKFmzs;2g!Q@6Txe!cF1d{{G^yu_a3GnFl zhTLERY9g!$uO6*Yaq(pS=h0a!;nMBx;L+>)0Bmh{umE(AHfZ0r1WJ6il!DIDa((ju zgzf*^tPK3^si4DKx4S;t`JaJ-A?^Q(H0y)>?Ew(McOZWA4}1Q0`@jGHr#08UVc>5u zfyh1B39fTHT~r*>tPhro@NcXA$-m9@N7}*X9BJG?(mHEY9E>k{^!lh2xb!*&fYX6% z>wo^%B1X_!tlgk2iO8of^7o%V}eg|Gsp&y-p~VJH-M!<=fQNlUhuH~ zP$uiRWA$aMtEoM@T`zRk-T;TB_CHXlgSIeqUk97edZ0uVyD8k?H70>Kk80ra6_+N69Z4?;~>{d;=V_=vvh?=XYB-^&eRPq-8CvM$6Y~14g;E_C7@vo z3QDl`ppc9D{r^9#qvz3GD*=u!P#@N(J47YIxAjtqrw@4lEjXPCfVZ3bs3d?qZqV%v z*^LP*OhAS@{=Worv;-&|Ji4!YASd2WpzR8f_-Q>*Vh*j5O7%dCu2JKw^*{+$6E^(% z|KGLsNh!NW=k5+rGd$SAqr3Ei3;(v*!!G>WLJzs{Z}UCq!oSV+!1qI->aN5BR1-LW zt{{YL%!H11@oxhi{LcWY6%LeI!)k>`j(b2OoIc&}Uh6nE{75NPbZq#USSk&^&`#9V z>Sn0`L`Cy2rV=iYGFH$L`kdeZ16^L(dWpXkbR3^!!;dunRxxlr{4 z`~)p_2Q|}Bo!RXQ$!E+TKncX7`x-PYmN5`2U|@AoPJp zw~tD|Cw>7}1{ePAE-DT_ovSB+$`+SS=MtaJ)u8a>7x4YSFBt5?FX$ceiC@q;C5=D- zR2qNrO_ntNx)W(G{6{Y_b~@*z@gF_Jp61dST#(l3Tm)*St9x{MSEM;M|6nR%2T_8h z+-Z)@znJ*juYUgzStk4P?DzlwSFo4z^KbWd%@Nf4HFuvs3ebnRo zM~_Zth$*G+F8teF-+;wk`L~|}-CW##!jpgf36I_YMo;Ur9{kQ1Jo(pO^yuXYGQQ-& ze9}kzrB^RYE~vxf2{Gtp)&Kwh!H(f?O$62Ea1#-x`SgOWjy~(izx|XCzw-f*eFr@F z*B=BO&E=#00%oGe_m7^;2SBcS2|583H1P#lbO*X03Nnoa>N7)5009ljL8jB#J?19?65c3wn)qzeYgP3Q|1e zbO9eT!)&;^MR0Y?;p$kJVUx@z@FjHC@C)=D;7jNhGBZHWn|#U4z~Bt-<_N(Tze&Qc z(vyQPep?Ec1znE{35|_#S#wsHDTS;I46b1FKC;3VWd*UpY%64AV2A+AcEJ~%O=M$W zcn+Q{p9WuWwwVoP-VM0kH*5?HE?~X9> zI0Dyk8m{94C(JDpTreN`aKT(PnG5D4(40MFCIZxBt?=yr30ks>(R1ZS8ZidlM_B+q zsMkdWv`Y{n1@6d#1_B@*SSXco$a934@CVBRQ1PSvw}PH5WjVUZdLCt0W~jcRKO>VI>sFa zb<>N%u?QLtEd{ec6YdbdgKlnU1rLW5F)%PdLIHFN5kw5?$r2R_&?JxpsJ@8+7c!tz zaehEgc8P_02-Gx-0J{=22GV^3$}F`71xSTY_i50@G7+F-NkFGQcyyoZc2Ow+UFi!l z5!B28YXJEKd{QdtAaw`O={hkg0iXk6O29{}JH#K>WME+U49*T9uR+FgKxa)CfV~Df zSGo+$8nll826ucjIOMT9zWI#>!IbEWoD#7`l4Cb4l0pbXk_k9+T#ZkHLe0R{_y8zk zz{dx5pX;nqfz->C#tmqV1Hrf%5I5tF5+pZ+qr^uAR5)Ok44|!R60$Na04tBIp_Z`p9Xb#WLYyr^ODxRIy3g9a$9YF4=@a+EJ0?MtR13x>RUqFvG z_v{RQ0Bvb{bl>vqbiM(;iw<%udN)Q%B?{TS;L&{+>|d~g()-Xfl>-_D1ZDB=8kG!> z<|7V{afb~U7#K>xt^+LCdK1Zw(& ztpgX|;tUK7&`4^2W8v9-(6{wKDLZ(K*y6Bjw+Dw0sF4L85(VwK!!$sT8U|Dn?N)?J zpH6%U52_9{TMt_G1}?1!LBcD7Cp`3m4w3Lca zi%<7SXhjN+)Ybzf7T_{#Gw323(2-xAyFnL+8ej4_4!$K5RJDN4Jn4=B9sgRQ0$Out z5D%{ZK?x6!BSb*eoP{u`bzX7PC&+zEJ>H|)8-KRY|!JhGe_yoxxU>}1nZwI>^w4BfbDeHsI zWr8@~k%55$;&{-C4v6DHhwwovcWjP#M00!`$nlXN$0y)-yr~Q5jsUQGaR>f!@Zb%o zE}_H+pu+>9K4^a90diXbsC8vvd;pYFK}={J)qKPQJn2#gjz37bCg9nbE#cW&3|ww(1Ot8uw%C;hX?<9AC&@7FhdMOnB$|O;Msi& z>^jgDn1?Mu=bVEbhnP{yz%io)O6v*WIVD7*0AmnOo^CX0v(J7jc-t~2FhfR zx(L*khBP35f}96V1sdQo!ngZ`tMLJ_%UTbVg1Z8sTF@c>Feq_$gDxom#S26XY#peE zfdnGx)?;WOcE-RmHh4m-`Hh0{f8XvO-QPh+V|3rN+z2|ayF`(Hdts0#|N8G92j4MU zT9pd!xc&eCf6#ay=r)kApe?bT8$rWHpapk1Dh{m&N^3nVD>*=$6-n3w-y0?a3ZQQ2 zxiI9Nf$?%b0|Nu-e1&RKTmYJk0j&t_4TJQa$a4bJl-KsnKbZKz7m2xm?!$mAiv-72 z^Qr%6UW7r|w!UoFM4lBVUk^ap;82MX4S9y`-ENGzY(g2q?aJyc+ zkYoyI1QFypkZH)CWB$RvJ=g^naQxeY-H>FvgFR4qK1e+N?ZE-4+#n>b2lJ2S-JrD3 zP^#n6JsC6@2b%j-s^x?c3mn z5~v!2tleJ^A29kICYUd0g4BW6D}y%KR)F2I7jDNHCfJtB+i=!Pxa@a03$z6nvhPBa8K%RFnSlXv znr=RPOtB2UEw>szrr5^Jz%UbR?;5ze&2V));OaozazSHkEDX1qVWIH>t`}6bKth}u zzKs^zs4GzcAG`~xCq26%ce*%2k|(J92CX_ZKxY|)`Zl1}F*ujD9w-$6rCjj93uxfT z0n}PK=7Cgac9(!hlpG+9XB!3v21vyT8gxcfoDyLk&2Jn)_JB|QE#(DuMj@BtIm8_X z)z^^b1c+4t8a)f>&f@UoU;hHsKG6VOi5&qt4zxx^!KK%o5$d)Q0r1htmtjXEg9bHn zR02S|%0a_i&`b9m;tqrE&4IWIG$4&|6?otf5-Ol8fgr7in@~5ns1RyBctBf(pjHHA z62hbVdUp%>j%JVU(~#*1Nbofufd&wwWdYj319b}MN|NEd_hB9bC?k@x_W`1V^1Tlb zbC5GaJpc7v`y631cFflNIlI9*24kiW$&+d<)+eVljJ3)tP zl`wTOgAU|o_UL2>MZEwM1A}k(37_smphT+R(R~p#(rExooZ6rcXs?e7Xwb~~5=>Oo zr~5ounNRm&*i;y#@74ox1oX;CpWZntAZL1ZM++ESU|`ALV9qZ znQ%yZcqRh_!%T2tz82+nC|EDLaTxm>wBM73VdVLH7Fv9RZ!rX24%NFxeK^lMY4URMq{<;JFf}s;YYZtsdENfH(O4&hF zM2W4d@yX6w8Bl=^TGIl}w%`#-#AHnC{}NfyWlkU!kQ45}!SI?3)oyV4&CltpfvHg@~9j1q~)crY=CYh(ShULHpbwQx~9nb?*v;3ebfN3=EJtn~e+% z43Lq}hYSo1kS#l(;9GV;Ej-AU9nh_Kkm4bM5jHMb&Il_gKsP`^#!5j)mq2dMBPj`i z@0R-n8WsYj9Z(u#>a-e7LZl=i$QejfNkWj>3Q!V)jH?cbBs5wI9Vi*8r4Xdr9xa6i zz9Jj4r4W^p&}b<%a!VmdBYLzH8ZCtwSQyA_PUB8Oh>?fTaK{kv%u(+e6$a3pP~#g0 z^uE~#@WB7x07eD|SK}w(l_4K|K=&^G1h=tWJ3*(cmN2_^DuPEMd^**ATmSR7fcD(` zwtnMp0d)Z&3-S}N^}9eDt6+UF7nK5!UXVbii%Nw@cWVUp?w13!`xOB1e))8!s3?GX zSQ^mY7O49L7Jwxnkfa5u`(*&?emQ`;Ujh7rt`ktYUk#w{m#dFuii$@mJBSJ>F?TgS z>C;)O;A(upr?XVU_>xa&ssX4QnE-Acw;m{g4WA@n44?FZgAQr*1QfQO-O&b~-NhE3 z-H$yopxInVzY25{HDqcWv{f21T@E_r9MZ445ARoj?11#EKwHNl z{VLGLT*$CXA5zgRBhr!A{GhEPISE-u0^R`L&%gjV zLg3+KOge@3KL_M0TH5f|SJ({Zx7)sc|(?5`*=NQn+VE9Tq&|C$0FE*rX%!H3G z)xpa_kUt>hAgFqS-2M$7D1@vH^z44=3R>I@z5koP1+@Fo7c|SE4yt52!7GtVR0>>M z50r9xfa)!7Mh1p%Cl*`Kb~FB#zu;vFO#CfhzzjkDme&jn3>e{6VSLG>(^&#E77r@Q zJvy^FJUWX7P=d}F7Id8<#8q0z!Dbn3QBvYz8LUyt=3yCZP?EvF-5oTu>%za?9W?dp z*6q&I>Fm+%F2KLuorizDJIBEn%$?3Y5CIS;tHd!4q^q z&rfg_3fdgkdYiw64|LJ;cK@Pp2f=$!+}-%MpM>l?2kkv^4*>bF!1xmXcK;H`?;m{l z*B@*C$;jUVI{vu%Co_KwXyJLU<1f%Ehj!5BG2@fqbyC}zz=s+L6!|rS)+RA9)wp_s z4=?xtI$tCWw(smSe?E(M8ovbtcvSi`e?5!$Xa0N_ZvkX67jFU3K?WY6gA6>aPw+Q` zu3t(6pM%4{oy9xt;0cbjP8M&E{|7y-PZYhuyj}CK9nA#V8!qp_kOp@j=*W`OpFqtP@Z_L_2mc9=PL|V9s~%#p3ThN+hMQlW z;WNJgOZR86{%#i)2mTYCEZtDePq1iie!;}w!U#IU`~@R_3+U`K&|-ca#~l^taco{Z z1{z>TzNQpvzd7Vk2@Z_#|A7(y-N<1La&5j#H){BIgTkK!5&pM8`=Y?%-_6pE?57V{ z`~*%8pi~aBn135L|2Ai)4(1MTCXe1|X7K4SudwI>d!wYpv-yWWkvA*}IYE;UB7Vq8 zKgDtg{n*nF_-fW;pj-*PKUR!^fdP`CS{WD^!~z)@SQ$Wv0-1o%sMyEAzyM*Lhp%wB z#>l|%6nr4@ZTJcYRwkHUEhYvAL$F@ZP9+HI1YGtN6U@ASa29A6nIC9^#uFT#pm}-z z?T#gBpnM13;+O_nJq2Si^TSwda27k9B?w^|A2`ep_ZR3`x-Re(66pBFZZOM`fq|hH z%mS^h?gO)285kJ)!7P6U1_sc9t1Ju-!QqZ!I~)XHb357(kk55!QBe?JVBp{GSmX<< z2_W0tJW;o~c?Lih7{2_-z`)?sxktr70MzyQ53VjRflGal&NV6(0t^g0KzA;AbZ$|x z5MW>c?ThPv0Zv@9-QBm;dzTg49mdp5($M<5anVEmPcYzP{KM(%(=X`rj`aHD%@wcXf`gz?@CwD{q;n{s0bWlFXqaNKo;BZ2k zAOZ!bV_banVQvNnhO6LNAH=lpD+UGz(6PNN47`jC40hnc9@JNa&fEltIfm^Bz#AiJ z&6Oq0;3xqtrY;psYpyI(DCGiI4lnsZCE<3*B2b8X2rw`}Vk{F9V=oT=1#e8u3}}4| zS`zy5Cg|74k3s>jb7UjoM!=xhyGTy^eI0r||Q`vD>wdRl++?7rf`?{p0uV|k#X zCboN*q&5F!;%@;>SEV)o6y$FS1B*KsL0p5eO%l|sFa}wB5EOacKRrNO=RwP4utni_ z&|Yhh0#I=ajz;54-A5r++;PwDYoH?^48Wxje+#I3H9nAraBsIui^BhdouGpWS-KB; zS|2ES`w}!G=h=M@bn;y*D9M3);XR;q3psBq*t0j_FZ6hdPy7NdM-XRf@eBAI(SV(r z#R8Tz0G*KqIv@*lkdDt03ykd*nIzqKATJKPP6l_lUzg_1JYsDP3HmJ|v~V2*Kd*i$HI z0T86j1g$NDq*YNS*nY+YCfF`Z$SmUyQ1Sq$Jr8Wfhw(RXN__=dmjpVIjUC*2oC7W+ z__zBPfYKQUq%5$00m`iWPFKN63A8$nf4e&<^D~z6LleSzutl%a)0%%UqYDP5HUD5k z7qm@l{=tqer~xW+(FKJ;tlOx)XelKiT1uhNLJw<8$pT)*dUl_I)UwB1 zx=(!`# zk}e5ezCi~tpu1!hjBk5_mXSgZg$J#z1GT5W_{QL>2EkVb*D?q`q%J}5nL$hKTmP3B@ox_+@Bp3e06N_c z9;u+d=6rBp(*SgrE2v*K-{b#b59?b+PoOS7h;Z~_D|p-AlYxN&()N!8r6F*zfW~?t zZGZ3?hhVWzc-tSez!1{*AI$v{&|!c+-IqW+0$Wd(WP;)^1sY9EB~hRxUJ?SzbdbD; zr8`n$+I`WJfBhv;%24RO>Ct+rln1n=D@H{Dd{Uri_epTt1$7BNgMt_$4BjoOaTv71 z7?RqE2_*boHYP}y4diAB3#0P}sp&zTw^ndufjV!HnjX}7gVgk(&Ksnr2X)>cHN8V{ zSU7>60%)B$xKIKmen@hB=>~3!J3^b{Si%n6F#uly2JNSTj`jmJ-9YVa(0tAkaH$0C z9zc3~9^XIuKu+I-^*cbfm|6b^?YDuIRiJJ+KfG@MI@lJ{H<-i#>SW^S8o@-gH*chI3Jkd)R7FU=sGa*#>jB#O1h0k^3^wGC3lLi$+5_WU3b z4DpkzF{G~!ir{WUMFiv>pO=^>`39(QE1E9~`B~0$je#2T(dt z&}z!W4_r-w&d2lUhItO+UzBPJk_K>gpm22YL1$M$Vj9%jfyA#FBLjmGxN+gm$iQF> zW(70CI#u(KVm~b0G1L*XC$a!}FvdkC0JJ^TMa2SihlB%Y(Pn3fih)n}E06A*-Ty#m ztV%lWIB^^6XrpiIThQLblh|cFx}7UP*%ma^2bvhp03Fzw;9=PeZe29Tvw$W%zy_Sg zZb0*H@X=-bt)K&On|Fgw)Memr1*N96=IR1Qk8W>?(n!dLJ#b|G7X}?)%iqe%0?zpX zprbsMnh*TxYzAp<_!Y_Dd=)gfz0E~M5p+H*|Nax8GaD7SPw;O$)Y%L=sI#*hbZOyB z(C$3P9kA1^JirEX@VA2Y;D9!~fzEvNYCiO%b28ZApOO5{`$0F1f=u&sZ1@q$-@G1b zktf8W3m_>E?hE|e4niyf@qHn3U`P0HpXi(nvfGh=+kwtz7e)q#&Ti0g*9i9OXMLjQ2E5a zz8hqoWA_2a?hB6p4|r%FBQM@^3rk*4?rh9J~z=`5h0uVsY-C0;(z)x=$Va$?W*;AY%!8_o0J7 zn3zvEemlri!q$E0;t%E%y&{5+-wv`k@^3rH?{cyG)c2bz9Q-b)T$m5KS|6_$acukn zI$Xb&!*K_Qe$DP`ecYAb|2Rm!TPM`Q?jGB#SY+NrY#>|p-w!3Eui_}5PcMGyb_qrE%~-G@B1Pk1t)bLngW2fbf1L@CT) z&fQZ$LrXCK976WbAyoey0{Q07SDz|M0Y)tYmfUKKq&-z2^U%{7ruUKQH0}8P|Nm=I$d)zGpscsV|6qa6&7kDw z0UAy%QAzOWP5~X~v_=I~6oHR*gO92`b z4ykEDttH5&Odt5&M4-cvAX_Js;Ttfs7#J9s0zixMK_?4AWI@#yL@(&PV2CVeJ1j)@ z4HN9>D-mV}2GIUEW`s zg{$#@aIXb4r0d%K9a^57VlQw(r&3Bf?l8k4+wEKd8n*=%-=ORs0E>d=KP>Qk=eQS? z384mAU^mFcayO`%z~4F@aug_N9|YvIVtBdE-wMh!jyu>vi=KKz&wxu{cK+5$(4xKh z1$$>RSl>3+Gija8pau{BHrI3T64muAs8n~o021iz<^VNLL8ZOxd9d&$h%o4)T5u)c zdXf7?XSWKtpatFX+T9G2dik7zfx&SHAH)qez;58r#fe-G68wfYtOzUiB1KGFD z^)|>Fhy}O6_TB{vAUo#{Sol6f7<4)$MD;ze@I#0&$SDxv2VmjHAYuOf7x=fiKH|R6 z*$tXO=ilb~6e4_rf1B$QP-Oz~jSk39lLbJ7;LTumvj~_CGPJW9;#ZIXoy{_g3=GGc zK@kHwCK^2TuENZ~Faa7{WrfH!OA2U>nWt;_@iZ6f>Vjf({GcUvuwr{RsAPBHU*8Q1F~{x}P`YH`U*8N0UKjpthg_IXfV_F|Cx;93 zNmuK$uKfOIU9C^M^81|jIQW9a5o*p3(AILOZ3lmHH2)Of_c;Mc3Eg0$A<56B6U=ep zUq2a~W+6^;?4AO)7GeFtogniZ`PW}$zHsmtha>YPN9(JO{Qg%RtuH(B`&@Qxe!%aH za4g8IgFiT$e+cmV9PDfcaUn^(6YP)9$so_UbasQ%L+51Fl<*QXpX3TP%MpC}gA4z9 za0YSQ4R!@o4jL%@+b+2#Oy>u7z_ zk>BTJ^CN!8?v^RwdD8V@g$I9dI9eZeAB6L79_f#i_RZbXQH8ed2m zfxk)cH|Wyq9MF-c$3UGrQ0eH=?fU|$hKT?zFa)3NEzH2c0I5qreQ-#906JI?a?c&; zdMrqN02+3I$R@z+642-YL>4q22RT}C5xmY>30DW|DnZmmF*7iL`t{5V179+Jn?}bHz@2T- zc+k)rOPB}VnGLF1A$?8Mu>?YcM4;(pMo@neJW^E34w_CDC}Bk!SpYTBK*MdIP9kWW z7BcLHYls`Pc@)(A08Mm(js*t|9f6kLB8@C;0Z;#-j4XTruS&r>ybT*$0A(l0*g^wj z%%SKTWa$&Og#hFXZ=Z(_D_|Mkeg;_*VB-%SH$WfW2CW0|>^=f2RzSY?=*B;85RYTr z0Cc(+WZXcA5w=JGbSX^}cw7*4O9W(*0BBSbvNRbqOh9P78)=QvOK_d|1H4`bN=t59?cKYk}gSYsuoFLoRjEQj@ zVz?Urur{uhNRz-x6*QO)y2zP@0W_FB6)Xm-NT-2WpzhgpFbh=aO#!or8T14dLGXc3 zNEw7{_!_cC6_N{$--6c@fKH``j9!DXD9+Jq*kIrfPt?)tP2eI2vKEQI1=LG~4C#Y5 zb`u!p1fAR74H>}(4|8T=9p>}_-S~E(#McGX9rx`%=F7kS6lfQ=3h2a*lO?kJ+k99Q ze0v>OJpLbWiVi7dnfPcP}AKalp%$ZhVjYR0`vKS)9suj@3sU+&c?R(7!^}RUYeJ?3Qs_AB#kGx*# z1Hpb5q=x|NcR_jxpneymhXCq#L3#+F`VP`V0QI{dJp|N#7i8xJsNV%)4eGv4Xc&P7 zgudM;z)hu>;6u7l?wa-JZUkLsx{bbCsGPqDiA zKa|0v`^L+~pvCLA!HU7doSl2XYr}myAtiw0zW@LK*TdV$9^JkY9^L;uw0}cJ=RpMl zzx%)L>;FRqS`UmJ=ucXUJC!N1)Vx(vw|vOfWKaguKU=xE-|lOF(VTbGYbk4K0BHlqwWN^XZ3XzU+! zg8NP|3$!X?7nlWFXtEp3@?d0O*aK$yFfuUg1+zd4miK{KpnDnjgIS=%AP#_8F`&cy zz%0;?oP%Ii3L^u+xQm{r2az;F!AssPP9f>||;3=Aj0 zEYOmdlVBET*~=*~3p5*l8qDfpWMDW0W`T|mJPT$`VPs%92WHJ+WMDWCX3b$_V7LHg zEns9|xCmw~VPs&q1ZJ&ZWMH@qX02glV7LKhZ2)c81GBa;GBDf&vp{=upMhDRqlvGB zS)emPpMzOP7#SEIfLWlsdY^zU)(_Ai37GW<9v{Bl z7hpGxgCY*xQ}zbUYJxhO2l!h+-8j$Y9~?!c&>O~`J$eKGc=d`j!EYFkhTkv_8cv05 z5``D-pPl*ZK-+W^52W!IJrLlJKENM&kYA7yB<|wP;n{qUBaI(T(h;<0Q~`AP_(5Om zQ~b@K!>K>>3xIa8F?e<#f?ht(-&70Q2(Nw6vp1Ni(aXb- zk@xOfkd?{&EucG6!I759$iVQ4UjTg9yQ}eS(5>+NP2k1;y+Mqgy#aqb4!&aY_0sYj-bUvPQO5!euI^QH2nm#@Mz)ysrn1j^bf2Q#Qg(i z;nBq7*?dsIqxlshf2+*@|NnihZxs7_^ae9}^@>D!GGF%9KFq(3$+y>&1>|v$-XLbM zf&AN?2%7u{9PtN1ZA^~Cpi=#sP6;03*ntgO514 zFY<5m_ycAfe8k}hN)^p77)!4m1|K1O3EX%C?RN#I2+-!NPy7O)?YRub;Eja*O`zq9 zKD|yHp1pyv1mV%k0!4Iy!p)k?Bpl@$kS>3 zQ3pX=4Ou|K0Uti|KfCmaKk{-Ke-tR2IdXu6J-(o;24^8q{FF}jv_4p@3{u6?1WN57 z+dTe*(i+S{NAURa-NT?u7EHi_ZF~Ui+l%1c%;2q)ppBDFH4F?4p1mIbeR`e#`1i7O zc=qym`C4BnXZ5f?Rh;AqURL4RebS@(;SZ2~HxNY>e?Ci78vmoipZE(7-1y92aN5kN+kKjt(i}T^oIL&?01b}6 zI}EPN8yzm;0gVio=7M*E zm>3`MfGi>MfGo=J=xhcp)$xF=JMicPFURqKEQ06+FHd+4>bD7&ihxdX>IO}qy7W3h zZqR!z>A0ip7S?sT9^K8LsZEd0W>6aoRG~3=90xDu1hut1nvW<%9|p~WLI!0)eIv-? z1JGS0kUi_5GZ7$*3LJvNc7SF&a1YUfyBs$`Jtsbo=1KvE5_WJ9m4Gfe0j+5D=nkv^ z?VRA~bz*!8xA}DLm4`L>s@7sj#~o{N1c^3S1)eFE?w=m~>;HLJzb|I>(Ej^c0Jhy5v_%-S zsTi~ZDta%d#{)_p@N@@Wk!1b1`#Yr5!|(pJ``Z6t0gx`x*(ESjT)Gb!U+O;Q`29oo z2@l3@aAzd>n1?1fhCH-?lx~MaQ}-GbaAcg(E!(Ed=eY656L8M-;v_&=y8)cwxG`hy3*`wLhSEfs|hTwjGb)}#B! zYZFlLW0#V2+%X?VC|mz6w!`;*Y1O!-It9aTZKHj zPd7hg^k_b);?XO@?7@6-C+ONM59^B_{O%V)Q`rYNAQ=>N8IQ0>_u=LT>>kYrGCY_M z!^D|NjZuwcJ`6Y2!-M%C)Wm}x{O$+A1sgcS!%8dA1$Sq`MX(;|2x%}2bm%nXdWxM4 zu!}0d)le~Ln>A?VMHW(P5_HTNs5Qyo)CgWG>Ga=|`G9Y4z<x3LJKw=yACdo&+p z@wGltbmy=O0|SFCxUmUZycGdvfp)TMgQM#p0|P@AnB~#@h67rY-U46Ib%4JG)X)YW zn^FL+NdNmXAM`}7N5j+j^T9E)3$!2~Qk{NCSs ze?5;ANGgPxBaJ`dLK^?i%W3={uYe>%oCG|Y4+wxN&qJWf)4`)tM#XUlsQ!Vh+eA4| z$BBQNh(wyB_67d^r}_6^;$MF;&9PGiR>eN_=$29OXgL4@(}QKZVJY4!RH2?EamHU{O19_ z1Go8z0_Xx0PY&>5RQDJe7`{6?fW~OL8$oU_VQqfE{+hY@0b}cd5)shI7-)BbflK!R zkf{ca|1W~hdIBFX0lEO*ay@wJx3wO$*w)_x)V{X>U9;%WdXm2t)H>)5WC1r2{3ZTh z01cb{Z#__A#@Dt(MkdUQ_*6-FN2^FellOLTaShLsZ@-RC{5uX*shUjxf`PX`$b zHM98@%l~k2odde=$D_L)WDHotF@CoX&}-ITUI3kb^pmkPq&JWss`3AY)&nIn9^L&A z4F^GmO>bZZNGtONZ1!+K?E$S%_2`}s*4BJT;&p!WPo~nM-asK_gB=NY5F8MY($E81 z8iH&_@})<&zl5vt0mt3|8L-7A{4h(qA!j&%+VGH_xR59)(emhS2eo57EWv?LBDEJZ zwDwxqqq`Fv#H}Yw*gd+#75<0AZwRntFX4rq4QA=iQOXIj72^4$NQnheG~Q%jV1V3m z1X@D?VL8I@Cjy8-N%}!SyC+n=42oy9Yw2Do79LvW60AuumXY$AUJk zf){UrD+7(gJ3xbvAi=|+v*aKr#p^RLFhDLF0ILKofYky|<2!^ohVKAPID-wuSdjsm zg9b1EgKU_-4!YhBRCAnT2JQPgz~6Edw0&v2V-f#$&jOFmJt`3b3=H5#*AJgg$Pyb@ zwkg9mqCNl9tYnugPMIV{M+5!T=>@?a6I^f`86wu8(@6NvHQUHk1pLGJV2+O znlgh9g#(Rd&p;dx1Zhfne*XrV&jM|PbnQOo(yht>sh z>A~;*5_HC>flv2QuqXMqyOn?sSOSfuH~(PfZvl0^nh$_ZJ1eg;KIs8EKrIV&a%=N% zfg(r8=08j|*4>9e>(SCCfchLh2FP^EXR@RCyw!{ODye$X(sXz$cj91eG+P6U;!zjWqvYERn-`f*H6+0JRjfU%;dJH%E~> zI3yiCz(M%~I>-SEx*pW<1UV<)r3W=UdqCj{KCBE9o}i`ysK)4a=`lcY^ShUzOVq$| zxD1pgdP7tUko?QPjf;Pq6H^CMhZpFaA7+o{*NmmFUe|#gTT<=;igG`&Lp>l4g`8{$ z?-_vHOM3hl%fV|ltnu&B`nJT#quWmcTp#&#AN?Q3;lbDjE{IzXl!*HB-vmf+v+SLD^p@)ndSKpT=l`Of%~4}9e*B%6AC z|H!}HuK={H`6Fz`1eD+5i{2ZLfC>i3_&BUvmO(2^Kx-XY7~V55Fx&y3WM$0=JL3#A z=nPr^=mu}PgSHStdZIz_rh7O1%rwv*5y*UE=1ym8pgZ4E`ftn$pJEpoXfG#a$gmm&z8u!gRL7hT|&WWJSj-@=!zZm&lFF|<5 z2M&WOitdBm7ghiNmuEQmO0@COe^8kme&CY_zt(})la=iJ;o#2AC;q4djfdX+`Tzej ze-z}pRFDw3rT~?nHQ=Th=vr4r@O60*H+yv7gl5%--A~v+x6;To?|uVj)WW)!tp`d9 zp|a(Yjyn|Dunu7S>jvrn-~0z8-R zKGFQ+e<_dANysP%sLtZwcCh)!fBx1&(Bdg@7^yKZFf0eBJqHE`hLhk8d!RwxCtwz6 ziyvgf12hEH#tYic2TBm&i{TVJx*Z)r7uL2OD3SB9b_^)uIo=2gX^>5?)ll8!iQ*=N zi;PZsbUQonZv!1mRzkXO4nE)jT|G%|U>tlPfY7cC4oh8xyZN^rJorF@`8cRKWdQ1D zf-Z{39k^g+a6RCrH0Ty=NT?Nqj+g?6A827DL<}^94Pk-96qLCjH)De~6GN_%SMccm z2(ENN=R|a$bLrgngN=cq`vq9c10evtV-_KB1FQlRJcps0z|I3_DUj%KkM0^34-gC7 znzI0T&oS<>90LQx3UG1&ou~j|Im5+5;4FCB_UQI>05!A3JgmI~isksXF@oykPH*rT zFwuwMi4l?%4mw>2sTplF90(0ZVht+Vk98v_G0TuN1-MG=4NGf-U*J&p)qn?a82b>!#-N5Azk{+^)!|NmQV21(aS zI_?Mu6@8Ffjy-xood5=~*SjZ!^mU)R{JhuwKPX`vC?C4~N)xQD6D)@m3SfET1CSI3 z9%kx}QGtdKXp_hqaPkL@FhE$K3L1VrUbq=6&$UGX~fNFRTqO@xez(Ep+-f) zACf{EYE%@{`CD&;5=V`SQaXPtsQcVdqoN$l-+B~e2{||IuaOq`Xn83fy@xMo}Clm7z{%wx` zm_L9gQ(n1RAFSo{Og`Ykzb%Bt!LeJ+vH6g+3&=JNkM7fs2OkJH^|Ek*ZBTbS_>hD7 zxGVDs=3`(LnvTqe91lK_aAiK>*y+Xs7E^O&KH=Kw#{qU2XeB?$so>FOSW^xZ8%Rw# zP;`OM9Yt(~2NjH+;4&SwX1^WG0xkNU4^Bb-j0_A5!K@9S$!Rc)`I7ZS(3nUmhevng z4ps(+!`2f)6Q^MQ0TAE#0Cd>Zr~4A9ZtV_q05x?yz?t7O`G-d@i#VvREfa^OV|bMd zDSdppkHfE-XRLMrdkL%*Chck9*?r10`2u8U9^!w{uqqow0V3YP-C>33!=NoCkmhL~ z0|NuZo1h8{(uf3aY-{|*z`)2*#NBv^f#Lsu>%fF!=I+1*P)ie1?SsZYdP7t!kQzMx z-6bj(*58XX8>(X%N?42!yq5N8u6AH3;pldD&~^qVb#I7nZD$MX<3$GD*I?Q)jVs*< zX)c0`J8<0ts;f&>450pxQGp6-Gr(d6G@5n_94nwza*%keg3E$tf*`W3Co7pj&BWw` zjXxP27#M0Tntuw^@<7WU{(USQhZ}!_F4KUD)be;7{J{zmZ$7|!*yG?2E(n_oq>O=q zu~xnDCj$cq1F~{{h!TE~auEny1fpC4S(zk6N)n_@4#Ji@3|iVE2;QWq!@$4*VMW7P zph^ZJ=9zrp;7=x>UX~`$6%EgW51Ej9Y63!^9r*<~ zK!n2|evOCx8aF=k$2|JX9~1TGGk?TIehttlsGz`S{s^Z(pt*C9s>7f8BRM|vM>?>4 z=8rk?nO~6WGk>JSXa2|p7Ek7bpZNv6ethPSxbvA`FbbqcAm9hs{4e|(AcLWHM1A?p zA8`s~q{k1CE&LHqUqC#EFZ>!97Jv*ywc!KI29PF!fDd3B-k{p>25!R#s10wxHoSq@ z0CGOaA7C$deL(la3z!WcO#%*2KsE%tfCNy~6R1sa8(u(d04Wo6c!F+&XY&h%|0h55 z3wpgkwFeSRPdu7mDYTyC@6Y=8|Nmi+?js(Z5#R=R0jS>!8rJLn>61JMe69z-X2A=7 z&59eJ`31B;^9vNSe0Jg&u$k?`ujz2Ygg~((T;K6YY_F5X6yb?sWu7cqShJaaFqE zDT=3|^*?{#G|=Qoa*GP6iu5@6fax>8fFLMz5XlIXh9Dso2~S4+k$?4c<{>ZzK)D-dfGru6`XMO?CAD{UJ6LF`YNQ@NZ_~kQy8nI9ZdiBCMVPqhB$ z?=SoJ|3AM5=oFaV7?lFhrOB@R8qOD7`8B)`xbkZRZ*b+;h+g2zuaP`~U!z7P0DPUY zPxl><#y6n)$|Jc&1ys{}9Q?t2@F9yUzozqv&#wFeERB$4APA8LB?Zu$&w~$`JiAYL zXrJ)0KFHt3!OXzm$gf%a!jWII`i3jNruPn4e$C(&uKb$OGhF#KlRI4aHFH!HT=+Fh zR0Lf3HEmQN3!Fg7#^VRbo{TH}8ZSXy2@tR14!_1{5F-K*~6x{gi!XI%P zq%r`MHe%j^m=Pa9q8~v_j~DzJU=uw+MOVyUkOU~$1Pj1M6_nAND8b~2XC~^rnwmv)ZM;!j_ z!XF8V9gyrz7yfWi|AfJjU*qg&{uoex5M%%u@cy$azaY%YL!bF$E;{mSod4{~AMx|E zBYzAiVGBBZaOBrG3i5gYD9|E~gB%_302B&;Kl2ML0cU!Cja#4j1=hek6>$lq-T@pN zw?H0pO{u9iT04;4nMirh4Ih;RevLgUpixMEjV&smWhXBD8lZc{8jpZ-)!~VZ z3=H+)Q6tcLNC*qmvO?Me;hB7(`=&>)4XD!g=rw8oc96M5(s4%~C)OdNgTI+tZ}azo z)`o%xvjf0;5Cp_N^9%5Q<`-}WRpN-!Hx5zy3W|T`k8u0*nO_j32BU`M16SXWx=Fwf zRDXkt;j}-W`31s%fDA^hz2hKdFTbEawDNZQ0x}s~;POX$D1cfAD$qg;WLji`%4hz_ z2!+r50)C*X8*KO&kl~<`(jB$pjsv9-(5gm%Xua+B24pz6wB?WVZ~z(Z0y7*`Zzs5b z40iw-4yv`mhQ9$h`U%JeXq_F0QtI*x`a`R1wwgJb!!IuW=?B2$krMF1Jb4BN zh8tiTKo`3~ZRm_q;Xn*Gf$rT41s|T({6@jC`+!ICE06Appk(XX{o~>f*X{?72mdn} z|93q2hZ%ft(M^|L9nh5@4352}?9I=9FcxxvIw^7<$&mdNy*y04A_mrzK?lv$OLk5M zrTo_-t=~#`J(ABsdK}#+UNeKHH9LD=cd#-rFcx!obWa9Z2{j0^1c)?)X0S3aIQ8=E z1)Z1W16l3W{O|{(HwS3iPsJk{Y^G1yu zf9eG7xB2MVJprWGr_(jSrL)$?r888ZGnS*%RRWYy3>~{WK>D3}S-`R0{NRW72}kP= zkgc_>uEtMZJNhI)^kIJF*a=qm+Q>8cIH* z1joHgcc_4e_CufE6cq>0yRZ6m=6ZN^AM@?y z>G4cH@6qY&;L*#|=VAS^BoMhH<-`IimN-813xIk<9N-ef5wtM-Grxe)XMO=E7LVpb zpk7k|h+qJ%9Rbx*pqfa~v-u@x6=vxUP=yFyJmlEx{?{YSo>ieJD3Bnedq z(&EH}Y?LRs3+MqVPrU>{#yULs%pd6_f?*b@u^<3ZCxTE1>NLQe~^G470#ZZ-_b>h&oUW4^k`P(R@e&#eFY4n_q$!?ejMu_y<~ZeWoY^7CfW`A~=p6 z`6C@TJeyyjxSA6b$-dTSidQ)Hy8rO)zT=a8z@z)0XYwJhULJ5s>&bl9vsa|Wr&pxS z)A}rbQv(YFgI6z0D`<&=NArUR9=4!d!rznxT44Z6kgf@y+DE|q1i3=_vA zEdd$IiLis`Gk>HY$O@Lv{1LZ4JMjy~NPv?eIMTgEptgWq1+fJjmS9^%ptgXLQG_#y z6m$}R+5+nTf%EKJ!Ngf>Lzk6L63@@e77XNPz570f(9j)E))6J>b*_ z_KynG9tBWa-Wfy+Iw?TyxdO810>U0pBPtM-+9N@YSEzqLjVdP{usu3ZdoEQ1tsq*nsR@0J3w5NAtl3guND?&4(R4L5nKa{`vnO zoCQJaaXp$3ium?=^LREN;_>Nq=kWm1+V??@1Q%+aKcJ5M0(PV`e@uu3sC5i3WW9cX zTGz`KBawRAscz{aHK#-6m*iDZ7f&riwGS_E*fuKM98lZd- z0VDyZ<@%y8l1; zpXK}QPFEh6?gK8}r#-r_LD!|Zb{_|;bkTMdaOpnn`2Pg5+_mE_DjEWy(?$>eVs-6} z|Ns5IOJ}HvWA`bDrtb5u-G@D(>-M^1Ib6GsyJ#PVYl6DMrTe@`_ch1vQ{alc+gHS; zS47&i`#4OUtM(zVagP5_K$ODN^|C}GDRa?2fK8bz%TCZf*5(6$FaB`pt^99&kiQSS z;Hdk$BdD>*aPSaQ^MQXZovs`novs?a9!#CS3KtJK_Imtx?Y`F;YTtXnVqLmlIDWqgy>2Dq)&>8b!S z5N=*)ii!bf)mdkbiU!y-=Rva@3Sb|3FxM(LHoxQef4cQ{{Z*gtT#s&$yJC5Kx?MH? z9|G-B>I`K8xsty%8S1U>SO(wjv)#TTko3_RqXIFj+f~4)yEdTtz#pIP+z5~46Wy*h z%?JMYcTaf_YIOS~fAH;P>GAL7+2U(`gum$@Xyme&r5BV0L5tNqntwR(H-UC*`u4`C z7{J?ej{Mt&x>=?KjEsbqtz`~p$=0j}~3T;&IlN)EWnHxQLC z;40t1RlWeJ@FZ4Pw zc{aav0P`HKz;#>zHNJeU5A!$A1Glh_6cu~+R&e-gAN1{w1O=p!Z*LHbM{oTHaL7Be zz(SrGVtuE(2*j*TcL9*)|6mq-LxR&88l0W((D-rgbYcGC+5B3-v)AE=C-Y%X>m$W8 zJi1*S{JUG;gZp~DHsFSrN3Z_}Pv$c|)(`ocL3`RgdqY$>Jev>jz|#Y$l&t`l_5ok` zHI9Rt>7bJR6^P;V#}QOGyYNSNf*M62uV|e3%pc*&0~(EU1hsTPqb)x_^GCjP1dUPg zM_&HSAMwHwJmwYi3q0uP!XI;@K(=9dBXoIFzCd1$Qi((#gA9P;|!paRUu=bvK$Nyy)6W0z_1(4Vi!Oxh(s9;1J^}9-6xyR{`W}ke8C1< zSPWXx=Gh(SV12g4-L-q-16BqGN9$9yO1`~~Pgofke3%b9@^54CV7_4UzliJqLC^mO z9Ia2)ymquc#qV;~(>gGKza6wa#nt#g^UweMt?NMPyt^;}y85HnOUlFg9DnmX1_lO8 zrc&vKDi(%PQP|uSr=BbPIbr*h@t00{spdALyzy9;L#QgvNA2J}t>v$Y&87Md)gHRlxg`5JQRb>*O zMFt9oLF)vUgJ+{Xn%{srIgFqA1p+_tYk(4L;*-z(F`%Ld+~c~yuW6$4nO~sr2Y8Ak z!t=#v{+PlS;F1qE+JE{pXs{nLnZ@{-Uoa3lCjy#@c?p_E0}cK;bp#*-{GcKIGoXFEkQQ&uG0?6hP!s$bzXr&9K}V1gfloY|k4Qkbpm&z2fG-Aj z=>EvBSpyo@*K|=)=)RT4pLYzj%OASM}7^^ zL=uXIH2%DEpiReV{Q4(~SesA$e=YCGA9a9V1G4o4Y+mz;|DX7EPFSDg_rC#ZGJ^*T zOH?92Q+uFE3eeTP;9Kl985kIDgR=u@Ez3oae#6AIteNYWh zK~(#of&!oo*^C~YjvOAHjsk}rz-z_9Tg5JwLbrE=7S}j&fTpSpK*QVY9^FF!FMzfa zISGLHx<1`Zp#59d|6lNc-15rn(|z3dl1KLmaQX!84Cn?;iGUZ$g4X6im)C;MtcC2q zhA#*G4=U*xe7X;VwS(qQqCNhDr!cu07#NJeiTwWq)`yBdIsQKkx*X{LVHfLLMK{33A;{evhruL#)s{#1O=2Ca4|VXR5@nBW zCk`aHdUQjAk=v)6$)o$C@kx*W4?ruBK(jW`$oS;}S_glr#2R87By3u5mx%jxpZ9@e zc}S2#!^)={X%#b645AelvJf7~HwKV}U-5^<;Q87OIe00o*nXbwi)r~9Z!_jM2GG>k{Lut)d#|G^UA zYl2Ha%bP%JJVAHqfKpKWVbD%J$eK1#B1WEX?Y;q8eftx7N6Z5YtW$3ut+zoFa}Tl0 zHt#lIVqkD6PV#_`dvte$1U#5ecD9^0VPatDZU*uBxBD}BFdyjbIcxzEb7w(TX2Zn5 zV0^OqAL9F0 zBcIE&TL2WB7hdanb~}Jm3y*I%gGcvufln;7z2K<0HF%+JVLAMt{5;tfL7QELRw6zW7OlRm0LyzMu zpm|gVk4_gA4yYKv0E-HUF979(*b>GE_yv4a1V9J!2zm;DCuKdFtr$v0J({Z|AQT5f zDZ58=l>kG@BCueIvPW|jLQs@nfXVm($N&K^0e+Yvh~0^xf-wOcU!ZC{5zGS3A}4`a zFt1!()5N&+Z4Z!m0C+if0widYnZZXIxOBRxWE^)<0i7?#aNI?u090NacTp(;O&T9}QK-mDqx*vKN$}3^AK;DOparKM-N6E&BTPVj zK9AlNpj#|Gy3c~j647pF7ME^rpKfQ4P8aAf-D?4tZf_PBQ28GWS&t2>tw2}lfzEgF zQGqN11|6vmGOzna>wywUkUJAV?&F5K@4Vyx3*Cob^LTXH86SAfx&t&o06G#8ddrhK!neG#&v(Q(W|6&=vwnH#8e`!~;0vm@+ajOaZ4D&=K8J!K`S|PIv)OqF)AI z%MGf_E`r({f*#$Spl#AvXE32I15cyu}{cyu~ycyu}%fEVU; z3WD4n0m_RmosKL%ot_*X%?BmGG3(iV2wYr%@^S{)NuUCV4V-WeyBeSL>Er@g`yNDh zH-c>Tfo#wBv_4SI=F`b#eBiLB_5qJhCQzfH^<)Vvl73Lzj>)6D6Xa5lPOifsld#Ey zj0MU2bRROl1kU)NEoaOwx& zc>?yt%XCK2#$?AG;PeNrBSHD#p+|Ey2SdrN?qC*Iz zBaXYMxPa(xmlgv=&aghn-wxUm2|kAjyd|>R19T=r2{*VdI^p>L;%gD#ZVv%p&=tzy z$_SPVUVz#pVc;A9x;FPx32{jt+|uc6hXng}NbIZ!CEnxU(-1(x{aOc{s$Z*v)PnB+ z^A_;vgerw<><8)S+`fZ>fdP~}b5tro7yWs_w=pyy2>{)o3o3Xa$sTlV2c*FbS~9*D zoY<@3Spjs@10*Yej!TAQ1;lNt#s@$-n%M(XhMg?s1La0$&rSw#6dwks4bX0~LtryK zT2GdOR?&lnK}qHSSQz9;oO!7AQYmJl_aGw0!}WO_2Ni}4pfvu@1CmEzxdN9ONCbd$ z4yd^eN@ZB~{aFX%16FTe__ zkog6jLHl}m4H!Tt;t6^ixO5g*bY|ChbV9bhcJ79_e={f@K--2npluf=Dh{CW84pm4 z$1(0Os38c6c2FxGbY(IN11Q=dEYP?&gatAN!UBykL0GWX8`zZ|pgPV)MFM)<$|X>5 z?@|dUO@ML+n`gI#XD2xP>wH@;l~jTROQ37Zd>}E|$)e(;eaIu(vEsOk3h0_8P!{C? z#oKWg6&`3-6#!+?<1Q*9AiA5Sg@3(cjR*hwLmtfF1!tbtH#|F;eE8iC8z1oPWCW#o z38XZy5Dh)kq1#6VO&pXIAvpnbBPHYnThP!2gyj$%8s-=RT^WaRr=*LDhpX`u*Y2O- z{tf6N2FLC%phONj-|G&j6l#52@&z=M7~s(jiInc69@@LX8C(09hxKs}ezzZ>s}Evb zx}9r0yU+O~dsl$!as?OA^cbB-&9|mnTfrKY$C8m7N9ROPQayYK)PgyPWRMTEYKByJ zo}hbI4|-T%D1Yd|e9HKMr}atC&JIur?0OGc0_JiETpw~mtuKSQ2H6oM_RTv%4UpAWVh>(mVTq0hH0B z!M-U4_i(}E22lIJTed*wC6H=k>;Dp2{I+((M|FI8SR*T6L&LJWL}CZv=FZK2x&-XG}E{Kmtj8+7i`?a%xInH)ad zCqdgDK+9SRc|fDhpanaXpj{9yDxi5a0Y?!ReoY<~&@`W?gk$qB7XGFt&_s1-j7orG zuhSn;8ROvDeZ!;s*TEM|p4vY>8V`aBYftT){|~tI%76-U29(8rbGTDLiCyC|X!Qwy z@`1M+7!}=8wD&S{4G?6cYoQKm^U|P2mTr2Q3r42pT(Y_{<-3>odQgBxuDy zcLvy~E8q=I2%}m)^G7~MGYYh%P;d%ANCVug7SL*R&_)>0MB5zh9I#>N?&`qeF3@bM z;1qt42Do7zP=|rGZ3!&lE&v;b?y??iF5~&kFF1uCqycVN56orYgUURbUoi5w9sn=p zt^rl0F)9JB*0+jt9lHvS%+(qht4>&rbXTa~MIv#!q}O3xd?c z9RLqzkLH6cAp1*Kf%Y31fX46@z}pBwTMu|ZCxdx_&afCwRq&u2O;zBXml5F1OG&Eg z%u$g54Yn$Puh#B%Wby63(Ot>m(|xJCkf%FSz@@uT#HG7Z0(4*i=;C!yO{3t`+X*Uv zd^%H9G<>vgIqm`79o&6`U!K98e?N;b=vX?(?hC%XK}`JXuRC_1;9q~vg@64~N9Kt& z|Ns9#_=~UmkR$*4W7;RW4>>Y>{&D1Af80g;zz)!j+SV8NeGWNVA93XOIg;ks>GaRB zvlFDrwbStr_#mRWpi9m*Kt-?uXpGqaJTeNJb&dz0dpB%6x;93lBIXsjRwUT4E6U+aj;Dvt7H$0d>bRYG!KE~g4 z3bIYk2Q+=I;iBRIs<#3_HCVz+QAP%a<{wP_t)L4CjZcDxjeS9*H=ynT=u|pTgB-N@ zvcjjcMx_9hP8_>~SRA{ZBwV|TI9$7{cwD=a1YEnbL^@qmR6xh+K~DL7=L<<@9@?*c zx^H*?a@++fdO-SJ`S-JE!%LoMCP)7D*BrY~IP$MQ@5;aa2wK^56jb)`uXj;V0Tn=K zh0h^JexE~Wj-Ae^h0lA&(pf&;Q@}eQyM0s?z@c&wG*#oHqTm4un1deHEh?a6zxbO# zCjk3&*QgZubmyp4fX*EQc^WhvvIe}15j1oO?yfZ+0p)DRxVY%Ujyt3$V(p-T+nukE zfGH0o4gZ)K7$6-lRuRI%~5fX;F3 zW^De!TEYpAuHOs{3@+VFpoJ;iF4hN1*dW)pxO6+QfW+8btQ}cOUw1NfJF;{-u{2w< ze&QE!hQQ4pXArL?9+Xb|JWr4 zSJ3H)ozND#fFtPOz9Pq7=l|U&JbPuDJbPIhd_X6+ds^Qpj`HYb=>qdStZxvDcBsiC=)Fi$C%df8-JVh!f8If;?T0%?CLg`2~0y_yt&+ z;8KqKf;>$w)~Ab(xPs111YH*WnO{H`)KzxuKJ2J{u=xiIe-mh=!=uwi1tR3weX{ul z3+T|M?pvV3y8D->_6<+%AN(3m|AQ9&Klbe9>2U4kX`H~XaR#(~nLiS=)CRQqz(*3a zgY*PgP|(Nn52O>o_?bUagd4Pd2_hlF&#wVe?Fd?y=EDuz(*e`M4-yLD2Cskv?Gyp6 z-wCng0H6E_I{d%aquWNs)%tX?xu-U0qr+=o?F-%@=V;$_>^{t|aTaVtBoAm)6(eZL z4`_WH4;Q>_x(jLvfacyf4hJ(ZFhEWW2A$#qS=9n+u7D3x2mp1#96Y*jfoeSikM5r? z{Gb&-pkZ@K#~s#FvF5sNXAb`G1D{;@1)K#wxj+xV{=_d(ECD_^?vo3D9C!iG3sCU$ zYn<~q?hYEwWB9}$agJZ($0z7G-zP`@m;;~q<9>ePkNnQBasCs(U^xeWxG6{%hz;u6 zLOBuVKJg3sL)SJlf8vk%4(r->vUfYPfIOQ3DrTWed_cMlKqan2r#p*B_wi5sg6Z-bb5k&>k?H0rFsK3Q7L(OOOH)seBZ0Q3(K-nV&%Crr;|cUHCN! zl#N7|f==+VF9Tkt6~OCC&^#NY>;u)Gkg^YS!Nx3W6^xunbWF4S72X@H>U57<2|ofX2K*l`3fPA3W#U?a0A=0A-Z>7IZbI0Hn*% z4Ii`zRivPWBJlGGTK|{ahAfTr=)Md(uK}(WoU}ko%OK@5XhHum@ah1E;84(M0UnKS zK&=AQ#Q+gLojxiSzTF>vKu3*xcE9lG^ieVJ>3#)X&Umtvqu24@%TJ)=E;_fUK+YA| z1MX7!TE8d_G5&8^$-&>YfQ1411n%zR9+@Y=m1FZk9uMZzB}%^C=X|@5dUjt0SAU>& z1N|%v4F8W?S926yeA�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ɕ>Npv=Yx4>M5X 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 G&j0`%QN;ED diff --git a/vendor/miniaudio/logging.odin b/vendor/miniaudio/logging.odin index 0c14a10c2..b03778079 100644 --- a/vendor/miniaudio/logging.odin +++ b/vendor/miniaudio/logging.odin @@ -12,6 +12,36 @@ when ODIN_OS == .Windows { MAX_LOG_CALLBACKS :: 4 + +/* +The callback for handling log messages. + + +Parameters +---------- +pUserData (in) + The user data pointer that was passed into ma_log_register_callback(). + +logLevel (in) + The log level. This can be one of the following: + + +----------------------+ + | Log Level | + +----------------------+ + | MA_LOG_LEVEL_DEBUG | + | MA_LOG_LEVEL_INFO | + | MA_LOG_LEVEL_WARNING | + | MA_LOG_LEVEL_ERROR | + +----------------------+ + +pMessage (in) + The log message. + + +Remarks +------- +Do not modify the state of the device from inside the callback. +*/ log_callback_proc :: proc "c" (pUserData: rawptr, level: u32, pMessage: cstring) log_callback :: struct { diff --git a/vendor/miniaudio/node_graph.odin b/vendor/miniaudio/node_graph.odin new file mode 100644 index 000000000..ac47d43d8 --- /dev/null +++ b/vendor/miniaudio/node_graph.odin @@ -0,0 +1,469 @@ +package miniaudio + +import "core:c" + +when ODIN_OS == .Windows { + foreign import lib "lib/miniaudio.lib" +} else when ODIN_OS == .Linux { + foreign import lib "lib/miniaudio.a" +} else { + foreign import lib "system:miniaudio" +} + +/************************************************************************************************************************************************************ + +Node Graph + +************************************************************************************************************************************************************/ + +/* Must never exceed 254. */ +MAX_NODE_BUS_COUNT :: 254 + +/* Used internally by miniaudio for memory management. Must never exceed MA_MAX_NODE_BUS_COUNT. */ +MAX_NODE_LOCAL_BUS_COUNT :: 2 + +/* Use this when the bus count is determined by the node instance rather than the vtable. */ +NODE_BUS_COUNT_UNKNOWN :: 255 + +node :: struct {} + +/* Node flags. */ +node_flags :: enum c.int { + PASSTHROUGH = 0x00000001, + CONTINUOUS_PROCESSING = 0x00000002, + ALLOW_NULL_INPUT = 0x00000004, + DIFFERENT_PROCESSING_RATES = 0x00000008, + SILENT_OUTPUT = 0x00000010, +} + +/* The playback state of a node. Either started or stopped. */ +node_state :: enum c.int { + started = 0, + stopped = 1, +} + +node_vtable :: struct { + /* + Extended processing callback. This callback is used for effects that process input and output + at different rates (i.e. they perform resampling). This is similar to the simple version, only + they take two seperate frame counts: one for input, and one for output. + + On input, `pFrameCountOut` is equal to the capacity of the output buffer for each bus, whereas + `pFrameCountIn` will be equal to the number of PCM frames in each of the buffers in `ppFramesIn`. + + On output, set `pFrameCountOut` to the number of PCM frames that were actually output and set + `pFrameCountIn` to the number of input frames that were consumed. + */ + onProcess: proc "c" (pNode: ^node, ppFramesIn: ^[^]f32, pFrameCountIn: ^u32, ppFramesOut: ^[^]f32, pFrameCountOut: ^u32), + + /* + A callback for retrieving the number of a input frames that are required to output the + specified number of output frames. You would only want to implement this when the node performs + resampling. This is optional, even for nodes that perform resampling, but it does offer a + small reduction in latency as it allows miniaudio to calculate the exact number of input frames + to read at a time instead of having to estimate. + */ + onGetRequiredInputFrameCount: proc "c" (pNode: ^node, outputFrameCount: u32, pInputFrameCount: ^u32) -> result, + + /* + The number of input buses. This is how many sub-buffers will be contained in the `ppFramesIn` + parameters of the callbacks above. + */ + inputBusCount: u8, + + /* + The number of output buses. This is how many sub-buffers will be contained in the `ppFramesOut` + parameters of the callbacks above. + */ + outputBusCount: u8, + + /* + Flags describing characteristics of the node. This is currently just a placeholder for some + ideas for later on. + */ + flags: u32, +} + +node_config :: struct { + vtable: ^node_vtable, /* Should never be null. Initialization of the node will fail if so. */ + initialState: node_state, /* Defaults to ma_node_state_started. */ + inputBusCount: u32, /* Only used if the vtable specifies an input bus count of `MA_NODE_BUS_COUNT_UNKNOWN`, otherwise must be set to `MA_NODE_BUS_COUNT_UNKNOWN` (default). */ + outputBusCount: u32, /* Only used if the vtable specifies an output bus count of `MA_NODE_BUS_COUNT_UNKNOWN`, otherwise be set to `MA_NODE_BUS_COUNT_UNKNOWN` (default). */ + pInputChannels: ^u32, /* The number of elements are determined by the input bus count as determined by the vtable, or `inputBusCount` if the vtable specifies `MA_NODE_BUS_COUNT_UNKNOWN`. */ + pOutputChannels: ^u32, /* The number of elements are determined by the output bus count as determined by the vtable, or `outputBusCount` if the vtable specifies `MA_NODE_BUS_COUNT_UNKNOWN`. */ +} + +/* +A node has multiple output buses. An output bus is attached to an input bus as an item in a linked +list. Think of the input bus as a linked list, with the output bus being an item in that list. +*/ +node_output_bus :: struct { + /* Immutable. */ + pNode: ^node, /* The node that owns this output bus. The input node. Will be null for dummy head and tail nodes. */ + outputBusIndex: u8, /* The index of the output bus on pNode that this output bus represents. */ + channels: u8, /* The number of channels in the audio stream for this bus. */ + + /* Mutable via multiple threads. Must be used atomically. The weird ordering here is for packing reasons. */ + inputNodeInputBusIndex: u8, /*atomic*/ /* The index of the input bus on the input. Required for detaching. */ + flags: u32, /*atomic*/ /* Some state flags for tracking the read state of the output buffer. A combination of MA_NODE_OUTPUT_BUS_FLAG_*. */ + refCount: u32, /*atomic*/ /* Reference count for some thread-safety when detaching. */ + isAttached: b32, /*atomic*/ /* This is used to prevent iteration of nodes that are in the middle of being detached. Used for thread safety. */ + lock: spinlock, /*atomic*/ /* Unfortunate lock, but significantly simplifies the implementation. Required for thread-safe attaching and detaching. */ + volume: f32, /*atomic*/ /* Linear. */ + pNext: ^node_output_bus, /*atomic*/ /* If null, it's the tail node or detached. */ + pPrev: ^node_output_bus, /*atomic*/ /* If null, it's the head node or detached. */ + pInputNode: ^node, /*atomic*/ /* The node that this output bus is attached to. Required for detaching. */ +} + +/* +A node has multiple input buses. The output buses of a node are connecting to the input busses of +another. An input bus is essentially just a linked list of output buses. +*/ +node_input_bus :: struct { + /* Mutable via multiple threads. */ + head: node_output_bus, /* Dummy head node for simplifying some lock-free thread-safety stuff. */ + nextCounter: u32, /*atomic*/ /* This is used to determine whether or not the input bus is finding the next node in the list. Used for thread safety when detaching output buses. */ + lock: spinlock, /*atomic*/ /* Unfortunate lock, but significantly simplifies the implementation. Required for thread-safe attaching and detaching. */ + + /* Set once at startup. */ + channels: u8, /* The number of channels in the audio stream for this bus. */ +} + + +node_base :: struct { + /* These variables are set once at startup. */ + pNodeGraph: ^node_graph, /* The graph this node belongs to. */ + vtable: ^node_vtable, + pCachedData: [^]f32, /* Allocated on the heap. Fixed size. Needs to be stored on the heap because reading from output buses is done in separate function calls. */ + cachedDataCapInFramesPerBus: u16, /* The capacity of the input data cache in frames, per bus. */ + + /* These variables are read and written only from the audio thread. */ + cachedFrameCountOut: u16, + cachedFrameCountIn: u16, + consumedFrameCountIn: u16, + + /* These variables are read and written between different threads. */ + state: node_state, /*atomic*/ /* When set to stopped, nothing will be read, regardless of the times in stateTimes. */ + stateTimes: [2]u64, /*atomic*/ /* Indexed by ma_node_state. Specifies the time based on the global clock that a node should be considered to be in the relevant state. */ + localTime: u64, /*atomic*/ /* The node's local clock. This is just a running sum of the number of output frames that have been processed. Can be modified by any thread with `ma_node_set_time()`. */ + inputBusCount: u32, + outputBusCount: u32, + pInputBuses: [^]node_input_bus, + pOutputBuses: [^]node_output_bus, + + /* Memory management. */ + _inputBuses: [MAX_NODE_LOCAL_BUS_COUNT]node_input_bus, + _outputBuses: [MAX_NODE_LOCAL_BUS_COUNT]node_output_bus, + _pHeap: rawptr, /* A heap allocation for internal use only. pInputBuses and/or pOutputBuses will point to this if the bus count exceeds MA_MAX_NODE_LOCAL_BUS_COUNT. */ + _ownsHeap: b32, /* If set to true, the node owns the heap allocation and _pHeap will be freed in ma_node_uninit(). */ +}; + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + node_config_init :: proc() -> node_config --- + + node_get_heap_size :: proc(pNodeGraph: ^node_graph, pConfig: ^node_config, pHeapSizeInBytes: ^c.size_t) -> result --- + node_init_preallocated :: proc(pNodeGraph: ^node_graph, pConfig: ^node_config, pHeap: rawptr, pNode: ^node) -> result --- + node_init :: proc(pNodeGraph: ^node_graph, pConfig: ^node_config, pAllocationCallbacks: ^allocation_callbacks, pNode: ^node) -> result --- + node_uninit :: proc(pNode: ^node, pAllocationCallbacks: ^allocation_callbacks) --- + node_get_node_graph :: proc(pNode: ^node) -> ^node_graph --- + node_get_input_bus_count :: proc(pNode: ^node) -> u32 --- + node_get_output_bus_count :: proc(pNode: ^node) -> u32 --- + node_get_input_channels :: proc(pNode: ^node, inputBusIndex: u32) -> u32 --- + node_get_output_channels :: proc(pNode: ^node, outputBusIndex: u32) -> u32 --- + node_attach_output_bus :: proc(pNode: ^node, outputBusIndex: u32, pOtherNode: ^node, otherNodeInputBusIndex: u32) -> result --- + node_detach_output_bus :: proc(pNode: ^node, outputBusIndex: u32) -> result --- + node_detach_all_output_buses :: proc(pNode: ^node) -> result --- + node_set_output_bus_volume :: proc(pNode: ^node, outputBusIndex: u32, volume: f32) -> result --- + node_get_output_bus_volume :: proc(pNode: ^node, outputBusIndex: u32) -> f32 --- + node_set_state :: proc(pNode: ^node, state: node_state) -> result --- + node_get_state :: proc(pNode: ^node) -> node_state --- + node_set_state_time :: proc(pNode: ^node, state: node_state, globalTime: u64) -> result --- + node_get_state_time :: proc(pNode: ^node, state: node_state) -> u64 --- + node_get_state_by_time :: proc(pNode: ^node, globalTime: u64) -> node_state --- + node_get_state_by_time_range :: proc(pNode: ^node, globalTimeBeg: u64, globalTimeEnd: u64) -> node_state --- + node_get_time :: proc(pNode: ^node) -> u64 --- + node_set_time :: proc(pNode: ^node, localTime: u64) -> result --- +} + +node_graph_config :: struct { + channels: u32, + nodeCacheCapInFrames: u16, +} + +node_graph :: struct { + /* Immutable. */ + base: node_base, /* The node graph itself is a node so it can be connected as an input to different node graph. This has zero inputs and calls ma_node_graph_read_pcm_frames() to generate it's output. */ + endpoint: node_base, /* Special node that all nodes eventually connect to. Data is read from this node in ma_node_graph_read_pcm_frames(). */ + nodeCacheCapInFrames: u16, + + /* Read and written by multiple threads. */ + isReading: b32, /*atomic*/ +}; + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + node_graph_config_init :: proc(channels: u32) -> node_graph_config --- + + node_graph_init :: proc(pConfig: ^node_graph_config, pAllocationCallbacks: ^allocation_callbacks, pNodeGraph: ^node_graph) -> result --- + node_graph_uninit :: proc(pNodeGraph: ^node_graph, pAllocationCallbacks: ^allocation_callbacks) --- + node_graph_get_endpoint :: proc(pNodeGraph: ^node_graph) -> ^node --- + node_graph_read_pcm_frames :: proc(pNodeGraph: ^node_graph, pFramesOut: rawptr, frameCount: u64, pFramesRead: ^u64) -> result --- + node_graph_get_channels :: proc(pNodeGraph: ^node_graph) -> u32 --- + node_graph_get_time :: proc(pNodeGraph: ^node_graph) -> u64 --- + node_graph_set_time :: proc(pNodeGraph: ^node_graph, globalTime: u64) -> result --- +} + + + +/* Data source node. 0 input buses, 1 output bus. Used for reading from a data source. */ +data_source_node_config :: struct { + nodeConfig: node_config, + pDataSource: ^data_source, +} + +data_source_node :: struct { + base: node_base, + pDataSource: ^data_source, +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + data_source_node_config_init :: proc(pDataSource: ^data_source) -> data_source_node_config --- + + data_source_node_init :: proc(pNodeGraph: ^node_graph, pConfig: ^data_source_node_config, pAllocationCallbacks: ^allocation_callbacks, pDataSourceNode: ^data_source_node) -> result --- + data_source_node_uninit :: proc(pDataSourceNode: ^data_source_node, pAllocationCallbacks: ^allocation_callbacks) --- + data_source_node_set_looping :: proc(pDataSourceNode: ^data_source_node, isLooping: b32) -> result --- + data_source_node_is_looping :: proc(pDataSourceNode: ^data_source_node) -> b32 --- +} + + +/* Splitter Node. 1 input, 2 outputs. Used for splitting/copying a stream so it can be as input into two separate output nodes. */ +splitter_node_config :: struct { + nodeConfig: node_config, + channels: u32, +} + +splitter_node :: struct { + base: node_base, +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + splitter_node_config_init :: proc(channels: u32) -> splitter_node_config --- + + splitter_node_init :: proc(pNodeGraph: ^node_graph, pConfig: ^splitter_node_config, pAllocationCallbacks: ^allocation_callbacks, pSplitterNode: ^splitter_node) -> result --- + splitter_node_uninit :: proc(pSplitterNode: ^splitter_node, pAllocationCallbacks: ^allocation_callbacks) --- +} + + +/* +Biquad Node +*/ +biquad_node_config :: struct { + nodeConfig: node_config, + biquad: biquad_config, +} + +biquad_node :: struct { + baseNode: node_base, + biquad: biquad, +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + biquad_node_config_init :: proc(channels: u32, b0, b1, b2, a0, a1, a2: f32) -> biquad_node_config --- + + biquad_node_init :: proc(pNodeGraph: ^node_graph, pConfig: ^biquad_node_config, pAllocationCallbacks: ^allocation_callbacks, pNode: ^biquad_node) -> result --- + biquad_node_reinit :: proc(pConfig: ^biquad_config, pNode: ^biquad_node) -> result --- + biquad_node_uninit :: proc(pNode: ^biquad_node, pAllocationCallbacks: ^allocation_callbacks) --- +} + + +/* +Low Pass Filter Node +*/ +lpf_node_config :: struct { + nodeConfig: node_config, + lpf: lpf_config, +} + +lpf_node :: struct { + baseNode: node_base, + lpf: lpf, +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + lpf_node_config_init :: proc(channels, sampleRate: u32, cutoffFrequency: f64, order: u32) -> lpf_node_config --- + + lpf_node_init :: proc(pNodeGraph: ^node_graph, pConfig: ^lpf_node_config, pAllocationCallbacks: ^allocation_callbacks, pNode: ^lpf_node) -> result --- + lpf_node_reinit :: proc(pConfig: ^lpf_config, pNode: ^lpf_node) -> result --- + lpf_node_uninit :: proc(pNode: ^lpf_node, pAllocationCallbacks: ^allocation_callbacks) --- +} + + +/* +High Pass Filter Node +*/ +hpf_node_config :: struct { + nodeConfig: node_config, + hpf: hpf_config, +} + +hpf_node :: struct { + baseNode: node_base, + hpf: hpf, +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + hpf_node_config_init :: proc(channels, sampleRate: u32, cutoffFrequency: f64, order: u32) -> hpf_node_config --- + + hpf_node_init :: proc(pNodeGraph: ^node_graph, pConfig: ^hpf_node_config, pAllocationCallbacks: ^allocation_callbacks, pNode: ^hpf_node) -> result --- + hpf_node_reinit :: proc(pConfig: ^hpf_config, pNode: ^hpf_node) -> result --- + hpf_node_uninit :: proc(pNode: ^hpf_node, pAllocationCallbacks: ^allocation_callbacks) --- +} + + +/* +Band Pass Filter Node +*/ +bpf_node_config :: struct { + nodeConfig: node_config, + bpf: bpf_config, +} + +bpf_node :: struct { + baseNode: node_base, + bpf: bpf, +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + bpf_node_config_init :: proc(channels, sampleRate: u32, cutoffFrequency: f64, order: u32) -> bpf_node_config --- + + bpf_node_init :: proc(pNodeGraph: ^node_graph, pConfig: ^bpf_node_config, pAllocationCallbacks: ^allocation_callbacks, pNode: ^bpf_node) -> result --- + bpf_node_reinit :: proc(pConfig: ^bpf_config, pNode: ^bpf_node) -> result --- + bpf_node_uninit :: proc(pNode: ^bpf_node, pAllocationCallbacks: ^allocation_callbacks) --- +} + + +/* +Notching Filter Node +*/ +notch_node_config :: struct { + nodeConfig: node_config, + notch: notch_config, +} + +notch_node :: struct { + baseNode: node_base, + notch: notch2, +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + notch_node_config_init :: proc(channels, sampleRate: u32, q, frequency: f64) -> notch_node_config --- + + notch_node_init :: proc(pNodeGraph: ^node_graph, pConfig: ^notch_node_config, pAllocationCallbacks: ^allocation_callbacks, pNode: ^notch_node) -> result --- + notch_node_reinit :: proc(pConfig: ^notch_config, pNode: ^notch_node) -> result --- + notch_node_uninit :: proc(pNode: ^notch_node, pAllocationCallbacks: ^allocation_callbacks) --- +} + + +/* +Peaking Filter Node +*/ +peak_node_config :: struct { + nodeConfig: node_config, + peak: peak_config, +} + +peak_node :: struct { + baseNode: node_base, + peak: peak2, +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + peak_node_config_init :: proc(channels, sampleRate: u32, gainDB, q, frequency: f64) -> peak_node_config --- + + peak_node_init :: proc(pNodeGraph: ^node_graph, pConfig: ^peak_node_config, pAllocationCallbacks: ^allocation_callbacks, pNode: ^peak_node) -> result --- + peak_node_reinit :: proc(pConfig: ^peak_config, pNode: ^peak_node) -> result --- + peak_node_uninit :: proc(pNode: ^peak_node, pAllocationCallbacks: ^allocation_callbacks) --- +} + + +/* +Low Shelf Filter Node +*/ +loshelf_node_config :: struct { + nodeConfig: node_config, + loshelf: loshelf_config, +} + +loshelf_node :: struct { + baseNode: node_base, + loshelf: loshelf2, +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + loshelf_node_config_init :: proc(channels, sampleRate: u32, gainDB, q, frequency: f64) -> loshelf_node_config --- + + loshelf_node_init :: proc(pNodeGraph: ^node_graph, pConfig: ^loshelf_node_config, pAllocationCallbacks: ^allocation_callbacks, pNode: ^loshelf_node) -> result --- + loshelf_node_reinit :: proc(pConfig: ^loshelf_config, pNode: ^loshelf_node) -> result --- + loshelf_node_uninit :: proc(pNode: ^loshelf_node, pAllocationCallbacks: ^allocation_callbacks) --- +} + + +/* +High Shelf Filter Node +*/ +hishelf_node_config :: struct { + nodeConfig: node_config, + hishelf: hishelf_config, +} + +hishelf_node :: struct { + baseNode: node_base, + hishelf: hishelf2, +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + hishelf_node_config_init :: proc(channels, sampleRate: u32, gainDB, q, frequency: f64) -> hishelf_node_config --- + + hishelf_node_init :: proc(pNodeGraph: ^node_graph, pConfig: ^hishelf_node_config, pAllocationCallbacks: ^allocation_callbacks, pNode: ^hishelf_node) -> result --- + hishelf_node_reinit :: proc(pConfig: ^hishelf_config, pNode: ^hishelf_node) -> result --- + hishelf_node_uninit :: proc(pNode: ^hishelf_node, pAllocationCallbacks: ^allocation_callbacks) --- +} + + +/* +Delay Filter Node +*/ +delay_node_config :: struct { + nodeConfig: node_config, + delay: delay_config, +} + +delay_node :: struct { + baseNode: node_base, + delay: delay, +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + delay_node_config_init :: proc(channels, sampleRate, delayInFrames: u32, decay: f32) -> delay_node_config --- + + delay_node_init :: proc(pNodeGraph: ^node_graph, pConfig: ^delay_node_config, pAllocationCallbacks: ^allocation_callbacks, pDelayNode: ^delay_node) -> result --- + delay_node_uninit :: proc(pDelayNode: ^delay_node, pAllocationCallbacks: ^allocation_callbacks) --- + delay_node_set_wet :: proc(pDelayNode: ^delay_node, value: f32) --- + delay_node_get_wet :: proc(pDelayNode: ^delay_node) -> f32 --- + delay_node_set_dry :: proc(pDelayNode: ^delay_node, value: f32) --- + delay_node_get_dry :: proc(pDelayNode: ^delay_node) -> f32 --- + delay_node_set_decay :: proc(pDelayNode: ^delay_node, value: f32) --- + delay_node_get_decay :: proc(pDelayNode: ^delay_node) -> f32 --- +} diff --git a/vendor/miniaudio/resource_manager.odin b/vendor/miniaudio/resource_manager.odin new file mode 100644 index 000000000..c4d722342 --- /dev/null +++ b/vendor/miniaudio/resource_manager.odin @@ -0,0 +1,288 @@ +package miniaudio + +import "core:c" + +when ODIN_OS == .Windows { + foreign import lib "lib/miniaudio.lib" +} else when ODIN_OS == .Linux { + foreign import lib "lib/miniaudio.a" +} else { + foreign import lib "system:miniaudio" +} + +/************************************************************************************************************************************************************ + +Resource Manager + +************************************************************************************************************************************************************/ + +resource_manager_data_source_flags :: enum c.int { + STREAM = 0x00000001, /* When set, does not load the entire data source in memory. Disk I/O will happen on job threads. */ + DECODE = 0x00000002, /* Decode data before storing in memory. When set, decoding is done at the resource manager level rather than the mixing thread. Results in faster mixing, but higher memory usage. */ + ASYNC = 0x00000004, /* When set, the resource manager will load the data source asynchronously. */ + WAIT_INIT = 0x00000008, /* When set, waits for initialization of the underlying data source before returning from ma_resource_manager_data_source_init(). */ + UNKNOWN_LENGTH = 0x00000010, /* Gives the resource manager a hint that the length of the data source is unknown and calling `ma_data_source_get_length_in_pcm_frames()` should be avoided. */ +} + +/* +Pipeline notifications used by the resource manager. Made up of both an async notification and a fence, both of which are optional. +*/ +resource_manager_pipeline_stage_notification :: struct { + pNotification: ^async_notification, + pFence: ^fence, +} + +resource_manager_pipeline_notifications :: struct { + init: resource_manager_pipeline_stage_notification, /* Initialization of the decoder. */ + done: resource_manager_pipeline_stage_notification, /* Decoding fully completed. */ +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + resource_manager_pipeline_notifications_init :: proc() -> resource_manager_pipeline_notifications --- +} + + +/* BEGIN BACKWARDS COMPATIBILITY */ +/* TODO: Remove this block in version 0.12. */ +resource_manager_job :: job +resource_manager_job_init :: job_init +JOB_TYPE_RESOURCE_MANAGER_QUEUE_FLAG_NON_BLOCKING :: job_queue_flags.NON_BLOCKING +resource_manager_job_queue_config :: job_queue_config +resource_manager_job_queue_config_init :: job_queue_config_init +resource_manager_job_queue :: job_queue +resource_manager_job_queue_get_heap_size :: job_queue_get_heap_size +resource_manager_job_queue_init_preallocated :: job_queue_init_preallocated +resource_manager_job_queue_init :: job_queue_init +resource_manager_job_queue_uninit :: job_queue_uninit +resource_manager_job_queue_post :: job_queue_post +resource_manager_job_queue_next :: job_queue_next +/* END BACKWARDS COMPATIBILITY */ + + + +/* Maximum job thread count will be restricted to this, but this may be removed later and replaced with a heap allocation thereby removing any limitation. */ +RESOURCE_MANAGER_MAX_JOB_THREAD_COUNT :: 64 + +resource_manager_flags :: enum c.int { + /* Indicates ma_resource_manager_next_job() should not block. Only valid when the job thread count is 0. */ + NON_BLOCKING = 0x00000001, + + /* Disables any kind of multithreading. Implicitly enables MA_RESOURCE_MANAGER_FLAG_NON_BLOCKING. */ + NO_THREADING = 0x00000002, +} + +resource_manager_data_source_config :: struct { + pFilePath: cstring, + pFilePathW: [^]c.wchar_t, + pNotifications: ^resource_manager_pipeline_notifications, + initialSeekPointInPCMFrames: u64, + rangeBegInPCMFrames: u64, + rangeEndInPCMFrames: u64, + loopPointBegInPCMFrames: u64, + loopPointEndInPCMFrames: u64, + isLooping: b32, + flags: u32, +} + +resource_manager_data_supply_type :: enum c.int { + unknown = 0, /* Used for determining whether or the data supply has been initialized. */ + encoded, /* Data supply is an encoded buffer. Connector is ma_decoder. */ + decoded, /* Data supply is a decoded buffer. Connector is ma_audio_buffer. */ + decoded_paged, /* Data supply is a linked list of decoded buffers. Connector is ma_paged_audio_buffer. */ +} + +resource_manager_data_supply :: struct { + type: resource_manager_data_supply_type, /*atomic*/ /* Read and written from different threads so needs to be accessed atomically. */ + backend: struct #raw_union { + encoded: struct { + pData: rawptr, + sizeInBytes: c.size_t, + }, + decoded: struct { + pData: rawptr, + totalFrameCount: u64, + decodedFrameCount: u64, + format: format, + channels: u32, + sampleRate: u32, + }, + decodedPaged: struct { + data: paged_audio_buffer_data, + decodedFrameCount: u64, + sampleRate: u32, + }, + }, +} + +resource_manager_data_buffer_node :: struct { + hashedName32: u32, /* The hashed name. This is the key. */ + refCount: u32, + result: result, /*atomic*/ /* Result from asynchronous loading. When loading set to MA_BUSY. When fully loaded set to MA_SUCCESS. When deleting set to MA_UNAVAILABLE. */ + executionCounter: u32, /*atomic*/ /* For allocating execution orders for jobs. */ + executionPointer: u32, /*atomic*/ /* For managing the order of execution for asynchronous jobs relating to this object. Incremented as jobs complete processing. */ + isDataOwnedByResourceManager: b32, /* Set to true when the underlying data buffer was allocated the resource manager. Set to false if it is owned by the application (via ma_resource_manager_register_*()). */ + data: resource_manager_data_supply, + pParent: ^resource_manager_data_buffer_node, + pChildLo: ^resource_manager_data_buffer_node, + pChildHi: ^resource_manager_data_buffer_node, +} + +resource_manager_data_buffer :: struct { + ds: data_source_base, /* Base data source. A data buffer is a data source. */ + pResourceManager: ^resource_manager, /* A pointer to the resource manager that owns this buffer. */ + pNode: ^resource_manager_data_buffer_node, /* The data node. This is reference counted and is what supplies the data. */ + flags: u32, /* The flags that were passed used to initialize the buffer. */ + executionCounter: u32, /*atomic*/ /* For allocating execution orders for jobs. */ + executionPointer: u32, /*atomic*/ /* For managing the order of execution for asynchronous jobs relating to this object. Incremented as jobs complete processing. */ + seekTargetInPCMFrames: u64, /* Only updated by the public API. Never written nor read from the job thread. */ + seekToCursorOnNextRead: b32, /* On the next read we need to seek to the frame cursor. */ + result: result, /*atomic*/ /* Keeps track of a result of decoding. Set to MA_BUSY while the buffer is still loading. Set to MA_SUCCESS when loading is finished successfully. Otherwise set to some other code. */ + isLooping: b32, /*atomic*/ /* Can be read and written by different threads at the same time. Must be used atomically. */ + isConnectorInitialized: b32, /* Used for asynchronous loading to ensure we don't try to initialize the connector multiple times while waiting for the node to fully load. */ + connector: struct #raw_union { + decoder: decoder, /* Supply type is ma_resource_manager_data_supply_type_encoded */ + buffer: audio_buffer, /* Supply type is ma_resource_manager_data_supply_type_decoded */ + pagedBuffer: paged_audio_buffer, /* Supply type is ma_resource_manager_data_supply_type_decoded_paged */ + }, /* Connects this object to the node's data supply. */ +} + +resource_manager_data_stream :: struct { + ds: data_source_base, /* Base data source. A data stream is a data source. */ + pResourceManager: ^resource_manager, /* A pointer to the resource manager that owns this data stream. */ + flags: u32, /* The flags that were passed used to initialize the stream. */ + decoder: decoder, /* Used for filling pages with data. This is only ever accessed by the job thread. The public API should never touch this. */ + isDecoderInitialized: b32, /* Required for determining whether or not the decoder should be uninitialized in MA_JOB_TYPE_RESOURCE_MANAGER_FREE_DATA_STREAM. */ + totalLengthInPCMFrames: u64, /* This is calculated when first loaded by the MA_JOB_TYPE_RESOURCE_MANAGER_LOAD_DATA_STREAM. */ + relativeCursor: u32, /* The playback cursor, relative to the current page. Only ever accessed by the public API. Never accessed by the job thread. */ + absoluteCursor: u64, /*atomic*/ /* The playback cursor, in absolute position starting from the start of the file. */ + currentPageIndex: u32, /* Toggles between 0 and 1. Index 0 is the first half of pPageData. Index 1 is the second half. Only ever accessed by the public API. Never accessed by the job thread. */ + executionCounter: u32, /*atomic*/ /* For allocating execution orders for jobs. */ + executionPointer: u32, /*atomic*/ /* For managing the order of execution for asynchronous jobs relating to this object. Incremented as jobs complete processing. */ + + /* Written by the public API, read by the job thread. */ + isLooping: b32, /*atomic*/ /* Whether or not the stream is looping. It's important to set the looping flag at the data stream level for smooth loop transitions. */ + + /* Written by the job thread, read by the public API. */ + pPageData: rawptr, /* Buffer containing the decoded data of each page. Allocated once at initialization time. */ + pageFrameCount: [2]u32, /*atomic*/ /* The number of valid PCM frames in each page. Used to determine the last valid frame. */ + + /* Written and read by both the public API and the job thread. These must be atomic. */ + result: result, /*atomic*/ /* Result from asynchronous loading. When loading set to MA_BUSY. When initialized set to MA_SUCCESS. When deleting set to MA_UNAVAILABLE. If an error occurs when loading, set to an error code. */ + isDecoderAtEnd: b32, /*atomic*/ /* Whether or not the decoder has reached the end. */ + isPageValid: [2]b32, /*atomic*/ /* Booleans to indicate whether or not a page is valid. Set to false by the public API, set to true by the job thread. Set to false as the pages are consumed, true when they are filled. */ + seekCounter: b32, /*atomic*/ /* When 0, no seeking is being performed. When > 0, a seek is being performed and reading should be delayed with MA_BUSY. */ +} + +resource_manager_data_source :: struct { + backend: struct #raw_union { + buffer: resource_manager_data_buffer, + stream: resource_manager_data_stream, + }, /* Must be the first item because we need the first item to be the data source callbacks for the buffer or stream. */ + + flags: u32, /* The flags that were passed in to ma_resource_manager_data_source_init(). */ + executionCounter: u32, /*atomic*/ /* For allocating execution orders for jobs. */ + executionPointer: u32, /*atomic*/ /* For managing the order of execution for asynchronous jobs relating to this object. Incremented as jobs complete processing. */ +} + +resource_manager_config :: struct { + allocationCallbacks: allocation_callbacks, + pLog: ^log, + decodedFormat: format, /* The decoded format to use. Set to ma_format_unknown (default) to use the file's native format. */ + decodedChannels: u32, /* The decoded channel count to use. Set to 0 (default) to use the file's native channel count. */ + decodedSampleRate: u32, /* the decoded sample rate to use. Set to 0 (default) to use the file's native sample rate. */ + jobThreadCount: u32, /* Set to 0 if you want to self-manage your job threads. Defaults to 1. */ + jobQueueCapacity: u32, /* The maximum number of jobs that can fit in the queue at a time. Defaults to MA_JOB_TYPE_RESOURCE_MANAGER_QUEUE_CAPACITY. Cannot be zero. */ + flags: u32, + pVFS: ^vfs, /* Can be NULL in which case defaults will be used. */ + ppCustomDecodingBackendVTables: ^[^]decoding_backend_vtable, + customDecodingBackendCount: u32, + pCustomDecodingBackendUserData: rawptr, +} + +resource_manager :: struct { + config: resource_manager_config, + pRootDataBufferNode: ^resource_manager_data_buffer_node, /* The root buffer in the binary tree. */ + dataBufferBSTLock: (struct {} when NO_THREADING else mutex), /* For synchronizing access to the data buffer binary tree. */ + jobThreads: (struct {} when NO_THREADING else [RESOURCE_MANAGER_MAX_JOB_THREAD_COUNT]thread), /* The threads for executing jobs. */ + jobQueue: job_queue, /* Multi-consumer, multi-producer job queue for managing jobs for asynchronous decoding and streaming. */ + defaultVFS: default_vfs, /* Only used if a custom VFS is not specified. */ + log: log, /* Only used if no log was specified in the config. */ +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + resource_manager_data_source_config_init :: proc() -> resource_manager_data_source_config --- + resource_manager_config_init :: proc() -> resource_manager_config --- + + /* Init. */ + resource_manager_init :: proc(pConfig: ^resource_manager_config, pResourceManager: ^resource_manager) -> result --- + resource_manager_uninit :: proc(pResourceManager: ^resource_manager) --- + resource_manager_get_log :: proc(pResourceManager: ^resource_manager) -> ^log ---; + + /* Registration. */ + resource_manager_register_file :: proc(pResourceManager: ^resource_manager, pFilePath: cstring, flags: u32) -> result --- + resource_manager_register_file_w :: proc(pResourceManager: ^resource_manager, pFilePath: [^]c.wchar_t, flags: u32) -> result --- + resource_manager_register_decoded_data :: proc(pResourceManager: ^resource_manager, pName: cstring, pData: rawptr, frameCount: u64, format: format, channels: u32, sampleRate: u32) -> result --- /* Does not copy. Increments the reference count if already exists and returns MA_SUCCESS. */ + resource_manager_register_decoded_data_w :: proc(pResourceManager: ^resource_manager, pName: [^]c.wchar_t, pData: rawptr, frameCount: u64, format: format, channels: u32, sampleRate: u32) -> result --- + resource_manager_register_encoded_data :: proc(pResourceManager: ^resource_manager, pName: cstring, pData: rawptr, sizeInBytes: c.size_t) -> result --- /* Does not copy. Increments the reference count if already exists and returns MA_SUCCESS. */ + resource_manager_register_encoded_data_w :: proc(pResourceManager: ^resource_manager, pName: [^]c.wchar_t, pData: rawptr, sizeInBytes: c.size_t) -> result --- + resource_manager_unregister_file :: proc(pResourceManager: ^resource_manager, pFilePath: cstring) -> result --- + resource_manager_unregister_file_w :: proc(pResourceManager: ^resource_manager, pFilePath: [^]c.wchar_t) -> result --- + resource_manager_unregister_data :: proc(pResourceManager: ^resource_manager, pName: cstring) -> result --- + resource_manager_unregister_data_w :: proc(pResourceManager: ^resource_manager, pName: [^]c.wchar_t) -> result --- + + /* Data Buffers. */ + resource_manager_data_buffer_init_ex :: proc(pResourceManager: ^resource_manager, pConfig: ^resource_manager_data_source_config, pDataBuffer: ^resource_manager_data_buffer) -> result --- + resource_manager_data_buffer_init :: proc(pResourceManager: ^resource_manager, pFilePath: cstring, flags: u32, pNotifications: ^resource_manager_pipeline_notifications, pDataBuffer: ^resource_manager_data_buffer) -> result --- + resource_manager_data_buffer_init_w :: proc(pResourceManager: ^resource_manager, pFilePath: [^]c.wchar_t, flags: u32, pNotifications: ^resource_manager_pipeline_notifications, pDataBuffer: ^resource_manager_data_buffer) -> result --- + resource_manager_data_buffer_init_copy :: proc(pResourceManager: ^resource_manager, pExistingDataBuffer, pDataBuffer: ^resource_manager_data_buffer) -> result --- + resource_manager_data_buffer_uninit :: proc(pDataBuffer: ^resource_manager_data_buffer) -> result --- + resource_manager_data_buffer_read_pcm_frames :: proc(pDataBuffer: ^resource_manager_data_buffer, pFramesOut: rawptr, frameCount: u64, pFramesRead: ^u64) -> result --- + resource_manager_data_buffer_seek_to_pcm_frame :: proc(pDataBuffer: ^resource_manager_data_buffer, frameIndex: u64) -> result --- + resource_manager_data_buffer_get_data_format :: proc(pDataBuffer: ^resource_manager_data_buffer, pFormat: ^format, pChannels: ^u32, pSampleRate: ^u32, pChannelMap: [^]channel, channelMapCap: c.size_t) -> result --- + resource_manager_data_buffer_get_cursor_in_pcm_frames :: proc(pDataBuffer: ^resource_manager_data_buffer, pCursor: ^u64) -> result --- + resource_manager_data_buffer_get_length_in_pcm_frames :: proc(pDataBuffer: ^resource_manager_data_buffer, pLength: ^u64) -> result --- + resource_manager_data_buffer_result :: proc(pDataBuffer: ^resource_manager_data_buffer) -> result --- + resource_manager_data_buffer_set_looping :: proc(pDataBuffer: ^resource_manager_data_buffer, isLooping: b32) -> result --- + resource_manager_data_buffer_is_looping :: proc(pDataBuffer: ^resource_manager_data_buffer) -> b32 --- + resource_manager_data_buffer_get_available_frames :: proc(pDataBuffer: ^resource_manager_data_buffer, pAvailableFrames: ^u64) -> result --- + + /* Data Streams. */ + resource_manager_data_stream_init_ex :: proc(pResourceManager: ^resource_manager, pConfig: ^resource_manager_data_source_config, pDataStream: ^resource_manager_data_stream) -> result --- + resource_manager_data_stream_init :: proc(pResourceManager: ^resource_manager, pFilePath: cstring, flags: u32, pNotifications: ^resource_manager_pipeline_notifications, pDataStream: ^resource_manager_data_stream) -> result --- + resource_manager_data_stream_init_w :: proc(pResourceManager: ^resource_manager, pFilePath: [^]c.wchar_t, flags: u32, pNotifications: ^resource_manager_pipeline_notifications, pDataStream: ^resource_manager_data_stream) -> result --- + resource_manager_data_stream_uninit :: proc(pDataStream: ^resource_manager_data_stream) -> result --- + resource_manager_data_stream_read_pcm_frames :: proc(pDataStream: ^resource_manager_data_stream, pFramesOut: rawptr, frameCount: u64, pFramesRead: ^u64) -> result --- + resource_manager_data_stream_seek_to_pcm_frame :: proc(pDataStream: ^resource_manager_data_stream, frameIndex: u64) -> result --- + resource_manager_data_stream_get_data_format :: proc(pDataStream: ^resource_manager_data_stream, pFormat: ^format, pChannels, pSampleRate: ^u32, pChannelMap: [^]channel, channelMapCap: c.size_t) -> result --- + resource_manager_data_stream_get_cursor_in_pcm_frames :: proc(pDataStream: ^resource_manager_data_stream, pCursor: ^u64) -> result --- + resource_manager_data_stream_get_length_in_pcm_frames :: proc(pDataStream: ^resource_manager_data_stream, pLength: ^u64) -> result --- + resource_manager_data_stream_result :: proc(pDataStream: ^resource_manager_data_stream) -> result --- + resource_manager_data_stream_set_looping :: proc(pDataStream: ^resource_manager_data_stream, isLooping: b32) -> result --- + resource_manager_data_stream_is_looping :: proc(pDataStream: ^resource_manager_data_stream) -> b32 --- + resource_manager_data_stream_get_available_frames :: proc(pDataStream: ^resource_manager_data_stream, pAvailableFrames: ^u64) -> result --- + + /* Data Sources. */ + resource_manager_data_source_init_ex :: proc(pResourceManager: ^resource_manager, pConfig: ^resource_manager_data_source_config, pDataSource: ^resource_manager_data_source) -> result --- + resource_manager_data_source_init :: proc(pResourceManager: ^resource_manager, pName: cstring, flags: u32, pNotifications: ^resource_manager_pipeline_notifications, pDataSource: ^resource_manager_data_source) -> result --- + resource_manager_data_source_init_w :: proc(pResourceManager: ^resource_manager, pName: [^]c.wchar_t, flags: u32, pNotifications: ^resource_manager_pipeline_notifications, pDataSource: ^resource_manager_data_source) -> result --- + resource_manager_data_source_init_copy :: proc(pResourceManager: ^resource_manager, pExistingDataSource, pDataSource: ^resource_manager_data_source) -> result --- + resource_manager_data_source_uninit :: proc(pDataSource: ^resource_manager_data_source) -> result --- + resource_manager_data_source_read_pcm_frames :: proc(pDataSource: ^resource_manager_data_source, pFramesOut: rawptr, frameCount: u64, pFramesRead: ^u64) -> result --- + resource_manager_data_source_seek_to_pcm_frame :: proc(pDataSource: ^resource_manager_data_source, frameIndex: u64) -> result --- + resource_manager_data_source_get_data_format :: proc(pDataSource: ^resource_manager_data_source, pFormat: ^format, pChannels, pSampleRate: ^u32, pChannelMap: [^]channel, channelMapCap: c.size_t) -> result --- + resource_manager_data_source_get_cursor_in_pcm_frames :: proc(pDataSource: ^resource_manager_data_source, pCursor: ^u64) -> result --- + resource_manager_data_source_get_length_in_pcm_frames :: proc(pDataSource: ^resource_manager_data_source, pLength: ^u64) -> result --- + resource_manager_data_source_result :: proc(pDataSource: ^resource_manager_data_source) -> result --- + resource_manager_data_source_set_looping :: proc(pDataSource: ^resource_manager_data_source, isLooping: b32) -> result --- + resource_manager_data_source_is_looping :: proc(pDataSource: ^resource_manager_data_source) -> b32 --- + resource_manager_data_source_get_available_frames :: proc(pDataSource: ^resource_manager_data_source, pAvailableFrames: ^u64) -> result --- + + /* Job management. */ + resource_manager_post_job :: proc(pResourceManager: ^resource_manager, pJob: ^job) -> result --- + resource_manager_post_job_quit :: proc(pResourceManager: ^resource_manager) -> result --- /* Helper for posting a quit job. */ + resource_manager_next_job :: proc(pResourceManager: ^resource_manager, pJob: ^job) -> result --- + resource_manager_process_job :: proc(pResourceManager: ^resource_manager, pJob: ^job) -> result --- /* DEPRECATED. Use ma_job_process(). Will be removed in version 0.12. */ + resource_manager_process_next_job :: proc(pResourceManager: ^resource_manager) -> result --- /* Returns MA_CANCELLED if a MA_JOB_TYPE_QUIT job is found. In non-blocking mode, returns MA_NO_DATA_AVAILABLE if no jobs are available. */ +} diff --git a/vendor/miniaudio/src/miniaudio.h b/vendor/miniaudio/src/miniaudio.h index 5793f20d6..f774f0d5f 100644 --- a/vendor/miniaudio/src/miniaudio.h +++ b/vendor/miniaudio/src/miniaudio.h @@ -1,6 +1,6 @@ /* Audio playback and capture library. Choice of public domain or MIT-0. See license statements at the end of this file. -miniaudio - v0.10.42 - 2021-08-22 +miniaudio - v0.11.9 - 2022-04-20 David Reid - mackron@gmail.com @@ -12,7 +12,8 @@ GitHub: https://github.com/mackron/miniaudio /* 1. Introduction =============== -miniaudio is a single file library for audio playback and capture. To use it, do the following in one .c file: +miniaudio is a single file library for audio playback and capture. To use it, do the following in +one .c file: ```c #define MINIAUDIO_IMPLEMENTATION @@ -21,16 +22,44 @@ miniaudio is a single file library for audio playback and capture. To use it, do You can do `#include "miniaudio.h"` in other parts of the program just like any other header. -miniaudio uses the concept of a "device" as the abstraction for physical devices. The idea is that you choose a physical device to emit or capture audio from, -and then move data to/from the device when miniaudio tells you to. Data is delivered to and from devices asynchronously via a callback which you specify when -initializing the device. +miniaudio includes both low level and high level APIs. The low level API is good for those who want +to do all of their mixing themselves and only require a light weight interface to the underlying +audio device. The high level API is good for those who have complex mixing and effect requirements. -When initializing the device you first need to configure it. The device configuration allows you to specify things like the format of the data delivered via -the callback, the size of the internal buffer and the ID of the device you want to emit or capture audio from. +In miniaudio, objects are transparent structures. Unlike many other libraries, there are no handles +to opaque objects which means you need to allocate memory for objects yourself. In the examples +presented in this documentation you will often see objects declared on the stack. You need to be +careful when translating these examples to your own code so that you don't accidentally declare +your objects on the stack and then cause them to become invalid once the function returns. In +addition, you must ensure the memory address of your objects remain the same throughout their +lifetime. You therefore cannot be making copies of your objects. -Once you have the device configuration set up you can initialize the device. When initializing a device you need to allocate memory for the device object -beforehand. This gives the application complete control over how the memory is allocated. In the example below we initialize a playback device on the stack, -but you could allocate it on the heap if that suits your situation better. +A config/init pattern is used throughout the entire library. The idea is that you set up a config +object and pass that into the initialization routine. The advantage to this system is that the +config object can be initialized with logical defaults and new properties added to it without +breaking the API. The config object can be allocated on the stack and does not need to be +maintained after initialization of the corresponding object. + + +1.1. Low Level API +------------------ +The low level API gives you access to the raw audio data of an audio device. It supports playback, +capture, full-duplex and loopback (WASAPI only). You can enumerate over devices to determine which +physical device(s) you want to connect to. + +The low level API uses the concept of a "device" as the abstraction for physical devices. The idea +is that you choose a physical device to emit or capture audio from, and then move data to/from the +device when miniaudio tells you to. Data is delivered to and from devices asynchronously via a +callback which you specify when initializing the device. + +When initializing the device you first need to configure it. The device configuration allows you to +specify things like the format of the data delivered via the callback, the size of the internal +buffer and the ID of the device you want to emit or capture audio from. + +Once you have the device configuration set up you can initialize the device. When initializing a +device you need to allocate memory for the device object beforehand. This gives the application +complete control over how the memory is allocated. In the example below we initialize a playback +device on the stack, but you could allocate it on the heap if that suits your situation better. ```c void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount) @@ -63,20 +92,27 @@ but you could allocate it on the heap if that suits your situation better. } ``` -In the example above, `data_callback()` is where audio data is written and read from the device. The idea is in playback mode you cause sound to be emitted -from the speakers by writing audio data to the output buffer (`pOutput` in the example). In capture mode you read data from the input buffer (`pInput`) to -extract sound captured by the microphone. The `frameCount` parameter tells you how many frames can be written to the output buffer and read from the input -buffer. A "frame" is one sample for each channel. For example, in a stereo stream (2 channels), one frame is 2 samples: one for the left, one for the right. -The channel count is defined by the device config. The size in bytes of an individual sample is defined by the sample format which is also specified in the -device config. Multi-channel audio data is always interleaved, which means the samples for each frame are stored next to each other in memory. For example, in -a stereo stream the first pair of samples will be the left and right samples for the first frame, the second pair of samples will be the left and right samples -for the second frame, etc. +In the example above, `data_callback()` is where audio data is written and read from the device. +The idea is in playback mode you cause sound to be emitted from the speakers by writing audio data +to the output buffer (`pOutput` in the example). In capture mode you read data from the input +buffer (`pInput`) to extract sound captured by the microphone. The `frameCount` parameter tells you +how many frames can be written to the output buffer and read from the input buffer. A "frame" is +one sample for each channel. For example, in a stereo stream (2 channels), one frame is 2 +samples: one for the left, one for the right. The channel count is defined by the device config. +The size in bytes of an individual sample is defined by the sample format which is also specified +in the device config. Multi-channel audio data is always interleaved, which means the samples for +each frame are stored next to each other in memory. For example, in a stereo stream the first pair +of samples will be the left and right samples for the first frame, the second pair of samples will +be the left and right samples for the second frame, etc. -The configuration of the device is defined by the `ma_device_config` structure. The config object is always initialized with `ma_device_config_init()`. It's -important to always initialize the config with this function as it initializes it with logical defaults and ensures your program doesn't break when new members -are added to the `ma_device_config` structure. The example above uses a fairly simple and standard device configuration. The call to `ma_device_config_init()` -takes a single parameter, which is whether or not the device is a playback, capture, duplex or loopback device (loopback devices are not supported on all -backends). The `config.playback.format` member sets the sample format which can be one of the following (all formats are native-endian): +The configuration of the device is defined by the `ma_device_config` structure. The config object +is always initialized with `ma_device_config_init()`. It's important to always initialize the +config with this function as it initializes it with logical defaults and ensures your program +doesn't break when new members are added to the `ma_device_config` structure. The example above +uses a fairly simple and standard device configuration. The call to `ma_device_config_init()` takes +a single parameter, which is whether or not the device is a playback, capture, duplex or loopback +device (loopback devices are not supported on all backends). The `config.playback.format` member +sets the sample format which can be one of the following (all formats are native-endian): +---------------+----------------------------------------+---------------------------+ | Symbol | Description | Range | @@ -88,22 +124,30 @@ backends). The `config.playback.format` member sets the sample format which can | ma_format_u8 | 8-bit unsigned integer | [0, 255] | +---------------+----------------------------------------+---------------------------+ -The `config.playback.channels` member sets the number of channels to use with the device. The channel count cannot exceed MA_MAX_CHANNELS. The -`config.sampleRate` member sets the sample rate (which must be the same for both playback and capture in full-duplex configurations). This is usually set to -44100 or 48000, but can be set to anything. It's recommended to keep this between 8000 and 384000, however. +The `config.playback.channels` member sets the number of channels to use with the device. The +channel count cannot exceed MA_MAX_CHANNELS. The `config.sampleRate` member sets the sample rate +(which must be the same for both playback and capture in full-duplex configurations). This is +usually set to 44100 or 48000, but can be set to anything. It's recommended to keep this between +8000 and 384000, however. -Note that leaving the format, channel count and/or sample rate at their default values will result in the internal device's native configuration being used -which is useful if you want to avoid the overhead of miniaudio's automatic data conversion. +Note that leaving the format, channel count and/or sample rate at their default values will result +in the internal device's native configuration being used which is useful if you want to avoid the +overhead of miniaudio's automatic data conversion. -In addition to the sample format, channel count and sample rate, the data callback and user data pointer are also set via the config. The user data pointer is -not passed into the callback as a parameter, but is instead set to the `pUserData` member of `ma_device` which you can access directly since all miniaudio -structures are transparent. +In addition to the sample format, channel count and sample rate, the data callback and user data +pointer are also set via the config. The user data pointer is not passed into the callback as a +parameter, but is instead set to the `pUserData` member of `ma_device` which you can access +directly since all miniaudio structures are transparent. -Initializing the device is done with `ma_device_init()`. This will return a result code telling you what went wrong, if anything. On success it will return -`MA_SUCCESS`. After initialization is complete the device will be in a stopped state. To start it, use `ma_device_start()`. Uninitializing the device will stop -it, which is what the example above does, but you can also stop the device with `ma_device_stop()`. To resume the device simply call `ma_device_start()` again. -Note that it's important to never stop or start the device from inside the callback. This will result in a deadlock. Instead you set a variable or signal an -event indicating that the device needs to stop and handle it in a different thread. The following APIs must never be called inside the callback: +Initializing the device is done with `ma_device_init()`. This will return a result code telling you +what went wrong, if anything. On success it will return `MA_SUCCESS`. After initialization is +complete the device will be in a stopped state. To start it, use `ma_device_start()`. +Uninitializing the device will stop it, which is what the example above does, but you can also stop +the device with `ma_device_stop()`. To resume the device simply call `ma_device_start()` again. +Note that it's important to never stop or start the device from inside the callback. This will +result in a deadlock. Instead you set a variable or signal an event indicating that the device +needs to stop and handle it in a different thread. The following APIs must never be called inside +the callback: ```c ma_device_init() @@ -113,12 +157,14 @@ event indicating that the device needs to stop and handle it in a different thre ma_device_stop() ``` -You must never try uninitializing and reinitializing a device inside the callback. You must also never try to stop and start it from inside the callback. There -are a few other things you shouldn't do in the callback depending on your requirements, however this isn't so much a thread-safety thing, but rather a -real-time processing thing which is beyond the scope of this introduction. +You must never try uninitializing and reinitializing a device inside the callback. You must also +never try to stop and start it from inside the callback. There are a few other things you shouldn't +do in the callback depending on your requirements, however this isn't so much a thread-safety +thing, but rather a real-time processing thing which is beyond the scope of this introduction. -The example above demonstrates the initialization of a playback device, but it works exactly the same for capture. All you need to do is change the device type -from `ma_device_type_playback` to `ma_device_type_capture` when setting up the config, like so: +The example above demonstrates the initialization of a playback device, but it works exactly the +same for capture. All you need to do is change the device type from `ma_device_type_playback` to +`ma_device_type_capture` when setting up the config, like so: ```c ma_device_config config = ma_device_config_init(ma_device_type_capture); @@ -126,8 +172,9 @@ from `ma_device_type_playback` to `ma_device_type_capture` when setting up the c config.capture.channels = MY_CHANNEL_COUNT; ``` -In the data callback you just read from the input buffer (`pInput` in the example above) and leave the output buffer alone (it will be set to NULL when the -device type is set to `ma_device_type_capture`). +In the data callback you just read from the input buffer (`pInput` in the example above) and leave +the output buffer alone (it will be set to NULL when the device type is set to +`ma_device_type_capture`). These are the available device types and how you should handle the buffers in the callback: @@ -140,23 +187,29 @@ These are the available device types and how you should handle the buffers in th | ma_device_type_loopback | Read from input buffer, leave output buffer untouched. | +-------------------------+--------------------------------------------------------+ -You will notice in the example above that the sample format and channel count is specified separately for playback and capture. This is to support different -data formats between the playback and capture devices in a full-duplex system. An example may be that you want to capture audio data as a monaural stream (one -channel), but output sound to a stereo speaker system. Note that if you use different formats between playback and capture in a full-duplex configuration you -will need to convert the data yourself. There are functions available to help you do this which will be explained later. +You will notice in the example above that the sample format and channel count is specified +separately for playback and capture. This is to support different data formats between the playback +and capture devices in a full-duplex system. An example may be that you want to capture audio data +as a monaural stream (one channel), but output sound to a stereo speaker system. Note that if you +use different formats between playback and capture in a full-duplex configuration you will need to +convert the data yourself. There are functions available to help you do this which will be +explained later. -The example above did not specify a physical device to connect to which means it will use the operating system's default device. If you have multiple physical -devices connected and you want to use a specific one you will need to specify the device ID in the configuration, like so: +The example above did not specify a physical device to connect to which means it will use the +operating system's default device. If you have multiple physical devices connected and you want to +use a specific one you will need to specify the device ID in the configuration, like so: ```c config.playback.pDeviceID = pMyPlaybackDeviceID; // Only if requesting a playback or duplex device. config.capture.pDeviceID = pMyCaptureDeviceID; // Only if requesting a capture, duplex or loopback device. ``` -To retrieve the device ID you will need to perform device enumeration, however this requires the use of a new concept called the "context". Conceptually -speaking the context sits above the device. There is one context to many devices. The purpose of the context is to represent the backend at a more global level -and to perform operations outside the scope of an individual device. Mainly it is used for performing run-time linking against backend libraries, initializing -backends and enumerating devices. The example below shows how to enumerate devices. +To retrieve the device ID you will need to perform device enumeration, however this requires the +use of a new concept called the "context". Conceptually speaking the context sits above the device. +There is one context to many devices. The purpose of the context is to represent the backend at a +more global level and to perform operations outside the scope of an individual device. Mainly it is +used for performing run-time linking against backend libraries, initializing backends and +enumerating devices. The example below shows how to enumerate devices. ```c ma_context context; @@ -197,44 +250,236 @@ backends and enumerating devices. The example below shows how to enumerate devic ma_context_uninit(&context); ``` -The first thing we do in this example is initialize a `ma_context` object with `ma_context_init()`. The first parameter is a pointer to a list of `ma_backend` -values which are used to override the default backend priorities. When this is NULL, as in this example, miniaudio's default priorities are used. The second -parameter is the number of backends listed in the array pointed to by the first parameter. The third parameter is a pointer to a `ma_context_config` object -which can be NULL, in which case defaults are used. The context configuration is used for setting the logging callback, custom memory allocation callbacks, -user-defined data and some backend-specific configurations. +The first thing we do in this example is initialize a `ma_context` object with `ma_context_init()`. +The first parameter is a pointer to a list of `ma_backend` values which are used to override the +default backend priorities. When this is NULL, as in this example, miniaudio's default priorities +are used. The second parameter is the number of backends listed in the array pointed to by the +first parameter. The third parameter is a pointer to a `ma_context_config` object which can be +NULL, in which case defaults are used. The context configuration is used for setting the logging +callback, custom memory allocation callbacks, user-defined data and some backend-specific +configurations. -Once the context has been initialized you can enumerate devices. In the example above we use the simpler `ma_context_get_devices()`, however you can also use a -callback for handling devices by using `ma_context_enumerate_devices()`. When using `ma_context_get_devices()` you provide a pointer to a pointer that will, -upon output, be set to a pointer to a buffer containing a list of `ma_device_info` structures. You also provide a pointer to an unsigned integer that will -receive the number of items in the returned buffer. Do not free the returned buffers as their memory is managed internally by miniaudio. +Once the context has been initialized you can enumerate devices. In the example above we use the +simpler `ma_context_get_devices()`, however you can also use a callback for handling devices by +using `ma_context_enumerate_devices()`. When using `ma_context_get_devices()` you provide a pointer +to a pointer that will, upon output, be set to a pointer to a buffer containing a list of +`ma_device_info` structures. You also provide a pointer to an unsigned integer that will receive +the number of items in the returned buffer. Do not free the returned buffers as their memory is +managed internally by miniaudio. -The `ma_device_info` structure contains an `id` member which is the ID you pass to the device config. It also contains the name of the device which is useful -for presenting a list of devices to the user via the UI. +The `ma_device_info` structure contains an `id` member which is the ID you pass to the device +config. It also contains the name of the device which is useful for presenting a list of devices +to the user via the UI. -When creating your own context you will want to pass it to `ma_device_init()` when initializing the device. Passing in NULL, like we do in the first example, -will result in miniaudio creating the context for you, which you don't want to do since you've already created a context. Note that internally the context is -only tracked by it's pointer which means you must not change the location of the `ma_context` object. If this is an issue, consider using `malloc()` to -allocate memory for the context. +When creating your own context you will want to pass it to `ma_device_init()` when initializing the +device. Passing in NULL, like we do in the first example, will result in miniaudio creating the +context for you, which you don't want to do since you've already created a context. Note that +internally the context is only tracked by it's pointer which means you must not change the location +of the `ma_context` object. If this is an issue, consider using `malloc()` to allocate memory for +the context. + + +1.2. High Level API +------------------- +The high level API consists of three main parts: + + * Resource management for loading and streaming sounds. + * A node graph for advanced mixing and effect processing. + * A high level "engine" that wraps around the resource manager and node graph. + +The resource manager (`ma_resource_manager`) is used for loading sounds. It supports loading sounds +fully into memory and also streaming. It will also deal with reference counting for you which +avoids the same sound being loaded multiple times. + +The node graph is used for mixing and effect processing. The idea is that you connect a number of +nodes into the graph by connecting each node's outputs to another node's inputs. Each node can +implement it's own effect. By chaining nodes together, advanced mixing and effect processing can +be achieved. + +The engine encapsulates both the resource manager and the node graph to create a simple, easy to +use high level API. The resource manager and node graph APIs are covered in more later sections of +this manual. + +The code below shows how you can initialize an engine using it's default configuration. + + ```c + ma_result result; + ma_engine engine; + + result = ma_engine_init(NULL, &engine); + if (result != MA_SUCCESS) { + return result; // Failed to initialize the engine. + } + ``` + +This creates an engine instance which will initialize a device internally which you can access with +`ma_engine_get_device()`. It will also initialize a resource manager for you which can be accessed +with `ma_engine_get_resource_manager()`. The engine itself is a node graph (`ma_node_graph`) which +means you can pass a pointer to the engine object into any of the `ma_node_graph` APIs (with a +cast). Alternatively, you can use `ma_engine_get_node_graph()` instead of a cast. + +Note that all objects in miniaudio, including the `ma_engine` object in the example above, are +transparent structures. There are no handles to opaque structures in miniaudio which means you need +to be mindful of how you declare them. In the example above we are declaring it on the stack, but +this will result in the struct being invalidated once the function encapsulating it returns. If +allocating the engine on the heap is more appropriate, you can easily do so with a standard call +to `malloc()` or whatever heap allocation routine you like: + + ```c + ma_engine* pEngine = malloc(sizeof(*pEngine)); + ``` + +The `ma_engine` API uses the same config/init pattern used all throughout miniaudio. To configure +an engine, you can fill out a `ma_engine_config` object and pass it into the first parameter of +`ma_engine_init()`: + + ```c + ma_result result; + ma_engine engine; + ma_engine_config engineConfig; + + engineConfig = ma_engine_config_init(); + engineConfig.pResourceManager = &myCustomResourceManager; // <-- Initialized as some earlier stage. + + result = ma_engine_init(&engineConfig, &engine); + if (result != MA_SUCCESS) { + return result; + } + ``` + +This creates an engine instance using a custom config. In this particular example it's showing how +you can specify a custom resource manager rather than having the engine initialize one internally. +This is particularly useful if you want to have multiple engine's share the same resource manager. + +The engine must be uninitialized with `ma_engine_uninit()` when it's no longer needed. + +By default the engine will be started, but nothing will be playing because no sounds have been +initialized. The easiest but least flexible way of playing a sound is like so: + + ```c + ma_engine_play_sound(&engine, "my_sound.wav", NULL); + ``` + +This plays what miniaudio calls an "inline" sound. It plays the sound once, and then puts the +internal sound up for recycling. The last parameter is used to specify which sound group the sound +should be associated with which will be explained later. This particular way of playing a sound is +simple, but lacks flexibility and features. A more flexible way of playing a sound is to first +initialize a sound: + + ```c + ma_result result; + ma_sound sound; + + result = ma_sound_init_from_file(&engine, "my_sound.wav", 0, NULL, NULL, &sound); + if (result != MA_SUCCESS) { + return result; + } + + ma_sound_start(&sound); + ``` + +This returns a `ma_sound` object which represents a single instance of the specified sound file. If +you want to play the same file multiple times simultaneously, you need to create one sound for each +instance. + +Sounds should be uninitialized with `ma_sound_uninit()`. + +Sounds are not started by default. Start a sound with `ma_sound_start()` and stop it with +`ma_sound_stop()`. When a sound is stopped, it is not rewound to the start. Use +`ma_sound_seek_to_pcm_frames(&sound, 0)` to seek back to the start of a sound. By default, starting +and stopping sounds happens immediately, but sometimes it might be convenient to schedule the sound +the be started and/or stopped at a specific time. This can be done with the following functions: + + ```c + ma_sound_set_start_time_in_pcm_frames() + ma_sound_set_start_time_in_milliseconds() + ma_sound_set_stop_time_in_pcm_frames() + ma_sound_set_stop_time_in_milliseconds() + ``` + +The start/stop time needs to be specified based on the absolute timer which is controlled by the +engine. The current global time time in PCM frames can be retrieved with `ma_engine_get_time()`. +The engine's global time can be changed with `ma_engine_set_time()` for synchronization purposes if +required. Note that scheduling a start time still requires an explicit call to `ma_sound_start()` +before anything will play: + + ```c + ma_sound_set_start_time_in_pcm_frames(&sound, ma_engine_get_time(&engine) + (ma_engine_get_sample_rate(&engine) * 2); + ma_sound_start(&sound); + ``` + +The third parameter of `ma_sound_init_from_file()` is a set of flags that control how the sound be +loaded and a few options on which features should be enabled for that sound. By default, the sound +is synchronously loaded fully into memory straight from the file system without any kind of +decoding. If you want to decode the sound before storing it in memory, you need to specify the +`MA_SOUND_FLAG_DECODE` flag. This is useful if you want to incur the cost of decoding at an earlier +stage, such as a loading stage. Without this option, decoding will happen dynamically at mixing +time which might be too expensive on the audio thread. + +If you want to load the sound asynchronously, you can specify the `MA_SOUND_FLAG_ASYNC` flag. This +will result in `ma_sound_init_from_file()` returning quickly, but the sound will not start playing +until the sound has had some audio decoded. + +The fourth parameter is a pointer to sound group. A sound group is used as a mechanism to organise +sounds into groups which have their own effect processing and volume control. An example is a game +which might have separate groups for sfx, voice and music. Each of these groups have their own +independent volume control. Use `ma_sound_group_init()` or `ma_sound_group_init_ex()` to initialize +a sound group. + +Sounds and sound groups are nodes in the engine's node graph and can be plugged into any `ma_node` +API. This makes it possible to connect sounds and sound groups to effect nodes to produce complex +effect chains. + +A sound can have it's volume changed with `ma_sound_set_volume()`. If you prefer decibel volume +control you can use `ma_volume_db_to_linear()` to convert from decibel representation to linear. + +Panning and pitching is supported with `ma_sound_set_pan()` and `ma_sound_set_pitch()`. If you know +a sound will never have it's pitch changed with `ma_sound_set_pitch()` or via the doppler effect, +you can specify the `MA_SOUND_FLAG_NO_PITCH` flag when initializing the sound for an optimization. + +By default, sounds and sound groups have spatialization enabled. If you don't ever want to +spatialize your sounds, initialize the sound with the `MA_SOUND_FLAG_NO_SPATIALIZATION` flag. The +spatialization model is fairly simple and is roughly on feature parity with OpenAL. HRTF and +environmental occlusion are not currently supported, but planned for the future. The supported +features include: + + * Sound and listener positioning and orientation with cones + * Attenuation models: none, inverse, linear and exponential + * Doppler effect + +Sounds can be faded in and out with `ma_sound_set_fade_in_pcm_frames()`. + +To check if a sound is currently playing, you can use `ma_sound_is_playing()`. To check if a sound +is at the end, use `ma_sound_at_end()`. Looping of a sound can be controlled with +`ma_sound_set_looping()`. Use `ma_sound_is_looping()` to check whether or not the sound is looping. 2. Building =========== -miniaudio should work cleanly out of the box without the need to download or install any dependencies. See below for platform-specific details. +miniaudio should work cleanly out of the box without the need to download or install any +dependencies. See below for platform-specific details. 2.1. Windows ------------ -The Windows build should compile cleanly on all popular compilers without the need to configure any include paths nor link to any libraries. +The Windows build should compile cleanly on all popular compilers without the need to configure any +include paths nor link to any libraries. + +The UWP build may require linking to mmdevapi.lib if you get errors about an unresolved external +symbol for `ActivateAudioInterfaceAsync()`. + 2.2. macOS and iOS ------------------ -The macOS build should compile cleanly without the need to download any dependencies nor link to any libraries or frameworks. The iOS build needs to be -compiled as Objective-C and will need to link the relevant frameworks but should compile cleanly out of the box with Xcode. Compiling through the command line -requires linking to `-lpthread` and `-lm`. +The macOS build should compile cleanly without the need to download any dependencies nor link to +any libraries or frameworks. The iOS build needs to be compiled as Objective-C and will need to +link the relevant frameworks but should compile cleanly out of the box with Xcode. Compiling +through the command line requires linking to `-lpthread` and `-lm`. -Due to the way miniaudio links to frameworks at runtime, your application may not pass Apple's notarization process. To fix this there are two options. The -first is to use the `MA_NO_RUNTIME_LINKING` option, like so: +Due to the way miniaudio links to frameworks at runtime, your application may not pass Apple's +notarization process. To fix this there are two options. The first is to use the +`MA_NO_RUNTIME_LINKING` option, like so: ```c #ifdef __APPLE__ @@ -244,8 +489,9 @@ first is to use the `MA_NO_RUNTIME_LINKING` option, like so: #include "miniaudio.h" ``` -This will require linking with `-framework CoreFoundation -framework CoreAudio -framework AudioUnit`. Alternatively, if you would rather keep using runtime -linking you can add the following to your entitlements.xcent file: +This will require linking with `-framework CoreFoundation -framework CoreAudio -framework AudioUnit`. +Alternatively, if you would rather keep using runtime linking you can add the following to your +entitlements.xcent file: ``` com.apple.security.cs.allow-dyld-environment-variables @@ -254,26 +500,37 @@ linking you can add the following to your entitlements.xcent file: ``` +See this discussion for more info: https://github.com/mackron/miniaudio/issues/203. + 2.3. Linux ---------- -The Linux build only requires linking to `-ldl`, `-lpthread` and `-lm`. You do not need any development packages. +The Linux build only requires linking to `-ldl`, `-lpthread` and `-lm`. You do not need any +development packages. You may need to link with `-latomic` if you're compiling for 32-bit ARM. + 2.4. BSD -------- -The BSD build only requires linking to `-lpthread` and `-lm`. NetBSD uses audio(4), OpenBSD uses sndio and FreeBSD uses OSS. +The BSD build only requires linking to `-lpthread` and `-lm`. NetBSD uses audio(4), OpenBSD uses +sndio and FreeBSD uses OSS. You may need to link with `-latomic` if you're compiling for 32-bit +ARM. + 2.5. Android ------------ -AAudio is the highest priority backend on Android. This should work out of the box without needing any kind of compiler configuration. Support for AAudio -starts with Android 8 which means older versions will fall back to OpenSL|ES which requires API level 16+. +AAudio is the highest priority backend on Android. This should work out of the box without needing +any kind of compiler configuration. Support for AAudio starts with Android 8 which means older +versions will fall back to OpenSL|ES which requires API level 16+. + +There have been reports that the OpenSL|ES backend fails to initialize on some Android based +devices due to `dlopen()` failing to open "libOpenSLES.so". If this happens on your platform +you'll need to disable run-time linking with `MA_NO_RUNTIME_LINKING` and link with -lOpenSLES. -There have been reports that the OpenSL|ES backend fails to initialize on some Android based devices due to `dlopen()` failing to open "libOpenSLES.so". If -this happens on your platform you'll need to disable run-time linking with `MA_NO_RUNTIME_LINKING` and link with -lOpenSLES. 2.6. Emscripten --------------- -The Emscripten build emits Web Audio JavaScript directly and should compile cleanly out of the box. You cannot use -std=c* compiler flags, nor -ansi. +The Emscripten build emits Web Audio JavaScript directly and should compile cleanly out of the box. +You cannot use `-std=c*` compiler flags, nor `-ansi`. 2.7. Build Options @@ -366,28 +623,26 @@ The Emscripten build emits Web Audio JavaScript directly and should compile clea +----------------------------------+--------------------------------------------------------------------+ | MA_NO_MP3 | Disables the built-in MP3 decoder. | +----------------------------------+--------------------------------------------------------------------+ - | MA_NO_DEVICE_IO | Disables playback and recording. This will disable ma_context and | - | | ma_device APIs. This is useful if you only want to use miniaudio's | - | | data conversion and/or decoding APIs. | + | MA_NO_DEVICE_IO | Disables playback and recording. This will disable `ma_context` | + | | and `ma_device` APIs. This is useful if you only want to use | + | | miniaudio's data conversion and/or decoding APIs. | +----------------------------------+--------------------------------------------------------------------+ - | MA_NO_THREADING | Disables the ma_thread, ma_mutex, ma_semaphore and ma_event APIs. | - | | This option is useful if you only need to use miniaudio for data | - | | conversion, decoding and/or encoding. Some families of APIs | - | | require threading which means the following options must also be | - | | set: | + | MA_NO_THREADING | Disables the `ma_thread`, `ma_mutex`, `ma_semaphore` and | + | | `ma_event` APIs. This option is useful if you only need to use | + | | miniaudio for data conversion, decoding and/or encoding. Some | + | | families of APIsrequire threading which means the following | + | | options must also be set: | | | | | | ``` | | | MA_NO_DEVICE_IO | | | ``` | +----------------------------------+--------------------------------------------------------------------+ - | MA_NO_GENERATION | Disables generation APIs such a ma_waveform and ma_noise. | + | MA_NO_GENERATION | Disables generation APIs such a `ma_waveform` and `ma_noise`. | +----------------------------------+--------------------------------------------------------------------+ | MA_NO_SSE2 | Disables SSE2 optimizations. | +----------------------------------+--------------------------------------------------------------------+ | MA_NO_AVX2 | Disables AVX2 optimizations. | +----------------------------------+--------------------------------------------------------------------+ - | MA_NO_AVX512 | Disables AVX-512 optimizations. | - +----------------------------------+--------------------------------------------------------------------+ | MA_NO_NEON | Disables NEON optimizations. | +----------------------------------+--------------------------------------------------------------------+ | MA_NO_RUNTIME_LINKING | Disables runtime linking. This is useful for passing Apple's | @@ -399,47 +654,47 @@ The Emscripten build emits Web Audio JavaScript directly and should compile clea | | You may need to enable this if your target platform does not allow | | | runtime linking via `dlopen()`. | +----------------------------------+--------------------------------------------------------------------+ - | MA_DEBUG_OUTPUT | Enable processing of MA_LOG_LEVEL_DEBUG messages and `printf()` | - | | output. | + | MA_DEBUG_OUTPUT | Enable `printf()` output of debug logs (`MA_LOG_LEVEL_DEBUG`). | +----------------------------------+--------------------------------------------------------------------+ | MA_COINIT_VALUE | Windows only. The value to pass to internal calls to | | | `CoInitializeEx()`. Defaults to `COINIT_MULTITHREADED`. | +----------------------------------+--------------------------------------------------------------------+ | MA_API | Controls how public APIs should be decorated. Default is `extern`. | +----------------------------------+--------------------------------------------------------------------+ - | MA_DLL | If set, configures MA_API to either import or export APIs | - | | depending on whether or not the implementation is being defined. | - | | If defining the implementation, MA_API will be configured to | - | | export. Otherwise it will be configured to import. This has no | - | | effect if MA_API is defined externally. | - +----------------------------------+--------------------------------------------------------------------+ 3. Definitions ============== -This section defines common terms used throughout miniaudio. Unfortunately there is often ambiguity in the use of terms throughout the audio space, so this -section is intended to clarify how miniaudio uses each term. +This section defines common terms used throughout miniaudio. Unfortunately there is often ambiguity +in the use of terms throughout the audio space, so this section is intended to clarify how miniaudio +uses each term. 3.1. Sample ----------- -A sample is a single unit of audio data. If the sample format is f32, then one sample is one 32-bit floating point number. +A sample is a single unit of audio data. If the sample format is f32, then one sample is one 32-bit +floating point number. 3.2. Frame / PCM Frame ---------------------- -A frame is a group of samples equal to the number of channels. For a stereo stream a frame is 2 samples, a mono frame is 1 sample, a 5.1 surround sound frame -is 6 samples, etc. The terms "frame" and "PCM frame" are the same thing in miniaudio. Note that this is different to a compressed frame. If ever miniaudio -needs to refer to a compressed frame, such as a FLAC frame, it will always clarify what it's referring to with something like "FLAC frame". +A frame is a group of samples equal to the number of channels. For a stereo stream a frame is 2 +samples, a mono frame is 1 sample, a 5.1 surround sound frame is 6 samples, etc. The terms "frame" +and "PCM frame" are the same thing in miniaudio. Note that this is different to a compressed frame. +If ever miniaudio needs to refer to a compressed frame, such as a FLAC frame, it will always +clarify what it's referring to with something like "FLAC frame". 3.3. Channel ------------ -A stream of monaural audio that is emitted from an individual speaker in a speaker system, or received from an individual microphone in a microphone system. A -stereo stream has two channels (a left channel, and a right channel), a 5.1 surround sound system has 6 channels, etc. Some audio systems refer to a channel as -a complex audio stream that's mixed with other channels to produce the final mix - this is completely different to miniaudio's use of the term "channel" and -should not be confused. +A stream of monaural audio that is emitted from an individual speaker in a speaker system, or +received from an individual microphone in a microphone system. A stereo stream has two channels (a +left channel, and a right channel), a 5.1 surround sound system has 6 channels, etc. Some audio +systems refer to a channel as a complex audio stream that's mixed with other channels to produce +the final mix - this is completely different to miniaudio's use of the term "channel" and should +not be confused. 3.4. Sample Rate ---------------- -The sample rate in miniaudio is always expressed in Hz, such as 44100, 48000, etc. It's the number of PCM frames that are processed per second. +The sample rate in miniaudio is always expressed in Hz, such as 44100, 48000, etc. It's the number +of PCM frames that are processed per second. 3.5. Formats ------------ @@ -459,10 +714,1685 @@ All formats are native-endian. -4. Decoding +4. Data Sources +=============== +The data source abstraction in miniaudio is used for retrieving audio data from some source. A few +examples include `ma_decoder`, `ma_noise` and `ma_waveform`. You will need to be familiar with data +sources in order to make sense of some of the higher level concepts in miniaudio. + +The `ma_data_source` API is a generic interface for reading from a data source. Any object that +implements the data source interface can be plugged into any `ma_data_source` function. + +To read data from a data source: + + ```c + ma_result result; + ma_uint64 framesRead; + + result = ma_data_source_read_pcm_frames(pDataSource, pFramesOut, frameCount, &framesRead, loop); + if (result != MA_SUCCESS) { + return result; // Failed to read data from the data source. + } + ``` + +If you don't need the number of frames that were successfully read you can pass in `NULL` to the +`pFramesRead` parameter. If this returns a value less than the number of frames requested it means +the end of the file has been reached. `MA_AT_END` will be returned only when the number of frames +read is 0. + +When calling any data source function, with the exception of `ma_data_source_init()` and +`ma_data_source_uninit()`, you can pass in any object that implements a data source. For example, +you could plug in a decoder like so: + + ```c + ma_result result; + ma_uint64 framesRead; + ma_decoder decoder; // <-- This would be initialized with `ma_decoder_init_*()`. + + result = ma_data_source_read_pcm_frames(&decoder, pFramesOut, frameCount, &framesRead, loop); + if (result != MA_SUCCESS) { + return result; // Failed to read data from the decoder. + } + ``` + +If you want to seek forward you can pass in `NULL` to the `pFramesOut` parameter. Alternatively you +can use `ma_data_source_seek_pcm_frames()`. + +To seek to a specific PCM frame: + + ```c + result = ma_data_source_seek_to_pcm_frame(pDataSource, frameIndex); + if (result != MA_SUCCESS) { + return result; // Failed to seek to PCM frame. + } + ``` + +You can retrieve the total length of a data source in PCM frames, but note that some data sources +may not have the notion of a length, such as noise and waveforms, and others may just not have a +way of determining the length such as some decoders. To retrieve the length: + + ```c + ma_uint64 length; + + result = ma_data_source_get_length_in_pcm_frames(pDataSource, &length); + if (result != MA_SUCCESS) { + return result; // Failed to retrieve the length. + } + ``` + +Care should be taken when retrieving the length of a data source where the underlying decoder is +pulling data from a data stream with an undefined length, such as internet radio or some kind of +broadcast. If you do this, `ma_data_source_get_length_in_pcm_frames()` may never return. + +The current position of the cursor in PCM frames can also be retrieved: + + ```c + ma_uint64 cursor; + + result = ma_data_source_get_cursor_in_pcm_frames(pDataSource, &cursor); + if (result != MA_SUCCESS) { + return result; // Failed to retrieve the cursor. + } + ``` + +You will often need to know the data format that will be returned after reading. This can be +retrieved like so: + + ```c + ma_format format; + ma_uint32 channels; + ma_uint32 sampleRate; + ma_channel channelMap[MA_MAX_CHANNELS]; + + result = ma_data_source_get_data_format(pDataSource, &format, &channels, &sampleRate, channelMap, MA_MAX_CHANNELS); + if (result != MA_SUCCESS) { + return result; // Failed to retrieve data format. + } + ``` + +If you do not need a specific data format property, just pass in NULL to the respective parameter. + +There may be cases where you want to implement something like a sound bank where you only want to +read data within a certain range of the underlying data. To do this you can use a range: + + ```c + result = ma_data_source_set_range_in_pcm_frames(pDataSource, rangeBegInFrames, rangeEndInFrames); + if (result != MA_SUCCESS) { + return result; // Failed to set the range. + } + ``` + +This is useful if you have a sound bank where many sounds are stored in the same file and you want +the data source to only play one of those sub-sounds. + +Custom loop points can also be used with data sources. By default, data sources will loop after +they reach the end of the data source, but if you need to loop at a specific location, you can do +the following: + + ```c + result = ma_data_set_loop_point_in_pcm_frames(pDataSource, loopBegInFrames, loopEndInFrames); + if (result != MA_SUCCESS) { + return result; // Failed to set the loop point. + } + ``` + +The loop point is relative to the current range. + +It's sometimes useful to chain data sources together so that a seamless transition can be achieved. +To do this, you can use chaining: + + ```c + ma_decoder decoder1; + ma_decoder decoder2; + + // ... initialize decoders with ma_decoder_init_*() ... + + result = ma_data_source_set_next(&decoder1, &decoder2); + if (result != MA_SUCCESS) { + return result; // Failed to set the next data source. + } + + result = ma_data_source_read_pcm_frames(&decoder1, pFramesOut, frameCount, pFramesRead, MA_FALSE); + if (result != MA_SUCCESS) { + return result; // Failed to read from the decoder. + } + ``` + +In the example above we're using decoders. When reading from a chain, you always want to read from +the top level data source in the chain. In the example above, `decoder1` is the top level data +source in the chain. When `decoder1` reaches the end, `decoder2` will start seamlessly without any +gaps. + +Note that the `loop` parameter is set to false in the example above. When this is set to true, only +the current data source will be looped. You can loop the entire chain by linking in a loop like so: + + ```c + ma_data_source_set_next(&decoder1, &decoder2); // decoder1 -> decoder2 + ma_data_source_set_next(&decoder2, &decoder1); // decoder2 -> decoder1 (loop back to the start). + ``` + +Note that setting up chaining is not thread safe, so care needs to be taken if you're dynamically +changing links while the audio thread is in the middle of reading. + +Do not use `ma_decoder_seek_to_pcm_frame()` as a means to reuse a data source to play multiple +instances of the same sound simultaneously. Instead, initialize multiple data sources for each +instance. This can be extremely inefficient depending on the data source and can result in +glitching due to subtle changes to the state of internal filters. + + +4.1. Custom Data Sources +------------------------ +You can implement a custom data source by implementing the functions in `ma_data_source_vtable`. +Your custom object must have `ma_data_source_base` as it's first member: + + ```c + struct my_data_source + { + ma_data_source_base base; + ... + }; + ``` + +In your initialization routine, you need to call `ma_data_source_init()` in order to set up the +base object (`ma_data_source_base`): + + ```c + static ma_result my_data_source_read(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) + { + // Read data here. Output in the same format returned by my_data_source_get_data_format(). + } + + static ma_result my_data_source_seek(ma_data_source* pDataSource, ma_uint64 frameIndex) + { + // Seek to a specific PCM frame here. Return MA_NOT_IMPLEMENTED if seeking is not supported. + } + + static ma_result my_data_source_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) + { + // Return the format of the data here. + } + + static ma_result my_data_source_get_cursor(ma_data_source* pDataSource, ma_uint64* pCursor) + { + // Retrieve the current position of the cursor here. Return MA_NOT_IMPLEMENTED and set *pCursor to 0 if there is no notion of a cursor. + } + + static ma_result my_data_source_get_length(ma_data_source* pDataSource, ma_uint64* pLength) + { + // Retrieve the length in PCM frames here. Return MA_NOT_IMPLEMENTED and set *pLength to 0 if there is no notion of a length or if the length is unknown. + } + + static g_my_data_source_vtable = + { + my_data_source_read, + my_data_source_seek, + my_data_source_get_data_format, + my_data_source_get_cursor, + my_data_source_get_length + }; + + ma_result my_data_source_init(my_data_source* pMyDataSource) + { + ma_result result; + ma_data_source_config baseConfig; + + baseConfig = ma_data_source_config_init(); + baseConfig.vtable = &g_my_data_source_vtable; + + result = ma_data_source_init(&baseConfig, &pMyDataSource->base); + if (result != MA_SUCCESS) { + return result; + } + + // ... do the initialization of your custom data source here ... + + return MA_SUCCESS; + } + + void my_data_source_uninit(my_data_source* pMyDataSource) + { + // ... do the uninitialization of your custom data source here ... + + // You must uninitialize the base data source. + ma_data_source_uninit(&pMyDataSource->base); + } + ``` + +Note that `ma_data_source_init()` and `ma_data_source_uninit()` are never called directly outside +of the custom data source. It's up to the custom data source itself to call these within their own +init/uninit functions. + + + +5. Engine +========= +The `ma_engine` API is a high level API for managing and mixing sounds and effect processing. The +`ma_engine` object encapsulates a resource manager and a node graph, both of which will be +explained in more detail later. + +Sounds are called `ma_sound` and are created from an engine. Sounds can be associated with a mixing +group called `ma_sound_group` which are also created from the engine. Both `ma_sound` and +`ma_sound_group` objects are nodes within the engine's node graph. + +When the engine is initialized, it will normally create a device internally. If you would rather +manage the device yourself, you can do so and just pass a pointer to it via the engine config when +you initialize the engine. You can also just use the engine without a device, which again can be +configured via the engine config. + +The most basic way to initialize the engine is with a default config, like so: + + ```c + ma_result result; + ma_engine engine; + + result = ma_engine_init(NULL, &engine); + if (result != MA_SUCCESS) { + return result; // Failed to initialize the engine. + } + ``` + +This will result in the engine initializing a playback device using the operating system's default +device. This will be sufficient for many use cases, but if you need more flexibility you'll want to +configure the engine with an engine config: + + ```c + ma_result result; + ma_engine engine; + ma_engine_config engineConfig; + + engineConfig = ma_engine_config_init(); + engineConfig.pPlaybackDevice = &myDevice; + + result = ma_engine_init(&engineConfig, &engine); + if (result != MA_SUCCESS) { + return result; // Failed to initialize the engine. + } + ``` + +In the example above we're passing in a pre-initialized device. Since the caller is the one in +control of the device's data callback, it's their responsibility to manually call +`ma_engine_read_pcm_frames()` from inside their data callback: + + ```c + void playback_data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount) + { + ma_engine_read_pcm_frames(&g_Engine, pOutput, frameCount, NULL); + } + ``` + +You can also use the engine independent of a device entirely: + + ```c + ma_result result; + ma_engine engine; + ma_engine_config engineConfig; + + engineConfig = ma_engine_config_init(); + engineConfig.noDevice = MA_TRUE; + engineConfig.channels = 2; // Must be set when not using a device. + engineConfig.sampleRate = 48000; // Must be set when not using a device. + + result = ma_engine_init(&engineConfig, &engine); + if (result != MA_SUCCESS) { + return result; // Failed to initialize the engine. + } + ``` + +Note that when you're not using a device, you must set the channel count and sample rate in the +config or else miniaudio won't know what to use (miniaudio will use the device to determine this +normally). When not using a device, you need to use `ma_engine_read_pcm_frames()` to process audio +data from the engine. This kind of setup is useful if you want to do something like offline +processing. + +When a sound is loaded it goes through a resource manager. By default the engine will initialize a +resource manager internally, but you can also specify a pre-initialized resource manager: + + ```c + ma_result result; + ma_engine engine1; + ma_engine engine2; + ma_engine_config engineConfig; + + engineConfig = ma_engine_config_init(); + engineConfig.pResourceManager = &myResourceManager; + + ma_engine_init(&engineConfig, &engine1); + ma_engine_init(&engineConfig, &engine2); + ``` + +In this example we are initializing two engines, both of which are sharing the same resource +manager. This is especially useful for saving memory when loading the same file across multiple +engines. If you were not to use a shared resource manager, each engine instance would use their own +which would result in any sounds that are used between both engine's being loaded twice. By using +a shared resource manager, it would only be loaded once. Using multiple engine's is useful when you +need to output to multiple playback devices, such as in a local multiplayer game where each player +is using their own set of headphones. + +By default an engine will be in a started state. To make it so the engine is not automatically +started you can configure it as such: + + ```c + engineConfig.noAutoStart = MA_TRUE; + + // The engine will need to be started manually. + ma_engine_start(&engine); + + // Later on the engine can be stopped with ma_engine_stop(). + ma_engine_stop(&engine); + ``` + +The concept of starting or stopping an engine is only relevant when using the engine with a +device. Attempting to start or stop an engine that is not associated with a device will result in +`MA_INVALID_OPERATION`. + +The master volume of the engine can be controlled with `ma_engine_set_volume()` which takes a +linear scale, with 0 resulting in silence and anything above 1 resulting in amplification. If you +prefer decibel based volume control, use `ma_volume_db_to_linear()` to convert from dB to linear. + +When a sound is spatialized, it is done so relative to a listener. An engine can be configured to +have multiple listeners which can be configured via the config: + + ```c + engineConfig.listenerCount = 2; + ``` + +The maximum number of listeners is restricted to `MA_ENGINE_MAX_LISTENERS`. By default, when a +sound is spatialized, it will be done so relative to the closest listener. You can also pin a sound +to a specific listener which will be explained later. Listener's have a position, direction, cone, +and velocity (for doppler effect). A listener is referenced by an index, the meaning of which is up +to the caller (the index is 0 based and cannot go beyond the listener count, minus 1). The +position, direction and velocity are all specified in absolute terms: + + ```c + ma_engine_listener_set_position(&engine, listenerIndex, worldPosX, worldPosY, worldPosZ); + ``` + +The direction of the listener represents it's forward vector. The listener's up vector can also be +specified and defaults to +1 on the Y axis. + + ```c + ma_engine_listener_set_direction(&engine, listenerIndex, forwardX, forwardY, forwardZ); + ma_engine_listener_set_world_up(&engine, listenerIndex, 0, 1, 0); + ``` + +The engine supports directional attenuation. The listener can have a cone the controls how sound is +attenuated based on the listener's direction. When a sound is between the inner and outer cones, it +will be attenuated between 1 and the cone's outer gain: + + ```c + ma_engine_listener_set_cone(&engine, listenerIndex, innerAngleInRadians, outerAngleInRadians, outerGain); + ``` + +When a sound is inside the inner code, no directional attenuation is applied. When the sound is +outside of the outer cone, the attenuation will be set to `outerGain` in the example above. When +the sound is in between the inner and outer cones, the attenuation will be interpolated between 1 +and the outer gain. + +The engine's coordinate system follows the OpenGL coordinate system where positive X points right, +positive Y points up and negative Z points forward. + +The simplest and least flexible way to play a sound is like so: + + ```c + ma_engine_play_sound(&engine, "my_sound.wav", pGroup); + ``` + +This is a "fire and forget" style of function. The engine will manage the `ma_sound` object +internally. When the sound finishes playing, it'll be put up for recycling. For more flexibility +you'll want to initialize a sound object: + + ```c + ma_sound sound; + + result = ma_sound_init_from_file(&engine, "my_sound.wav", flags, pGroup, NULL, &sound); + if (result != MA_SUCCESS) { + return result; // Failed to load sound. + } + ``` + +Sounds need to be uninitialized with `ma_sound_uninit()`. + +The example above loads a sound from a file. If the resource manager has been disabled you will not +be able to use this function and instead you'll need to initialize a sound directly from a data +source: + + ```c + ma_sound sound; + + result = ma_sound_init_from_data_source(&engine, &dataSource, flags, pGroup, &sound); + if (result != MA_SUCCESS) { + return result; + } + ``` + +Each `ma_sound` object represents a single instance of the sound. If you want to play the same +sound multiple times at the same time, you need to initialize a separate `ma_sound` object. + +For the most flexibility when initializing sounds, use `ma_sound_init_ex()`. This uses miniaudio's +standard config/init pattern: + + ```c + ma_sound sound; + ma_sound_config soundConfig; + + soundConfig = ma_sound_config_init(); + soundConfig.pFilePath = NULL; // Set this to load from a file path. + soundConfig.pDataSource = NULL; // Set this to initialize from an existing data source. + soundConfig.pInitialAttachment = &someNodeInTheNodeGraph; + soundConfig.initialAttachmentInputBusIndex = 0; + soundConfig.channelsIn = 1; + soundConfig.channelsOut = 0; // Set to 0 to use the engine's native channel count. + + result = ma_sound_init_ex(&soundConfig, &sound); + if (result != MA_SUCCESS) { + return result; + } + ``` + +In the example above, the sound is being initialized without a file nor a data source. This is +valid, in which case the sound acts as a node in the middle of the node graph. This means you can +connect other sounds to this sound and allow it to act like a sound group. Indeed, this is exactly +what a `ma_sound_group` is. + +When loading a sound, you specify a set of flags that control how the sound is loaded and what +features are enabled for that sound. When no flags are set, the sound will be fully loaded into +memory in exactly the same format as how it's stored on the file system. The resource manager will +allocate a block of memory and then load the file directly into it. When reading audio data, it +will be decoded dynamically on the fly. In order to save processing time on the audio thread, it +might be beneficial to pre-decode the sound. You can do this with the `MA_SOUND_FLAG_DECODE` flag: + + ```c + ma_sound_init_from_file(&engine, "my_sound.wav", MA_SOUND_FLAG_DECODE, pGroup, NULL, &sound); + ``` + +By default, sounds will be loaded synchronously, meaning `ma_sound_init_*()` will not return until +the sound has been fully loaded. If this is prohibitive you can instead load sounds asynchronously +by specificying the `MA_SOUND_FLAG_ASYNC` flag: + + ```c + ma_sound_init_from_file(&engine, "my_sound.wav", MA_SOUND_FLAG_DECODE | MA_SOUND_FLAG_ASYNC, pGroup, NULL, &sound); + ``` + +This will result in `ma_sound_init_*()` returning quickly, but the sound won't yet have been fully +loaded. When you start the sound, it won't output anything until some sound is available. The sound +will start outputting audio before the sound has been fully decoded when the `MA_SOUND_FLAG_DECODE` +is specified. + +If you need to wait for an asynchronously loaded sound to be fully loaded, you can use a fence. A +fence in miniaudio is a simple synchronization mechanism which simply blocks until it's internal +counter hit's zero. You can specify a fence like so: + + ```c + ma_result result; + ma_fence fence; + ma_sound sounds[4]; + + result = ma_fence_init(&fence); + if (result != MA_SUCCES) { + return result; + } + + // Load some sounds asynchronously. + for (int iSound = 0; iSound < 4; iSound += 1) { + ma_sound_init_from_file(&engine, mySoundFilesPaths[iSound], MA_SOUND_FLAG_DECODE | MA_SOUND_FLAG_ASYNC, pGroup, &fence, &sounds[iSound]); + } + + // ... do some other stuff here in the mean time ... + + // Wait for all sounds to finish loading. + ma_fence_wait(&fence); + ``` + +If loading the entire sound into memory is prohibitive, you can also configure the engine to stream +the audio data: + + ```c + ma_sound_init_from_file(&engine, "my_sound.wav", MA_SOUND_FLAG_STREAM, pGroup, NULL, &sound); + ``` + +When streaming sounds, 2 seconds worth of audio data is stored in memory. Although it should work +fine, it's inefficient to use streaming for short sounds. Streaming is useful for things like music +tracks in games. + +When you initialize a sound, if you specify a sound group the sound will be attached to that group +automatically. If you set it to NULL, it will be automatically attached to the engine's endpoint. +If you would instead rather leave the sound unattached by default, you can can specify the +`MA_SOUND_FLAG_NO_DEFAULT_ATTACHMENT` flag. This is useful if you want to set up a complex node +graph. + +Sounds are not started by default. To start a sound, use `ma_sound_start()`. Stop a sound with +`ma_sound_stop()`. + +Sounds can have their volume controlled with `ma_sound_set_volume()` in the same way as the +engine's master volume. + +Sounds support stereo panning and pitching. Set the pan with `ma_sound_set_pan()`. Setting the pan +to 0 will result in an unpanned sound. Setting it to -1 will shift everything to the left, whereas ++1 will shift it to the right. The pitch can be controlled with `ma_sound_set_pitch()`. A larger +value will result in a higher pitch. The pitch must be greater than 0. + +The engine supports 3D spatialization of sounds. By default sounds will have spatialization +enabled, but if a sound does not need to be spatialized it's best to disable it. There are two ways +to disable spatialization of a sound: + + ```c + // Disable spatialization at initialization time via a flag: + ma_sound_init_from_file(&engine, "my_sound.wav", MA_SOUND_FLAG_NO_SPATIALIZATION, NULL, NULL, &sound); + + // Dynamically disable or enable spatialization post-initialization: + ma_sound_set_spatialization_enabled(&sound, isSpatializationEnabled); + ``` + +By default sounds will be spatialized based on the closest listener. If a sound should always be +spatialized relative to a specific listener it can be pinned to one: + + ```c + ma_sound_set_pinned_listener_index(&sound, listenerIndex); + ``` + +Like listeners, sounds have a position. By default, the position of a sound is in absolute space, +but it can be changed to be relative to a listener: + + ```c + ma_sound_set_positioning(&sound, ma_positioning_relative); + ``` + +Note that relative positioning of a sound only makes sense if there is either only one listener, or +the sound is pinned to a specific listener. To set the position of a sound: + + ```c + ma_sound_set_position(&sound, posX, posY, posZ); + ``` + +The direction works the same way as a listener and represents the sound's forward direction: + + ```c + ma_sound_set_direction(&sound, forwardX, forwardY, forwardZ); + ``` + +Sound's also have a cone for controlling directional attenuation. This works exactly the same as +listeners: + + ```c + ma_sound_set_cone(&sound, innerAngleInRadians, outerAngleInRadians, outerGain); + ``` + +The velocity of a sound is used for doppler effect and can be set as such: + + ```c + ma_sound_set_velocity(&sound, velocityX, velocityY, velocityZ); + ``` + +The engine supports different attenuation models which can be configured on a per-sound basis. By +default the attenuation model is set to `ma_attenuation_model_inverse` which is the equivalent to +OpenAL's `AL_INVERSE_DISTANCE_CLAMPED`. Configure the attenuation model like so: + + ```c + ma_sound_set_attenuation_model(&sound, ma_attenuation_model_inverse); + ``` + +The supported attenuation models include the following: + + +----------------------------------+----------------------------------------------+ + | ma_attenuation_model_none | No distance attenuation. | + +----------------------------------+----------------------------------------------+ + | ma_attenuation_model_inverse | Equivalent to `AL_INVERSE_DISTANCE_CLAMPED`. | + +----------------------------------+----------------------------------------------+ + | ma_attenuation_model_linear | Linear attenuation. | + +----------------------------------+----------------------------------------------+ + | ma_attenuation_model_exponential | Exponential attenuation. | + +----------------------------------+----------------------------------------------+ + +To control how quickly a sound rolls off as it moves away from the listener, you need to configure +the rolloff: + + ```c + ma_sound_set_rolloff(&sound, rolloff); + ``` + +You can control the minimum and maximum gain to apply from spatialization: + + ```c + ma_sound_set_min_gain(&sound, minGain); + ma_sound_set_max_gain(&sound, maxGain); + ``` + +Likewise, in the calculation of attenuation, you can control the minimum and maximum distances for +the attenuation calculation. This is useful if you want to ensure sounds don't drop below a certain +volume after the listener moves further away and to have sounds play a maximum volume when the +listener is within a certain distance: + + ```c + ma_sound_set_min_distance(&sound, minDistance); + ma_sound_set_max_distance(&sound, maxDistance); + ``` + +The engine's spatialization system supports doppler effect. The doppler factor can be configure on +a per-sound basis like so: + + ```c + ma_sound_set_doppler_factor(&sound, dopplerFactor); + ``` + +You can fade sounds in and out with `ma_sound_set_fade_in_pcm_frames()` and +`ma_sound_set_fade_in_milliseconds()`. Set the volume to -1 to use the current volume as the +starting volume: + + ```c + // Fade in over 1 second. + ma_sound_set_fade_in_milliseconds(&sound, 0, 1, 1000); + + // ... sometime later ... + + // Fade out over 1 second, starting from the current volume. + ma_sound_set_fade_in_milliseconds(&sound, -1, 0, 1000); + ``` + +By default sounds will start immediately, but sometimes for timing and synchronization purposes it +can be useful to schedule a sound to start or stop: + + ```c + // Start the sound in 1 second from now. + ma_sound_set_start_time_in_pcm_frames(&sound, ma_engine_get_time(&engine) + (ma_engine_get_sample_rate(&engine) * 1)); + + // Stop the sound in 2 seconds from now. + ma_sound_set_stop_time_in_pcm_frames(&sound, ma_engine_get_time(&engine) + (ma_engine_get_sample_rate(&engine) * 2)); + ``` + +Note that scheduling a start time still requires an explicit call to `ma_sound_start()` before +anything will play. + +The time is specified in global time which is controlled by the engine. You can get the engine's +current time with `ma_engine_get_time()`. The engine's global time is incremented automatically as +audio data is read, but it can be reset with `ma_engine_set_time()` in case it needs to be +resynchronized for some reason. + +To determine whether or not a sound is currently playing, use `ma_sound_is_playing()`. This will +take the scheduled start and stop times into account. + +Whether or not a sound should loop can be controlled with `ma_sound_set_looping()`. Sounds will not +be looping by default. Use `ma_sound_is_looping()` to determine whether or not a sound is looping. + +Use `ma_sound_at_end()` to determine whether or not a sound is currently at the end. For a looping +sound this should never return true. + +Internally a sound wraps around a data source. Some APIs exist to control the underlying data +source, mainly for convenience: + + ```c + ma_sound_seek_to_pcm_frame(&sound, frameIndex); + ma_sound_get_data_format(&sound, &format, &channels, &sampleRate, pChannelMap, channelMapCapacity); + ma_sound_get_cursor_in_pcm_frames(&sound, &cursor); + ma_sound_get_length_in_pcm_frames(&sound, &length); + ``` + +Sound groups have the same API as sounds, only they are called `ma_sound_group`, and since they do +not have any notion of a data source, anything relating to a data source is unavailable. + +Internally, sound data is loaded via the `ma_decoder` API which means by default in only supports +file formats that have built-in support in miniaudio. You can extend this to support any kind of +file format through the use of custom decoders. To do this you'll need to use a self-managed +resource manager and configure it appropriately. See the "Resource Management" section below for +details on how to set this up. + + +6. Resource Management +====================== +Many programs will want to manage sound resources for things such as reference counting and +streaming. This is supported by miniaudio via the `ma_resource_manager` API. + +The resource manager is mainly responsible for the following: + + * Loading of sound files into memory with reference counting. + * Streaming of sound data + +When loading a sound file, the resource manager will give you back a `ma_data_source` compatible +object called `ma_resource_manager_data_source`. This object can be passed into any +`ma_data_source` API which is how you can read and seek audio data. When loading a sound file, you +specify whether or not you want the sound to be fully loaded into memory (and optionally +pre-decoded) or streamed. When loading into memory, you can also specify whether or not you want +the data to be loaded asynchronously. + +The example below is how you can initialize a resource manager using it's default configuration: + + ```c + ma_resource_manager_config config; + ma_resource_manager resourceManager; + + config = ma_resource_manager_config_init(); + result = ma_resource_manager_init(&config, &resourceManager); + if (result != MA_SUCCESS) { + ma_device_uninit(&device); + printf("Failed to initialize the resource manager."); + return -1; + } + ``` + +You can configure the format, channels and sample rate of the decoded audio data. By default it +will use the file's native data format, but you can configure it to use a consistent format. This +is useful for offloading the cost of data conversion to load time rather than dynamically +converting at mixing time. To do this, you configure the decoded format, channels and sample rate +like the code below: + + ```c + config = ma_resource_manager_config_init(); + config.decodedFormat = device.playback.format; + config.decodedChannels = device.playback.channels; + config.decodedSampleRate = device.sampleRate; + ``` + +In the code above, the resource manager will be configured so that any decoded audio data will be +pre-converted at load time to the device's native data format. If instead you used defaults and +the data format of the file did not match the device's data format, you would need to convert the +data at mixing time which may be prohibitive in high-performance and large scale scenarios like +games. + +Internally the resource manager uses the `ma_decoder` API to load sounds. This means by default it +only supports decoders that are built into miniaudio. It's possible to support additional encoding +formats through the use of custom decoders. To do so, pass in your `ma_decoding_backend_vtable` +vtables into the resource manager config: + + ```c + ma_decoding_backend_vtable* pCustomBackendVTables[] = + { + &g_ma_decoding_backend_vtable_libvorbis, + &g_ma_decoding_backend_vtable_libopus + }; + + ... + + resourceManagerConfig.ppCustomDecodingBackendVTables = pCustomBackendVTables; + resourceManagerConfig.customDecodingBackendCount = sizeof(pCustomBackendVTables) / sizeof(pCustomBackendVTables[0]); + resourceManagerConfig.pCustomDecodingBackendUserData = NULL; + ``` + +This system can allow you to support any kind of file format. See the "Decoding" section for +details on how to implement custom decoders. The miniaudio repository includes examples for Opus +via libopus and libopusfile and Vorbis via libvorbis and libvorbisfile. + +Asynchronicity is achieved via a job system. When an operation needs to be performed, such as the +decoding of a page, a job will be posted to a queue which will then be processed by a job thread. +By default there will be only one job thread running, but this can be configured, like so: + + ```c + config = ma_resource_manager_config_init(); + config.jobThreadCount = MY_JOB_THREAD_COUNT; + ``` + +By default job threads are managed internally by the resource manager, however you can also self +manage your job threads if, for example, you want to integrate the job processing into your +existing job infrastructure, or if you simply don't like the way the resource manager does it. To +do this, just set the job thread count to 0 and process jobs manually. To process jobs, you first +need to retrieve a job using `ma_resource_manager_next_job()` and then process it using +`ma_job_process()`: + + ```c + config = ma_resource_manager_config_init(); + config.jobThreadCount = 0; // Don't manage any job threads internally. + config.flags = MA_RESOURCE_MANAGER_FLAG_NON_BLOCKING; // Optional. Makes `ma_resource_manager_next_job()` non-blocking. + + // ... Initialize your custom job threads ... + + void my_custom_job_thread(...) + { + for (;;) { + ma_job job; + ma_result result = ma_resource_manager_next_job(pMyResourceManager, &job); + if (result != MA_SUCCESS) { + if (result == MA_NOT_DATA_AVAILABLE) { + // No jobs are available. Keep going. Will only get this if the resource manager was initialized + // with MA_RESOURCE_MANAGER_FLAG_NON_BLOCKING. + continue; + } else if (result == MA_CANCELLED) { + // MA_JOB_TYPE_QUIT was posted. Exit. + break; + } else { + // Some other error occurred. + break; + } + } + + ma_job_process(&job); + } + } + ``` + +In the example above, the `MA_JOB_TYPE_QUIT` event is the used as the termination +indicator, but you can use whatever you would like to terminate the thread. The call to +`ma_resource_manager_next_job()` is blocking by default, but can be configured to be non-blocking +by initializing the resource manager with the `MA_RESOURCE_MANAGER_FLAG_NON_BLOCKING` configuration +flag. Note that the `MA_JOB_TYPE_QUIT` will never be removed from the job queue. This +is to give every thread the opportunity to catch the event and terminate naturally. + +When loading a file, it's sometimes convenient to be able to customize how files are opened and +read instead of using standard `fopen()`, `fclose()`, etc. which is what miniaudio will use by +default. This can be done by setting `pVFS` member of the resource manager's config: + + ```c + // Initialize your custom VFS object. See documentation for VFS for information on how to do this. + my_custom_vfs vfs = my_custom_vfs_init(); + + config = ma_resource_manager_config_init(); + config.pVFS = &vfs; + ``` + +This is particularly useful in programs like games where you want to read straight from an archive +rather than the normal file system. If you do not specify a custom VFS, the resource manager will +use the operating system's normal file operations. This is default. + +To load a sound file and create a data source, call `ma_resource_manager_data_source_init()`. When +loading a sound you need to specify the file path and options for how the sounds should be loaded. +By default a sound will be loaded synchronously. The returned data source is owned by the caller +which means the caller is responsible for the allocation and freeing of the data source. Below is +an example for initializing a data source: + + ```c + ma_resource_manager_data_source dataSource; + ma_result result = ma_resource_manager_data_source_init(pResourceManager, pFilePath, flags, &dataSource); + if (result != MA_SUCCESS) { + // Error. + } + + // ... + + // A ma_resource_manager_data_source object is compatible with the `ma_data_source` API. To read data, just call + // the `ma_data_source_read_pcm_frames()` like you would with any normal data source. + result = ma_data_source_read_pcm_frames(&dataSource, pDecodedData, frameCount, &framesRead); + if (result != MA_SUCCESS) { + // Failed to read PCM frames. + } + + // ... + + ma_resource_manager_data_source_uninit(pResourceManager, &dataSource); + ``` + +The `flags` parameter specifies how you want to perform loading of the sound file. It can be a +combination of the following flags: + + ``` + MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM + MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE + MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC + MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT + ``` + +When no flags are specified (set to 0), the sound will be fully loaded into memory, but not +decoded, meaning the raw file data will be stored in memory, and then dynamically decoded when +`ma_data_source_read_pcm_frames()` is called. To instead decode the audio data before storing it in +memory, use the `MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE` flag. By default, the sound file will +be loaded synchronously, meaning `ma_resource_manager_data_source_init()` will only return after +the entire file has been loaded. This is good for simplicity, but can be prohibitively slow. You +can instead load the sound asynchronously using the `MA_RESOURCE_MANAGER_DATA_SOURCE_ASYNC` flag. +This will result in `ma_resource_manager_data_source_init()` returning quickly, but no data will be +returned by `ma_data_source_read_pcm_frames()` until some data is available. When no data is +available because the asynchronous decoding hasn't caught up, `MA_BUSY` will be returned by +`ma_data_source_read_pcm_frames()`. + +For large sounds, it's often prohibitive to store the entire file in memory. To mitigate this, you +can instead stream audio data which you can do by specifying the +`MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM` flag. When streaming, data will be decoded in 1 +second pages. When a new page needs to be decoded, a job will be posted to the job queue and then +subsequently processed in a job thread. + +For in-memory sounds, reference counting is used to ensure the data is loaded only once. This means +multiple calls to `ma_resource_manager_data_source_init()` with the same file path will result in +the file data only being loaded once. Each call to `ma_resource_manager_data_source_init()` must be +matched up with a call to `ma_resource_manager_data_source_uninit()`. Sometimes it can be useful +for a program to register self-managed raw audio data and associate it with a file path. Use the +`ma_resource_manager_register_*()` and `ma_resource_manager_unregister_*()` APIs to do this. +`ma_resource_manager_register_decoded_data()` is used to associate a pointer to raw, self-managed +decoded audio data in the specified data format with the specified name. Likewise, +`ma_resource_manager_register_encoded_data()` is used to associate a pointer to raw self-managed +encoded audio data (the raw file data) with the specified name. Note that these names need not be +actual file paths. When `ma_resource_manager_data_source_init()` is called (without the +`MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM` flag), the resource manager will look for these +explicitly registered data buffers and, if found, will use it as the backing data for the data +source. Note that the resource manager does *not* make a copy of this data so it is up to the +caller to ensure the pointer stays valid for it's lifetime. Use +`ma_resource_manager_unregister_data()` to unregister the self-managed data. You can also use +`ma_resource_manager_register_file()` and `ma_resource_manager_unregister_file()` to register and +unregister a file. It does not make sense to use the `MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM` +flag with a self-managed data pointer. + + +6.1. Asynchronous Loading and Synchronization +--------------------------------------------- +When loading asynchronously, it can be useful to poll whether or not loading has finished. Use +`ma_resource_manager_data_source_result()` to determine this. For in-memory sounds, this will +return `MA_SUCCESS` when the file has been *entirely* decoded. If the sound is still being decoded, +`MA_BUSY` will be returned. Otherwise, some other error code will be returned if the sound failed +to load. For streaming data sources, `MA_SUCCESS` will be returned when the first page has been +decoded and the sound is ready to be played. If the first page is still being decoded, `MA_BUSY` +will be returned. Otherwise, some other error code will be returned if the sound failed to load. + +In addition to polling, you can also use a simple synchronization object called a "fence" to wait +for asynchronously loaded sounds to finish. This is called `ma_fence`. The advantage to using a +fence is that it can be used to wait for a group of sounds to finish loading rather than waiting +for sounds on an individual basis. There are two stages to loading a sound: + + * Initialization of the internal decoder; and + * Completion of decoding of the file (the file is fully decoded) + +You can specify separate fences for each of the different stages. Waiting for the initialization +of the internal decoder is important for when you need to know the sample format, channels and +sample rate of the file. + +The example below shows how you could use a fence when loading a number of sounds: + + ```c + // This fence will be released when all sounds are finished loading entirely. + ma_fence fence; + ma_fence_init(&fence); + + // This will be passed into the initialization routine for each sound. + ma_resource_manager_pipeline_notifications notifications = ma_resource_manager_pipeline_notifications_init(); + notifications.done.pFence = &fence; + + // Now load a bunch of sounds: + for (iSound = 0; iSound < soundCount; iSound += 1) { + ma_resource_manager_data_source_init(pResourceManager, pSoundFilePaths[iSound], flags, ¬ifications, &pSoundSources[iSound]); + } + + // ... DO SOMETHING ELSE WHILE SOUNDS ARE LOADING ... + + // Wait for loading of sounds to finish. + ma_fence_wait(&fence); + ``` + +In the example above we used a fence for waiting until the entire file has been fully decoded. If +you only need to wait for the initialization of the internal decoder to complete, you can use the +`init` member of the `ma_resource_manager_pipeline_notifications` object: + + ```c + notifications.init.pFence = &fence; + ``` + +If a fence is not appropriate for your situation, you can instead use a callback that is fired on +an individual sound basis. This is done in a very similar way to fences: + + ```c + typedef struct + { + ma_async_notification_callbacks cb; + void* pMyData; + } my_notification; + + void my_notification_callback(ma_async_notification* pNotification) + { + my_notification* pMyNotification = (my_notification*)pNotification; + + // Do something in response to the sound finishing loading. + } + + ... + + my_notification myCallback; + myCallback.cb.onSignal = my_notification_callback; + myCallback.pMyData = pMyData; + + ma_resource_manager_pipeline_notifications notifications = ma_resource_manager_pipeline_notifications_init(); + notifications.done.pNotification = &myCallback; + + ma_resource_manager_data_source_init(pResourceManager, "my_sound.wav", flags, ¬ifications, &mySound); + ``` + +In the example above we just extend the `ma_async_notification_callbacks` object and pass an +instantiation into the `ma_resource_manager_pipeline_notifications` in the same way as we did with +the fence, only we set `pNotification` instead of `pFence`. You can set both of these at the same +time and they should both work as expected. If using the `pNotification` system, you need to ensure +your `ma_async_notification_callbacks` object stays valid. + + + +6.2. Resource Manager Implementation Details +-------------------------------------------- +Resources are managed in two main ways: + + * By storing the entire sound inside an in-memory buffer (referred to as a data buffer) + * By streaming audio data on the fly (referred to as a data stream) + +A resource managed data source (`ma_resource_manager_data_source`) encapsulates a data buffer or +data stream, depending on whether or not the data source was initialized with the +`MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM` flag. If so, it will make use of a +`ma_resource_manager_data_stream` object. Otherwise it will use a `ma_resource_manager_data_buffer` +object. Both of these objects are data sources which means they can be used with any +`ma_data_source_*()` API. + +Another major feature of the resource manager is the ability to asynchronously decode audio files. +This relieves the audio thread of time-consuming decoding which can negatively affect scalability +due to the audio thread needing to complete it's work extremely quickly to avoid glitching. +Asynchronous decoding is achieved through a job system. There is a central multi-producer, +multi-consumer, fixed-capacity job queue. When some asynchronous work needs to be done, a job is +posted to the queue which is then read by a job thread. The number of job threads can be +configured for improved scalability, and job threads can all run in parallel without needing to +worry about the order of execution (how this is achieved is explained below). + +When a sound is being loaded asynchronously, playback can begin before the sound has been fully +decoded. This enables the application to start playback of the sound quickly, while at the same +time allowing to resource manager to keep loading in the background. Since there may be less +threads than the number of sounds being loaded at a given time, a simple scheduling system is used +to keep decoding time balanced and fair. The resource manager solves this by splitting decoding +into chunks called pages. By default, each page is 1 second long. When a page has been decoded, a +new job will be posted to start decoding the next page. By dividing up decoding into pages, an +individual sound shouldn't ever delay every other sound from having their first page decoded. Of +course, when loading many sounds at the same time, there will always be an amount of time required +to process jobs in the queue so in heavy load situations there will still be some delay. To +determine if a data source is ready to have some frames read, use +`ma_resource_manager_data_source_get_available_frames()`. This will return the number of frames +available starting from the current position. + + +6.2.1. Job Queue +---------------- +The resource manager uses a job queue which is multi-producer, multi-consumer, and fixed-capacity. +This job queue is not currently lock-free, and instead uses a spinlock to achieve thread-safety. +Only a fixed number of jobs can be allocated and inserted into the queue which is done through a +lock-free data structure for allocating an index into a fixed sized array, with reference counting +for mitigation of the ABA problem. The reference count is 32-bit. + +For many types of jobs it's important that they execute in a specific order. In these cases, jobs +are executed serially. For the resource manager, serial execution of jobs is only required on a +per-object basis (per data buffer or per data stream). Each of these objects stores an execution +counter. When a job is posted it is associated with an execution counter. When the job is +processed, it checks if the execution counter of the job equals the execution counter of the +owning object and if so, processes the job. If the counters are not equal, the job will be posted +back onto the job queue for later processing. When the job finishes processing the execution order +of the main object is incremented. This system means the no matter how many job threads are +executing, decoding of an individual sound will always get processed serially. The advantage to +having multiple threads comes into play when loading multiple sounds at the same time. + +The resource manager's job queue is not 100% lock-free and will use a spinlock to achieve +thread-safety for a very small section of code. This is only relevant when the resource manager +uses more than one job thread. If only using a single job thread, which is the default, the +lock should never actually wait in practice. The amount of time spent locking should be quite +short, but it's something to be aware of for those who have pedantic lock-free requirements and +need to use more than one job thread. There are plans to remove this lock in a future version. + +In addition, posting a job will release a semaphore, which on Win32 is implemented with +`ReleaseSemaphore` and on POSIX platforms via a condition variable: + + ```c + pthread_mutex_lock(&pSemaphore->lock); + { + pSemaphore->value += 1; + pthread_cond_signal(&pSemaphore->cond); + } + pthread_mutex_unlock(&pSemaphore->lock); + ``` + +Again, this is relevant for those with strict lock-free requirements in the audio thread. To avoid +this, you can use non-blocking mode (via the `MA_JOB_QUEUE_FLAG_NON_BLOCKING` +flag) and implement your own job processing routine (see the "Resource Manager" section above for +details on how to do this). + + + +6.2.2. Data Buffers +------------------- +When the `MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM` flag is excluded at initialization time, the +resource manager will try to load the data into an in-memory data buffer. Before doing so, however, +it will first check if the specified file is already loaded. If so, it will increment a reference +counter and just use the already loaded data. This saves both time and memory. When the data buffer +is uninitialized, the reference counter will be decremented. If the counter hits zero, the file +will be unloaded. This is a detail to keep in mind because it could result in excessive loading and +unloading of a sound. For example, the following sequence will result in a file be loaded twice, +once after the other: + + ```c + ma_resource_manager_data_source_init(pResourceManager, "my_file", ..., &myDataBuffer0); // Refcount = 1. Initial load. + ma_resource_manager_data_source_uninit(pResourceManager, &myDataBuffer0); // Refcount = 0. Unloaded. + + ma_resource_manager_data_source_init(pResourceManager, "my_file", ..., &myDataBuffer1); // Refcount = 1. Reloaded because previous uninit() unloaded it. + ma_resource_manager_data_source_uninit(pResourceManager, &myDataBuffer1); // Refcount = 0. Unloaded. + ``` + +A binary search tree (BST) is used for storing data buffers as it has good balance between +efficiency and simplicity. The key of the BST is a 64-bit hash of the file path that was passed +into `ma_resource_manager_data_source_init()`. The advantage of using a hash is that it saves +memory over storing the entire path, has faster comparisons, and results in a mostly balanced BST +due to the random nature of the hash. The disadvantage is that file names are case-sensitive. If +this is an issue, you should normalize your file names to upper- or lower-case before initializing +your data sources. + +When a sound file has not already been loaded and the `MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC` +flag is excluded, the file will be decoded synchronously by the calling thread. There are two +options for controlling how the audio is stored in the data buffer - encoded or decoded. When the +`MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE` option is excluded, the raw file data will be stored +in memory. Otherwise the sound will be decoded before storing it in memory. Synchronous loading is +a very simple and standard process of simply adding an item to the BST, allocating a block of +memory and then decoding (if `MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE` is specified). + +When the `MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC` flag is specified, loading of the data buffer +is done asynchronously. In this case, a job is posted to the queue to start loading and then the +function immediately returns, setting an internal result code to `MA_BUSY`. This result code is +returned when the program calls `ma_resource_manager_data_source_result()`. When decoding has fully +completed `MA_SUCCESS` will be returned. This can be used to know if loading has fully completed. + +When loading asynchronously, a single job is posted to the queue of the type +`MA_JOB_TYPE_RESOURCE_MANAGER_LOAD_DATA_BUFFER_NODE`. This involves making a copy of the file path and +associating it with job. When the job is processed by the job thread, it will first load the file +using the VFS associated with the resource manager. When using a custom VFS, it's important that it +be completely thread-safe because it will be used from one or more job threads at the same time. +Individual files should only ever be accessed by one thread at a time, however. After opening the +file via the VFS, the job will determine whether or not the file is being decoded. If not, it +simply allocates a block of memory and loads the raw file contents into it and returns. On the +other hand, when the file is being decoded, it will first allocate a decoder on the heap and +initialize it. Then it will check if the length of the file is known. If so it will allocate a +block of memory to store the decoded output and initialize it to silence. If the size is unknown, +it will allocate room for one page. After memory has been allocated, the first page will be +decoded. If the sound is shorter than a page, the result code will be set to `MA_SUCCESS` and the +completion event will be signalled and loading is now complete. If, however, there is more to +decode, a job with the code `MA_JOB_TYPE_RESOURCE_MANAGER_PAGE_DATA_BUFFER_NODE` is posted. This job +will decode the next page and perform the same process if it reaches the end. If there is more to +decode, the job will post another `MA_JOB_TYPE_RESOURCE_MANAGER_PAGE_DATA_BUFFER_NODE` job which will +keep on happening until the sound has been fully decoded. For sounds of an unknown length, each +page will be linked together as a linked list. Internally this is implemented via the +`ma_paged_audio_buffer` object. + + +6.2.3. Data Streams +------------------- +Data streams only ever store two pages worth of data for each instance. They are most useful for +large sounds like music tracks in games that would consume too much memory if fully decoded in +memory. After every frame from a page has been read, a job will be posted to load the next page +which is done from the VFS. + +For data streams, the `MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC` flag will determine whether or +not initialization of the data source waits until the two pages have been decoded. When unset, +`ma_resource_manager_data_source_init()` will wait until the two pages have been loaded, otherwise +it will return immediately. + +When frames are read from a data stream using `ma_resource_manager_data_source_read_pcm_frames()`, +`MA_BUSY` will be returned if there are no frames available. If there are some frames available, +but less than the number requested, `MA_SUCCESS` will be returned, but the actual number of frames +read will be less than the number requested. Due to the asynchronous nature of data streams, +seeking is also asynchronous. If the data stream is in the middle of a seek, `MA_BUSY` will be +returned when trying to read frames. + +When `ma_resource_manager_data_source_read_pcm_frames()` results in a page getting fully consumed +a job is posted to load the next page. This will be posted from the same thread that called +`ma_resource_manager_data_source_read_pcm_frames()`. + +Data streams are uninitialized by posting a job to the queue, but the function won't return until +that job has been processed. The reason for this is that the caller owns the data stream object and +therefore miniaudio needs to ensure everything completes before handing back control to the caller. +Also, if the data stream is uninitialized while pages are in the middle of decoding, they must +complete before destroying any underlying object and the job system handles this cleanly. + +Note that when a new page needs to be loaded, a job will be posted to the resource manager's job +thread from the audio thread. You must keep in mind the details mentioned in the "Job Queue" +section above regarding locking when posting an event if you require a strictly lock-free audio +thread. + + + +7. Node Graph +============= +miniaudio's routing infrastructure follows a node graph paradigm. The idea is that you create a +node whose outputs are attached to inputs of another node, thereby creating a graph. There are +different types of nodes, with each node in the graph processing input data to produce output, +which is then fed through the chain. Each node in the graph can apply their own custom effects. At +the start of the graph will usually be one or more data source nodes which have no inputs, but +instead pull their data from a data source. At the end of the graph is an endpoint which represents +the end of the chain and is where the final output is ultimately extracted from. + +Each node has a number of input buses and a number of output buses. An output bus from a node is +attached to an input bus of another. Multiple nodes can connect their output buses to another +node's input bus, in which case their outputs will be mixed before processing by the node. Below is +a diagram that illustrates a hypothetical node graph setup: + + ``` + >>>>>>>>>>>>>>>>>>>>>>>>>>>>>> Data flows left to right >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + +---------------+ +-----------------+ + | Data Source 1 =----+ +----------+ +----= Low Pass Filter =----+ + +---------------+ | | =----+ +-----------------+ | +----------+ + +----= Splitter | +----= ENDPOINT | + +---------------+ | | =----+ +-----------------+ | +----------+ + | Data Source 2 =----+ +----------+ +----= Echo / Delay =----+ + +---------------+ +-----------------+ + ``` + +In the above graph, it starts with two data sources whose outputs are attached to the input of a +splitter node. It's at this point that the two data sources are mixed. After mixing, the splitter +performs it's processing routine and produces two outputs which is simply a duplication of the +input stream. One output is attached to a low pass filter, whereas the other output is attached to +a echo/delay. The outputs of the the low pass filter and the echo are attached to the endpoint, and +since they're both connected to the same input but, they'll be mixed. + +Each input bus must be configured to accept the same number of channels, but the number of channels +used by input buses can be different to the number of channels for output buses in which case +miniaudio will automatically convert the input data to the output channel count before processing. +The number of channels of an output bus of one node must match the channel count of the input bus +it's attached to. The channel counts cannot be changed after the node has been initialized. If you +attempt to attach an output bus to an input bus with a different channel count, attachment will +fail. + +To use a node graph, you first need to initialize a `ma_node_graph` object. This is essentially a +container around the entire graph. The `ma_node_graph` object is required for some thread-safety +issues which will be explained later. A `ma_node_graph` object is initialized using miniaudio's +standard config/init system: + + ```c + ma_node_graph_config nodeGraphConfig = ma_node_graph_config_init(myChannelCount); + + result = ma_node_graph_init(&nodeGraphConfig, NULL, &nodeGraph); // Second parameter is a pointer to allocation callbacks. + if (result != MA_SUCCESS) { + // Failed to initialize node graph. + } + ``` + +When you initialize the node graph, you're specifying the channel count of the endpoint. The +endpoint is a special node which has one input bus and one output bus, both of which have the +same channel count, which is specified in the config. Any nodes that connect directly to the +endpoint must be configured such that their output buses have the same channel count. When you read +audio data from the node graph, it'll have the channel count you specified in the config. To read +data from the graph: + + ```c + ma_uint32 framesRead; + result = ma_node_graph_read_pcm_frames(&nodeGraph, pFramesOut, frameCount, &framesRead); + if (result != MA_SUCCESS) { + // Failed to read data from the node graph. + } + ``` + +When you read audio data, miniaudio starts at the node graph's endpoint node which then pulls in +data from it's input attachments, which in turn recusively pull in data from their inputs, and so +on. At the start of the graph there will be some kind of data source node which will have zero +inputs and will instead read directly from a data source. The base nodes don't literally need to +read from a `ma_data_source` object, but they will always have some kind of underlying object that +sources some kind of audio. The `ma_data_source_node` node can be used to read from a +`ma_data_source`. Data is always in floating-point format and in the number of channels you +specified when the graph was initialized. The sample rate is defined by the underlying data sources. +It's up to you to ensure they use a consistent and appropraite sample rate. + +The `ma_node` API is designed to allow custom nodes to be implemented with relative ease, but +miniaudio includes a few stock nodes for common functionality. This is how you would initialize a +node which reads directly from a data source (`ma_data_source_node`) which is an example of one +of the stock nodes that comes with miniaudio: + + ```c + ma_data_source_node_config config = ma_data_source_node_config_init(pMyDataSource); + + ma_data_source_node dataSourceNode; + result = ma_data_source_node_init(&nodeGraph, &config, NULL, &dataSourceNode); + if (result != MA_SUCCESS) { + // Failed to create data source node. + } + ``` + +The data source node will use the output channel count to determine the channel count of the output +bus. There will be 1 output bus and 0 input buses (data will be drawn directly from the data +source). The data source must output to floating-point (`ma_format_f32`) or else an error will be +returned from `ma_data_source_node_init()`. + +By default the node will not be attached to the graph. To do so, use `ma_node_attach_output_bus()`: + + ```c + result = ma_node_attach_output_bus(&dataSourceNode, 0, ma_node_graph_get_endpoint(&nodeGraph), 0); + if (result != MA_SUCCESS) { + // Failed to attach node. + } + ``` + +The code above connects the data source node directly to the endpoint. Since the data source node +has only a single output bus, the index will always be 0. Likewise, the endpoint only has a single +input bus which means the input bus index will also always be 0. + +To detach a specific output bus, use `ma_node_detach_output_bus()`. To detach all output buses, use +`ma_node_detach_all_output_buses()`. If you want to just move the output bus from one attachment to +another, you do not need to detach first. You can just call `ma_node_attach_output_bus()` and it'll +deal with it for you. + +Less frequently you may want to create a specialized node. This will be a node where you implement +your own processing callback to apply a custom effect of some kind. This is similar to initalizing +one of the stock node types, only this time you need to specify a pointer to a vtable containing a +pointer to the processing function and the number of input and output buses. Example: + + ```c + static void my_custom_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) + { + // Do some processing of ppFramesIn (one stream of audio data per input bus) + const float* pFramesIn_0 = ppFramesIn[0]; // Input bus @ index 0. + const float* pFramesIn_1 = ppFramesIn[1]; // Input bus @ index 1. + float* pFramesOut_0 = ppFramesOut[0]; // Output bus @ index 0. + + // Do some processing. On input, `pFrameCountIn` will be the number of input frames in each + // buffer in `ppFramesIn` and `pFrameCountOut` will be the capacity of each of the buffers + // in `ppFramesOut`. On output, `pFrameCountIn` should be set to the number of input frames + // your node consumed and `pFrameCountOut` should be set the number of output frames that + // were produced. + // + // You should process as many frames as you can. If your effect consumes input frames at the + // same rate as output frames (always the case, unless you're doing resampling), you need + // only look at `ppFramesOut` and process that exact number of frames. If you're doing + // resampling, you'll need to be sure to set both `pFrameCountIn` and `pFrameCountOut` + // properly. + } + + static ma_node_vtable my_custom_node_vtable = + { + my_custom_node_process_pcm_frames, // The function that will be called process your custom node. This is where you'd implement your effect processing. + NULL, // Optional. A callback for calculating the number of input frames that are required to process a specified number of output frames. + 2, // 2 input buses. + 1, // 1 output bus. + 0 // Default flags. + }; + + ... + + // Each bus needs to have a channel count specified. To do this you need to specify the channel + // counts in an array and then pass that into the node config. + ma_uint32 inputChannels[2]; // Equal in size to the number of input channels specified in the vtable. + ma_uint32 outputChannels[1]; // Equal in size to the number of output channels specicied in the vtable. + + inputChannels[0] = channelsIn; + inputChannels[1] = channelsIn; + outputChannels[0] = channelsOut; + + ma_node_config nodeConfig = ma_node_config_init(); + nodeConfig.vtable = &my_custom_node_vtable; + nodeConfig.pInputChannels = inputChannels; + nodeConfig.pOutputChannels = outputChannels; + + ma_node_base node; + result = ma_node_init(&nodeGraph, &nodeConfig, NULL, &node); + if (result != MA_SUCCESS) { + // Failed to initialize node. + } + ``` + +When initializing a custom node, as in the code above, you'll normally just place your vtable in +static space. The number of input and output buses are specified as part of the vtable. If you need +a variable number of buses on a per-node bases, the vtable should have the relevant bus count set +to `MA_NODE_BUS_COUNT_UNKNOWN`. In this case, the bus count should be set in the node config: + + ```c + static ma_node_vtable my_custom_node_vtable = + { + my_custom_node_process_pcm_frames, // The function that will be called process your custom node. This is where you'd implement your effect processing. + NULL, // Optional. A callback for calculating the number of input frames that are required to process a specified number of output frames. + MA_NODE_BUS_COUNT_UNKNOWN, // The number of input buses is determined on a per-node basis. + 1, // 1 output bus. + 0 // Default flags. + }; + + ... + + ma_node_config nodeConfig = ma_node_config_init(); + nodeConfig.vtable = &my_custom_node_vtable; + nodeConfig.inputBusCount = myBusCount; // <-- Since the vtable specifies MA_NODE_BUS_COUNT_UNKNOWN, the input bus count should be set here. + nodeConfig.pInputChannels = inputChannels; // <-- Make sure there are nodeConfig.inputBusCount elements in this array. + nodeConfig.pOutputChannels = outputChannels; // <-- The vtable specifies 1 output bus, so there must be 1 element in this array. + ``` + +In the above example it's important to never set the `inputBusCount` and `outputBusCount` members +to anything other than their defaults if the vtable specifies an explicit count. They can only be +set if the vtable specifies MA_NODE_BUS_COUNT_UNKNOWN in the relevant bus count. + +Most often you'll want to create a structure to encapsulate your node with some extra data. You +need to make sure the `ma_node_base` object is your first member of the structure: + + ```c + typedef struct + { + ma_node_base base; // <-- Make sure this is always the first member. + float someCustomData; + } my_custom_node; + ``` + +By doing this, your object will be compatible with all `ma_node` APIs and you can attach it to the +graph just like any other node. + +In the custom processing callback (`my_custom_node_process_pcm_frames()` in the example above), the +number of channels for each bus is what was specified by the config when the node was initialized +with `ma_node_init()`. In addition, all attachments to each of the input buses will have been +pre-mixed by miniaudio. The config allows you to specify different channel counts for each +individual input and output bus. It's up to the effect to handle it appropriate, and if it can't, +return an error in it's initialization routine. + +Custom nodes can be assigned some flags to describe their behaviour. These are set via the vtable +and include the following: + + +-----------------------------------------+---------------------------------------------------+ + | Flag Name | Description | + +-----------------------------------------+---------------------------------------------------+ + | MA_NODE_FLAG_PASSTHROUGH | Useful for nodes that do not do any kind of audio | + | | processing, but are instead used for tracking | + | | time, handling events, etc. Also used by the | + | | internal endpoint node. It reads directly from | + | | the input bus to the output bus. Nodes with this | + | | flag must have exactly 1 input bus and 1 output | + | | bus, and both buses must have the same channel | + | | counts. | + +-----------------------------------------+---------------------------------------------------+ + | MA_NODE_FLAG_CONTINUOUS_PROCESSING | Causes the processing callback to be called even | + | | when no data is available to be read from input | + | | attachments. This is useful for effects like | + | | echos where there will be a tail of audio data | + | | that still needs to be processed even when the | + | | original data sources have reached their ends. | + +-----------------------------------------+---------------------------------------------------+ + | MA_NODE_FLAG_ALLOW_NULL_INPUT | Used in conjunction with | + | | `MA_NODE_FLAG_CONTINUOUS_PROCESSING`. When this | + | | is set, the `ppFramesIn` parameter of the | + | | processing callback will be set to NULL when | + | | there are no input frames are available. When | + | | this is unset, silence will be posted to the | + | | processing callback. | + +-----------------------------------------+---------------------------------------------------+ + | MA_NODE_FLAG_DIFFERENT_PROCESSING_RATES | Used to tell miniaudio that input and output | + | | frames are processed at different rates. You | + | | should set this for any nodes that perform | + | | resampling. | + +-----------------------------------------+---------------------------------------------------+ + | MA_NODE_FLAG_SILENT_OUTPUT | Used to tell miniaudio that a node produces only | + | | silent output. This is useful for nodes where you | + | | don't want the output to contribute to the final | + | | mix. An example might be if you want split your | + | | stream and have one branch be output to a file. | + | | When using this flag, you should avoid writing to | + | | the output buffer of the node's processing | + | | callback because miniaudio will ignore it anyway. | + +-----------------------------------------+---------------------------------------------------+ + + +If you need to make a copy of an audio stream for effect processing you can use a splitter node +called `ma_splitter_node`. This takes has 1 input bus and splits the stream into 2 output buses. +You can use it like this: + + ```c + ma_splitter_node_config splitterNodeConfig = ma_splitter_node_config_init(channelsIn, channelsOut); + + ma_splitter_node splitterNode; + result = ma_splitter_node_init(&nodeGraph, &splitterNodeConfig, NULL, &splitterNode); + if (result != MA_SUCCESS) { + // Failed to create node. + } + + // Attach your output buses to two different input buses (can be on two different nodes). + ma_node_attach_output_bus(&splitterNode, 0, ma_node_graph_get_endpoint(&nodeGraph), 0); // Attach directly to the endpoint. + ma_node_attach_output_bus(&splitterNode, 1, &myEffectNode, 0); // Attach to input bus 0 of some effect node. + ``` + +The volume of an output bus can be configured on a per-bus basis: + + ```c + ma_node_set_output_bus_volume(&splitterNode, 0, 0.5f); + ma_node_set_output_bus_volume(&splitterNode, 1, 0.5f); + ``` + +In the code above we're using the splitter node from before and changing the volume of each of the +copied streams. + +You can start and stop a node with the following: + + ```c + ma_node_set_state(&splitterNode, ma_node_state_started); // The default state. + ma_node_set_state(&splitterNode, ma_node_state_stopped); + ``` + +By default the node is in a started state, but since it won't be connected to anything won't +actually be invoked by the node graph until it's connected. When you stop a node, data will not be +read from any of it's input connections. You can use this property to stop a group of sounds +atomically. + +You can configure the initial state of a node in it's config: + + ```c + nodeConfig.initialState = ma_node_state_stopped; + ``` + +Note that for the stock specialized nodes, all of their configs will have a `nodeConfig` member +which is the config to use with the base node. This is where the initial state can be configured +for specialized nodes: + + ```c + dataSourceNodeConfig.nodeConfig.initialState = ma_node_state_stopped; + ``` + +When using a specialized node like `ma_data_source_node` or `ma_splitter_node`, be sure to not +modify the `vtable` member of the `nodeConfig` object. + + +7.1. Timing +----------- +The node graph supports starting and stopping nodes at scheduled times. This is especially useful +for data source nodes where you want to get the node set up, but only start playback at a specific +time. There are two clocks: local and global. + +A local clock is per-node, whereas the global clock is per graph. Scheduling starts and stops can +only be done based on the global clock because the local clock will not be running while the node +is stopped. The global clocks advances whenever `ma_node_graph_read_pcm_frames()` is called. On the +other hand, the local clock only advances when the node's processing callback is fired, and is +advanced based on the output frame count. + +To retrieve the global time, use `ma_node_graph_get_time()`. The global time can be set with +`ma_node_graph_set_time()` which might be useful if you want to do seeking on a global timeline. +Getting and setting the local time is similar. Use `ma_node_get_time()` to retrieve the local time, +and `ma_node_set_time()` to set the local time. The global and local times will be advanced by the +audio thread, so care should be taken to avoid data races. Ideally you should avoid calling these +outside of the node processing callbacks which are always run on the audio thread. + +There is basic support for scheduling the starting and stopping of nodes. You can only schedule one +start and one stop at a time. This is mainly intended for putting nodes into a started or stopped +state in a frame-exact manner. Without this mechanism, starting and stopping of a node is limited +to the resolution of a call to `ma_node_graph_read_pcm_frames()` which would typically be in blocks +of several milliseconds. The following APIs can be used for scheduling node states: + + ```c + ma_node_set_state_time() + ma_node_get_state_time() + ``` + +The time is absolute and must be based on the global clock. An example is below: + + ```c + ma_node_set_state_time(&myNode, ma_node_state_started, sampleRate*1); // Delay starting to 1 second. + ma_node_set_state_time(&myNode, ma_node_state_stopped, sampleRate*5); // Delay stopping to 5 seconds. + ``` + +An example for changing the state using a relative time. + + ```c + ma_node_set_state_time(&myNode, ma_node_state_started, sampleRate*1 + ma_node_graph_get_time(&myNodeGraph)); + ma_node_set_state_time(&myNode, ma_node_state_stopped, sampleRate*5 + ma_node_graph_get_time(&myNodeGraph)); + ``` + +Note that due to the nature of multi-threading the times may not be 100% exact. If this is an +issue, consider scheduling state changes from within a processing callback. An idea might be to +have some kind of passthrough trigger node that is used specifically for tracking time and handling +events. + + + +7.2. Thread Safety and Locking +------------------------------ +When processing audio, it's ideal not to have any kind of locking in the audio thread. Since it's +expected that `ma_node_graph_read_pcm_frames()` would be run on the audio thread, it does so +without the use of any locks. This section discusses the implementation used by miniaudio and goes +over some of the compromises employed by miniaudio to achieve this goal. Note that the current +implementation may not be ideal - feedback and critiques are most welcome. + +The node graph API is not *entirely* lock-free. Only `ma_node_graph_read_pcm_frames()` is expected +to be lock-free. Attachment, detachment and uninitialization of nodes use locks to simplify the +implementation, but are crafted in a way such that such locking is not required when reading audio +data from the graph. Locking in these areas are achieved by means of spinlocks. + +The main complication with keeping `ma_node_graph_read_pcm_frames()` lock-free stems from the fact +that a node can be uninitialized, and it's memory potentially freed, while in the middle of being +processed on the audio thread. There are times when the audio thread will be referencing a node, +which means the uninitialization process of a node needs to make sure it delays returning until the +audio thread is finished so that control is not handed back to the caller thereby giving them a +chance to free the node's memory. + +When the audio thread is processing a node, it does so by reading from each of the output buses of +the node. In order for a node to process data for one of it's output buses, it needs to read from +each of it's input buses, and so on an so forth. It follows that once all output buses of a node +are detached, the node as a whole will be disconnected and no further processing will occur unless +it's output buses are reattached, which won't be happening when the node is being uninitialized. +By having `ma_node_detach_output_bus()` wait until the audio thread is finished with it, we can +simplify a few things, at the expense of making `ma_node_detach_output_bus()` a bit slower. By +doing this, the implementation of `ma_node_uninit()` becomes trivial - just detach all output +nodes, followed by each of the attachments to each of it's input nodes, and then do any final clean +up. + +With the above design, the worst-case scenario is `ma_node_detach_output_bus()` taking as long as +it takes to process the output bus being detached. This will happen if it's called at just the +wrong moment where the audio thread has just iterated it and has just started processing. The +caller of `ma_node_detach_output_bus()` will stall until the audio thread is finished, which +includes the cost of recursively processing it's inputs. This is the biggest compromise made with +the approach taken by miniaudio for it's lock-free processing system. The cost of detaching nodes +earlier in the pipeline (data sources, for example) will be cheaper than the cost of detaching +higher level nodes, such as some kind of final post-processing endpoint. If you need to do mass +detachments, detach starting from the lowest level nodes and work your way towards the final +endpoint node (but don't try detaching the node graph's endpoint). If the audio thread is not +running, detachment will be fast and detachment in any order will be the same. The reason nodes +need to wait for their input attachments to complete is due to the potential for desyncs between +data sources. If the node was to terminate processing mid way through processing it's inputs, +there's a chance that some of the underlying data sources will have been read, but then others not. +That will then result in a potential desynchronization when detaching and reattaching higher-level +nodes. A possible solution to this is to have an option when detaching to terminate processing +before processing all input attachments which should be fairly simple. + +Another compromise, albeit less significant, is locking when attaching and detaching nodes. This +locking is achieved by means of a spinlock in order to reduce memory overhead. A lock is present +for each input bus and output bus. When an output bus is connected to an input bus, both the output +bus and input bus is locked. This locking is specifically for attaching and detaching across +different threads and does not affect `ma_node_graph_read_pcm_frames()` in any way. The locking and +unlocking is mostly self-explanatory, but a slightly less intuitive aspect comes into it when +considering that iterating over attachments must not break as a result of attaching or detaching a +node while iteration is occuring. + +Attaching and detaching are both quite simple. When an output bus of a node is attached to an input +bus of another node, it's added to a linked list. Basically, an input bus is a linked list, where +each item in the list is and output bus. We have some intentional (and convenient) restrictions on +what can done with the linked list in order to simplify the implementation. First of all, whenever +something needs to iterate over the list, it must do so in a forward direction. Backwards iteration +is not supported. Also, items can only be added to the start of the list. + +The linked list is a doubly-linked list where each item in the list (an output bus) holds a pointer +to the next item in the list, and another to the previous item. A pointer to the previous item is +only required for fast detachment of the node - it is never used in iteration. This is an +important property because it means from the perspective of iteration, attaching and detaching of +an item can be done with a single atomic assignment. This is exploited by both the attachment and +detachment process. When attaching the node, the first thing that is done is the setting of the +local "next" and "previous" pointers of the node. After that, the item is "attached" to the list +by simply performing an atomic exchange with the head pointer. After that, the node is "attached" +to the list from the perspective of iteration. Even though the "previous" pointer of the next item +hasn't yet been set, from the perspective of iteration it's been attached because iteration will +only be happening in a forward direction which means the "previous" pointer won't actually ever get +used. The same general process applies to detachment. See `ma_node_attach_output_bus()` and +`ma_node_detach_output_bus()` for the implementation of this mechanism. + + + +8. Decoding =========== -The `ma_decoder` API is used for reading audio files. Decoders are completely decoupled from devices and can be used independently. The following formats are -supported: +The `ma_decoder` API is used for reading audio files. Decoders are completely decoupled from +devices and can be used independently. The following formats are supported: +---------+------------------+----------+ | Format | Decoding Backend | Built-In | @@ -473,7 +2403,8 @@ supported: | Vorbis | stb_vorbis | No | +---------+------------------+----------+ -Vorbis is supported via stb_vorbis which can be enabled by including the header section before the implementation of miniaudio, like the following: +Vorbis is supported via stb_vorbis which can be enabled by including the header section before the +implementation of miniaudio, like the following: ```c #define STB_VORBIS_HEADER_ONLY @@ -489,8 +2420,9 @@ Vorbis is supported via stb_vorbis which can be enabled by including the header A copy of stb_vorbis is included in the "extras" folder in the miniaudio repository (https://github.com/mackron/miniaudio). -Built-in decoders are amalgamated into the implementation section of miniaudio. You can disable the built-in decoders by specifying one or more of the -following options before the miniaudio implementation: +Built-in decoders are amalgamated into the implementation section of miniaudio. You can disable the +built-in decoders by specifying one or more of the following options before the miniaudio +implementation: ```c #define MA_NO_WAV @@ -498,10 +2430,12 @@ following options before the miniaudio implementation: #define MA_NO_FLAC ``` -Disabling built-in decoding libraries is useful if you use these libraries independantly of the `ma_decoder` API. +Disabling built-in decoding libraries is useful if you use these libraries independantly of the +`ma_decoder` API. -A decoder can be initialized from a file with `ma_decoder_init_file()`, a block of memory with `ma_decoder_init_memory()`, or from data delivered via callbacks -with `ma_decoder_init()`. Here is an example for loading a decoder from a file: +A decoder can be initialized from a file with `ma_decoder_init_file()`, a block of memory with +`ma_decoder_init_memory()`, or from data delivered via callbacks with `ma_decoder_init()`. Here is +an example for loading a decoder from a file: ```c ma_decoder decoder; @@ -515,20 +2449,23 @@ with `ma_decoder_init()`. Here is an example for loading a decoder from a file: ma_decoder_uninit(&decoder); ``` -When initializing a decoder, you can optionally pass in a pointer to a `ma_decoder_config` object (the `NULL` argument in the example above) which allows you -to configure the output format, channel count, sample rate and channel map: +When initializing a decoder, you can optionally pass in a pointer to a `ma_decoder_config` object +(the `NULL` argument in the example above) which allows you to configure the output format, channel +count, sample rate and channel map: ```c ma_decoder_config config = ma_decoder_config_init(ma_format_f32, 2, 48000); ``` -When passing in `NULL` for decoder config in `ma_decoder_init*()`, the output format will be the same as that defined by the decoding backend. +When passing in `NULL` for decoder config in `ma_decoder_init*()`, the output format will be the +same as that defined by the decoding backend. -Data is read from the decoder as PCM frames. This will return the number of PCM frames actually read. If the return value is less than the requested number of -PCM frames it means you've reached the end: +Data is read from the decoder as PCM frames. This will output the number of PCM frames actually +read. If this is less than the requested number of PCM frames it means you've reached the end. The +return value will be `MA_AT_END` if no samples have been read and the end has been reached. ```c - ma_uint64 framesRead = ma_decoder_read_pcm_frames(pDecoder, pFrames, framesToRead); + ma_result result = ma_decoder_read_pcm_frames(pDecoder, pFrames, framesToRead, &framesRead); if (framesRead < framesToRead) { // Reached the end. } @@ -549,8 +2486,10 @@ If you want to loop back to the start, you can simply seek back to the first PCM ma_decoder_seek_to_pcm_frame(pDecoder, 0); ``` -When loading a decoder, miniaudio uses a trial and error technique to find the appropriate decoding backend. This can be unnecessarily inefficient if the type -is already known. In this case you can use `encodingFormat` variable in the device config to specify a specific encoding format you want to decode: +When loading a decoder, miniaudio uses a trial and error technique to find the appropriate decoding +backend. This can be unnecessarily inefficient if the type is already known. In this case you can +use `encodingFormat` variable in the device config to specify a specific encoding format you want +to decode: ```c decoderConfig.encodingFormat = ma_encoding_format_wav; @@ -558,24 +2497,95 @@ is already known. In this case you can use `encodingFormat` variable in the devi See the `ma_encoding_format` enum for possible encoding formats. -The `ma_decoder_init_file()` API will try using the file extension to determine which decoding backend to prefer. +The `ma_decoder_init_file()` API will try using the file extension to determine which decoding +backend to prefer. + + +8.1. Custom Decoders +-------------------- +It's possible to implement a custom decoder and plug it into miniaudio. This is extremely useful +when you want to use the `ma_decoder` API, but need to support an encoding format that's not one of +the stock formats supported by miniaudio. This can be put to particularly good use when using the +`ma_engine` and/or `ma_resource_manager` APIs because they use `ma_decoder` internally. If, for +example, you wanted to support Opus, you can do so with a custom decoder (there if a reference +Opus decoder in the "extras" folder of the miniaudio repository which uses libopus + libopusfile). + +A custom decoder must implement a data source. A vtable called `ma_decoding_backend_vtable` needs +to be implemented which is then passed into the decoder config: + + ```c + ma_decoding_backend_vtable* pCustomBackendVTables[] = + { + &g_ma_decoding_backend_vtable_libvorbis, + &g_ma_decoding_backend_vtable_libopus + }; + + ... + + decoderConfig = ma_decoder_config_init_default(); + decoderConfig.pCustomBackendUserData = NULL; + decoderConfig.ppCustomBackendVTables = pCustomBackendVTables; + decoderConfig.customBackendCount = sizeof(pCustomBackendVTables) / sizeof(pCustomBackendVTables[0]); + ``` + +The `ma_decoding_backend_vtable` vtable has the following functions: + + ``` + onInit + onInitFile + onInitFileW + onInitMemory + onUninit + ``` + +There are only two functions that must be implemented - `onInit` and `onUninit`. The other +functions can be implemented for a small optimization for loading from a file path or memory. If +these are not specified, miniaudio will deal with it for you via a generic implementation. + +When you initialize a custom data source (by implementing the `onInit` function in the vtable) you +will need to output a pointer to a `ma_data_source` which implements your custom decoder. See the +section about data sources for details on how to implemen this. Alternatively, see the +"custom_decoders" example in the miniaudio repository. + +The `onInit` function takes a pointer to some callbacks for the purpose of reading raw audio data +from some abitrary source. You'll use these functions to read from the raw data and perform the +decoding. When you call them, you will pass in the `pReadSeekTellUserData` pointer to the relevant +parameter. + +The `pConfig` parameter in `onInit` can be used to configure the backend if appropriate. It's only +used as a hint and can be ignored. However, if any of the properties are relevant to your decoder, +an optimal implementation will handle the relevant properties appropriately. + +If memory allocation is required, it should be done so via the specified allocation callbacks if +possible (the `pAllocationCallbacks` parameter). + +If an error occurs when initializing the decoder, you should leave `ppBackend` unset, or set to +NULL, and make sure everything is cleaned up appropriately and an appropriate result code returned. +When multiple custom backends are specified, miniaudio will cycle through the vtables in the order +they're listed in the array that's passed into the decoder config so it's important that your +initialization routine is clean. + +When a decoder is uninitialized, the `onUninit` callback will be fired which will give you an +opportunity to clean up and internal data. -5. Encoding +9. Encoding =========== -The `ma_encoding` API is used for writing audio files. The only supported output format is WAV which is achieved via dr_wav which is amalgamated into the -implementation section of miniaudio. This can be disabled by specifying the following option before the implementation of miniaudio: +The `ma_encoding` API is used for writing audio files. The only supported output format is WAV +which is achieved via dr_wav which is amalgamated into the implementation section of miniaudio. +This can be disabled by specifying the following option before the implementation of miniaudio: ```c #define MA_NO_WAV ``` -An encoder can be initialized to write to a file with `ma_encoder_init_file()` or from data delivered via callbacks with `ma_encoder_init()`. Below is an -example for initializing an encoder to output to a file. +An encoder can be initialized to write to a file with `ma_encoder_init_file()` or from data +delivered via callbacks with `ma_encoder_init()`. Below is an example for initializing an encoder +to output to a file. ```c - ma_encoder_config config = ma_encoder_config_init(ma_resource_format_wav, FORMAT, CHANNELS, SAMPLE_RATE); + ma_encoder_config config = ma_encoder_config_init(ma_encoding_format_wav, FORMAT, CHANNELS, SAMPLE_RATE); ma_encoder encoder; ma_result result = ma_encoder_init_file("my_file.wav", &config, &encoder); if (result != MA_SUCCESS) { @@ -587,17 +2597,20 @@ example for initializing an encoder to output to a file. ma_encoder_uninit(&encoder); ``` -When initializing an encoder you must specify a config which is initialized with `ma_encoder_config_init()`. Here you must specify the file type, the output -sample format, output channel count and output sample rate. The following file types are supported: +When initializing an encoder you must specify a config which is initialized with +`ma_encoder_config_init()`. Here you must specify the file type, the output sample format, output +channel count and output sample rate. The following file types are supported: +------------------------+-------------+ | Enum | Description | +------------------------+-------------+ - | ma_resource_format_wav | WAV | + | ma_encoding_format_wav | WAV | +------------------------+-------------+ -If the format, channel count or sample rate is not supported by the output file type an error will be returned. The encoder will not perform data conversion so -you will need to convert it before outputting any audio data. To output audio data, use `ma_encoder_write_pcm_frames()`, like in the example below: +If the format, channel count or sample rate is not supported by the output file type an error will +be returned. The encoder will not perform data conversion so you will need to convert it before +outputting any audio data. To output audio data, use `ma_encoder_write_pcm_frames()`, like in the +example below: ```c framesWritten = ma_encoder_write_pcm_frames(&encoder, pPCMFramesToWrite, framesToWrite); @@ -606,21 +2619,25 @@ you will need to convert it before outputting any audio data. To output audio da Encoders must be uninitialized with `ma_encoder_uninit()`. -6. Data Conversion -================== -A data conversion API is included with miniaudio which supports the majority of data conversion requirements. This supports conversion between sample formats, -channel counts (with channel mapping) and sample rates. + +10. Data Conversion +=================== +A data conversion API is included with miniaudio which supports the majority of data conversion +requirements. This supports conversion between sample formats, channel counts (with channel +mapping) and sample rates. -6.1. Sample Format Conversion ------------------------------ -Conversion between sample formats is achieved with the `ma_pcm_*_to_*()`, `ma_pcm_convert()` and `ma_convert_pcm_frames_format()` APIs. Use `ma_pcm_*_to_*()` -to convert between two specific formats. Use `ma_pcm_convert()` to convert based on a `ma_format` variable. Use `ma_convert_pcm_frames_format()` to convert -PCM frames where you want to specify the frame count and channel count as a variable instead of the total sample count. +10.1. Sample Format Conversion +------------------------------ +Conversion between sample formats is achieved with the `ma_pcm_*_to_*()`, `ma_pcm_convert()` and +`ma_convert_pcm_frames_format()` APIs. Use `ma_pcm_*_to_*()` to convert between two specific +formats. Use `ma_pcm_convert()` to convert based on a `ma_format` variable. Use +`ma_convert_pcm_frames_format()` to convert PCM frames where you want to specify the frame count +and channel count as a variable instead of the total sample count. -6.1.1. Dithering ----------------- +10.1.1. Dithering +----------------- Dithering can be set using the ditherMode parameter. The different dithering modes include the following, in order of efficiency: @@ -633,8 +2650,9 @@ The different dithering modes include the following, in order of efficiency: | Triangle | ma_dither_mode_triangle | +-----------+--------------------------+ -Note that even if the dither mode is set to something other than `ma_dither_mode_none`, it will be ignored for conversions where dithering is not needed. -Dithering is available for the following conversions: +Note that even if the dither mode is set to something other than `ma_dither_mode_none`, it will be +ignored for conversions where dithering is not needed. Dithering is available for the following +conversions: ``` s16 -> u8 @@ -646,14 +2664,16 @@ Dithering is available for the following conversions: f32 -> s16 ``` -Note that it is not an error to pass something other than ma_dither_mode_none for conversions where dither is not used. It will just be ignored. +Note that it is not an error to pass something other than ma_dither_mode_none for conversions where +dither is not used. It will just be ignored. -6.2. Channel Conversion ------------------------ -Channel conversion is used for channel rearrangement and conversion from one channel count to another. The `ma_channel_converter` API is used for channel -conversion. Below is an example of initializing a simple channel converter which converts from mono to stereo. +10.2. Channel Conversion +------------------------ +Channel conversion is used for channel rearrangement and conversion from one channel count to +another. The `ma_channel_converter` API is used for channel conversion. Below is an example of +initializing a simple channel converter which converts from mono to stereo. ```c ma_channel_converter_config config = ma_channel_converter_config_init( @@ -664,7 +2684,7 @@ conversion. Below is an example of initializing a simple channel converter which NULL, // Output channel map ma_channel_mix_mode_default); // The mixing algorithm to use when combining channels. - result = ma_channel_converter_init(&config, &converter); + result = ma_channel_converter_init(&config, NULL, &converter); if (result != MA_SUCCESS) { // Error. } @@ -679,34 +2699,43 @@ To perform the conversion simply call `ma_channel_converter_process_pcm_frames() } ``` -It is up to the caller to ensure the output buffer is large enough to accomodate the new PCM frames. +It is up to the caller to ensure the output buffer is large enough to accomodate the new PCM +frames. Input and output PCM frames are always interleaved. Deinterleaved layouts are not supported. -6.2.1. Channel Mapping ----------------------- -In addition to converting from one channel count to another, like the example above, the channel converter can also be used to rearrange channels. When -initializing the channel converter, you can optionally pass in channel maps for both the input and output frames. If the channel counts are the same, and each -channel map contains the same channel positions with the exception that they're in a different order, a simple shuffling of the channels will be performed. If, -however, there is not a 1:1 mapping of channel positions, or the channel counts differ, the input channels will be mixed based on a mixing mode which is -specified when initializing the `ma_channel_converter_config` object. +10.2.1. Channel Mapping +----------------------- +In addition to converting from one channel count to another, like the example above, the channel +converter can also be used to rearrange channels. When initializing the channel converter, you can +optionally pass in channel maps for both the input and output frames. If the channel counts are the +same, and each channel map contains the same channel positions with the exception that they're in +a different order, a simple shuffling of the channels will be performed. If, however, there is not +a 1:1 mapping of channel positions, or the channel counts differ, the input channels will be mixed +based on a mixing mode which is specified when initializing the `ma_channel_converter_config` +object. -When converting from mono to multi-channel, the mono channel is simply copied to each output channel. When going the other way around, the audio of each output -channel is simply averaged and copied to the mono channel. +When converting from mono to multi-channel, the mono channel is simply copied to each output +channel. When going the other way around, the audio of each output channel is simply averaged and +copied to the mono channel. -In more complicated cases blending is used. The `ma_channel_mix_mode_simple` mode will drop excess channels and silence extra channels. For example, converting -from 4 to 2 channels, the 3rd and 4th channels will be dropped, whereas converting from 2 to 4 channels will put silence into the 3rd and 4th channels. +In more complicated cases blending is used. The `ma_channel_mix_mode_simple` mode will drop excess +channels and silence extra channels. For example, converting from 4 to 2 channels, the 3rd and 4th +channels will be dropped, whereas converting from 2 to 4 channels will put silence into the 3rd and +4th channels. -The `ma_channel_mix_mode_rectangle` mode uses spacial locality based on a rectangle to compute a simple distribution between input and output. Imagine sitting -in the middle of a room, with speakers on the walls representing channel positions. The MA_CHANNEL_FRONT_LEFT position can be thought of as being in the corner -of the front and left walls. +The `ma_channel_mix_mode_rectangle` mode uses spacial locality based on a rectangle to compute a +simple distribution between input and output. Imagine sitting in the middle of a room, with +speakers on the walls representing channel positions. The `MA_CHANNEL_FRONT_LEFT` position can be +thought of as being in the corner of the front and left walls. -Finally, the `ma_channel_mix_mode_custom_weights` mode can be used to use custom user-defined weights. Custom weights can be passed in as the last parameter of +Finally, the `ma_channel_mix_mode_custom_weights` mode can be used to use custom user-defined +weights. Custom weights can be passed in as the last parameter of `ma_channel_converter_config_init()`. -Predefined channel maps can be retrieved with `ma_get_standard_channel_map()`. This takes a `ma_standard_channel_map` enum as it's first parameter, which can -be one of the following: +Predefined channel maps can be retrieved with `ma_channel_map_init_standard()`. This takes a +`ma_standard_channel_map` enum as it's first parameter, which can be one of the following: +-----------------------------------+-----------------------------------------------------------+ | Name | Description | @@ -778,9 +2807,10 @@ Below are the channel maps used by default in miniaudio (`ma_standard_channel_ma -6.3. Resampling ---------------- -Resampling is achieved with the `ma_resampler` object. To create a resampler object, do something like the following: +10.3. Resampling +---------------- +Resampling is achieved with the `ma_resampler` object. To create a resampler object, do something +like the following: ```c ma_resampler_config config = ma_resampler_config_init( @@ -817,104 +2847,128 @@ The following example shows how data can be processed // number of output frames written. ``` -To initialize the resampler you first need to set up a config (`ma_resampler_config`) with `ma_resampler_config_init()`. You need to specify the sample format -you want to use, the number of channels, the input and output sample rate, and the algorithm. +To initialize the resampler you first need to set up a config (`ma_resampler_config`) with +`ma_resampler_config_init()`. You need to specify the sample format you want to use, the number of +channels, the input and output sample rate, and the algorithm. -The sample format can be either `ma_format_s16` or `ma_format_f32`. If you need a different format you will need to perform pre- and post-conversions yourself -where necessary. Note that the format is the same for both input and output. The format cannot be changed after initialization. +The sample format can be either `ma_format_s16` or `ma_format_f32`. If you need a different format +you will need to perform pre- and post-conversions yourself where necessary. Note that the format +is the same for both input and output. The format cannot be changed after initialization. -The resampler supports multiple channels and is always interleaved (both input and output). The channel count cannot be changed after initialization. +The resampler supports multiple channels and is always interleaved (both input and output). The +channel count cannot be changed after initialization. -The sample rates can be anything other than zero, and are always specified in hertz. They should be set to something like 44100, etc. The sample rate is the -only configuration property that can be changed after initialization. +The sample rates can be anything other than zero, and are always specified in hertz. They should be +set to something like 44100, etc. The sample rate is the only configuration property that can be +changed after initialization. -The miniaudio resampler supports multiple algorithms: +The miniaudio resampler has built-in support for the following algorithms: +-----------+------------------------------+ | Algorithm | Enum Token | +-----------+------------------------------+ | Linear | ma_resample_algorithm_linear | - | Speex | ma_resample_algorithm_speex | + | Custom | ma_resample_algorithm_custom | +-----------+------------------------------+ -Because Speex is not public domain it is strictly opt-in and the code is stored in separate files. if you opt-in to the Speex backend you will need to consider -it's license, the text of which can be found in it's source files in "extras/speex_resampler". Details on how to opt-in to the Speex resampler is explained in -the Speex Resampler section below. - The algorithm cannot be changed after initialization. -Processing always happens on a per PCM frame basis and always assumes interleaved input and output. De-interleaved processing is not supported. To process -frames, use `ma_resampler_process_pcm_frames()`. On input, this function takes the number of output frames you can fit in the output buffer and the number of -input frames contained in the input buffer. On output these variables contain the number of output frames that were written to the output buffer and the -number of input frames that were consumed in the process. You can pass in NULL for the input buffer in which case it will be treated as an infinitely large -buffer of zeros. The output buffer can also be NULL, in which case the processing will be treated as seek. +Processing always happens on a per PCM frame basis and always assumes interleaved input and output. +De-interleaved processing is not supported. To process frames, use +`ma_resampler_process_pcm_frames()`. On input, this function takes the number of output frames you +can fit in the output buffer and the number of input frames contained in the input buffer. On +output these variables contain the number of output frames that were written to the output buffer +and the number of input frames that were consumed in the process. You can pass in NULL for the +input buffer in which case it will be treated as an infinitely large buffer of zeros. The output +buffer can also be NULL, in which case the processing will be treated as seek. -The sample rate can be changed dynamically on the fly. You can change this with explicit sample rates with `ma_resampler_set_rate()` and also with a decimal -ratio with `ma_resampler_set_rate_ratio()`. The ratio is in/out. +The sample rate can be changed dynamically on the fly. You can change this with explicit sample +rates with `ma_resampler_set_rate()` and also with a decimal ratio with +`ma_resampler_set_rate_ratio()`. The ratio is in/out. -Sometimes it's useful to know exactly how many input frames will be required to output a specific number of frames. You can calculate this with -`ma_resampler_get_required_input_frame_count()`. Likewise, it's sometimes useful to know exactly how many frames would be output given a certain number of -input frames. You can do this with `ma_resampler_get_expected_output_frame_count()`. +Sometimes it's useful to know exactly how many input frames will be required to output a specific +number of frames. You can calculate this with `ma_resampler_get_required_input_frame_count()`. +Likewise, it's sometimes useful to know exactly how many frames would be output given a certain +number of input frames. You can do this with `ma_resampler_get_expected_output_frame_count()`. -Due to the nature of how resampling works, the resampler introduces some latency. This can be retrieved in terms of both the input rate and the output rate -with `ma_resampler_get_input_latency()` and `ma_resampler_get_output_latency()`. +Due to the nature of how resampling works, the resampler introduces some latency. This can be +retrieved in terms of both the input rate and the output rate with +`ma_resampler_get_input_latency()` and `ma_resampler_get_output_latency()`. -6.3.1. Resampling Algorithms ----------------------------- -The choice of resampling algorithm depends on your situation and requirements. The linear resampler is the most efficient and has the least amount of latency, -but at the expense of poorer quality. The Speex resampler is higher quality, but slower with more latency. It also performs several heap allocations internally -for memory management. +10.3.1. Resampling Algorithms +----------------------------- +The choice of resampling algorithm depends on your situation and requirements. -6.3.1.1. Linear Resampling --------------------------- -The linear resampler is the fastest, but comes at the expense of poorer quality. There is, however, some control over the quality of the linear resampler which -may make it a suitable option depending on your requirements. +10.3.1.1. Linear Resampling +--------------------------- +The linear resampler is the fastest, but comes at the expense of poorer quality. There is, however, +some control over the quality of the linear resampler which may make it a suitable option depending +on your requirements. -The linear resampler performs low-pass filtering before or after downsampling or upsampling, depending on the sample rates you're converting between. When -decreasing the sample rate, the low-pass filter will be applied before downsampling. When increasing the rate it will be performed after upsampling. By default -a fourth order low-pass filter will be applied. This can be configured via the `lpfOrder` configuration variable. Setting this to 0 will disable filtering. +The linear resampler performs low-pass filtering before or after downsampling or upsampling, +depending on the sample rates you're converting between. When decreasing the sample rate, the +low-pass filter will be applied before downsampling. When increasing the rate it will be performed +after upsampling. By default a fourth order low-pass filter will be applied. This can be configured +via the `lpfOrder` configuration variable. Setting this to 0 will disable filtering. -The low-pass filter has a cutoff frequency which defaults to half the sample rate of the lowest of the input and output sample rates (Nyquist Frequency). This -can be controlled with the `lpfNyquistFactor` config variable. This defaults to 1, and should be in the range of 0..1, although a value of 0 does not make -sense and should be avoided. A value of 1 will use the Nyquist Frequency as the cutoff. A value of 0.5 will use half the Nyquist Frequency as the cutoff, etc. -Values less than 1 will result in more washed out sound due to more of the higher frequencies being removed. This config variable has no impact on performance -and is a purely perceptual configuration. +The low-pass filter has a cutoff frequency which defaults to half the sample rate of the lowest of +the input and output sample rates (Nyquist Frequency). -The API for the linear resampler is the same as the main resampler API, only it's called `ma_linear_resampler`. +The API for the linear resampler is the same as the main resampler API, only it's called +`ma_linear_resampler`. -6.3.1.2. Speex Resampling +10.3.2. Custom Resamplers ------------------------- -The Speex resampler is made up of third party code which is released under the BSD license. Because it is licensed differently to miniaudio, which is public -domain, it is strictly opt-in and all of it's code is stored in separate files. If you opt-in to the Speex resampler you must consider the license text in it's -source files. To opt-in, you must first `#include` the following file before the implementation of miniaudio.h: +You can implement a custom resampler by using the `ma_resample_algorithm_custom` resampling +algorithm and setting a vtable in the resampler config: ```c - #include "extras/speex_resampler/ma_speex_resampler.h" + ma_resampler_config config = ma_resampler_config_init(..., ma_resample_algorithm_custom); + config.pBackendVTable = &g_customResamplerVTable; ``` -Both the header and implementation is contained within the same file. The implementation can be included in your program like so: +Custom resamplers are useful if the stock algorithms are not appropriate for your use case. You +need to implement the required functions in `ma_resampling_backend_vtable`. Note that not all +functions in the vtable need to be implemented, but if it's possible to implement, they should be. - ```c - #define MINIAUDIO_SPEEX_RESAMPLER_IMPLEMENTATION - #include "extras/speex_resampler/ma_speex_resampler.h" - ``` +You can use the `ma_linear_resampler` object for an example on how to implement the vtable. The +`onGetHeapSize` callback is used to calculate the size of any internal heap allocation the custom +resampler will need to make given the supplied config. When you initialize the resampler via the +`onInit` callback, you'll be given a pointer to a heap allocation which is where you should store +the heap allocated data. You should not free this data in `onUninit` because miniaudio will manage +it for you. -Note that even if you opt-in to the Speex backend, miniaudio won't use it unless you explicitly ask for it in the respective config of the object you are -initializing. If you try to use the Speex resampler without opting in, initialization of the `ma_resampler` object will fail with `MA_NO_BACKEND`. +The `onProcess` callback is where the actual resampling takes place. On input, `pFrameCountIn` +points to a variable containing the number of frames in the `pFramesIn` buffer and +`pFrameCountOut` points to a variable containing the capacity in frames of the `pFramesOut` buffer. +On output, `pFrameCountIn` should be set to the number of input frames that were fully consumed, +whereas `pFrameCountOut` should be set to the number of frames that were written to `pFramesOut`. -The only configuration option to consider with the Speex resampler is the `speex.quality` config variable. This is a value between 0 and 10, with 0 being -the fastest with the poorest quality and 10 being the slowest with the highest quality. The default value is 3. +The `onSetRate` callback is optional and is used for dynamically changing the sample rate. If +dynamic rate changes are not supported, you can set this callback to NULL. + +The `onGetInputLatency` and `onGetOutputLatency` functions are used for retrieving the latency in +input and output rates respectively. These can be NULL in which case latency calculations will be +assumed to be NULL. + +The `onGetRequiredInputFrameCount` callback is used to give miniaudio a hint as to how many input +frames are required to be available to produce the given number of output frames. Likewise, the +`onGetExpectedOutputFrameCount` callback is used to determine how many output frames will be +produced given the specified number of input frames. miniaudio will use these as a hint, but they +are optional and can be set to NULL if you're unable to implement them. -6.4. General Data Conversion ----------------------------- -The `ma_data_converter` API can be used to wrap sample format conversion, channel conversion and resampling into one operation. This is what miniaudio uses -internally to convert between the format requested when the device was initialized and the format of the backend's native device. The API for general data -conversion is very similar to the resampling API. Create a `ma_data_converter` object like this: +10.4. General Data Conversion +----------------------------- +The `ma_data_converter` API can be used to wrap sample format conversion, channel conversion and +resampling into one operation. This is what miniaudio uses internally to convert between the format +requested when the device was initialized and the format of the backend's native device. The API +for general data conversion is very similar to the resampling API. Create a `ma_data_converter` +object like this: ```c ma_data_converter_config config = ma_data_converter_config_init( @@ -927,14 +2981,15 @@ conversion is very similar to the resampling API. Create a `ma_data_converter` o ); ma_data_converter converter; - ma_result result = ma_data_converter_init(&config, &converter); + ma_result result = ma_data_converter_init(&config, NULL, &converter); if (result != MA_SUCCESS) { // An error occurred... } ``` -In the example above we use `ma_data_converter_config_init()` to initialize the config, however there's many more properties that can be configured, such as -channel maps and resampling quality. Something like the following may be more suitable depending on your requirements: +In the example above we use `ma_data_converter_config_init()` to initialize the config, however +there's many more properties that can be configured, such as channel maps and resampling quality. +Something like the following may be more suitable depending on your requirements: ```c ma_data_converter_config config = ma_data_converter_config_init_default(); @@ -944,14 +2999,14 @@ channel maps and resampling quality. Something like the following may be more su config.channelsOut = outputChannels; config.sampleRateIn = inputSampleRate; config.sampleRateOut = outputSampleRate; - ma_get_standard_channel_map(ma_standard_channel_map_flac, config.channelCountIn, config.channelMapIn); + ma_channel_map_init_standard(ma_standard_channel_map_flac, config.channelMapIn, sizeof(config.channelMapIn)/sizeof(config.channelMapIn[0]), config.channelCountIn); config.resampling.linear.lpfOrder = MA_MAX_FILTER_ORDER; ``` Do the following to uninitialize the data converter: ```c - ma_data_converter_uninit(&converter); + ma_data_converter_uninit(&converter, NULL); ``` The following example shows how data can be processed @@ -968,33 +3023,42 @@ The following example shows how data can be processed // of output frames written. ``` -The data converter supports multiple channels and is always interleaved (both input and output). The channel count cannot be changed after initialization. +The data converter supports multiple channels and is always interleaved (both input and output). +The channel count cannot be changed after initialization. -Sample rates can be anything other than zero, and are always specified in hertz. They should be set to something like 44100, etc. The sample rate is the only -configuration property that can be changed after initialization, but only if the `resampling.allowDynamicSampleRate` member of `ma_data_converter_config` is -set to `MA_TRUE`. To change the sample rate, use `ma_data_converter_set_rate()` or `ma_data_converter_set_rate_ratio()`. The ratio must be in/out. The -resampling algorithm cannot be changed after initialization. +Sample rates can be anything other than zero, and are always specified in hertz. They should be set +to something like 44100, etc. The sample rate is the only configuration property that can be +changed after initialization, but only if the `resampling.allowDynamicSampleRate` member of +`ma_data_converter_config` is set to `MA_TRUE`. To change the sample rate, use +`ma_data_converter_set_rate()` or `ma_data_converter_set_rate_ratio()`. The ratio must be in/out. +The resampling algorithm cannot be changed after initialization. -Processing always happens on a per PCM frame basis and always assumes interleaved input and output. De-interleaved processing is not supported. To process -frames, use `ma_data_converter_process_pcm_frames()`. On input, this function takes the number of output frames you can fit in the output buffer and the number -of input frames contained in the input buffer. On output these variables contain the number of output frames that were written to the output buffer and the -number of input frames that were consumed in the process. You can pass in NULL for the input buffer in which case it will be treated as an infinitely large -buffer of zeros. The output buffer can also be NULL, in which case the processing will be treated as seek. +Processing always happens on a per PCM frame basis and always assumes interleaved input and output. +De-interleaved processing is not supported. To process frames, use +`ma_data_converter_process_pcm_frames()`. On input, this function takes the number of output frames +you can fit in the output buffer and the number of input frames contained in the input buffer. On +output these variables contain the number of output frames that were written to the output buffer +and the number of input frames that were consumed in the process. You can pass in NULL for the +input buffer in which case it will be treated as an infinitely large +buffer of zeros. The output buffer can also be NULL, in which case the processing will be treated +as seek. -Sometimes it's useful to know exactly how many input frames will be required to output a specific number of frames. You can calculate this with -`ma_data_converter_get_required_input_frame_count()`. Likewise, it's sometimes useful to know exactly how many frames would be output given a certain number of -input frames. You can do this with `ma_data_converter_get_expected_output_frame_count()`. +Sometimes it's useful to know exactly how many input frames will be required to output a specific +number of frames. You can calculate this with `ma_data_converter_get_required_input_frame_count()`. +Likewise, it's sometimes useful to know exactly how many frames would be output given a certain +number of input frames. You can do this with `ma_data_converter_get_expected_output_frame_count()`. -Due to the nature of how resampling works, the data converter introduces some latency if resampling is required. This can be retrieved in terms of both the -input rate and the output rate with `ma_data_converter_get_input_latency()` and `ma_data_converter_get_output_latency()`. +Due to the nature of how resampling works, the data converter introduces some latency if resampling +is required. This can be retrieved in terms of both the input rate and the output rate with +`ma_data_converter_get_input_latency()` and `ma_data_converter_get_output_latency()`. -7. Filtering -============ +11. Filtering +============= -7.1. Biquad Filtering ---------------------- +11.1. Biquad Filtering +---------------------- Biquad filtering is achieved with the `ma_biquad` API. Example: ```c @@ -1009,28 +3073,33 @@ Biquad filtering is achieved with the `ma_biquad` API. Example: ma_biquad_process_pcm_frames(&biquad, pFramesOut, pFramesIn, frameCount); ``` -Biquad filtering is implemented using transposed direct form 2. The numerator coefficients are b0, b1 and b2, and the denominator coefficients are a0, a1 and -a2. The a0 coefficient is required and coefficients must not be pre-normalized. +Biquad filtering is implemented using transposed direct form 2. The numerator coefficients are b0, +b1 and b2, and the denominator coefficients are a0, a1 and a2. The a0 coefficient is required and +coefficients must not be pre-normalized. -Supported formats are `ma_format_s16` and `ma_format_f32`. If you need to use a different format you need to convert it yourself beforehand. When using -`ma_format_s16` the biquad filter will use fixed point arithmetic. When using `ma_format_f32`, floating point arithmetic will be used. +Supported formats are `ma_format_s16` and `ma_format_f32`. If you need to use a different format +you need to convert it yourself beforehand. When using `ma_format_s16` the biquad filter will use +fixed point arithmetic. When using `ma_format_f32`, floating point arithmetic will be used. Input and output frames are always interleaved. -Filtering can be applied in-place by passing in the same pointer for both the input and output buffers, like so: +Filtering can be applied in-place by passing in the same pointer for both the input and output +buffers, like so: ```c ma_biquad_process_pcm_frames(&biquad, pMyData, pMyData, frameCount); ``` -If you need to change the values of the coefficients, but maintain the values in the registers you can do so with `ma_biquad_reinit()`. This is useful if you -need to change the properties of the filter while keeping the values of registers valid to avoid glitching. Do not use `ma_biquad_init()` for this as it will -do a full initialization which involves clearing the registers to 0. Note that changing the format or channel count after initialization is invalid and will -result in an error. +If you need to change the values of the coefficients, but maintain the values in the registers you +can do so with `ma_biquad_reinit()`. This is useful if you need to change the properties of the +filter while keeping the values of registers valid to avoid glitching. Do not use +`ma_biquad_init()` for this as it will do a full initialization which involves clearing the +registers to 0. Note that changing the format or channel count after initialization is invalid and +will result in an error. -7.2. Low-Pass Filtering ------------------------ +11.2. Low-Pass Filtering +------------------------ Low-pass filtering is achieved with the following APIs: +---------+------------------------------------------+ @@ -1055,16 +3124,18 @@ Low-pass filter example: ma_lpf_process_pcm_frames(&lpf, pFramesOut, pFramesIn, frameCount); ``` -Supported formats are `ma_format_s16` and` ma_format_f32`. If you need to use a different format you need to convert it yourself beforehand. Input and output -frames are always interleaved. +Supported formats are `ma_format_s16` and` ma_format_f32`. If you need to use a different format +you need to convert it yourself beforehand. Input and output frames are always interleaved. -Filtering can be applied in-place by passing in the same pointer for both the input and output buffers, like so: +Filtering can be applied in-place by passing in the same pointer for both the input and output +buffers, like so: ```c ma_lpf_process_pcm_frames(&lpf, pMyData, pMyData, frameCount); ``` -The maximum filter order is limited to `MA_MAX_FILTER_ORDER` which is set to 8. If you need more, you can chain first and second order filters together. +The maximum filter order is limited to `MA_MAX_FILTER_ORDER` which is set to 8. If you need more, +you can chain first and second order filters together. ```c for (iFilter = 0; iFilter < filterCount; iFilter += 1) { @@ -1072,19 +3143,22 @@ The maximum filter order is limited to `MA_MAX_FILTER_ORDER` which is set to 8. } ``` -If you need to change the configuration of the filter, but need to maintain the state of internal registers you can do so with `ma_lpf_reinit()`. This may be -useful if you need to change the sample rate and/or cutoff frequency dynamically while maintaing smooth transitions. Note that changing the format or channel -count after initialization is invalid and will result in an error. +If you need to change the configuration of the filter, but need to maintain the state of internal +registers you can do so with `ma_lpf_reinit()`. This may be useful if you need to change the sample +rate and/or cutoff frequency dynamically while maintaing smooth transitions. Note that changing the +format or channel count after initialization is invalid and will result in an error. -The `ma_lpf` object supports a configurable order, but if you only need a first order filter you may want to consider using `ma_lpf1`. Likewise, if you only -need a second order filter you can use `ma_lpf2`. The advantage of this is that they're lighter weight and a bit more efficient. +The `ma_lpf` object supports a configurable order, but if you only need a first order filter you +may want to consider using `ma_lpf1`. Likewise, if you only need a second order filter you can use +`ma_lpf2`. The advantage of this is that they're lighter weight and a bit more efficient. -If an even filter order is specified, a series of second order filters will be processed in a chain. If an odd filter order is specified, a first order filter -will be applied, followed by a series of second order filters in a chain. +If an even filter order is specified, a series of second order filters will be processed in a +chain. If an odd filter order is specified, a first order filter will be applied, followed by a +series of second order filters in a chain. -7.3. High-Pass Filtering ------------------------- +11.3. High-Pass Filtering +------------------------- High-pass filtering is achieved with the following APIs: +---------+-------------------------------------------+ @@ -1095,12 +3169,12 @@ High-pass filtering is achieved with the following APIs: | ma_hpf | High order high-pass filter (Butterworth) | +---------+-------------------------------------------+ -High-pass filters work exactly the same as low-pass filters, only the APIs are called `ma_hpf1`, `ma_hpf2` and `ma_hpf`. See example code for low-pass filters -for example usage. +High-pass filters work exactly the same as low-pass filters, only the APIs are called `ma_hpf1`, +`ma_hpf2` and `ma_hpf`. See example code for low-pass filters for example usage. -7.4. Band-Pass Filtering ------------------------- +11.4. Band-Pass Filtering +------------------------- Band-pass filtering is achieved with the following APIs: +---------+-------------------------------+ @@ -1110,13 +3184,14 @@ Band-pass filtering is achieved with the following APIs: | ma_bpf | High order band-pass filter | +---------+-------------------------------+ -Band-pass filters work exactly the same as low-pass filters, only the APIs are called `ma_bpf2` and `ma_hpf`. See example code for low-pass filters for example -usage. Note that the order for band-pass filters must be an even number which means there is no first order band-pass filter, unlike low-pass and high-pass -filters. +Band-pass filters work exactly the same as low-pass filters, only the APIs are called `ma_bpf2` and +`ma_hpf`. See example code for low-pass filters for example usage. Note that the order for +band-pass filters must be an even number which means there is no first order band-pass filter, +unlike low-pass and high-pass filters. -7.5. Notch Filtering --------------------- +11.5. Notch Filtering +--------------------- Notch filtering is achieved with the following APIs: +-----------+------------------------------------------+ @@ -1126,7 +3201,7 @@ Notch filtering is achieved with the following APIs: +-----------+------------------------------------------+ -7.6. Peaking EQ Filtering +11.6. Peaking EQ Filtering ------------------------- Peaking filtering is achieved with the following APIs: @@ -1137,8 +3212,8 @@ Peaking filtering is achieved with the following APIs: +----------+------------------------------------------+ -7.7. Low Shelf Filtering ------------------------- +11.7. Low Shelf Filtering +------------------------- Low shelf filtering is achieved with the following APIs: +-------------+------------------------------------------+ @@ -1147,11 +3222,12 @@ Low shelf filtering is achieved with the following APIs: | ma_loshelf2 | Second order low shelf filter | +-------------+------------------------------------------+ -Where a high-pass filter is used to eliminate lower frequencies, a low shelf filter can be used to just turn them down rather than eliminate them entirely. +Where a high-pass filter is used to eliminate lower frequencies, a low shelf filter can be used to +just turn them down rather than eliminate them entirely. -7.8. High Shelf Filtering -------------------------- +11.8. High Shelf Filtering +-------------------------- High shelf filtering is achieved with the following APIs: +-------------+------------------------------------------+ @@ -1160,18 +3236,20 @@ High shelf filtering is achieved with the following APIs: | ma_hishelf2 | Second order high shelf filter | +-------------+------------------------------------------+ -The high shelf filter has the same API as the low shelf filter, only you would use `ma_hishelf` instead of `ma_loshelf`. Where a low shelf filter is used to -adjust the volume of low frequencies, the high shelf filter does the same thing for high frequencies. +The high shelf filter has the same API as the low shelf filter, only you would use `ma_hishelf` +instead of `ma_loshelf`. Where a low shelf filter is used to adjust the volume of low frequencies, +the high shelf filter does the same thing for high frequencies. -8. Waveform and Noise Generation -================================ +12. Waveform and Noise Generation +================================= -8.1. Waveforms --------------- -miniaudio supports generation of sine, square, triangle and sawtooth waveforms. This is achieved with the `ma_waveform` API. Example: +12.1. Waveforms +--------------- +miniaudio supports generation of sine, square, triangle and sawtooth waveforms. This is achieved +with the `ma_waveform` API. Example: ```c ma_waveform_config config = ma_waveform_config_init( @@ -1193,11 +3271,12 @@ miniaudio supports generation of sine, square, triangle and sawtooth waveforms. ma_waveform_read_pcm_frames(&waveform, pOutput, frameCount); ``` -The amplitude, frequency, type, and sample rate can be changed dynamically with `ma_waveform_set_amplitude()`, `ma_waveform_set_frequency()`, -`ma_waveform_set_type()`, and `ma_waveform_set_sample_rate()` respectively. +The amplitude, frequency, type, and sample rate can be changed dynamically with +`ma_waveform_set_amplitude()`, `ma_waveform_set_frequency()`, `ma_waveform_set_type()`, and +`ma_waveform_set_sample_rate()` respectively. -You can invert the waveform by setting the amplitude to a negative value. You can use this to control whether or not a sawtooth has a positive or negative -ramp, for example. +You can invert the waveform by setting the amplitude to a negative value. You can use this to +control whether or not a sawtooth has a positive or negative ramp, for example. Below are the supported waveform types: @@ -1212,8 +3291,8 @@ Below are the supported waveform types: -8.2. Noise ----------- +12.2. Noise +----------- miniaudio supports generation of white, pink and Brownian noise via the `ma_noise` API. Example: ```c @@ -1235,13 +3314,16 @@ miniaudio supports generation of white, pink and Brownian noise via the `ma_nois ma_noise_read_pcm_frames(&noise, pOutput, frameCount); ``` -The noise API uses simple LCG random number generation. It supports a custom seed which is useful for things like automated testing requiring reproducibility. -Setting the seed to zero will default to `MA_DEFAULT_LCG_SEED`. +The noise API uses simple LCG random number generation. It supports a custom seed which is useful +for things like automated testing requiring reproducibility. Setting the seed to zero will default +to `MA_DEFAULT_LCG_SEED`. -The amplitude, seed, and type can be changed dynamically with `ma_noise_set_amplitude()`, `ma_noise_set_seed()`, and `ma_noise_set_type()` respectively. +The amplitude, seed, and type can be changed dynamically with `ma_noise_set_amplitude()`, +`ma_noise_set_seed()`, and `ma_noise_set_type()` respectively. -By default, the noise API will use different values for different channels. So, for example, the left side in a stereo stream will be different to the right -side. To instead have each channel use the same random value, set the `duplicateChannels` member of the noise config to true, like so: +By default, the noise API will use different values for different channels. So, for example, the +left side in a stereo stream will be different to the right side. To instead have each channel use +the same random value, set the `duplicateChannels` member of the noise config to true, like so: ```c config.duplicateChannels = MA_TRUE; @@ -1259,10 +3341,11 @@ Below are the supported noise types. -9. Audio Buffers -================ -miniaudio supports reading from a buffer of raw audio data via the `ma_audio_buffer` API. This can read from memory that's managed by the application, but -can also handle the memory management for you internally. Memory management is flexible and should support most use cases. +13. Audio Buffers +================= +miniaudio supports reading from a buffer of raw audio data via the `ma_audio_buffer` API. This can +read from memory that's managed by the application, but can also handle the memory management for +you internally. Memory management is flexible and should support most use cases. Audio buffers are initialised using the standard configuration system used everywhere in miniaudio: @@ -1285,11 +3368,14 @@ Audio buffers are initialised using the standard configuration system used every ma_audio_buffer_uninit(&buffer); ``` -In the example above, the memory pointed to by `pExistingData` will *not* be copied and is how an application can do self-managed memory allocation. If you -would rather make a copy of the data, use `ma_audio_buffer_init_copy()`. To uninitialize the buffer, use `ma_audio_buffer_uninit()`. +In the example above, the memory pointed to by `pExistingData` will *not* be copied and is how an +application can do self-managed memory allocation. If you would rather make a copy of the data, use +`ma_audio_buffer_init_copy()`. To uninitialize the buffer, use `ma_audio_buffer_uninit()`. -Sometimes it can be convenient to allocate the memory for the `ma_audio_buffer` structure and the raw audio data in a contiguous block of memory. That is, -the raw audio data will be located immediately after the `ma_audio_buffer` structure. To do this, use `ma_audio_buffer_alloc_and_init()`: +Sometimes it can be convenient to allocate the memory for the `ma_audio_buffer` structure and the +raw audio data in a contiguous block of memory. That is, the raw audio data will be located +immediately after the `ma_audio_buffer` structure. To do this, use +`ma_audio_buffer_alloc_and_init()`: ```c ma_audio_buffer_config config = ma_audio_buffer_config_init( @@ -1310,13 +3396,18 @@ the raw audio data will be located immediately after the `ma_audio_buffer` struc ma_audio_buffer_uninit_and_free(&buffer); ``` -If you initialize the buffer with `ma_audio_buffer_alloc_and_init()` you should uninitialize it with `ma_audio_buffer_uninit_and_free()`. In the example above, -the memory pointed to by `pExistingData` will be copied into the buffer, which is contrary to the behavior of `ma_audio_buffer_init()`. +If you initialize the buffer with `ma_audio_buffer_alloc_and_init()` you should uninitialize it +with `ma_audio_buffer_uninit_and_free()`. In the example above, the memory pointed to by +`pExistingData` will be copied into the buffer, which is contrary to the behavior of +`ma_audio_buffer_init()`. -An audio buffer has a playback cursor just like a decoder. As you read frames from the buffer, the cursor moves forward. The last parameter (`loop`) can be -used to determine if the buffer should loop. The return value is the number of frames actually read. If this is less than the number of frames requested it -means the end has been reached. This should never happen if the `loop` parameter is set to true. If you want to manually loop back to the start, you can do so -with with `ma_audio_buffer_seek_to_pcm_frame(pAudioBuffer, 0)`. Below is an example for reading data from an audio buffer. +An audio buffer has a playback cursor just like a decoder. As you read frames from the buffer, the +cursor moves forward. The last parameter (`loop`) can be used to determine if the buffer should +loop. The return value is the number of frames actually read. If this is less than the number of +frames requested it means the end has been reached. This should never happen if the `loop` +parameter is set to true. If you want to manually loop back to the start, you can do so with with +`ma_audio_buffer_seek_to_pcm_frame(pAudioBuffer, 0)`. Below is an example for reading data from an +audio buffer. ```c ma_uint64 framesRead = ma_audio_buffer_read_pcm_frames(pAudioBuffer, pFramesOut, desiredFrameCount, isLooping); @@ -1325,8 +3416,8 @@ with with `ma_audio_buffer_seek_to_pcm_frame(pAudioBuffer, 0)`. Below is an exam } ``` -Sometimes you may want to avoid the cost of data movement between the internal buffer and the output buffer. Instead you can use memory mapping to retrieve a -pointer to a segment of data: +Sometimes you may want to avoid the cost of data movement between the internal buffer and the +output buffer. Instead you can use memory mapping to retrieve a pointer to a segment of data: ```c void* pMappedFrames; @@ -1342,23 +3433,30 @@ pointer to a segment of data: } ``` -When you use memory mapping, the read cursor is increment by the frame count passed in to `ma_audio_buffer_unmap()`. If you decide not to process every frame -you can pass in a value smaller than the value returned by `ma_audio_buffer_map()`. The disadvantage to using memory mapping is that it does not handle looping -for you. You can determine if the buffer is at the end for the purpose of looping with `ma_audio_buffer_at_end()` or by inspecting the return value of -`ma_audio_buffer_unmap()` and checking if it equals `MA_AT_END`. You should not treat `MA_AT_END` as an error when returned by `ma_audio_buffer_unmap()`. +When you use memory mapping, the read cursor is increment by the frame count passed in to +`ma_audio_buffer_unmap()`. If you decide not to process every frame you can pass in a value smaller +than the value returned by `ma_audio_buffer_map()`. The disadvantage to using memory mapping is +that it does not handle looping for you. You can determine if the buffer is at the end for the +purpose of looping with `ma_audio_buffer_at_end()` or by inspecting the return value of +`ma_audio_buffer_unmap()` and checking if it equals `MA_AT_END`. You should not treat `MA_AT_END` +as an error when returned by `ma_audio_buffer_unmap()`. -10. Ring Buffers +14. Ring Buffers ================ -miniaudio supports lock free (single producer, single consumer) ring buffers which are exposed via the `ma_rb` and `ma_pcm_rb` APIs. The `ma_rb` API operates -on bytes, whereas the `ma_pcm_rb` operates on PCM frames. They are otherwise identical as `ma_pcm_rb` is just a wrapper around `ma_rb`. +miniaudio supports lock free (single producer, single consumer) ring buffers which are exposed via +the `ma_rb` and `ma_pcm_rb` APIs. The `ma_rb` API operates on bytes, whereas the `ma_pcm_rb` +operates on PCM frames. They are otherwise identical as `ma_pcm_rb` is just a wrapper around +`ma_rb`. -Unlike most other APIs in miniaudio, ring buffers support both interleaved and deinterleaved streams. The caller can also allocate their own backing memory for -the ring buffer to use internally for added flexibility. Otherwise the ring buffer will manage it's internal memory for you. +Unlike most other APIs in miniaudio, ring buffers support both interleaved and deinterleaved +streams. The caller can also allocate their own backing memory for the ring buffer to use +internally for added flexibility. Otherwise the ring buffer will manage it's internal memory for +you. -The examples below use the PCM frame variant of the ring buffer since that's most likely the one you will want to use. To initialize a ring buffer, do -something like the following: +The examples below use the PCM frame variant of the ring buffer since that's most likely the one +you will want to use. To initialize a ring buffer, do something like the following: ```c ma_pcm_rb rb; @@ -1368,39 +3466,53 @@ something like the following: } ``` -The `ma_pcm_rb_init()` function takes the sample format and channel count as parameters because it's the PCM varient of the ring buffer API. For the regular -ring buffer that operates on bytes you would call `ma_rb_init()` which leaves these out and just takes the size of the buffer in bytes instead of frames. The -fourth parameter is an optional pre-allocated buffer and the fifth parameter is a pointer to a `ma_allocation_callbacks` structure for custom memory allocation -routines. Passing in `NULL` for this results in `MA_MALLOC()` and `MA_FREE()` being used. +The `ma_pcm_rb_init()` function takes the sample format and channel count as parameters because +it's the PCM varient of the ring buffer API. For the regular ring buffer that operates on bytes you +would call `ma_rb_init()` which leaves these out and just takes the size of the buffer in bytes +instead of frames. The fourth parameter is an optional pre-allocated buffer and the fifth parameter +is a pointer to a `ma_allocation_callbacks` structure for custom memory allocation routines. +Passing in `NULL` for this results in `MA_MALLOC()` and `MA_FREE()` being used. -Use `ma_pcm_rb_init_ex()` if you need a deinterleaved buffer. The data for each sub-buffer is offset from each other based on the stride. To manage your -sub-buffers you can use `ma_pcm_rb_get_subbuffer_stride()`, `ma_pcm_rb_get_subbuffer_offset()` and `ma_pcm_rb_get_subbuffer_ptr()`. +Use `ma_pcm_rb_init_ex()` if you need a deinterleaved buffer. The data for each sub-buffer is +offset from each other based on the stride. To manage your sub-buffers you can use +`ma_pcm_rb_get_subbuffer_stride()`, `ma_pcm_rb_get_subbuffer_offset()` and +`ma_pcm_rb_get_subbuffer_ptr()`. -Use `ma_pcm_rb_acquire_read()` and `ma_pcm_rb_acquire_write()` to retrieve a pointer to a section of the ring buffer. You specify the number of frames you -need, and on output it will set to what was actually acquired. If the read or write pointer is positioned such that the number of frames requested will require -a loop, it will be clamped to the end of the buffer. Therefore, the number of frames you're given may be less than the number you requested. +Use `ma_pcm_rb_acquire_read()` and `ma_pcm_rb_acquire_write()` to retrieve a pointer to a section +of the ring buffer. You specify the number of frames you need, and on output it will set to what +was actually acquired. If the read or write pointer is positioned such that the number of frames +requested will require a loop, it will be clamped to the end of the buffer. Therefore, the number +of frames you're given may be less than the number you requested. -After calling `ma_pcm_rb_acquire_read()` or `ma_pcm_rb_acquire_write()`, you do your work on the buffer and then "commit" it with `ma_pcm_rb_commit_read()` or -`ma_pcm_rb_commit_write()`. This is where the read/write pointers are updated. When you commit you need to pass in the buffer that was returned by the earlier -call to `ma_pcm_rb_acquire_read()` or `ma_pcm_rb_acquire_write()` and is only used for validation. The number of frames passed to `ma_pcm_rb_commit_read()` and -`ma_pcm_rb_commit_write()` is what's used to increment the pointers, and can be less that what was originally requested. +After calling `ma_pcm_rb_acquire_read()` or `ma_pcm_rb_acquire_write()`, you do your work on the +buffer and then "commit" it with `ma_pcm_rb_commit_read()` or `ma_pcm_rb_commit_write()`. This is +where the read/write pointers are updated. When you commit you need to pass in the buffer that was +returned by the earlier call to `ma_pcm_rb_acquire_read()` or `ma_pcm_rb_acquire_write()` and is +only used for validation. The number of frames passed to `ma_pcm_rb_commit_read()` and +`ma_pcm_rb_commit_write()` is what's used to increment the pointers, and can be less that what was +originally requested. -If you want to correct for drift between the write pointer and the read pointer you can use a combination of `ma_pcm_rb_pointer_distance()`, -`ma_pcm_rb_seek_read()` and `ma_pcm_rb_seek_write()`. Note that you can only move the pointers forward, and you should only move the read pointer forward via -the consumer thread, and the write pointer forward by the producer thread. If there is too much space between the pointers, move the read pointer forward. If +If you want to correct for drift between the write pointer and the read pointer you can use a +combination of `ma_pcm_rb_pointer_distance()`, `ma_pcm_rb_seek_read()` and +`ma_pcm_rb_seek_write()`. Note that you can only move the pointers forward, and you should only +move the read pointer forward via the consumer thread, and the write pointer forward by the +producer thread. If there is too much space between the pointers, move the read pointer forward. If there is too little space between the pointers, move the write pointer forward. -You can use a ring buffer at the byte level instead of the PCM frame level by using the `ma_rb` API. This is exactly the same, only you will use the `ma_rb` -functions instead of `ma_pcm_rb` and instead of frame counts you will pass around byte counts. +You can use a ring buffer at the byte level instead of the PCM frame level by using the `ma_rb` +API. This is exactly the same, only you will use the `ma_rb` functions instead of `ma_pcm_rb` and +instead of frame counts you will pass around byte counts. -The maximum size of the buffer in bytes is `0x7FFFFFFF-(MA_SIMD_ALIGNMENT-1)` due to the most significant bit being used to encode a loop flag and the internally -managed buffers always being aligned to MA_SIMD_ALIGNMENT. +The maximum size of the buffer in bytes is `0x7FFFFFFF-(MA_SIMD_ALIGNMENT-1)` due to the most +significant bit being used to encode a loop flag and the internally managed buffers always being +aligned to `MA_SIMD_ALIGNMENT`. -Note that the ring buffer is only thread safe when used by a single consumer thread and single producer thread. +Note that the ring buffer is only thread safe when used by a single consumer thread and single +producer thread. -11. Backends +15. Backends ============ The following backends are supported by miniaudio. @@ -1426,28 +3538,36 @@ The following backends are supported by miniaudio. Some backends have some nuance details you may want to be aware of. -11.1. WASAPI +15.1. WASAPI ------------ -- Low-latency shared mode will be disabled when using an application-defined sample rate which is different to the device's native sample rate. To work around - this, set `wasapi.noAutoConvertSRC` to true in the device config. This is due to IAudioClient3_InitializeSharedAudioStream() failing when the - `AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM` flag is specified. Setting wasapi.noAutoConvertSRC will result in miniaudio's internal resampler being used instead - which will in turn enable the use of low-latency shared mode. +- Low-latency shared mode will be disabled when using an application-defined sample rate which is + different to the device's native sample rate. To work around this, set `wasapi.noAutoConvertSRC` + to true in the device config. This is due to IAudioClient3_InitializeSharedAudioStream() failing + when the `AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM` flag is specified. Setting wasapi.noAutoConvertSRC + will result in miniaudio's internal resampler being used instead which will in turn enable the + use of low-latency shared mode. -11.2. PulseAudio +15.2. PulseAudio ---------------- - If you experience bad glitching/noise on Arch Linux, consider this fix from the Arch wiki: - https://wiki.archlinux.org/index.php/PulseAudio/Troubleshooting#Glitches,_skips_or_crackling. Alternatively, consider using a different backend such as ALSA. + https://wiki.archlinux.org/index.php/PulseAudio/Troubleshooting#Glitches,_skips_or_crackling. + Alternatively, consider using a different backend such as ALSA. -11.3. Android +15.3. Android ------------- -- To capture audio on Android, remember to add the RECORD_AUDIO permission to your manifest: `` -- With OpenSL|ES, only a single ma_context can be active at any given time. This is due to a limitation with OpenSL|ES. -- With AAudio, only default devices are enumerated. This is due to AAudio not having an enumeration API (devices are enumerated through Java). You can however - perform your own device enumeration through Java and then set the ID in the ma_device_id structure (ma_device_id.aaudio) and pass it to ma_device_init(). -- The backend API will perform resampling where possible. The reason for this as opposed to using miniaudio's built-in resampler is to take advantage of any - potential device-specific optimizations the driver may implement. +- To capture audio on Android, remember to add the RECORD_AUDIO permission to your manifest: + `` +- With OpenSL|ES, only a single ma_context can be active at any given time. This is due to a + limitation with OpenSL|ES. +- With AAudio, only default devices are enumerated. This is due to AAudio not having an enumeration + API (devices are enumerated through Java). You can however perform your own device enumeration + through Java and then set the ID in the ma_device_id structure (ma_device_id.aaudio) and pass it + to ma_device_init(). +- The backend API will perform resampling where possible. The reason for this as opposed to using + miniaudio's built-in resampler is to take advantage of any potential device-specific + optimizations the driver may implement. -11.4. UWP +15.4. UWP --------- - UWP only supports default playback and capture devices. - UWP requires the Microphone capability to be enabled in the application's manifest (Package.appxmanifest): @@ -1461,29 +3581,51 @@ Some backends have some nuance details you may want to be aware of. ``` -11.5. Web Audio / Emscripten +15.5. Web Audio / Emscripten ---------------------------- - You cannot use `-std=c*` compiler flags, nor `-ansi`. This only applies to the Emscripten build. -- The first time a context is initialized it will create a global object called "miniaudio" whose primary purpose is to act as a factory for device objects. -- Currently the Web Audio backend uses ScriptProcessorNode's, but this may need to change later as they've been deprecated. -- Google has implemented a policy in their browsers that prevent automatic media output without first receiving some kind of user input. The following web page - has additional details: https://developers.google.com/web/updates/2017/09/autoplay-policy-changes. Starting the device may fail if you try to start playback - without first handling some kind of user input. +- The first time a context is initialized it will create a global object called "miniaudio" whose + primary purpose is to act as a factory for device objects. +- Currently the Web Audio backend uses ScriptProcessorNode's, but this may need to change later as + they've been deprecated. +- Google has implemented a policy in their browsers that prevent automatic media output without + first receiving some kind of user input. The following web page has additional details: + https://developers.google.com/web/updates/2017/09/autoplay-policy-changes. Starting the device + may fail if you try to start playback without first handling some kind of user input. -12. Miscellaneous Notes +16. Optimization Tips +===================== + +16.1. High Level API +-------------------- +- If a sound does not require doppler or pitch shifting, consider disabling pitching by + initializing the sound with the `MA_SOUND_FLAG_NO_PITCH` flag. +- If a sound does not require spatialization, disable it by initialzing the sound with the + `MA_SOUND_FLAG_NO_SPATIALIZATION` flag. It can be renabled again post-initialization with + `ma_sound_set_spatialization_enabled()`. + + + +17. Miscellaneous Notes ======================= -- Automatic stream routing is enabled on a per-backend basis. Support is explicitly enabled for WASAPI and Core Audio, however other backends such as - PulseAudio may naturally support it, though not all have been tested. -- The contents of the output buffer passed into the data callback will always be pre-initialized to silence unless the `noPreZeroedOutputBuffer` config variable - in `ma_device_config` is set to true, in which case it'll be undefined which will require you to write something to the entire buffer. -- By default miniaudio will automatically clip samples. This only applies when the playback sample format is configured as `ma_format_f32`. If you are doing - clipping yourself, you can disable this overhead by setting `noClip` to true in the device config. -- The sndio backend is currently only enabled on OpenBSD builds. -- The audio(4) backend is supported on OpenBSD, but you may need to disable sndiod before you can use it. +- Automatic stream routing is enabled on a per-backend basis. Support is explicitly enabled for + WASAPI and Core Audio, however other backends such as PulseAudio may naturally support it, though + not all have been tested. +- The contents of the output buffer passed into the data callback will always be pre-initialized to + silence unless the `noPreSilencedOutputBuffer` config variable in `ma_device_config` is set to + true, in which case it'll be undefined which will require you to write something to the entire + buffer. +- By default miniaudio will automatically clip samples. This only applies when the playback sample + format is configured as `ma_format_f32`. If you are doing clipping yourself, you can disable this + overhead by setting `noClip` to true in the device config. - Note that GCC and Clang requires `-msse2`, `-mavx2`, etc. for SIMD optimizations. -- When compiling with VC6 and earlier, decoding is restricted to files less than 2GB in size. This is due to 64-bit file APIs not being available. +- The sndio backend is currently only enabled on OpenBSD builds. +- The audio(4) backend is supported on OpenBSD, but you may need to disable sndiod before you can + use it. +- When compiling with VC6 and earlier, decoding is restricted to files less than 2GB in size. This + is due to 64-bit file APIs not being available. */ #ifndef miniaudio_h @@ -1497,8 +3639,8 @@ extern "C" { #define MA_XSTRINGIFY(x) MA_STRINGIFY(x) #define MA_VERSION_MAJOR 0 -#define MA_VERSION_MINOR 10 -#define MA_VERSION_REVISION 42 +#define MA_VERSION_MINOR 11 +#define MA_VERSION_REVISION 9 #define MA_VERSION_STRING MA_XSTRINGIFY(MA_VERSION_MAJOR) "." MA_XSTRINGIFY(MA_VERSION_MINOR) "." MA_XSTRINGIFY(MA_VERSION_REVISION) #if defined(_MSC_VER) && !defined(__clang__) @@ -1513,66 +3655,55 @@ extern "C" { #pragma GCC diagnostic ignored "-Wc11-extensions" /* anonymous unions are a C11 extension */ #endif #endif + -/* Platform/backend detection. */ -#ifdef _WIN32 - #define MA_WIN32 - #if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_PC_APP || WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP) - #define MA_WIN32_UWP - #else - #define MA_WIN32_DESKTOP - #endif + +#if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__)) || defined(_M_X64) || defined(__ia64) || defined(_M_IA64) || defined(__aarch64__) || defined(_M_ARM64) || defined(__powerpc64__) + #define MA_SIZEOF_PTR 8 #else - #define MA_POSIX - #include /* Unfortunate #include, but needed for pthread_t, pthread_mutex_t and pthread_cond_t types. */ - - #ifdef __unix__ - #define MA_UNIX - #if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) - #define MA_BSD - #endif - #endif - #ifdef __linux__ - #define MA_LINUX - #endif - #ifdef __APPLE__ - #define MA_APPLE - #endif - #ifdef __ANDROID__ - #define MA_ANDROID - #endif - #ifdef __EMSCRIPTEN__ - #define MA_EMSCRIPTEN - #endif + #define MA_SIZEOF_PTR 4 #endif #include /* For size_t. */ /* Sized types. */ -typedef signed char ma_int8; -typedef unsigned char ma_uint8; -typedef signed short ma_int16; -typedef unsigned short ma_uint16; -typedef signed int ma_int32; -typedef unsigned int ma_uint32; -#if defined(_MSC_VER) - typedef signed __int64 ma_int64; - typedef unsigned __int64 ma_uint64; +#if defined(MA_USE_STDINT) + #include + typedef int8_t ma_int8; + typedef uint8_t ma_uint8; + typedef int16_t ma_int16; + typedef uint16_t ma_uint16; + typedef int32_t ma_int32; + typedef uint32_t ma_uint32; + typedef int64_t ma_int64; + typedef uint64_t ma_uint64; #else - #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wlong-long" - #if defined(__clang__) - #pragma GCC diagnostic ignored "-Wc++11-long-long" + typedef signed char ma_int8; + typedef unsigned char ma_uint8; + typedef signed short ma_int16; + typedef unsigned short ma_uint16; + typedef signed int ma_int32; + typedef unsigned int ma_uint32; + #if defined(_MSC_VER) && !defined(__clang__) + typedef signed __int64 ma_int64; + typedef unsigned __int64 ma_uint64; + #else + #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wlong-long" + #if defined(__clang__) + #pragma GCC diagnostic ignored "-Wc++11-long-long" + #endif + #endif + typedef signed long long ma_int64; + typedef unsigned long long ma_uint64; + #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) + #pragma GCC diagnostic pop #endif #endif - typedef signed long long ma_int64; - typedef unsigned long long ma_uint64; - #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) - #pragma GCC diagnostic pop - #endif -#endif -#if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__)) || defined(_M_X64) || defined(__ia64) || defined (_M_IA64) || defined(__aarch64__) || defined(_M_ARM64) || defined(__powerpc64__) +#endif /* MA_USE_STDINT */ + +#if MA_SIZEOF_PTR == 8 typedef ma_uint64 ma_uintptr; #else typedef ma_uint32 ma_uintptr; @@ -1603,6 +3734,58 @@ typedef ma_uint16 wchar_t; #endif +/* Platform/backend detection. */ +#ifdef _WIN32 + #define MA_WIN32 + #if defined(WINAPI_FAMILY) && ((defined(WINAPI_FAMILY_PC_APP) && WINAPI_FAMILY == WINAPI_FAMILY_PC_APP) || (defined(WINAPI_FAMILY_PHONE_APP) && WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP)) + #define MA_WIN32_UWP + #elif defined(WINAPI_FAMILY) && (defined(WINAPI_FAMILY_GAMES) && WINAPI_FAMILY == WINAPI_FAMILY_GAMES) + #define MA_WIN32_GDK + #else + #define MA_WIN32_DESKTOP + #endif +#else + #define MA_POSIX + + /* + Use the MA_NO_PTHREAD_IN_HEADER option at your own risk. This is intentionally undocumented. + You can use this to avoid including pthread.h in the header section. The downside is that it + results in some fixed sized structures being declared for the various types that are used in + miniaudio. The risk here is that these types might be too small for a given platform. This + risk is yours to take and no support will be offered if you enable this option. + */ + #ifndef MA_NO_PTHREAD_IN_HEADER + #include /* Unfortunate #include, but needed for pthread_t, pthread_mutex_t and pthread_cond_t types. */ + typedef pthread_t ma_pthread_t; + typedef pthread_mutex_t ma_pthread_mutex_t; + typedef pthread_cond_t ma_pthread_cond_t; + #else + typedef ma_uintptr ma_pthread_t; + typedef union ma_pthread_mutex_t { char __data[40]; ma_uint64 __alignment; } ma_pthread_mutex_t; + typedef union ma_pthread_cond_t { char __data[48]; ma_uint64 __alignment; } ma_pthread_cond_t; + #endif + + #ifdef __unix__ + #define MA_UNIX + #if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) + #define MA_BSD + #endif + #endif + #ifdef __linux__ + #define MA_LINUX + #endif + #ifdef __APPLE__ + #define MA_APPLE + #endif + #ifdef __ANDROID__ + #define MA_ANDROID + #endif + #ifdef __EMSCRIPTEN__ + #define MA_EMSCRIPTEN + #endif +#endif + + #ifdef _MSC_VER #define MA_INLINE __forceinline #elif defined(__GNUC__) @@ -1614,9 +3797,15 @@ typedef ma_uint16 wchar_t; I am using "__inline__" only when we're compiling in strict ANSI mode. */ #if defined(__STRICT_ANSI__) - #define MA_INLINE __inline__ __attribute__((always_inline)) + #define MA_GNUC_INLINE_HINT __inline__ #else - #define MA_INLINE inline __attribute__((always_inline)) + #define MA_GNUC_INLINE_HINT inline + #endif + + #if (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 2)) || defined(__clang__) + #define MA_INLINE MA_GNUC_INLINE_HINT __attribute__((always_inline)) + #else + #define MA_INLINE MA_GNUC_INLINE_HINT #endif #elif defined(__WATCOMC__) #define MA_INLINE __inline @@ -1654,201 +3843,219 @@ typedef ma_uint16 wchar_t; #endif #endif -/* SIMD alignment in bytes. Currently set to 64 bytes in preparation for future AVX-512 optimizations. */ -#define MA_SIMD_ALIGNMENT 64 +/* SIMD alignment in bytes. Currently set to 32 bytes in preparation for future AVX optimizations. */ +#define MA_SIMD_ALIGNMENT 32 /* Logging Levels ============== Log levels are only used to give logging callbacks some context as to the severity of a log message -so they can do filtering. All log levels will be posted to registered logging callbacks, except for -MA_LOG_LEVEL_DEBUG which will only get processed if MA_DEBUG_OUTPUT is enabled. +so they can do filtering. All log levels will be posted to registered logging callbacks. If you +don't want to output a certain log level you can discriminate against the log level in the callback. MA_LOG_LEVEL_DEBUG - Used for debugging. These log messages are only posted when `MA_DEBUG_OUTPUT` is enabled. + Used for debugging. Useful for debug and test builds, but should be disabled in release builds. MA_LOG_LEVEL_INFO - Informational logging. Useful for debugging. This will also enable warning and error logs. This - will never be called from within the data callback. + Informational logging. Useful for debugging. This will never be called from within the data + callback. MA_LOG_LEVEL_WARNING - Warnings. You should enable this in you development builds and action them when encounted. This - will also enable error logs. These logs usually indicate a potential problem or - misconfiguration, but still allow you to keep running. This will never be called from within - the data callback. + Warnings. You should enable this in you development builds and action them when encounted. These + logs usually indicate a potential problem or misconfiguration, but still allow you to keep + running. This will never be called from within the data callback. MA_LOG_LEVEL_ERROR Error logging. This will be fired when an operation fails and is subsequently aborted. This can be fired from within the data callback, in which case the device will be stopped. You should always have this log level enabled. */ -#define MA_LOG_LEVEL_DEBUG 4 -#define MA_LOG_LEVEL_INFO 3 -#define MA_LOG_LEVEL_WARNING 2 -#define MA_LOG_LEVEL_ERROR 1 - -/* Deprecated. */ -#define MA_LOG_LEVEL_VERBOSE MA_LOG_LEVEL_DEBUG - -/* Deprecated. */ -#ifndef MA_LOG_LEVEL -#define MA_LOG_LEVEL MA_LOG_LEVEL_ERROR -#endif +typedef enum +{ + MA_LOG_LEVEL_DEBUG = 4, + MA_LOG_LEVEL_INFO = 3, + MA_LOG_LEVEL_WARNING = 2, + MA_LOG_LEVEL_ERROR = 1 +} ma_log_level; /* -An annotation for variables which must be used atomically. This doesn't actually do anything - it's -just used as a way for humans to identify variables that should be used atomically. +Variables needing to be accessed atomically should be declared with this macro for two reasons: + + 1) It allows people who read the code to identify a variable as such; and + 2) It forces alignment on platforms where it's required or optimal. + +Note that for x86/64, alignment is not strictly necessary, but does have some performance +implications. Where supported by the compiler, alignment will be used, but otherwise if the CPU +architecture does not require it, it will simply leave it unaligned. This is the case with old +versions of Visual Studio, which I've confirmed with at least VC6. */ -#define MA_ATOMIC +#if defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) + #include + #define MA_ATOMIC(alignment, type) alignas(alignment) type +#else + #if defined(__GNUC__) + /* GCC-style compilers. */ + #define MA_ATOMIC(alignment, type) type __attribute__((aligned(alignment))) + #elif defined(_MSC_VER) && _MSC_VER > 1200 /* 1200 = VC6. Alignment not supported, but not necessary because x86 is the only supported target. */ + /* MSVC. */ + #define MA_ATOMIC(alignment, type) __declspec(align(alignment)) type + #else + /* Other compilers. */ + #define MA_ATOMIC(alignment, type) type + #endif +#endif typedef struct ma_context ma_context; typedef struct ma_device ma_device; typedef ma_uint8 ma_channel; -#define MA_CHANNEL_NONE 0 -#define MA_CHANNEL_MONO 1 -#define MA_CHANNEL_FRONT_LEFT 2 -#define MA_CHANNEL_FRONT_RIGHT 3 -#define MA_CHANNEL_FRONT_CENTER 4 -#define MA_CHANNEL_LFE 5 -#define MA_CHANNEL_BACK_LEFT 6 -#define MA_CHANNEL_BACK_RIGHT 7 -#define MA_CHANNEL_FRONT_LEFT_CENTER 8 -#define MA_CHANNEL_FRONT_RIGHT_CENTER 9 -#define MA_CHANNEL_BACK_CENTER 10 -#define MA_CHANNEL_SIDE_LEFT 11 -#define MA_CHANNEL_SIDE_RIGHT 12 -#define MA_CHANNEL_TOP_CENTER 13 -#define MA_CHANNEL_TOP_FRONT_LEFT 14 -#define MA_CHANNEL_TOP_FRONT_CENTER 15 -#define MA_CHANNEL_TOP_FRONT_RIGHT 16 -#define MA_CHANNEL_TOP_BACK_LEFT 17 -#define MA_CHANNEL_TOP_BACK_CENTER 18 -#define MA_CHANNEL_TOP_BACK_RIGHT 19 -#define MA_CHANNEL_AUX_0 20 -#define MA_CHANNEL_AUX_1 21 -#define MA_CHANNEL_AUX_2 22 -#define MA_CHANNEL_AUX_3 23 -#define MA_CHANNEL_AUX_4 24 -#define MA_CHANNEL_AUX_5 25 -#define MA_CHANNEL_AUX_6 26 -#define MA_CHANNEL_AUX_7 27 -#define MA_CHANNEL_AUX_8 28 -#define MA_CHANNEL_AUX_9 29 -#define MA_CHANNEL_AUX_10 30 -#define MA_CHANNEL_AUX_11 31 -#define MA_CHANNEL_AUX_12 32 -#define MA_CHANNEL_AUX_13 33 -#define MA_CHANNEL_AUX_14 34 -#define MA_CHANNEL_AUX_15 35 -#define MA_CHANNEL_AUX_16 36 -#define MA_CHANNEL_AUX_17 37 -#define MA_CHANNEL_AUX_18 38 -#define MA_CHANNEL_AUX_19 39 -#define MA_CHANNEL_AUX_20 40 -#define MA_CHANNEL_AUX_21 41 -#define MA_CHANNEL_AUX_22 42 -#define MA_CHANNEL_AUX_23 43 -#define MA_CHANNEL_AUX_24 44 -#define MA_CHANNEL_AUX_25 45 -#define MA_CHANNEL_AUX_26 46 -#define MA_CHANNEL_AUX_27 47 -#define MA_CHANNEL_AUX_28 48 -#define MA_CHANNEL_AUX_29 49 -#define MA_CHANNEL_AUX_30 50 -#define MA_CHANNEL_AUX_31 51 -#define MA_CHANNEL_LEFT MA_CHANNEL_FRONT_LEFT -#define MA_CHANNEL_RIGHT MA_CHANNEL_FRONT_RIGHT -#define MA_CHANNEL_POSITION_COUNT (MA_CHANNEL_AUX_31 + 1) +typedef enum +{ + MA_CHANNEL_NONE = 0, + MA_CHANNEL_MONO = 1, + MA_CHANNEL_FRONT_LEFT = 2, + MA_CHANNEL_FRONT_RIGHT = 3, + MA_CHANNEL_FRONT_CENTER = 4, + MA_CHANNEL_LFE = 5, + MA_CHANNEL_BACK_LEFT = 6, + MA_CHANNEL_BACK_RIGHT = 7, + MA_CHANNEL_FRONT_LEFT_CENTER = 8, + MA_CHANNEL_FRONT_RIGHT_CENTER = 9, + MA_CHANNEL_BACK_CENTER = 10, + MA_CHANNEL_SIDE_LEFT = 11, + MA_CHANNEL_SIDE_RIGHT = 12, + MA_CHANNEL_TOP_CENTER = 13, + MA_CHANNEL_TOP_FRONT_LEFT = 14, + MA_CHANNEL_TOP_FRONT_CENTER = 15, + MA_CHANNEL_TOP_FRONT_RIGHT = 16, + MA_CHANNEL_TOP_BACK_LEFT = 17, + MA_CHANNEL_TOP_BACK_CENTER = 18, + MA_CHANNEL_TOP_BACK_RIGHT = 19, + MA_CHANNEL_AUX_0 = 20, + MA_CHANNEL_AUX_1 = 21, + MA_CHANNEL_AUX_2 = 22, + MA_CHANNEL_AUX_3 = 23, + MA_CHANNEL_AUX_4 = 24, + MA_CHANNEL_AUX_5 = 25, + MA_CHANNEL_AUX_6 = 26, + MA_CHANNEL_AUX_7 = 27, + MA_CHANNEL_AUX_8 = 28, + MA_CHANNEL_AUX_9 = 29, + MA_CHANNEL_AUX_10 = 30, + MA_CHANNEL_AUX_11 = 31, + MA_CHANNEL_AUX_12 = 32, + MA_CHANNEL_AUX_13 = 33, + MA_CHANNEL_AUX_14 = 34, + MA_CHANNEL_AUX_15 = 35, + MA_CHANNEL_AUX_16 = 36, + MA_CHANNEL_AUX_17 = 37, + MA_CHANNEL_AUX_18 = 38, + MA_CHANNEL_AUX_19 = 39, + MA_CHANNEL_AUX_20 = 40, + MA_CHANNEL_AUX_21 = 41, + MA_CHANNEL_AUX_22 = 42, + MA_CHANNEL_AUX_23 = 43, + MA_CHANNEL_AUX_24 = 44, + MA_CHANNEL_AUX_25 = 45, + MA_CHANNEL_AUX_26 = 46, + MA_CHANNEL_AUX_27 = 47, + MA_CHANNEL_AUX_28 = 48, + MA_CHANNEL_AUX_29 = 49, + MA_CHANNEL_AUX_30 = 50, + MA_CHANNEL_AUX_31 = 51, + MA_CHANNEL_LEFT = MA_CHANNEL_FRONT_LEFT, + MA_CHANNEL_RIGHT = MA_CHANNEL_FRONT_RIGHT, + MA_CHANNEL_POSITION_COUNT = (MA_CHANNEL_AUX_31 + 1) +} _ma_channel_position; /* Do not use `_ma_channel_position` directly. Use `ma_channel` instead. */ + +typedef enum +{ + MA_SUCCESS = 0, + MA_ERROR = -1, /* A generic error. */ + MA_INVALID_ARGS = -2, + MA_INVALID_OPERATION = -3, + MA_OUT_OF_MEMORY = -4, + MA_OUT_OF_RANGE = -5, + MA_ACCESS_DENIED = -6, + MA_DOES_NOT_EXIST = -7, + MA_ALREADY_EXISTS = -8, + MA_TOO_MANY_OPEN_FILES = -9, + MA_INVALID_FILE = -10, + MA_TOO_BIG = -11, + MA_PATH_TOO_LONG = -12, + MA_NAME_TOO_LONG = -13, + MA_NOT_DIRECTORY = -14, + MA_IS_DIRECTORY = -15, + MA_DIRECTORY_NOT_EMPTY = -16, + MA_AT_END = -17, + MA_NO_SPACE = -18, + MA_BUSY = -19, + MA_IO_ERROR = -20, + MA_INTERRUPT = -21, + MA_UNAVAILABLE = -22, + MA_ALREADY_IN_USE = -23, + MA_BAD_ADDRESS = -24, + MA_BAD_SEEK = -25, + MA_BAD_PIPE = -26, + MA_DEADLOCK = -27, + MA_TOO_MANY_LINKS = -28, + MA_NOT_IMPLEMENTED = -29, + MA_NO_MESSAGE = -30, + MA_BAD_MESSAGE = -31, + MA_NO_DATA_AVAILABLE = -32, + MA_INVALID_DATA = -33, + MA_TIMEOUT = -34, + MA_NO_NETWORK = -35, + MA_NOT_UNIQUE = -36, + MA_NOT_SOCKET = -37, + MA_NO_ADDRESS = -38, + MA_BAD_PROTOCOL = -39, + MA_PROTOCOL_UNAVAILABLE = -40, + MA_PROTOCOL_NOT_SUPPORTED = -41, + MA_PROTOCOL_FAMILY_NOT_SUPPORTED = -42, + MA_ADDRESS_FAMILY_NOT_SUPPORTED = -43, + MA_SOCKET_NOT_SUPPORTED = -44, + MA_CONNECTION_RESET = -45, + MA_ALREADY_CONNECTED = -46, + MA_NOT_CONNECTED = -47, + MA_CONNECTION_REFUSED = -48, + MA_NO_HOST = -49, + MA_IN_PROGRESS = -50, + MA_CANCELLED = -51, + MA_MEMORY_ALREADY_MAPPED = -52, + + /* General miniaudio-specific errors. */ + MA_FORMAT_NOT_SUPPORTED = -100, + MA_DEVICE_TYPE_NOT_SUPPORTED = -101, + MA_SHARE_MODE_NOT_SUPPORTED = -102, + MA_NO_BACKEND = -103, + MA_NO_DEVICE = -104, + MA_API_NOT_FOUND = -105, + MA_INVALID_DEVICE_CONFIG = -106, + MA_LOOP = -107, + + /* State errors. */ + MA_DEVICE_NOT_INITIALIZED = -200, + MA_DEVICE_ALREADY_INITIALIZED = -201, + MA_DEVICE_NOT_STARTED = -202, + MA_DEVICE_NOT_STOPPED = -203, + + /* Operation errors. */ + MA_FAILED_TO_INIT_BACKEND = -300, + MA_FAILED_TO_OPEN_BACKEND_DEVICE = -301, + MA_FAILED_TO_START_BACKEND_DEVICE = -302, + MA_FAILED_TO_STOP_BACKEND_DEVICE = -303 +} ma_result; -typedef int ma_result; -#define MA_SUCCESS 0 -#define MA_ERROR -1 /* A generic error. */ -#define MA_INVALID_ARGS -2 -#define MA_INVALID_OPERATION -3 -#define MA_OUT_OF_MEMORY -4 -#define MA_OUT_OF_RANGE -5 -#define MA_ACCESS_DENIED -6 -#define MA_DOES_NOT_EXIST -7 -#define MA_ALREADY_EXISTS -8 -#define MA_TOO_MANY_OPEN_FILES -9 -#define MA_INVALID_FILE -10 -#define MA_TOO_BIG -11 -#define MA_PATH_TOO_LONG -12 -#define MA_NAME_TOO_LONG -13 -#define MA_NOT_DIRECTORY -14 -#define MA_IS_DIRECTORY -15 -#define MA_DIRECTORY_NOT_EMPTY -16 -#define MA_AT_END -17 -#define MA_NO_SPACE -18 -#define MA_BUSY -19 -#define MA_IO_ERROR -20 -#define MA_INTERRUPT -21 -#define MA_UNAVAILABLE -22 -#define MA_ALREADY_IN_USE -23 -#define MA_BAD_ADDRESS -24 -#define MA_BAD_SEEK -25 -#define MA_BAD_PIPE -26 -#define MA_DEADLOCK -27 -#define MA_TOO_MANY_LINKS -28 -#define MA_NOT_IMPLEMENTED -29 -#define MA_NO_MESSAGE -30 -#define MA_BAD_MESSAGE -31 -#define MA_NO_DATA_AVAILABLE -32 -#define MA_INVALID_DATA -33 -#define MA_TIMEOUT -34 -#define MA_NO_NETWORK -35 -#define MA_NOT_UNIQUE -36 -#define MA_NOT_SOCKET -37 -#define MA_NO_ADDRESS -38 -#define MA_BAD_PROTOCOL -39 -#define MA_PROTOCOL_UNAVAILABLE -40 -#define MA_PROTOCOL_NOT_SUPPORTED -41 -#define MA_PROTOCOL_FAMILY_NOT_SUPPORTED -42 -#define MA_ADDRESS_FAMILY_NOT_SUPPORTED -43 -#define MA_SOCKET_NOT_SUPPORTED -44 -#define MA_CONNECTION_RESET -45 -#define MA_ALREADY_CONNECTED -46 -#define MA_NOT_CONNECTED -47 -#define MA_CONNECTION_REFUSED -48 -#define MA_NO_HOST -49 -#define MA_IN_PROGRESS -50 -#define MA_CANCELLED -51 -#define MA_MEMORY_ALREADY_MAPPED -52 - -/* General miniaudio-specific errors. */ -#define MA_FORMAT_NOT_SUPPORTED -100 -#define MA_DEVICE_TYPE_NOT_SUPPORTED -101 -#define MA_SHARE_MODE_NOT_SUPPORTED -102 -#define MA_NO_BACKEND -103 -#define MA_NO_DEVICE -104 -#define MA_API_NOT_FOUND -105 -#define MA_INVALID_DEVICE_CONFIG -106 -#define MA_LOOP -107 - -/* State errors. */ -#define MA_DEVICE_NOT_INITIALIZED -200 -#define MA_DEVICE_ALREADY_INITIALIZED -201 -#define MA_DEVICE_NOT_STARTED -202 -#define MA_DEVICE_NOT_STOPPED -203 - -/* Operation errors. */ -#define MA_FAILED_TO_INIT_BACKEND -300 -#define MA_FAILED_TO_OPEN_BACKEND_DEVICE -301 -#define MA_FAILED_TO_START_BACKEND_DEVICE -302 -#define MA_FAILED_TO_STOP_BACKEND_DEVICE -303 - - -#define MA_MIN_CHANNELS 1 -#ifndef MA_MAX_CHANNELS -#define MA_MAX_CHANNELS 32 +#define MA_MIN_CHANNELS 1 +#ifndef MA_MAX_CHANNELS +#define MA_MAX_CHANNELS 254 #endif - #ifndef MA_MAX_FILTER_ORDER -#define MA_MAX_FILTER_ORDER 8 +#define MA_MAX_FILTER_ORDER 8 #endif typedef enum @@ -1911,17 +4118,12 @@ typedef enum ma_standard_sample_rate_count = 14 /* Need to maintain the count manually. Make sure this is updated if items are added to enum. */ } ma_standard_sample_rate; -/* These are deprecated. Use ma_standard_sample_rate_min and ma_standard_sample_rate_max. */ -#define MA_MIN_SAMPLE_RATE (ma_uint32)ma_standard_sample_rate_min -#define MA_MAX_SAMPLE_RATE (ma_uint32)ma_standard_sample_rate_max - typedef enum { ma_channel_mix_mode_rectangular = 0, /* Simple averaging based on the plane(s) the channel is sitting on. */ ma_channel_mix_mode_simple, /* Drop excess channels; zeroed out extra channels. */ ma_channel_mix_mode_custom_weights, /* Use custom weights specified in ma_channel_router_config. */ - ma_channel_mix_mode_planar_blend = ma_channel_mix_mode_rectangular, ma_channel_mix_mode_default = ma_channel_mix_mode_rectangular } ma_channel_mix_mode; @@ -1959,6 +4161,9 @@ typedef struct } ma_lcg; +/* Spinlocks are 32-bit for compatibility reasons. */ +typedef ma_uint32 ma_spinlock; + #ifndef MA_NO_THREADING /* Thread priorities should be ordered such that the default priority of the worker thread is 0. */ typedef enum @@ -1973,21 +4178,18 @@ typedef enum ma_thread_priority_default = 0 } ma_thread_priority; -/* Spinlocks are 32-bit for compatibility reasons. */ -typedef ma_uint32 ma_spinlock; - #if defined(MA_WIN32) typedef ma_handle ma_thread; #endif #if defined(MA_POSIX) -typedef pthread_t ma_thread; +typedef ma_pthread_t ma_thread; #endif #if defined(MA_WIN32) typedef ma_handle ma_mutex; #endif #if defined(MA_POSIX) -typedef pthread_mutex_t ma_mutex; +typedef ma_pthread_mutex_t ma_mutex; #endif #if defined(MA_WIN32) @@ -1997,8 +4199,8 @@ typedef ma_handle ma_event; typedef struct { ma_uint32 value; - pthread_mutex_t lock; - pthread_cond_t cond; + ma_pthread_mutex_t lock; + ma_pthread_cond_t cond; } ma_event; #endif /* MA_POSIX */ @@ -2009,8 +4211,8 @@ typedef ma_handle ma_semaphore; typedef struct { int value; - pthread_mutex_t lock; - pthread_cond_t cond; + ma_pthread_mutex_t lock; + ma_pthread_cond_t cond; } ma_semaphore; #endif /* MA_POSIX */ #else @@ -2052,6 +4254,36 @@ Logging #define MA_MAX_LOG_CALLBACKS 4 #endif + +/* +The callback for handling log messages. + + +Parameters +---------- +pUserData (in) + The user data pointer that was passed into ma_log_register_callback(). + +logLevel (in) + The log level. This can be one of the following: + + +----------------------+ + | Log Level | + +----------------------+ + | MA_LOG_LEVEL_DEBUG | + | MA_LOG_LEVEL_INFO | + | MA_LOG_LEVEL_WARNING | + | MA_LOG_LEVEL_ERROR | + +----------------------+ + +pMessage (in) + The log message. + + +Remarks +------- +Do not modify the state of the device from inside the callback. +*/ typedef void (* ma_log_callback_proc)(void* pUserData, ma_uint32 level, const char* pMessage); typedef struct @@ -2116,12 +4348,20 @@ typedef struct ma_biquad_coefficient b2; ma_biquad_coefficient a1; ma_biquad_coefficient a2; - ma_biquad_coefficient r1[MA_MAX_CHANNELS]; - ma_biquad_coefficient r2[MA_MAX_CHANNELS]; + ma_biquad_coefficient* pR1; + ma_biquad_coefficient* pR2; + + /* Memory management. */ + void* _pHeap; + ma_bool32 _ownsHeap; } ma_biquad; -MA_API ma_result ma_biquad_init(const ma_biquad_config* pConfig, ma_biquad* pBQ); +MA_API ma_result ma_biquad_get_heap_size(const ma_biquad_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_biquad_init_preallocated(const ma_biquad_config* pConfig, void* pHeap, ma_biquad* pBQ); +MA_API ma_result ma_biquad_init(const ma_biquad_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_biquad* pBQ); +MA_API void ma_biquad_uninit(ma_biquad* pBQ, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_result ma_biquad_reinit(const ma_biquad_config* pConfig, ma_biquad* pBQ); +MA_API ma_result ma_biquad_clear_cache(ma_biquad* pBQ); MA_API ma_result ma_biquad_process_pcm_frames(ma_biquad* pBQ, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); MA_API ma_uint32 ma_biquad_get_latency(const ma_biquad* pBQ); @@ -2148,11 +4388,19 @@ typedef struct ma_format format; ma_uint32 channels; ma_biquad_coefficient a; - ma_biquad_coefficient r1[MA_MAX_CHANNELS]; + ma_biquad_coefficient* pR1; + + /* Memory management. */ + void* _pHeap; + ma_bool32 _ownsHeap; } ma_lpf1; -MA_API ma_result ma_lpf1_init(const ma_lpf1_config* pConfig, ma_lpf1* pLPF); +MA_API ma_result ma_lpf1_get_heap_size(const ma_lpf1_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_lpf1_init_preallocated(const ma_lpf1_config* pConfig, void* pHeap, ma_lpf1* pLPF); +MA_API ma_result ma_lpf1_init(const ma_lpf1_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_lpf1* pLPF); +MA_API void ma_lpf1_uninit(ma_lpf1* pLPF, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_result ma_lpf1_reinit(const ma_lpf1_config* pConfig, ma_lpf1* pLPF); +MA_API ma_result ma_lpf1_clear_cache(ma_lpf1* pLPF); MA_API ma_result ma_lpf1_process_pcm_frames(ma_lpf1* pLPF, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); MA_API ma_uint32 ma_lpf1_get_latency(const ma_lpf1* pLPF); @@ -2161,8 +4409,12 @@ typedef struct ma_biquad bq; /* The second order low-pass filter is implemented as a biquad filter. */ } ma_lpf2; -MA_API ma_result ma_lpf2_init(const ma_lpf2_config* pConfig, ma_lpf2* pLPF); +MA_API ma_result ma_lpf2_get_heap_size(const ma_lpf2_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_lpf2_init_preallocated(const ma_lpf2_config* pConfig, void* pHeap, ma_lpf2* pHPF); +MA_API ma_result ma_lpf2_init(const ma_lpf2_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_lpf2* pLPF); +MA_API void ma_lpf2_uninit(ma_lpf2* pLPF, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_result ma_lpf2_reinit(const ma_lpf2_config* pConfig, ma_lpf2* pLPF); +MA_API ma_result ma_lpf2_clear_cache(ma_lpf2* pLPF); MA_API ma_result ma_lpf2_process_pcm_frames(ma_lpf2* pLPF, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); MA_API ma_uint32 ma_lpf2_get_latency(const ma_lpf2* pLPF); @@ -2185,12 +4437,20 @@ typedef struct ma_uint32 sampleRate; ma_uint32 lpf1Count; ma_uint32 lpf2Count; - ma_lpf1 lpf1[1]; - ma_lpf2 lpf2[MA_MAX_FILTER_ORDER/2]; + ma_lpf1* pLPF1; + ma_lpf2* pLPF2; + + /* Memory management. */ + void* _pHeap; + ma_bool32 _ownsHeap; } ma_lpf; -MA_API ma_result ma_lpf_init(const ma_lpf_config* pConfig, ma_lpf* pLPF); +MA_API ma_result ma_lpf_get_heap_size(const ma_lpf_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_lpf_init_preallocated(const ma_lpf_config* pConfig, void* pHeap, ma_lpf* pLPF); +MA_API ma_result ma_lpf_init(const ma_lpf_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_lpf* pLPF); +MA_API void ma_lpf_uninit(ma_lpf* pLPF, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_result ma_lpf_reinit(const ma_lpf_config* pConfig, ma_lpf* pLPF); +MA_API ma_result ma_lpf_clear_cache(ma_lpf* pLPF); MA_API ma_result ma_lpf_process_pcm_frames(ma_lpf* pLPF, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); MA_API ma_uint32 ma_lpf_get_latency(const ma_lpf* pLPF); @@ -2217,10 +4477,17 @@ typedef struct ma_format format; ma_uint32 channels; ma_biquad_coefficient a; - ma_biquad_coefficient r1[MA_MAX_CHANNELS]; + ma_biquad_coefficient* pR1; + + /* Memory management. */ + void* _pHeap; + ma_bool32 _ownsHeap; } ma_hpf1; -MA_API ma_result ma_hpf1_init(const ma_hpf1_config* pConfig, ma_hpf1* pHPF); +MA_API ma_result ma_hpf1_get_heap_size(const ma_hpf1_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_hpf1_init_preallocated(const ma_hpf1_config* pConfig, void* pHeap, ma_hpf1* pLPF); +MA_API ma_result ma_hpf1_init(const ma_hpf1_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_hpf1* pHPF); +MA_API void ma_hpf1_uninit(ma_hpf1* pHPF, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_result ma_hpf1_reinit(const ma_hpf1_config* pConfig, ma_hpf1* pHPF); MA_API ma_result ma_hpf1_process_pcm_frames(ma_hpf1* pHPF, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); MA_API ma_uint32 ma_hpf1_get_latency(const ma_hpf1* pHPF); @@ -2230,7 +4497,10 @@ typedef struct ma_biquad bq; /* The second order high-pass filter is implemented as a biquad filter. */ } ma_hpf2; -MA_API ma_result ma_hpf2_init(const ma_hpf2_config* pConfig, ma_hpf2* pHPF); +MA_API ma_result ma_hpf2_get_heap_size(const ma_hpf2_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_hpf2_init_preallocated(const ma_hpf2_config* pConfig, void* pHeap, ma_hpf2* pHPF); +MA_API ma_result ma_hpf2_init(const ma_hpf2_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_hpf2* pHPF); +MA_API void ma_hpf2_uninit(ma_hpf2* pHPF, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_result ma_hpf2_reinit(const ma_hpf2_config* pConfig, ma_hpf2* pHPF); MA_API ma_result ma_hpf2_process_pcm_frames(ma_hpf2* pHPF, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); MA_API ma_uint32 ma_hpf2_get_latency(const ma_hpf2* pHPF); @@ -2254,11 +4524,18 @@ typedef struct ma_uint32 sampleRate; ma_uint32 hpf1Count; ma_uint32 hpf2Count; - ma_hpf1 hpf1[1]; - ma_hpf2 hpf2[MA_MAX_FILTER_ORDER/2]; + ma_hpf1* pHPF1; + ma_hpf2* pHPF2; + + /* Memory management. */ + void* _pHeap; + ma_bool32 _ownsHeap; } ma_hpf; -MA_API ma_result ma_hpf_init(const ma_hpf_config* pConfig, ma_hpf* pHPF); +MA_API ma_result ma_hpf_get_heap_size(const ma_hpf_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_hpf_init_preallocated(const ma_hpf_config* pConfig, void* pHeap, ma_hpf* pLPF); +MA_API ma_result ma_hpf_init(const ma_hpf_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_hpf* pHPF); +MA_API void ma_hpf_uninit(ma_hpf* pHPF, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_result ma_hpf_reinit(const ma_hpf_config* pConfig, ma_hpf* pHPF); MA_API ma_result ma_hpf_process_pcm_frames(ma_hpf* pHPF, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); MA_API ma_uint32 ma_hpf_get_latency(const ma_hpf* pHPF); @@ -2285,7 +4562,10 @@ typedef struct ma_biquad bq; /* The second order band-pass filter is implemented as a biquad filter. */ } ma_bpf2; -MA_API ma_result ma_bpf2_init(const ma_bpf2_config* pConfig, ma_bpf2* pBPF); +MA_API ma_result ma_bpf2_get_heap_size(const ma_bpf2_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_bpf2_init_preallocated(const ma_bpf2_config* pConfig, void* pHeap, ma_bpf2* pBPF); +MA_API ma_result ma_bpf2_init(const ma_bpf2_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_bpf2* pBPF); +MA_API void ma_bpf2_uninit(ma_bpf2* pBPF, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_result ma_bpf2_reinit(const ma_bpf2_config* pConfig, ma_bpf2* pBPF); MA_API ma_result ma_bpf2_process_pcm_frames(ma_bpf2* pBPF, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); MA_API ma_uint32 ma_bpf2_get_latency(const ma_bpf2* pBPF); @@ -2307,10 +4587,17 @@ typedef struct ma_format format; ma_uint32 channels; ma_uint32 bpf2Count; - ma_bpf2 bpf2[MA_MAX_FILTER_ORDER/2]; + ma_bpf2* pBPF2; + + /* Memory management. */ + void* _pHeap; + ma_bool32 _ownsHeap; } ma_bpf; -MA_API ma_result ma_bpf_init(const ma_bpf_config* pConfig, ma_bpf* pBPF); +MA_API ma_result ma_bpf_get_heap_size(const ma_bpf_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_bpf_init_preallocated(const ma_bpf_config* pConfig, void* pHeap, ma_bpf* pBPF); +MA_API ma_result ma_bpf_init(const ma_bpf_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_bpf* pBPF); +MA_API void ma_bpf_uninit(ma_bpf* pBPF, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_result ma_bpf_reinit(const ma_bpf_config* pConfig, ma_bpf* pBPF); MA_API ma_result ma_bpf_process_pcm_frames(ma_bpf* pBPF, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); MA_API ma_uint32 ma_bpf_get_latency(const ma_bpf* pBPF); @@ -2337,7 +4624,10 @@ typedef struct ma_biquad bq; } ma_notch2; -MA_API ma_result ma_notch2_init(const ma_notch2_config* pConfig, ma_notch2* pFilter); +MA_API ma_result ma_notch2_get_heap_size(const ma_notch2_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_notch2_init_preallocated(const ma_notch2_config* pConfig, void* pHeap, ma_notch2* pFilter); +MA_API ma_result ma_notch2_init(const ma_notch2_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_notch2* pFilter); +MA_API void ma_notch2_uninit(ma_notch2* pFilter, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_result ma_notch2_reinit(const ma_notch2_config* pConfig, ma_notch2* pFilter); MA_API ma_result ma_notch2_process_pcm_frames(ma_notch2* pFilter, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); MA_API ma_uint32 ma_notch2_get_latency(const ma_notch2* pFilter); @@ -2365,7 +4655,10 @@ typedef struct ma_biquad bq; } ma_peak2; -MA_API ma_result ma_peak2_init(const ma_peak2_config* pConfig, ma_peak2* pFilter); +MA_API ma_result ma_peak2_get_heap_size(const ma_peak2_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_peak2_init_preallocated(const ma_peak2_config* pConfig, void* pHeap, ma_peak2* pFilter); +MA_API ma_result ma_peak2_init(const ma_peak2_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_peak2* pFilter); +MA_API void ma_peak2_uninit(ma_peak2* pFilter, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_result ma_peak2_reinit(const ma_peak2_config* pConfig, ma_peak2* pFilter); MA_API ma_result ma_peak2_process_pcm_frames(ma_peak2* pFilter, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); MA_API ma_uint32 ma_peak2_get_latency(const ma_peak2* pFilter); @@ -2393,7 +4686,10 @@ typedef struct ma_biquad bq; } ma_loshelf2; -MA_API ma_result ma_loshelf2_init(const ma_loshelf2_config* pConfig, ma_loshelf2* pFilter); +MA_API ma_result ma_loshelf2_get_heap_size(const ma_loshelf2_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_loshelf2_init_preallocated(const ma_loshelf2_config* pConfig, void* pHeap, ma_loshelf2* pFilter); +MA_API ma_result ma_loshelf2_init(const ma_loshelf2_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_loshelf2* pFilter); +MA_API void ma_loshelf2_uninit(ma_loshelf2* pFilter, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_result ma_loshelf2_reinit(const ma_loshelf2_config* pConfig, ma_loshelf2* pFilter); MA_API ma_result ma_loshelf2_process_pcm_frames(ma_loshelf2* pFilter, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); MA_API ma_uint32 ma_loshelf2_get_latency(const ma_loshelf2* pFilter); @@ -2421,13 +4717,316 @@ typedef struct ma_biquad bq; } ma_hishelf2; -MA_API ma_result ma_hishelf2_init(const ma_hishelf2_config* pConfig, ma_hishelf2* pFilter); +MA_API ma_result ma_hishelf2_get_heap_size(const ma_hishelf2_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_hishelf2_init_preallocated(const ma_hishelf2_config* pConfig, void* pHeap, ma_hishelf2* pFilter); +MA_API ma_result ma_hishelf2_init(const ma_hishelf2_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_hishelf2* pFilter); +MA_API void ma_hishelf2_uninit(ma_hishelf2* pFilter, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_result ma_hishelf2_reinit(const ma_hishelf2_config* pConfig, ma_hishelf2* pFilter); MA_API ma_result ma_hishelf2_process_pcm_frames(ma_hishelf2* pFilter, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); MA_API ma_uint32 ma_hishelf2_get_latency(const ma_hishelf2* pFilter); +/* +Delay +*/ +typedef struct +{ + ma_uint32 channels; + ma_uint32 sampleRate; + ma_uint32 delayInFrames; + ma_bool32 delayStart; /* Set to true to delay the start of the output; false otherwise. */ + float wet; /* 0..1. Default = 1. */ + float dry; /* 0..1. Default = 1. */ + float decay; /* 0..1. Default = 0 (no feedback). Feedback decay. Use this for echo. */ +} ma_delay_config; + +MA_API ma_delay_config ma_delay_config_init(ma_uint32 channels, ma_uint32 sampleRate, ma_uint32 delayInFrames, float decay); + + +typedef struct +{ + ma_delay_config config; + ma_uint32 cursor; /* Feedback is written to this cursor. Always equal or in front of the read cursor. */ + ma_uint32 bufferSizeInFrames; /* The maximum of config.startDelayInFrames and config.feedbackDelayInFrames. */ + float* pBuffer; +} ma_delay; + +MA_API ma_result ma_delay_init(const ma_delay_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_delay* pDelay); +MA_API void ma_delay_uninit(ma_delay* pDelay, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API ma_result ma_delay_process_pcm_frames(ma_delay* pDelay, void* pFramesOut, const void* pFramesIn, ma_uint32 frameCount); +MA_API void ma_delay_set_wet(ma_delay* pDelay, float value); +MA_API float ma_delay_get_wet(const ma_delay* pDelay); +MA_API void ma_delay_set_dry(ma_delay* pDelay, float value); +MA_API float ma_delay_get_dry(const ma_delay* pDelay); +MA_API void ma_delay_set_decay(ma_delay* pDelay, float value); +MA_API float ma_delay_get_decay(const ma_delay* pDelay); + + +/* Gainer for smooth volume changes. */ +typedef struct +{ + ma_uint32 channels; + ma_uint32 smoothTimeInFrames; +} ma_gainer_config; + +MA_API ma_gainer_config ma_gainer_config_init(ma_uint32 channels, ma_uint32 smoothTimeInFrames); + + +typedef struct +{ + ma_gainer_config config; + ma_uint32 t; + float* pOldGains; + float* pNewGains; + + /* Memory management. */ + void* _pHeap; + ma_bool32 _ownsHeap; +} ma_gainer; + +MA_API ma_result ma_gainer_get_heap_size(const ma_gainer_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_gainer_init_preallocated(const ma_gainer_config* pConfig, void* pHeap, ma_gainer* pGainer); +MA_API ma_result ma_gainer_init(const ma_gainer_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_gainer* pGainer); +MA_API void ma_gainer_uninit(ma_gainer* pGainer, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API ma_result ma_gainer_process_pcm_frames(ma_gainer* pGainer, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); +MA_API ma_result ma_gainer_set_gain(ma_gainer* pGainer, float newGain); +MA_API ma_result ma_gainer_set_gains(ma_gainer* pGainer, float* pNewGains); + + + +/* Stereo panner. */ +typedef enum +{ + ma_pan_mode_balance = 0, /* Does not blend one side with the other. Technically just a balance. Compatible with other popular audio engines and therefore the default. */ + ma_pan_mode_pan /* A true pan. The sound from one side will "move" to the other side and blend with it. */ +} ma_pan_mode; + +typedef struct +{ + ma_format format; + ma_uint32 channels; + ma_pan_mode mode; + float pan; +} ma_panner_config; + +MA_API ma_panner_config ma_panner_config_init(ma_format format, ma_uint32 channels); + + +typedef struct +{ + ma_format format; + ma_uint32 channels; + ma_pan_mode mode; + float pan; /* -1..1 where 0 is no pan, -1 is left side, +1 is right side. Defaults to 0. */ +} ma_panner; + +MA_API ma_result ma_panner_init(const ma_panner_config* pConfig, ma_panner* pPanner); +MA_API ma_result ma_panner_process_pcm_frames(ma_panner* pPanner, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); +MA_API void ma_panner_set_mode(ma_panner* pPanner, ma_pan_mode mode); +MA_API ma_pan_mode ma_panner_get_mode(const ma_panner* pPanner); +MA_API void ma_panner_set_pan(ma_panner* pPanner, float pan); +MA_API float ma_panner_get_pan(const ma_panner* pPanner); + + + +/* Fader. */ +typedef struct +{ + ma_format format; + ma_uint32 channels; + ma_uint32 sampleRate; +} ma_fader_config; + +MA_API ma_fader_config ma_fader_config_init(ma_format format, ma_uint32 channels, ma_uint32 sampleRate); + +typedef struct +{ + ma_fader_config config; + float volumeBeg; /* If volumeBeg and volumeEnd is equal to 1, no fading happens (ma_fader_process_pcm_frames() will run as a passthrough). */ + float volumeEnd; + ma_uint64 lengthInFrames; /* The total length of the fade. */ + ma_uint64 cursorInFrames; /* The current time in frames. Incremented by ma_fader_process_pcm_frames(). */ +} ma_fader; + +MA_API ma_result ma_fader_init(const ma_fader_config* pConfig, ma_fader* pFader); +MA_API ma_result ma_fader_process_pcm_frames(ma_fader* pFader, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); +MA_API void ma_fader_get_data_format(const ma_fader* pFader, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate); +MA_API void ma_fader_set_fade(ma_fader* pFader, float volumeBeg, float volumeEnd, ma_uint64 lengthInFrames); +MA_API float ma_fader_get_current_volume(ma_fader* pFader); + + + +/* Spatializer. */ +typedef struct +{ + float x; + float y; + float z; +} ma_vec3f; + +typedef enum +{ + ma_attenuation_model_none, /* No distance attenuation and no spatialization. */ + ma_attenuation_model_inverse, /* Equivalent to OpenAL's AL_INVERSE_DISTANCE_CLAMPED. */ + ma_attenuation_model_linear, /* Linear attenuation. Equivalent to OpenAL's AL_LINEAR_DISTANCE_CLAMPED. */ + ma_attenuation_model_exponential /* Exponential attenuation. Equivalent to OpenAL's AL_EXPONENT_DISTANCE_CLAMPED. */ +} ma_attenuation_model; + +typedef enum +{ + ma_positioning_absolute, + ma_positioning_relative +} ma_positioning; + +typedef enum +{ + ma_handedness_right, + ma_handedness_left +} ma_handedness; + + +typedef struct +{ + ma_uint32 channelsOut; + ma_channel* pChannelMapOut; + ma_handedness handedness; /* Defaults to right. Forward is -1 on the Z axis. In a left handed system, forward is +1 on the Z axis. */ + float coneInnerAngleInRadians; + float coneOuterAngleInRadians; + float coneOuterGain; + float speedOfSound; + ma_vec3f worldUp; +} ma_spatializer_listener_config; + +MA_API ma_spatializer_listener_config ma_spatializer_listener_config_init(ma_uint32 channelsOut); + + +typedef struct +{ + ma_spatializer_listener_config config; + ma_vec3f position; /* The absolute position of the listener. */ + ma_vec3f direction; /* The direction the listener is facing. The world up vector is config.worldUp. */ + ma_vec3f velocity; + ma_bool32 isEnabled; + + /* Memory management. */ + ma_bool32 _ownsHeap; + void* _pHeap; +} ma_spatializer_listener; + +MA_API ma_result ma_spatializer_listener_get_heap_size(const ma_spatializer_listener_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_spatializer_listener_init_preallocated(const ma_spatializer_listener_config* pConfig, void* pHeap, ma_spatializer_listener* pListener); +MA_API ma_result ma_spatializer_listener_init(const ma_spatializer_listener_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_spatializer_listener* pListener); +MA_API void ma_spatializer_listener_uninit(ma_spatializer_listener* pListener, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API ma_channel* ma_spatializer_listener_get_channel_map(ma_spatializer_listener* pListener); +MA_API void ma_spatializer_listener_set_cone(ma_spatializer_listener* pListener, float innerAngleInRadians, float outerAngleInRadians, float outerGain); +MA_API void ma_spatializer_listener_get_cone(const ma_spatializer_listener* pListener, float* pInnerAngleInRadians, float* pOuterAngleInRadians, float* pOuterGain); +MA_API void ma_spatializer_listener_set_position(ma_spatializer_listener* pListener, float x, float y, float z); +MA_API ma_vec3f ma_spatializer_listener_get_position(const ma_spatializer_listener* pListener); +MA_API void ma_spatializer_listener_set_direction(ma_spatializer_listener* pListener, float x, float y, float z); +MA_API ma_vec3f ma_spatializer_listener_get_direction(const ma_spatializer_listener* pListener); +MA_API void ma_spatializer_listener_set_velocity(ma_spatializer_listener* pListener, float x, float y, float z); +MA_API ma_vec3f ma_spatializer_listener_get_velocity(const ma_spatializer_listener* pListener); +MA_API void ma_spatializer_listener_set_speed_of_sound(ma_spatializer_listener* pListener, float speedOfSound); +MA_API float ma_spatializer_listener_get_speed_of_sound(const ma_spatializer_listener* pListener); +MA_API void ma_spatializer_listener_set_world_up(ma_spatializer_listener* pListener, float x, float y, float z); +MA_API ma_vec3f ma_spatializer_listener_get_world_up(const ma_spatializer_listener* pListener); +MA_API void ma_spatializer_listener_set_enabled(ma_spatializer_listener* pListener, ma_bool32 isEnabled); +MA_API ma_bool32 ma_spatializer_listener_is_enabled(const ma_spatializer_listener* pListener); + + +typedef struct +{ + ma_uint32 channelsIn; + ma_uint32 channelsOut; + ma_channel* pChannelMapIn; + ma_attenuation_model attenuationModel; + ma_positioning positioning; + ma_handedness handedness; /* Defaults to right. Forward is -1 on the Z axis. In a left handed system, forward is +1 on the Z axis. */ + float minGain; + float maxGain; + float minDistance; + float maxDistance; + float rolloff; + float coneInnerAngleInRadians; + float coneOuterAngleInRadians; + float coneOuterGain; + float dopplerFactor; /* Set to 0 to disable doppler effect. */ + float directionalAttenuationFactor; /* Set to 0 to disable directional attenuation. */ + ma_uint32 gainSmoothTimeInFrames; /* When the gain of a channel changes during spatialization, the transition will be linearly interpolated over this number of frames. */ +} ma_spatializer_config; + +MA_API ma_spatializer_config ma_spatializer_config_init(ma_uint32 channelsIn, ma_uint32 channelsOut); + + +typedef struct +{ + ma_uint32 channelsIn; + ma_uint32 channelsOut; + ma_channel* pChannelMapIn; + ma_attenuation_model attenuationModel; + ma_positioning positioning; + ma_handedness handedness; /* Defaults to right. Forward is -1 on the Z axis. In a left handed system, forward is +1 on the Z axis. */ + float minGain; + float maxGain; + float minDistance; + float maxDistance; + float rolloff; + float coneInnerAngleInRadians; + float coneOuterAngleInRadians; + float coneOuterGain; + float dopplerFactor; /* Set to 0 to disable doppler effect. */ + float directionalAttenuationFactor; /* Set to 0 to disable directional attenuation. */ + ma_uint32 gainSmoothTimeInFrames; /* When the gain of a channel changes during spatialization, the transition will be linearly interpolated over this number of frames. */ + ma_vec3f position; + ma_vec3f direction; + ma_vec3f velocity; /* For doppler effect. */ + float dopplerPitch; /* Will be updated by ma_spatializer_process_pcm_frames() and can be used by higher level functions to apply a pitch shift for doppler effect. */ + ma_gainer gainer; /* For smooth gain transitions. */ + float* pNewChannelGainsOut; /* An offset of _pHeap. Used by ma_spatializer_process_pcm_frames() to store new channel gains. The number of elements in this array is equal to config.channelsOut. */ + + /* Memory management. */ + void* _pHeap; + ma_bool32 _ownsHeap; +} ma_spatializer; + +MA_API ma_result ma_spatializer_get_heap_size(const ma_spatializer_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_spatializer_init_preallocated(const ma_spatializer_config* pConfig, void* pHeap, ma_spatializer* pSpatializer); +MA_API ma_result ma_spatializer_init(const ma_spatializer_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_spatializer* pSpatializer); +MA_API void ma_spatializer_uninit(ma_spatializer* pSpatializer, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API ma_result ma_spatializer_process_pcm_frames(ma_spatializer* pSpatializer, ma_spatializer_listener* pListener, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); +MA_API ma_uint32 ma_spatializer_get_input_channels(const ma_spatializer* pSpatializer); +MA_API ma_uint32 ma_spatializer_get_output_channels(const ma_spatializer* pSpatializer); +MA_API void ma_spatializer_set_attenuation_model(ma_spatializer* pSpatializer, ma_attenuation_model attenuationModel); +MA_API ma_attenuation_model ma_spatializer_get_attenuation_model(const ma_spatializer* pSpatializer); +MA_API void ma_spatializer_set_positioning(ma_spatializer* pSpatializer, ma_positioning positioning); +MA_API ma_positioning ma_spatializer_get_positioning(const ma_spatializer* pSpatializer); +MA_API void ma_spatializer_set_rolloff(ma_spatializer* pSpatializer, float rolloff); +MA_API float ma_spatializer_get_rolloff(const ma_spatializer* pSpatializer); +MA_API void ma_spatializer_set_min_gain(ma_spatializer* pSpatializer, float minGain); +MA_API float ma_spatializer_get_min_gain(const ma_spatializer* pSpatializer); +MA_API void ma_spatializer_set_max_gain(ma_spatializer* pSpatializer, float maxGain); +MA_API float ma_spatializer_get_max_gain(const ma_spatializer* pSpatializer); +MA_API void ma_spatializer_set_min_distance(ma_spatializer* pSpatializer, float minDistance); +MA_API float ma_spatializer_get_min_distance(const ma_spatializer* pSpatializer); +MA_API void ma_spatializer_set_max_distance(ma_spatializer* pSpatializer, float maxDistance); +MA_API float ma_spatializer_get_max_distance(const ma_spatializer* pSpatializer); +MA_API void ma_spatializer_set_cone(ma_spatializer* pSpatializer, float innerAngleInRadians, float outerAngleInRadians, float outerGain); +MA_API void ma_spatializer_get_cone(const ma_spatializer* pSpatializer, float* pInnerAngleInRadians, float* pOuterAngleInRadians, float* pOuterGain); +MA_API void ma_spatializer_set_doppler_factor(ma_spatializer* pSpatializer, float dopplerFactor); +MA_API float ma_spatializer_get_doppler_factor(const ma_spatializer* pSpatializer); +MA_API void ma_spatializer_set_directional_attenuation_factor(ma_spatializer* pSpatializer, float directionalAttenuationFactor); +MA_API float ma_spatializer_get_directional_attenuation_factor(const ma_spatializer* pSpatializer); +MA_API void ma_spatializer_set_position(ma_spatializer* pSpatializer, float x, float y, float z); +MA_API ma_vec3f ma_spatializer_get_position(const ma_spatializer* pSpatializer); +MA_API void ma_spatializer_set_direction(ma_spatializer* pSpatializer, float x, float y, float z); +MA_API ma_vec3f ma_spatializer_get_direction(const ma_spatializer* pSpatializer); +MA_API void ma_spatializer_set_velocity(ma_spatializer* pSpatializer, float x, float y, float z); +MA_API ma_vec3f ma_spatializer_get_velocity(const ma_spatializer* pSpatializer); +MA_API void ma_spatializer_get_relative_position_and_direction(const ma_spatializer* pSpatializer, const ma_spatializer_listener* pListener, ma_vec3f* pRelativePos, ma_vec3f* pRelativeDir); + + + /************************************************************************************************************************************************************ ************************************************************************************************************************************************************* @@ -2465,75 +5064,106 @@ typedef struct ma_uint32 inTimeFrac; union { - float f32[MA_MAX_CHANNELS]; - ma_int16 s16[MA_MAX_CHANNELS]; + float* f32; + ma_int16* s16; } x0; /* The previous input frame. */ union { - float f32[MA_MAX_CHANNELS]; - ma_int16 s16[MA_MAX_CHANNELS]; + float* f32; + ma_int16* s16; } x1; /* The next input frame. */ ma_lpf lpf; + + /* Memory management. */ + void* _pHeap; + ma_bool32 _ownsHeap; } ma_linear_resampler; -MA_API ma_result ma_linear_resampler_init(const ma_linear_resampler_config* pConfig, ma_linear_resampler* pResampler); -MA_API void ma_linear_resampler_uninit(ma_linear_resampler* pResampler); +MA_API ma_result ma_linear_resampler_get_heap_size(const ma_linear_resampler_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_linear_resampler_init_preallocated(const ma_linear_resampler_config* pConfig, void* pHeap, ma_linear_resampler* pResampler); +MA_API ma_result ma_linear_resampler_init(const ma_linear_resampler_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_linear_resampler* pResampler); +MA_API void ma_linear_resampler_uninit(ma_linear_resampler* pResampler, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_result ma_linear_resampler_process_pcm_frames(ma_linear_resampler* pResampler, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut); MA_API ma_result ma_linear_resampler_set_rate(ma_linear_resampler* pResampler, ma_uint32 sampleRateIn, ma_uint32 sampleRateOut); MA_API ma_result ma_linear_resampler_set_rate_ratio(ma_linear_resampler* pResampler, float ratioInOut); -MA_API ma_uint64 ma_linear_resampler_get_required_input_frame_count(const ma_linear_resampler* pResampler, ma_uint64 outputFrameCount); -MA_API ma_uint64 ma_linear_resampler_get_expected_output_frame_count(const ma_linear_resampler* pResampler, ma_uint64 inputFrameCount); MA_API ma_uint64 ma_linear_resampler_get_input_latency(const ma_linear_resampler* pResampler); MA_API ma_uint64 ma_linear_resampler_get_output_latency(const ma_linear_resampler* pResampler); +MA_API ma_result ma_linear_resampler_get_required_input_frame_count(const ma_linear_resampler* pResampler, ma_uint64 outputFrameCount, ma_uint64* pInputFrameCount); +MA_API ma_result ma_linear_resampler_get_expected_output_frame_count(const ma_linear_resampler* pResampler, ma_uint64 inputFrameCount, ma_uint64* pOutputFrameCount); +MA_API ma_result ma_linear_resampler_reset(ma_linear_resampler* pResampler); + + +typedef struct ma_resampler_config ma_resampler_config; + +typedef void ma_resampling_backend; +typedef struct +{ + ma_result (* onGetHeapSize )(void* pUserData, const ma_resampler_config* pConfig, size_t* pHeapSizeInBytes); + ma_result (* onInit )(void* pUserData, const ma_resampler_config* pConfig, void* pHeap, ma_resampling_backend** ppBackend); + void (* onUninit )(void* pUserData, ma_resampling_backend* pBackend, const ma_allocation_callbacks* pAllocationCallbacks); + ma_result (* onProcess )(void* pUserData, ma_resampling_backend* pBackend, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut); + ma_result (* onSetRate )(void* pUserData, ma_resampling_backend* pBackend, ma_uint32 sampleRateIn, ma_uint32 sampleRateOut); /* Optional. Rate changes will be disabled. */ + ma_uint64 (* onGetInputLatency )(void* pUserData, const ma_resampling_backend* pBackend); /* Optional. Latency will be reported as 0. */ + ma_uint64 (* onGetOutputLatency )(void* pUserData, const ma_resampling_backend* pBackend); /* Optional. Latency will be reported as 0. */ + ma_result (* onGetRequiredInputFrameCount )(void* pUserData, const ma_resampling_backend* pBackend, ma_uint64 outputFrameCount, ma_uint64* pInputFrameCount); /* Optional. Latency mitigation will be disabled. */ + ma_result (* onGetExpectedOutputFrameCount)(void* pUserData, const ma_resampling_backend* pBackend, ma_uint64 inputFrameCount, ma_uint64* pOutputFrameCount); /* Optional. Latency mitigation will be disabled. */ + ma_result (* onReset )(void* pUserData, ma_resampling_backend* pBackend); +} ma_resampling_backend_vtable; typedef enum { - ma_resample_algorithm_linear = 0, /* Fastest, lowest quality. Optional low-pass filtering. Default. */ - ma_resample_algorithm_speex + ma_resample_algorithm_linear = 0, /* Fastest, lowest quality. Optional low-pass filtering. Default. */ + ma_resample_algorithm_custom, } ma_resample_algorithm; -typedef struct +struct ma_resampler_config { ma_format format; /* Must be either ma_format_f32 or ma_format_s16. */ ma_uint32 channels; ma_uint32 sampleRateIn; ma_uint32 sampleRateOut; - ma_resample_algorithm algorithm; + ma_resample_algorithm algorithm; /* When set to ma_resample_algorithm_custom, pBackendVTable will be used. */ + ma_resampling_backend_vtable* pBackendVTable; + void* pBackendUserData; struct { ma_uint32 lpfOrder; - double lpfNyquistFactor; } linear; - struct - { - int quality; /* 0 to 10. Defaults to 3. */ - } speex; -} ma_resampler_config; +}; MA_API ma_resampler_config ma_resampler_config_init(ma_format format, ma_uint32 channels, ma_uint32 sampleRateIn, ma_uint32 sampleRateOut, ma_resample_algorithm algorithm); typedef struct { - ma_resampler_config config; + ma_resampling_backend* pBackend; + ma_resampling_backend_vtable* pBackendVTable; + void* pBackendUserData; + ma_format format; + ma_uint32 channels; + ma_uint32 sampleRateIn; + ma_uint32 sampleRateOut; union { ma_linear_resampler linear; - struct - { - void* pSpeexResamplerState; /* SpeexResamplerState* */ - } speex; - } state; + } state; /* State for stock resamplers so we can avoid a malloc. For stock resamplers, pBackend will point here. */ + + /* Memory management. */ + void* _pHeap; + ma_bool32 _ownsHeap; } ma_resampler; +MA_API ma_result ma_resampler_get_heap_size(const ma_resampler_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_resampler_init_preallocated(const ma_resampler_config* pConfig, void* pHeap, ma_resampler* pResampler); + /* Initializes a new resampler object from a config. */ -MA_API ma_result ma_resampler_init(const ma_resampler_config* pConfig, ma_resampler* pResampler); +MA_API ma_result ma_resampler_init(const ma_resampler_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_resampler* pResampler); /* Uninitializes a resampler. */ -MA_API void ma_resampler_uninit(ma_resampler* pResampler); +MA_API void ma_resampler_uninit(ma_resampler* pResampler, const ma_allocation_callbacks* pAllocationCallbacks); /* Converts the given input data. @@ -2572,23 +5202,6 @@ The ration is in/out. */ MA_API ma_result ma_resampler_set_rate_ratio(ma_resampler* pResampler, float ratio); - -/* -Calculates the number of whole input frames that would need to be read from the client in order to output the specified -number of output frames. - -The returned value does not include cached input frames. It only returns the number of extra frames that would need to be -read from the input buffer in order to output the specified number of output frames. -*/ -MA_API ma_uint64 ma_resampler_get_required_input_frame_count(const ma_resampler* pResampler, ma_uint64 outputFrameCount); - -/* -Calculates the number of whole output frames that would be output after fully reading and consuming the specified number of -input frames. -*/ -MA_API ma_uint64 ma_resampler_get_expected_output_frame_count(const ma_resampler* pResampler, ma_uint64 inputFrameCount); - - /* Retrieves the latency introduced by the resampler in input frames. */ @@ -2599,6 +5212,25 @@ Retrieves the latency introduced by the resampler in output frames. */ MA_API ma_uint64 ma_resampler_get_output_latency(const ma_resampler* pResampler); +/* +Calculates the number of whole input frames that would need to be read from the client in order to output the specified +number of output frames. + +The returned value does not include cached input frames. It only returns the number of extra frames that would need to be +read from the input buffer in order to output the specified number of output frames. +*/ +MA_API ma_result ma_resampler_get_required_input_frame_count(const ma_resampler* pResampler, ma_uint64 outputFrameCount, ma_uint64* pInputFrameCount); + +/* +Calculates the number of whole output frames that would be output after fully reading and consuming the specified number of +input frames. +*/ +MA_API ma_result ma_resampler_get_expected_output_frame_count(const ma_resampler* pResampler, ma_uint64 inputFrameCount, ma_uint64* pOutputFrameCount); + +/* +Resets the resampler's timer and clears it's internal cache. +*/ +MA_API ma_result ma_resampler_reset(ma_resampler* pResampler); /************************************************************************************************************************************************************** @@ -2606,15 +5238,33 @@ MA_API ma_uint64 ma_resampler_get_output_latency(const ma_resampler* pResampler) Channel Conversion **************************************************************************************************************************************************************/ +typedef enum +{ + ma_channel_conversion_path_unknown, + ma_channel_conversion_path_passthrough, + ma_channel_conversion_path_mono_out, /* Converting to mono. */ + ma_channel_conversion_path_mono_in, /* Converting from mono. */ + ma_channel_conversion_path_shuffle, /* Simple shuffle. Will use this when all channels are present in both input and output channel maps, but just in a different order. */ + ma_channel_conversion_path_weights /* Blended based on weights. */ +} ma_channel_conversion_path; + +typedef enum +{ + ma_mono_expansion_mode_duplicate = 0, /* The default. */ + ma_mono_expansion_mode_average, /* Average the mono channel across all channels. */ + ma_mono_expansion_mode_stereo_only, /* Duplicate to the left and right channels only and ignore the others. */ + ma_mono_expansion_mode_default = ma_mono_expansion_mode_duplicate +} ma_mono_expansion_mode; + typedef struct { ma_format format; ma_uint32 channelsIn; ma_uint32 channelsOut; - ma_channel channelMapIn[MA_MAX_CHANNELS]; - ma_channel channelMapOut[MA_MAX_CHANNELS]; + const ma_channel* pChannelMapIn; + const ma_channel* pChannelMapOut; ma_channel_mix_mode mixingMode; - float weights[MA_MAX_CHANNELS][MA_MAX_CHANNELS]; /* [in][out]. Only used when mixingMode is set to ma_channel_mix_mode_custom_weights. */ + float** ppWeights; /* [in][out]. Only used when mixingMode is set to ma_channel_mix_mode_custom_weights. */ } ma_channel_converter_config; MA_API ma_channel_converter_config ma_channel_converter_config_init(ma_format format, ma_uint32 channelsIn, const ma_channel* pChannelMapIn, ma_uint32 channelsOut, const ma_channel* pChannelMapOut, ma_channel_mix_mode mixingMode); @@ -2624,24 +5274,29 @@ typedef struct ma_format format; ma_uint32 channelsIn; ma_uint32 channelsOut; - ma_channel channelMapIn[MA_MAX_CHANNELS]; - ma_channel channelMapOut[MA_MAX_CHANNELS]; ma_channel_mix_mode mixingMode; + ma_channel_conversion_path conversionPath; + ma_channel* pChannelMapIn; + ma_channel* pChannelMapOut; + ma_uint8* pShuffleTable; /* Indexed by output channel index. */ union { - float f32[MA_MAX_CHANNELS][MA_MAX_CHANNELS]; - ma_int32 s16[MA_MAX_CHANNELS][MA_MAX_CHANNELS]; - } weights; - ma_bool8 isPassthrough; - ma_bool8 isSimpleShuffle; - ma_bool8 isSimpleMonoExpansion; - ma_bool8 isStereoToMono; - ma_uint8 shuffleTable[MA_MAX_CHANNELS]; + float** f32; + ma_int32** s16; + } weights; /* [in][out] */ + + /* Memory management. */ + void* _pHeap; + ma_bool32 _ownsHeap; } ma_channel_converter; -MA_API ma_result ma_channel_converter_init(const ma_channel_converter_config* pConfig, ma_channel_converter* pConverter); -MA_API void ma_channel_converter_uninit(ma_channel_converter* pConverter); +MA_API ma_result ma_channel_converter_get_heap_size(const ma_channel_converter_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_channel_converter_init_preallocated(const ma_channel_converter_config* pConfig, void* pHeap, ma_channel_converter* pConverter); +MA_API ma_result ma_channel_converter_init(const ma_channel_converter_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_channel_converter* pConverter); +MA_API void ma_channel_converter_uninit(ma_channel_converter* pConverter, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_result ma_channel_converter_process_pcm_frames(ma_channel_converter* pConverter, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); +MA_API ma_result ma_channel_converter_get_input_channel_map(const ma_channel_converter* pConverter, ma_channel* pChannelMap, size_t channelMapCap); +MA_API ma_result ma_channel_converter_get_output_channel_map(const ma_channel_converter* pConverter, ma_channel* pChannelMap, size_t channelMapCap); /************************************************************************************************************************************************************** @@ -2657,33 +5312,39 @@ typedef struct ma_uint32 channelsOut; ma_uint32 sampleRateIn; ma_uint32 sampleRateOut; - ma_channel channelMapIn[MA_MAX_CHANNELS]; - ma_channel channelMapOut[MA_MAX_CHANNELS]; + ma_channel* pChannelMapIn; + ma_channel* pChannelMapOut; ma_dither_mode ditherMode; ma_channel_mix_mode channelMixMode; - float channelWeights[MA_MAX_CHANNELS][MA_MAX_CHANNELS]; /* [in][out]. Only used when channelMixMode is set to ma_channel_mix_mode_custom_weights. */ - struct - { - ma_resample_algorithm algorithm; - ma_bool32 allowDynamicSampleRate; - struct - { - ma_uint32 lpfOrder; - double lpfNyquistFactor; - } linear; - struct - { - int quality; - } speex; - } resampling; + float** ppChannelWeights; /* [in][out]. Only used when mixingMode is set to ma_channel_mix_mode_custom_weights. */ + ma_bool32 allowDynamicSampleRate; + ma_resampler_config resampling; } ma_data_converter_config; MA_API ma_data_converter_config ma_data_converter_config_init_default(void); MA_API ma_data_converter_config ma_data_converter_config_init(ma_format formatIn, ma_format formatOut, ma_uint32 channelsIn, ma_uint32 channelsOut, ma_uint32 sampleRateIn, ma_uint32 sampleRateOut); + +typedef enum +{ + ma_data_converter_execution_path_passthrough, /* No conversion. */ + ma_data_converter_execution_path_format_only, /* Only format conversion. */ + ma_data_converter_execution_path_channels_only, /* Only channel conversion. */ + ma_data_converter_execution_path_resample_only, /* Only resampling. */ + ma_data_converter_execution_path_resample_first, /* All conversions, but resample as the first step. */ + ma_data_converter_execution_path_channels_first /* All conversions, but channels as the first step. */ +} ma_data_converter_execution_path; + typedef struct { - ma_data_converter_config config; + ma_format formatIn; + ma_format formatOut; + ma_uint32 channelsIn; + ma_uint32 channelsOut; + ma_uint32 sampleRateIn; + ma_uint32 sampleRateOut; + ma_dither_mode ditherMode; + ma_data_converter_execution_path executionPath; /* The execution path the data converter will follow when processing. */ ma_channel_converter channelConverter; ma_resampler resampler; ma_bool8 hasPreFormatConversion; @@ -2691,17 +5352,26 @@ typedef struct ma_bool8 hasChannelConverter; ma_bool8 hasResampler; ma_bool8 isPassthrough; + + /* Memory management. */ + ma_bool8 _ownsHeap; + void* _pHeap; } ma_data_converter; -MA_API ma_result ma_data_converter_init(const ma_data_converter_config* pConfig, ma_data_converter* pConverter); -MA_API void ma_data_converter_uninit(ma_data_converter* pConverter); +MA_API ma_result ma_data_converter_get_heap_size(const ma_data_converter_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_data_converter_init_preallocated(const ma_data_converter_config* pConfig, void* pHeap, ma_data_converter* pConverter); +MA_API ma_result ma_data_converter_init(const ma_data_converter_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_converter* pConverter); +MA_API void ma_data_converter_uninit(ma_data_converter* pConverter, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_result ma_data_converter_process_pcm_frames(ma_data_converter* pConverter, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut); MA_API ma_result ma_data_converter_set_rate(ma_data_converter* pConverter, ma_uint32 sampleRateIn, ma_uint32 sampleRateOut); MA_API ma_result ma_data_converter_set_rate_ratio(ma_data_converter* pConverter, float ratioInOut); -MA_API ma_uint64 ma_data_converter_get_required_input_frame_count(const ma_data_converter* pConverter, ma_uint64 outputFrameCount); -MA_API ma_uint64 ma_data_converter_get_expected_output_frame_count(const ma_data_converter* pConverter, ma_uint64 inputFrameCount); MA_API ma_uint64 ma_data_converter_get_input_latency(const ma_data_converter* pConverter); MA_API ma_uint64 ma_data_converter_get_output_latency(const ma_data_converter* pConverter); +MA_API ma_result ma_data_converter_get_required_input_frame_count(const ma_data_converter* pConverter, ma_uint64 outputFrameCount, ma_uint64* pInputFrameCount); +MA_API ma_result ma_data_converter_get_expected_output_frame_count(const ma_data_converter* pConverter, ma_uint64 inputFrameCount, ma_uint64* pOutputFrameCount); +MA_API ma_result ma_data_converter_get_input_channel_map(const ma_data_converter* pConverter, ma_channel* pChannelMap, size_t channelMapCap); +MA_API ma_result ma_data_converter_get_output_channel_map(const ma_data_converter* pConverter, ma_channel* pChannelMap, size_t channelMapCap); +MA_API ma_result ma_data_converter_reset(ma_data_converter* pConverter); /************************************************************************************************************************************************************ @@ -2753,9 +5423,6 @@ This is used in the shuffle table to indicate that the channel index is undefine */ #define MA_CHANNEL_INDEX_NULL 255 -/* Retrieves the channel position of the specified channel based on miniaudio's default channel map. */ -MA_API ma_channel ma_channel_map_get_default_channel(ma_uint32 channelCount, ma_uint32 channelIndex); - /* Retrieves the channel position of the specified channel in the given channel map. @@ -2768,14 +5435,14 @@ Initializes a blank channel map. When a blank channel map is specified anywhere it indicates that the native channel map should be used. */ -MA_API void ma_channel_map_init_blank(ma_uint32 channels, ma_channel* pChannelMap); +MA_API void ma_channel_map_init_blank(ma_channel* pChannelMap, ma_uint32 channels); /* Helper for retrieving a standard channel map. -The output channel map buffer must have a capacity of at least `channels`. +The output channel map buffer must have a capacity of at least `channelMapCap`. */ -MA_API void ma_get_standard_channel_map(ma_standard_channel_map standardChannelMap, ma_uint32 channels, ma_channel* pChannelMap); +MA_API void ma_channel_map_init_standard(ma_standard_channel_map standardChannelMap, ma_channel* pChannelMap, size_t channelMapCap, ma_uint32 channels); /* Copies a channel map. @@ -2789,7 +5456,7 @@ Copies a channel map if one is specified, otherwise copies the default channel m The output buffer must have a capacity of at least `channels`. If not NULL, the input channel map must also have a capacity of at least `channels`. */ -MA_API void ma_channel_map_copy_or_default(ma_channel* pOut, const ma_channel* pIn, ma_uint32 channels); +MA_API void ma_channel_map_copy_or_default(ma_channel* pOut, size_t channelMapCapOut, const ma_channel* pIn, ma_uint32 channels); /* @@ -2804,7 +5471,7 @@ Invalid channel maps: The channel map buffer must have a capacity of at least `channels`. */ -MA_API ma_bool32 ma_channel_map_valid(ma_uint32 channels, const ma_channel* pChannelMap); +MA_API ma_bool32 ma_channel_map_is_valid(const ma_channel* pChannelMap, ma_uint32 channels); /* Helper for comparing two channel maps for equality. @@ -2813,14 +5480,14 @@ This assumes the channel count is the same between the two. Both channels map buffers must have a capacity of at least `channels`. */ -MA_API ma_bool32 ma_channel_map_equal(ma_uint32 channels, const ma_channel* pChannelMapA, const ma_channel* pChannelMapB); +MA_API ma_bool32 ma_channel_map_is_equal(const ma_channel* pChannelMapA, const ma_channel* pChannelMapB, ma_uint32 channels); /* Helper for determining if a channel map is blank (all channels set to MA_CHANNEL_NONE). The channel map buffer must have a capacity of at least `channels`. */ -MA_API ma_bool32 ma_channel_map_blank(ma_uint32 channels, const ma_channel* pChannelMap); +MA_API ma_bool32 ma_channel_map_is_blank(const ma_channel* pChannelMap, ma_uint32 channels); /* Helper for determining whether or not a channel is present in the given channel map. @@ -2860,10 +5527,10 @@ typedef struct ma_uint32 subbufferSizeInBytes; ma_uint32 subbufferCount; ma_uint32 subbufferStrideInBytes; - MA_ATOMIC ma_uint32 encodedReadOffset; /* Most significant bit is the loop flag. Lower 31 bits contains the actual offset in bytes. Must be used atomically. */ - MA_ATOMIC ma_uint32 encodedWriteOffset; /* Most significant bit is the loop flag. Lower 31 bits contains the actual offset in bytes. Must be used atomically. */ - ma_bool8 ownsBuffer; /* Used to know whether or not miniaudio is responsible for free()-ing the buffer. */ - ma_bool8 clearOnWriteAcquire; /* When set, clears the acquired write buffer before returning from ma_rb_acquire_write(). */ + MA_ATOMIC(4, ma_uint32) encodedReadOffset; /* Most significant bit is the loop flag. Lower 31 bits contains the actual offset in bytes. Must be used atomically. */ + MA_ATOMIC(4, ma_uint32) encodedWriteOffset; /* Most significant bit is the loop flag. Lower 31 bits contains the actual offset in bytes. Must be used atomically. */ + ma_bool8 ownsBuffer; /* Used to know whether or not miniaudio is responsible for free()-ing the buffer. */ + ma_bool8 clearOnWriteAcquire; /* When set, clears the acquired write buffer before returning from ma_rb_acquire_write(). */ ma_allocation_callbacks allocationCallbacks; } ma_rb; @@ -2872,9 +5539,9 @@ MA_API ma_result ma_rb_init(size_t bufferSizeInBytes, void* pOptionalPreallocate MA_API void ma_rb_uninit(ma_rb* pRB); MA_API void ma_rb_reset(ma_rb* pRB); MA_API ma_result ma_rb_acquire_read(ma_rb* pRB, size_t* pSizeInBytes, void** ppBufferOut); -MA_API ma_result ma_rb_commit_read(ma_rb* pRB, size_t sizeInBytes, void* pBufferOut); +MA_API ma_result ma_rb_commit_read(ma_rb* pRB, size_t sizeInBytes); MA_API ma_result ma_rb_acquire_write(ma_rb* pRB, size_t* pSizeInBytes, void** ppBufferOut); -MA_API ma_result ma_rb_commit_write(ma_rb* pRB, size_t sizeInBytes, void* pBufferOut); +MA_API ma_result ma_rb_commit_write(ma_rb* pRB, size_t sizeInBytes); MA_API ma_result ma_rb_seek_read(ma_rb* pRB, size_t offsetInBytes); MA_API ma_result ma_rb_seek_write(ma_rb* pRB, size_t offsetInBytes); MA_API ma_int32 ma_rb_pointer_distance(ma_rb* pRB); /* Returns the distance between the write pointer and the read pointer. Should never be negative for a correct program. Will return the number of bytes that can be read before the read pointer hits the write pointer. */ @@ -2898,9 +5565,9 @@ MA_API ma_result ma_pcm_rb_init(ma_format format, ma_uint32 channels, ma_uint32 MA_API void ma_pcm_rb_uninit(ma_pcm_rb* pRB); MA_API void ma_pcm_rb_reset(ma_pcm_rb* pRB); MA_API ma_result ma_pcm_rb_acquire_read(ma_pcm_rb* pRB, ma_uint32* pSizeInFrames, void** ppBufferOut); -MA_API ma_result ma_pcm_rb_commit_read(ma_pcm_rb* pRB, ma_uint32 sizeInFrames, void* pBufferOut); +MA_API ma_result ma_pcm_rb_commit_read(ma_pcm_rb* pRB, ma_uint32 sizeInFrames); MA_API ma_result ma_pcm_rb_acquire_write(ma_pcm_rb* pRB, ma_uint32* pSizeInFrames, void** ppBufferOut); -MA_API ma_result ma_pcm_rb_commit_write(ma_pcm_rb* pRB, ma_uint32 sizeInFrames, void* pBufferOut); +MA_API ma_result ma_pcm_rb_commit_write(ma_pcm_rb* pRB, ma_uint32 sizeInFrames); MA_API ma_result ma_pcm_rb_seek_read(ma_pcm_rb* pRB, ma_uint32 offsetInFrames); MA_API ma_result ma_pcm_rb_seek_write(ma_pcm_rb* pRB, ma_uint32 offsetInFrames); MA_API ma_int32 ma_pcm_rb_pointer_distance(ma_pcm_rb* pRB); /* Return value is in frames. */ @@ -2942,17 +5609,22 @@ Retrieves a human readable description of the given result code. MA_API const char* ma_result_description(ma_result result); /* -malloc(). Calls MA_MALLOC(). +malloc() */ MA_API void* ma_malloc(size_t sz, const ma_allocation_callbacks* pAllocationCallbacks); /* -realloc(). Calls MA_REALLOC(). +calloc() +*/ +MA_API void* ma_calloc(size_t sz, const ma_allocation_callbacks* pAllocationCallbacks); + +/* +realloc() */ MA_API void* ma_realloc(void* p, size_t sz, const ma_allocation_callbacks* pAllocationCallbacks); /* -free(). Calls MA_FREE(). +free() */ MA_API void ma_free(void* p, const ma_allocation_callbacks* pAllocationCallbacks); @@ -2994,6 +5666,413 @@ MA_API const char* ma_log_level_to_string(ma_uint32 logLevel); + +/************************************************************************************************************************************************************ + +Synchronization + +************************************************************************************************************************************************************/ +/* +Locks a spinlock. +*/ +MA_API ma_result ma_spinlock_lock(volatile ma_spinlock* pSpinlock); + +/* +Locks a spinlock, but does not yield() when looping. +*/ +MA_API ma_result ma_spinlock_lock_noyield(volatile ma_spinlock* pSpinlock); + +/* +Unlocks a spinlock. +*/ +MA_API ma_result ma_spinlock_unlock(volatile ma_spinlock* pSpinlock); + + +#ifndef MA_NO_THREADING + +/* +Creates a mutex. + +A mutex must be created from a valid context. A mutex is initially unlocked. +*/ +MA_API ma_result ma_mutex_init(ma_mutex* pMutex); + +/* +Deletes a mutex. +*/ +MA_API void ma_mutex_uninit(ma_mutex* pMutex); + +/* +Locks a mutex with an infinite timeout. +*/ +MA_API void ma_mutex_lock(ma_mutex* pMutex); + +/* +Unlocks a mutex. +*/ +MA_API void ma_mutex_unlock(ma_mutex* pMutex); + + +/* +Initializes an auto-reset event. +*/ +MA_API ma_result ma_event_init(ma_event* pEvent); + +/* +Uninitializes an auto-reset event. +*/ +MA_API void ma_event_uninit(ma_event* pEvent); + +/* +Waits for the specified auto-reset event to become signalled. +*/ +MA_API ma_result ma_event_wait(ma_event* pEvent); + +/* +Signals the specified auto-reset event. +*/ +MA_API ma_result ma_event_signal(ma_event* pEvent); +#endif /* MA_NO_THREADING */ + + +/* +Fence +===== +This locks while the counter is larger than 0. Counter can be incremented and decremented by any +thread, but care needs to be taken when waiting. It is possible for one thread to acquire the +fence just as another thread returns from ma_fence_wait(). + +The idea behind a fence is to allow you to wait for a group of operations to complete. When an +operation starts, the counter is incremented which locks the fence. When the operation completes, +the fence will be released which decrements the counter. ma_fence_wait() will block until the +counter hits zero. + +If threading is disabled, ma_fence_wait() will spin on the counter. +*/ +typedef struct +{ +#ifndef MA_NO_THREADING + ma_event e; +#endif + ma_uint32 counter; +} ma_fence; + +MA_API ma_result ma_fence_init(ma_fence* pFence); +MA_API void ma_fence_uninit(ma_fence* pFence); +MA_API ma_result ma_fence_acquire(ma_fence* pFence); /* Increment counter. */ +MA_API ma_result ma_fence_release(ma_fence* pFence); /* Decrement counter. */ +MA_API ma_result ma_fence_wait(ma_fence* pFence); /* Wait for counter to reach 0. */ + + + +/* +Notification callback for asynchronous operations. +*/ +typedef void ma_async_notification; + +typedef struct +{ + void (* onSignal)(ma_async_notification* pNotification); +} ma_async_notification_callbacks; + +MA_API ma_result ma_async_notification_signal(ma_async_notification* pNotification); + + +/* +Simple polling notification. + +This just sets a variable when the notification has been signalled which is then polled with ma_async_notification_poll_is_signalled() +*/ +typedef struct +{ + ma_async_notification_callbacks cb; + ma_bool32 signalled; +} ma_async_notification_poll; + +MA_API ma_result ma_async_notification_poll_init(ma_async_notification_poll* pNotificationPoll); +MA_API ma_bool32 ma_async_notification_poll_is_signalled(const ma_async_notification_poll* pNotificationPoll); + + +/* +Event Notification + +This uses an ma_event. If threading is disabled (MA_NO_THREADING), initialization will fail. +*/ +typedef struct +{ + ma_async_notification_callbacks cb; +#ifndef MA_NO_THREADING + ma_event e; +#endif +} ma_async_notification_event; + +MA_API ma_result ma_async_notification_event_init(ma_async_notification_event* pNotificationEvent); +MA_API ma_result ma_async_notification_event_uninit(ma_async_notification_event* pNotificationEvent); +MA_API ma_result ma_async_notification_event_wait(ma_async_notification_event* pNotificationEvent); +MA_API ma_result ma_async_notification_event_signal(ma_async_notification_event* pNotificationEvent); + + + + +/************************************************************************************************************************************************************ + +Job Queue + +************************************************************************************************************************************************************/ + +/* +Slot Allocator +-------------- +The idea of the slot allocator is for it to be used in conjunction with a fixed sized buffer. You use the slot allocator to allocator an index that can be used +as the insertion point for an object. + +Slots are reference counted to help mitigate the ABA problem in the lock-free queue we use for tracking jobs. + +The slot index is stored in the low 32 bits. The reference counter is stored in the high 32 bits: + + +-----------------+-----------------+ + | 32 Bits | 32 Bits | + +-----------------+-----------------+ + | Reference Count | Slot Index | + +-----------------+-----------------+ +*/ +typedef struct +{ + ma_uint32 capacity; /* The number of slots to make available. */ +} ma_slot_allocator_config; + +MA_API ma_slot_allocator_config ma_slot_allocator_config_init(ma_uint32 capacity); + + +typedef struct +{ + MA_ATOMIC(4, ma_uint32) bitfield; /* Must be used atomically because the allocation and freeing routines need to make copies of this which must never be optimized away by the compiler. */ +} ma_slot_allocator_group; + +typedef struct +{ + ma_slot_allocator_group* pGroups; /* Slots are grouped in chunks of 32. */ + ma_uint32* pSlots; /* 32 bits for reference counting for ABA mitigation. */ + ma_uint32 count; /* Allocation count. */ + ma_uint32 capacity; + + /* Memory management. */ + ma_bool32 _ownsHeap; + void* _pHeap; +} ma_slot_allocator; + +MA_API ma_result ma_slot_allocator_get_heap_size(const ma_slot_allocator_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_slot_allocator_init_preallocated(const ma_slot_allocator_config* pConfig, void* pHeap, ma_slot_allocator* pAllocator); +MA_API ma_result ma_slot_allocator_init(const ma_slot_allocator_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_slot_allocator* pAllocator); +MA_API void ma_slot_allocator_uninit(ma_slot_allocator* pAllocator, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API ma_result ma_slot_allocator_alloc(ma_slot_allocator* pAllocator, ma_uint64* pSlot); +MA_API ma_result ma_slot_allocator_free(ma_slot_allocator* pAllocator, ma_uint64 slot); + + +typedef struct ma_job ma_job; + +/* +Callback for processing a job. Each job type will have their own processing callback which will be +called by ma_job_process(). +*/ +typedef ma_result (* ma_job_proc)(ma_job* pJob); + +/* When a job type is added here an callback needs to be added go "g_jobVTable" in the implementation section. */ +typedef enum +{ + /* Miscellaneous. */ + MA_JOB_TYPE_QUIT = 0, + MA_JOB_TYPE_CUSTOM, + + /* Resource Manager. */ + MA_JOB_TYPE_RESOURCE_MANAGER_LOAD_DATA_BUFFER_NODE, + MA_JOB_TYPE_RESOURCE_MANAGER_FREE_DATA_BUFFER_NODE, + MA_JOB_TYPE_RESOURCE_MANAGER_PAGE_DATA_BUFFER_NODE, + MA_JOB_TYPE_RESOURCE_MANAGER_LOAD_DATA_BUFFER, + MA_JOB_TYPE_RESOURCE_MANAGER_FREE_DATA_BUFFER, + MA_JOB_TYPE_RESOURCE_MANAGER_LOAD_DATA_STREAM, + MA_JOB_TYPE_RESOURCE_MANAGER_FREE_DATA_STREAM, + MA_JOB_TYPE_RESOURCE_MANAGER_PAGE_DATA_STREAM, + MA_JOB_TYPE_RESOURCE_MANAGER_SEEK_DATA_STREAM, + + /* Device. */ + MA_JOB_TYPE_DEVICE_AAUDIO_REROUTE, + + /* Count. Must always be last. */ + MA_JOB_TYPE_COUNT +} ma_job_type; + +struct ma_job +{ + union + { + struct + { + ma_uint16 code; /* Job type. */ + ma_uint16 slot; /* Index into a ma_slot_allocator. */ + ma_uint32 refcount; + } breakup; + ma_uint64 allocation; + } toc; /* 8 bytes. We encode the job code into the slot allocation data to save space. */ + MA_ATOMIC(8, ma_uint64) next; /* refcount + slot for the next item. Does not include the job code. */ + ma_uint32 order; /* Execution order. Used to create a data dependency and ensure a job is executed in order. Usage is contextual depending on the job type. */ + + union + { + /* Miscellaneous. */ + struct + { + ma_job_proc proc; + ma_uintptr data0; + ma_uintptr data1; + } custom; + + /* Resource Manager */ + union + { + struct + { + /*ma_resource_manager**/ void* pResourceManager; + /*ma_resource_manager_data_buffer_node**/ void* pDataBufferNode; + char* pFilePath; + wchar_t* pFilePathW; + ma_uint32 flags; /* Resource manager data source flags that were used when initializing the data buffer. */ + ma_async_notification* pInitNotification; /* Signalled when the data buffer has been initialized and the format/channels/rate can be retrieved. */ + ma_async_notification* pDoneNotification; /* Signalled when the data buffer has been fully decoded. Will be passed through to MA_JOB_TYPE_RESOURCE_MANAGER_PAGE_DATA_BUFFER_NODE when decoding. */ + ma_fence* pInitFence; /* Released when initialization of the decoder is complete. */ + ma_fence* pDoneFence; /* Released if initialization of the decoder fails. Passed through to PAGE_DATA_BUFFER_NODE untouched if init is successful. */ + } loadDataBufferNode; + struct + { + /*ma_resource_manager**/ void* pResourceManager; + /*ma_resource_manager_data_buffer_node**/ void* pDataBufferNode; + ma_async_notification* pDoneNotification; + ma_fence* pDoneFence; + } freeDataBufferNode; + struct + { + /*ma_resource_manager**/ void* pResourceManager; + /*ma_resource_manager_data_buffer_node**/ void* pDataBufferNode; + /*ma_decoder**/ void* pDecoder; + ma_async_notification* pDoneNotification; /* Signalled when the data buffer has been fully decoded. */ + ma_fence* pDoneFence; /* Passed through from LOAD_DATA_BUFFER_NODE and released when the data buffer completes decoding or an error occurs. */ + } pageDataBufferNode; + + struct + { + /*ma_resource_manager_data_buffer**/ void* pDataBuffer; + ma_async_notification* pInitNotification; /* Signalled when the data buffer has been initialized and the format/channels/rate can be retrieved. */ + ma_async_notification* pDoneNotification; /* Signalled when the data buffer has been fully decoded. */ + ma_fence* pInitFence; /* Released when the data buffer has been initialized and the format/channels/rate can be retrieved. */ + ma_fence* pDoneFence; /* Released when the data buffer has been fully decoded. */ + ma_uint64 rangeBegInPCMFrames; + ma_uint64 rangeEndInPCMFrames; + ma_uint64 loopPointBegInPCMFrames; + ma_uint64 loopPointEndInPCMFrames; + ma_uint32 isLooping; + } loadDataBuffer; + struct + { + /*ma_resource_manager_data_buffer**/ void* pDataBuffer; + ma_async_notification* pDoneNotification; + ma_fence* pDoneFence; + } freeDataBuffer; + + struct + { + /*ma_resource_manager_data_stream**/ void* pDataStream; + char* pFilePath; /* Allocated when the job is posted, freed by the job thread after loading. */ + wchar_t* pFilePathW; /* ^ As above ^. Only used if pFilePath is NULL. */ + ma_uint64 initialSeekPoint; + ma_async_notification* pInitNotification; /* Signalled after the first two pages have been decoded and frames can be read from the stream. */ + ma_fence* pInitFence; + } loadDataStream; + struct + { + /*ma_resource_manager_data_stream**/ void* pDataStream; + ma_async_notification* pDoneNotification; + ma_fence* pDoneFence; + } freeDataStream; + struct + { + /*ma_resource_manager_data_stream**/ void* pDataStream; + ma_uint32 pageIndex; /* The index of the page to decode into. */ + } pageDataStream; + struct + { + /*ma_resource_manager_data_stream**/ void* pDataStream; + ma_uint64 frameIndex; + } seekDataStream; + } resourceManager; + + /* Device. */ + union + { + union + { + struct + { + /*ma_device**/ void* pDevice; + /*ma_device_type*/ ma_uint32 deviceType; + } reroute; + } aaudio; + } device; + } data; +}; + +MA_API ma_job ma_job_init(ma_uint16 code); +MA_API ma_result ma_job_process(ma_job* pJob); + + +/* +When set, ma_job_queue_next() will not wait and no semaphore will be signaled in +ma_job_queue_post(). ma_job_queue_next() will return MA_NO_DATA_AVAILABLE if nothing is available. + +This flag should always be used for platforms that do not support multithreading. +*/ +typedef enum +{ + MA_JOB_QUEUE_FLAG_NON_BLOCKING = 0x00000001 +} ma_job_queue_flags; + +typedef struct +{ + ma_uint32 flags; + ma_uint32 capacity; /* The maximum number of jobs that can fit in the queue at a time. */ +} ma_job_queue_config; + +MA_API ma_job_queue_config ma_job_queue_config_init(ma_uint32 flags, ma_uint32 capacity); + + +typedef struct +{ + ma_uint32 flags; /* Flags passed in at initialization time. */ + ma_uint32 capacity; /* The maximum number of jobs that can fit in the queue at a time. Set by the config. */ + MA_ATOMIC(8, ma_uint64) head; /* The first item in the list. Required for removing from the top of the list. */ + MA_ATOMIC(8, ma_uint64) tail; /* The last item in the list. Required for appending to the end of the list. */ +#ifndef MA_NO_THREADING + ma_semaphore sem; /* Only used when MA_JOB_QUEUE_FLAG_NON_BLOCKING is unset. */ +#endif + ma_slot_allocator allocator; + ma_job* pJobs; +#ifndef MA_USE_EXPERIMENTAL_LOCK_FREE_JOB_QUEUE + ma_spinlock lock; +#endif + + /* Memory management. */ + void* _pHeap; + ma_bool32 _ownsHeap; +} ma_job_queue; + +MA_API ma_result ma_job_queue_get_heap_size(const ma_job_queue_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_job_queue_init_preallocated(const ma_job_queue_config* pConfig, void* pHeap, ma_job_queue* pQueue); +MA_API ma_result ma_job_queue_init(const ma_job_queue_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_job_queue* pQueue); +MA_API void ma_job_queue_uninit(ma_job_queue* pQueue, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API ma_result ma_job_queue_post(ma_job_queue* pQueue, const ma_job* pJob); +MA_API ma_result ma_job_queue_next(ma_job_queue* pQueue, ma_job* pJob); /* Returns MA_CANCELLED if the next job is a quit job. */ + + + /************************************************************************************************************************************************************ ************************************************************************************************************************************************************* @@ -3100,11 +6179,14 @@ This section contains the APIs for device playback and capture. Here is where yo #define MA_HAS_NULL #endif -#define MA_STATE_UNINITIALIZED 0 -#define MA_STATE_STOPPED 1 /* The device's default state after initialization. */ -#define MA_STATE_STARTED 2 /* The device is started and is requesting and/or delivering audio data. */ -#define MA_STATE_STARTING 3 /* Transitioning from a stopped state to started. */ -#define MA_STATE_STOPPING 4 /* Transitioning from a started state to stopped. */ +typedef enum +{ + ma_device_state_uninitialized = 0, + ma_device_state_stopped = 1, /* The device's default state after initialization. */ + ma_device_state_started = 2, /* The device is started and is requesting and/or delivering audio data. */ + ma_device_state_starting = 3, /* Transitioning from a stopped state to started. */ + ma_device_state_stopping = 4 /* Transitioning from a started state to stopped. */ +} ma_device_state; #ifdef MA_SUPPORT_WASAPI /* We need a IMMNotificationClient object for WASAPI. */ @@ -3139,6 +6221,114 @@ typedef enum #define MA_BACKEND_COUNT (ma_backend_null+1) +/* +Device job thread. This is used by backends that require asynchronous processing of certain +operations. It is not used by all backends. + +The device job thread is made up of a thread and a job queue. You can post a job to the thread with +ma_device_job_thread_post(). The thread will do the processing of the job. +*/ +typedef struct +{ + ma_bool32 noThread; /* Set this to true if you want to process jobs yourself. */ + ma_uint32 jobQueueCapacity; + ma_uint32 jobQueueFlags; +} ma_device_job_thread_config; + +MA_API ma_device_job_thread_config ma_device_job_thread_config_init(void); + +typedef struct +{ + ma_thread thread; + ma_job_queue jobQueue; + ma_bool32 _hasThread; +} ma_device_job_thread; + +MA_API ma_result ma_device_job_thread_init(const ma_device_job_thread_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_device_job_thread* pJobThread); +MA_API void ma_device_job_thread_uninit(ma_device_job_thread* pJobThread, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API ma_result ma_device_job_thread_post(ma_device_job_thread* pJobThread, const ma_job* pJob); +MA_API ma_result ma_device_job_thread_next(ma_device_job_thread* pJobThread, ma_job* pJob); + + + +/* Device notification types. */ +typedef enum +{ + ma_device_notification_type_started, + ma_device_notification_type_stopped, + ma_device_notification_type_rerouted, + ma_device_notification_type_interruption_began, + ma_device_notification_type_interruption_ended +} ma_device_notification_type; + +typedef struct +{ + ma_device* pDevice; + ma_device_notification_type type; + union + { + struct + { + int _unused; + } started; + struct + { + int _unused; + } stopped; + struct + { + int _unused; + } rerouted; + struct + { + int _unused; + } interruption; + } data; +} ma_device_notification; + +/* +The notification callback for when the application should be notified of a change to the device. + +This callback is used for notifying the application of changes such as when the device has started, +stopped, rerouted or an interruption has occurred. Note that not all backends will post all +notification types. For example, some backends will perform automatic stream routing without any +kind of notification to the host program which means miniaudio will never know about it and will +never be able to fire the rerouted notification. You should keep this in mind when designing your +program. + +The stopped notification will *not* get fired when a device is rerouted. + + +Parameters +---------- +pNotification (in) + A pointer to a structure containing information about the event. Use the `pDevice` member of + this object to retrieve the relevant device. The `type` member can be used to discriminate + against each of the notification types. + + +Remarks +------- +Do not restart or uninitialize the device from the callback. + +Not all notifications will be triggered by all backends, however the started and stopped events +should be reliable for all backends. Some backends do not have a good way to detect device +stoppages due to unplugging the device which may result in the stopped callback not getting +fired. This has been observed with at least one BSD variant. + +The rerouted notification is fired *after* the reroute has occurred. The stopped notification will +*not* get fired when a device is rerouted. The following backends are known to do automatic stream +rerouting, but do not have a way to be notified of the change: + + * DirectSound + +The interruption notifications are used on mobile platforms for detecting when audio is interrupted +due to things like an incoming phone call. Currently this is only implemented on iOS. None of the +Android backends will report this notification. +*/ +typedef void (* ma_device_notification_proc)(const ma_device_notification* pNotification); + + /* The callback for processing audio data from the device. @@ -3179,9 +6369,14 @@ callback. The following APIs cannot be called from inside the callback: The proper way to stop the device is to call `ma_device_stop()` from a different thread, normally the main application thread. */ -typedef void (* ma_device_callback_proc)(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount); +typedef void (* ma_device_data_proc)(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount); + + + /* +DEPRECATED. Use ma_device_notification_proc instead. + The callback for when the device has been stopped. This will be called when the device is stopped explicitly with `ma_device_stop()` and also called implicitly when the device is stopped through external forces @@ -3198,41 +6393,7 @@ Remarks ------- Do not restart or uninitialize the device from the callback. */ -typedef void (* ma_stop_proc)(ma_device* pDevice); - -/* -The callback for handling log messages. - - -Parameters ----------- -pContext (in) - A pointer to the context the log message originated from. - -pDevice (in) - A pointer to the device the log message originate from, if any. This can be null, in which case the message came from the context. - -logLevel (in) - The log level. This can be one of the following: - - +----------------------+ - | Log Level | - +----------------------+ - | MA_LOG_LEVEL_DEBUG | - | MA_LOG_LEVEL_INFO | - | MA_LOG_LEVEL_WARNING | - | MA_LOG_LEVEL_ERROR | - +----------------------+ - -message (in) - The log message. - - -Remarks -------- -Do not modify the state of the device from inside the callback. -*/ -typedef void (* ma_log_proc)(ma_context* pContext, ma_device* pDevice, ma_uint32 logLevel, const char* message); +typedef void (* ma_stop_proc)(ma_device* pDevice); /* DEPRECATED. Use ma_device_notification_proc instead. */ typedef enum { @@ -3378,30 +6539,17 @@ typedef struct ma_backend_callbacks ma_backend_callbacks; #define MA_DATA_FORMAT_FLAG_EXCLUSIVE_MODE (1U << 1) /* If set, this is supported in exclusive mode. Otherwise not natively supported by exclusive mode. */ +#ifndef MA_MAX_DEVICE_NAME_LENGTH +#define MA_MAX_DEVICE_NAME_LENGTH 255 +#endif + typedef struct { /* Basic info. This is the only information guaranteed to be filled in during device enumeration. */ ma_device_id id; - char name[256]; + char name[MA_MAX_DEVICE_NAME_LENGTH + 1]; /* +1 for null terminator. */ ma_bool32 isDefault; - /* - Detailed info. As much of this is filled as possible with ma_context_get_device_info(). Note that you are allowed to initialize - a device with settings outside of this range, but it just means the data will be converted using miniaudio's data conversion - pipeline before sending the data to/from the device. Most programs will need to not worry about these values, but it's provided - here mainly for informational purposes or in the rare case that someone might find it useful. - - These will be set to 0 when returned by ma_context_enumerate_devices() or ma_context_get_devices(). - */ - ma_uint32 formatCount; - ma_format formats[ma_format_count]; - ma_uint32 minChannels; - ma_uint32 maxChannels; - ma_uint32 minSampleRate; - ma_uint32 maxSampleRate; - - - /* Experimental. Don't use these right now. */ ma_uint32 nativeDataFormatCount; struct { @@ -3420,29 +6568,21 @@ struct ma_device_config ma_uint32 periodSizeInMilliseconds; ma_uint32 periods; ma_performance_profile performanceProfile; - ma_bool8 noPreZeroedOutputBuffer; /* When set to true, the contents of the output buffer passed into the data callback will be left undefined rather than initialized to zero. */ + ma_bool8 noPreSilencedOutputBuffer; /* When set to true, the contents of the output buffer passed into the data callback will be left undefined rather than initialized to silence. */ ma_bool8 noClip; /* When set to true, the contents of the output buffer passed into the data callback will be clipped after returning. Only applies when the playback sample format is f32. */ - ma_device_callback_proc dataCallback; + ma_bool8 noDisableDenormals; /* Do not disable denormals when firing the data callback. */ + ma_bool8 noFixedSizedCallback; /* Disables strict fixed-sized data callbacks. Setting this to true will result in the period size being treated only as a hint to the backend. This is an optimization for those who don't need fixed sized callbacks. */ + ma_device_data_proc dataCallback; + ma_device_notification_proc notificationCallback; ma_stop_proc stopCallback; void* pUserData; - struct - { - ma_resample_algorithm algorithm; - struct - { - ma_uint32 lpfOrder; - } linear; - struct - { - int quality; - } speex; - } resampling; + ma_resampler_config resampling; struct { const ma_device_id* pDeviceID; ma_format format; ma_uint32 channels; - ma_channel channelMap[MA_MAX_CHANNELS]; + ma_channel* pChannelMap; ma_channel_mix_mode channelMixMode; ma_share_mode shareMode; } playback; @@ -3451,7 +6591,7 @@ struct ma_device_config const ma_device_id* pDeviceID; ma_format format; ma_uint32 channels; - ma_channel channelMap[MA_MAX_CHANNELS]; + ma_channel* pChannelMap; ma_channel_mix_mode channelMixMode; ma_share_mode shareMode; } capture; @@ -3489,6 +6629,7 @@ struct ma_device_config ma_aaudio_usage usage; ma_aaudio_content_type contentType; ma_aaudio_input_preset inputPreset; + ma_bool32 noAutoStartAfterReroute; } aaudio; }; @@ -3554,7 +6695,7 @@ callbacks defined in this structure. Once the context has been initialized you can initialize a device. Before doing so, however, the application may want to know which physical devices are available. This is where `onContextEnumerateDevices()` comes in. This is fairly simple. For each device, fire the given callback with, at a minimum, the basic information filled out in `ma_device_info`. When the callback returns `MA_FALSE`, enumeration -needs to stop and the `onContextEnumerateDevices()` function return with a success code. +needs to stop and the `onContextEnumerateDevices()` function returns with a success code. Detailed device information can be retrieved from a device ID using `onContextGetDeviceInfo()`. This takes as input the device type and ID, and on output returns detailed information about the device in `ma_device_info`. The `onContextGetDeviceInfo()` callback must handle the @@ -3569,7 +6710,7 @@ internally by miniaudio. On input, if the sample format is set to `ma_format_unknown`, the backend is free to use whatever sample format it desires, so long as it's supported by miniaudio. When the channel count is set to 0, the backend should use the device's native channel count. The same applies for -sample rate. For the channel map, the default should be used when `ma_channel_map_blank()` returns true (all channels set to +sample rate. For the channel map, the default should be used when `ma_channel_map_is_blank()` returns true (all channels set to `MA_CHANNEL_NONE`). On input, the `periodSizeInFrames` or `periodSizeInMilliseconds` option should always be set. The backend should inspect both of these variables. If `periodSizeInFrames` is set, it should take priority, otherwise it needs to be derived from the period size in milliseconds (`periodSizeInMilliseconds`) and the sample rate, keeping in mind that the sample rate may be 0, in which case the @@ -3588,14 +6729,17 @@ This allows miniaudio to then process any necessary data conversion and then pas If the backend requires absolute flexibility with it's data delivery, it can optionally implement the `onDeviceDataLoop()` callback which will allow it to implement the logic that will run on the audio thread. This is much more advanced and is completely optional. -The audio thread should run data delivery logic in a loop while `ma_device_get_state() == MA_STATE_STARTED` and no errors have been +The audio thread should run data delivery logic in a loop while `ma_device_get_state() == ma_device_state_started` and no errors have been encounted. Do not start or stop the device here. That will be handled from outside the `onDeviceDataLoop()` callback. The invocation of the `onDeviceDataLoop()` callback will be handled by miniaudio. When you start the device, miniaudio will fire this -callback. When the device is stopped, the `ma_device_get_state() == MA_STATE_STARTED` condition will fail and the loop will be terminated +callback. When the device is stopped, the `ma_device_get_state() == ma_device_state_started` condition will fail and the loop will be terminated which will then fall through to the part that stops the device. For an example on how to implement the `onDeviceDataLoop()` callback, look at `ma_device_audio_thread__default_read_write()`. Implement the `onDeviceDataLoopWakeup()` callback if you need a mechanism to wake up the audio thread. + +If the backend supports an optimized retrieval of device information from an initialized `ma_device` object, it should implement the +`onDeviceGetInfo()` callback. This is optional, in which case it will fall back to `onContextGetDeviceInfo()` which is less efficient. */ struct ma_backend_callbacks { @@ -3611,11 +6755,11 @@ struct ma_backend_callbacks ma_result (* onDeviceWrite)(ma_device* pDevice, const void* pFrames, ma_uint32 frameCount, ma_uint32* pFramesWritten); ma_result (* onDeviceDataLoop)(ma_device* pDevice); ma_result (* onDeviceDataLoopWakeup)(ma_device* pDevice); + ma_result (* onDeviceGetInfo)(ma_device* pDevice, ma_device_type type, ma_device_info* pDeviceInfo); }; struct ma_context_config { - ma_log_proc logCallback; /* Legacy logging callback. Will be removed in version 0.11. */ ma_log* pLog; ma_thread_priority threadPriority; size_t threadStackSize; @@ -3678,7 +6822,6 @@ struct ma_context ma_backend backend; /* DirectSound, ALSA, etc. */ ma_log* pLog; ma_log log; /* Only used if the log is owned by the context. The pLog member will be set to &log in this case. */ - ma_log_proc logCallback; /* Legacy callback. Will be removed in version 0.11. */ ma_thread_priority threadPriority; size_t threadStackSize; void* pUserData; @@ -3863,6 +7006,7 @@ struct ma_context ma_proc pa_stream_set_write_callback; ma_proc pa_stream_set_read_callback; ma_proc pa_stream_set_suspended_callback; + ma_proc pa_stream_set_moved_callback; ma_proc pa_stream_is_suspended; ma_proc pa_stream_flush; ma_proc pa_stream_drain; @@ -3878,6 +7022,8 @@ struct ma_context /*pa_mainloop**/ ma_ptr pMainLoop; /*pa_context**/ ma_ptr pPulseContext; + char* pApplicationName; /* Set when the context is initialized. Used by devices for their local pa_context objects. */ + char* pServerName; /* Set when the context is initialized. Used by devices for their local pa_context objects. */ } pulse; #endif #ifdef MA_SUPPORT_JACK @@ -4004,6 +7150,7 @@ struct ma_context ma_proc AAudioStream_getFramesPerBurst; ma_proc AAudioStream_requestStart; ma_proc AAudioStream_requestStop; + ma_device_job_thread jobThread; /* For processing operations outside of the error callback, specifically device disconnections and rerouting. */ } aaudio; #endif #ifdef MA_SUPPORT_OPENSL @@ -4087,37 +7234,39 @@ struct ma_device ma_context* pContext; ma_device_type type; ma_uint32 sampleRate; - MA_ATOMIC ma_uint32 state; /* The state of the device is variable and can change at any time on any thread. Must be used atomically. */ - ma_device_callback_proc onData; /* Set once at initialization time and should not be changed after. */ - ma_stop_proc onStop; /* Set once at initialization time and should not be changed after. */ - void* pUserData; /* Application defined data. */ + MA_ATOMIC(4, ma_device_state) state; /* The state of the device is variable and can change at any time on any thread. Must be used atomically. */ + ma_device_data_proc onData; /* Set once at initialization time and should not be changed after. */ + ma_device_notification_proc onNotification; /* Set once at initialization time and should not be changed after. */ + ma_stop_proc onStop; /* DEPRECATED. Use the notification callback instead. Set once at initialization time and should not be changed after. */ + void* pUserData; /* Application defined data. */ ma_mutex startStopLock; ma_event wakeupEvent; ma_event startEvent; ma_event stopEvent; ma_thread thread; - ma_result workResult; /* This is set by the worker thread after it's finished doing a job. */ - ma_bool8 isOwnerOfContext; /* When set to true, uninitializing the device will also uninitialize the context. Set to true when NULL is passed into ma_device_init(). */ - ma_bool8 noPreZeroedOutputBuffer; + ma_result workResult; /* This is set by the worker thread after it's finished doing a job. */ + ma_bool8 isOwnerOfContext; /* When set to true, uninitializing the device will also uninitialize the context. Set to true when NULL is passed into ma_device_init(). */ + ma_bool8 noPreSilencedOutputBuffer; ma_bool8 noClip; - MA_ATOMIC float masterVolumeFactor; /* Linear 0..1. Can be read and written simultaneously by different threads. Must be used atomically. */ - ma_duplex_rb duplexRB; /* Intermediary buffer for duplex device on asynchronous backends. */ + ma_bool8 noDisableDenormals; + ma_bool8 noFixedSizedCallback; + MA_ATOMIC(4, float) masterVolumeFactor; /* Linear 0..1. Can be read and written simultaneously by different threads. Must be used atomically. */ + ma_duplex_rb duplexRB; /* Intermediary buffer for duplex device on asynchronous backends. */ struct { ma_resample_algorithm algorithm; + ma_resampling_backend_vtable* pBackendVTable; + void* pBackendUserData; struct { ma_uint32 lpfOrder; } linear; - struct - { - int quality; - } speex; } resampling; struct { + ma_device_id* pID; /* Set to NULL if using default ID, otherwise set to the address of "id". */ ma_device_id id; /* If using an explicit device, will be set to a copy of the ID used for initialization. Otherwise cleared to 0. */ - char name[256]; /* Maybe temporary. Likely to be replaced with a query API. */ + char name[MA_MAX_DEVICE_NAME_LENGTH + 1]; /* Maybe temporary. Likely to be replaced with a query API. */ ma_share_mode shareMode; /* Set to whatever was passed in when the device was initialized. */ ma_format format; ma_uint32 channels; @@ -4130,11 +7279,19 @@ struct ma_device ma_uint32 internalPeriods; ma_channel_mix_mode channelMixMode; ma_data_converter converter; + void* pIntermediaryBuffer; /* For implementing fixed sized buffer callbacks. Will be null if using variable sized callbacks. */ + ma_uint32 intermediaryBufferCap; + ma_uint32 intermediaryBufferLen; /* How many valid frames are sitting in the intermediary buffer. */ + void* pInputCache; /* In external format. Can be null. */ + ma_uint64 inputCacheCap; + ma_uint64 inputCacheConsumed; + ma_uint64 inputCacheRemaining; } playback; struct { + ma_device_id* pID; /* Set to NULL if using default ID, otherwise set to the address of "id". */ ma_device_id id; /* If using an explicit device, will be set to a copy of the ID used for initialization. Otherwise cleared to 0. */ - char name[256]; /* Maybe temporary. Likely to be replaced with a query API. */ + char name[MA_MAX_DEVICE_NAME_LENGTH + 1]; /* Maybe temporary. Likely to be replaced with a query API. */ ma_share_mode shareMode; /* Set to whatever was passed in when the device was initialized. */ ma_format format; ma_uint32 channels; @@ -4147,6 +7304,9 @@ struct ma_device ma_uint32 internalPeriods; ma_channel_mix_mode channelMixMode; ma_data_converter converter; + void* pIntermediaryBuffer; /* For implementing fixed sized buffer callbacks. Will be null if using variable sized callbacks. */ + ma_uint32 intermediaryBufferCap; + ma_uint32 intermediaryBufferLen; /* How many valid frames are sitting in the intermediary buffer. */ } capture; union @@ -4162,16 +7322,22 @@ struct ma_device ma_IMMNotificationClient notificationClient; /*HANDLE*/ ma_handle hEventPlayback; /* Auto reset. Initialized to signaled. */ /*HANDLE*/ ma_handle hEventCapture; /* Auto reset. Initialized to unsignaled. */ - ma_uint32 actualPeriodSizeInFramesPlayback; /* Value from GetBufferSize(). internalPeriodSizeInFrames is not set to the _actual_ buffer size when low-latency shared mode is being used due to the way the IAudioClient3 API works. */ - ma_uint32 actualPeriodSizeInFramesCapture; + ma_uint32 actualBufferSizeInFramesPlayback; /* Value from GetBufferSize(). internalPeriodSizeInFrames is not set to the _actual_ buffer size when low-latency shared mode is being used due to the way the IAudioClient3 API works. */ + ma_uint32 actualBufferSizeInFramesCapture; ma_uint32 originalPeriodSizeInFrames; ma_uint32 originalPeriodSizeInMilliseconds; ma_uint32 originalPeriods; ma_performance_profile originalPerformanceProfile; ma_uint32 periodSizeInFramesPlayback; ma_uint32 periodSizeInFramesCapture; - MA_ATOMIC ma_bool32 isStartedCapture; /* Can be read and written simultaneously across different threads. Must be used atomically, and must be 32-bit. */ - MA_ATOMIC ma_bool32 isStartedPlayback; /* Can be read and written simultaneously across different threads. Must be used atomically, and must be 32-bit. */ + void* pMappedBufferCapture; + ma_uint32 mappedBufferCaptureCap; + ma_uint32 mappedBufferCaptureLen; + void* pMappedBufferPlayback; + ma_uint32 mappedBufferPlaybackCap; + ma_uint32 mappedBufferPlaybackLen; + MA_ATOMIC(4, ma_bool32) isStartedCapture; /* Can be read and written simultaneously across different threads. Must be used atomically, and must be 32-bit. */ + MA_ATOMIC(4, ma_bool32) isStartedPlayback; /* Can be read and written simultaneously across different threads. Must be used atomically, and must be 32-bit. */ ma_bool8 noAutoConvertSRC; /* When set to true, disables the use of AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM. */ ma_bool8 noDefaultQualitySRC; /* When set to true, disables the use of AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY. */ ma_bool8 noHardwareOffloading; @@ -4228,6 +7394,8 @@ struct ma_device #ifdef MA_SUPPORT_PULSEAUDIO struct { + /*pa_mainloop**/ ma_ptr pMainLoop; + /*pa_context**/ ma_ptr pPulseContext; /*pa_stream**/ ma_ptr pStreamPlayback; /*pa_stream**/ ma_ptr pStreamCapture; } pulse; @@ -4236,8 +7404,8 @@ struct ma_device struct { /*jack_client_t**/ ma_ptr pClient; - /*jack_port_t**/ ma_ptr pPortsPlayback[MA_MAX_CHANNELS]; - /*jack_port_t**/ ma_ptr pPortsCapture[MA_MAX_CHANNELS]; + /*jack_port_t**/ ma_ptr* ppPortsPlayback; + /*jack_port_t**/ ma_ptr* ppPortsCapture; float* pIntermediaryBufferPlayback; /* Typed as a float because JACK is always floating point. */ float* pIntermediaryBufferCapture; } jack; @@ -4260,7 +7428,7 @@ struct ma_device ma_bool32 isDefaultCaptureDevice; ma_bool32 isSwitchingPlaybackDevice; /* <-- Set to true when the default device has changed and miniaudio is in the process of switching. */ ma_bool32 isSwitchingCaptureDevice; /* <-- Set to true when the default device has changed and miniaudio is in the process of switching. */ - void* pRouteChangeHandler; /* Only used on mobile platforms. Obj-C object for handling route changes. */ + void* pNotificationHandler; /* Only used on mobile platforms. Obj-C object for handling route changes. */ } coreaudio; #endif #ifdef MA_SUPPORT_SNDIO @@ -4291,6 +7459,10 @@ struct ma_device { /*AAudioStream**/ ma_ptr pStreamPlayback; /*AAudioStream**/ ma_ptr pStreamCapture; + ma_aaudio_usage usage; + ma_aaudio_content_type contentType; + ma_aaudio_input_preset inputPreset; + ma_bool32 noAutoStartAfterReroute; } aaudio; #endif #ifdef MA_SUPPORT_OPENSL @@ -4334,7 +7506,7 @@ struct ma_device ma_uint32 currentPeriodFramesRemainingCapture; ma_uint64 lastProcessedFramePlayback; ma_uint64 lastProcessedFrameCapture; - MA_ATOMIC ma_bool32 isStarted; /* Read and written by multiple threads. Must be used atomically, and must be 32-bit for compiler compatibility. */ + MA_ATOMIC(4, ma_bool32) isStarted; /* Read and written by multiple threads. Must be used atomically, and must be 32-bit for compiler compatibility. */ } null_device; #endif }; @@ -4446,6 +7618,9 @@ can then be set directly on the structure. Below are the members of the `ma_cont | ma_thread_priority_default | |--------------------------------------| + threadStackSize + The desired size of the stack for the audio thread. Defaults to the operating system's default. + pUserData A pointer to application-defined data. This can be accessed from the context object directly such as `context.pUserData`. @@ -4500,6 +7675,12 @@ can then be set directly on the structure. Below are the members of the `ma_cont | ma_ios_session_category_option_allow_air_play | AVAudioSessionCategoryOptionAllowAirPlay | |---------------------------------------------------------------------------|------------------------------------------------------------------| + coreaudio.noAudioSessionActivate + iOS only. When set to true, does not perform an explicit [[AVAudioSession sharedInstace] setActive:true] on initialization. + + coreaudio.noAudioSessionDeactivate + iOS only. When set to true, does not perform an explicit [[AVAudioSession sharedInstace] setActive:false] on uninitialization. + jack.pClientName The name of the client to pass to `jack_client_open()`. @@ -4543,9 +7724,12 @@ ma_backend backends[] = { ma_backend_dsound }; +ma_log log; +ma_log_init(&log); +ma_log_register_callback(&log, ma_log_callback_init(my_log_callbac, pMyLogUserData)); + ma_context_config config = ma_context_config_init(); -config.logCallback = my_log_callback; -config.pUserData = pMyUserData; +config.pLog = &log; // Specify a custom log object in the config so any logs that are posted from ma_context_init() are captured. ma_context context; ma_result result = ma_context_init(backends, sizeof(backends)/sizeof(backends[0]), &config, &context); @@ -4555,6 +7739,9 @@ if (result != MA_SUCCESS) { // Couldn't find an appropriate backend. } } + +// You could also attach a log callback post-initialization: +ma_log_register_callback(ma_context_get_log(&context), ma_log_callback_init(my_log_callback, pMyLogUserData)); ``` @@ -4606,6 +7793,8 @@ Remarks Pass the returned pointer to `ma_log_post()`, `ma_log_postv()` or `ma_log_postf()` to post a log message. +You can attach your own logging callback to the log with `ma_log_register_callback()` + Return Value ------------ @@ -4747,10 +7936,6 @@ deviceType (in) pDeviceID (in) The ID of the device being queried. -shareMode (in) - The share mode to query for device capabilities. This should be set to whatever you're intending on using when initializing the device. If you're unsure, - set this to `ma_share_mode_shared`. - pDeviceInfo (out) A pointer to the `ma_device_info` structure that will receive the device information. @@ -4776,7 +7961,7 @@ the requested share mode is unsupported. This leaves pDeviceInfo unmodified in the result of an error. */ -MA_API ma_result ma_context_get_device_info(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_share_mode shareMode, ma_device_info* pDeviceInfo); +MA_API ma_result ma_context_get_device_info(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_device_info* pDeviceInfo); /* Determines if the given context supports loopback mode. @@ -4947,7 +8132,7 @@ then be set directly on the structure. Below are the members of the `ma_device_c A hint to miniaudio as to the performance requirements of your program. Can be either `ma_performance_profile_low_latency` (default) or `ma_performance_profile_conservative`. This mainly affects the size of default buffers and can usually be left at it's default value. - noPreZeroedOutputBuffer + noPreSilencedOutputBuffer When set to true, the contents of the output buffer passed into the data callback will be left undefined. When set to false (default), the contents of the output buffer will be cleared the zero. You can use this to avoid the overhead of zeroing out the buffer if you can guarantee that your data callback will write to every sample in the output buffer, or if you are doing your own clearing. @@ -4957,12 +8142,19 @@ then be set directly on the structure. Below are the members of the `ma_device_c contents of the output buffer are left alone after returning and it will be left up to the backend itself to decide whether or not the clip. This only applies when the playback sample format is f32. + noDisableDenormals + By default, miniaudio will disable denormals when the data callback is called. Setting this to true will prevent the disabling of denormals. + + noFixedSizedCallback + Allows miniaudio to fire the data callback with any frame count. When this is set to true, the data callback will be fired with a consistent frame + count as specified by `periodSizeInFrames` or `periodSizeInMilliseconds`. When set to false, miniaudio will fire the callback with whatever the + backend requests, which could be anything. + dataCallback The callback to fire whenever data is ready to be delivered to or from the device. - stopCallback - The callback to fire whenever the device has stopped, either explicitly via `ma_device_stop()`, or implicitly due to things like the device being - disconnected. + notificationCallback + The callback to fire when something has changed with the device, such as whether or not it has been started or stopped. pUserData The user data pointer to use with the device. You can access this directly from the device object like `device.pUserData`. @@ -4971,6 +8163,12 @@ then be set directly on the structure. Below are the members of the `ma_device_c The resampling algorithm to use when miniaudio needs to perform resampling between the rate specified by `sampleRate` and the device's native rate. The default value is `ma_resample_algorithm_linear`, and the quality can be configured with `resampling.linear.lpfOrder`. + resampling.pBackendVTable + A pointer to an optional vtable that can be used for plugging in a custom resampler. + + resampling.pBackendUserData + A pointer that will passed to callbacks in pBackendVTable. + resampling.linear.lpfOrder The linear resampler applies a low-pass filter as part of it's procesing for anti-aliasing. This setting controls the order of the filter. The higher the value, the better the quality, in general. Setting this to 0 will disable low-pass filtering altogether. The maximum value is @@ -4988,9 +8186,9 @@ then be set directly on the structure. Below are the members of the `ma_device_c The number of channels to use for playback. When set to 0 the device's native channel count will be used. This can be retrieved after initialization from the device object directly with `device.playback.channels`. - playback.channelMap + playback.pChannelMap The channel map to use for playback. When left empty, the device's native channel map will be used. This can be retrieved after initialization from the - device object direct with `device.playback.channelMap`. + device object direct with `device.playback.pChannelMap`. When set, the buffer should contain `channels` items. playback.shareMode The preferred share mode to use for playback. Can be either `ma_share_mode_shared` (default) or `ma_share_mode_exclusive`. Note that if you specify @@ -5009,9 +8207,9 @@ then be set directly on the structure. Below are the members of the `ma_device_c The number of channels to use for capture. When set to 0 the device's native channel count will be used. This can be retrieved after initialization from the device object directly with `device.capture.channels`. - capture.channelMap + capture.pChannelMap The channel map to use for capture. When left empty, the device's native channel map will be used. This can be retrieved after initialization from the - device object direct with `device.capture.channelMap`. + device object direct with `device.capture.pChannelMap`. When set, the buffer should contain `channels` items. capture.shareMode The preferred share mode to use for capture. Can be either `ma_share_mode_shared` (default) or `ma_share_mode_exclusive`. Note that if you specify @@ -5056,6 +8254,30 @@ then be set directly on the structure. Below are the members of the `ma_device_c find the closest match between the sample rate requested in the device config and the sample rates natively supported by the hardware. When set to false, the sample rate currently set by the operating system will always be used. + opensl.streamType + OpenSL only. Explicitly sets the stream type. If left unset (`ma_opensl_stream_type_default`), the + stream type will be left unset. Think of this as the type of audio you're playing. + + opensl.recordingPreset + OpenSL only. Explicitly sets the type of recording your program will be doing. When left + unset, the recording preset will be left unchanged. + + aaudio.usage + AAudio only. Explicitly sets the nature of the audio the program will be consuming. When + left unset, the usage will be left unchanged. + + aaudio.contentType + AAudio only. Sets the content type. When left unset, the content type will be left unchanged. + + aaudio.inputPreset + AAudio only. Explicitly sets the type of recording your program will be doing. When left + unset, the input preset will be left unchanged. + + aaudio.noAutoStartAfterReroute + AAudio only. Controls whether or not the device should be automatically restarted after a + stream reroute. When set to false (default) the device will be restarted automatically; + otherwise the device will be stopped. + Once initialized, the device's config is immutable. If you need to change the config you will need to initialize a new device. @@ -5259,6 +8481,95 @@ Helper function for retrieving the log object associated with the context that o MA_API ma_log* ma_device_get_log(ma_device* pDevice); +/* +Retrieves information about the device. + + +Parameters +---------- +pDevice (in) + A pointer to the device whose information is being retrieved. + +type (in) + The device type. This parameter is required for duplex devices. When retrieving device + information, you are doing so for an individual playback or capture device. + +pDeviceInfo (out) + A pointer to the `ma_device_info` that will receive the device information. + + +Return Value +------------ +MA_SUCCESS if successful; any other error code otherwise. + + +Thread Safety +------------- +Unsafe. This should be considered unsafe because it may be calling into the backend which may or +may not be safe. + + +Callback Safety +--------------- +Unsafe. You should avoid calling this in the data callback because it may call into the backend +which may or may not be safe. +*/ +MA_API ma_result ma_device_get_info(ma_device* pDevice, ma_device_type type, ma_device_info* pDeviceInfo); + + +/* +Retrieves the name of the device. + + +Parameters +---------- +pDevice (in) + A pointer to the device whose information is being retrieved. + +type (in) + The device type. This parameter is required for duplex devices. When retrieving device + information, you are doing so for an individual playback or capture device. + +pName (out) + A pointer to the buffer that will receive the name. + +nameCap (in) + The capacity of the output buffer, including space for the null terminator. + +pLengthNotIncludingNullTerminator (out, optional) + A pointer to the variable that will receive the length of the name, not including the null + terminator. + + +Return Value +------------ +MA_SUCCESS if successful; any other error code otherwise. + + +Thread Safety +------------- +Unsafe. This should be considered unsafe because it may be calling into the backend which may or +may not be safe. + + +Callback Safety +--------------- +Unsafe. You should avoid calling this in the data callback because it may call into the backend +which may or may not be safe. + + +Remarks +------- +If the name does not fully fit into the output buffer, it'll be truncated. You can pass in NULL to +`pName` if you want to first get the length of the name for the purpose of memory allocation of the +output buffer. Allocating a buffer of size `MA_MAX_DEVICE_NAME_LENGTH + 1` should be enough for +most cases and will avoid the need for the inefficiency of calling this function twice. + +This is implemented in terms of `ma_device_get_info()`. +*/ +MA_API ma_result ma_device_get_name(ma_device* pDevice, ma_device_type type, char* pName, size_t nameCap, size_t* pLengthNotIncludingNullTerminator); + + /* Starts the device. For playback devices this begins playback. For capture devices it begins recording. @@ -5398,17 +8709,17 @@ Return Value ------------ The current state of the device. The return value will be one of the following: - +------------------------+------------------------------------------------------------------------------+ - | MA_STATE_UNINITIALIZED | Will only be returned if the device is in the middle of initialization. | - +------------------------+------------------------------------------------------------------------------+ - | MA_STATE_STOPPED | The device is stopped. The initial state of the device after initialization. | - +------------------------+------------------------------------------------------------------------------+ - | MA_STATE_STARTED | The device started and requesting and/or delivering audio data. | - +------------------------+------------------------------------------------------------------------------+ - | MA_STATE_STARTING | The device is in the process of starting. | - +------------------------+------------------------------------------------------------------------------+ - | MA_STATE_STOPPING | The device is in the process of stopping. | - +------------------------+------------------------------------------------------------------------------+ + +-------------------------------+------------------------------------------------------------------------------+ + | ma_device_state_uninitialized | Will only be returned if the device is in the middle of initialization. | + +-------------------------------+------------------------------------------------------------------------------+ + | ma_device_state_stopped | The device is stopped. The initial state of the device after initialization. | + +-------------------------------+------------------------------------------------------------------------------+ + | ma_device_state_started | The device started and requesting and/or delivering audio data. | + +-------------------------------+------------------------------------------------------------------------------+ + | ma_device_state_starting | The device is in the process of starting. | + +-------------------------------+------------------------------------------------------------------------------+ + | ma_device_state_stopping | The device is in the process of stopping. | + +-------------------------------+------------------------------------------------------------------------------+ Thread Safety @@ -5427,22 +8738,71 @@ Remarks The general flow of a devices state goes like this: ``` - ma_device_init() -> MA_STATE_UNINITIALIZED -> MA_STATE_STOPPED - ma_device_start() -> MA_STATE_STARTING -> MA_STATE_STARTED - ma_device_stop() -> MA_STATE_STOPPING -> MA_STATE_STOPPED + ma_device_init() -> ma_device_state_uninitialized -> ma_device_state_stopped + ma_device_start() -> ma_device_state_starting -> ma_device_state_started + ma_device_stop() -> ma_device_state_stopping -> ma_device_state_stopped ``` When the state of the device is changed with `ma_device_start()` or `ma_device_stop()` at this same time as this function is called, the value returned by this function could potentially be out of sync. If this is significant to your program you need to implement your own synchronization. */ -MA_API ma_uint32 ma_device_get_state(const ma_device* pDevice); +MA_API ma_device_state ma_device_get_state(const ma_device* pDevice); + + +/* +Performs post backend initialization routines for setting up internal data conversion. + +This should be called whenever the backend is initialized. The only time this should be called from +outside of miniaudio is if you're implementing a custom backend, and you would only do it if you +are reinitializing the backend due to rerouting or reinitializing for some reason. + + +Parameters +---------- +pDevice [in] + A pointer to the device. + +deviceType [in] + The type of the device that was just reinitialized. + +pPlaybackDescriptor [in] + The descriptor of the playback device containing the internal data format and buffer sizes. + +pPlaybackDescriptor [in] + The descriptor of the capture device containing the internal data format and buffer sizes. + + +Return Value +------------ +MA_SUCCESS if successful; any other error otherwise. + + +Thread Safety +------------- +Unsafe. This will be reinitializing internal data converters which may be in use by another thread. + + +Callback Safety +--------------- +Unsafe. This will be reinitializing internal data converters which may be in use by the callback. + + +Remarks +------- +For a duplex device, you can call this for only one side of the system. This is why the deviceType +is specified as a parameter rather than deriving it from the device. + +You do not need to call this manually unless you are doing a custom backend, in which case you need +only do it if you're manually performing rerouting or reinitialization. +*/ +MA_API ma_result ma_device_post_init(ma_device* pDevice, ma_device_type deviceType, const ma_device_descriptor* pPlaybackDescriptor, const ma_device_descriptor* pCaptureDescriptor); /* Sets the master volume factor for the device. -The volume factor must be between 0 (silence) and 1 (full volume). Use `ma_device_set_master_gain_db()` to use decibel notation, where 0 is full volume and +The volume factor must be between 0 (silence) and 1 (full volume). Use `ma_device_set_master_volume_db()` to use decibel notation, where 0 is full volume and values less than 0 decreases the volume. @@ -5452,14 +8812,14 @@ pDevice (in) A pointer to the device whose volume is being set. volume (in) - The new volume factor. Must be within the range of [0, 1]. + The new volume factor. Must be >= 0. Return Value ------------ MA_SUCCESS if the volume was set successfully. MA_INVALID_ARGS if pDevice is NULL. -MA_INVALID_ARGS if the volume factor is not within the range of [0, 1]. +MA_INVALID_ARGS if volume is negative. Thread Safety @@ -5482,8 +8842,8 @@ This does not change the operating system's volume. It only affects the volume f See Also -------- ma_device_get_master_volume() -ma_device_set_master_volume_gain_db() -ma_device_get_master_volume_gain_db() +ma_device_set_master_volume_db() +ma_device_get_master_volume_db() */ MA_API ma_result ma_device_set_master_volume(ma_device* pDevice, float volume); @@ -5575,7 +8935,7 @@ ma_device_get_master_volume_gain_db() ma_device_set_master_volume() ma_device_get_master_volume() */ -MA_API ma_result ma_device_set_master_gain_db(ma_device* pDevice, float gainDB); +MA_API ma_result ma_device_set_master_volume_db(ma_device* pDevice, float gainDB); /* Retrieves the master gain in decibels. @@ -5614,11 +8974,11 @@ If an error occurs, `*pGainDB` will be set to 0. See Also -------- -ma_device_set_master_volume_gain_db() +ma_device_set_master_volume_db() ma_device_set_master_volume() ma_device_get_master_volume() */ -MA_API ma_result ma_device_get_master_gain_db(ma_device* pDevice, float* pGainDB); +MA_API ma_result ma_device_get_master_volume_db(ma_device* pDevice, float* pGainDB); /* @@ -5814,68 +9174,6 @@ MA_API ma_bool32 ma_is_loopback_supported(ma_backend backend); #endif /* MA_NO_DEVICE_IO */ -#ifndef MA_NO_THREADING - -/* -Locks a spinlock. -*/ -MA_API ma_result ma_spinlock_lock(volatile ma_spinlock* pSpinlock); - -/* -Locks a spinlock, but does not yield() when looping. -*/ -MA_API ma_result ma_spinlock_lock_noyield(volatile ma_spinlock* pSpinlock); - -/* -Unlocks a spinlock. -*/ -MA_API ma_result ma_spinlock_unlock(volatile ma_spinlock* pSpinlock); - - -/* -Creates a mutex. - -A mutex must be created from a valid context. A mutex is initially unlocked. -*/ -MA_API ma_result ma_mutex_init(ma_mutex* pMutex); - -/* -Deletes a mutex. -*/ -MA_API void ma_mutex_uninit(ma_mutex* pMutex); - -/* -Locks a mutex with an infinite timeout. -*/ -MA_API void ma_mutex_lock(ma_mutex* pMutex); - -/* -Unlocks a mutex. -*/ -MA_API void ma_mutex_unlock(ma_mutex* pMutex); - - -/* -Initializes an auto-reset event. -*/ -MA_API ma_result ma_event_init(ma_event* pEvent); - -/* -Uninitializes an auto-reset event. -*/ -MA_API void ma_event_uninit(ma_event* pEvent); - -/* -Waits for the specified auto-reset event to become signalled. -*/ -MA_API ma_result ma_event_wait(ma_event* pEvent); - -/* -Signals the specified auto-reset event. -*/ -MA_API ma_result ma_event_signal(ma_event* pEvent); -#endif /* MA_NO_THREADING */ - /************************************************************************************************************************************************************ @@ -5883,13 +9181,6 @@ Utiltities ************************************************************************************************************************************************************/ -/* -Adjust buffer size based on a scaling factor. - -This just multiplies the base size by the scaling factor, making sure it's a size of at least 1. -*/ -MA_API ma_uint32 ma_scale_buffer_size(ma_uint32 baseBufferSize, float scale); - /* Calculates a buffer size in milliseconds from the specified number of frames and sample rate. */ @@ -5914,7 +9205,6 @@ For all formats except `ma_format_u8`, the output buffer will be filled with 0. makes more sense for the purpose of mixing to initialize it to the center point. */ MA_API void ma_silence_pcm_frames(void* p, ma_uint64 frameCount, ma_format format, ma_uint32 channels); -static MA_INLINE void ma_zero_pcm_frames(void* p, ma_uint64 frameCount, ma_format format, ma_uint32 channels) { ma_silence_pcm_frames(p, frameCount, format, channels); } /* @@ -5927,10 +9217,14 @@ static MA_INLINE const float* ma_offset_pcm_frames_const_ptr_f32(const float* p, /* -Clips f32 samples. +Clips samples. */ -MA_API void ma_clip_samples_f32(float* p, ma_uint64 sampleCount); -static MA_INLINE void ma_clip_pcm_frames_f32(float* p, ma_uint64 frameCount, ma_uint32 channels) { ma_clip_samples_f32(p, frameCount*channels); } +MA_API void ma_clip_samples_u8(ma_uint8* pDst, const ma_int16* pSrc, ma_uint64 count); +MA_API void ma_clip_samples_s16(ma_int16* pDst, const ma_int32* pSrc, ma_uint64 count); +MA_API void ma_clip_samples_s24(ma_uint8* pDst, const ma_int64* pSrc, ma_uint64 count); +MA_API void ma_clip_samples_s32(ma_int32* pDst, const ma_int64* pSrc, ma_uint64 count); +MA_API void ma_clip_samples_f32(float* pDst, const float* pSrc, ma_uint64 count); +MA_API void ma_clip_pcm_frames(void* pDst, const void* pSrc, ma_uint64 frameCount, ma_format format, ma_uint32 channels); /* Helper for applying a volume factor to samples. @@ -5949,11 +9243,11 @@ MA_API void ma_apply_volume_factor_s24(void* pSamples, ma_uint64 sampleCount, fl MA_API void ma_apply_volume_factor_s32(ma_int32* pSamples, ma_uint64 sampleCount, float factor); MA_API void ma_apply_volume_factor_f32(float* pSamples, ma_uint64 sampleCount, float factor); -MA_API void ma_copy_and_apply_volume_factor_pcm_frames_u8(ma_uint8* pPCMFramesOut, const ma_uint8* pPCMFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor); -MA_API void ma_copy_and_apply_volume_factor_pcm_frames_s16(ma_int16* pPCMFramesOut, const ma_int16* pPCMFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor); -MA_API void ma_copy_and_apply_volume_factor_pcm_frames_s24(void* pPCMFramesOut, const void* pPCMFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor); -MA_API void ma_copy_and_apply_volume_factor_pcm_frames_s32(ma_int32* pPCMFramesOut, const ma_int32* pPCMFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor); -MA_API void ma_copy_and_apply_volume_factor_pcm_frames_f32(float* pPCMFramesOut, const float* pPCMFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor); +MA_API void ma_copy_and_apply_volume_factor_pcm_frames_u8(ma_uint8* pFramesOut, const ma_uint8* pFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor); +MA_API void ma_copy_and_apply_volume_factor_pcm_frames_s16(ma_int16* pFramesOut, const ma_int16* pFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor); +MA_API void ma_copy_and_apply_volume_factor_pcm_frames_s24(void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor); +MA_API void ma_copy_and_apply_volume_factor_pcm_frames_s32(ma_int32* pFramesOut, const ma_int32* pFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor); +MA_API void ma_copy_and_apply_volume_factor_pcm_frames_f32(float* pFramesOut, const float* pFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor); MA_API void ma_copy_and_apply_volume_factor_pcm_frames(void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount, ma_format format, ma_uint32 channels, float factor); MA_API void ma_apply_volume_factor_pcm_frames_u8(ma_uint8* pFrames, ma_uint64 frameCount, ma_uint32 channels, float factor); @@ -5963,36 +9257,55 @@ MA_API void ma_apply_volume_factor_pcm_frames_s32(ma_int32* pFrames, ma_uint64 f MA_API void ma_apply_volume_factor_pcm_frames_f32(float* pFrames, ma_uint64 frameCount, ma_uint32 channels, float factor); MA_API void ma_apply_volume_factor_pcm_frames(void* pFrames, ma_uint64 frameCount, ma_format format, ma_uint32 channels, float factor); +MA_API void ma_copy_and_apply_volume_factor_per_channel_f32(float* pFramesOut, const float* pFramesIn, ma_uint64 frameCount, ma_uint32 channels, float* pChannelGains); + + +MA_API void ma_copy_and_apply_volume_and_clip_samples_u8(ma_uint8* pDst, const ma_int16* pSrc, ma_uint64 count, float volume); +MA_API void ma_copy_and_apply_volume_and_clip_samples_s16(ma_int16* pDst, const ma_int32* pSrc, ma_uint64 count, float volume); +MA_API void ma_copy_and_apply_volume_and_clip_samples_s24(ma_uint8* pDst, const ma_int64* pSrc, ma_uint64 count, float volume); +MA_API void ma_copy_and_apply_volume_and_clip_samples_s32(ma_int32* pDst, const ma_int64* pSrc, ma_uint64 count, float volume); +MA_API void ma_copy_and_apply_volume_and_clip_samples_f32(float* pDst, const float* pSrc, ma_uint64 count, float volume); +MA_API void ma_copy_and_apply_volume_and_clip_pcm_frames(void* pDst, const void* pSrc, ma_uint64 frameCount, ma_format format, ma_uint32 channels, float volume); + /* Helper for converting a linear factor to gain in decibels. */ -MA_API float ma_factor_to_gain_db(float factor); +MA_API float ma_volume_linear_to_db(float factor); /* Helper for converting gain in decibels to a linear factor. */ -MA_API float ma_gain_db_to_factor(float gain); +MA_API float ma_volume_db_to_linear(float gain); + + +/************************************************************************************************** + +Data Source + +**************************************************************************************************/ typedef void ma_data_source; +#define MA_DATA_SOURCE_SELF_MANAGED_RANGE_AND_LOOP_POINT 0x00000001 + typedef struct { ma_result (* onRead)(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); ma_result (* onSeek)(ma_data_source* pDataSource, ma_uint64 frameIndex); - ma_result (* onMap)(ma_data_source* pDataSource, void** ppFramesOut, ma_uint64* pFrameCount); /* Returns MA_AT_END if the end has been reached. This should be considered successful. */ - ma_result (* onUnmap)(ma_data_source* pDataSource, ma_uint64 frameCount); - ma_result (* onGetDataFormat)(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate); + ma_result (* onGetDataFormat)(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap); ma_result (* onGetCursor)(ma_data_source* pDataSource, ma_uint64* pCursor); ma_result (* onGetLength)(ma_data_source* pDataSource, ma_uint64* pLength); -} ma_data_source_vtable, ma_data_source_callbacks; /* TODO: Remove ma_data_source_callbacks in version 0.11. */ + ma_result (* onSetLooping)(ma_data_source* pDataSource, ma_bool32 isLooping); + ma_uint32 flags; +} ma_data_source_vtable; typedef ma_data_source* (* ma_data_source_get_next_proc)(ma_data_source* pDataSource); typedef struct { - const ma_data_source_vtable* vtable; /* Can be null, which is useful for proxies. */ + const ma_data_source_vtable* vtable; } ma_data_source_config; MA_API ma_data_source_config ma_data_source_config_init(void); @@ -6000,9 +9313,6 @@ MA_API ma_data_source_config ma_data_source_config_init(void); typedef struct { - ma_data_source_callbacks cb; /* TODO: Remove this. */ - - /* Variables below are placeholder and not yet used. */ const ma_data_source_vtable* vtable; ma_uint64 rangeBegInFrames; ma_uint64 rangeEndInFrames; /* Set to -1 for unranged (default). */ @@ -6011,30 +9321,31 @@ typedef struct ma_data_source* pCurrent; /* When non-NULL, the data source being initialized will act as a proxy and will route all operations to pCurrent. Used in conjunction with pNext/onGetNext for seamless chaining. */ ma_data_source* pNext; /* When set to NULL, onGetNext will be used. */ ma_data_source_get_next_proc onGetNext; /* Will be used when pNext is NULL. If both are NULL, no next will be used. */ + MA_ATOMIC(4, ma_bool32) isLooping; } ma_data_source_base; MA_API ma_result ma_data_source_init(const ma_data_source_config* pConfig, ma_data_source* pDataSource); MA_API void ma_data_source_uninit(ma_data_source* pDataSource); -MA_API ma_result ma_data_source_read_pcm_frames(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead, ma_bool32 loop); /* Must support pFramesOut = NULL in which case a forward seek should be performed. */ -MA_API ma_result ma_data_source_seek_pcm_frames(ma_data_source* pDataSource, ma_uint64 frameCount, ma_uint64* pFramesSeeked, ma_bool32 loop); /* Can only seek forward. Equivalent to ma_data_source_read_pcm_frames(pDataSource, NULL, frameCount); */ +MA_API ma_result ma_data_source_read_pcm_frames(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); /* Must support pFramesOut = NULL in which case a forward seek should be performed. */ +MA_API ma_result ma_data_source_seek_pcm_frames(ma_data_source* pDataSource, ma_uint64 frameCount, ma_uint64* pFramesSeeked); /* Can only seek forward. Equivalent to ma_data_source_read_pcm_frames(pDataSource, NULL, frameCount, &framesRead); */ MA_API ma_result ma_data_source_seek_to_pcm_frame(ma_data_source* pDataSource, ma_uint64 frameIndex); -MA_API ma_result ma_data_source_map(ma_data_source* pDataSource, void** ppFramesOut, ma_uint64* pFrameCount); /* Returns MA_NOT_IMPLEMENTED if mapping is not supported. */ -MA_API ma_result ma_data_source_unmap(ma_data_source* pDataSource, ma_uint64 frameCount); /* Returns MA_AT_END if the end has been reached. */ -MA_API ma_result ma_data_source_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate); +MA_API ma_result ma_data_source_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap); MA_API ma_result ma_data_source_get_cursor_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pCursor); MA_API ma_result ma_data_source_get_length_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pLength); /* Returns MA_NOT_IMPLEMENTED if the length is unknown or cannot be determined. Decoders can return this. */ -#if defined(MA_EXPERIMENTAL__DATA_LOOPING_AND_CHAINING) +MA_API ma_result ma_data_source_get_cursor_in_seconds(ma_data_source* pDataSource, float* pCursor); +MA_API ma_result ma_data_source_get_length_in_seconds(ma_data_source* pDataSource, float* pLength); +MA_API ma_result ma_data_source_set_looping(ma_data_source* pDataSource, ma_bool32 isLooping); +MA_API ma_bool32 ma_data_source_is_looping(const ma_data_source* pDataSource); MA_API ma_result ma_data_source_set_range_in_pcm_frames(ma_data_source* pDataSource, ma_uint64 rangeBegInFrames, ma_uint64 rangeEndInFrames); -MA_API void ma_data_source_get_range_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pRangeBegInFrames, ma_uint64* pRangeEndInFrames); +MA_API void ma_data_source_get_range_in_pcm_frames(const ma_data_source* pDataSource, ma_uint64* pRangeBegInFrames, ma_uint64* pRangeEndInFrames); MA_API ma_result ma_data_source_set_loop_point_in_pcm_frames(ma_data_source* pDataSource, ma_uint64 loopBegInFrames, ma_uint64 loopEndInFrames); -MA_API void ma_data_source_get_loop_point_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pLoopBegInFrames, ma_uint64* pLoopEndInFrames); +MA_API void ma_data_source_get_loop_point_in_pcm_frames(const ma_data_source* pDataSource, ma_uint64* pLoopBegInFrames, ma_uint64* pLoopEndInFrames); MA_API ma_result ma_data_source_set_current(ma_data_source* pDataSource, ma_data_source* pCurrentDataSource); -MA_API ma_data_source* ma_data_source_get_current(ma_data_source* pDataSource); +MA_API ma_data_source* ma_data_source_get_current(const ma_data_source* pDataSource); MA_API ma_result ma_data_source_set_next(ma_data_source* pDataSource, ma_data_source* pNextDataSource); -MA_API ma_data_source* ma_data_source_get_next(ma_data_source* pDataSource); +MA_API ma_data_source* ma_data_source_get_next(const ma_data_source* pDataSource); MA_API ma_result ma_data_source_set_next_callback(ma_data_source* pDataSource, ma_data_source_get_next_proc onGetNext); -MA_API ma_data_source_get_next_proc ma_data_source_get_next_callback(ma_data_source* pDataSource); -#endif +MA_API ma_data_source_get_next_proc ma_data_source_get_next_callback(const ma_data_source* pDataSource); typedef struct @@ -6042,6 +9353,7 @@ typedef struct ma_data_source_base ds; ma_format format; ma_uint32 channels; + ma_uint32 sampleRate; ma_uint64 cursor; ma_uint64 sizeInFrames; const void* pData; @@ -6065,6 +9377,7 @@ typedef struct { ma_format format; ma_uint32 channels; + ma_uint32 sampleRate; ma_uint64 sizeInFrames; const void* pData; /* If set to NULL, will allocate a block of memory for you. */ ma_allocation_callbacks allocationCallbacks; @@ -6095,6 +9408,69 @@ MA_API ma_result ma_audio_buffer_get_length_in_pcm_frames(const ma_audio_buffer* MA_API ma_result ma_audio_buffer_get_available_frames(const ma_audio_buffer* pAudioBuffer, ma_uint64* pAvailableFrames); +/* +Paged Audio Buffer +================== +A paged audio buffer is made up of a linked list of pages. It's expandable, but not shrinkable. It +can be used for cases where audio data is streamed in asynchronously while allowing data to be read +at the same time. + +This is lock-free, but not 100% thread safe. You can append a page and read from the buffer across +simultaneously across different threads, however only one thread at a time can append, and only one +thread at a time can read and seek. +*/ +typedef struct ma_paged_audio_buffer_page ma_paged_audio_buffer_page; +struct ma_paged_audio_buffer_page +{ + MA_ATOMIC(MA_SIZEOF_PTR, ma_paged_audio_buffer_page*) pNext; + ma_uint64 sizeInFrames; + ma_uint8 pAudioData[1]; +}; + +typedef struct +{ + ma_format format; + ma_uint32 channels; + ma_paged_audio_buffer_page head; /* Dummy head for the lock-free algorithm. Always has a size of 0. */ + MA_ATOMIC(MA_SIZEOF_PTR, ma_paged_audio_buffer_page*) pTail; /* Never null. Initially set to &head. */ +} ma_paged_audio_buffer_data; + +MA_API ma_result ma_paged_audio_buffer_data_init(ma_format format, ma_uint32 channels, ma_paged_audio_buffer_data* pData); +MA_API void ma_paged_audio_buffer_data_uninit(ma_paged_audio_buffer_data* pData, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API ma_paged_audio_buffer_page* ma_paged_audio_buffer_data_get_head(ma_paged_audio_buffer_data* pData); +MA_API ma_paged_audio_buffer_page* ma_paged_audio_buffer_data_get_tail(ma_paged_audio_buffer_data* pData); +MA_API ma_result ma_paged_audio_buffer_data_get_length_in_pcm_frames(ma_paged_audio_buffer_data* pData, ma_uint64* pLength); +MA_API ma_result ma_paged_audio_buffer_data_allocate_page(ma_paged_audio_buffer_data* pData, ma_uint64 pageSizeInFrames, const void* pInitialData, const ma_allocation_callbacks* pAllocationCallbacks, ma_paged_audio_buffer_page** ppPage); +MA_API ma_result ma_paged_audio_buffer_data_free_page(ma_paged_audio_buffer_data* pData, ma_paged_audio_buffer_page* pPage, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API ma_result ma_paged_audio_buffer_data_append_page(ma_paged_audio_buffer_data* pData, ma_paged_audio_buffer_page* pPage); +MA_API ma_result ma_paged_audio_buffer_data_allocate_and_append_page(ma_paged_audio_buffer_data* pData, ma_uint32 pageSizeInFrames, const void* pInitialData, const ma_allocation_callbacks* pAllocationCallbacks); + + +typedef struct +{ + ma_paged_audio_buffer_data* pData; /* Must not be null. */ +} ma_paged_audio_buffer_config; + +MA_API ma_paged_audio_buffer_config ma_paged_audio_buffer_config_init(ma_paged_audio_buffer_data* pData); + + +typedef struct +{ + ma_data_source_base ds; + ma_paged_audio_buffer_data* pData; /* Audio data is read from here. Cannot be null. */ + ma_paged_audio_buffer_page* pCurrent; + ma_uint64 relativeCursor; /* Relative to the current page. */ + ma_uint64 absoluteCursor; +} ma_paged_audio_buffer; + +MA_API ma_result ma_paged_audio_buffer_init(const ma_paged_audio_buffer_config* pConfig, ma_paged_audio_buffer* pPagedAudioBuffer); +MA_API void ma_paged_audio_buffer_uninit(ma_paged_audio_buffer* pPagedAudioBuffer); +MA_API ma_result ma_paged_audio_buffer_read_pcm_frames(ma_paged_audio_buffer* pPagedAudioBuffer, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); /* Returns MA_AT_END if no more pages available. */ +MA_API ma_result ma_paged_audio_buffer_seek_to_pcm_frame(ma_paged_audio_buffer* pPagedAudioBuffer, ma_uint64 frameIndex); +MA_API ma_result ma_paged_audio_buffer_get_cursor_in_pcm_frames(ma_paged_audio_buffer* pPagedAudioBuffer, ma_uint64* pCursor); +MA_API ma_result ma_paged_audio_buffer_get_length_in_pcm_frames(ma_paged_audio_buffer* pPagedAudioBuffer, ma_uint64* pLength); + + /************************************************************************************************************************************************************ @@ -6108,8 +9484,11 @@ appropriate for a given situation. typedef void ma_vfs; typedef ma_handle ma_vfs_file; -#define MA_OPEN_MODE_READ 0x00000001 -#define MA_OPEN_MODE_WRITE 0x00000002 +typedef enum +{ + MA_OPEN_MODE_READ = 0x00000001, + MA_OPEN_MODE_WRITE = 0x00000002 +} ma_open_mode_flags; typedef enum { @@ -6162,11 +9541,6 @@ typedef ma_result (* ma_tell_proc)(void* pUserData, ma_int64* pCursor); #if !defined(MA_NO_DECODING) || !defined(MA_NO_ENCODING) -typedef enum -{ - ma_resource_format_wav -} ma_resource_format; - typedef enum { ma_encoding_format_unknown = 0, @@ -6193,25 +9567,24 @@ typedef struct ma_decoder ma_decoder; typedef struct { ma_format preferredFormat; + ma_uint32 seekPointCount; /* Set to > 0 to generate a seektable if the decoding backend supports it. */ } ma_decoding_backend_config; -MA_API ma_decoding_backend_config ma_decoding_backend_config_init(ma_format preferredFormat); +MA_API ma_decoding_backend_config ma_decoding_backend_config_init(ma_format preferredFormat, ma_uint32 seekPointCount); typedef struct { - ma_result (* onInit )(void* pUserData, ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void* pReadSeekTellUserData, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend); - ma_result (* onInitFile )(void* pUserData, const char* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend); /* Optional. */ - ma_result (* onInitFileW )(void* pUserData, const wchar_t* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend); /* Optional. */ - ma_result (* onInitMemory )(void* pUserData, const void* pData, size_t dataSize, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend); /* Optional. */ - void (* onUninit )(void* pUserData, ma_data_source* pBackend, const ma_allocation_callbacks* pAllocationCallbacks); - ma_result (* onGetChannelMap)(void* pUserData, ma_data_source* pBackend, ma_channel* pChannelMap, size_t channelMapCap); + ma_result (* onInit )(void* pUserData, ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void* pReadSeekTellUserData, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend); + ma_result (* onInitFile )(void* pUserData, const char* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend); /* Optional. */ + ma_result (* onInitFileW )(void* pUserData, const wchar_t* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend); /* Optional. */ + ma_result (* onInitMemory)(void* pUserData, const void* pData, size_t dataSize, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend); /* Optional. */ + void (* onUninit )(void* pUserData, ma_data_source* pBackend, const ma_allocation_callbacks* pAllocationCallbacks); } ma_decoding_backend_vtable; -/* TODO: Convert read and seek to be consistent with the VFS API (ma_result return value, bytes read moved to an output parameter). */ -typedef size_t (* ma_decoder_read_proc)(ma_decoder* pDecoder, void* pBufferOut, size_t bytesToRead); /* Returns the number of bytes read. */ -typedef ma_bool32 (* ma_decoder_seek_proc)(ma_decoder* pDecoder, ma_int64 byteOffset, ma_seek_origin origin); +typedef ma_result (* ma_decoder_read_proc)(ma_decoder* pDecoder, void* pBufferOut, size_t bytesToRead, size_t* pBytesRead); /* Returns the number of bytes read. */ +typedef ma_result (* ma_decoder_seek_proc)(ma_decoder* pDecoder, ma_int64 byteOffset, ma_seek_origin origin); typedef ma_result (* ma_decoder_tell_proc)(ma_decoder* pDecoder, ma_int64* pCursor); typedef struct @@ -6219,23 +9592,13 @@ typedef struct ma_format format; /* Set to 0 or ma_format_unknown to use the stream's internal format. */ ma_uint32 channels; /* Set to 0 to use the stream's internal channels. */ ma_uint32 sampleRate; /* Set to 0 to use the stream's internal sample rate. */ - ma_channel channelMap[MA_MAX_CHANNELS]; + ma_channel* pChannelMap; ma_channel_mix_mode channelMixMode; ma_dither_mode ditherMode; - struct - { - ma_resample_algorithm algorithm; - struct - { - ma_uint32 lpfOrder; - } linear; - struct - { - int quality; - } speex; - } resampling; + ma_resampler_config resampling; ma_allocation_callbacks allocationCallbacks; ma_encoding_format encodingFormat; + ma_uint32 seekPointCount; /* When set to > 0, specifies the number of seek points to use for the generation of a seek table. Not all decoding backends support this. */ ma_decoding_backend_vtable** ppCustomBackendVTables; ma_uint32 customBackendCount; void* pCustomBackendUserData; @@ -6255,8 +9618,11 @@ struct ma_decoder ma_format outputFormat; ma_uint32 outputChannels; ma_uint32 outputSampleRate; - ma_channel outputChannelMap[MA_MAX_CHANNELS]; - ma_data_converter converter; /* <-- Data conversion is achieved by running frames through this. */ + ma_data_converter converter; /* Data conversion is achieved by running frames through this. */ + void* pInputCache; /* In input format. Can be null if it's not needed. */ + ma_uint64 inputCacheCap; /* The capacity of the input cache. */ + ma_uint64 inputCacheConsumed; /* The number of frames that have been consumed in the cache. Used for determining the next valid frame. */ + ma_uint64 inputCacheRemaining; /* The number of valid frames remaining in the cahce. */ ma_allocation_callbacks allocationCallbacks; union { @@ -6289,6 +9655,25 @@ Uninitializes a decoder. */ MA_API ma_result ma_decoder_uninit(ma_decoder* pDecoder); +/* +Reads PCM frames from the given decoder. + +This is not thread safe without your own synchronization. +*/ +MA_API ma_result ma_decoder_read_pcm_frames(ma_decoder* pDecoder, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); + +/* +Seeks to a PCM frame based on it's absolute index. + +This is not thread safe without your own synchronization. +*/ +MA_API ma_result ma_decoder_seek_to_pcm_frame(ma_decoder* pDecoder, ma_uint64 frameIndex); + +/* +Retrieves the decoder's output data format. +*/ +MA_API ma_result ma_decoder_get_data_format(ma_decoder* pDecoder, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap); + /* Retrieves the current position of the read cursor in PCM frames. */ @@ -6308,21 +9693,7 @@ For MP3's, this will decode the entire file. Do not call this in time critical s This function is not thread safe without your own synchronization. */ -MA_API ma_uint64 ma_decoder_get_length_in_pcm_frames(ma_decoder* pDecoder); - -/* -Reads PCM frames from the given decoder. - -This is not thread safe without your own synchronization. -*/ -MA_API ma_uint64 ma_decoder_read_pcm_frames(ma_decoder* pDecoder, void* pFramesOut, ma_uint64 frameCount); - -/* -Seeks to a PCM frame based on it's absolute index. - -This is not thread safe without your own synchronization. -*/ -MA_API ma_result ma_decoder_seek_to_pcm_frame(ma_decoder* pDecoder, ma_uint64 frameIndex); +MA_API ma_result ma_decoder_get_length_in_pcm_frames(ma_decoder* pDecoder, ma_uint64* pLength); /* Retrieves the number of frames that can be read before reaching the end. @@ -6343,43 +9714,6 @@ MA_API ma_result ma_decode_from_vfs(ma_vfs* pVFS, const char* pFilePath, ma_deco MA_API ma_result ma_decode_file(const char* pFilePath, ma_decoder_config* pConfig, ma_uint64* pFrameCountOut, void** ppPCMFramesOut); MA_API ma_result ma_decode_memory(const void* pData, size_t dataSize, ma_decoder_config* pConfig, ma_uint64* pFrameCountOut, void** ppPCMFramesOut); - - - -/* -DEPRECATED - -Set the "encodingFormat" variable in the decoder config instead: - - decoderConfig.encodingFormat = ma_encoding_format_wav; - -These functions will be removed in version 0.11. -*/ -MA_API ma_result ma_decoder_init_wav(ma_decoder_read_proc onRead, ma_decoder_seek_proc onSeek, void* pUserData, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_flac(ma_decoder_read_proc onRead, ma_decoder_seek_proc onSeek, void* pUserData, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_mp3(ma_decoder_read_proc onRead, ma_decoder_seek_proc onSeek, void* pUserData, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_vorbis(ma_decoder_read_proc onRead, ma_decoder_seek_proc onSeek, void* pUserData, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_memory_wav(const void* pData, size_t dataSize, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_memory_flac(const void* pData, size_t dataSize, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_memory_mp3(const void* pData, size_t dataSize, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_memory_vorbis(const void* pData, size_t dataSize, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_vfs_wav(ma_vfs* pVFS, const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_vfs_flac(ma_vfs* pVFS, const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_vfs_mp3(ma_vfs* pVFS, const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_vfs_vorbis(ma_vfs* pVFS, const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_vfs_wav_w(ma_vfs* pVFS, const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_vfs_flac_w(ma_vfs* pVFS, const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_vfs_mp3_w(ma_vfs* pVFS, const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_vfs_vorbis_w(ma_vfs* pVFS, const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_file_wav(const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_file_flac(const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_file_mp3(const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_file_vorbis(const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_file_wav_w(const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_file_flac_w(const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_file_mp3_w(const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_file_vorbis_w(const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); - #endif /* MA_NO_DECODING */ @@ -6394,22 +9728,22 @@ Encoders do not perform any format conversion for you. If your target format doe #ifndef MA_NO_ENCODING typedef struct ma_encoder ma_encoder; -typedef size_t (* ma_encoder_write_proc) (ma_encoder* pEncoder, const void* pBufferIn, size_t bytesToWrite); /* Returns the number of bytes written. */ -typedef ma_bool32 (* ma_encoder_seek_proc) (ma_encoder* pEncoder, int byteOffset, ma_seek_origin origin); +typedef ma_result (* ma_encoder_write_proc) (ma_encoder* pEncoder, const void* pBufferIn, size_t bytesToWrite, size_t* pBytesWritten); +typedef ma_result (* ma_encoder_seek_proc) (ma_encoder* pEncoder, ma_int64 offset, ma_seek_origin origin); typedef ma_result (* ma_encoder_init_proc) (ma_encoder* pEncoder); typedef void (* ma_encoder_uninit_proc) (ma_encoder* pEncoder); -typedef ma_uint64 (* ma_encoder_write_pcm_frames_proc)(ma_encoder* pEncoder, const void* pFramesIn, ma_uint64 frameCount); +typedef ma_result (* ma_encoder_write_pcm_frames_proc)(ma_encoder* pEncoder, const void* pFramesIn, ma_uint64 frameCount, ma_uint64* pFramesWritten); typedef struct { - ma_resource_format resourceFormat; + ma_encoding_format encodingFormat; ma_format format; ma_uint32 channels; ma_uint32 sampleRate; ma_allocation_callbacks allocationCallbacks; } ma_encoder_config; -MA_API ma_encoder_config ma_encoder_config_init(ma_resource_format resourceFormat, ma_format format, ma_uint32 channels, ma_uint32 sampleRate); +MA_API ma_encoder_config ma_encoder_config_init(ma_encoding_format encodingFormat, ma_format format, ma_uint32 channels, ma_uint32 sampleRate); struct ma_encoder { @@ -6421,14 +9755,23 @@ struct ma_encoder ma_encoder_write_pcm_frames_proc onWritePCMFrames; void* pUserData; void* pInternalEncoder; /* <-- The drwav/drflac/stb_vorbis/etc. objects. */ - void* pFile; /* FILE*. Only used when initialized with ma_encoder_init_file(). */ + union + { + struct + { + ma_vfs* pVFS; + ma_vfs_file file; + } vfs; + } data; }; MA_API ma_result ma_encoder_init(ma_encoder_write_proc onWrite, ma_encoder_seek_proc onSeek, void* pUserData, const ma_encoder_config* pConfig, ma_encoder* pEncoder); +MA_API ma_result ma_encoder_init_vfs(ma_vfs* pVFS, const char* pFilePath, const ma_encoder_config* pConfig, ma_encoder* pEncoder); +MA_API ma_result ma_encoder_init_vfs_w(ma_vfs* pVFS, const wchar_t* pFilePath, const ma_encoder_config* pConfig, ma_encoder* pEncoder); MA_API ma_result ma_encoder_init_file(const char* pFilePath, const ma_encoder_config* pConfig, ma_encoder* pEncoder); MA_API ma_result ma_encoder_init_file_w(const wchar_t* pFilePath, const ma_encoder_config* pConfig, ma_encoder* pEncoder); MA_API void ma_encoder_uninit(ma_encoder* pEncoder); -MA_API ma_uint64 ma_encoder_write_pcm_frames(ma_encoder* pEncoder, const void* pFramesIn, ma_uint64 frameCount); +MA_API ma_result ma_encoder_write_pcm_frames(ma_encoder* pEncoder, const void* pFramesIn, ma_uint64 frameCount, ma_uint64* pFramesWritten); #endif /* MA_NO_ENCODING */ @@ -6469,7 +9812,7 @@ typedef struct MA_API ma_result ma_waveform_init(const ma_waveform_config* pConfig, ma_waveform* pWaveform); MA_API void ma_waveform_uninit(ma_waveform* pWaveform); -MA_API ma_uint64 ma_waveform_read_pcm_frames(ma_waveform* pWaveform, void* pFramesOut, ma_uint64 frameCount); +MA_API ma_result ma_waveform_read_pcm_frames(ma_waveform* pWaveform, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); MA_API ma_result ma_waveform_seek_to_pcm_frame(ma_waveform* pWaveform, ma_uint64 frameIndex); MA_API ma_result ma_waveform_set_amplitude(ma_waveform* pWaveform, double amplitude); MA_API ma_result ma_waveform_set_frequency(ma_waveform* pWaveform, double frequency); @@ -6483,6 +9826,7 @@ typedef enum ma_noise_type_brownian } ma_noise_type; + typedef struct { ma_format format; @@ -6504,32 +9848,1187 @@ typedef struct { struct { - double bin[MA_MAX_CHANNELS][16]; - double accumulation[MA_MAX_CHANNELS]; - ma_uint32 counter[MA_MAX_CHANNELS]; + double** bin; + double* accumulation; + ma_uint32* counter; } pink; struct { - double accumulation[MA_MAX_CHANNELS]; + double* accumulation; } brownian; } state; + + /* Memory management. */ + void* _pHeap; + ma_bool32 _ownsHeap; } ma_noise; -MA_API ma_result ma_noise_init(const ma_noise_config* pConfig, ma_noise* pNoise); -MA_API void ma_noise_uninit(ma_noise* pNoise); -MA_API ma_uint64 ma_noise_read_pcm_frames(ma_noise* pNoise, void* pFramesOut, ma_uint64 frameCount); +MA_API ma_result ma_noise_get_heap_size(const ma_noise_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_noise_init_preallocated(const ma_noise_config* pConfig, void* pHeap, ma_noise* pNoise); +MA_API ma_result ma_noise_init(const ma_noise_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_noise* pNoise); +MA_API void ma_noise_uninit(ma_noise* pNoise, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API ma_result ma_noise_read_pcm_frames(ma_noise* pNoise, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); MA_API ma_result ma_noise_set_amplitude(ma_noise* pNoise, double amplitude); MA_API ma_result ma_noise_set_seed(ma_noise* pNoise, ma_int32 seed); MA_API ma_result ma_noise_set_type(ma_noise* pNoise, ma_noise_type type); #endif /* MA_NO_GENERATION */ + + +/************************************************************************************************************************************************************ + +Resource Manager + +************************************************************************************************************************************************************/ +/* The resource manager cannot be enabled if there is no decoder. */ +#if !defined(MA_NO_RESOURCE_MANAGER) && defined(MA_NO_DECODING) +#define MA_NO_RESOURCE_MANAGER +#endif + +#ifndef MA_NO_RESOURCE_MANAGER +typedef struct ma_resource_manager ma_resource_manager; +typedef struct ma_resource_manager_data_buffer_node ma_resource_manager_data_buffer_node; +typedef struct ma_resource_manager_data_buffer ma_resource_manager_data_buffer; +typedef struct ma_resource_manager_data_stream ma_resource_manager_data_stream; +typedef struct ma_resource_manager_data_source ma_resource_manager_data_source; + +typedef enum +{ + MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM = 0x00000001, /* When set, does not load the entire data source in memory. Disk I/O will happen on job threads. */ + MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE = 0x00000002, /* Decode data before storing in memory. When set, decoding is done at the resource manager level rather than the mixing thread. Results in faster mixing, but higher memory usage. */ + MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC = 0x00000004, /* When set, the resource manager will load the data source asynchronously. */ + MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT = 0x00000008, /* When set, waits for initialization of the underlying data source before returning from ma_resource_manager_data_source_init(). */ + MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_UNKNOWN_LENGTH = 0x00000010 /* Gives the resource manager a hint that the length of the data source is unknown and calling `ma_data_source_get_length_in_pcm_frames()` should be avoided. */ +} ma_resource_manager_data_source_flags; + + +/* +Pipeline notifications used by the resource manager. Made up of both an async notification and a fence, both of which are optional. +*/ +typedef struct +{ + ma_async_notification* pNotification; + ma_fence* pFence; +} ma_resource_manager_pipeline_stage_notification; + +typedef struct +{ + ma_resource_manager_pipeline_stage_notification init; /* Initialization of the decoder. */ + ma_resource_manager_pipeline_stage_notification done; /* Decoding fully completed. */ +} ma_resource_manager_pipeline_notifications; + +MA_API ma_resource_manager_pipeline_notifications ma_resource_manager_pipeline_notifications_init(void); + + + +/* BEGIN BACKWARDS COMPATIBILITY */ +/* TODO: Remove this block in version 0.12. */ +#if 1 +#define ma_resource_manager_job ma_job +#define ma_resource_manager_job_init ma_job_init +#define MA_JOB_TYPE_RESOURCE_MANAGER_QUEUE_FLAG_NON_BLOCKING MA_JOB_QUEUE_FLAG_NON_BLOCKING +#define ma_resource_manager_job_queue_config ma_job_queue_config +#define ma_resource_manager_job_queue_config_init ma_job_queue_config_init +#define ma_resource_manager_job_queue ma_job_queue +#define ma_resource_manager_job_queue_get_heap_size ma_job_queue_get_heap_size +#define ma_resource_manager_job_queue_init_preallocated ma_job_queue_init_preallocated +#define ma_resource_manager_job_queue_init ma_job_queue_init +#define ma_resource_manager_job_queue_uninit ma_job_queue_uninit +#define ma_resource_manager_job_queue_post ma_job_queue_post +#define ma_resource_manager_job_queue_next ma_job_queue_next +#endif +/* END BACKWARDS COMPATIBILITY */ + + + + +/* Maximum job thread count will be restricted to this, but this may be removed later and replaced with a heap allocation thereby removing any limitation. */ +#ifndef MA_RESOURCE_MANAGER_MAX_JOB_THREAD_COUNT +#define MA_RESOURCE_MANAGER_MAX_JOB_THREAD_COUNT 64 +#endif + +typedef enum +{ + /* Indicates ma_resource_manager_next_job() should not block. Only valid when the job thread count is 0. */ + MA_RESOURCE_MANAGER_FLAG_NON_BLOCKING = 0x00000001, + + /* Disables any kind of multithreading. Implicitly enables MA_RESOURCE_MANAGER_FLAG_NON_BLOCKING. */ + MA_RESOURCE_MANAGER_FLAG_NO_THREADING = 0x00000002 +} ma_resource_manager_flags; + +typedef struct +{ + const char* pFilePath; + const wchar_t* pFilePathW; + const ma_resource_manager_pipeline_notifications* pNotifications; + ma_uint64 initialSeekPointInPCMFrames; + ma_uint64 rangeBegInPCMFrames; + ma_uint64 rangeEndInPCMFrames; + ma_uint64 loopPointBegInPCMFrames; + ma_uint64 loopPointEndInPCMFrames; + ma_bool32 isLooping; + ma_uint32 flags; +} ma_resource_manager_data_source_config; + +MA_API ma_resource_manager_data_source_config ma_resource_manager_data_source_config_init(void); + + +typedef enum +{ + ma_resource_manager_data_supply_type_unknown = 0, /* Used for determining whether or the data supply has been initialized. */ + ma_resource_manager_data_supply_type_encoded, /* Data supply is an encoded buffer. Connector is ma_decoder. */ + ma_resource_manager_data_supply_type_decoded, /* Data supply is a decoded buffer. Connector is ma_audio_buffer. */ + ma_resource_manager_data_supply_type_decoded_paged /* Data supply is a linked list of decoded buffers. Connector is ma_paged_audio_buffer. */ +} ma_resource_manager_data_supply_type; + +typedef struct +{ + MA_ATOMIC(4, ma_resource_manager_data_supply_type) type; /* Read and written from different threads so needs to be accessed atomically. */ + union + { + struct + { + const void* pData; + size_t sizeInBytes; + } encoded; + struct + { + const void* pData; + ma_uint64 totalFrameCount; + ma_uint64 decodedFrameCount; + ma_format format; + ma_uint32 channels; + ma_uint32 sampleRate; + } decoded; + struct + { + ma_paged_audio_buffer_data data; + ma_uint64 decodedFrameCount; + ma_uint32 sampleRate; + } decodedPaged; + } backend; +} ma_resource_manager_data_supply; + +struct ma_resource_manager_data_buffer_node +{ + ma_uint32 hashedName32; /* The hashed name. This is the key. */ + ma_uint32 refCount; + MA_ATOMIC(4, ma_result) result; /* Result from asynchronous loading. When loading set to MA_BUSY. When fully loaded set to MA_SUCCESS. When deleting set to MA_UNAVAILABLE. */ + MA_ATOMIC(4, ma_uint32) executionCounter; /* For allocating execution orders for jobs. */ + MA_ATOMIC(4, ma_uint32) executionPointer; /* For managing the order of execution for asynchronous jobs relating to this object. Incremented as jobs complete processing. */ + ma_bool32 isDataOwnedByResourceManager; /* Set to true when the underlying data buffer was allocated the resource manager. Set to false if it is owned by the application (via ma_resource_manager_register_*()). */ + ma_resource_manager_data_supply data; + ma_resource_manager_data_buffer_node* pParent; + ma_resource_manager_data_buffer_node* pChildLo; + ma_resource_manager_data_buffer_node* pChildHi; +}; + +struct ma_resource_manager_data_buffer +{ + ma_data_source_base ds; /* Base data source. A data buffer is a data source. */ + ma_resource_manager* pResourceManager; /* A pointer to the resource manager that owns this buffer. */ + ma_resource_manager_data_buffer_node* pNode; /* The data node. This is reference counted and is what supplies the data. */ + ma_uint32 flags; /* The flags that were passed used to initialize the buffer. */ + MA_ATOMIC(4, ma_uint32) executionCounter; /* For allocating execution orders for jobs. */ + MA_ATOMIC(4, ma_uint32) executionPointer; /* For managing the order of execution for asynchronous jobs relating to this object. Incremented as jobs complete processing. */ + ma_uint64 seekTargetInPCMFrames; /* Only updated by the public API. Never written nor read from the job thread. */ + ma_bool32 seekToCursorOnNextRead; /* On the next read we need to seek to the frame cursor. */ + MA_ATOMIC(4, ma_result) result; /* Keeps track of a result of decoding. Set to MA_BUSY while the buffer is still loading. Set to MA_SUCCESS when loading is finished successfully. Otherwise set to some other code. */ + MA_ATOMIC(4, ma_bool32) isLooping; /* Can be read and written by different threads at the same time. Must be used atomically. */ + ma_bool32 isConnectorInitialized; /* Used for asynchronous loading to ensure we don't try to initialize the connector multiple times while waiting for the node to fully load. */ + union + { + ma_decoder decoder; /* Supply type is ma_resource_manager_data_supply_type_encoded */ + ma_audio_buffer buffer; /* Supply type is ma_resource_manager_data_supply_type_decoded */ + ma_paged_audio_buffer pagedBuffer; /* Supply type is ma_resource_manager_data_supply_type_decoded_paged */ + } connector; /* Connects this object to the node's data supply. */ +}; + +struct ma_resource_manager_data_stream +{ + ma_data_source_base ds; /* Base data source. A data stream is a data source. */ + ma_resource_manager* pResourceManager; /* A pointer to the resource manager that owns this data stream. */ + ma_uint32 flags; /* The flags that were passed used to initialize the stream. */ + ma_decoder decoder; /* Used for filling pages with data. This is only ever accessed by the job thread. The public API should never touch this. */ + ma_bool32 isDecoderInitialized; /* Required for determining whether or not the decoder should be uninitialized in MA_JOB_TYPE_RESOURCE_MANAGER_FREE_DATA_STREAM. */ + ma_uint64 totalLengthInPCMFrames; /* This is calculated when first loaded by the MA_JOB_TYPE_RESOURCE_MANAGER_LOAD_DATA_STREAM. */ + ma_uint32 relativeCursor; /* The playback cursor, relative to the current page. Only ever accessed by the public API. Never accessed by the job thread. */ + MA_ATOMIC(8, ma_uint64) absoluteCursor; /* The playback cursor, in absolute position starting from the start of the file. */ + ma_uint32 currentPageIndex; /* Toggles between 0 and 1. Index 0 is the first half of pPageData. Index 1 is the second half. Only ever accessed by the public API. Never accessed by the job thread. */ + MA_ATOMIC(4, ma_uint32) executionCounter; /* For allocating execution orders for jobs. */ + MA_ATOMIC(4, ma_uint32) executionPointer; /* For managing the order of execution for asynchronous jobs relating to this object. Incremented as jobs complete processing. */ + + /* Written by the public API, read by the job thread. */ + MA_ATOMIC(4, ma_bool32) isLooping; /* Whether or not the stream is looping. It's important to set the looping flag at the data stream level for smooth loop transitions. */ + + /* Written by the job thread, read by the public API. */ + void* pPageData; /* Buffer containing the decoded data of each page. Allocated once at initialization time. */ + MA_ATOMIC(4, ma_uint32) pageFrameCount[2]; /* The number of valid PCM frames in each page. Used to determine the last valid frame. */ + + /* Written and read by both the public API and the job thread. These must be atomic. */ + MA_ATOMIC(4, ma_result) result; /* Result from asynchronous loading. When loading set to MA_BUSY. When initialized set to MA_SUCCESS. When deleting set to MA_UNAVAILABLE. If an error occurs when loading, set to an error code. */ + MA_ATOMIC(4, ma_bool32) isDecoderAtEnd; /* Whether or not the decoder has reached the end. */ + MA_ATOMIC(4, ma_bool32) isPageValid[2]; /* Booleans to indicate whether or not a page is valid. Set to false by the public API, set to true by the job thread. Set to false as the pages are consumed, true when they are filled. */ + MA_ATOMIC(4, ma_bool32) seekCounter; /* When 0, no seeking is being performed. When > 0, a seek is being performed and reading should be delayed with MA_BUSY. */ +}; + +struct ma_resource_manager_data_source +{ + union + { + ma_resource_manager_data_buffer buffer; + ma_resource_manager_data_stream stream; + } backend; /* Must be the first item because we need the first item to be the data source callbacks for the buffer or stream. */ + + ma_uint32 flags; /* The flags that were passed in to ma_resource_manager_data_source_init(). */ + MA_ATOMIC(4, ma_uint32) executionCounter; /* For allocating execution orders for jobs. */ + MA_ATOMIC(4, ma_uint32) executionPointer; /* For managing the order of execution for asynchronous jobs relating to this object. Incremented as jobs complete processing. */ +}; + +typedef struct +{ + ma_allocation_callbacks allocationCallbacks; + ma_log* pLog; + ma_format decodedFormat; /* The decoded format to use. Set to ma_format_unknown (default) to use the file's native format. */ + ma_uint32 decodedChannels; /* The decoded channel count to use. Set to 0 (default) to use the file's native channel count. */ + ma_uint32 decodedSampleRate; /* the decoded sample rate to use. Set to 0 (default) to use the file's native sample rate. */ + ma_uint32 jobThreadCount; /* Set to 0 if you want to self-manage your job threads. Defaults to 1. */ + ma_uint32 jobQueueCapacity; /* The maximum number of jobs that can fit in the queue at a time. Defaults to MA_JOB_TYPE_RESOURCE_MANAGER_QUEUE_CAPACITY. Cannot be zero. */ + ma_uint32 flags; + ma_vfs* pVFS; /* Can be NULL in which case defaults will be used. */ + ma_decoding_backend_vtable** ppCustomDecodingBackendVTables; + ma_uint32 customDecodingBackendCount; + void* pCustomDecodingBackendUserData; +} ma_resource_manager_config; + +MA_API ma_resource_manager_config ma_resource_manager_config_init(void); + +struct ma_resource_manager +{ + ma_resource_manager_config config; + ma_resource_manager_data_buffer_node* pRootDataBufferNode; /* The root buffer in the binary tree. */ +#ifndef MA_NO_THREADING + ma_mutex dataBufferBSTLock; /* For synchronizing access to the data buffer binary tree. */ + ma_thread jobThreads[MA_RESOURCE_MANAGER_MAX_JOB_THREAD_COUNT]; /* The threads for executing jobs. */ +#endif + ma_job_queue jobQueue; /* Multi-consumer, multi-producer job queue for managing jobs for asynchronous decoding and streaming. */ + ma_default_vfs defaultVFS; /* Only used if a custom VFS is not specified. */ + ma_log log; /* Only used if no log was specified in the config. */ +}; + +/* Init. */ +MA_API ma_result ma_resource_manager_init(const ma_resource_manager_config* pConfig, ma_resource_manager* pResourceManager); +MA_API void ma_resource_manager_uninit(ma_resource_manager* pResourceManager); +MA_API ma_log* ma_resource_manager_get_log(ma_resource_manager* pResourceManager); + +/* Registration. */ +MA_API ma_result ma_resource_manager_register_file(ma_resource_manager* pResourceManager, const char* pFilePath, ma_uint32 flags); +MA_API ma_result ma_resource_manager_register_file_w(ma_resource_manager* pResourceManager, const wchar_t* pFilePath, ma_uint32 flags); +MA_API ma_result ma_resource_manager_register_decoded_data(ma_resource_manager* pResourceManager, const char* pName, const void* pData, ma_uint64 frameCount, ma_format format, ma_uint32 channels, ma_uint32 sampleRate); /* Does not copy. Increments the reference count if already exists and returns MA_SUCCESS. */ +MA_API ma_result ma_resource_manager_register_decoded_data_w(ma_resource_manager* pResourceManager, const wchar_t* pName, const void* pData, ma_uint64 frameCount, ma_format format, ma_uint32 channels, ma_uint32 sampleRate); +MA_API ma_result ma_resource_manager_register_encoded_data(ma_resource_manager* pResourceManager, const char* pName, const void* pData, size_t sizeInBytes); /* Does not copy. Increments the reference count if already exists and returns MA_SUCCESS. */ +MA_API ma_result ma_resource_manager_register_encoded_data_w(ma_resource_manager* pResourceManager, const wchar_t* pName, const void* pData, size_t sizeInBytes); +MA_API ma_result ma_resource_manager_unregister_file(ma_resource_manager* pResourceManager, const char* pFilePath); +MA_API ma_result ma_resource_manager_unregister_file_w(ma_resource_manager* pResourceManager, const wchar_t* pFilePath); +MA_API ma_result ma_resource_manager_unregister_data(ma_resource_manager* pResourceManager, const char* pName); +MA_API ma_result ma_resource_manager_unregister_data_w(ma_resource_manager* pResourceManager, const wchar_t* pName); + +/* Data Buffers. */ +MA_API ma_result ma_resource_manager_data_buffer_init_ex(ma_resource_manager* pResourceManager, const ma_resource_manager_data_source_config* pConfig, ma_resource_manager_data_buffer* pDataBuffer); +MA_API ma_result ma_resource_manager_data_buffer_init(ma_resource_manager* pResourceManager, const char* pFilePath, ma_uint32 flags, const ma_resource_manager_pipeline_notifications* pNotifications, ma_resource_manager_data_buffer* pDataBuffer); +MA_API ma_result ma_resource_manager_data_buffer_init_w(ma_resource_manager* pResourceManager, const wchar_t* pFilePath, ma_uint32 flags, const ma_resource_manager_pipeline_notifications* pNotifications, ma_resource_manager_data_buffer* pDataBuffer); +MA_API ma_result ma_resource_manager_data_buffer_init_copy(ma_resource_manager* pResourceManager, const ma_resource_manager_data_buffer* pExistingDataBuffer, ma_resource_manager_data_buffer* pDataBuffer); +MA_API ma_result ma_resource_manager_data_buffer_uninit(ma_resource_manager_data_buffer* pDataBuffer); +MA_API ma_result ma_resource_manager_data_buffer_read_pcm_frames(ma_resource_manager_data_buffer* pDataBuffer, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); +MA_API ma_result ma_resource_manager_data_buffer_seek_to_pcm_frame(ma_resource_manager_data_buffer* pDataBuffer, ma_uint64 frameIndex); +MA_API ma_result ma_resource_manager_data_buffer_get_data_format(ma_resource_manager_data_buffer* pDataBuffer, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap); +MA_API ma_result ma_resource_manager_data_buffer_get_cursor_in_pcm_frames(ma_resource_manager_data_buffer* pDataBuffer, ma_uint64* pCursor); +MA_API ma_result ma_resource_manager_data_buffer_get_length_in_pcm_frames(ma_resource_manager_data_buffer* pDataBuffer, ma_uint64* pLength); +MA_API ma_result ma_resource_manager_data_buffer_result(const ma_resource_manager_data_buffer* pDataBuffer); +MA_API ma_result ma_resource_manager_data_buffer_set_looping(ma_resource_manager_data_buffer* pDataBuffer, ma_bool32 isLooping); +MA_API ma_bool32 ma_resource_manager_data_buffer_is_looping(const ma_resource_manager_data_buffer* pDataBuffer); +MA_API ma_result ma_resource_manager_data_buffer_get_available_frames(ma_resource_manager_data_buffer* pDataBuffer, ma_uint64* pAvailableFrames); + +/* Data Streams. */ +MA_API ma_result ma_resource_manager_data_stream_init_ex(ma_resource_manager* pResourceManager, const ma_resource_manager_data_source_config* pConfig, ma_resource_manager_data_stream* pDataStream); +MA_API ma_result ma_resource_manager_data_stream_init(ma_resource_manager* pResourceManager, const char* pFilePath, ma_uint32 flags, const ma_resource_manager_pipeline_notifications* pNotifications, ma_resource_manager_data_stream* pDataStream); +MA_API ma_result ma_resource_manager_data_stream_init_w(ma_resource_manager* pResourceManager, const wchar_t* pFilePath, ma_uint32 flags, const ma_resource_manager_pipeline_notifications* pNotifications, ma_resource_manager_data_stream* pDataStream); +MA_API ma_result ma_resource_manager_data_stream_uninit(ma_resource_manager_data_stream* pDataStream); +MA_API ma_result ma_resource_manager_data_stream_read_pcm_frames(ma_resource_manager_data_stream* pDataStream, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); +MA_API ma_result ma_resource_manager_data_stream_seek_to_pcm_frame(ma_resource_manager_data_stream* pDataStream, ma_uint64 frameIndex); +MA_API ma_result ma_resource_manager_data_stream_get_data_format(ma_resource_manager_data_stream* pDataStream, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap); +MA_API ma_result ma_resource_manager_data_stream_get_cursor_in_pcm_frames(ma_resource_manager_data_stream* pDataStream, ma_uint64* pCursor); +MA_API ma_result ma_resource_manager_data_stream_get_length_in_pcm_frames(ma_resource_manager_data_stream* pDataStream, ma_uint64* pLength); +MA_API ma_result ma_resource_manager_data_stream_result(const ma_resource_manager_data_stream* pDataStream); +MA_API ma_result ma_resource_manager_data_stream_set_looping(ma_resource_manager_data_stream* pDataStream, ma_bool32 isLooping); +MA_API ma_bool32 ma_resource_manager_data_stream_is_looping(const ma_resource_manager_data_stream* pDataStream); +MA_API ma_result ma_resource_manager_data_stream_get_available_frames(ma_resource_manager_data_stream* pDataStream, ma_uint64* pAvailableFrames); + +/* Data Sources. */ +MA_API ma_result ma_resource_manager_data_source_init_ex(ma_resource_manager* pResourceManager, const ma_resource_manager_data_source_config* pConfig, ma_resource_manager_data_source* pDataSource); +MA_API ma_result ma_resource_manager_data_source_init(ma_resource_manager* pResourceManager, const char* pName, ma_uint32 flags, const ma_resource_manager_pipeline_notifications* pNotifications, ma_resource_manager_data_source* pDataSource); +MA_API ma_result ma_resource_manager_data_source_init_w(ma_resource_manager* pResourceManager, const wchar_t* pName, ma_uint32 flags, const ma_resource_manager_pipeline_notifications* pNotifications, ma_resource_manager_data_source* pDataSource); +MA_API ma_result ma_resource_manager_data_source_init_copy(ma_resource_manager* pResourceManager, const ma_resource_manager_data_source* pExistingDataSource, ma_resource_manager_data_source* pDataSource); +MA_API ma_result ma_resource_manager_data_source_uninit(ma_resource_manager_data_source* pDataSource); +MA_API ma_result ma_resource_manager_data_source_read_pcm_frames(ma_resource_manager_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); +MA_API ma_result ma_resource_manager_data_source_seek_to_pcm_frame(ma_resource_manager_data_source* pDataSource, ma_uint64 frameIndex); +MA_API ma_result ma_resource_manager_data_source_get_data_format(ma_resource_manager_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap); +MA_API ma_result ma_resource_manager_data_source_get_cursor_in_pcm_frames(ma_resource_manager_data_source* pDataSource, ma_uint64* pCursor); +MA_API ma_result ma_resource_manager_data_source_get_length_in_pcm_frames(ma_resource_manager_data_source* pDataSource, ma_uint64* pLength); +MA_API ma_result ma_resource_manager_data_source_result(const ma_resource_manager_data_source* pDataSource); +MA_API ma_result ma_resource_manager_data_source_set_looping(ma_resource_manager_data_source* pDataSource, ma_bool32 isLooping); +MA_API ma_bool32 ma_resource_manager_data_source_is_looping(const ma_resource_manager_data_source* pDataSource); +MA_API ma_result ma_resource_manager_data_source_get_available_frames(ma_resource_manager_data_source* pDataSource, ma_uint64* pAvailableFrames); + +/* Job management. */ +MA_API ma_result ma_resource_manager_post_job(ma_resource_manager* pResourceManager, const ma_job* pJob); +MA_API ma_result ma_resource_manager_post_job_quit(ma_resource_manager* pResourceManager); /* Helper for posting a quit job. */ +MA_API ma_result ma_resource_manager_next_job(ma_resource_manager* pResourceManager, ma_job* pJob); +MA_API ma_result ma_resource_manager_process_job(ma_resource_manager* pResourceManager, ma_job* pJob); /* DEPRECATED. Use ma_job_process(). Will be removed in version 0.12. */ +MA_API ma_result ma_resource_manager_process_next_job(ma_resource_manager* pResourceManager); /* Returns MA_CANCELLED if a MA_JOB_TYPE_QUIT job is found. In non-blocking mode, returns MA_NO_DATA_AVAILABLE if no jobs are available. */ +#endif /* MA_NO_RESOURCE_MANAGER */ + + + +/************************************************************************************************************************************************************ + +Node Graph + +************************************************************************************************************************************************************/ +#ifndef MA_NO_NODE_GRAPH +/* Must never exceed 254. */ +#ifndef MA_MAX_NODE_BUS_COUNT +#define MA_MAX_NODE_BUS_COUNT 254 +#endif + +/* Used internally by miniaudio for memory management. Must never exceed MA_MAX_NODE_BUS_COUNT. */ +#ifndef MA_MAX_NODE_LOCAL_BUS_COUNT +#define MA_MAX_NODE_LOCAL_BUS_COUNT 2 +#endif + +/* Use this when the bus count is determined by the node instance rather than the vtable. */ +#define MA_NODE_BUS_COUNT_UNKNOWN 255 + +typedef struct ma_node_graph ma_node_graph; +typedef void ma_node; + + +/* Node flags. */ +typedef enum +{ + MA_NODE_FLAG_PASSTHROUGH = 0x00000001, + MA_NODE_FLAG_CONTINUOUS_PROCESSING = 0x00000002, + MA_NODE_FLAG_ALLOW_NULL_INPUT = 0x00000004, + MA_NODE_FLAG_DIFFERENT_PROCESSING_RATES = 0x00000008, + MA_NODE_FLAG_SILENT_OUTPUT = 0x00000010 +} ma_node_flags; + + +/* The playback state of a node. Either started or stopped. */ +typedef enum +{ + ma_node_state_started = 0, + ma_node_state_stopped = 1 +} ma_node_state; + + +typedef struct +{ + /* + Extended processing callback. This callback is used for effects that process input and output + at different rates (i.e. they perform resampling). This is similar to the simple version, only + they take two seperate frame counts: one for input, and one for output. + + On input, `pFrameCountOut` is equal to the capacity of the output buffer for each bus, whereas + `pFrameCountIn` will be equal to the number of PCM frames in each of the buffers in `ppFramesIn`. + + On output, set `pFrameCountOut` to the number of PCM frames that were actually output and set + `pFrameCountIn` to the number of input frames that were consumed. + */ + void (* onProcess)(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut); + + /* + A callback for retrieving the number of a input frames that are required to output the + specified number of output frames. You would only want to implement this when the node performs + resampling. This is optional, even for nodes that perform resampling, but it does offer a + small reduction in latency as it allows miniaudio to calculate the exact number of input frames + to read at a time instead of having to estimate. + */ + ma_result (* onGetRequiredInputFrameCount)(ma_node* pNode, ma_uint32 outputFrameCount, ma_uint32* pInputFrameCount); + + /* + The number of input buses. This is how many sub-buffers will be contained in the `ppFramesIn` + parameters of the callbacks above. + */ + ma_uint8 inputBusCount; + + /* + The number of output buses. This is how many sub-buffers will be contained in the `ppFramesOut` + parameters of the callbacks above. + */ + ma_uint8 outputBusCount; + + /* + Flags describing characteristics of the node. This is currently just a placeholder for some + ideas for later on. + */ + ma_uint32 flags; +} ma_node_vtable; + +typedef struct +{ + const ma_node_vtable* vtable; /* Should never be null. Initialization of the node will fail if so. */ + ma_node_state initialState; /* Defaults to ma_node_state_started. */ + ma_uint32 inputBusCount; /* Only used if the vtable specifies an input bus count of `MA_NODE_BUS_COUNT_UNKNOWN`, otherwise must be set to `MA_NODE_BUS_COUNT_UNKNOWN` (default). */ + ma_uint32 outputBusCount; /* Only used if the vtable specifies an output bus count of `MA_NODE_BUS_COUNT_UNKNOWN`, otherwise be set to `MA_NODE_BUS_COUNT_UNKNOWN` (default). */ + const ma_uint32* pInputChannels; /* The number of elements are determined by the input bus count as determined by the vtable, or `inputBusCount` if the vtable specifies `MA_NODE_BUS_COUNT_UNKNOWN`. */ + const ma_uint32* pOutputChannels; /* The number of elements are determined by the output bus count as determined by the vtable, or `outputBusCount` if the vtable specifies `MA_NODE_BUS_COUNT_UNKNOWN`. */ +} ma_node_config; + +MA_API ma_node_config ma_node_config_init(void); + + +/* +A node has multiple output buses. An output bus is attached to an input bus as an item in a linked +list. Think of the input bus as a linked list, with the output bus being an item in that list. +*/ +typedef struct ma_node_output_bus ma_node_output_bus; +struct ma_node_output_bus +{ + /* Immutable. */ + ma_node* pNode; /* The node that owns this output bus. The input node. Will be null for dummy head and tail nodes. */ + ma_uint8 outputBusIndex; /* The index of the output bus on pNode that this output bus represents. */ + ma_uint8 channels; /* The number of channels in the audio stream for this bus. */ + + /* Mutable via multiple threads. Must be used atomically. The weird ordering here is for packing reasons. */ + MA_ATOMIC(1, ma_uint8) inputNodeInputBusIndex; /* The index of the input bus on the input. Required for detaching. */ + MA_ATOMIC(4, ma_uint32) flags; /* Some state flags for tracking the read state of the output buffer. A combination of MA_NODE_OUTPUT_BUS_FLAG_*. */ + MA_ATOMIC(4, ma_uint32) refCount; /* Reference count for some thread-safety when detaching. */ + MA_ATOMIC(4, ma_bool32) isAttached; /* This is used to prevent iteration of nodes that are in the middle of being detached. Used for thread safety. */ + MA_ATOMIC(4, ma_spinlock) lock; /* Unfortunate lock, but significantly simplifies the implementation. Required for thread-safe attaching and detaching. */ + MA_ATOMIC(4, float) volume; /* Linear. */ + MA_ATOMIC(MA_SIZEOF_PTR, ma_node_output_bus*) pNext; /* If null, it's the tail node or detached. */ + MA_ATOMIC(MA_SIZEOF_PTR, ma_node_output_bus*) pPrev; /* If null, it's the head node or detached. */ + MA_ATOMIC(MA_SIZEOF_PTR, ma_node*) pInputNode; /* The node that this output bus is attached to. Required for detaching. */ +}; + +/* +A node has multiple input buses. The output buses of a node are connecting to the input busses of +another. An input bus is essentially just a linked list of output buses. +*/ +typedef struct ma_node_input_bus ma_node_input_bus; +struct ma_node_input_bus +{ + /* Mutable via multiple threads. */ + ma_node_output_bus head; /* Dummy head node for simplifying some lock-free thread-safety stuff. */ + MA_ATOMIC(4, ma_uint32) nextCounter; /* This is used to determine whether or not the input bus is finding the next node in the list. Used for thread safety when detaching output buses. */ + MA_ATOMIC(4, ma_spinlock) lock; /* Unfortunate lock, but significantly simplifies the implementation. Required for thread-safe attaching and detaching. */ + + /* Set once at startup. */ + ma_uint8 channels; /* The number of channels in the audio stream for this bus. */ +}; + + +typedef struct ma_node_base ma_node_base; +struct ma_node_base +{ + /* These variables are set once at startup. */ + ma_node_graph* pNodeGraph; /* The graph this node belongs to. */ + const ma_node_vtable* vtable; + float* pCachedData; /* Allocated on the heap. Fixed size. Needs to be stored on the heap because reading from output buses is done in separate function calls. */ + ma_uint16 cachedDataCapInFramesPerBus; /* The capacity of the input data cache in frames, per bus. */ + + /* These variables are read and written only from the audio thread. */ + ma_uint16 cachedFrameCountOut; + ma_uint16 cachedFrameCountIn; + ma_uint16 consumedFrameCountIn; + + /* These variables are read and written between different threads. */ + MA_ATOMIC(4, ma_node_state) state; /* When set to stopped, nothing will be read, regardless of the times in stateTimes. */ + MA_ATOMIC(8, ma_uint64) stateTimes[2]; /* Indexed by ma_node_state. Specifies the time based on the global clock that a node should be considered to be in the relevant state. */ + MA_ATOMIC(8, ma_uint64) localTime; /* The node's local clock. This is just a running sum of the number of output frames that have been processed. Can be modified by any thread with `ma_node_set_time()`. */ + ma_uint32 inputBusCount; + ma_uint32 outputBusCount; + ma_node_input_bus* pInputBuses; + ma_node_output_bus* pOutputBuses; + + /* Memory management. */ + ma_node_input_bus _inputBuses[MA_MAX_NODE_LOCAL_BUS_COUNT]; + ma_node_output_bus _outputBuses[MA_MAX_NODE_LOCAL_BUS_COUNT]; + void* _pHeap; /* A heap allocation for internal use only. pInputBuses and/or pOutputBuses will point to this if the bus count exceeds MA_MAX_NODE_LOCAL_BUS_COUNT. */ + ma_bool32 _ownsHeap; /* If set to true, the node owns the heap allocation and _pHeap will be freed in ma_node_uninit(). */ +}; + +MA_API ma_result ma_node_get_heap_size(ma_node_graph* pNodeGraph, const ma_node_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_node_init_preallocated(ma_node_graph* pNodeGraph, const ma_node_config* pConfig, void* pHeap, ma_node* pNode); +MA_API ma_result ma_node_init(ma_node_graph* pNodeGraph, const ma_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_node* pNode); +MA_API void ma_node_uninit(ma_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API ma_node_graph* ma_node_get_node_graph(const ma_node* pNode); +MA_API ma_uint32 ma_node_get_input_bus_count(const ma_node* pNode); +MA_API ma_uint32 ma_node_get_output_bus_count(const ma_node* pNode); +MA_API ma_uint32 ma_node_get_input_channels(const ma_node* pNode, ma_uint32 inputBusIndex); +MA_API ma_uint32 ma_node_get_output_channels(const ma_node* pNode, ma_uint32 outputBusIndex); +MA_API ma_result ma_node_attach_output_bus(ma_node* pNode, ma_uint32 outputBusIndex, ma_node* pOtherNode, ma_uint32 otherNodeInputBusIndex); +MA_API ma_result ma_node_detach_output_bus(ma_node* pNode, ma_uint32 outputBusIndex); +MA_API ma_result ma_node_detach_all_output_buses(ma_node* pNode); +MA_API ma_result ma_node_set_output_bus_volume(ma_node* pNode, ma_uint32 outputBusIndex, float volume); +MA_API float ma_node_get_output_bus_volume(const ma_node* pNode, ma_uint32 outputBusIndex); +MA_API ma_result ma_node_set_state(ma_node* pNode, ma_node_state state); +MA_API ma_node_state ma_node_get_state(const ma_node* pNode); +MA_API ma_result ma_node_set_state_time(ma_node* pNode, ma_node_state state, ma_uint64 globalTime); +MA_API ma_uint64 ma_node_get_state_time(const ma_node* pNode, ma_node_state state); +MA_API ma_node_state ma_node_get_state_by_time(const ma_node* pNode, ma_uint64 globalTime); +MA_API ma_node_state ma_node_get_state_by_time_range(const ma_node* pNode, ma_uint64 globalTimeBeg, ma_uint64 globalTimeEnd); +MA_API ma_uint64 ma_node_get_time(const ma_node* pNode); +MA_API ma_result ma_node_set_time(ma_node* pNode, ma_uint64 localTime); + + +typedef struct +{ + ma_uint32 channels; + ma_uint16 nodeCacheCapInFrames; +} ma_node_graph_config; + +MA_API ma_node_graph_config ma_node_graph_config_init(ma_uint32 channels); + + +struct ma_node_graph +{ + /* Immutable. */ + ma_node_base base; /* The node graph itself is a node so it can be connected as an input to different node graph. This has zero inputs and calls ma_node_graph_read_pcm_frames() to generate it's output. */ + ma_node_base endpoint; /* Special node that all nodes eventually connect to. Data is read from this node in ma_node_graph_read_pcm_frames(). */ + ma_uint16 nodeCacheCapInFrames; + + /* Read and written by multiple threads. */ + MA_ATOMIC(4, ma_bool32) isReading; +}; + +MA_API ma_result ma_node_graph_init(const ma_node_graph_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_node_graph* pNodeGraph); +MA_API void ma_node_graph_uninit(ma_node_graph* pNodeGraph, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API ma_node* ma_node_graph_get_endpoint(ma_node_graph* pNodeGraph); +MA_API ma_result ma_node_graph_read_pcm_frames(ma_node_graph* pNodeGraph, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); +MA_API ma_uint32 ma_node_graph_get_channels(const ma_node_graph* pNodeGraph); +MA_API ma_uint64 ma_node_graph_get_time(const ma_node_graph* pNodeGraph); +MA_API ma_result ma_node_graph_set_time(ma_node_graph* pNodeGraph, ma_uint64 globalTime); + + + +/* Data source node. 0 input buses, 1 output bus. Used for reading from a data source. */ +typedef struct +{ + ma_node_config nodeConfig; + ma_data_source* pDataSource; +} ma_data_source_node_config; + +MA_API ma_data_source_node_config ma_data_source_node_config_init(ma_data_source* pDataSource); + + +typedef struct +{ + ma_node_base base; + ma_data_source* pDataSource; +} ma_data_source_node; + +MA_API ma_result ma_data_source_node_init(ma_node_graph* pNodeGraph, const ma_data_source_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source_node* pDataSourceNode); +MA_API void ma_data_source_node_uninit(ma_data_source_node* pDataSourceNode, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API ma_result ma_data_source_node_set_looping(ma_data_source_node* pDataSourceNode, ma_bool32 isLooping); +MA_API ma_bool32 ma_data_source_node_is_looping(ma_data_source_node* pDataSourceNode); + + +/* Splitter Node. 1 input, 2 outputs. Used for splitting/copying a stream so it can be as input into two separate output nodes. */ +typedef struct +{ + ma_node_config nodeConfig; + ma_uint32 channels; +} ma_splitter_node_config; + +MA_API ma_splitter_node_config ma_splitter_node_config_init(ma_uint32 channels); + + +typedef struct +{ + ma_node_base base; +} ma_splitter_node; + +MA_API ma_result ma_splitter_node_init(ma_node_graph* pNodeGraph, const ma_splitter_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_splitter_node* pSplitterNode); +MA_API void ma_splitter_node_uninit(ma_splitter_node* pSplitterNode, const ma_allocation_callbacks* pAllocationCallbacks); + + +/* +Biquad Node +*/ +typedef struct +{ + ma_node_config nodeConfig; + ma_biquad_config biquad; +} ma_biquad_node_config; + +MA_API ma_biquad_node_config ma_biquad_node_config_init(ma_uint32 channels, float b0, float b1, float b2, float a0, float a1, float a2); + + +typedef struct +{ + ma_node_base baseNode; + ma_biquad biquad; +} ma_biquad_node; + +MA_API ma_result ma_biquad_node_init(ma_node_graph* pNodeGraph, const ma_biquad_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_biquad_node* pNode); +MA_API ma_result ma_biquad_node_reinit(const ma_biquad_config* pConfig, ma_biquad_node* pNode); +MA_API void ma_biquad_node_uninit(ma_biquad_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks); + + +/* +Low Pass Filter Node +*/ +typedef struct +{ + ma_node_config nodeConfig; + ma_lpf_config lpf; +} ma_lpf_node_config; + +MA_API ma_lpf_node_config ma_lpf_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, double cutoffFrequency, ma_uint32 order); + + +typedef struct +{ + ma_node_base baseNode; + ma_lpf lpf; +} ma_lpf_node; + +MA_API ma_result ma_lpf_node_init(ma_node_graph* pNodeGraph, const ma_lpf_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_lpf_node* pNode); +MA_API ma_result ma_lpf_node_reinit(const ma_lpf_config* pConfig, ma_lpf_node* pNode); +MA_API void ma_lpf_node_uninit(ma_lpf_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks); + + +/* +High Pass Filter Node +*/ +typedef struct +{ + ma_node_config nodeConfig; + ma_hpf_config hpf; +} ma_hpf_node_config; + +MA_API ma_hpf_node_config ma_hpf_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, double cutoffFrequency, ma_uint32 order); + + +typedef struct +{ + ma_node_base baseNode; + ma_hpf hpf; +} ma_hpf_node; + +MA_API ma_result ma_hpf_node_init(ma_node_graph* pNodeGraph, const ma_hpf_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_hpf_node* pNode); +MA_API ma_result ma_hpf_node_reinit(const ma_hpf_config* pConfig, ma_hpf_node* pNode); +MA_API void ma_hpf_node_uninit(ma_hpf_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks); + + +/* +Band Pass Filter Node +*/ +typedef struct +{ + ma_node_config nodeConfig; + ma_bpf_config bpf; +} ma_bpf_node_config; + +MA_API ma_bpf_node_config ma_bpf_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, double cutoffFrequency, ma_uint32 order); + + +typedef struct +{ + ma_node_base baseNode; + ma_bpf bpf; +} ma_bpf_node; + +MA_API ma_result ma_bpf_node_init(ma_node_graph* pNodeGraph, const ma_bpf_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_bpf_node* pNode); +MA_API ma_result ma_bpf_node_reinit(const ma_bpf_config* pConfig, ma_bpf_node* pNode); +MA_API void ma_bpf_node_uninit(ma_bpf_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks); + + +/* +Notching Filter Node +*/ +typedef struct +{ + ma_node_config nodeConfig; + ma_notch_config notch; +} ma_notch_node_config; + +MA_API ma_notch_node_config ma_notch_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, double q, double frequency); + + +typedef struct +{ + ma_node_base baseNode; + ma_notch2 notch; +} ma_notch_node; + +MA_API ma_result ma_notch_node_init(ma_node_graph* pNodeGraph, const ma_notch_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_notch_node* pNode); +MA_API ma_result ma_notch_node_reinit(const ma_notch_config* pConfig, ma_notch_node* pNode); +MA_API void ma_notch_node_uninit(ma_notch_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks); + + +/* +Peaking Filter Node +*/ +typedef struct +{ + ma_node_config nodeConfig; + ma_peak_config peak; +} ma_peak_node_config; + +MA_API ma_peak_node_config ma_peak_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, double gainDB, double q, double frequency); + + +typedef struct +{ + ma_node_base baseNode; + ma_peak2 peak; +} ma_peak_node; + +MA_API ma_result ma_peak_node_init(ma_node_graph* pNodeGraph, const ma_peak_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_peak_node* pNode); +MA_API ma_result ma_peak_node_reinit(const ma_peak_config* pConfig, ma_peak_node* pNode); +MA_API void ma_peak_node_uninit(ma_peak_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks); + + +/* +Low Shelf Filter Node +*/ +typedef struct +{ + ma_node_config nodeConfig; + ma_loshelf_config loshelf; +} ma_loshelf_node_config; + +MA_API ma_loshelf_node_config ma_loshelf_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, double gainDB, double q, double frequency); + + +typedef struct +{ + ma_node_base baseNode; + ma_loshelf2 loshelf; +} ma_loshelf_node; + +MA_API ma_result ma_loshelf_node_init(ma_node_graph* pNodeGraph, const ma_loshelf_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_loshelf_node* pNode); +MA_API ma_result ma_loshelf_node_reinit(const ma_loshelf_config* pConfig, ma_loshelf_node* pNode); +MA_API void ma_loshelf_node_uninit(ma_loshelf_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks); + + +/* +High Shelf Filter Node +*/ +typedef struct +{ + ma_node_config nodeConfig; + ma_hishelf_config hishelf; +} ma_hishelf_node_config; + +MA_API ma_hishelf_node_config ma_hishelf_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, double gainDB, double q, double frequency); + + +typedef struct +{ + ma_node_base baseNode; + ma_hishelf2 hishelf; +} ma_hishelf_node; + +MA_API ma_result ma_hishelf_node_init(ma_node_graph* pNodeGraph, const ma_hishelf_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_hishelf_node* pNode); +MA_API ma_result ma_hishelf_node_reinit(const ma_hishelf_config* pConfig, ma_hishelf_node* pNode); +MA_API void ma_hishelf_node_uninit(ma_hishelf_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks); + + +typedef struct +{ + ma_node_config nodeConfig; + ma_delay_config delay; +} ma_delay_node_config; + +MA_API ma_delay_node_config ma_delay_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, ma_uint32 delayInFrames, float decay); + + +typedef struct +{ + ma_node_base baseNode; + ma_delay delay; +} ma_delay_node; + +MA_API ma_result ma_delay_node_init(ma_node_graph* pNodeGraph, const ma_delay_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_delay_node* pDelayNode); +MA_API void ma_delay_node_uninit(ma_delay_node* pDelayNode, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API void ma_delay_node_set_wet(ma_delay_node* pDelayNode, float value); +MA_API float ma_delay_node_get_wet(const ma_delay_node* pDelayNode); +MA_API void ma_delay_node_set_dry(ma_delay_node* pDelayNode, float value); +MA_API float ma_delay_node_get_dry(const ma_delay_node* pDelayNode); +MA_API void ma_delay_node_set_decay(ma_delay_node* pDelayNode, float value); +MA_API float ma_delay_node_get_decay(const ma_delay_node* pDelayNode); +#endif /* MA_NO_NODE_GRAPH */ + + +/************************************************************************************************************************************************************ + +Engine + +************************************************************************************************************************************************************/ +#if !defined(MA_NO_ENGINE) && !defined(MA_NO_NODE_GRAPH) +typedef struct ma_engine ma_engine; +typedef struct ma_sound ma_sound; + + +/* Sound flags. */ +typedef enum +{ + MA_SOUND_FLAG_STREAM = 0x00000001, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM */ + MA_SOUND_FLAG_DECODE = 0x00000002, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE */ + MA_SOUND_FLAG_ASYNC = 0x00000004, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC */ + MA_SOUND_FLAG_WAIT_INIT = 0x00000008, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT */ + MA_SOUND_FLAG_NO_DEFAULT_ATTACHMENT = 0x00000010, /* Do not attach to the endpoint by default. Useful for when setting up nodes in a complex graph system. */ + MA_SOUND_FLAG_NO_PITCH = 0x00000020, /* Disable pitch shifting with ma_sound_set_pitch() and ma_sound_group_set_pitch(). This is an optimization. */ + MA_SOUND_FLAG_NO_SPATIALIZATION = 0x00000040 /* Disable spatialization. */ +} ma_sound_flags; + +#ifndef MA_ENGINE_MAX_LISTENERS +#define MA_ENGINE_MAX_LISTENERS 4 +#endif + +#define MA_LISTENER_INDEX_CLOSEST ((ma_uint8)-1) + +typedef enum +{ + ma_engine_node_type_sound, + ma_engine_node_type_group +} ma_engine_node_type; + +typedef struct +{ + ma_engine* pEngine; + ma_engine_node_type type; + ma_uint32 channelsIn; + ma_uint32 channelsOut; + ma_uint32 sampleRate; /* Only used when the type is set to ma_engine_node_type_sound. */ + ma_bool8 isPitchDisabled; /* Pitching can be explicitly disable with MA_SOUND_FLAG_NO_PITCH to optimize processing. */ + ma_bool8 isSpatializationDisabled; /* Spatialization can be explicitly disabled with MA_SOUND_FLAG_NO_SPATIALIZATION. */ + ma_uint8 pinnedListenerIndex; /* The index of the listener this node should always use for spatialization. If set to MA_LISTENER_INDEX_CLOSEST the engine will use the closest listener. */ +} ma_engine_node_config; + +MA_API ma_engine_node_config ma_engine_node_config_init(ma_engine* pEngine, ma_engine_node_type type, ma_uint32 flags); + + +/* Base node object for both ma_sound and ma_sound_group. */ +typedef struct +{ + ma_node_base baseNode; /* Must be the first member for compatiblity with the ma_node API. */ + ma_engine* pEngine; /* A pointer to the engine. Set based on the value from the config. */ + ma_uint32 sampleRate; /* The sample rate of the input data. For sounds backed by a data source, this will be the data source's sample rate. Otherwise it'll be the engine's sample rate. */ + ma_fader fader; + ma_linear_resampler resampler; /* For pitch shift. */ + ma_spatializer spatializer; + ma_panner panner; + MA_ATOMIC(4, float) pitch; + float oldPitch; /* For determining whether or not the resampler needs to be updated to reflect the new pitch. The resampler will be updated on the mixing thread. */ + float oldDopplerPitch; /* For determining whether or not the resampler needs to be updated to take a new doppler pitch into account. */ + MA_ATOMIC(4, ma_bool32) isPitchDisabled; /* When set to true, pitching will be disabled which will allow the resampler to be bypassed to save some computation. */ + MA_ATOMIC(4, ma_bool32) isSpatializationDisabled; /* Set to false by default. When set to false, will not have spatialisation applied. */ + MA_ATOMIC(4, ma_uint32) pinnedListenerIndex; /* The index of the listener this node should always use for spatialization. If set to MA_LISTENER_INDEX_CLOSEST the engine will use the closest listener. */ + + /* Memory management. */ + ma_bool8 _ownsHeap; + void* _pHeap; +} ma_engine_node; + +MA_API ma_result ma_engine_node_get_heap_size(const ma_engine_node_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_engine_node_init_preallocated(const ma_engine_node_config* pConfig, void* pHeap, ma_engine_node* pEngineNode); +MA_API ma_result ma_engine_node_init(const ma_engine_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_engine_node* pEngineNode); +MA_API void ma_engine_node_uninit(ma_engine_node* pEngineNode, const ma_allocation_callbacks* pAllocationCallbacks); + + +#define MA_SOUND_SOURCE_CHANNEL_COUNT 0xFFFFFFFF + +typedef struct +{ + const char* pFilePath; /* Set this to load from the resource manager. */ + const wchar_t* pFilePathW; /* Set this to load from the resource manager. */ + ma_data_source* pDataSource; /* Set this to load from an existing data source. */ + ma_node* pInitialAttachment; /* If set, the sound will be attached to an input of this node. This can be set to a ma_sound. If set to NULL, the sound will be attached directly to the endpoint unless MA_SOUND_FLAG_NO_DEFAULT_ATTACHMENT is set in `flags`. */ + ma_uint32 initialAttachmentInputBusIndex; /* The index of the input bus of pInitialAttachment to attach the sound to. */ + ma_uint32 channelsIn; /* Ignored if using a data source as input (the data source's channel count will be used always). Otherwise, setting to 0 will cause the engine's channel count to be used. */ + ma_uint32 channelsOut; /* Set this to 0 (default) to use the engine's channel count. Set to MA_SOUND_SOURCE_CHANNEL_COUNT to use the data source's channel count (only used if using a data source as input). */ + ma_uint32 flags; /* A combination of MA_SOUND_FLAG_* flags. */ + ma_uint64 initialSeekPointInPCMFrames; /* Initializes the sound such that it's seeked to this location by default. */ + ma_uint64 rangeBegInPCMFrames; + ma_uint64 rangeEndInPCMFrames; + ma_uint64 loopPointBegInPCMFrames; + ma_uint64 loopPointEndInPCMFrames; + ma_bool32 isLooping; + ma_fence* pDoneFence; /* Released when the resource manager has finished decoding the entire sound. Not used with streams. */ +} ma_sound_config; + +MA_API ma_sound_config ma_sound_config_init(void); + +struct ma_sound +{ + ma_engine_node engineNode; /* Must be the first member for compatibility with the ma_node API. */ + ma_data_source* pDataSource; + MA_ATOMIC(8, ma_uint64) seekTarget; /* The PCM frame index to seek to in the mixing thread. Set to (~(ma_uint64)0) to not perform any seeking. */ + MA_ATOMIC(4, ma_bool32) atEnd; + ma_bool8 ownsDataSource; + + /* + We're declaring a resource manager data source object here to save us a malloc when loading a + sound via the resource manager, which I *think* will be the most common scenario. + */ +#ifndef MA_NO_RESOURCE_MANAGER + ma_resource_manager_data_source* pResourceManagerDataSource; +#endif +}; + +/* Structure specifically for sounds played with ma_engine_play_sound(). Making this a separate structure to reduce overhead. */ +typedef struct ma_sound_inlined ma_sound_inlined; +struct ma_sound_inlined +{ + ma_sound sound; + ma_sound_inlined* pNext; + ma_sound_inlined* pPrev; +}; + +/* A sound group is just a sound. */ +typedef ma_sound_config ma_sound_group_config; +typedef ma_sound ma_sound_group; + +MA_API ma_sound_group_config ma_sound_group_config_init(void); + + +typedef struct +{ +#if !defined(MA_NO_RESOURCE_MANAGER) + ma_resource_manager* pResourceManager; /* Can be null in which case a resource manager will be created for you. */ +#endif +#if !defined(MA_NO_DEVICE_IO) + ma_context* pContext; + ma_device* pDevice; /* If set, the caller is responsible for calling ma_engine_data_callback() in the device's data callback. */ + ma_device_id* pPlaybackDeviceID; /* The ID of the playback device to use with the default listener. */ +#endif + ma_log* pLog; /* When set to NULL, will use the context's log. */ + ma_uint32 listenerCount; /* Must be between 1 and MA_ENGINE_MAX_LISTENERS. */ + ma_uint32 channels; /* The number of channels to use when mixing and spatializing. When set to 0, will use the native channel count of the device. */ + ma_uint32 sampleRate; /* The sample rate. When set to 0 will use the native channel count of the device. */ + ma_uint32 periodSizeInFrames; /* If set to something other than 0, updates will always be exactly this size. The underlying device may be a different size, but from the perspective of the mixer that won't matter.*/ + ma_uint32 periodSizeInMilliseconds; /* Used if periodSizeInFrames is unset. */ + ma_uint32 gainSmoothTimeInFrames; /* The number of frames to interpolate the gain of spatialized sounds across. If set to 0, will use gainSmoothTimeInMilliseconds. */ + ma_uint32 gainSmoothTimeInMilliseconds; /* When set to 0, gainSmoothTimeInFrames will be used. If both are set to 0, a default value will be used. */ + ma_allocation_callbacks allocationCallbacks; + ma_bool32 noAutoStart; /* When set to true, requires an explicit call to ma_engine_start(). This is false by default, meaning the engine will be started automatically in ma_engine_init(). */ + ma_bool32 noDevice; /* When set to true, don't create a default device. ma_engine_read_pcm_frames() can be called manually to read data. */ + ma_mono_expansion_mode monoExpansionMode; /* Controls how the mono channel should be expanded to other channels when spatialization is disabled on a sound. */ + ma_vfs* pResourceManagerVFS; /* A pointer to a pre-allocated VFS object to use with the resource manager. This is ignored if pResourceManager is not NULL. */ +} ma_engine_config; + +MA_API ma_engine_config ma_engine_config_init(void); + + +struct ma_engine +{ + ma_node_graph nodeGraph; /* An engine is a node graph. It should be able to be plugged into any ma_node_graph API (with a cast) which means this must be the first member of this struct. */ +#if !defined(MA_NO_RESOURCE_MANAGER) + ma_resource_manager* pResourceManager; +#endif +#if !defined(MA_NO_DEVICE_IO) + ma_device* pDevice; /* Optionally set via the config, otherwise allocated by the engine in ma_engine_init(). */ +#endif + ma_log* pLog; + ma_uint32 sampleRate; + ma_uint32 listenerCount; + ma_spatializer_listener listeners[MA_ENGINE_MAX_LISTENERS]; + ma_allocation_callbacks allocationCallbacks; + ma_bool8 ownsResourceManager; + ma_bool8 ownsDevice; + ma_spinlock inlinedSoundLock; /* For synchronizing access so the inlined sound list. */ + ma_sound_inlined* pInlinedSoundHead; /* The first inlined sound. Inlined sounds are tracked in a linked list. */ + MA_ATOMIC(4, ma_uint32) inlinedSoundCount; /* The total number of allocated inlined sound objects. Used for debugging. */ + ma_uint32 gainSmoothTimeInFrames; /* The number of frames to interpolate the gain of spatialized sounds across. */ + ma_mono_expansion_mode monoExpansionMode; +}; + +MA_API ma_result ma_engine_init(const ma_engine_config* pConfig, ma_engine* pEngine); +MA_API void ma_engine_uninit(ma_engine* pEngine); +MA_API ma_result ma_engine_read_pcm_frames(ma_engine* pEngine, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); +MA_API ma_node_graph* ma_engine_get_node_graph(ma_engine* pEngine); +#if !defined(MA_NO_RESOURCE_MANAGER) +MA_API ma_resource_manager* ma_engine_get_resource_manager(ma_engine* pEngine); +#endif +MA_API ma_device* ma_engine_get_device(ma_engine* pEngine); +MA_API ma_log* ma_engine_get_log(ma_engine* pEngine); +MA_API ma_node* ma_engine_get_endpoint(ma_engine* pEngine); +MA_API ma_uint64 ma_engine_get_time(const ma_engine* pEngine); +MA_API ma_result ma_engine_set_time(ma_engine* pEngine, ma_uint64 globalTime); +MA_API ma_uint32 ma_engine_get_channels(const ma_engine* pEngine); +MA_API ma_uint32 ma_engine_get_sample_rate(const ma_engine* pEngine); + +MA_API ma_result ma_engine_start(ma_engine* pEngine); +MA_API ma_result ma_engine_stop(ma_engine* pEngine); +MA_API ma_result ma_engine_set_volume(ma_engine* pEngine, float volume); +MA_API ma_result ma_engine_set_gain_db(ma_engine* pEngine, float gainDB); + +MA_API ma_uint32 ma_engine_get_listener_count(const ma_engine* pEngine); +MA_API ma_uint32 ma_engine_find_closest_listener(const ma_engine* pEngine, float absolutePosX, float absolutePosY, float absolutePosZ); +MA_API void ma_engine_listener_set_position(ma_engine* pEngine, ma_uint32 listenerIndex, float x, float y, float z); +MA_API ma_vec3f ma_engine_listener_get_position(const ma_engine* pEngine, ma_uint32 listenerIndex); +MA_API void ma_engine_listener_set_direction(ma_engine* pEngine, ma_uint32 listenerIndex, float x, float y, float z); +MA_API ma_vec3f ma_engine_listener_get_direction(const ma_engine* pEngine, ma_uint32 listenerIndex); +MA_API void ma_engine_listener_set_velocity(ma_engine* pEngine, ma_uint32 listenerIndex, float x, float y, float z); +MA_API ma_vec3f ma_engine_listener_get_velocity(const ma_engine* pEngine, ma_uint32 listenerIndex); +MA_API void ma_engine_listener_set_cone(ma_engine* pEngine, ma_uint32 listenerIndex, float innerAngleInRadians, float outerAngleInRadians, float outerGain); +MA_API void ma_engine_listener_get_cone(const ma_engine* pEngine, ma_uint32 listenerIndex, float* pInnerAngleInRadians, float* pOuterAngleInRadians, float* pOuterGain); +MA_API void ma_engine_listener_set_world_up(ma_engine* pEngine, ma_uint32 listenerIndex, float x, float y, float z); +MA_API ma_vec3f ma_engine_listener_get_world_up(const ma_engine* pEngine, ma_uint32 listenerIndex); +MA_API void ma_engine_listener_set_enabled(ma_engine* pEngine, ma_uint32 listenerIndex, ma_bool32 isEnabled); +MA_API ma_bool32 ma_engine_listener_is_enabled(const ma_engine* pEngine, ma_uint32 listenerIndex); + +#ifndef MA_NO_RESOURCE_MANAGER +MA_API ma_result ma_engine_play_sound_ex(ma_engine* pEngine, const char* pFilePath, ma_node* pNode, ma_uint32 nodeInputBusIndex); +MA_API ma_result ma_engine_play_sound(ma_engine* pEngine, const char* pFilePath, ma_sound_group* pGroup); /* Fire and forget. */ +#endif + +#ifndef MA_NO_RESOURCE_MANAGER +MA_API ma_result ma_sound_init_from_file(ma_engine* pEngine, const char* pFilePath, ma_uint32 flags, ma_sound_group* pGroup, ma_fence* pDoneFence, ma_sound* pSound); +MA_API ma_result ma_sound_init_from_file_w(ma_engine* pEngine, const wchar_t* pFilePath, ma_uint32 flags, ma_sound_group* pGroup, ma_fence* pDoneFence, ma_sound* pSound); +MA_API ma_result ma_sound_init_copy(ma_engine* pEngine, const ma_sound* pExistingSound, ma_uint32 flags, ma_sound_group* pGroup, ma_sound* pSound); +#endif +MA_API ma_result ma_sound_init_from_data_source(ma_engine* pEngine, ma_data_source* pDataSource, ma_uint32 flags, ma_sound_group* pGroup, ma_sound* pSound); +MA_API ma_result ma_sound_init_ex(ma_engine* pEngine, const ma_sound_config* pConfig, ma_sound* pSound); +MA_API void ma_sound_uninit(ma_sound* pSound); +MA_API ma_engine* ma_sound_get_engine(const ma_sound* pSound); +MA_API ma_data_source* ma_sound_get_data_source(const ma_sound* pSound); +MA_API ma_result ma_sound_start(ma_sound* pSound); +MA_API ma_result ma_sound_stop(ma_sound* pSound); +MA_API void ma_sound_set_volume(ma_sound* pSound, float volume); +MA_API float ma_sound_get_volume(const ma_sound* pSound); +MA_API void ma_sound_set_pan(ma_sound* pSound, float pan); +MA_API float ma_sound_get_pan(const ma_sound* pSound); +MA_API void ma_sound_set_pan_mode(ma_sound* pSound, ma_pan_mode panMode); +MA_API ma_pan_mode ma_sound_get_pan_mode(const ma_sound* pSound); +MA_API void ma_sound_set_pitch(ma_sound* pSound, float pitch); +MA_API float ma_sound_get_pitch(const ma_sound* pSound); +MA_API void ma_sound_set_spatialization_enabled(ma_sound* pSound, ma_bool32 enabled); +MA_API ma_bool32 ma_sound_is_spatialization_enabled(const ma_sound* pSound); +MA_API void ma_sound_set_pinned_listener_index(ma_sound* pSound, ma_uint32 listenerIndex); +MA_API ma_uint32 ma_sound_get_pinned_listener_index(const ma_sound* pSound); +MA_API ma_uint32 ma_sound_get_listener_index(const ma_sound* pSound); +MA_API ma_vec3f ma_sound_get_direction_to_listener(const ma_sound* pSound); +MA_API void ma_sound_set_position(ma_sound* pSound, float x, float y, float z); +MA_API ma_vec3f ma_sound_get_position(const ma_sound* pSound); +MA_API void ma_sound_set_direction(ma_sound* pSound, float x, float y, float z); +MA_API ma_vec3f ma_sound_get_direction(const ma_sound* pSound); +MA_API void ma_sound_set_velocity(ma_sound* pSound, float x, float y, float z); +MA_API ma_vec3f ma_sound_get_velocity(const ma_sound* pSound); +MA_API void ma_sound_set_attenuation_model(ma_sound* pSound, ma_attenuation_model attenuationModel); +MA_API ma_attenuation_model ma_sound_get_attenuation_model(const ma_sound* pSound); +MA_API void ma_sound_set_positioning(ma_sound* pSound, ma_positioning positioning); +MA_API ma_positioning ma_sound_get_positioning(const ma_sound* pSound); +MA_API void ma_sound_set_rolloff(ma_sound* pSound, float rolloff); +MA_API float ma_sound_get_rolloff(const ma_sound* pSound); +MA_API void ma_sound_set_min_gain(ma_sound* pSound, float minGain); +MA_API float ma_sound_get_min_gain(const ma_sound* pSound); +MA_API void ma_sound_set_max_gain(ma_sound* pSound, float maxGain); +MA_API float ma_sound_get_max_gain(const ma_sound* pSound); +MA_API void ma_sound_set_min_distance(ma_sound* pSound, float minDistance); +MA_API float ma_sound_get_min_distance(const ma_sound* pSound); +MA_API void ma_sound_set_max_distance(ma_sound* pSound, float maxDistance); +MA_API float ma_sound_get_max_distance(const ma_sound* pSound); +MA_API void ma_sound_set_cone(ma_sound* pSound, float innerAngleInRadians, float outerAngleInRadians, float outerGain); +MA_API void ma_sound_get_cone(const ma_sound* pSound, float* pInnerAngleInRadians, float* pOuterAngleInRadians, float* pOuterGain); +MA_API void ma_sound_set_doppler_factor(ma_sound* pSound, float dopplerFactor); +MA_API float ma_sound_get_doppler_factor(const ma_sound* pSound); +MA_API void ma_sound_set_directional_attenuation_factor(ma_sound* pSound, float directionalAttenuationFactor); +MA_API float ma_sound_get_directional_attenuation_factor(const ma_sound* pSound); +MA_API void ma_sound_set_fade_in_pcm_frames(ma_sound* pSound, float volumeBeg, float volumeEnd, ma_uint64 fadeLengthInFrames); +MA_API void ma_sound_set_fade_in_milliseconds(ma_sound* pSound, float volumeBeg, float volumeEnd, ma_uint64 fadeLengthInMilliseconds); +MA_API float ma_sound_get_current_fade_volume(ma_sound* pSound); +MA_API void ma_sound_set_start_time_in_pcm_frames(ma_sound* pSound, ma_uint64 absoluteGlobalTimeInFrames); +MA_API void ma_sound_set_start_time_in_milliseconds(ma_sound* pSound, ma_uint64 absoluteGlobalTimeInMilliseconds); +MA_API void ma_sound_set_stop_time_in_pcm_frames(ma_sound* pSound, ma_uint64 absoluteGlobalTimeInFrames); +MA_API void ma_sound_set_stop_time_in_milliseconds(ma_sound* pSound, ma_uint64 absoluteGlobalTimeInMilliseconds); +MA_API ma_bool32 ma_sound_is_playing(const ma_sound* pSound); +MA_API ma_uint64 ma_sound_get_time_in_pcm_frames(const ma_sound* pSound); +MA_API void ma_sound_set_looping(ma_sound* pSound, ma_bool32 isLooping); +MA_API ma_bool32 ma_sound_is_looping(const ma_sound* pSound); +MA_API ma_bool32 ma_sound_at_end(const ma_sound* pSound); +MA_API ma_result ma_sound_seek_to_pcm_frame(ma_sound* pSound, ma_uint64 frameIndex); /* Just a wrapper around ma_data_source_seek_to_pcm_frame(). */ +MA_API ma_result ma_sound_get_data_format(ma_sound* pSound, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap); +MA_API ma_result ma_sound_get_cursor_in_pcm_frames(ma_sound* pSound, ma_uint64* pCursor); +MA_API ma_result ma_sound_get_length_in_pcm_frames(ma_sound* pSound, ma_uint64* pLength); +MA_API ma_result ma_sound_get_cursor_in_seconds(ma_sound* pSound, float* pCursor); +MA_API ma_result ma_sound_get_length_in_seconds(ma_sound* pSound, float* pLength); + +MA_API ma_result ma_sound_group_init(ma_engine* pEngine, ma_uint32 flags, ma_sound_group* pParentGroup, ma_sound_group* pGroup); +MA_API ma_result ma_sound_group_init_ex(ma_engine* pEngine, const ma_sound_group_config* pConfig, ma_sound_group* pGroup); +MA_API void ma_sound_group_uninit(ma_sound_group* pGroup); +MA_API ma_engine* ma_sound_group_get_engine(const ma_sound_group* pGroup); +MA_API ma_result ma_sound_group_start(ma_sound_group* pGroup); +MA_API ma_result ma_sound_group_stop(ma_sound_group* pGroup); +MA_API void ma_sound_group_set_volume(ma_sound_group* pGroup, float volume); +MA_API float ma_sound_group_get_volume(const ma_sound_group* pGroup); +MA_API void ma_sound_group_set_pan(ma_sound_group* pGroup, float pan); +MA_API float ma_sound_group_get_pan(const ma_sound_group* pGroup); +MA_API void ma_sound_group_set_pan_mode(ma_sound_group* pGroup, ma_pan_mode panMode); +MA_API ma_pan_mode ma_sound_group_get_pan_mode(const ma_sound_group* pGroup); +MA_API void ma_sound_group_set_pitch(ma_sound_group* pGroup, float pitch); +MA_API float ma_sound_group_get_pitch(const ma_sound_group* pGroup); +MA_API void ma_sound_group_set_spatialization_enabled(ma_sound_group* pGroup, ma_bool32 enabled); +MA_API ma_bool32 ma_sound_group_is_spatialization_enabled(const ma_sound_group* pGroup); +MA_API void ma_sound_group_set_pinned_listener_index(ma_sound_group* pGroup, ma_uint32 listenerIndex); +MA_API ma_uint32 ma_sound_group_get_pinned_listener_index(const ma_sound_group* pGroup); +MA_API ma_uint32 ma_sound_group_get_listener_index(const ma_sound_group* pGroup); +MA_API ma_vec3f ma_sound_group_get_direction_to_listener(const ma_sound_group* pGroup); +MA_API void ma_sound_group_set_position(ma_sound_group* pGroup, float x, float y, float z); +MA_API ma_vec3f ma_sound_group_get_position(const ma_sound_group* pGroup); +MA_API void ma_sound_group_set_direction(ma_sound_group* pGroup, float x, float y, float z); +MA_API ma_vec3f ma_sound_group_get_direction(const ma_sound_group* pGroup); +MA_API void ma_sound_group_set_velocity(ma_sound_group* pGroup, float x, float y, float z); +MA_API ma_vec3f ma_sound_group_get_velocity(const ma_sound_group* pGroup); +MA_API void ma_sound_group_set_attenuation_model(ma_sound_group* pGroup, ma_attenuation_model attenuationModel); +MA_API ma_attenuation_model ma_sound_group_get_attenuation_model(const ma_sound_group* pGroup); +MA_API void ma_sound_group_set_positioning(ma_sound_group* pGroup, ma_positioning positioning); +MA_API ma_positioning ma_sound_group_get_positioning(const ma_sound_group* pGroup); +MA_API void ma_sound_group_set_rolloff(ma_sound_group* pGroup, float rolloff); +MA_API float ma_sound_group_get_rolloff(const ma_sound_group* pGroup); +MA_API void ma_sound_group_set_min_gain(ma_sound_group* pGroup, float minGain); +MA_API float ma_sound_group_get_min_gain(const ma_sound_group* pGroup); +MA_API void ma_sound_group_set_max_gain(ma_sound_group* pGroup, float maxGain); +MA_API float ma_sound_group_get_max_gain(const ma_sound_group* pGroup); +MA_API void ma_sound_group_set_min_distance(ma_sound_group* pGroup, float minDistance); +MA_API float ma_sound_group_get_min_distance(const ma_sound_group* pGroup); +MA_API void ma_sound_group_set_max_distance(ma_sound_group* pGroup, float maxDistance); +MA_API float ma_sound_group_get_max_distance(const ma_sound_group* pGroup); +MA_API void ma_sound_group_set_cone(ma_sound_group* pGroup, float innerAngleInRadians, float outerAngleInRadians, float outerGain); +MA_API void ma_sound_group_get_cone(const ma_sound_group* pGroup, float* pInnerAngleInRadians, float* pOuterAngleInRadians, float* pOuterGain); +MA_API void ma_sound_group_set_doppler_factor(ma_sound_group* pGroup, float dopplerFactor); +MA_API float ma_sound_group_get_doppler_factor(const ma_sound_group* pGroup); +MA_API void ma_sound_group_set_directional_attenuation_factor(ma_sound_group* pGroup, float directionalAttenuationFactor); +MA_API float ma_sound_group_get_directional_attenuation_factor(const ma_sound_group* pGroup); +MA_API void ma_sound_group_set_fade_in_pcm_frames(ma_sound_group* pGroup, float volumeBeg, float volumeEnd, ma_uint64 fadeLengthInFrames); +MA_API void ma_sound_group_set_fade_in_milliseconds(ma_sound_group* pGroup, float volumeBeg, float volumeEnd, ma_uint64 fadeLengthInMilliseconds); +MA_API float ma_sound_group_get_current_fade_volume(ma_sound_group* pGroup); +MA_API void ma_sound_group_set_start_time_in_pcm_frames(ma_sound_group* pGroup, ma_uint64 absoluteGlobalTimeInFrames); +MA_API void ma_sound_group_set_start_time_in_milliseconds(ma_sound_group* pGroup, ma_uint64 absoluteGlobalTimeInMilliseconds); +MA_API void ma_sound_group_set_stop_time_in_pcm_frames(ma_sound_group* pGroup, ma_uint64 absoluteGlobalTimeInFrames); +MA_API void ma_sound_group_set_stop_time_in_milliseconds(ma_sound_group* pGroup, ma_uint64 absoluteGlobalTimeInMilliseconds); +MA_API ma_bool32 ma_sound_group_is_playing(const ma_sound_group* pGroup); +MA_API ma_uint64 ma_sound_group_get_time_in_pcm_frames(const ma_sound_group* pGroup); +#endif /* MA_NO_ENGINE */ + #ifdef __cplusplus } #endif #endif /* miniaudio_h */ +/* +This is for preventing greying out of the implementation section. +*/ +#if defined(Q_CREATOR_RUN) || defined(__INTELLISENSE__) || defined(__CDT_PARSER__) +#define MINIAUDIO_IMPLEMENTATION +#endif /************************************************************************************************************************************************************ ************************************************************************************************************************************************************* @@ -6552,6 +11051,9 @@ IMPLEMENTATION #include /* For strcasecmp(). */ #include /* For wcslen(), wcsrtombs() */ #endif +#ifdef _MSC_VER + #include /* For _controlfp_s constants */ +#endif #ifdef MA_WIN32 #include @@ -6560,6 +11062,7 @@ IMPLEMENTATION #include /* For memset() */ #include #include /* select() (used for ma_sleep()). */ +#include #endif #include /* For fstat(), etc. */ @@ -6602,15 +11105,10 @@ IMPLEMENTATION #define MA_X64 #elif defined(__i386) || defined(_M_IX86) #define MA_X86 -#elif defined(__arm__) || defined(_M_ARM) +#elif defined(__arm__) || defined(_M_ARM) || defined(__arm64) || defined(__arm64__) || defined(__aarch64__) || defined(_M_ARM64) #define MA_ARM #endif -/* Cannot currently support AVX-512 if AVX is disabled. */ -#if !defined(MA_NO_AVX512) && defined(MA_NO_AVX2) -#define MA_NO_AVX512 -#endif - /* Intrinsics Support */ #if defined(MA_X64) || defined(MA_X86) #if defined(_MSC_VER) && !defined(__clang__) @@ -6624,9 +11122,6 @@ IMPLEMENTATION #if _MSC_VER >= 1700 && !defined(MA_NO_AVX2) /* 2012 */ #define MA_SUPPORT_AVX2 #endif - #if _MSC_VER >= 1910 && !defined(MA_NO_AVX512) /* 2017 */ - #define MA_SUPPORT_AVX512 - #endif #else /* Assume GNUC-style. */ #if defined(__SSE2__) && !defined(MA_NO_SSE2) @@ -6638,9 +11133,6 @@ IMPLEMENTATION #if defined(__AVX2__) && !defined(MA_NO_AVX2) #define MA_SUPPORT_AVX2 #endif - #if defined(__AVX512F__) && !defined(MA_NO_AVX512) - #define MA_SUPPORT_AVX512 - #endif #endif /* If at this point we still haven't determined compiler support for the intrinsics just fall back to __has_include. */ @@ -6654,14 +11146,9 @@ IMPLEMENTATION #if !defined(MA_SUPPORT_AVX2) && !defined(MA_NO_AVX2) && __has_include() #define MA_SUPPORT_AVX2 #endif - #if !defined(MA_SUPPORT_AVX512) && !defined(MA_NO_AVX512) && __has_include() - #define MA_SUPPORT_AVX512 - #endif #endif - #if defined(MA_SUPPORT_AVX512) - #include /* Not a mistake. Intentionally including instead of because otherwise the compiler will complain. */ - #elif defined(MA_SUPPORT_AVX2) || defined(MA_SUPPORT_AVX) + #if defined(MA_SUPPORT_AVX2) || defined(MA_SUPPORT_AVX) #include #elif defined(MA_SUPPORT_SSE2) #include @@ -6671,16 +11158,6 @@ IMPLEMENTATION #if defined(MA_ARM) #if !defined(MA_NO_NEON) && (defined(__ARM_NEON) || defined(__aarch64__) || defined(_M_ARM64)) #define MA_SUPPORT_NEON - #endif - - /* Fall back to looking for the #include file. */ - #if !defined(__GNUC__) && !defined(__clang__) && defined(__has_include) - #if !defined(MA_SUPPORT_NEON) && !defined(MA_NO_NEON) && __has_include() - #define MA_SUPPORT_NEON - #endif - #endif - - #if defined(MA_SUPPORT_NEON) #include #endif #endif @@ -6689,6 +11166,7 @@ IMPLEMENTATION #if defined(_MSC_VER) #pragma warning(push) #pragma warning(disable:4752) /* found Intel(R) Advanced Vector Extensions; consider using /arch:AVX */ + #pragma warning(disable:4049) /* compiler limit : terminating line number emission */ #endif #if defined(MA_X64) || defined(MA_X86) @@ -6850,41 +11328,6 @@ static MA_INLINE ma_bool32 ma_has_avx2(void) #endif } -static MA_INLINE ma_bool32 ma_has_avx512f(void) -{ -#if defined(MA_SUPPORT_AVX512) - #if (defined(MA_X64) || defined(MA_X86)) && !defined(MA_NO_AVX512) - #if defined(__AVX512F__) - return MA_TRUE; /* If the compiler is allowed to freely generate AVX-512F code we can assume support. */ - #else - /* AVX-512 requires both CPU and OS support. */ - #if defined(MA_NO_CPUID) || defined(MA_NO_XGETBV) - return MA_FALSE; - #else - int info1[4]; - int info7[4]; - ma_cpuid(info1, 1); - ma_cpuid(info7, 7); - if (((info1[2] & (1 << 27)) != 0) && ((info7[1] & (1 << 16)) != 0)) { - ma_uint64 xrc = ma_xgetbv(0); - if ((xrc & 0xE6) == 0xE6) { - return MA_TRUE; - } else { - return MA_FALSE; - } - } else { - return MA_FALSE; - } - #endif - #endif - #else - return MA_FALSE; /* AVX-512F is only supported on x86 and x64 architectures. */ - #endif -#else - return MA_FALSE; /* No compiler support. */ -#endif -} - static MA_INLINE ma_bool32 ma_has_neon(void) { #if defined(MA_SUPPORT_NEON) @@ -6934,7 +11377,7 @@ static MA_INLINE ma_bool32 ma_has_neon(void) #elif defined(_MSC_VER) #define MA_ASSUME(x) __assume(x) #else - #define MA_ASSUME(x) while(0) + #define MA_ASSUME(x) (void)(x) #endif #endif @@ -7033,7 +11476,7 @@ static void ma_sleep__posix(ma_uint32 milliseconds) (void)milliseconds; MA_ASSERT(MA_FALSE); /* The Emscripten build should never sleep. */ #else - #if _POSIX_C_SOURCE >= 199309L + #if defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 199309L struct timespec ts; ts.tv_sec = milliseconds / 1000; ts.tv_nsec = milliseconds % 1000 * 1000000; @@ -7048,7 +11491,7 @@ static void ma_sleep__posix(ma_uint32 milliseconds) } #endif -static void ma_sleep(ma_uint32 milliseconds) +static MA_INLINE void ma_sleep(ma_uint32 milliseconds) { #ifdef MA_WIN32 ma_sleep__win32(milliseconds); @@ -7077,7 +11520,7 @@ static MA_INLINE void ma_yield() #else __asm__ __volatile__ ("pause"); #endif -#elif (defined(__arm__) && defined(__ARM_ARCH) && __ARM_ARCH >= 7) || (defined(_M_ARM) && _M_ARM >= 7) || defined(__ARM_ARCH_6K__) || defined(__ARM_ARCH_6T2__) +#elif (defined(__arm__) && defined(__ARM_ARCH) && __ARM_ARCH >= 7) || defined(_M_ARM64) || (defined(_M_ARM) && _M_ARM >= 7) || defined(__ARM_ARCH_6K__) || defined(__ARM_ARCH_6T2__) /* ARM */ #if defined(_MSC_VER) /* Apparently there is a __yield() intrinsic that's compatible with ARM, but I cannot find documentation for it nor can I find where it's declared. */ @@ -7091,6 +11534,96 @@ static MA_INLINE void ma_yield() } +#define MA_MM_DENORMALS_ZERO_MASK 0x0040 +#define MA_MM_FLUSH_ZERO_MASK 0x8000 + +static MA_INLINE unsigned int ma_disable_denormals() +{ + unsigned int prevState; + + #if defined(_MSC_VER) + { + /* + Older versions of Visual Studio don't support the "safe" versions of _controlfp_s(). I don't + know which version of Visual Studio first added support for _controlfp_s(), but I do know + that VC6 lacks support. _MSC_VER = 1200 is VC6, but if you get compilation errors on older + versions of Visual Studio, let me know and I'll make the necessary adjustment. + */ + #if _MSC_VER <= 1200 + { + prevState = _statusfp(); + _controlfp(prevState | _DN_FLUSH, _MCW_DN); + } + #else + { + unsigned int unused; + _controlfp_s(&prevState, 0, 0); + _controlfp_s(&unused, prevState | _DN_FLUSH, _MCW_DN); + } + #endif + } + #elif defined(MA_X86) || defined(MA_X64) + { + #if defined(__SSE2__) && !(defined(__TINYC__) || defined(__WATCOMC__)) /* <-- Add compilers that lack support for _mm_getcsr() and _mm_setcsr() to this list. */ + { + prevState = _mm_getcsr(); + _mm_setcsr(prevState | MA_MM_DENORMALS_ZERO_MASK | MA_MM_FLUSH_ZERO_MASK); + } + #else + { + /* x88/64, but no support for _mm_getcsr()/_mm_setcsr(). May need to fall back to inlined assembly here. */ + prevState = 0; + } + #endif + } + #else + { + /* Unknown or unsupported architecture. No-op. */ + prevState = 0; + } + #endif + + return prevState; +} + +static MA_INLINE void ma_restore_denormals(unsigned int prevState) +{ + #if defined(_MSC_VER) + { + /* Older versions of Visual Studio do not support _controlfp_s(). See ma_disable_denormals(). */ + #if _MSC_VER <= 1200 + { + _controlfp(prevState, _MCW_DN); + } + #else + { + unsigned int unused; + _controlfp_s(&unused, prevState, _MCW_DN); + } + #endif + } + #elif defined(MA_X86) || defined(MA_X64) + { + #if defined(__SSE2__) && !(defined(__TINYC__) || defined(__WATCOMC__)) /* <-- Add compilers that lack support for _mm_getcsr() and _mm_setcsr() to this list. */ + { + _mm_setcsr(prevState); + } + #else + { + /* x88/64, but no support for _mm_getcsr()/_mm_setcsr(). May need to fall back to inlined assembly here. */ + (void)prevState; + } + #endif + } + #else + { + /* Unknown or unsupported architecture. No-op. */ + (void)prevState; + } + #endif +} + + #ifndef MA_COINIT_VALUE #define MA_COINIT_VALUE 0 /* 0 = COINIT_MULTITHREADED */ @@ -7344,11 +11877,21 @@ static MA_INLINE double ma_sqrtd(double x) } +static MA_INLINE float ma_sinf(float x) +{ + return (float)ma_sind((float)x); +} + static MA_INLINE double ma_cosd(double x) { return ma_sind((MA_PI_D*0.5) - x); } +static MA_INLINE float ma_cosf(float x) +{ + return (float)ma_cosd((float)x); +} + static MA_INLINE double ma_log10d(double x) { return ma_logd(x) * 0.43429448190325182765; @@ -7684,6 +12227,10 @@ MA_API int ma_strappend(char* dst, size_t dstSize, const char* srcA, const char* MA_API char* ma_copy_string(const char* src, const ma_allocation_callbacks* pAllocationCallbacks) { + if (src == NULL) { + return NULL; + } + size_t sz = strlen(src)+1; char* dst = (char*)ma_malloc(sz, pAllocationCallbacks); if (dst == NULL) { @@ -8367,76 +12914,6 @@ static void ma__free_default(void* p, void* pUserData) MA_FREE(p); } - -static void* ma__malloc_from_callbacks(size_t sz, const ma_allocation_callbacks* pAllocationCallbacks) -{ - if (pAllocationCallbacks == NULL) { - return NULL; - } - - if (pAllocationCallbacks->onMalloc != NULL) { - return pAllocationCallbacks->onMalloc(sz, pAllocationCallbacks->pUserData); - } - - /* Try using realloc(). */ - if (pAllocationCallbacks->onRealloc != NULL) { - return pAllocationCallbacks->onRealloc(NULL, sz, pAllocationCallbacks->pUserData); - } - - return NULL; -} - -static void* ma__realloc_from_callbacks(void* p, size_t szNew, size_t szOld, const ma_allocation_callbacks* pAllocationCallbacks) -{ - if (pAllocationCallbacks == NULL) { - return NULL; - } - - if (pAllocationCallbacks->onRealloc != NULL) { - return pAllocationCallbacks->onRealloc(p, szNew, pAllocationCallbacks->pUserData); - } - - /* Try emulating realloc() in terms of malloc()/free(). */ - if (pAllocationCallbacks->onMalloc != NULL && pAllocationCallbacks->onFree != NULL) { - void* p2; - - p2 = pAllocationCallbacks->onMalloc(szNew, pAllocationCallbacks->pUserData); - if (p2 == NULL) { - return NULL; - } - - if (p != NULL) { - MA_COPY_MEMORY(p2, p, szOld); - pAllocationCallbacks->onFree(p, pAllocationCallbacks->pUserData); - } - - return p2; - } - - return NULL; -} - -static MA_INLINE void* ma__calloc_from_callbacks(size_t sz, const ma_allocation_callbacks* pAllocationCallbacks) -{ - void* p = ma__malloc_from_callbacks(sz, pAllocationCallbacks); - if (p != NULL) { - MA_ZERO_MEMORY(p, sz); - } - - return p; -} - -static void ma__free_from_callbacks(void* p, const ma_allocation_callbacks* pAllocationCallbacks) -{ - if (p == NULL || pAllocationCallbacks == NULL) { - return; - } - - if (pAllocationCallbacks->onFree != NULL) { - pAllocationCallbacks->onFree(p, pAllocationCallbacks->pUserData); - } -} - static ma_allocation_callbacks ma_allocation_callbacks_init_default(void) { ma_allocation_callbacks callbacks; @@ -8547,7 +13024,7 @@ MA_API ma_result ma_log_init(const ma_allocation_callbacks* pAllocationCallbacks } } #endif - + /* If we're using debug output, enable it. */ #if defined(MA_DEBUG_OUTPUT) { @@ -8644,15 +13121,6 @@ MA_API ma_result ma_log_post(ma_log* pLog, ma_uint32 level, const char* pMessage return MA_INVALID_ARGS; } - /* If it's a debug log, ignore it unless MA_DEBUG_OUTPUT is enabled. */ - #if !defined(MA_DEBUG_OUTPUT) - { - if (level == MA_LOG_LEVEL_DEBUG) { - return MA_INVALID_ARGS; /* Don't post debug messages if debug output is disabled. */ - } - } - #endif - ma_log_lock(pLog); { ma_uint32 iLog; @@ -8719,18 +13187,6 @@ MA_API ma_result ma_log_postv(ma_log* pLog, ma_uint32 level, const char* pFormat return MA_INVALID_ARGS; } - /* - If it's a debug log, ignore it unless MA_DEBUG_OUTPUT is enabled. Do this before generating the - formatted message string so that we don't waste time only to have ma_log_post() reject it. - */ - #if !defined(MA_DEBUG_OUTPUT) - { - if (level == MA_LOG_LEVEL_DEBUG) { - return MA_INVALID_ARGS; /* Don't post debug messages if debug output is disabled. */ - } - } - #endif - #if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || ((!defined(_MSC_VER) || _MSC_VER >= 1900) && !defined(__STRICT_ANSI__) && !defined(_NO_EXT_KEYS)) { ma_result result; @@ -8840,18 +13296,6 @@ MA_API ma_result ma_log_postf(ma_log* pLog, ma_uint32 level, const char* pFormat return MA_INVALID_ARGS; } - /* - If it's a debug log, ignore it unless MA_DEBUG_OUTPUT is enabled. Do this before generating the - formatted message string so that we don't waste time only to have ma_log_post() reject it. - */ - #if !defined(MA_DEBUG_OUTPUT) - { - if (level == MA_LOG_LEVEL_DEBUG) { - return MA_INVALID_ARGS; /* Don't post debug messages if debug output is disabled. */ - } - } - #endif - va_start(args, pFormat); { result = ma_log_postv(pLog, level, pFormat, args); @@ -8863,8 +13307,32 @@ MA_API ma_result ma_log_postf(ma_log* pLog, ma_uint32 level, const char* pFormat +static MA_INLINE ma_uint8 ma_clip_u8(ma_int32 x) +{ + return (ma_uint8)(ma_clamp(x, -128, 127) + 128); +} + +static MA_INLINE ma_int16 ma_clip_s16(ma_int32 x) +{ + return (ma_int16)ma_clamp(x, -32768, 32767); +} + +static MA_INLINE ma_int64 ma_clip_s24(ma_int64 x) +{ + return (ma_int64)ma_clamp(x, -8388608, 8388607); +} + +static MA_INLINE ma_int32 ma_clip_s32(ma_int64 x) +{ + /* This dance is to silence warnings with -std=c89. A good compiler should be able to optimize this away. */ + ma_int64 clipMin; + ma_int64 clipMax; + clipMin = -((ma_int64)2147483647 + 1); + clipMax = (ma_int64)2147483647; + + return (ma_int32)ma_clamp(x, clipMin, clipMax); +} -/* Clamps an f32 sample to -1..1 */ static MA_INLINE float ma_clip_f32(float x) { if (x < -1) return -1; @@ -8872,6 +13340,7 @@ static MA_INLINE float ma_clip_f32(float x) return x; } + static MA_INLINE float ma_mix_f32(float x, float y, float a) { return x*(1-a) + y*a; @@ -8896,12 +13365,6 @@ static MA_INLINE __m256 ma_mix_f32_fast__avx2(__m256 x, __m256 y, __m256 a) return _mm256_add_ps(x, _mm256_mul_ps(_mm256_sub_ps(y, x), a)); } #endif -#if defined(MA_SUPPORT_AVX512) -static MA_INLINE __m512 ma_mix_f32_fast__avx512(__m512 x, __m512 y, __m512 a) -{ - return _mm512_add_ps(x, _mm512_mul_ps(_mm512_sub_ps(y, x), a)); -} -#endif #if defined(MA_SUPPORT_NEON) static MA_INLINE float32x4_t ma_mix_f32_fast__neon(float32x4_t x, float32x4_t y, float32x4_t a) { @@ -8944,6 +13407,27 @@ static MA_INLINE ma_uint32 ma_gcf_u32(ma_uint32 a, ma_uint32 b) } +static ma_uint32 ma_ffs_32(ma_uint32 x) +{ + ma_uint32 i; + + /* Just a naive implementation just to get things working for now. Will optimize this later. */ + for (i = 0; i < 32; i += 1) { + if ((x & (1 << i)) != 0) { + return i; + } + } + + return i; +} + +static MA_INLINE ma_int16 ma_float_to_fixed_16(float x) +{ + return (ma_int16)(x * (1 << 8)); +} + + + /* Random Number Generation @@ -9104,7 +13588,7 @@ typedef signed short c89atomic_int16; typedef unsigned short c89atomic_uint16; typedef signed int c89atomic_int32; typedef unsigned int c89atomic_uint32; -#if defined(_MSC_VER) +#if defined(_MSC_VER) && !defined(__clang__) typedef signed __int64 c89atomic_int64; typedef unsigned __int64 c89atomic_uint64; #else @@ -9153,7 +13637,7 @@ typedef unsigned char c89atomic_bool; #define C89ATOMIC_X64 #elif defined(__i386) || defined(_M_IX86) #define C89ATOMIC_X86 -#elif defined(__arm__) || defined(_M_ARM) +#elif defined(__arm__) || defined(_M_ARM) || defined(__arm64) || defined(__arm64__) || defined(__aarch64__) || defined(_M_ARM64) #define C89ATOMIC_ARM #endif #if defined(_MSC_VER) @@ -9262,7 +13746,7 @@ typedef unsigned char c89atomic_bool; #define c89atomic_compare_and_swap_32(dst, expected, desired) (c89atomic_uint32)_InterlockedCompareExchange((volatile long*)dst, (long)desired, (long)expected) #endif #if defined(C89ATOMIC_HAS_64) - #define c89atomic_compare_and_swap_64(dst, expected, desired) (c89atomic_uint64)_InterlockedCompareExchange64((volatile long long*)dst, (long long)desired, (long long)expected) + #define c89atomic_compare_and_swap_64(dst, expected, desired) (c89atomic_uint64)_InterlockedCompareExchange64((volatile c89atomic_int64*)dst, (c89atomic_int64)desired, (c89atomic_int64)expected) #endif #endif #if defined(C89ATOMIC_MSVC_USE_INLINED_ASSEMBLY) @@ -10449,11 +14933,11 @@ typedef unsigned char c89atomic_bool; { return (void*)c89atomic_exchange_explicit_64((volatile c89atomic_uint64*)dst, (c89atomic_uint64)src, order); } - static C89ATOMIC_INLINE c89atomic_bool c89atomic_compare_exchange_strong_explicit_ptr(volatile void** dst, volatile void** expected, void* desired, c89atomic_memory_order successOrder, c89atomic_memory_order failureOrder) + static C89ATOMIC_INLINE c89atomic_bool c89atomic_compare_exchange_strong_explicit_ptr(volatile void** dst, void** expected, void* desired, c89atomic_memory_order successOrder, c89atomic_memory_order failureOrder) { return c89atomic_compare_exchange_strong_explicit_64((volatile c89atomic_uint64*)dst, (c89atomic_uint64*)expected, (c89atomic_uint64)desired, successOrder, failureOrder); } - static C89ATOMIC_INLINE c89atomic_bool c89atomic_compare_exchange_weak_explicit_ptr(volatile void** dst, volatile void** expected, void* desired, c89atomic_memory_order successOrder, c89atomic_memory_order failureOrder) + static C89ATOMIC_INLINE c89atomic_bool c89atomic_compare_exchange_weak_explicit_ptr(volatile void** dst, void** expected, void* desired, c89atomic_memory_order successOrder, c89atomic_memory_order failureOrder) { return c89atomic_compare_exchange_weak_explicit_64((volatile c89atomic_uint64*)dst, (c89atomic_uint64*)expected, (c89atomic_uint64)desired, successOrder, failureOrder); } @@ -10482,7 +14966,7 @@ typedef unsigned char c89atomic_bool; { return c89atomic_compare_exchange_strong_explicit_32((volatile c89atomic_uint32*)dst, (c89atomic_uint32*)expected, (c89atomic_uint32)desired, successOrder, failureOrder); } - static C89ATOMIC_INLINE c89atomic_bool c89atomic_compare_exchange_weak_explicit_ptr(volatile void** dst, volatile void** expected, void* desired, c89atomic_memory_order successOrder, c89atomic_memory_order failureOrder) + static C89ATOMIC_INLINE c89atomic_bool c89atomic_compare_exchange_weak_explicit_ptr(volatile void** dst, void** expected, void* desired, c89atomic_memory_order successOrder, c89atomic_memory_order failureOrder) { return c89atomic_compare_exchange_weak_explicit_32((volatile c89atomic_uint32*)dst, (c89atomic_uint32*)expected, (c89atomic_uint32)desired, successOrder, failureOrder); } @@ -10498,8 +14982,8 @@ typedef unsigned char c89atomic_bool; #define c89atomic_store_ptr(dst, src) c89atomic_store_explicit_ptr((volatile void**)dst, (void*)src, c89atomic_memory_order_seq_cst) #define c89atomic_load_ptr(ptr) c89atomic_load_explicit_ptr((volatile void**)ptr, c89atomic_memory_order_seq_cst) #define c89atomic_exchange_ptr(dst, src) c89atomic_exchange_explicit_ptr((volatile void**)dst, (void*)src, c89atomic_memory_order_seq_cst) -#define c89atomic_compare_exchange_strong_ptr(dst, expected, desired) c89atomic_compare_exchange_strong_explicit_ptr((volatile void**)dst, (volatile void**)expected, (void*)desired, c89atomic_memory_order_seq_cst, c89atomic_memory_order_seq_cst) -#define c89atomic_compare_exchange_weak_ptr(dst, expected, desired) c89atomic_compare_exchange_weak_explicit_ptr((volatile void**)dst, (volatile void**)expected, (void*)desired, c89atomic_memory_order_seq_cst, c89atomic_memory_order_seq_cst) +#define c89atomic_compare_exchange_strong_ptr(dst, expected, desired) c89atomic_compare_exchange_strong_explicit_ptr((volatile void**)dst, (void**)expected, (void*)desired, c89atomic_memory_order_seq_cst, c89atomic_memory_order_seq_cst) +#define c89atomic_compare_exchange_weak_ptr(dst, expected, desired) c89atomic_compare_exchange_weak_explicit_ptr((volatile void**)dst, (void**)expected, (void*)desired, c89atomic_memory_order_seq_cst, c89atomic_memory_order_seq_cst) #define c89atomic_test_and_set_8( ptr) c89atomic_test_and_set_explicit_8( ptr, c89atomic_memory_order_seq_cst) #define c89atomic_test_and_set_16(ptr) c89atomic_test_and_set_explicit_16(ptr, c89atomic_memory_order_seq_cst) #define c89atomic_test_and_set_32(ptr) c89atomic_test_and_set_explicit_32(ptr, c89atomic_memory_order_seq_cst) @@ -10672,16 +15156,16 @@ static C89ATOMIC_INLINE void c89atomic_store_explicit_f64(volatile double* dst, x.f = src; c89atomic_store_explicit_64((volatile c89atomic_uint64*)dst, x.i, order); } -static C89ATOMIC_INLINE float c89atomic_load_explicit_f32(volatile float* ptr, c89atomic_memory_order order) +static C89ATOMIC_INLINE float c89atomic_load_explicit_f32(volatile const float* ptr, c89atomic_memory_order order) { c89atomic_if32 r; - r.i = c89atomic_load_explicit_32((volatile c89atomic_uint32*)ptr, order); + r.i = c89atomic_load_explicit_32((volatile const c89atomic_uint32*)ptr, order); return r.f; } -static C89ATOMIC_INLINE double c89atomic_load_explicit_f64(volatile double* ptr, c89atomic_memory_order order) +static C89ATOMIC_INLINE double c89atomic_load_explicit_f64(volatile const double* ptr, c89atomic_memory_order order) { c89atomic_if64 r; - r.i = c89atomic_load_explicit_64((volatile c89atomic_uint64*)ptr, order); + r.i = c89atomic_load_explicit_64((volatile const c89atomic_uint64*)ptr, order); return r.f; } static C89ATOMIC_INLINE float c89atomic_exchange_explicit_f32(volatile float* dst, float src, c89atomic_memory_order order) @@ -10733,26 +15217,29 @@ static C89ATOMIC_INLINE void c89atomic_spinlock_unlock(volatile c89atomic_spinlo MA_API ma_uint64 ma_calculate_frame_count_after_resampling(ma_uint32 sampleRateOut, ma_uint32 sampleRateIn, ma_uint64 frameCountIn) { - /* For robustness we're going to use a resampler object to calculate this since that already has a way of calculating this. */ - ma_result result; - ma_uint64 frameCountOut; - ma_resampler_config config; - ma_resampler resampler; + /* This is based on the calculation in ma_linear_resampler_get_expected_output_frame_count(). */ + ma_uint64 outputFrameCount; + ma_uint64 preliminaryInputFrameCountFromFrac; + ma_uint64 preliminaryInputFrameCount; + + if (sampleRateIn == 0 || sampleRateOut == 0 || frameCountIn == 0) { + return 0; + } if (sampleRateOut == sampleRateIn) { return frameCountIn; } - config = ma_resampler_config_init(ma_format_s16, 1, sampleRateIn, sampleRateOut, ma_resample_algorithm_linear); - result = ma_resampler_init(&config, &resampler); - if (result != MA_SUCCESS) { - return 0; + outputFrameCount = (frameCountIn * sampleRateOut) / sampleRateIn; + + preliminaryInputFrameCountFromFrac = (outputFrameCount * (sampleRateIn / sampleRateOut)) / sampleRateOut; + preliminaryInputFrameCount = (outputFrameCount * (sampleRateIn % sampleRateOut)) + preliminaryInputFrameCountFromFrac; + + if (preliminaryInputFrameCount <= frameCountIn) { + outputFrameCount += 1; } - frameCountOut = ma_resampler_get_expected_output_frame_count(&resampler, frameCountIn); - - ma_resampler_uninit(&resampler); - return frameCountOut; + return outputFrameCount; } #ifndef MA_DATA_CONVERTER_STACK_BUFFER_SIZE @@ -10790,16 +15277,6 @@ static ma_result ma_result_from_GetLastError(DWORD error) Threading *******************************************************************************/ -#ifndef MA_NO_THREADING -#ifdef MA_WIN32 - #define MA_THREADCALL WINAPI - typedef unsigned long ma_thread_result; -#else - #define MA_THREADCALL - typedef void* ma_thread_result; -#endif -typedef ma_thread_result (MA_THREADCALL * ma_thread_entry_proc)(void* pData); - static MA_INLINE ma_result ma_spinlock_lock_ex(volatile ma_spinlock* pSpinlock, ma_bool32 yield) { if (pSpinlock == NULL) { @@ -10841,6 +15318,17 @@ MA_API ma_result ma_spinlock_unlock(volatile ma_spinlock* pSpinlock) return MA_SUCCESS; } + +#ifndef MA_NO_THREADING +#ifdef MA_WIN32 + #define MA_THREADCALL WINAPI + typedef unsigned long ma_thread_result; +#else + #define MA_THREADCALL + typedef void* ma_thread_result; +#endif +typedef ma_thread_result (MA_THREADCALL * ma_thread_entry_proc)(void* pData); + #ifdef MA_WIN32 static int ma_thread_priority_to_win32(ma_thread_priority priority) { @@ -11048,7 +15536,7 @@ static ma_result ma_thread_create__posix(ma_thread* pThread, ma_thread_priority (void)stackSize; #endif - result = pthread_create(pThread, pAttr, entryProc, pData); + result = pthread_create((pthread_t*)pThread, pAttr, entryProc, pData); /* The thread attributes object is no longer required. */ if (pAttr != NULL) { @@ -11064,8 +15552,7 @@ static ma_result ma_thread_create__posix(ma_thread* pThread, ma_thread_priority static void ma_thread_wait__posix(ma_thread* pThread) { - pthread_join(*pThread, NULL); - pthread_detach(*pThread); + pthread_join((pthread_t)*pThread, NULL); } @@ -11099,14 +15586,14 @@ static ma_result ma_event_init__posix(ma_event* pEvent) { int result; - result = pthread_mutex_init(&pEvent->lock, NULL); + result = pthread_mutex_init((pthread_mutex_t*)&pEvent->lock, NULL); if (result != 0) { return ma_result_from_errno(result); } - result = pthread_cond_init(&pEvent->cond, NULL); + result = pthread_cond_init((pthread_cond_t*)&pEvent->cond, NULL); if (result != 0) { - pthread_mutex_destroy(&pEvent->lock); + pthread_mutex_destroy((pthread_mutex_t*)&pEvent->lock); return ma_result_from_errno(result); } @@ -11116,32 +15603,32 @@ static ma_result ma_event_init__posix(ma_event* pEvent) static void ma_event_uninit__posix(ma_event* pEvent) { - pthread_cond_destroy(&pEvent->cond); - pthread_mutex_destroy(&pEvent->lock); + pthread_cond_destroy((pthread_cond_t*)&pEvent->cond); + pthread_mutex_destroy((pthread_mutex_t*)&pEvent->lock); } static ma_result ma_event_wait__posix(ma_event* pEvent) { - pthread_mutex_lock(&pEvent->lock); + pthread_mutex_lock((pthread_mutex_t*)&pEvent->lock); { while (pEvent->value == 0) { - pthread_cond_wait(&pEvent->cond, &pEvent->lock); + pthread_cond_wait((pthread_cond_t*)&pEvent->cond, (pthread_mutex_t*)&pEvent->lock); } pEvent->value = 0; /* Auto-reset. */ } - pthread_mutex_unlock(&pEvent->lock); + pthread_mutex_unlock((pthread_mutex_t*)&pEvent->lock); return MA_SUCCESS; } static ma_result ma_event_signal__posix(ma_event* pEvent) { - pthread_mutex_lock(&pEvent->lock); + pthread_mutex_lock((pthread_mutex_t*)&pEvent->lock); { pEvent->value = 1; - pthread_cond_signal(&pEvent->cond); + pthread_cond_signal((pthread_cond_t*)&pEvent->cond); } - pthread_mutex_unlock(&pEvent->lock); + pthread_mutex_unlock((pthread_mutex_t*)&pEvent->lock); return MA_SUCCESS; } @@ -11157,14 +15644,14 @@ static ma_result ma_semaphore_init__posix(int initialValue, ma_semaphore* pSemap pSemaphore->value = initialValue; - result = pthread_mutex_init(&pSemaphore->lock, NULL); + result = pthread_mutex_init((pthread_mutex_t*)&pSemaphore->lock, NULL); if (result != 0) { return ma_result_from_errno(result); /* Failed to create mutex. */ } - result = pthread_cond_init(&pSemaphore->cond, NULL); + result = pthread_cond_init((pthread_cond_t*)&pSemaphore->cond, NULL); if (result != 0) { - pthread_mutex_destroy(&pSemaphore->lock); + pthread_mutex_destroy((pthread_mutex_t*)&pSemaphore->lock); return ma_result_from_errno(result); /* Failed to create condition variable. */ } @@ -11177,8 +15664,8 @@ static void ma_semaphore_uninit__posix(ma_semaphore* pSemaphore) return; } - pthread_cond_destroy(&pSemaphore->cond); - pthread_mutex_destroy(&pSemaphore->lock); + pthread_cond_destroy((pthread_cond_t*)&pSemaphore->cond); + pthread_mutex_destroy((pthread_mutex_t*)&pSemaphore->lock); } static ma_result ma_semaphore_wait__posix(ma_semaphore* pSemaphore) @@ -11187,16 +15674,16 @@ static ma_result ma_semaphore_wait__posix(ma_semaphore* pSemaphore) return MA_INVALID_ARGS; } - pthread_mutex_lock(&pSemaphore->lock); + pthread_mutex_lock((pthread_mutex_t*)&pSemaphore->lock); { /* We need to wait on a condition variable before escaping. We can't return from this function until the semaphore has been signaled. */ while (pSemaphore->value == 0) { - pthread_cond_wait(&pSemaphore->cond, &pSemaphore->lock); + pthread_cond_wait((pthread_cond_t*)&pSemaphore->cond, (pthread_mutex_t*)&pSemaphore->lock); } pSemaphore->value -= 1; } - pthread_mutex_unlock(&pSemaphore->lock); + pthread_mutex_unlock((pthread_mutex_t*)&pSemaphore->lock); return MA_SUCCESS; } @@ -11207,12 +15694,12 @@ static ma_result ma_semaphore_release__posix(ma_semaphore* pSemaphore) return MA_INVALID_ARGS; } - pthread_mutex_lock(&pSemaphore->lock); + pthread_mutex_lock((pthread_mutex_t*)&pSemaphore->lock); { pSemaphore->value += 1; - pthread_cond_signal(&pSemaphore->cond); + pthread_cond_signal((pthread_cond_t*)&pSemaphore->cond); } - pthread_mutex_unlock(&pSemaphore->lock); + pthread_mutex_unlock((pthread_mutex_t*)&pSemaphore->lock); return MA_SUCCESS; } @@ -11257,7 +15744,7 @@ static ma_result ma_thread_create(ma_thread* pThread, ma_thread_priority priorit ma_thread_proxy_data* pProxyData; if (pThread == NULL || entryProc == NULL) { - return MA_FALSE; + return MA_INVALID_ARGS; } pProxyData = (ma_thread_proxy_data*)ma_malloc(sizeof(*pProxyData), pAllocationCallbacks); /* Will be freed by the proxy entry proc. */ @@ -11386,14 +15873,14 @@ static ma_result ma_event_alloc_and_init(ma_event** ppEvent, ma_allocation_callb *ppEvent = NULL; - pEvent = ma_malloc(sizeof(*pEvent), pAllocationCallbacks/*, MA_ALLOCATION_TYPE_EVENT*/); + pEvent = ma_malloc(sizeof(*pEvent), pAllocationCallbacks); if (pEvent == NULL) { return MA_OUT_OF_MEMORY; } result = ma_event_init(pEvent); if (result != MA_SUCCESS) { - ma_free(pEvent, pAllocationCallbacks/*, MA_ALLOCATION_TYPE_EVENT*/); + ma_free(pEvent, pAllocationCallbacks); return result; } @@ -11424,7 +15911,7 @@ static void ma_event_uninit_and_free(ma_event* pEvent, ma_allocation_callbacks* } ma_event_uninit(pEvent); - ma_free(pEvent, pAllocationCallbacks/*, MA_ALLOCATION_TYPE_EVENT*/); + ma_free(pEvent, pAllocationCallbacks); } #endif @@ -11527,6 +16014,1025 @@ MA_API ma_result ma_semaphore_release(ma_semaphore* pSemaphore) +#define MA_FENCE_COUNTER_MAX 0x7FFFFFFF + +MA_API ma_result ma_fence_init(ma_fence* pFence) +{ + if (pFence == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pFence); + pFence->counter = 0; + + #ifndef MA_NO_THREADING + { + ma_result result; + + result = ma_event_init(&pFence->e); + if (result != MA_SUCCESS) { + return result; + } + } + #endif + + return MA_SUCCESS; +} + +MA_API void ma_fence_uninit(ma_fence* pFence) +{ + if (pFence == NULL) { + return; + } + + #ifndef MA_NO_THREADING + { + ma_event_uninit(&pFence->e); + } + #endif + + MA_ZERO_OBJECT(pFence); +} + +MA_API ma_result ma_fence_acquire(ma_fence* pFence) +{ + if (pFence == NULL) { + return MA_INVALID_ARGS; + } + + for (;;) { + ma_uint32 oldCounter = c89atomic_load_32(&pFence->counter); + ma_uint32 newCounter = oldCounter + 1; + + /* Make sure we're not about to exceed our maximum value. */ + if (newCounter > MA_FENCE_COUNTER_MAX) { + MA_ASSERT(MA_FALSE); + return MA_OUT_OF_RANGE; + } + + if (c89atomic_compare_exchange_weak_32(&pFence->counter, &oldCounter, newCounter)) { + return MA_SUCCESS; + } else { + if (oldCounter == MA_FENCE_COUNTER_MAX) { + MA_ASSERT(MA_FALSE); + return MA_OUT_OF_RANGE; /* The other thread took the last available slot. Abort. */ + } + } + } + + /* Should never get here. */ + /*return MA_SUCCESS;*/ +} + +MA_API ma_result ma_fence_release(ma_fence* pFence) +{ + if (pFence == NULL) { + return MA_INVALID_ARGS; + } + + for (;;) { + ma_uint32 oldCounter = c89atomic_load_32(&pFence->counter); + ma_uint32 newCounter = oldCounter - 1; + + if (oldCounter == 0) { + MA_ASSERT(MA_FALSE); + return MA_INVALID_OPERATION; /* Acquire/release mismatch. */ + } + + if (c89atomic_compare_exchange_weak_32(&pFence->counter, &oldCounter, newCounter)) { + #ifndef MA_NO_THREADING + { + if (newCounter == 0) { + ma_event_signal(&pFence->e); /* <-- ma_fence_wait() will be waiting on this. */ + } + } + #endif + + return MA_SUCCESS; + } else { + if (oldCounter == 0) { + MA_ASSERT(MA_FALSE); + return MA_INVALID_OPERATION; /* Another thread has taken the 0 slot. Acquire/release mismatch. */ + } + } + } + + /* Should never get here. */ + /*return MA_SUCCESS;*/ +} + +MA_API ma_result ma_fence_wait(ma_fence* pFence) +{ + if (pFence == NULL) { + return MA_INVALID_ARGS; + } + + for (;;) { + ma_uint32 counter; + + counter = c89atomic_load_32(&pFence->counter); + if (counter == 0) { + /* + Counter has hit zero. By the time we get here some other thread may have acquired the + fence again, but that is where the caller needs to take care with how they se the fence. + */ + return MA_SUCCESS; + } + + /* Getting here means the counter is > 0. We'll need to wait for something to happen. */ + #ifndef MA_NO_THREADING + { + ma_result result; + + result = ma_event_wait(&pFence->e); + if (result != MA_SUCCESS) { + return result; + } + } + #endif + } + + /* Should never get here. */ + /*return MA_INVALID_OPERATION;*/ +} + + +MA_API ma_result ma_async_notification_signal(ma_async_notification* pNotification) +{ + ma_async_notification_callbacks* pNotificationCallbacks = (ma_async_notification_callbacks*)pNotification; + + if (pNotification == NULL) { + return MA_INVALID_ARGS; + } + + if (pNotificationCallbacks->onSignal == NULL) { + return MA_NOT_IMPLEMENTED; + } + + pNotificationCallbacks->onSignal(pNotification); + return MA_INVALID_ARGS; +} + + +static void ma_async_notification_poll__on_signal(ma_async_notification* pNotification) +{ + ((ma_async_notification_poll*)pNotification)->signalled = MA_TRUE; +} + +MA_API ma_result ma_async_notification_poll_init(ma_async_notification_poll* pNotificationPoll) +{ + if (pNotificationPoll == NULL) { + return MA_INVALID_ARGS; + } + + pNotificationPoll->cb.onSignal = ma_async_notification_poll__on_signal; + pNotificationPoll->signalled = MA_FALSE; + + return MA_SUCCESS; +} + +MA_API ma_bool32 ma_async_notification_poll_is_signalled(const ma_async_notification_poll* pNotificationPoll) +{ + if (pNotificationPoll == NULL) { + return MA_FALSE; + } + + return pNotificationPoll->signalled; +} + + +static void ma_async_notification_event__on_signal(ma_async_notification* pNotification) +{ + ma_async_notification_event_signal((ma_async_notification_event*)pNotification); +} + +MA_API ma_result ma_async_notification_event_init(ma_async_notification_event* pNotificationEvent) +{ + if (pNotificationEvent == NULL) { + return MA_INVALID_ARGS; + } + + pNotificationEvent->cb.onSignal = ma_async_notification_event__on_signal; + + #ifndef MA_NO_THREADING + { + ma_result result; + + result = ma_event_init(&pNotificationEvent->e); + if (result != MA_SUCCESS) { + return result; + } + + return MA_SUCCESS; + } + #else + { + return MA_NOT_IMPLEMENTED; /* Threading is disabled. */ + } + #endif +} + +MA_API ma_result ma_async_notification_event_uninit(ma_async_notification_event* pNotificationEvent) +{ + if (pNotificationEvent == NULL) { + return MA_INVALID_ARGS; + } + + #ifndef MA_NO_THREADING + { + ma_event_uninit(&pNotificationEvent->e); + return MA_SUCCESS; + } + #else + { + return MA_NOT_IMPLEMENTED; /* Threading is disabled. */ + } + #endif +} + +MA_API ma_result ma_async_notification_event_wait(ma_async_notification_event* pNotificationEvent) +{ + if (pNotificationEvent == NULL) { + return MA_INVALID_ARGS; + } + + #ifndef MA_NO_THREADING + { + return ma_event_wait(&pNotificationEvent->e); + } + #else + { + return MA_NOT_IMPLEMENTED; /* Threading is disabled. */ + } + #endif +} + +MA_API ma_result ma_async_notification_event_signal(ma_async_notification_event* pNotificationEvent) +{ + if (pNotificationEvent == NULL) { + return MA_INVALID_ARGS; + } + + #ifndef MA_NO_THREADING + { + return ma_event_signal(&pNotificationEvent->e); + } + #else + { + return MA_NOT_IMPLEMENTED; /* Threading is disabled. */ + } + #endif +} + + + +/************************************************************************************************************************************************************ + +Job Queue + +************************************************************************************************************************************************************/ +MA_API ma_slot_allocator_config ma_slot_allocator_config_init(ma_uint32 capacity) +{ + ma_slot_allocator_config config; + + MA_ZERO_OBJECT(&config); + config.capacity = capacity; + + return config; +} + + +static MA_INLINE ma_uint32 ma_slot_allocator_calculate_group_capacity(ma_uint32 slotCapacity) +{ + ma_uint32 cap = slotCapacity / 32; + if ((slotCapacity % 32) != 0) { + cap += 1; + } + + return cap; +} + +static MA_INLINE ma_uint32 ma_slot_allocator_group_capacity(const ma_slot_allocator* pAllocator) +{ + return ma_slot_allocator_calculate_group_capacity(pAllocator->capacity); +} + + +typedef struct +{ + size_t sizeInBytes; + size_t groupsOffset; + size_t slotsOffset; +} ma_slot_allocator_heap_layout; + +static ma_result ma_slot_allocator_get_heap_layout(const ma_slot_allocator_config* pConfig, ma_slot_allocator_heap_layout* pHeapLayout) +{ + MA_ASSERT(pHeapLayout != NULL); + + MA_ZERO_OBJECT(pHeapLayout); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->capacity == 0) { + return MA_INVALID_ARGS; + } + + pHeapLayout->sizeInBytes = 0; + + /* Groups. */ + pHeapLayout->groupsOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += ma_align_64(ma_slot_allocator_calculate_group_capacity(pConfig->capacity) * sizeof(ma_slot_allocator_group)); + + /* Slots. */ + pHeapLayout->slotsOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += ma_align_64(pConfig->capacity * sizeof(ma_uint32)); + + return MA_SUCCESS; +} + +MA_API ma_result ma_slot_allocator_get_heap_size(const ma_slot_allocator_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_result result; + ma_slot_allocator_heap_layout layout; + + if (pHeapSizeInBytes == NULL) { + return MA_INVALID_ARGS; + } + + *pHeapSizeInBytes = 0; + + result = ma_slot_allocator_get_heap_layout(pConfig, &layout); + if (result != MA_SUCCESS) { + return result; + } + + *pHeapSizeInBytes = layout.sizeInBytes; + + return result; +} + +MA_API ma_result ma_slot_allocator_init_preallocated(const ma_slot_allocator_config* pConfig, void* pHeap, ma_slot_allocator* pAllocator) +{ + ma_result result; + ma_slot_allocator_heap_layout heapLayout; + + if (pAllocator == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pAllocator); + + if (pHeap == NULL) { + return MA_INVALID_ARGS; + } + + result = ma_slot_allocator_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + pAllocator->_pHeap = pHeap; + MA_ZERO_MEMORY(pHeap, heapLayout.sizeInBytes); + + pAllocator->pGroups = (ma_slot_allocator_group*)ma_offset_ptr(pHeap, heapLayout.groupsOffset); + pAllocator->pSlots = (ma_uint32*)ma_offset_ptr(pHeap, heapLayout.slotsOffset); + pAllocator->capacity = pConfig->capacity; + + return MA_SUCCESS; +} + +MA_API ma_result ma_slot_allocator_init(const ma_slot_allocator_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_slot_allocator* pAllocator) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_slot_allocator_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; /* Failed to retrieve the size of the heap allocation. */ + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_slot_allocator_init_preallocated(pConfig, pHeap, pAllocator); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pAllocator->_ownsHeap = MA_TRUE; + return MA_SUCCESS; +} + +MA_API void ma_slot_allocator_uninit(ma_slot_allocator* pAllocator, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pAllocator == NULL) { + return; + } + + if (pAllocator->_ownsHeap) { + ma_free(pAllocator->_pHeap, pAllocationCallbacks); + } +} + +MA_API ma_result ma_slot_allocator_alloc(ma_slot_allocator* pAllocator, ma_uint64* pSlot) +{ + ma_uint32 iAttempt; + const ma_uint32 maxAttempts = 2; /* The number of iterations to perform until returning MA_OUT_OF_MEMORY if no slots can be found. */ + + if (pAllocator == NULL || pSlot == NULL) { + return MA_INVALID_ARGS; + } + + for (iAttempt = 0; iAttempt < maxAttempts; iAttempt += 1) { + /* We need to acquire a suitable bitfield first. This is a bitfield that's got an available slot within it. */ + ma_uint32 iGroup; + for (iGroup = 0; iGroup < ma_slot_allocator_group_capacity(pAllocator); iGroup += 1) { + /* CAS */ + for (;;) { + ma_uint32 oldBitfield; + ma_uint32 newBitfield; + ma_uint32 bitOffset; + + oldBitfield = c89atomic_load_32(&pAllocator->pGroups[iGroup].bitfield); /* <-- This copy must happen. The compiler must not optimize this away. */ + + /* Fast check to see if anything is available. */ + if (oldBitfield == 0xFFFFFFFF) { + break; /* No available bits in this bitfield. */ + } + + bitOffset = ma_ffs_32(~oldBitfield); + MA_ASSERT(bitOffset < 32); + + newBitfield = oldBitfield | (1 << bitOffset); + + if (c89atomic_compare_and_swap_32(&pAllocator->pGroups[iGroup].bitfield, oldBitfield, newBitfield) == oldBitfield) { + ma_uint32 slotIndex; + + /* Increment the counter as soon as possible to have other threads report out-of-memory sooner than later. */ + c89atomic_fetch_add_32(&pAllocator->count, 1); + + /* The slot index is required for constructing the output value. */ + slotIndex = (iGroup << 5) + bitOffset; /* iGroup << 5 = iGroup * 32 */ + if (slotIndex >= pAllocator->capacity) { + return MA_OUT_OF_MEMORY; + } + + /* Increment the reference count before constructing the output value. */ + pAllocator->pSlots[slotIndex] += 1; + + /* Construct the output value. */ + *pSlot = (((ma_uint64)pAllocator->pSlots[slotIndex] << 32) | slotIndex); + + return MA_SUCCESS; + } + } + } + + /* We weren't able to find a slot. If it's because we've reached our capacity we need to return MA_OUT_OF_MEMORY. Otherwise we need to do another iteration and try again. */ + if (pAllocator->count < pAllocator->capacity) { + ma_yield(); + } else { + return MA_OUT_OF_MEMORY; + } + } + + /* We couldn't find a slot within the maximum number of attempts. */ + return MA_OUT_OF_MEMORY; +} + +MA_API ma_result ma_slot_allocator_free(ma_slot_allocator* pAllocator, ma_uint64 slot) +{ + ma_uint32 iGroup; + ma_uint32 iBit; + + if (pAllocator == NULL) { + return MA_INVALID_ARGS; + } + + iGroup = (ma_uint32)((slot & 0xFFFFFFFF) >> 5); /* slot / 32 */ + iBit = (ma_uint32)((slot & 0xFFFFFFFF) & 31); /* slot % 32 */ + + if (iGroup >= ma_slot_allocator_group_capacity(pAllocator)) { + return MA_INVALID_ARGS; + } + + MA_ASSERT(iBit < 32); /* This must be true due to the logic we used to actually calculate it. */ + + while (c89atomic_load_32(&pAllocator->count) > 0) { + /* CAS */ + ma_uint32 oldBitfield; + ma_uint32 newBitfield; + + oldBitfield = c89atomic_load_32(&pAllocator->pGroups[iGroup].bitfield); /* <-- This copy must happen. The compiler must not optimize this away. */ + newBitfield = oldBitfield & ~(1 << iBit); + + /* Debugging for checking for double-frees. */ + #if defined(MA_DEBUG_OUTPUT) + { + if ((oldBitfield & (1 << iBit)) == 0) { + MA_ASSERT(MA_FALSE); /* Double free detected.*/ + } + } + #endif + + if (c89atomic_compare_and_swap_32(&pAllocator->pGroups[iGroup].bitfield, oldBitfield, newBitfield) == oldBitfield) { + c89atomic_fetch_sub_32(&pAllocator->count, 1); + return MA_SUCCESS; + } + } + + /* Getting here means there are no allocations available for freeing. */ + return MA_INVALID_OPERATION; +} + + +#define MA_JOB_ID_NONE ~((ma_uint64)0) +#define MA_JOB_SLOT_NONE (ma_uint16)(~0) + +static MA_INLINE ma_uint32 ma_job_extract_refcount(ma_uint64 toc) +{ + return (ma_uint32)(toc >> 32); +} + +static MA_INLINE ma_uint16 ma_job_extract_slot(ma_uint64 toc) +{ + return (ma_uint16)(toc & 0x0000FFFF); +} + +static MA_INLINE ma_uint16 ma_job_extract_code(ma_uint64 toc) +{ + return (ma_uint16)((toc & 0xFFFF0000) >> 16); +} + +static MA_INLINE ma_uint64 ma_job_toc_to_allocation(ma_uint64 toc) +{ + return ((ma_uint64)ma_job_extract_refcount(toc) << 32) | (ma_uint64)ma_job_extract_slot(toc); +} + +static MA_INLINE ma_uint64 ma_job_set_refcount(ma_uint64 toc, ma_uint32 refcount) +{ + /* Clear the reference count first. */ + toc = toc & ~((ma_uint64)0xFFFFFFFF << 32); + toc = toc | ((ma_uint64)refcount << 32); + + return toc; +} + + +MA_API ma_job ma_job_init(ma_uint16 code) +{ + ma_job job; + + MA_ZERO_OBJECT(&job); + job.toc.breakup.code = code; + job.toc.breakup.slot = MA_JOB_SLOT_NONE; /* Temp value. Will be allocated when posted to a queue. */ + job.next = MA_JOB_ID_NONE; + + return job; +} + + +static ma_result ma_job_process__noop(ma_job* pJob); +static ma_result ma_job_process__quit(ma_job* pJob); +static ma_result ma_job_process__custom(ma_job* pJob); +static ma_result ma_job_process__resource_manager__load_data_buffer_node(ma_job* pJob); +static ma_result ma_job_process__resource_manager__free_data_buffer_node(ma_job* pJob); +static ma_result ma_job_process__resource_manager__page_data_buffer_node(ma_job* pJob); +static ma_result ma_job_process__resource_manager__load_data_buffer(ma_job* pJob); +static ma_result ma_job_process__resource_manager__free_data_buffer(ma_job* pJob); +static ma_result ma_job_process__resource_manager__load_data_stream(ma_job* pJob); +static ma_result ma_job_process__resource_manager__free_data_stream(ma_job* pJob); +static ma_result ma_job_process__resource_manager__page_data_stream(ma_job* pJob); +static ma_result ma_job_process__resource_manager__seek_data_stream(ma_job* pJob); + +#if !defined(MA_NO_DEVICE_IO) +static ma_result ma_job_process__device__aaudio_reroute(ma_job* pJob); +#endif + +static ma_job_proc g_jobVTable[MA_JOB_TYPE_COUNT] = +{ + /* Miscellaneous. */ + ma_job_process__quit, /* MA_JOB_TYPE_QUIT */ + ma_job_process__custom, /* MA_JOB_TYPE_CUSTOM */ + + /* Resource Manager. */ + ma_job_process__resource_manager__load_data_buffer_node, /* MA_JOB_TYPE_RESOURCE_MANAGER_LOAD_DATA_BUFFER_NODE */ + ma_job_process__resource_manager__free_data_buffer_node, /* MA_JOB_TYPE_RESOURCE_MANAGER_FREE_DATA_BUFFER_NODE */ + ma_job_process__resource_manager__page_data_buffer_node, /* MA_JOB_TYPE_RESOURCE_MANAGER_PAGE_DATA_BUFFER_NODE */ + ma_job_process__resource_manager__load_data_buffer, /* MA_JOB_TYPE_RESOURCE_MANAGER_LOAD_DATA_BUFFER */ + ma_job_process__resource_manager__free_data_buffer, /* MA_JOB_TYPE_RESOURCE_MANAGER_FREE_DATA_BUFFER */ + ma_job_process__resource_manager__load_data_stream, /* MA_JOB_TYPE_RESOURCE_MANAGER_LOAD_DATA_STREAM */ + ma_job_process__resource_manager__free_data_stream, /* MA_JOB_TYPE_RESOURCE_MANAGER_FREE_DATA_STREAM */ + ma_job_process__resource_manager__page_data_stream, /* MA_JOB_TYPE_RESOURCE_MANAGER_PAGE_DATA_STREAM */ + ma_job_process__resource_manager__seek_data_stream, /* MA_JOB_TYPE_RESOURCE_MANAGER_SEEK_DATA_STREAM */ + + /* Device. */ +#if !defined(MA_NO_DEVICE_IO) + ma_job_process__device__aaudio_reroute /*MA_JOB_TYPE_DEVICE_AAUDIO_REROUTE*/ +#endif +}; + +MA_API ma_result ma_job_process(ma_job* pJob) +{ + if (pJob == NULL) { + return MA_INVALID_ARGS; + } + + if (pJob->toc.breakup.code > MA_JOB_TYPE_COUNT) { + return MA_INVALID_OPERATION; + } + + return g_jobVTable[pJob->toc.breakup.code](pJob); +} + +static ma_result ma_job_process__noop(ma_job* pJob) +{ + MA_ASSERT(pJob != NULL); + + /* No-op. */ + (void)pJob; + + return MA_SUCCESS; +} + +static ma_result ma_job_process__quit(ma_job* pJob) +{ + return ma_job_process__noop(pJob); +} + +static ma_result ma_job_process__custom(ma_job* pJob) +{ + MA_ASSERT(pJob != NULL); + + /* No-op if there's no callback. */ + if (pJob->data.custom.proc == NULL) { + return MA_SUCCESS; + } + + return pJob->data.custom.proc(pJob); +} + + + +MA_API ma_job_queue_config ma_job_queue_config_init(ma_uint32 flags, ma_uint32 capacity) +{ + ma_job_queue_config config; + + config.flags = flags; + config.capacity = capacity; + + return config; +} + + +typedef struct +{ + size_t sizeInBytes; + size_t allocatorOffset; + size_t jobsOffset; +} ma_job_queue_heap_layout; + +static ma_result ma_job_queue_get_heap_layout(const ma_job_queue_config* pConfig, ma_job_queue_heap_layout* pHeapLayout) +{ + ma_result result; + + MA_ASSERT(pHeapLayout != NULL); + + MA_ZERO_OBJECT(pHeapLayout); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->capacity == 0) { + return MA_INVALID_ARGS; + } + + pHeapLayout->sizeInBytes = 0; + + /* Allocator. */ + { + ma_slot_allocator_config allocatorConfig; + size_t allocatorHeapSizeInBytes; + + allocatorConfig = ma_slot_allocator_config_init(pConfig->capacity); + result = ma_slot_allocator_get_heap_size(&allocatorConfig, &allocatorHeapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + pHeapLayout->allocatorOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += allocatorHeapSizeInBytes; + } + + /* Jobs. */ + pHeapLayout->jobsOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += ma_align_64(pConfig->capacity * sizeof(ma_job)); + + return MA_SUCCESS; +} + +MA_API ma_result ma_job_queue_get_heap_size(const ma_job_queue_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_result result; + ma_job_queue_heap_layout layout; + + if (pHeapSizeInBytes == NULL) { + return MA_INVALID_ARGS; + } + + *pHeapSizeInBytes = 0; + + result = ma_job_queue_get_heap_layout(pConfig, &layout); + if (result != MA_SUCCESS) { + return result; + } + + *pHeapSizeInBytes = layout.sizeInBytes; + + return MA_SUCCESS; +} + +MA_API ma_result ma_job_queue_init_preallocated(const ma_job_queue_config* pConfig, void* pHeap, ma_job_queue* pQueue) +{ + ma_result result; + ma_job_queue_heap_layout heapLayout; + ma_slot_allocator_config allocatorConfig; + + if (pQueue == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pQueue); + + result = ma_job_queue_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + pQueue->_pHeap = pHeap; + MA_ZERO_MEMORY(pHeap, heapLayout.sizeInBytes); + + pQueue->flags = pConfig->flags; + pQueue->capacity = pConfig->capacity; + pQueue->pJobs = (ma_job*)ma_offset_ptr(pHeap, heapLayout.jobsOffset); + + allocatorConfig = ma_slot_allocator_config_init(pConfig->capacity); + result = ma_slot_allocator_init_preallocated(&allocatorConfig, ma_offset_ptr(pHeap, heapLayout.allocatorOffset), &pQueue->allocator); + if (result != MA_SUCCESS) { + return result; + } + + /* We need a semaphore if we're running in non-blocking mode. If threading is disabled we need to return an error. */ + if ((pQueue->flags & MA_JOB_QUEUE_FLAG_NON_BLOCKING) == 0) { + #ifndef MA_NO_THREADING + { + ma_semaphore_init(0, &pQueue->sem); + } + #else + { + /* Threading is disabled and we've requested non-blocking mode. */ + return MA_INVALID_OPERATION; + } + #endif + } + + /* + Our queue needs to be initialized with a free standing node. This should always be slot 0. Required for the lock free algorithm. The first job in the queue is + just a dummy item for giving us the first item in the list which is stored in the "next" member. + */ + ma_slot_allocator_alloc(&pQueue->allocator, &pQueue->head); /* Will never fail. */ + pQueue->pJobs[ma_job_extract_slot(pQueue->head)].next = MA_JOB_ID_NONE; + pQueue->tail = pQueue->head; + + return MA_SUCCESS; +} + +MA_API ma_result ma_job_queue_init(const ma_job_queue_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_job_queue* pQueue) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_job_queue_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_job_queue_init_preallocated(pConfig, pHeap, pQueue); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pQueue->_ownsHeap = MA_TRUE; + return MA_SUCCESS; +} + +MA_API void ma_job_queue_uninit(ma_job_queue* pQueue, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pQueue == NULL) { + return; + } + + /* All we need to do is uninitialize the semaphore. */ + if ((pQueue->flags & MA_JOB_QUEUE_FLAG_NON_BLOCKING) == 0) { + #ifndef MA_NO_THREADING + { + ma_semaphore_uninit(&pQueue->sem); + } + #else + { + MA_ASSERT(MA_FALSE); /* Should never get here. Should have been checked at initialization time. */ + } + #endif + } + + ma_slot_allocator_uninit(&pQueue->allocator, pAllocationCallbacks); + + if (pQueue->_ownsHeap) { + ma_free(pQueue->_pHeap, pAllocationCallbacks); + } +} + +static ma_bool32 ma_job_queue_cas(volatile ma_uint64* dst, ma_uint64 expected, ma_uint64 desired) +{ + /* The new counter is taken from the expected value. */ + return c89atomic_compare_and_swap_64(dst, expected, ma_job_set_refcount(desired, ma_job_extract_refcount(expected) + 1)) == expected; +} + +MA_API ma_result ma_job_queue_post(ma_job_queue* pQueue, const ma_job* pJob) +{ + /* + Lock free queue implementation based on the paper by Michael and Scott: Nonblocking Algorithms and Preemption-Safe Locking on Multiprogrammed Shared Memory Multiprocessors + */ + ma_result result; + ma_uint64 slot; + ma_uint64 tail; + ma_uint64 next; + + if (pQueue == NULL || pJob == NULL) { + return MA_INVALID_ARGS; + } + + /* We need a new slot. */ + result = ma_slot_allocator_alloc(&pQueue->allocator, &slot); + if (result != MA_SUCCESS) { + return result; /* Probably ran out of slots. If so, MA_OUT_OF_MEMORY will be returned. */ + } + + /* At this point we should have a slot to place the job. */ + MA_ASSERT(ma_job_extract_slot(slot) < pQueue->capacity); + + /* We need to put the job into memory before we do anything. */ + pQueue->pJobs[ma_job_extract_slot(slot)] = *pJob; + pQueue->pJobs[ma_job_extract_slot(slot)].toc.allocation = slot; /* This will overwrite the job code. */ + pQueue->pJobs[ma_job_extract_slot(slot)].toc.breakup.code = pJob->toc.breakup.code; /* The job code needs to be applied again because the line above overwrote it. */ + pQueue->pJobs[ma_job_extract_slot(slot)].next = MA_JOB_ID_NONE; /* Reset for safety. */ + + #ifndef MA_USE_EXPERIMENTAL_LOCK_FREE_JOB_QUEUE + ma_spinlock_lock(&pQueue->lock); + #endif + { + /* The job is stored in memory so now we need to add it to our linked list. We only ever add items to the end of the list. */ + for (;;) { + tail = c89atomic_load_64(&pQueue->tail); + next = c89atomic_load_64(&pQueue->pJobs[ma_job_extract_slot(tail)].next); + + if (ma_job_toc_to_allocation(tail) == ma_job_toc_to_allocation(c89atomic_load_64(&pQueue->tail))) { + if (ma_job_extract_slot(next) == 0xFFFF) { + if (ma_job_queue_cas(&pQueue->pJobs[ma_job_extract_slot(tail)].next, next, slot)) { + break; + } + } else { + ma_job_queue_cas(&pQueue->tail, tail, ma_job_extract_slot(next)); + } + } + } + ma_job_queue_cas(&pQueue->tail, tail, slot); + } + #ifndef MA_USE_EXPERIMENTAL_LOCK_FREE_JOB_QUEUE + ma_spinlock_unlock(&pQueue->lock); + #endif + + + /* Signal the semaphore as the last step if we're using synchronous mode. */ + if ((pQueue->flags & MA_JOB_QUEUE_FLAG_NON_BLOCKING) == 0) { + #ifndef MA_NO_THREADING + { + ma_semaphore_release(&pQueue->sem); + } + #else + { + MA_ASSERT(MA_FALSE); /* Should never get here. Should have been checked at initialization time. */ + } + #endif + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_job_queue_next(ma_job_queue* pQueue, ma_job* pJob) +{ + ma_uint64 head; + ma_uint64 tail; + ma_uint64 next; + + if (pQueue == NULL || pJob == NULL) { + return MA_INVALID_ARGS; + } + + /* If we're running in synchronous mode we'll need to wait on a semaphore. */ + if ((pQueue->flags & MA_JOB_QUEUE_FLAG_NON_BLOCKING) == 0) { + #ifndef MA_NO_THREADING + { + ma_semaphore_wait(&pQueue->sem); + } + #else + { + MA_ASSERT(MA_FALSE); /* Should never get here. Should have been checked at initialization time. */ + } + #endif + } + + #ifndef MA_USE_EXPERIMENTAL_LOCK_FREE_JOB_QUEUE + ma_spinlock_lock(&pQueue->lock); + #endif + { + /* + BUG: In lock-free mode, multiple threads can be in this section of code. The "head" variable in the loop below + is stored. One thread can fall through to the freeing of this item while another is still using "head" for the + retrieval of the "next" variable. + + The slot allocator might need to make use of some reference counting to ensure it's only truely freed when + there are no more references to the item. This must be fixed before removing these locks. + */ + + /* Now we need to remove the root item from the list. */ + for (;;) { + head = c89atomic_load_64(&pQueue->head); + tail = c89atomic_load_64(&pQueue->tail); + next = c89atomic_load_64(&pQueue->pJobs[ma_job_extract_slot(head)].next); + + if (ma_job_toc_to_allocation(head) == ma_job_toc_to_allocation(c89atomic_load_64(&pQueue->head))) { + if (ma_job_extract_slot(head) == ma_job_extract_slot(tail)) { + if (ma_job_extract_slot(next) == 0xFFFF) { + #ifndef MA_USE_EXPERIMENTAL_LOCK_FREE_JOB_QUEUE + ma_spinlock_unlock(&pQueue->lock); + #endif + return MA_NO_DATA_AVAILABLE; + } + ma_job_queue_cas(&pQueue->tail, tail, ma_job_extract_slot(next)); + } else { + *pJob = pQueue->pJobs[ma_job_extract_slot(next)]; + if (ma_job_queue_cas(&pQueue->head, head, ma_job_extract_slot(next))) { + break; + } + } + } + } + } + #ifndef MA_USE_EXPERIMENTAL_LOCK_FREE_JOB_QUEUE + ma_spinlock_unlock(&pQueue->lock); + #endif + + ma_slot_allocator_free(&pQueue->allocator, head); + + /* + If it's a quit job make sure it's put back on the queue to ensure other threads have an opportunity to detect it and terminate naturally. We + could instead just leave it on the queue, but that would involve fiddling with the lock-free code above and I want to keep that as simple as + possible. + */ + if (pJob->toc.breakup.code == MA_JOB_TYPE_QUIT) { + ma_job_queue_post(pQueue, pJob); + return MA_CANCELLED; /* Return a cancelled status just in case the thread is checking return codes and not properly checking for a quit job. */ + } + + return MA_SUCCESS; +} + + + + /************************************************************************************************************************************************************ ************************************************************************************************************************************************************* @@ -11940,58 +17446,19 @@ typedef int (WINAPI * MA_PFN_StringFromGUID2)(const GUID* const rguid, LPOLE typedef HWND (WINAPI * MA_PFN_GetForegroundWindow)(void); typedef HWND (WINAPI * MA_PFN_GetDesktopWindow)(void); +#if defined(MA_WIN32_DESKTOP) /* Microsoft documents these APIs as returning LSTATUS, but the Win32 API shipping with some compilers do not define it. It's just a LONG. */ typedef LONG (WINAPI * MA_PFN_RegOpenKeyExA)(HKEY hKey, LPCSTR lpSubKey, DWORD ulOptions, REGSAM samDesired, PHKEY phkResult); typedef LONG (WINAPI * MA_PFN_RegCloseKey)(HKEY hKey); typedef LONG (WINAPI * MA_PFN_RegQueryValueExA)(HKEY hKey, LPCSTR lpValueName, LPDWORD lpReserved, LPDWORD lpType, LPBYTE lpData, LPDWORD lpcbData); -#endif +#endif /* MA_WIN32_DESKTOP */ +#endif /* MA_WIN32 */ #define MA_DEFAULT_PLAYBACK_DEVICE_NAME "Default Playback Device" #define MA_DEFAULT_CAPTURE_DEVICE_NAME "Default Capture Device" -/* Posts a log message. */ -static void ma_post_log_message(ma_context* pContext, ma_device* pDevice, ma_uint32 logLevel, const char* message) -{ - if (pContext == NULL) { - if (pDevice != NULL) { - pContext = pDevice->pContext; - } - } - - if (pContext == NULL) { - return; - } - - ma_log_post(ma_context_get_log(pContext), logLevel, message); /* <-- This will deal with MA_DEBUG_OUTPUT. */ - - /* Legacy. */ -#if defined(MA_LOG_LEVEL) - if (logLevel <= MA_LOG_LEVEL) { - ma_log_proc onLog; - - onLog = pContext->logCallback; - if (onLog) { - onLog(pContext, pDevice, logLevel, message); - } - } -#endif -} - -/* Posts an log message. Throw a breakpoint in here if you're needing to debug. The return value is always "resultCode". */ -static ma_result ma_context_post_error(ma_context* pContext, ma_device* pDevice, ma_uint32 logLevel, const char* message, ma_result resultCode) -{ - ma_post_log_message(pContext, pDevice, logLevel, message); - return resultCode; -} - -static ma_result ma_post_error(ma_device* pDevice, ma_uint32 logLevel, const char* message, ma_result resultCode) -{ - return ma_context_post_error(ma_device_get_context(pDevice), pDevice, logLevel, message, resultCode); -} - - /******************************************************************************* @@ -12001,7 +17468,7 @@ Timing *******************************************************************************/ #ifdef MA_WIN32 static LARGE_INTEGER g_ma_TimerFrequency; /* <-- Initialized to zero since it's static. */ - static void ma_timer_init(ma_timer* pTimer) + void ma_timer_init(ma_timer* pTimer) { LARGE_INTEGER counter; @@ -12013,7 +17480,7 @@ Timing pTimer->counter = counter.QuadPart; } - static double ma_timer_get_time_in_seconds(ma_timer* pTimer) + double ma_timer_get_time_in_seconds(ma_timer* pTimer) { LARGE_INTEGER counter; if (!QueryPerformanceCounter(&counter)) { @@ -12051,7 +17518,7 @@ Timing return (emscripten_get_now() - pTimer->counterD) / 1000; /* Emscripten is in milliseconds. */ } #else - #if _POSIX_C_SOURCE >= 199309L + #if defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 199309L #if defined(CLOCK_MONOTONIC) #define MA_CLOCK_ID CLOCK_MONOTONIC #else @@ -12215,51 +17682,234 @@ static ma_uint32 ma_get_closest_standard_sample_rate(ma_uint32 sampleRateIn) #endif +static MA_INLINE unsigned int ma_device_disable_denormals(ma_device* pDevice) +{ + MA_ASSERT(pDevice != NULL); + + if (!pDevice->noDisableDenormals) { + return ma_disable_denormals(); + } else { + return 0; + } +} + +static MA_INLINE void ma_device_restore_denormals(ma_device* pDevice, unsigned int prevState) +{ + MA_ASSERT(pDevice != NULL); + + if (!pDevice->noDisableDenormals) { + ma_restore_denormals(prevState); + } else { + /* Do nothing. */ + (void)prevState; + } +} + +static ma_device_notification ma_device_notification_init(ma_device* pDevice, ma_device_notification_type type) +{ + ma_device_notification notification; + + MA_ZERO_OBJECT(¬ification); + notification.pDevice = pDevice; + notification.type = type; + + return notification; +} + +static void ma_device__on_notification(ma_device_notification notification) +{ + MA_ASSERT(notification.pDevice != NULL); + + if (notification.pDevice->onNotification != NULL) { + notification.pDevice->onNotification(¬ification); + } + + /* TEMP FOR COMPATIBILITY: If it's a stopped notification, fire the onStop callback as well. This is only for backwards compatibility and will be removed. */ + if (notification.pDevice->onStop != NULL && notification.type == ma_device_notification_type_stopped) { + notification.pDevice->onStop(notification.pDevice); + } +} + +void ma_device__on_notification_started(ma_device* pDevice) +{ + ma_device__on_notification(ma_device_notification_init(pDevice, ma_device_notification_type_started)); +} + +void ma_device__on_notification_stopped(ma_device* pDevice) +{ + ma_device__on_notification(ma_device_notification_init(pDevice, ma_device_notification_type_stopped)); +} + +void ma_device__on_notification_rerouted(ma_device* pDevice) +{ + ma_device__on_notification(ma_device_notification_init(pDevice, ma_device_notification_type_rerouted)); +} + +void ma_device__on_notification_interruption_began(ma_device* pDevice) +{ + ma_device__on_notification(ma_device_notification_init(pDevice, ma_device_notification_type_interruption_began)); +} + +void ma_device__on_notification_interruption_ended(ma_device* pDevice) +{ + ma_device__on_notification(ma_device_notification_init(pDevice, ma_device_notification_type_interruption_ended)); +} + + +static void ma_device__on_data_inner(ma_device* pDevice, void* pFramesOut, const void* pFramesIn, ma_uint32 frameCount) +{ + MA_ASSERT(pDevice != NULL); + MA_ASSERT(pDevice->onData != NULL); + + if (!pDevice->noPreSilencedOutputBuffer && pFramesOut != NULL) { + ma_silence_pcm_frames(pFramesOut, frameCount, pDevice->playback.format, pDevice->playback.channels); + } + + pDevice->onData(pDevice, pFramesOut, pFramesIn, frameCount); +} + static void ma_device__on_data(ma_device* pDevice, void* pFramesOut, const void* pFramesIn, ma_uint32 frameCount) +{ + MA_ASSERT(pDevice != NULL); + + if (pDevice->noFixedSizedCallback) { + /* Fast path. Not using a fixed sized callback. Process directly from the specified buffers. */ + ma_device__on_data_inner(pDevice, pFramesOut, pFramesIn, frameCount); + } else { + /* Slow path. Using a fixed sized callback. Need to use the intermediary buffer. */ + ma_uint32 totalFramesProcessed = 0; + + while (totalFramesProcessed < frameCount) { + ma_uint32 totalFramesRemaining = frameCount - totalFramesProcessed; + ma_uint32 framesToProcessThisIteration = 0; + + if (pFramesIn != NULL) { + /* Capturing. Write to the intermediary buffer. If there's no room, fire the callback to empty it. */ + if (pDevice->capture.intermediaryBufferLen < pDevice->capture.intermediaryBufferCap) { + /* There's some room left in the intermediary buffer. Write to it without firing the callback. */ + framesToProcessThisIteration = totalFramesRemaining; + if (framesToProcessThisIteration > pDevice->capture.intermediaryBufferCap - pDevice->capture.intermediaryBufferLen) { + framesToProcessThisIteration = pDevice->capture.intermediaryBufferCap - pDevice->capture.intermediaryBufferLen; + } + + ma_copy_pcm_frames( + ma_offset_pcm_frames_ptr(pDevice->capture.pIntermediaryBuffer, pDevice->capture.intermediaryBufferLen, pDevice->capture.format, pDevice->capture.channels), + ma_offset_pcm_frames_const_ptr(pFramesIn, totalFramesProcessed, pDevice->capture.format, pDevice->capture.channels), + framesToProcessThisIteration, + pDevice->capture.format, pDevice->capture.channels); + + pDevice->capture.intermediaryBufferLen += framesToProcessThisIteration; + } + + if (pDevice->capture.intermediaryBufferLen == pDevice->capture.intermediaryBufferCap) { + /* No room left in the intermediary buffer. Fire the data callback. */ + if (pDevice->type == ma_device_type_duplex) { + /* We'll do the duplex data callback later after we've processed the playback data. */ + } else { + ma_device__on_data_inner(pDevice, NULL, pDevice->capture.pIntermediaryBuffer, pDevice->capture.intermediaryBufferCap); + + /* The intermediary buffer has just been drained. */ + pDevice->capture.intermediaryBufferLen = 0; + } + } + } + + if (pFramesOut != NULL) { + /* Playing back. Read from the intermediary buffer. If there's nothing in it, fire the callback to fill it. */ + if (pDevice->playback.intermediaryBufferLen > 0) { + /* There's some content in the intermediary buffer. Read from that without firing the callback. */ + if (pDevice->type == ma_device_type_duplex) { + /* The frames processed this iteration for a duplex device will always be based on the capture side. Leave it unmodified. */ + } else { + framesToProcessThisIteration = totalFramesRemaining; + if (framesToProcessThisIteration > pDevice->playback.intermediaryBufferLen) { + framesToProcessThisIteration = pDevice->playback.intermediaryBufferLen; + } + } + + ma_copy_pcm_frames( + ma_offset_pcm_frames_ptr(pFramesOut, totalFramesProcessed, pDevice->playback.format, pDevice->playback.channels), + ma_offset_pcm_frames_ptr(pDevice->playback.pIntermediaryBuffer, pDevice->playback.intermediaryBufferCap - pDevice->playback.intermediaryBufferLen, pDevice->playback.format, pDevice->playback.channels), + framesToProcessThisIteration, + pDevice->playback.format, pDevice->playback.channels); + + pDevice->playback.intermediaryBufferLen -= framesToProcessThisIteration; + } + + if (pDevice->playback.intermediaryBufferLen == 0) { + /* There's nothing in the intermediary buffer. Fire the data callback to fill it. */ + if (pDevice->type == ma_device_type_duplex) { + /* In duplex mode, the data callback will be fired later. Nothing to do here. */ + } else { + ma_device__on_data_inner(pDevice, pDevice->playback.pIntermediaryBuffer, NULL, pDevice->playback.intermediaryBufferCap); + + /* The intermediary buffer has just been filled. */ + pDevice->playback.intermediaryBufferLen = pDevice->playback.intermediaryBufferCap; + } + } + } + + /* If we're in duplex mode we might need to do a refill of the data. */ + if (pDevice->type == ma_device_type_duplex) { + if (pDevice->capture.intermediaryBufferLen == pDevice->capture.intermediaryBufferCap) { + ma_device__on_data_inner(pDevice, pDevice->playback.pIntermediaryBuffer, pDevice->capture.pIntermediaryBuffer, pDevice->capture.intermediaryBufferCap); + + pDevice->playback.intermediaryBufferLen = pDevice->playback.intermediaryBufferCap; /* The playback buffer will have just been filled. */ + pDevice->capture.intermediaryBufferLen = 0; /* The intermediary buffer has just been drained. */ + } + } + + /* Make sure this is only incremented once in the duplex case. */ + totalFramesProcessed += framesToProcessThisIteration; + } + } +} + +static void ma_device__handle_data_callback(ma_device* pDevice, void* pFramesOut, const void* pFramesIn, ma_uint32 frameCount) { float masterVolumeFactor; ma_device_get_master_volume(pDevice, &masterVolumeFactor); /* Use ma_device_get_master_volume() to ensure the volume is loaded atomically. */ if (pDevice->onData) { - if (!pDevice->noPreZeroedOutputBuffer && pFramesOut != NULL) { - ma_silence_pcm_frames(pFramesOut, frameCount, pDevice->playback.format, pDevice->playback.channels); - } + unsigned int prevDenormalState = ma_device_disable_denormals(pDevice); + { + /* Volume control of input makes things a bit awkward because the input buffer is read-only. We'll need to use a temp buffer and loop in this case. */ + if (pFramesIn != NULL && masterVolumeFactor < 1) { + ma_uint8 tempFramesIn[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; + ma_uint32 bpfCapture = ma_get_bytes_per_frame(pDevice->capture.format, pDevice->capture.channels); + ma_uint32 bpfPlayback = ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels); + ma_uint32 totalFramesProcessed = 0; + while (totalFramesProcessed < frameCount) { + ma_uint32 framesToProcessThisIteration = frameCount - totalFramesProcessed; + if (framesToProcessThisIteration > sizeof(tempFramesIn)/bpfCapture) { + framesToProcessThisIteration = sizeof(tempFramesIn)/bpfCapture; + } - /* Volume control of input makes things a bit awkward because the input buffer is read-only. We'll need to use a temp buffer and loop in this case. */ - if (pFramesIn != NULL && masterVolumeFactor < 1) { - ma_uint8 tempFramesIn[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint32 bpfCapture = ma_get_bytes_per_frame(pDevice->capture.format, pDevice->capture.channels); - ma_uint32 bpfPlayback = ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels); - ma_uint32 totalFramesProcessed = 0; - while (totalFramesProcessed < frameCount) { - ma_uint32 framesToProcessThisIteration = frameCount - totalFramesProcessed; - if (framesToProcessThisIteration > sizeof(tempFramesIn)/bpfCapture) { - framesToProcessThisIteration = sizeof(tempFramesIn)/bpfCapture; + ma_copy_and_apply_volume_factor_pcm_frames(tempFramesIn, ma_offset_ptr(pFramesIn, totalFramesProcessed*bpfCapture), framesToProcessThisIteration, pDevice->capture.format, pDevice->capture.channels, masterVolumeFactor); + + ma_device__on_data(pDevice, ma_offset_ptr(pFramesOut, totalFramesProcessed*bpfPlayback), tempFramesIn, framesToProcessThisIteration); + + totalFramesProcessed += framesToProcessThisIteration; + } + } else { + ma_device__on_data(pDevice, pFramesOut, pFramesIn, frameCount); + } + + /* Volume control and clipping for playback devices. */ + if (pFramesOut != NULL) { + if (masterVolumeFactor < 1) { + if (pFramesIn == NULL) { /* <-- In full-duplex situations, the volume will have been applied to the input samples before the data callback. Applying it again post-callback will incorrectly compound it. */ + ma_apply_volume_factor_pcm_frames(pFramesOut, frameCount, pDevice->playback.format, pDevice->playback.channels, masterVolumeFactor); + } } - ma_copy_and_apply_volume_factor_pcm_frames(tempFramesIn, ma_offset_ptr(pFramesIn, totalFramesProcessed*bpfCapture), framesToProcessThisIteration, pDevice->capture.format, pDevice->capture.channels, masterVolumeFactor); - - pDevice->onData(pDevice, ma_offset_ptr(pFramesOut, totalFramesProcessed*bpfPlayback), tempFramesIn, framesToProcessThisIteration); - - totalFramesProcessed += framesToProcessThisIteration; - } - } else { - pDevice->onData(pDevice, pFramesOut, pFramesIn, frameCount); - } - - /* Volume control and clipping for playback devices. */ - if (pFramesOut != NULL) { - if (masterVolumeFactor < 1) { - if (pFramesIn == NULL) { /* <-- In full-duplex situations, the volume will have been applied to the input samples before the data callback. Applying it again post-callback will incorrectly compound it. */ - ma_apply_volume_factor_pcm_frames(pFramesOut, frameCount, pDevice->playback.format, pDevice->playback.channels, masterVolumeFactor); + if (!pDevice->noClip && pDevice->playback.format == ma_format_f32) { + ma_clip_samples_f32((float*)pFramesOut, (const float*)pFramesOut, frameCount * pDevice->playback.channels); /* Intentionally specifying the same pointer for both input and output for in-place processing. */ } } - - if (!pDevice->noClip && pDevice->playback.format == ma_format_f32) { - ma_clip_pcm_frames_f32((float*)pFramesOut, frameCount, pDevice->playback.channels); - } } + ma_device_restore_denormals(pDevice, prevDenormalState); } } @@ -12273,58 +17923,99 @@ static void ma_device__read_frames_from_client(ma_device* pDevice, ma_uint32 fra MA_ASSERT(pFramesOut != NULL); if (pDevice->playback.converter.isPassthrough) { - ma_device__on_data(pDevice, pFramesOut, NULL, frameCount); + ma_device__handle_data_callback(pDevice, pFramesOut, NULL, frameCount); } else { ma_result result; ma_uint64 totalFramesReadOut; - ma_uint64 totalFramesReadIn; void* pRunningFramesOut; totalFramesReadOut = 0; - totalFramesReadIn = 0; pRunningFramesOut = pFramesOut; - while (totalFramesReadOut < frameCount) { - ma_uint8 pIntermediaryBuffer[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; /* In client format. */ - ma_uint64 intermediaryBufferCap = sizeof(pIntermediaryBuffer) / ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels); - ma_uint64 framesToReadThisIterationIn; - ma_uint64 framesReadThisIterationIn; - ma_uint64 framesToReadThisIterationOut; - ma_uint64 framesReadThisIterationOut; - ma_uint64 requiredInputFrameCount; + /* + We run slightly different logic depending on whether or not we're using a heap-allocated + buffer for caching input data. This will be the case if the data converter does not have + the ability to retrieve the required input frame count for a given output frame count. + */ + if (pDevice->playback.pInputCache != NULL) { + while (totalFramesReadOut < frameCount) { + ma_uint64 framesToReadThisIterationIn; + ma_uint64 framesToReadThisIterationOut; - framesToReadThisIterationOut = (frameCount - totalFramesReadOut); - framesToReadThisIterationIn = framesToReadThisIterationOut; - if (framesToReadThisIterationIn > intermediaryBufferCap) { - framesToReadThisIterationIn = intermediaryBufferCap; + /* If there's any data available in the cache, that needs to get processed first. */ + if (pDevice->playback.inputCacheRemaining > 0) { + framesToReadThisIterationOut = (frameCount - totalFramesReadOut); + framesToReadThisIterationIn = framesToReadThisIterationOut; + if (framesToReadThisIterationIn > pDevice->playback.inputCacheRemaining) { + framesToReadThisIterationIn = pDevice->playback.inputCacheRemaining; + } + + result = ma_data_converter_process_pcm_frames(&pDevice->playback.converter, ma_offset_pcm_frames_ptr(pDevice->playback.pInputCache, pDevice->playback.inputCacheConsumed, pDevice->playback.format, pDevice->playback.channels), &framesToReadThisIterationIn, pRunningFramesOut, &framesToReadThisIterationOut); + if (result != MA_SUCCESS) { + break; + } + + pDevice->playback.inputCacheConsumed += framesToReadThisIterationIn; + pDevice->playback.inputCacheRemaining -= framesToReadThisIterationIn; + + totalFramesReadOut += framesToReadThisIterationOut; + pRunningFramesOut = ma_offset_ptr(pRunningFramesOut, framesToReadThisIterationOut * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels)); + + if (framesToReadThisIterationIn == 0 && framesToReadThisIterationOut == 0) { + break; /* We're done. */ + } + } + + /* Getting here means there's no data in the cache and we need to fill it up with data from the client. */ + if (pDevice->playback.inputCacheRemaining == 0) { + ma_device__handle_data_callback(pDevice, pDevice->playback.pInputCache, NULL, (ma_uint32)pDevice->playback.inputCacheCap); + + pDevice->playback.inputCacheConsumed = 0; + pDevice->playback.inputCacheRemaining = pDevice->playback.inputCacheCap; + } } + } else { + while (totalFramesReadOut < frameCount) { + ma_uint8 pIntermediaryBuffer[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; /* In client format. */ + ma_uint64 intermediaryBufferCap = sizeof(pIntermediaryBuffer) / ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels); + ma_uint64 framesToReadThisIterationIn; + ma_uint64 framesReadThisIterationIn; + ma_uint64 framesToReadThisIterationOut; + ma_uint64 framesReadThisIterationOut; + ma_uint64 requiredInputFrameCount; - requiredInputFrameCount = ma_data_converter_get_required_input_frame_count(&pDevice->playback.converter, framesToReadThisIterationOut); - if (framesToReadThisIterationIn > requiredInputFrameCount) { - framesToReadThisIterationIn = requiredInputFrameCount; - } + framesToReadThisIterationOut = (frameCount - totalFramesReadOut); + framesToReadThisIterationIn = framesToReadThisIterationOut; + if (framesToReadThisIterationIn > intermediaryBufferCap) { + framesToReadThisIterationIn = intermediaryBufferCap; + } - if (framesToReadThisIterationIn > 0) { - ma_device__on_data(pDevice, pIntermediaryBuffer, NULL, (ma_uint32)framesToReadThisIterationIn); - totalFramesReadIn += framesToReadThisIterationIn; - } + ma_data_converter_get_required_input_frame_count(&pDevice->playback.converter, framesToReadThisIterationOut, &requiredInputFrameCount); + if (framesToReadThisIterationIn > requiredInputFrameCount) { + framesToReadThisIterationIn = requiredInputFrameCount; + } - /* - At this point we have our decoded data in input format and now we need to convert to output format. Note that even if we didn't read any - input frames, we still want to try processing frames because there may some output frames generated from cached input data. - */ - framesReadThisIterationIn = framesToReadThisIterationIn; - framesReadThisIterationOut = framesToReadThisIterationOut; - result = ma_data_converter_process_pcm_frames(&pDevice->playback.converter, pIntermediaryBuffer, &framesReadThisIterationIn, pRunningFramesOut, &framesReadThisIterationOut); - if (result != MA_SUCCESS) { - break; - } + if (framesToReadThisIterationIn > 0) { + ma_device__handle_data_callback(pDevice, pIntermediaryBuffer, NULL, (ma_uint32)framesToReadThisIterationIn); + } - totalFramesReadOut += framesReadThisIterationOut; - pRunningFramesOut = ma_offset_ptr(pRunningFramesOut, framesReadThisIterationOut * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels)); + /* + At this point we have our decoded data in input format and now we need to convert to output format. Note that even if we didn't read any + input frames, we still want to try processing frames because there may some output frames generated from cached input data. + */ + framesReadThisIterationIn = framesToReadThisIterationIn; + framesReadThisIterationOut = framesToReadThisIterationOut; + result = ma_data_converter_process_pcm_frames(&pDevice->playback.converter, pIntermediaryBuffer, &framesReadThisIterationIn, pRunningFramesOut, &framesReadThisIterationOut); + if (result != MA_SUCCESS) { + break; + } - if (framesReadThisIterationIn == 0 && framesReadThisIterationOut == 0) { - break; /* We're done. */ + totalFramesReadOut += framesReadThisIterationOut; + pRunningFramesOut = ma_offset_ptr(pRunningFramesOut, framesReadThisIterationOut * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels)); + + if (framesReadThisIterationIn == 0 && framesReadThisIterationOut == 0) { + break; /* We're done. */ + } } } } @@ -12338,7 +18029,7 @@ static void ma_device__send_frames_to_client(ma_device* pDevice, ma_uint32 frame MA_ASSERT(pFramesInDeviceFormat != NULL); if (pDevice->capture.converter.isPassthrough) { - ma_device__on_data(pDevice, NULL, pFramesInDeviceFormat, frameCountInDeviceFormat); + ma_device__handle_data_callback(pDevice, NULL, pFramesInDeviceFormat, frameCountInDeviceFormat); } else { ma_result result; ma_uint8 pFramesInClientFormat[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; @@ -12361,7 +18052,7 @@ static void ma_device__send_frames_to_client(ma_device* pDevice, ma_uint32 frame } if (clientFramesProcessedThisIteration > 0) { - ma_device__on_data(pDevice, NULL, pFramesInClientFormat, (ma_uint32)clientFramesProcessedThisIteration); /* Safe cast. */ + ma_device__handle_data_callback(pDevice, NULL, pFramesInClientFormat, (ma_uint32)clientFramesProcessedThisIteration); /* Safe cast. */ } pRunningFramesInDeviceFormat = ma_offset_ptr(pRunningFramesInDeviceFormat, deviceFramesProcessedThisIteration * ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels)); @@ -12396,7 +18087,7 @@ static ma_result ma_device__handle_duplex_callback_capture(ma_device* pDevice, m result = ma_pcm_rb_acquire_write(pRB, &framesToProcessInClientFormat, &pFramesInClientFormat); if (result != MA_SUCCESS) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "Failed to acquire capture PCM frames from ring buffer.", result); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "Failed to acquire capture PCM frames from ring buffer."); break; } @@ -12414,9 +18105,9 @@ static ma_result ma_device__handle_duplex_callback_capture(ma_device* pDevice, m break; } - result = ma_pcm_rb_commit_write(pRB, (ma_uint32)framesProcessedInClientFormat, pFramesInClientFormat); /* Safe cast. */ + result = ma_pcm_rb_commit_write(pRB, (ma_uint32)framesProcessedInClientFormat); /* Safe cast. */ if (result != MA_SUCCESS) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "Failed to commit capture PCM frames to ring buffer.", result); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "Failed to commit capture PCM frames to ring buffer."); break; } @@ -12435,16 +18126,14 @@ static ma_result ma_device__handle_duplex_callback_capture(ma_device* pDevice, m static ma_result ma_device__handle_duplex_callback_playback(ma_device* pDevice, ma_uint32 frameCount, void* pFramesInInternalFormat, ma_pcm_rb* pRB) { ma_result result; - ma_uint8 playbackFramesInExternalFormat[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; ma_uint8 silentInputFrames[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint32 totalFramesToReadFromClient; - ma_uint32 totalFramesReadFromClient; ma_uint32 totalFramesReadOut = 0; MA_ASSERT(pDevice != NULL); MA_ASSERT(frameCount > 0); MA_ASSERT(pFramesInInternalFormat != NULL); MA_ASSERT(pRB != NULL); + MA_ASSERT(pDevice->playback.pInputCache != NULL); /* Sitting in the ring buffer should be captured data from the capture callback in external format. If there's not enough data in there for @@ -12452,68 +18141,61 @@ static ma_result ma_device__handle_duplex_callback_playback(ma_device* pDevice, */ MA_ZERO_MEMORY(silentInputFrames, sizeof(silentInputFrames)); - /* We need to calculate how many output frames are required to be read from the client to completely fill frameCount internal frames. */ - totalFramesToReadFromClient = (ma_uint32)ma_data_converter_get_required_input_frame_count(&pDevice->playback.converter, frameCount); - totalFramesReadFromClient = 0; - while (totalFramesReadFromClient < totalFramesToReadFromClient && ma_device_is_started(pDevice)) { - ma_uint32 framesRemainingFromClient; - ma_uint32 framesToProcessFromClient; - ma_uint32 inputFrameCount; - void* pInputFrames; - - framesRemainingFromClient = (totalFramesToReadFromClient - totalFramesReadFromClient); - framesToProcessFromClient = sizeof(playbackFramesInExternalFormat) / ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels); - if (framesToProcessFromClient > framesRemainingFromClient) { - framesToProcessFromClient = framesRemainingFromClient; - } - - /* We need to grab captured samples before firing the callback. If there's not enough input samples we just pass silence. */ - inputFrameCount = framesToProcessFromClient; - result = ma_pcm_rb_acquire_read(pRB, &inputFrameCount, &pInputFrames); - if (result == MA_SUCCESS) { - if (inputFrameCount > 0) { - /* Use actual input frames. */ - ma_device__on_data(pDevice, playbackFramesInExternalFormat, pInputFrames, inputFrameCount); - } else { - if (ma_pcm_rb_pointer_distance(pRB) == 0) { - break; /* Underrun. */ - } - } - - /* We're done with the captured samples. */ - result = ma_pcm_rb_commit_read(pRB, inputFrameCount, pInputFrames); - if (result != MA_SUCCESS) { - break; /* Don't know what to do here... Just abandon ship. */ - } - } else { - /* Use silent input frames. */ - inputFrameCount = ma_min( - sizeof(playbackFramesInExternalFormat) / ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels), - sizeof(silentInputFrames) / ma_get_bytes_per_frame(pDevice->capture.format, pDevice->capture.channels) - ); - - ma_device__on_data(pDevice, playbackFramesInExternalFormat, silentInputFrames, inputFrameCount); - } - - /* We have samples in external format so now we need to convert to internal format and output to the device. */ - { - ma_uint64 framesConvertedIn = inputFrameCount; + while (totalFramesReadOut < frameCount && ma_device_is_started(pDevice)) { + /* + We should have a buffer allocated on the heap. Any playback frames still sitting in there + need to be sent to the internal device before we process any more data from the client. + */ + if (pDevice->playback.inputCacheRemaining > 0) { + ma_uint64 framesConvertedIn = pDevice->playback.inputCacheRemaining; ma_uint64 framesConvertedOut = (frameCount - totalFramesReadOut); - ma_data_converter_process_pcm_frames(&pDevice->playback.converter, playbackFramesInExternalFormat, &framesConvertedIn, pFramesInInternalFormat, &framesConvertedOut); + ma_data_converter_process_pcm_frames(&pDevice->playback.converter, ma_offset_pcm_frames_ptr(pDevice->playback.pInputCache, pDevice->playback.inputCacheConsumed, pDevice->playback.format, pDevice->playback.channels), &framesConvertedIn, pFramesInInternalFormat, &framesConvertedOut); + + pDevice->playback.inputCacheConsumed += framesConvertedIn; + pDevice->playback.inputCacheRemaining -= framesConvertedIn; - totalFramesReadFromClient += (ma_uint32)framesConvertedIn; /* Safe cast. */ totalFramesReadOut += (ma_uint32)framesConvertedOut; /* Safe cast. */ pFramesInInternalFormat = ma_offset_ptr(pFramesInInternalFormat, framesConvertedOut * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels)); } + + /* If there's no more data in the cache we'll need to fill it with some. */ + if (totalFramesReadOut < frameCount && pDevice->playback.inputCacheRemaining == 0) { + ma_uint32 inputFrameCount; + void* pInputFrames; + + inputFrameCount = (ma_uint32)pDevice->playback.inputCacheCap; + result = ma_pcm_rb_acquire_read(pRB, &inputFrameCount, &pInputFrames); + if (result == MA_SUCCESS) { + if (inputFrameCount > 0) { + ma_device__handle_data_callback(pDevice, pDevice->playback.pInputCache, pInputFrames, inputFrameCount); + } else { + if (ma_pcm_rb_pointer_distance(pRB) == 0) { + break; /* Underrun. */ + } + } + } else { + /* No capture data available. Feed in silence. */ + inputFrameCount = (ma_uint32)ma_min(pDevice->playback.inputCacheCap, sizeof(silentInputFrames) / ma_get_bytes_per_frame(pDevice->capture.format, pDevice->capture.channels)); + ma_device__handle_data_callback(pDevice, pDevice->playback.pInputCache, silentInputFrames, inputFrameCount); + } + + pDevice->playback.inputCacheConsumed = 0; + pDevice->playback.inputCacheRemaining = inputFrameCount; + + result = ma_pcm_rb_commit_read(pRB, inputFrameCount); + if (result != MA_SUCCESS) { + return result; /* Should never happen. */ + } + } } return MA_SUCCESS; } /* A helper for changing the state of the device. */ -static MA_INLINE void ma_device__set_state(ma_device* pDevice, ma_uint32 newState) +static MA_INLINE void ma_device__set_state(ma_device* pDevice, ma_device_state newState) { - c89atomic_exchange_32(&pDevice->state, newState); + c89atomic_exchange_i32((ma_int32*)&pDevice->state, (ma_int32)newState); } @@ -12541,7 +18223,6 @@ MA_API ma_uint32 ma_get_format_priority_index(ma_format format) /* Lower = bette static ma_result ma_device__post_init_setup(ma_device* pDevice, ma_device_type deviceType); - static ma_bool32 ma_device_descriptor_is_valid(const ma_device_descriptor* pDeviceDescriptor) { if (pDeviceDescriptor == NULL) { @@ -12552,7 +18233,7 @@ static ma_bool32 ma_device_descriptor_is_valid(const ma_device_descriptor* pDevi return MA_FALSE; } - if (pDeviceDescriptor->channels < MA_MIN_CHANNELS || pDeviceDescriptor->channels > MA_MAX_CHANNELS) { + if (pDeviceDescriptor->channels == 0 || pDeviceDescriptor->channels > MA_MAX_CHANNELS) { return MA_FALSE; } @@ -12594,7 +18275,7 @@ static ma_result ma_device_audio_thread__default_read_write(ma_device* pDevice) /* NOTE: The device was started outside of this function, in the worker thread. */ - while (ma_device_get_state(pDevice) == MA_STATE_STARTED && !exitLoop) { + while (ma_device_get_state(pDevice) == ma_device_state_started && !exitLoop) { switch (pDevice->type) { case ma_device_type_duplex: { @@ -12644,7 +18325,7 @@ static ma_result ma_device_audio_thread__default_read_write(ma_device* pDevice) break; } - ma_device__on_data(pDevice, playbackClientData, capturedClientData, (ma_uint32)capturedClientFramesToProcessThisIteration); /* Safe cast .*/ + ma_device__handle_data_callback(pDevice, playbackClientData, capturedClientData, (ma_uint32)capturedClientFramesToProcessThisIteration); /* Safe cast .*/ capturedDeviceFramesProcessed += (ma_uint32)capturedDeviceFramesToProcessThisIteration; /* Safe cast. */ capturedDeviceFramesRemaining -= (ma_uint32)capturedDeviceFramesToProcessThisIteration; /* Safe cast. */ @@ -12974,7 +18655,7 @@ static ma_result ma_device_init__null(ma_device* pDevice, const ma_device_config pDescriptorCapture->sampleRate = (pDescriptorCapture->sampleRate != 0) ? pDescriptorCapture->sampleRate : MA_DEFAULT_SAMPLE_RATE; if (pDescriptorCapture->channelMap[0] == MA_CHANNEL_NONE) { - ma_get_standard_channel_map(ma_standard_channel_map_default, pDescriptorCapture->channels, pDescriptorCapture->channelMap); + ma_channel_map_init_standard(ma_standard_channel_map_default, pDescriptorCapture->channelMap, ma_countof(pDescriptorCapture->channelMap), pDescriptorCapture->channels); } pDescriptorCapture->periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptorCapture, pDescriptorCapture->sampleRate, pConfig->performanceProfile); @@ -12986,7 +18667,7 @@ static ma_result ma_device_init__null(ma_device* pDevice, const ma_device_config pDescriptorPlayback->sampleRate = (pDescriptorPlayback->sampleRate != 0) ? pDescriptorPlayback->sampleRate : MA_DEFAULT_SAMPLE_RATE; if (pDescriptorPlayback->channelMap[0] == MA_CHANNEL_NONE) { - ma_get_standard_channel_map(ma_standard_channel_map_default, pDescriptorPlayback->channels, pDescriptorPlayback->channelMap); + ma_channel_map_init_standard(ma_standard_channel_map_default, pDescriptorPlayback->channelMap, ma_countof(pDescriptorCapture->channelMap), pDescriptorPlayback->channels); } pDescriptorPlayback->periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptorPlayback, pDescriptorPlayback->sampleRate, pConfig->performanceProfile); @@ -13555,7 +19236,7 @@ static const PROPERTYKEY MA_PKEY_Device_FriendlyName = {{0xA45C254E, static const PROPERTYKEY MA_PKEY_AudioEngine_DeviceFormat = {{0xF19F064D, 0x82C, 0x4E27, {0xBC, 0x73, 0x68, 0x82, 0xA1, 0xBB, 0x8E, 0x4C}}, 0}; static const IID MA_IID_IUnknown = {0x00000000, 0x0000, 0x0000, {0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}}; /* 00000000-0000-0000-C000-000000000046 */ -#ifndef MA_WIN32_DESKTOP +#if !defined(MA_WIN32_DESKTOP) && !defined(MA_WIN32_GDK) static const IID MA_IID_IAgileObject = {0x94EA2B94, 0xE9CC, 0x49E0, {0xC0, 0xFF, 0xEE, 0x64, 0xCA, 0x8F, 0x5B, 0x90}}; /* 94EA2B94-E9CC-49E0-C0FF-EE64CA8F5B90 */ #endif @@ -13565,7 +19246,7 @@ static const IID MA_IID_IAudioClient3 = {0x7ED4EE07, static const IID MA_IID_IAudioRenderClient = {0xF294ACFC, 0x3146, 0x4483, {0xA7, 0xBF, 0xAD, 0xDC, 0xA7, 0xC2, 0x60, 0xE2}}; /* F294ACFC-3146-4483-A7BF-ADDCA7C260E2 = __uuidof(IAudioRenderClient) */ static const IID MA_IID_IAudioCaptureClient = {0xC8ADBD64, 0xE71E, 0x48A0, {0xA4, 0xDE, 0x18, 0x5C, 0x39, 0x5C, 0xD3, 0x17}}; /* C8ADBD64-E71E-48A0-A4DE-185C395CD317 = __uuidof(IAudioCaptureClient) */ static const IID MA_IID_IMMNotificationClient = {0x7991EEC9, 0x7E89, 0x4D85, {0x83, 0x90, 0x6C, 0x70, 0x3C, 0xEC, 0x60, 0xC0}}; /* 7991EEC9-7E89-4D85-8390-6C703CEC60C0 = __uuidof(IMMNotificationClient) */ -#ifndef MA_WIN32_DESKTOP +#if !defined(MA_WIN32_DESKTOP) && !defined(MA_WIN32_GDK) static const IID MA_IID_DEVINTERFACE_AUDIO_RENDER = {0xE6327CAD, 0xDCEC, 0x4949, {0xAE, 0x8A, 0x99, 0x1E, 0x97, 0x6A, 0x79, 0xD2}}; /* E6327CAD-DCEC-4949-AE8A-991E976A79D2 */ static const IID MA_IID_DEVINTERFACE_AUDIO_CAPTURE = {0x2EEF81BE, 0x33FA, 0x4800, {0x96, 0x70, 0x1C, 0xD4, 0x74, 0x97, 0x2C, 0x3F}}; /* 2EEF81BE-33FA-4800-9670-1CD474972C3F */ static const IID MA_IID_IActivateAudioInterfaceCompletionHandler = {0x41D949AB, 0x9862, 0x444A, {0x80, 0xF6, 0xC2, 0x61, 0x33, 0x4D, 0xA5, 0xEB}}; /* 41D949AB-9862-444A-80F6-C261334DA5EB */ @@ -13582,7 +19263,7 @@ static const IID MA_IID_IMMDeviceEnumerator_Instance = {0xA95664D2, #endif typedef struct ma_IUnknown ma_IUnknown; -#ifdef MA_WIN32_DESKTOP +#if defined(MA_WIN32_DESKTOP) || defined(MA_WIN32_GDK) #define MA_MM_DEVICE_STATE_ACTIVE 1 #define MA_MM_DEVICE_STATE_DISABLED 2 #define MA_MM_DEVICE_STATE_NOTPRESENT 4 @@ -13668,7 +19349,7 @@ static MA_INLINE HRESULT ma_IUnknown_QueryInterface(ma_IUnknown* pThis, const II static MA_INLINE ULONG ma_IUnknown_AddRef(ma_IUnknown* pThis) { return pThis->lpVtbl->AddRef(pThis); } static MA_INLINE ULONG ma_IUnknown_Release(ma_IUnknown* pThis) { return pThis->lpVtbl->Release(pThis); } -#ifdef MA_WIN32_DESKTOP +#if defined(MA_WIN32_DESKTOP) || defined(MA_WIN32_GDK) /* IMMNotificationClient */ typedef struct { @@ -14012,7 +19693,7 @@ static MA_INLINE HRESULT ma_IAudioCaptureClient_GetBuffer(ma_IAudioCaptureClient static MA_INLINE HRESULT ma_IAudioCaptureClient_ReleaseBuffer(ma_IAudioCaptureClient* pThis, ma_uint32 numFramesRead) { return pThis->lpVtbl->ReleaseBuffer(pThis, numFramesRead); } static MA_INLINE HRESULT ma_IAudioCaptureClient_GetNextPacketSize(ma_IAudioCaptureClient* pThis, ma_uint32* pNumFramesInNextPacket) { return pThis->lpVtbl->GetNextPacketSize(pThis, pNumFramesInNextPacket); } -#ifndef MA_WIN32_DESKTOP +#if !defined(MA_WIN32_DESKTOP) && !defined(MA_WIN32_GDK) #include typedef struct ma_completion_handler_uwp ma_completion_handler_uwp; @@ -14029,7 +19710,7 @@ typedef struct struct ma_completion_handler_uwp { ma_completion_handler_uwp_vtbl* lpVtbl; - MA_ATOMIC ma_uint32 counter; + MA_ATOMIC(4, ma_uint32) counter; HANDLE hEvent; }; @@ -14109,7 +19790,7 @@ static void ma_completion_handler_uwp_wait(ma_completion_handler_uwp* pHandler) #endif /* !MA_WIN32_DESKTOP */ /* We need a virtual table for our notification client object that's used for detecting changes to the default device. */ -#ifdef MA_WIN32_DESKTOP +#if defined(MA_WIN32_DESKTOP) || defined(MA_WIN32_GDK) static HRESULT STDMETHODCALLTYPE ma_IMMNotificationClient_QueryInterface(ma_IMMNotificationClient* pThis, const IID* const riid, void** ppObject) { /* @@ -14183,7 +19864,7 @@ static HRESULT STDMETHODCALLTYPE ma_IMMNotificationClient_OnDeviceStateChanged(m use this to determine whether or not we need to automatically start the device when it's plugged back in again. */ - if (ma_device_get_state(pThis->pDevice) == MA_STATE_STARTED) { + if (ma_device_get_state(pThis->pDevice) == ma_device_state_started) { if (isPlayback) { pThis->pDevice->wasapi.isDetachedPlayback = MA_TRUE; } @@ -14293,13 +19974,13 @@ static HRESULT STDMETHODCALLTYPE ma_IMMNotificationClient_OnDefaultDeviceChanged /* Second attempt at device rerouting. We're going to retrieve the device's state at the time of the route change. We're then going to stop the device, reinitialize the device, and then start - it again if the state before stopping was MA_STATE_STARTED. + it again if the state before stopping was ma_device_state_started. */ { ma_uint32 previousState = ma_device_get_state(pThis->pDevice); ma_bool8 restartDevice = MA_FALSE; - if (previousState == MA_STATE_STARTED) { + if (previousState == ma_device_state_started) { ma_device_stop(pThis->pDevice); restartDevice = MA_TRUE; } @@ -14364,7 +20045,7 @@ static ma_IMMNotificationClientVtbl g_maNotificationCientVtbl = { }; #endif /* MA_WIN32_DESKTOP */ -#ifdef MA_WIN32_DESKTOP +#if defined(MA_WIN32_DESKTOP) || defined(MA_WIN32_GDK) typedef ma_IMMDevice ma_WASAPIDeviceInterface; #else typedef ma_IUnknown ma_WASAPIDeviceInterface; @@ -14584,7 +20265,8 @@ static ma_result ma_context_get_device_info_from_IAudioClient__wasapi(ma_context if (SUCCEEDED(hr)) { ma_add_native_data_format_to_device_info_from_WAVEFORMATEX(pWF, ma_share_mode_shared, pInfo); } else { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve mix format for device info retrieval.", ma_result_from_HRESULT(hr)); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve mix format for device info retrieval."); + return ma_result_from_HRESULT(hr); } /* @@ -14592,7 +20274,7 @@ static ma_result ma_context_get_device_info_from_IAudioClient__wasapi(ma_context UWP. Failure to retrieve the exclusive mode format is not considered an error, so from here on out, MA_SUCCESS is guaranteed to be returned. */ - #ifdef MA_WIN32_DESKTOP + #if defined(MA_WIN32_DESKTOP) || defined(MA_WIN32_GDK) { ma_IPropertyStore *pProperties; @@ -14622,7 +20304,7 @@ static ma_result ma_context_get_device_info_from_IAudioClient__wasapi(ma_context The format returned by PKEY_AudioEngine_DeviceFormat is not supported, so fall back to a search. We assume the channel count returned by MA_PKEY_AudioEngine_DeviceFormat is valid and correct. For simplicity we're only returning one format. */ - ma_uint32 channels = pInfo->minChannels; + ma_uint32 channels = pWF->nChannels; ma_channel defaultChannelMap[MA_MAX_CHANNELS]; WAVEFORMATEXTENSIBLE wf; ma_bool32 found; @@ -14633,7 +20315,7 @@ static ma_result ma_context_get_device_info_from_IAudioClient__wasapi(ma_context channels = MA_MAX_CHANNELS; } - ma_get_standard_channel_map(ma_standard_channel_map_microsoft, channels, defaultChannelMap); + ma_channel_map_init_standard(ma_standard_channel_map_microsoft, defaultChannelMap, ma_countof(defaultChannelMap), channels); MA_ZERO_OBJECT(&wf); wf.Format.cbSize = sizeof(wf); @@ -14675,16 +20357,16 @@ static ma_result ma_context_get_device_info_from_IAudioClient__wasapi(ma_context ma_PropVariantClear(pContext, &var); if (!found) { - ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_WARNING, "[WASAPI] Failed to find suitable device format for device info retrieval.", MA_FORMAT_NOT_SUPPORTED); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_WARNING, "[WASAPI] Failed to find suitable device format for device info retrieval."); } } } else { - ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_WARNING, "[WASAPI] Failed to retrieve device format for device info retrieval.", ma_result_from_HRESULT(hr)); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_WARNING, "[WASAPI] Failed to retrieve device format for device info retrieval."); } ma_IPropertyStore_Release(pProperties); } else { - ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_WARNING, "[WASAPI] Failed to open property store for device info retrieval.", ma_result_from_HRESULT(hr)); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_WARNING, "[WASAPI] Failed to open property store for device info retrieval."); } } #endif @@ -14692,7 +20374,7 @@ static ma_result ma_context_get_device_info_from_IAudioClient__wasapi(ma_context return MA_SUCCESS; } -#ifdef MA_WIN32_DESKTOP +#if defined(MA_WIN32_DESKTOP) || defined(MA_WIN32_GDK) static ma_EDataFlow ma_device_type_to_EDataFlow(ma_device_type deviceType) { if (deviceType == ma_device_type_playback) { @@ -14717,7 +20399,8 @@ static ma_result ma_context_create_IMMDeviceEnumerator__wasapi(ma_context* pCont hr = ma_CoCreateInstance(pContext, MA_CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, MA_IID_IMMDeviceEnumerator, (void**)&pDeviceEnumerator); if (FAILED(hr)) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create device enumerator.", ma_result_from_HRESULT(hr)); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create device enumerator."); + return ma_result_from_HRESULT(hr); } *ppDeviceEnumerator = pDeviceEnumerator; @@ -14790,7 +20473,8 @@ static ma_result ma_context_get_MMDevice__wasapi(ma_context* pContext, ma_device hr = ma_CoCreateInstance(pContext, MA_CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, MA_IID_IMMDeviceEnumerator, (void**)&pDeviceEnumerator); if (FAILED(hr)) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create IMMDeviceEnumerator.", ma_result_from_HRESULT(hr)); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create IMMDeviceEnumerator."); + return ma_result_from_HRESULT(hr); } if (pDeviceID == NULL) { @@ -14801,7 +20485,8 @@ static ma_result ma_context_get_MMDevice__wasapi(ma_context* pContext, ma_device ma_IMMDeviceEnumerator_Release(pDeviceEnumerator); if (FAILED(hr)) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve IMMDevice.", ma_result_from_HRESULT(hr)); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve IMMDevice."); + return ma_result_from_HRESULT(hr); } return MA_SUCCESS; @@ -14881,7 +20566,8 @@ static ma_result ma_context_get_device_info_from_MMDevice__wasapi(ma_context* pC ma_IAudioClient_Release(pAudioClient); return result; } else { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to activate audio client for device info retrieval.", ma_result_from_HRESULT(hr)); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to activate audio client for device info retrieval."); + return ma_result_from_HRESULT(hr); } } @@ -14908,7 +20594,8 @@ static ma_result ma_context_enumerate_devices_by_type__wasapi(ma_context* pConte if (SUCCEEDED(hr)) { hr = ma_IMMDeviceCollection_GetCount(pDeviceCollection, &deviceCount); if (FAILED(hr)) { - result = ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to get device count.", ma_result_from_HRESULT(hr)); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to get device count."); + result = ma_result_from_HRESULT(hr); goto done; } @@ -14999,13 +20686,15 @@ static ma_result ma_context_get_IAudioClient_UWP__wasapi(ma_context* pContext, m hr = StringFromIID(&iid, &iidStr); #endif if (FAILED(hr)) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to convert device IID to string for ActivateAudioInterfaceAsync(). Out of memory.", ma_result_from_HRESULT(hr)); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to convert device IID to string for ActivateAudioInterfaceAsync(). Out of memory."); + return ma_result_from_HRESULT(hr); } result = ma_completion_handler_uwp_init(&completionHandler); if (result != MA_SUCCESS) { ma_CoTaskMemFree(pContext, iidStr); - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create event for waiting for ActivateAudioInterfaceAsync().", result); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create event for waiting for ActivateAudioInterfaceAsync()."); + return result; } #if defined(__cplusplus) @@ -15016,7 +20705,8 @@ static ma_result ma_context_get_IAudioClient_UWP__wasapi(ma_context* pContext, m if (FAILED(hr)) { ma_completion_handler_uwp_uninit(&completionHandler); ma_CoTaskMemFree(pContext, iidStr); - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[WASAPI] ActivateAudioInterfaceAsync() failed.", ma_result_from_HRESULT(hr)); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] ActivateAudioInterfaceAsync() failed."); + return ma_result_from_HRESULT(hr); } ma_CoTaskMemFree(pContext, iidStr); @@ -15029,13 +20719,15 @@ static ma_result ma_context_get_IAudioClient_UWP__wasapi(ma_context* pContext, m ma_IActivateAudioInterfaceAsyncOperation_Release(pAsyncOp); if (FAILED(hr) || FAILED(activateResult)) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to activate device.", FAILED(hr) ? ma_result_from_HRESULT(hr) : ma_result_from_HRESULT(activateResult)); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to activate device."); + return FAILED(hr) ? ma_result_from_HRESULT(hr) : ma_result_from_HRESULT(activateResult); } /* Here is where we grab the IAudioClient interface. */ hr = ma_IUnknown_QueryInterface(pActivatedInterface, &MA_IID_IAudioClient, (void**)ppAudioClient); if (FAILED(hr)) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to query IAudioClient interface.", ma_result_from_HRESULT(hr)); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to query IAudioClient interface."); + return ma_result_from_HRESULT(hr); } if (ppActivatedInterface) { @@ -15050,7 +20742,7 @@ static ma_result ma_context_get_IAudioClient_UWP__wasapi(ma_context* pContext, m static ma_result ma_context_get_IAudioClient__wasapi(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_IAudioClient** ppAudioClient, ma_WASAPIDeviceInterface** ppDeviceInterface) { -#ifdef MA_WIN32_DESKTOP +#if defined(MA_WIN32_DESKTOP) || defined(MA_WIN32_GDK) return ma_context_get_IAudioClient_Desktop__wasapi(pContext, deviceType, pDeviceID, ppAudioClient, ppDeviceInterface); #else return ma_context_get_IAudioClient_UWP__wasapi(pContext, deviceType, pDeviceID, ppAudioClient, ppDeviceInterface); @@ -15061,14 +20753,15 @@ static ma_result ma_context_get_IAudioClient__wasapi(ma_context* pContext, ma_de static ma_result ma_context_enumerate_devices__wasapi(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) { /* Different enumeration for desktop and UWP. */ -#ifdef MA_WIN32_DESKTOP +#if defined(MA_WIN32_DESKTOP) || defined(MA_WIN32_GDK) /* Desktop */ HRESULT hr; ma_IMMDeviceEnumerator* pDeviceEnumerator; hr = ma_CoCreateInstance(pContext, MA_CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, MA_IID_IMMDeviceEnumerator, (void**)&pDeviceEnumerator); if (FAILED(hr)) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create device enumerator.", ma_result_from_HRESULT(hr)); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create device enumerator."); + return ma_result_from_HRESULT(hr); } ma_context_enumerate_devices_by_type__wasapi(pContext, pDeviceEnumerator, ma_device_type_playback, callback, pUserData); @@ -15112,7 +20805,7 @@ static ma_result ma_context_enumerate_devices__wasapi(ma_context* pContext, ma_e static ma_result ma_context_get_device_info__wasapi(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_device_info* pDeviceInfo) { -#ifdef MA_WIN32_DESKTOP +#if defined(MA_WIN32_DESKTOP) || defined(MA_WIN32_GDK) ma_result result; ma_IMMDevice* pMMDevice = NULL; LPWSTR pDefaultDeviceID = NULL; @@ -15164,7 +20857,7 @@ static ma_result ma_device_uninit__wasapi(ma_device* pDevice) { MA_ASSERT(pDevice != NULL); -#ifdef MA_WIN32_DESKTOP +#if defined(MA_WIN32_DESKTOP) || defined(MA_WIN32_GDK) if (pDevice->wasapi.pDeviceEnumerator) { ((ma_IMMDeviceEnumerator*)pDevice->wasapi.pDeviceEnumerator)->lpVtbl->UnregisterEndpointNotificationCallback((ma_IMMDeviceEnumerator*)pDevice->wasapi.pDeviceEnumerator, &pDevice->wasapi.notificationClient); ma_IMMDeviceEnumerator_Release((ma_IMMDeviceEnumerator*)pDevice->wasapi.pDeviceEnumerator); @@ -15172,9 +20865,23 @@ static ma_result ma_device_uninit__wasapi(ma_device* pDevice) #endif if (pDevice->wasapi.pRenderClient) { + if (pDevice->wasapi.pMappedBufferPlayback != NULL) { + ma_IAudioRenderClient_ReleaseBuffer((ma_IAudioRenderClient*)pDevice->wasapi.pRenderClient, pDevice->wasapi.mappedBufferPlaybackCap, 0); + pDevice->wasapi.pMappedBufferPlayback = NULL; + pDevice->wasapi.mappedBufferPlaybackCap = 0; + pDevice->wasapi.mappedBufferPlaybackLen = 0; + } + ma_IAudioRenderClient_Release((ma_IAudioRenderClient*)pDevice->wasapi.pRenderClient); } if (pDevice->wasapi.pCaptureClient) { + if (pDevice->wasapi.pMappedBufferCapture != NULL) { + ma_IAudioCaptureClient_ReleaseBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, pDevice->wasapi.mappedBufferCaptureCap); + pDevice->wasapi.pMappedBufferCapture = NULL; + pDevice->wasapi.mappedBufferCaptureCap = 0; + pDevice->wasapi.mappedBufferCaptureLen = 0; + } + ma_IAudioCaptureClient_Release((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient); } @@ -15293,7 +21000,7 @@ static ma_result ma_device_init_internal__wasapi(ma_context* pContext, ma_device /* Here is where we try to determine the best format to use with the device. If the client if wanting exclusive mode, first try finding the best format for that. If this fails, fall back to shared mode. */ result = MA_FORMAT_NOT_SUPPORTED; if (pData->shareMode == ma_share_mode_exclusive) { - #ifdef MA_WIN32_DESKTOP + #if defined(MA_WIN32_DESKTOP) || defined(MA_WIN32_GDK) /* In exclusive mode on desktop we always use the backend's native format. */ ma_IPropertyStore* pStore = NULL; hr = ma_IMMDevice_OpenPropertyStore(pDeviceInterface, STGM_READ, &pStore); @@ -15405,7 +21112,7 @@ static ma_result ma_device_init_internal__wasapi(ma_context* pContext, ma_device /* Slightly different initialization for shared and exclusive modes. We try exclusive mode first, and if it fails, fall back to shared mode. */ if (shareMode == MA_AUDCLNT_SHAREMODE_EXCLUSIVE) { - MA_REFERENCE_TIME bufferDuration = periodDurationInMicroseconds * 10; + MA_REFERENCE_TIME bufferDuration = periodDurationInMicroseconds * pData->periodsOut * 10; /* If the periodicy is too small, Initialize() will fail with AUDCLNT_E_INVALID_DEVICE_PERIOD. In this case we should just keep increasing @@ -15439,7 +21146,7 @@ static ma_result ma_device_init_internal__wasapi(ma_context* pContext, ma_device /* Unfortunately we need to release and re-acquire the audio client according to MSDN. Seems silly - why not just call IAudioClient_Initialize() again?! */ ma_IAudioClient_Release((ma_IAudioClient*)pData->pAudioClient); - #ifdef MA_WIN32_DESKTOP + #if defined(MA_WIN32_DESKTOP) || defined(MA_WIN32_GDK) hr = ma_IMMDevice_Activate(pDeviceInterface, &MA_IID_IAudioClient, CLSCTX_ALL, NULL, (void**)&pData->pAudioClient); #else hr = ma_IUnknown_QueryInterface(pDeviceInterface, &MA_IID_IAudioClient, (void**)&pData->pAudioClient); @@ -15497,15 +21204,11 @@ static ma_result ma_device_init_internal__wasapi(ma_context* pContext, ma_device /* The period needs to be clamped between minPeriodInFrames and maxPeriodInFrames. */ actualPeriodInFrames = ma_clamp(actualPeriodInFrames, minPeriodInFrames, maxPeriodInFrames); - #if defined(MA_DEBUG_OUTPUT) - { - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "[WASAPI] Trying IAudioClient3_InitializeSharedAudioStream(actualPeriodInFrames=%d)\n", actualPeriodInFrames); - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, " defaultPeriodInFrames=%d\n", defaultPeriodInFrames); - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, " fundamentalPeriodInFrames=%d\n", fundamentalPeriodInFrames); - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, " minPeriodInFrames=%d\n", minPeriodInFrames); - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, " maxPeriodInFrames=%d\n", maxPeriodInFrames); - } - #endif + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "[WASAPI] Trying IAudioClient3_InitializeSharedAudioStream(actualPeriodInFrames=%d)\n", actualPeriodInFrames); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, " defaultPeriodInFrames=%d\n", defaultPeriodInFrames); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, " fundamentalPeriodInFrames=%d\n", fundamentalPeriodInFrames); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, " minPeriodInFrames=%d\n", minPeriodInFrames); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, " maxPeriodInFrames=%d\n", maxPeriodInFrames); /* If the client requested a largish buffer than we don't actually want to use low latency shared mode because it forces small buffers. */ if (actualPeriodInFrames >= desiredPeriodInFrames) { @@ -15517,12 +21220,9 @@ static ma_result ma_device_init_internal__wasapi(ma_context* pContext, ma_device if (SUCCEEDED(hr)) { wasInitializedUsingIAudioClient3 = MA_TRUE; pData->periodSizeInFramesOut = actualPeriodInFrames; - #if defined(MA_DEBUG_OUTPUT) - { - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "[WASAPI] Using IAudioClient3\n"); - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, " periodSizeInFramesOut=%d\n", pData->periodSizeInFramesOut); - } - #endif + + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "[WASAPI] Using IAudioClient3\n"); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, " periodSizeInFramesOut=%d\n", pData->periodSizeInFramesOut); } else { ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "[WASAPI] IAudioClient3_InitializeSharedAudioStream failed. Falling back to IAudioClient.\n"); } @@ -15590,7 +21290,7 @@ static ma_result ma_device_init_internal__wasapi(ma_context* pContext, ma_device /* Grab the name of the device. */ - #ifdef MA_WIN32_DESKTOP + #if defined(MA_WIN32_DESKTOP) || defined(MA_WIN32_GDK) { ma_IPropertyStore *pProperties; hr = ma_IMMDevice_OpenPropertyStore(pDeviceInterface, STGM_READ, &pProperties); @@ -15613,7 +21313,7 @@ static ma_result ma_device_init_internal__wasapi(ma_context* pContext, ma_device stream routing so that IDs can be compared and we can determine which device has been detached and whether or not it matches with our ma_device. */ - #ifdef MA_WIN32_DESKTOP + #if defined(MA_WIN32_DESKTOP) || defined(MA_WIN32_GDK) { /* Desktop */ ma_context_get_device_id_from_MMDevice__wasapi(pContext, pDeviceInterface, &pData->id); @@ -15627,7 +21327,7 @@ static ma_result ma_device_init_internal__wasapi(ma_context* pContext, ma_device done: /* Clean up. */ -#ifdef MA_WIN32_DESKTOP +#if defined(MA_WIN32_DESKTOP) || defined(MA_WIN32_GDK) if (pDeviceInterface != NULL) { ma_IMMDevice_Release(pDeviceInterface); } @@ -15652,7 +21352,7 @@ done: } if (errorMsg != NULL && errorMsg[0] != '\0') { - ma_post_log_message(pContext, NULL, MA_LOG_LEVEL_ERROR, errorMsg); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "%s", errorMsg); } return result; @@ -15750,7 +21450,7 @@ static ma_result ma_device_reinit__wasapi(ma_device* pDevice, ma_device_type dev ma_IAudioClient_SetEventHandle((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture, pDevice->wasapi.hEventCapture); pDevice->wasapi.periodSizeInFramesCapture = data.periodSizeInFramesOut; - ma_IAudioClient_GetBufferSize((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture, &pDevice->wasapi.actualPeriodSizeInFramesCapture); + ma_IAudioClient_GetBufferSize((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture, &pDevice->wasapi.actualBufferSizeInFramesCapture); /* We must always have a valid ID. */ ma_wcscpy_s(pDevice->capture.id.wasapi, sizeof(pDevice->capture.id.wasapi), data.id.wasapi); @@ -15771,9 +21471,9 @@ static ma_result ma_device_reinit__wasapi(ma_device* pDevice, ma_device_type dev ma_IAudioClient_SetEventHandle((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback, pDevice->wasapi.hEventPlayback); pDevice->wasapi.periodSizeInFramesPlayback = data.periodSizeInFramesOut; - ma_IAudioClient_GetBufferSize((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback, &pDevice->wasapi.actualPeriodSizeInFramesPlayback); + ma_IAudioClient_GetBufferSize((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback, &pDevice->wasapi.actualBufferSizeInFramesPlayback); - /* We must always have a valid ID. */ + /* We must always have a valid ID because rerouting will look at it. */ ma_wcscpy_s(pDevice->playback.id.wasapi, sizeof(pDevice->playback.id.wasapi), data.id.wasapi); } @@ -15784,7 +21484,7 @@ static ma_result ma_device_init__wasapi(ma_device* pDevice, const ma_device_conf { ma_result result = MA_SUCCESS; -#ifdef MA_WIN32_DESKTOP +#if defined(MA_WIN32_DESKTOP) || defined(MA_WIN32_GDK) HRESULT hr; ma_IMMDeviceEnumerator* pDeviceEnumerator; #endif @@ -15845,12 +21545,13 @@ static ma_result ma_device_init__wasapi(ma_device* pDevice, const ma_device_conf pDevice->wasapi.pAudioClientCapture = NULL; } - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create event for capture.", result); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create event for capture."); + return result; } ma_IAudioClient_SetEventHandle((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture, pDevice->wasapi.hEventCapture); pDevice->wasapi.periodSizeInFramesCapture = data.periodSizeInFramesOut; - ma_IAudioClient_GetBufferSize((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture, &pDevice->wasapi.actualPeriodSizeInFramesCapture); + ma_IAudioClient_GetBufferSize((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture, &pDevice->wasapi.actualBufferSizeInFramesCapture); /* We must always have a valid ID. */ ma_wcscpy_s(pDevice->capture.id.wasapi, sizeof(pDevice->capture.id.wasapi), data.id.wasapi); @@ -15938,14 +21639,15 @@ static ma_result ma_device_init__wasapi(ma_device* pDevice, const ma_device_conf pDevice->wasapi.pAudioClientPlayback = NULL; } - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create event for playback.", result); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create event for playback."); + return result; } ma_IAudioClient_SetEventHandle((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback, pDevice->wasapi.hEventPlayback); pDevice->wasapi.periodSizeInFramesPlayback = data.periodSizeInFramesOut; - ma_IAudioClient_GetBufferSize((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback, &pDevice->wasapi.actualPeriodSizeInFramesPlayback); + ma_IAudioClient_GetBufferSize((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback, &pDevice->wasapi.actualBufferSizeInFramesPlayback); - /* We must always have a valid ID. */ + /* We must always have a valid ID because rerouting will look at it. */ ma_wcscpy_s(pDevice->playback.id.wasapi, sizeof(pDevice->playback.id.wasapi), data.id.wasapi); /* The descriptor needs to be updated with actual values. */ @@ -15962,7 +21664,7 @@ static ma_result ma_device_init__wasapi(ma_device* pDevice, const ma_device_conf we are connecting to the default device we want to do automatic stream routing when the device is disabled or unplugged. Otherwise we want to just stop the device outright and let the application handle it. */ -#ifdef MA_WIN32_DESKTOP +#if defined(MA_WIN32_DESKTOP) || defined(MA_WIN32_GDK) if (pConfig->wasapi.noAutoStreamRouting == MA_FALSE) { if ((pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) && pConfig->capture.pDeviceID == NULL) { pDevice->wasapi.allowCaptureAutoStreamRouting = MA_TRUE; @@ -15975,7 +21677,8 @@ static ma_result ma_device_init__wasapi(ma_device* pDevice, const ma_device_conf hr = ma_CoCreateInstance(pDevice->pContext, MA_CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, MA_IID_IMMDeviceEnumerator, (void**)&pDeviceEnumerator); if (FAILED(hr)) { ma_device_uninit__wasapi(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create device enumerator.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create device enumerator."); + return ma_result_from_HRESULT(hr); } pDevice->wasapi.notificationClient.lpVtbl = (void*)&g_maNotificationCientVtbl; @@ -16036,16 +21739,16 @@ static ma_result ma_device__get_available_frames__wasapi(ma_device* pDevice, ma_ } if ((ma_ptr)pAudioClient == pDevice->wasapi.pAudioClientPlayback) { - *pFrameCount = pDevice->wasapi.actualPeriodSizeInFramesPlayback - paddingFramesCount; + *pFrameCount = pDevice->wasapi.actualBufferSizeInFramesPlayback - paddingFramesCount; } else { *pFrameCount = paddingFramesCount; } } else { /* Exclusive mode. */ if ((ma_ptr)pAudioClient == pDevice->wasapi.pAudioClientPlayback) { - *pFrameCount = pDevice->wasapi.actualPeriodSizeInFramesPlayback; + *pFrameCount = pDevice->wasapi.actualBufferSizeInFramesPlayback; } else { - *pFrameCount = pDevice->wasapi.actualPeriodSizeInFramesCapture; + *pFrameCount = pDevice->wasapi.actualBufferSizeInFramesCapture; } } @@ -16065,12 +21768,14 @@ static ma_result ma_device_reroute__wasapi(ma_device* pDevice, ma_device_type de result = ma_device_reinit__wasapi(pDevice, deviceType); if (result != MA_SUCCESS) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[WASAPI] Reinitializing device after route change failed.\n"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[WASAPI] Reinitializing device after route change failed.\n"); return result; } ma_device__post_init_setup(pDevice, deviceType); + ma_device__on_notification_rerouted(pDevice); + return MA_SUCCESS; } @@ -16083,14 +21788,21 @@ static ma_result ma_device_start__wasapi(ma_device* pDevice) if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex || pDevice->type == ma_device_type_loopback) { hr = ma_IAudioClient_Start((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture); if (FAILED(hr)) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to start internal capture device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to start internal capture device."); + return ma_result_from_HRESULT(hr); } c89atomic_exchange_32(&pDevice->wasapi.isStartedCapture, MA_TRUE); } if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - /* No need to do anything for playback as that'll be started automatically in the data loop. */ + hr = ma_IAudioClient_Start((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback); + if (FAILED(hr)) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to start internal playback device."); + return ma_result_from_HRESULT(hr); + } + + c89atomic_exchange_32(&pDevice->wasapi.isStartedPlayback, MA_TRUE); } return MA_SUCCESS; @@ -16106,13 +21818,23 @@ static ma_result ma_device_stop__wasapi(ma_device* pDevice) if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex || pDevice->type == ma_device_type_loopback) { hr = ma_IAudioClient_Stop((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture); if (FAILED(hr)) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to stop internal capture device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to stop internal capture device."); + return ma_result_from_HRESULT(hr); } /* The audio client needs to be reset otherwise restarting will fail. */ hr = ma_IAudioClient_Reset((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture); if (FAILED(hr)) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to reset internal capture device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to reset internal capture device."); + return ma_result_from_HRESULT(hr); + } + + /* If we have a mapped buffer we need to release it. */ + if (pDevice->wasapi.pMappedBufferCapture != NULL) { + ma_IAudioCaptureClient_ReleaseBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, pDevice->wasapi.mappedBufferCaptureCap); + pDevice->wasapi.pMappedBufferCapture = NULL; + pDevice->wasapi.mappedBufferCaptureCap = 0; + pDevice->wasapi.mappedBufferCaptureLen = 0; } c89atomic_exchange_32(&pDevice->wasapi.isStartedCapture, MA_FALSE); @@ -16125,7 +21847,7 @@ static ma_result ma_device_stop__wasapi(ma_device* pDevice) */ if (c89atomic_load_32(&pDevice->wasapi.isStartedPlayback)) { /* We need to make sure we put a timeout here or else we'll risk getting stuck in a deadlock in some cases. */ - DWORD waitTime = pDevice->wasapi.actualPeriodSizeInFramesPlayback / pDevice->playback.internalSampleRate; + DWORD waitTime = pDevice->wasapi.actualBufferSizeInFramesPlayback / pDevice->playback.internalSampleRate; if (pDevice->playback.shareMode == ma_share_mode_exclusive) { WaitForSingleObject(pDevice->wasapi.hEventPlayback, waitTime); @@ -16138,7 +21860,7 @@ static ma_result ma_device_stop__wasapi(ma_device* pDevice) break; } - if (framesAvailablePlayback >= pDevice->wasapi.actualPeriodSizeInFramesPlayback) { + if (framesAvailablePlayback >= pDevice->wasapi.actualBufferSizeInFramesPlayback) { break; } @@ -16159,13 +21881,22 @@ static ma_result ma_device_stop__wasapi(ma_device* pDevice) hr = ma_IAudioClient_Stop((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback); if (FAILED(hr)) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to stop internal playback device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to stop internal playback device."); + return ma_result_from_HRESULT(hr); } /* The audio client needs to be reset otherwise restarting will fail. */ hr = ma_IAudioClient_Reset((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback); if (FAILED(hr)) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to reset internal playback device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to reset internal playback device."); + return ma_result_from_HRESULT(hr); + } + + if (pDevice->wasapi.pMappedBufferPlayback != NULL) { + ma_IAudioRenderClient_ReleaseBuffer((ma_IAudioRenderClient*)pDevice->wasapi.pRenderClient, pDevice->wasapi.mappedBufferPlaybackCap, 0); + pDevice->wasapi.pMappedBufferPlayback = NULL; + pDevice->wasapi.mappedBufferPlaybackCap = 0; + pDevice->wasapi.mappedBufferPlaybackLen = 0; } c89atomic_exchange_32(&pDevice->wasapi.isStartedPlayback, MA_FALSE); @@ -16179,508 +21910,237 @@ static ma_result ma_device_stop__wasapi(ma_device* pDevice) #define MA_WASAPI_WAIT_TIMEOUT_MILLISECONDS 5000 #endif -static ma_result ma_device_data_loop__wasapi(ma_device* pDevice) +static ma_result ma_device_read__wasapi(ma_device* pDevice, void* pFrames, ma_uint32 frameCount, ma_uint32* pFramesRead) { - ma_result result; - HRESULT hr; - ma_bool32 exitLoop = MA_FALSE; - ma_uint32 framesWrittenToPlaybackDevice = 0; - ma_uint32 mappedDeviceBufferSizeInFramesCapture = 0; - ma_uint32 mappedDeviceBufferSizeInFramesPlayback = 0; - ma_uint32 mappedDeviceBufferFramesRemainingCapture = 0; - ma_uint32 mappedDeviceBufferFramesRemainingPlayback = 0; - BYTE* pMappedDeviceBufferCapture = NULL; - BYTE* pMappedDeviceBufferPlayback = NULL; - ma_uint32 bpfCaptureDevice = ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); - ma_uint32 bpfPlaybackDevice = ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); - ma_uint32 bpfCaptureClient = ma_get_bytes_per_frame(pDevice->capture.format, pDevice->capture.channels); - ma_uint32 bpfPlaybackClient = ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels); - ma_uint8 inputDataInClientFormat[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint32 inputDataInClientFormatCap = 0; - ma_uint8 outputDataInClientFormat[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint32 outputDataInClientFormatCap = 0; - ma_uint32 outputDataInClientFormatCount = 0; - ma_uint32 outputDataInClientFormatConsumed = 0; - ma_uint32 periodSizeInFramesCapture = 0; + ma_result result = MA_SUCCESS; + ma_uint32 totalFramesProcessed = 0; - MA_ASSERT(pDevice != NULL); + /* + When reading, we need to get a buffer and process all of it before releasing it. Because the + frame count (frameCount) can be different to the size of the buffer, we'll need to cache the + pointer to the buffer. + */ - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex || pDevice->type == ma_device_type_loopback) { - periodSizeInFramesCapture = pDevice->capture.internalPeriodSizeInFrames; - inputDataInClientFormatCap = sizeof(inputDataInClientFormat) / bpfCaptureClient; - } + /* Keep running until we've processed the requested number of frames. */ + while (ma_device_get_state(pDevice) == ma_device_state_started && totalFramesProcessed < frameCount) { + ma_uint32 framesRemaining = frameCount - totalFramesProcessed; - if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - outputDataInClientFormatCap = sizeof(outputDataInClientFormat) / bpfPlaybackClient; - } + /* If we have a mapped data buffer, consume that first. */ + if (pDevice->wasapi.pMappedBufferCapture != NULL) { + /* We have a cached data pointer so consume that before grabbing another one from WASAPI. */ + ma_uint32 framesToProcessNow = framesRemaining; + if (framesToProcessNow > pDevice->wasapi.mappedBufferCaptureLen) { + framesToProcessNow = pDevice->wasapi.mappedBufferCaptureLen; + } - while (ma_device_get_state(pDevice) == MA_STATE_STARTED && !exitLoop) { - switch (pDevice->type) - { - case ma_device_type_duplex: - { - ma_uint32 framesAvailableCapture; - ma_uint32 framesAvailablePlayback; - DWORD flagsCapture; /* Passed to IAudioCaptureClient_GetBuffer(). */ + /* Now just copy the data over to the output buffer. */ + ma_copy_pcm_frames( + ma_offset_pcm_frames_ptr(pFrames, totalFramesProcessed, pDevice->capture.internalFormat, pDevice->capture.internalChannels), + ma_offset_pcm_frames_const_ptr(pDevice->wasapi.pMappedBufferCapture, pDevice->wasapi.mappedBufferCaptureCap - pDevice->wasapi.mappedBufferCaptureLen, pDevice->capture.internalFormat, pDevice->capture.internalChannels), + framesToProcessNow, + pDevice->capture.internalFormat, pDevice->capture.internalChannels + ); - /* The process is to map the playback buffer and fill it as quickly as possible from input data. */ - if (pMappedDeviceBufferPlayback == NULL) { - result = ma_device__get_available_frames__wasapi(pDevice, (ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback, &framesAvailablePlayback); - if (result != MA_SUCCESS) { - return result; - } + totalFramesProcessed += framesToProcessNow; + pDevice->wasapi.mappedBufferCaptureLen -= framesToProcessNow; - /* In exclusive mode, the frame count needs to exactly match the value returned by GetCurrentPadding(). */ - if (pDevice->playback.shareMode != ma_share_mode_exclusive) { - if (framesAvailablePlayback > pDevice->wasapi.periodSizeInFramesPlayback) { - framesAvailablePlayback = pDevice->wasapi.periodSizeInFramesPlayback; - } - } + /* If the data buffer has been fully consumed we need to release it. */ + if (pDevice->wasapi.mappedBufferCaptureLen == 0) { + ma_IAudioCaptureClient_ReleaseBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, pDevice->wasapi.mappedBufferCaptureCap); + pDevice->wasapi.pMappedBufferCapture = NULL; + pDevice->wasapi.mappedBufferCaptureCap = 0; + } + } else { + /* We don't have any cached data pointer, so grab another one. */ + HRESULT hr; + DWORD flags; - /* We're ready to map the playback device's buffer. We don't release this until it's been entirely filled. */ - hr = ma_IAudioRenderClient_GetBuffer((ma_IAudioRenderClient*)pDevice->wasapi.pRenderClient, framesAvailablePlayback, &pMappedDeviceBufferPlayback); - if (FAILED(hr)) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve internal buffer from playback device in preparation for writing to the device.", ma_result_from_HRESULT(hr)); - exitLoop = MA_TRUE; - break; - } - - mappedDeviceBufferSizeInFramesPlayback = framesAvailablePlayback; - mappedDeviceBufferFramesRemainingPlayback = framesAvailablePlayback; - } - - if (mappedDeviceBufferFramesRemainingPlayback > 0) { - /* At this point we should have a buffer available for output. We need to keep writing input samples to it. */ - for (;;) { - /* Try grabbing some captured data if we haven't already got a mapped buffer. */ - if (pMappedDeviceBufferCapture == NULL) { - if (pDevice->capture.shareMode == ma_share_mode_shared) { - if (WaitForSingleObject(pDevice->wasapi.hEventCapture, MA_WASAPI_WAIT_TIMEOUT_MILLISECONDS) != WAIT_OBJECT_0) { - return MA_ERROR; /* Wait failed. */ - } - } - - result = ma_device__get_available_frames__wasapi(pDevice, (ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture, &framesAvailableCapture); - if (result != MA_SUCCESS) { - exitLoop = MA_TRUE; - break; - } - - /* Wait for more if nothing is available. */ - if (framesAvailableCapture == 0) { - /* In exclusive mode we waited at the top. */ - if (pDevice->capture.shareMode != ma_share_mode_shared) { - if (WaitForSingleObject(pDevice->wasapi.hEventCapture, MA_WASAPI_WAIT_TIMEOUT_MILLISECONDS) != WAIT_OBJECT_0) { - return MA_ERROR; /* Wait failed. */ - } - } - - continue; - } - - /* Getting here means there's data available for writing to the output device. */ - mappedDeviceBufferSizeInFramesCapture = ma_min(framesAvailableCapture, periodSizeInFramesCapture); - hr = ma_IAudioCaptureClient_GetBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, (BYTE**)&pMappedDeviceBufferCapture, &mappedDeviceBufferSizeInFramesCapture, &flagsCapture, NULL, NULL); - if (FAILED(hr)) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve internal buffer from capture device in preparation for writing to the device.", ma_result_from_HRESULT(hr)); - exitLoop = MA_TRUE; - break; - } - - - /* Overrun detection. */ - if ((flagsCapture & MA_AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY) != 0) { - /* Glitched. Probably due to an overrun. */ - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[WASAPI] Data discontinuity (possible overrun). framesAvailableCapture=%d, mappedBufferSizeInFramesCapture=%d\n", framesAvailableCapture, mappedDeviceBufferSizeInFramesCapture); - - /* - Exeriment: If we get an overrun it probably means we're straddling the end of the buffer. In order to prevent a never-ending sequence of glitches let's experiment - by dropping every frame until we're left with only a single period. To do this we just keep retrieving and immediately releasing buffers until we're down to the - last period. - */ - if (framesAvailableCapture >= pDevice->wasapi.actualPeriodSizeInFramesCapture) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[WASAPI] Synchronizing capture stream. "); - do - { - hr = ma_IAudioCaptureClient_ReleaseBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, mappedDeviceBufferSizeInFramesCapture); - if (FAILED(hr)) { - break; - } - - framesAvailableCapture -= mappedDeviceBufferSizeInFramesCapture; - - if (framesAvailableCapture > 0) { - mappedDeviceBufferSizeInFramesCapture = ma_min(framesAvailableCapture, periodSizeInFramesCapture); - hr = ma_IAudioCaptureClient_GetBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, (BYTE**)&pMappedDeviceBufferCapture, &mappedDeviceBufferSizeInFramesCapture, &flagsCapture, NULL, NULL); - if (FAILED(hr)) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve internal buffer from capture device in preparation for writing to the device.", ma_result_from_HRESULT(hr)); - exitLoop = MA_TRUE; - break; - } - } else { - pMappedDeviceBufferCapture = NULL; - mappedDeviceBufferSizeInFramesCapture = 0; - } - } while (framesAvailableCapture > periodSizeInFramesCapture); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "framesAvailableCapture=%d, mappedBufferSizeInFramesCapture=%d\n", framesAvailableCapture, mappedDeviceBufferSizeInFramesCapture); - } - } else { - #ifdef MA_DEBUG_OUTPUT - if (flagsCapture != 0) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[WASAPI] Capture Flags: %ld\n", flagsCapture); - } - #endif - } - - mappedDeviceBufferFramesRemainingCapture = mappedDeviceBufferSizeInFramesCapture; - } - - - /* At this point we should have both input and output data available. We now need to convert the data and post it to the client. */ - for (;;) { - BYTE* pRunningDeviceBufferCapture; - BYTE* pRunningDeviceBufferPlayback; - ma_uint32 framesToProcess; - ma_uint32 framesProcessed; - - pRunningDeviceBufferCapture = pMappedDeviceBufferCapture + ((mappedDeviceBufferSizeInFramesCapture - mappedDeviceBufferFramesRemainingCapture ) * bpfCaptureDevice); - pRunningDeviceBufferPlayback = pMappedDeviceBufferPlayback + ((mappedDeviceBufferSizeInFramesPlayback - mappedDeviceBufferFramesRemainingPlayback) * bpfPlaybackDevice); - - /* There may be some data sitting in the converter that needs to be processed first. Once this is exhaused, run the data callback again. */ - if (!pDevice->playback.converter.isPassthrough && outputDataInClientFormatConsumed < outputDataInClientFormatCount) { - ma_uint64 convertedFrameCountClient = (outputDataInClientFormatCount - outputDataInClientFormatConsumed); - ma_uint64 convertedFrameCountDevice = mappedDeviceBufferFramesRemainingPlayback; - void* pConvertedFramesClient = outputDataInClientFormat + (outputDataInClientFormatConsumed * bpfPlaybackClient); - void* pConvertedFramesDevice = pRunningDeviceBufferPlayback; - result = ma_data_converter_process_pcm_frames(&pDevice->playback.converter, pConvertedFramesClient, &convertedFrameCountClient, pConvertedFramesDevice, &convertedFrameCountDevice); - if (result != MA_SUCCESS) { - break; - } - - outputDataInClientFormatConsumed += (ma_uint32)convertedFrameCountClient; /* Safe cast. */ - mappedDeviceBufferFramesRemainingPlayback -= (ma_uint32)convertedFrameCountDevice; /* Safe cast. */ - - if (mappedDeviceBufferFramesRemainingPlayback == 0) { - break; - } - } - - /* - Getting here means we need to fire the callback. If format conversion is unnecessary, we can optimize this by passing the pointers to the internal - buffers directly to the callback. - */ - if (pDevice->capture.converter.isPassthrough && pDevice->playback.converter.isPassthrough) { - /* Optimal path. We can pass mapped pointers directly to the callback. */ - framesToProcess = ma_min(mappedDeviceBufferFramesRemainingCapture, mappedDeviceBufferFramesRemainingPlayback); - framesProcessed = framesToProcess; - - ma_device__on_data(pDevice, pRunningDeviceBufferPlayback, pRunningDeviceBufferCapture, framesToProcess); - - mappedDeviceBufferFramesRemainingCapture -= framesProcessed; - mappedDeviceBufferFramesRemainingPlayback -= framesProcessed; - - if (mappedDeviceBufferFramesRemainingCapture == 0) { - break; /* Exhausted input data. */ - } - if (mappedDeviceBufferFramesRemainingPlayback == 0) { - break; /* Exhausted output data. */ - } - } else if (pDevice->capture.converter.isPassthrough) { - /* The input buffer is a passthrough, but the playback buffer requires a conversion. */ - framesToProcess = ma_min(mappedDeviceBufferFramesRemainingCapture, outputDataInClientFormatCap); - framesProcessed = framesToProcess; - - ma_device__on_data(pDevice, outputDataInClientFormat, pRunningDeviceBufferCapture, framesToProcess); - outputDataInClientFormatCount = framesProcessed; - outputDataInClientFormatConsumed = 0; - - mappedDeviceBufferFramesRemainingCapture -= framesProcessed; - if (mappedDeviceBufferFramesRemainingCapture == 0) { - break; /* Exhausted input data. */ - } - } else if (pDevice->playback.converter.isPassthrough) { - /* The input buffer requires conversion, the playback buffer is passthrough. */ - ma_uint64 capturedDeviceFramesToProcess = mappedDeviceBufferFramesRemainingCapture; - ma_uint64 capturedClientFramesToProcess = ma_min(inputDataInClientFormatCap, mappedDeviceBufferFramesRemainingPlayback); - - result = ma_data_converter_process_pcm_frames(&pDevice->capture.converter, pRunningDeviceBufferCapture, &capturedDeviceFramesToProcess, inputDataInClientFormat, &capturedClientFramesToProcess); - if (result != MA_SUCCESS) { - break; - } - - if (capturedClientFramesToProcess == 0) { - break; - } - - ma_device__on_data(pDevice, pRunningDeviceBufferPlayback, inputDataInClientFormat, (ma_uint32)capturedClientFramesToProcess); /* Safe cast. */ - - mappedDeviceBufferFramesRemainingCapture -= (ma_uint32)capturedDeviceFramesToProcess; - mappedDeviceBufferFramesRemainingPlayback -= (ma_uint32)capturedClientFramesToProcess; - } else { - ma_uint64 capturedDeviceFramesToProcess = mappedDeviceBufferFramesRemainingCapture; - ma_uint64 capturedClientFramesToProcess = ma_min(inputDataInClientFormatCap, outputDataInClientFormatCap); - - result = ma_data_converter_process_pcm_frames(&pDevice->capture.converter, pRunningDeviceBufferCapture, &capturedDeviceFramesToProcess, inputDataInClientFormat, &capturedClientFramesToProcess); - if (result != MA_SUCCESS) { - break; - } - - if (capturedClientFramesToProcess == 0) { - break; - } - - ma_device__on_data(pDevice, outputDataInClientFormat, inputDataInClientFormat, (ma_uint32)capturedClientFramesToProcess); - - mappedDeviceBufferFramesRemainingCapture -= (ma_uint32)capturedDeviceFramesToProcess; - outputDataInClientFormatCount = (ma_uint32)capturedClientFramesToProcess; - outputDataInClientFormatConsumed = 0; - } - } - - - /* If at this point we've run out of capture data we need to release the buffer. */ - if (mappedDeviceBufferFramesRemainingCapture == 0 && pMappedDeviceBufferCapture != NULL) { - hr = ma_IAudioCaptureClient_ReleaseBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, mappedDeviceBufferSizeInFramesCapture); - if (FAILED(hr)) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to release internal buffer from capture device after reading from the device.", ma_result_from_HRESULT(hr)); - exitLoop = MA_TRUE; - break; - } - - pMappedDeviceBufferCapture = NULL; - mappedDeviceBufferFramesRemainingCapture = 0; - mappedDeviceBufferSizeInFramesCapture = 0; - } - - /* Get out of this loop if we're run out of room in the playback buffer. */ - if (mappedDeviceBufferFramesRemainingPlayback == 0) { - break; - } - } - } - - - /* If at this point we've run out of data we need to release the buffer. */ - if (mappedDeviceBufferFramesRemainingPlayback == 0 && pMappedDeviceBufferPlayback != NULL) { - hr = ma_IAudioRenderClient_ReleaseBuffer((ma_IAudioRenderClient*)pDevice->wasapi.pRenderClient, mappedDeviceBufferSizeInFramesPlayback, 0); - if (FAILED(hr)) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to release internal buffer from playback device after writing to the device.", ma_result_from_HRESULT(hr)); - exitLoop = MA_TRUE; - break; - } - - framesWrittenToPlaybackDevice += mappedDeviceBufferSizeInFramesPlayback; - - pMappedDeviceBufferPlayback = NULL; - mappedDeviceBufferFramesRemainingPlayback = 0; - mappedDeviceBufferSizeInFramesPlayback = 0; - } - - if (!c89atomic_load_32(&pDevice->wasapi.isStartedPlayback)) { - ma_uint32 startThreshold = pDevice->playback.internalPeriodSizeInFrames * 1; - - /* Prevent a deadlock. If we don't clamp against the actual buffer size we'll never end up starting the playback device which will result in a deadlock. */ - if (startThreshold > pDevice->wasapi.actualPeriodSizeInFramesPlayback) { - startThreshold = pDevice->wasapi.actualPeriodSizeInFramesPlayback; - } - - if (pDevice->playback.shareMode == ma_share_mode_exclusive || framesWrittenToPlaybackDevice >= startThreshold) { - hr = ma_IAudioClient_Start((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback); - if (FAILED(hr)) { - ma_IAudioClient_Stop((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture); - ma_IAudioClient_Reset((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to start internal playback device.", ma_result_from_HRESULT(hr)); - } - - c89atomic_exchange_32(&pDevice->wasapi.isStartedPlayback, MA_TRUE); - } - } - - /* Make sure the device has started before waiting. */ - if (WaitForSingleObject(pDevice->wasapi.hEventPlayback, MA_WASAPI_WAIT_TIMEOUT_MILLISECONDS) != WAIT_OBJECT_0) { - return MA_ERROR; /* Wait failed. */ - } - } break; - - - - case ma_device_type_capture: - case ma_device_type_loopback: - { - ma_uint32 framesAvailableCapture; - DWORD flagsCapture; /* Passed to IAudioCaptureClient_GetBuffer(). */ - - /* Wait for data to become available first. */ - if (WaitForSingleObject(pDevice->wasapi.hEventCapture, MA_WASAPI_WAIT_TIMEOUT_MILLISECONDS) != WAIT_OBJECT_0) { - /* - For capture we can terminate here because it probably means the microphone just isn't delivering data for whatever reason, but - for loopback is most likely means nothing is actually playing. We want to keep trying in this situation. - */ - if (pDevice->type == ma_device_type_loopback) { - continue; /* Keep waiting in loopback mode. */ - } else { - exitLoop = MA_TRUE; - break; /* Wait failed. */ - } - } - - /* See how many frames are available. Since we waited at the top, I don't think this should ever return 0. I'm checking for this anyway. */ - result = ma_device__get_available_frames__wasapi(pDevice, (ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture, &framesAvailableCapture); - if (result != MA_SUCCESS) { - exitLoop = MA_TRUE; - break; - } - - if (framesAvailableCapture < pDevice->wasapi.periodSizeInFramesCapture) { - continue; /* Nothing available. Keep waiting. */ - } - - /* Map the data buffer in preparation for sending to the client. */ - mappedDeviceBufferSizeInFramesCapture = framesAvailableCapture; - hr = ma_IAudioCaptureClient_GetBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, (BYTE**)&pMappedDeviceBufferCapture, &mappedDeviceBufferSizeInFramesCapture, &flagsCapture, NULL, NULL); - if (FAILED(hr)) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve internal buffer from capture device in preparation for writing to the device.", ma_result_from_HRESULT(hr)); - exitLoop = MA_TRUE; - break; - } + /* First just ask WASAPI for a data buffer. If it's not available, we'll wait for more. */ + hr = ma_IAudioCaptureClient_GetBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, (BYTE**)&pDevice->wasapi.pMappedBufferCapture, &pDevice->wasapi.mappedBufferCaptureCap, &flags, NULL, NULL); + if (hr == S_OK) { + /* We got a data buffer. Continue to the next loop iteration which will then read from the mapped pointer. */ /* Overrun detection. */ - if ((flagsCapture & MA_AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY) != 0) { + if ((flags & MA_AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY) != 0) { /* Glitched. Probably due to an overrun. */ - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[WASAPI] Data discontinuity (possible overrun). framesAvailableCapture=%d, mappedBufferSizeInFramesCapture=%d\n", framesAvailableCapture, mappedDeviceBufferSizeInFramesCapture); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[WASAPI] Data discontinuity (possible overrun). Attempting recovery. mappedBufferCaptureCap=%d\n", pDevice->wasapi.mappedBufferCaptureCap); /* - Exeriment: If we get an overrun it probably means we're straddling the end of the buffer. In order to prevent a never-ending sequence of glitches let's experiment - by dropping every frame until we're left with only a single period. To do this we just keep retrieving and immediately releasing buffers until we're down to the - last period. + If we got an overrun it probably means we're straddling the end of the buffer. In order to prevent + a never-ending sequence of glitches we're going to recover by completely clearing out the capture + buffer. */ - if (framesAvailableCapture >= pDevice->wasapi.actualPeriodSizeInFramesCapture) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[WASAPI] Synchronizing capture stream. "); - do - { - hr = ma_IAudioCaptureClient_ReleaseBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, mappedDeviceBufferSizeInFramesCapture); + { + ma_uint32 iterationCount = 4; /* Safety to prevent an infinite loop. */ + ma_uint32 i; + + for (i = 0; i < iterationCount; i += 1) { + hr = ma_IAudioCaptureClient_ReleaseBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, pDevice->wasapi.mappedBufferCaptureCap); if (FAILED(hr)) { break; } - framesAvailableCapture -= mappedDeviceBufferSizeInFramesCapture; - - if (framesAvailableCapture > 0) { - mappedDeviceBufferSizeInFramesCapture = ma_min(framesAvailableCapture, periodSizeInFramesCapture); - hr = ma_IAudioCaptureClient_GetBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, (BYTE**)&pMappedDeviceBufferCapture, &mappedDeviceBufferSizeInFramesCapture, &flagsCapture, NULL, NULL); - if (FAILED(hr)) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve internal buffer from capture device in preparation for writing to the device.", ma_result_from_HRESULT(hr)); - exitLoop = MA_TRUE; - break; - } - } else { - pMappedDeviceBufferCapture = NULL; - mappedDeviceBufferSizeInFramesCapture = 0; + hr = ma_IAudioCaptureClient_GetBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, (BYTE**)&pDevice->wasapi.pMappedBufferCapture, &pDevice->wasapi.mappedBufferCaptureCap, &flags, NULL, NULL); + if (hr == MA_AUDCLNT_S_BUFFER_EMPTY || FAILED(hr)) { + break; } - } while (framesAvailableCapture > periodSizeInFramesCapture); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "framesAvailableCapture=%d, mappedBufferSizeInFramesCapture=%d\n", framesAvailableCapture, mappedDeviceBufferSizeInFramesCapture); + } } + + /* We should not have a valid buffer at this point so make sure everything is empty. */ + pDevice->wasapi.pMappedBufferCapture = NULL; + pDevice->wasapi.mappedBufferCaptureCap = 0; + pDevice->wasapi.mappedBufferCaptureLen = 0; } else { - #ifdef MA_DEBUG_OUTPUT - if (flagsCapture != 0) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[WASAPI] Capture Flags: %ld\n", flagsCapture); - } - #endif - } + /* The data is clean. */ + pDevice->wasapi.mappedBufferCaptureLen = pDevice->wasapi.mappedBufferCaptureCap; - /* We should have a buffer at this point, but let's just do a sanity check anyway. */ - if (mappedDeviceBufferSizeInFramesCapture > 0 && pMappedDeviceBufferCapture != NULL) { - ma_device__send_frames_to_client(pDevice, mappedDeviceBufferSizeInFramesCapture, pMappedDeviceBufferCapture); - - /* At this point we're done with the buffer. */ - hr = ma_IAudioCaptureClient_ReleaseBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, mappedDeviceBufferSizeInFramesCapture); - pMappedDeviceBufferCapture = NULL; /* <-- Important. Not doing this can result in an error once we leave this loop because it will use this to know whether or not a final ReleaseBuffer() needs to be called. */ - mappedDeviceBufferSizeInFramesCapture = 0; - if (FAILED(hr)) { - ma_post_log_message(ma_device_get_context(pDevice), pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to release internal buffer from capture device after reading from the device."); - exitLoop = MA_TRUE; - break; + if (flags != 0) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[WASAPI] Capture Flags: %ld\n", flags); } } - } break; + continue; + } else { + if (hr == MA_AUDCLNT_S_BUFFER_EMPTY || hr == MA_AUDCLNT_E_BUFFER_ERROR) { + /* + No data is available. We need to wait for more. There's two situations to consider + here. The first is normal capture mode. If this times out it probably means the + microphone isn't delivering data for whatever reason. In this case we'll just + abort the read and return whatever we were able to get. The other situations is + loopback mode, in which case a timeout probably just means the nothing is playing + through the speakers. + */ + if (WaitForSingleObject(pDevice->wasapi.hEventCapture, MA_WASAPI_WAIT_TIMEOUT_MILLISECONDS) != WAIT_OBJECT_0) { + if (pDevice->type == ma_device_type_loopback) { + continue; /* Keep waiting in loopback mode. */ + } else { + result = MA_ERROR; + break; /* Wait failed. */ + } + } - - case ma_device_type_playback: - { - ma_uint32 framesAvailablePlayback; - - /* Check how much space is available. If this returns 0 we just keep waiting. */ - result = ma_device__get_available_frames__wasapi(pDevice, (ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback, &framesAvailablePlayback); - if (result != MA_SUCCESS) { - exitLoop = MA_TRUE; + /* At this point we should be able to loop back to the start of the loop and try retrieving a data buffer again. */ + } else { + /* An error occured and we need to abort. */ + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve internal buffer from capture device in preparation for reading from the device. HRESULT = %d. Stopping device.\n", (int)hr); + result = ma_result_from_HRESULT(hr); break; } - - if (framesAvailablePlayback >= pDevice->wasapi.periodSizeInFramesPlayback) { - /* Map a the data buffer in preparation for the callback. */ - hr = ma_IAudioRenderClient_GetBuffer((ma_IAudioRenderClient*)pDevice->wasapi.pRenderClient, framesAvailablePlayback, &pMappedDeviceBufferPlayback); - if (FAILED(hr)) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve internal buffer from playback device in preparation for writing to the device.", ma_result_from_HRESULT(hr)); - exitLoop = MA_TRUE; - break; - } - - /* We should have a buffer at this point. */ - ma_device__read_frames_from_client(pDevice, framesAvailablePlayback, pMappedDeviceBufferPlayback); - - /* At this point we're done writing to the device and we just need to release the buffer. */ - hr = ma_IAudioRenderClient_ReleaseBuffer((ma_IAudioRenderClient*)pDevice->wasapi.pRenderClient, framesAvailablePlayback, 0); - pMappedDeviceBufferPlayback = NULL; /* <-- Important. Not doing this can result in an error once we leave this loop because it will use this to know whether or not a final ReleaseBuffer() needs to be called. */ - mappedDeviceBufferSizeInFramesPlayback = 0; - - if (FAILED(hr)) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to release internal buffer from playback device after writing to the device.", ma_result_from_HRESULT(hr)); - exitLoop = MA_TRUE; - break; - } - - framesWrittenToPlaybackDevice += framesAvailablePlayback; - } - - if (!c89atomic_load_32(&pDevice->wasapi.isStartedPlayback)) { - hr = ma_IAudioClient_Start((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback); - if (FAILED(hr)) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to start internal playback device.", ma_result_from_HRESULT(hr)); - exitLoop = MA_TRUE; - break; - } - - c89atomic_exchange_32(&pDevice->wasapi.isStartedPlayback, MA_TRUE); - } - - /* Make sure we don't wait on the event before we've started the device or we may end up deadlocking. */ - if (WaitForSingleObject(pDevice->wasapi.hEventPlayback, MA_WASAPI_WAIT_TIMEOUT_MILLISECONDS) != WAIT_OBJECT_0) { - exitLoop = MA_TRUE; - break; /* Wait failed. Probably timed out. */ - } - } break; - - default: return MA_INVALID_ARGS; + } } } - /* Here is where the device needs to be stopped. */ - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex || pDevice->type == ma_device_type_loopback) { - /* Any mapped buffers need to be released. */ - if (pMappedDeviceBufferCapture != NULL) { - hr = ma_IAudioCaptureClient_ReleaseBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, mappedDeviceBufferSizeInFramesCapture); + /* + If we were unable to process the entire requested frame count, but we still have a mapped buffer, + there's a good chance either an error occurred or the device was stopped mid-read. In this case + we'll need to make sure the buffer is released. + */ + if (totalFramesProcessed < frameCount && pDevice->wasapi.pMappedBufferCapture != NULL) { + ma_IAudioCaptureClient_ReleaseBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, pDevice->wasapi.mappedBufferCaptureCap); + pDevice->wasapi.pMappedBufferCapture = NULL; + pDevice->wasapi.mappedBufferCaptureCap = 0; + pDevice->wasapi.mappedBufferCaptureLen = 0; + } + + if (pFramesRead != NULL) { + *pFramesRead = totalFramesProcessed; + } + + return result; +} + +static ma_result ma_device_write__wasapi(ma_device* pDevice, const void* pFrames, ma_uint32 frameCount, ma_uint32* pFramesWritten) +{ + ma_result result = MA_SUCCESS; + ma_uint32 totalFramesProcessed = 0; + + /* Keep writing to the device until it's stopped or we've consumed all of our input. */ + while (ma_device_get_state(pDevice) == ma_device_state_started && totalFramesProcessed < frameCount) { + ma_uint32 framesRemaining = frameCount - totalFramesProcessed; + + /* + We're going to do this in a similar way to capture. We'll first check if the cached data pointer + is valid, and if so, read from that. Otherwise We will call IAudioRenderClient_GetBuffer() with + a requested buffer size equal to our actual period size. If it returns AUDCLNT_E_BUFFER_TOO_LARGE + it means we need to wait for some data to become available. + */ + if (pDevice->wasapi.pMappedBufferPlayback != NULL) { + /* We still have some space available in the mapped data buffer. Write to it. */ + ma_uint32 framesToProcessNow = framesRemaining; + if (framesToProcessNow > (pDevice->wasapi.mappedBufferPlaybackCap - pDevice->wasapi.mappedBufferPlaybackLen)) { + framesToProcessNow = (pDevice->wasapi.mappedBufferPlaybackCap - pDevice->wasapi.mappedBufferPlaybackLen); + } + + /* Now just copy the data over to the output buffer. */ + ma_copy_pcm_frames( + ma_offset_pcm_frames_ptr(pDevice->wasapi.pMappedBufferPlayback, pDevice->wasapi.mappedBufferPlaybackLen, pDevice->playback.internalFormat, pDevice->playback.internalChannels), + ma_offset_pcm_frames_const_ptr(pFrames, totalFramesProcessed, pDevice->playback.internalFormat, pDevice->playback.internalChannels), + framesToProcessNow, + pDevice->playback.internalFormat, pDevice->playback.internalChannels + ); + + totalFramesProcessed += framesToProcessNow; + pDevice->wasapi.mappedBufferPlaybackLen += framesToProcessNow; + + /* If the data buffer has been fully consumed we need to release it. */ + if (pDevice->wasapi.mappedBufferPlaybackLen == pDevice->wasapi.mappedBufferPlaybackCap) { + ma_IAudioRenderClient_ReleaseBuffer((ma_IAudioRenderClient*)pDevice->wasapi.pRenderClient, pDevice->wasapi.mappedBufferPlaybackCap, 0); + pDevice->wasapi.pMappedBufferPlayback = NULL; + pDevice->wasapi.mappedBufferPlaybackCap = 0; + pDevice->wasapi.mappedBufferPlaybackLen = 0; + + /* + In exclusive mode we need to wait here. Exclusive mode is weird because GetBuffer() never + seems to return AUDCLNT_E_BUFFER_TOO_LARGE, which is what we normally use to determine + whether or not we need to wait for more data. + */ + if (pDevice->playback.shareMode == ma_share_mode_exclusive) { + if (WaitForSingleObject(pDevice->wasapi.hEventPlayback, MA_WASAPI_WAIT_TIMEOUT_MILLISECONDS) != WAIT_OBJECT_0) { + result = MA_ERROR; + break; /* Wait failed. Probably timed out. */ + } + } + } + } else { + /* We don't have a mapped data buffer so we'll need to get one. */ + HRESULT hr; + ma_uint32 bufferSizeInFrames; + + /* Special rules for exclusive mode. */ + if (pDevice->playback.shareMode == ma_share_mode_exclusive) { + bufferSizeInFrames = pDevice->wasapi.actualBufferSizeInFramesPlayback; + } else { + bufferSizeInFrames = pDevice->wasapi.periodSizeInFramesPlayback; + } + + hr = ma_IAudioRenderClient_GetBuffer((ma_IAudioRenderClient*)pDevice->wasapi.pRenderClient, bufferSizeInFrames, (BYTE**)&pDevice->wasapi.pMappedBufferPlayback); + if (hr == S_OK) { + /* We have data available. */ + pDevice->wasapi.mappedBufferPlaybackCap = bufferSizeInFrames; + pDevice->wasapi.mappedBufferPlaybackLen = 0; + } else { + if (hr == MA_AUDCLNT_E_BUFFER_TOO_LARGE || hr == MA_AUDCLNT_E_BUFFER_ERROR) { + /* Not enough data available. We need to wait for more. */ + if (WaitForSingleObject(pDevice->wasapi.hEventPlayback, MA_WASAPI_WAIT_TIMEOUT_MILLISECONDS) != WAIT_OBJECT_0) { + result = MA_ERROR; + break; /* Wait failed. Probably timed out. */ + } + } else { + /* Some error occurred. We'll need to abort. */ + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve internal buffer from playback device in preparation for writing to the device. HRESULT = %d. Stopping device.\n", (int)hr); + result = ma_result_from_HRESULT(hr); + break; + } + } } } - if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - /* Any mapped buffers need to be released. */ - if (pMappedDeviceBufferPlayback != NULL) { - hr = ma_IAudioRenderClient_ReleaseBuffer((ma_IAudioRenderClient*)pDevice->wasapi.pRenderClient, mappedDeviceBufferSizeInFramesPlayback, 0); - } + if (pFramesWritten != NULL) { + *pFramesWritten = totalFramesProcessed; } - return MA_SUCCESS; + return result; } static ma_result ma_device_data_loop_wakeup__wasapi(ma_device* pDevice) @@ -16825,9 +22285,9 @@ static ma_result ma_context_init__wasapi(ma_context* pContext, const ma_context_ pCallbacks->onDeviceUninit = ma_device_uninit__wasapi; pCallbacks->onDeviceStart = ma_device_start__wasapi; pCallbacks->onDeviceStop = ma_device_stop__wasapi; - pCallbacks->onDeviceRead = NULL; /* Not used. Reading is done manually in the audio thread. */ - pCallbacks->onDeviceWrite = NULL; /* Not used. Writing is done manually in the audio thread. */ - pCallbacks->onDeviceDataLoop = ma_device_data_loop__wasapi; + pCallbacks->onDeviceRead = ma_device_read__wasapi; + pCallbacks->onDeviceWrite = ma_device_write__wasapi; + pCallbacks->onDeviceDataLoop = NULL; pCallbacks->onDeviceDataLoopWakeup = ma_device_data_loop_wakeup__wasapi; return MA_SUCCESS; @@ -17089,9 +22549,9 @@ struct ma_IDirectSoundCapture { ma_IDirectSoundCaptureVtbl* lpVtbl; }; -static MA_INLINE HRESULT ma_IDirectSoundCapture_QueryInterface(ma_IDirectSoundCapture* pThis, const IID* const riid, void** ppObject) { return pThis->lpVtbl->QueryInterface(pThis, riid, ppObject); } -static MA_INLINE ULONG ma_IDirectSoundCapture_AddRef(ma_IDirectSoundCapture* pThis) { return pThis->lpVtbl->AddRef(pThis); } -static MA_INLINE ULONG ma_IDirectSoundCapture_Release(ma_IDirectSoundCapture* pThis) { return pThis->lpVtbl->Release(pThis); } +static MA_INLINE HRESULT ma_IDirectSoundCapture_QueryInterface (ma_IDirectSoundCapture* pThis, const IID* const riid, void** ppObject) { return pThis->lpVtbl->QueryInterface(pThis, riid, ppObject); } +static MA_INLINE ULONG ma_IDirectSoundCapture_AddRef (ma_IDirectSoundCapture* pThis) { return pThis->lpVtbl->AddRef(pThis); } +static MA_INLINE ULONG ma_IDirectSoundCapture_Release (ma_IDirectSoundCapture* pThis) { return pThis->lpVtbl->Release(pThis); } static MA_INLINE HRESULT ma_IDirectSoundCapture_CreateCaptureBuffer(ma_IDirectSoundCapture* pThis, const MA_DSCBUFFERDESC* pDSCBufferDesc, ma_IDirectSoundCaptureBuffer** ppDSCBuffer, void* pUnkOuter) { return pThis->lpVtbl->CreateCaptureBuffer(pThis, pDSCBufferDesc, ppDSCBuffer, pUnkOuter); } static MA_INLINE HRESULT ma_IDirectSoundCapture_GetCaps (ma_IDirectSoundCapture* pThis, MA_DSCCAPS* pDSCCaps) { return pThis->lpVtbl->GetCaps(pThis, pDSCCaps); } static MA_INLINE HRESULT ma_IDirectSoundCapture_Initialize (ma_IDirectSoundCapture* pThis, const GUID* pGuidDevice) { return pThis->lpVtbl->Initialize(pThis, pGuidDevice); } @@ -17250,7 +22710,8 @@ static ma_result ma_context_create_IDirectSound__dsound(ma_context* pContext, ma pDirectSound = NULL; if (FAILED(((ma_DirectSoundCreateProc)pContext->dsound.DirectSoundCreate)((pDeviceID == NULL) ? NULL : (const GUID*)pDeviceID->dsound, &pDirectSound, NULL))) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[DirectSound] DirectSoundCreate() failed for playback device.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[DirectSound] DirectSoundCreate() failed for playback device."); + return MA_FAILED_TO_OPEN_BACKEND_DEVICE; } /* The cooperative level must be set before doing anything else. */ @@ -17261,7 +22722,8 @@ static ma_result ma_context_create_IDirectSound__dsound(ma_context* pContext, ma hr = ma_IDirectSound_SetCooperativeLevel(pDirectSound, hWnd, (shareMode == ma_share_mode_exclusive) ? MA_DSSCL_EXCLUSIVE : MA_DSSCL_PRIORITY); if (FAILED(hr)) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSound_SetCooperateiveLevel() failed for playback device.", ma_result_from_HRESULT(hr)); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSound_SetCooperateiveLevel() failed for playback device."); + return ma_result_from_HRESULT(hr); } *ppDirectSound = pDirectSound; @@ -17286,7 +22748,8 @@ static ma_result ma_context_create_IDirectSoundCapture__dsound(ma_context* pCont hr = ((ma_DirectSoundCaptureCreateProc)pContext->dsound.DirectSoundCaptureCreate)((pDeviceID == NULL) ? NULL : (const GUID*)pDeviceID->dsound, &pDirectSoundCapture, NULL); if (FAILED(hr)) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[DirectSound] DirectSoundCaptureCreate() failed for capture device.", ma_result_from_HRESULT(hr)); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[DirectSound] DirectSoundCaptureCreate() failed for capture device."); + return ma_result_from_HRESULT(hr); } *ppDirectSoundCapture = pDirectSoundCapture; @@ -17317,7 +22780,8 @@ static ma_result ma_context_get_format_info_for_IDirectSoundCapture__dsound(ma_c caps.dwSize = sizeof(caps); hr = ma_IDirectSoundCapture_GetCaps(pDirectSoundCapture, &caps); if (FAILED(hr)) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundCapture_GetCaps() failed for capture device.", ma_result_from_HRESULT(hr)); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundCapture_GetCaps() failed for capture device."); + return ma_result_from_HRESULT(hr); } if (pChannels) { @@ -17552,7 +23016,8 @@ static ma_result ma_context_get_device_info__dsound(ma_context* pContext, ma_dev caps.dwSize = sizeof(caps); hr = ma_IDirectSound_GetCaps(pDirectSound, &caps); if (FAILED(hr)) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSound_GetCaps() failed for playback device.", ma_result_from_HRESULT(hr)); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSound_GetCaps() failed for playback device."); + return ma_result_from_HRESULT(hr); } @@ -17628,13 +23093,13 @@ static ma_result ma_context_get_device_info__dsound(ma_context* pContext, ma_dev /* The format is always an integer format and is based on the bits per sample. */ if (bitsPerSample == 8) { - pDeviceInfo->formats[0] = ma_format_u8; + pDeviceInfo->nativeDataFormats[0].format = ma_format_u8; } else if (bitsPerSample == 16) { - pDeviceInfo->formats[0] = ma_format_s16; + pDeviceInfo->nativeDataFormats[0].format = ma_format_s16; } else if (bitsPerSample == 24) { - pDeviceInfo->formats[0] = ma_format_s24; + pDeviceInfo->nativeDataFormats[0].format = ma_format_s24; } else if (bitsPerSample == 32) { - pDeviceInfo->formats[0] = ma_format_s32; + pDeviceInfo->nativeDataFormats[0].format = ma_format_s32; } else { return MA_FORMAT_NOT_SUPPORTED; } @@ -17727,8 +23192,11 @@ static ma_result ma_config_to_WAVEFORMATEXTENSIBLE(ma_format format, ma_uint32 c static ma_uint32 ma_calculate_period_size_in_frames_from_descriptor__dsound(const ma_device_descriptor* pDescriptor, ma_uint32 nativeSampleRate, ma_performance_profile performanceProfile) { - /* DirectSound has a minimum period size of 20ms. */ - ma_uint32 minPeriodSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(20, nativeSampleRate); + /* + DirectSound has a minimum period size of 20ms. In practice, this doesn't seem to be enough for + reliable glitch-free processing so going to use 30ms instead. + */ + ma_uint32 minPeriodSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(30, nativeSampleRate); ma_uint32 periodSizeInFrames; periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptor, nativeSampleRate, performanceProfile); @@ -17799,7 +23267,8 @@ static ma_result ma_device_init__dsound(ma_device* pDevice, const ma_device_conf hr = ma_IDirectSoundCapture_CreateCaptureBuffer((ma_IDirectSoundCapture*)pDevice->dsound.pCapture, &descDS, (ma_IDirectSoundCaptureBuffer**)&pDevice->dsound.pCaptureBuffer, NULL); if (FAILED(hr)) { ma_device_uninit__dsound(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundCapture_CreateCaptureBuffer() failed for capture device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundCapture_CreateCaptureBuffer() failed for capture device."); + return ma_result_from_HRESULT(hr); } /* Get the _actual_ properties of the buffer. */ @@ -17807,7 +23276,8 @@ static ma_result ma_device_init__dsound(ma_device* pDevice, const ma_device_conf hr = ma_IDirectSoundCaptureBuffer_GetFormat((ma_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer, (WAVEFORMATEX*)pActualFormat, sizeof(rawdata), NULL); if (FAILED(hr)) { ma_device_uninit__dsound(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to retrieve the actual format of the capture device's buffer.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to retrieve the actual format of the capture device's buffer."); + return ma_result_from_HRESULT(hr); } /* We can now start setting the output data formats. */ @@ -17833,7 +23303,8 @@ static ma_result ma_device_init__dsound(ma_device* pDevice, const ma_device_conf hr = ma_IDirectSoundCapture_CreateCaptureBuffer((ma_IDirectSoundCapture*)pDevice->dsound.pCapture, &descDS, (ma_IDirectSoundCaptureBuffer**)&pDevice->dsound.pCaptureBuffer, NULL); if (FAILED(hr)) { ma_device_uninit__dsound(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] Second attempt at IDirectSoundCapture_CreateCaptureBuffer() failed for capture device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] Second attempt at IDirectSoundCapture_CreateCaptureBuffer() failed for capture device."); + return ma_result_from_HRESULT(hr); } } @@ -17869,7 +23340,8 @@ static ma_result ma_device_init__dsound(ma_device* pDevice, const ma_device_conf hr = ma_IDirectSound_CreateSoundBuffer((ma_IDirectSound*)pDevice->dsound.pPlayback, &descDSPrimary, (ma_IDirectSoundBuffer**)&pDevice->dsound.pPlaybackPrimaryBuffer, NULL); if (FAILED(hr)) { ma_device_uninit__dsound(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSound_CreateSoundBuffer() failed for playback device's primary buffer.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSound_CreateSoundBuffer() failed for playback device's primary buffer."); + return ma_result_from_HRESULT(hr); } @@ -17879,7 +23351,8 @@ static ma_result ma_device_init__dsound(ma_device* pDevice, const ma_device_conf hr = ma_IDirectSound_GetCaps((ma_IDirectSound*)pDevice->dsound.pPlayback, &caps); if (FAILED(hr)) { ma_device_uninit__dsound(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSound_GetCaps() failed for playback device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSound_GetCaps() failed for playback device."); + return ma_result_from_HRESULT(hr); } if (pDescriptorPlayback->channels == 0) { @@ -17921,7 +23394,8 @@ static ma_result ma_device_init__dsound(ma_device* pDevice, const ma_device_conf hr = ma_IDirectSoundBuffer_SetFormat((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackPrimaryBuffer, (WAVEFORMATEX*)&wf); if (FAILED(hr)) { ma_device_uninit__dsound(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to set format of playback device's primary buffer.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to set format of playback device's primary buffer."); + return ma_result_from_HRESULT(hr); } /* Get the _actual_ properties of the buffer. */ @@ -17929,7 +23403,8 @@ static ma_result ma_device_init__dsound(ma_device* pDevice, const ma_device_conf hr = ma_IDirectSoundBuffer_GetFormat((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackPrimaryBuffer, (WAVEFORMATEX*)pActualFormat, sizeof(rawdata), NULL); if (FAILED(hr)) { ma_device_uninit__dsound(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to retrieve the actual format of the playback device's primary buffer.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to retrieve the actual format of the playback device's primary buffer."); + return ma_result_from_HRESULT(hr); } /* We now have enough information to start setting some output properties. */ @@ -17971,7 +23446,8 @@ static ma_result ma_device_init__dsound(ma_device* pDevice, const ma_device_conf hr = ma_IDirectSound_CreateSoundBuffer((ma_IDirectSound*)pDevice->dsound.pPlayback, &descDS, (ma_IDirectSoundBuffer**)&pDevice->dsound.pPlaybackBuffer, NULL); if (FAILED(hr)) { ma_device_uninit__dsound(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSound_CreateSoundBuffer() failed for playback device's secondary buffer.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSound_CreateSoundBuffer() failed for playback device's secondary buffer."); + return ma_result_from_HRESULT(hr); } /* DirectSound should give us a buffer exactly the size we asked for. */ @@ -18011,12 +23487,14 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) /* The first thing to do is start the capture device. The playback device is only started after the first period is written. */ if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - if (FAILED(ma_IDirectSoundCaptureBuffer_Start((ma_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer, MA_DSCBSTART_LOOPING))) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundCaptureBuffer_Start() failed.", MA_FAILED_TO_START_BACKEND_DEVICE); + hr = ma_IDirectSoundCaptureBuffer_Start((ma_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer, MA_DSCBSTART_LOOPING); + if (FAILED(hr)) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundCaptureBuffer_Start() failed."); + return ma_result_from_HRESULT(hr); } } - while (ma_device_get_state(pDevice) == MA_STATE_STARTED) { + while (ma_device_get_state(pDevice) == ma_device_state_started) { switch (pDevice->type) { case ma_device_type_duplex: @@ -18065,7 +23543,8 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) hr = ma_IDirectSoundCaptureBuffer_Lock((ma_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer, lockOffsetInBytesCapture, lockSizeInBytesCapture, &pMappedDeviceBufferCapture, &mappedSizeInBytesCapture, NULL, NULL, 0); if (FAILED(hr)) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to map buffer from capture device in preparation for writing to the device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to map buffer from capture device in preparation for writing to the device."); + return ma_result_from_HRESULT(hr); } @@ -18091,7 +23570,7 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) outputFramesInClientFormatCount = (ma_uint32)clientCapturedFramesToProcess; mappedDeviceFramesProcessedCapture += (ma_uint32)deviceCapturedFramesToProcess; - ma_device__on_data(pDevice, outputFramesInClientFormat, inputFramesInClientFormat, (ma_uint32)clientCapturedFramesToProcess); + ma_device__handle_data_callback(pDevice, outputFramesInClientFormat, inputFramesInClientFormat, (ma_uint32)clientCapturedFramesToProcess); /* At this point we have input and output data in client format. All we need to do now is convert it to the output device format. This may take a few passes. */ for (;;) { @@ -18119,7 +23598,7 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) availableBytesPlayback += physicalPlayCursorInBytes; /* Wrap around. */ } else { /* This is an error. */ - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[DirectSound] (Duplex/Playback) WARNING: Play cursor has moved in front of the write cursor (same loop iterations). physicalPlayCursorInBytes=%ld, virtualWriteCursorInBytes=%ld.\n", physicalPlayCursorInBytes, virtualWriteCursorInBytesPlayback); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[DirectSound] (Duplex/Playback): Play cursor has moved in front of the write cursor (same loop iteration). physicalPlayCursorInBytes=%ld, virtualWriteCursorInBytes=%ld.\n", physicalPlayCursorInBytes, virtualWriteCursorInBytesPlayback); availableBytesPlayback = 0; } } else { @@ -18128,7 +23607,7 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) availableBytesPlayback = physicalPlayCursorInBytes - virtualWriteCursorInBytesPlayback; } else { /* This is an error. */ - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[DirectSound] (Duplex/Playback) WARNING: Write cursor has moved behind the play cursor (different loop iterations). physicalPlayCursorInBytes=%ld, virtualWriteCursorInBytes=%ld.\n", physicalPlayCursorInBytes, virtualWriteCursorInBytesPlayback); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[DirectSound] (Duplex/Playback): Write cursor has moved behind the play cursor (different loop iterations). physicalPlayCursorInBytes=%ld, virtualWriteCursorInBytes=%ld.\n", physicalPlayCursorInBytes, virtualWriteCursorInBytesPlayback); availableBytesPlayback = 0; } } @@ -18140,7 +23619,8 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) hr = ma_IDirectSoundBuffer_Play((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, 0, 0, MA_DSBPLAY_LOOPING); if (FAILED(hr)) { ma_IDirectSoundCaptureBuffer_Stop((ma_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundBuffer_Play() failed.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundBuffer_Play() failed."); + return ma_result_from_HRESULT(hr); } isPlaybackDeviceStarted = MA_TRUE; } else { @@ -18162,7 +23642,8 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) hr = ma_IDirectSoundBuffer_Lock((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, lockOffsetInBytesPlayback, lockSizeInBytesPlayback, &pMappedDeviceBufferPlayback, &mappedSizeInBytesPlayback, NULL, NULL, 0); if (FAILED(hr)) { - result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to map buffer from playback device in preparation for writing to the device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to map buffer from playback device in preparation for writing to the device."); + result = ma_result_from_HRESULT(hr); break; } @@ -18178,7 +23659,7 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) silentPaddingInBytes = lockSizeInBytesPlayback; } - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[DirectSound] (Duplex/Playback) Playback buffer starved. availableBytesPlayback=%ld, silentPaddingInBytes=%ld\n", availableBytesPlayback, silentPaddingInBytes); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[DirectSound] (Duplex/Playback) Playback buffer starved. availableBytesPlayback=%ld, silentPaddingInBytes=%ld\n", availableBytesPlayback, silentPaddingInBytes); } } @@ -18204,7 +23685,8 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) hr = ma_IDirectSoundBuffer_Unlock((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, pMappedDeviceBufferPlayback, framesWrittenThisIteration*bpfDevicePlayback, NULL, 0); if (FAILED(hr)) { - result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to unlock internal buffer from playback device after writing to the device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to unlock internal buffer from playback device after writing to the device."); + result = ma_result_from_HRESULT(hr); break; } @@ -18223,7 +23705,8 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) hr = ma_IDirectSoundBuffer_Play((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, 0, 0, MA_DSBPLAY_LOOPING); if (FAILED(hr)) { ma_IDirectSoundCaptureBuffer_Stop((ma_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundBuffer_Play() failed.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundBuffer_Play() failed."); + return ma_result_from_HRESULT(hr); } isPlaybackDeviceStarted = MA_TRUE; } @@ -18242,7 +23725,8 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) /* At this point we're done with the mapped portion of the capture buffer. */ hr = ma_IDirectSoundCaptureBuffer_Unlock((ma_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer, pMappedDeviceBufferCapture, mappedSizeInBytesCapture, NULL, 0); if (FAILED(hr)) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to unlock internal buffer from capture device after reading from the device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to unlock internal buffer from capture device after reading from the device."); + return ma_result_from_HRESULT(hr); } prevReadCursorInBytesCapture = (lockOffsetInBytesCapture + mappedSizeInBytesCapture); } break; @@ -18292,20 +23776,20 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) hr = ma_IDirectSoundCaptureBuffer_Lock((ma_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer, lockOffsetInBytesCapture, lockSizeInBytesCapture, &pMappedDeviceBufferCapture, &mappedSizeInBytesCapture, NULL, NULL, 0); if (FAILED(hr)) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to map buffer from capture device in preparation for writing to the device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to map buffer from capture device in preparation for writing to the device."); + result = ma_result_from_HRESULT(hr); } - #ifdef MA_DEBUG_OUTPUT if (lockSizeInBytesCapture != mappedSizeInBytesCapture) { ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[DirectSound] (Capture) lockSizeInBytesCapture=%ld != mappedSizeInBytesCapture=%ld\n", lockSizeInBytesCapture, mappedSizeInBytesCapture); } - #endif ma_device__send_frames_to_client(pDevice, mappedSizeInBytesCapture/bpfDeviceCapture, pMappedDeviceBufferCapture); hr = ma_IDirectSoundCaptureBuffer_Unlock((ma_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer, pMappedDeviceBufferCapture, mappedSizeInBytesCapture, NULL, 0); if (FAILED(hr)) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to unlock internal buffer from capture device after reading from the device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to unlock internal buffer from capture device after reading from the device."); + return ma_result_from_HRESULT(hr); } prevReadCursorInBytesCapture = lockOffsetInBytesCapture + mappedSizeInBytesCapture; @@ -18339,7 +23823,7 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) availableBytesPlayback += physicalPlayCursorInBytes; /* Wrap around. */ } else { /* This is an error. */ - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[DirectSound] (Playback) WARNING: Play cursor has moved in front of the write cursor (same loop iterations). physicalPlayCursorInBytes=%ld, virtualWriteCursorInBytes=%ld.\n", physicalPlayCursorInBytes, virtualWriteCursorInBytesPlayback); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[DirectSound] (Playback): Play cursor has moved in front of the write cursor (same loop iterations). physicalPlayCursorInBytes=%ld, virtualWriteCursorInBytes=%ld.\n", physicalPlayCursorInBytes, virtualWriteCursorInBytesPlayback); availableBytesPlayback = 0; } } else { @@ -18348,7 +23832,7 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) availableBytesPlayback = physicalPlayCursorInBytes - virtualWriteCursorInBytesPlayback; } else { /* This is an error. */ - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[DirectSound] (Playback) WARNING: Write cursor has moved behind the play cursor (different loop iterations). physicalPlayCursorInBytes=%ld, virtualWriteCursorInBytes=%ld.\n", physicalPlayCursorInBytes, virtualWriteCursorInBytesPlayback); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[DirectSound] (Playback): Write cursor has moved behind the play cursor (different loop iterations). physicalPlayCursorInBytes=%ld, virtualWriteCursorInBytes=%ld.\n", physicalPlayCursorInBytes, virtualWriteCursorInBytesPlayback); availableBytesPlayback = 0; } } @@ -18359,7 +23843,8 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) if (availableBytesPlayback == 0 && !isPlaybackDeviceStarted) { hr = ma_IDirectSoundBuffer_Play((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, 0, 0, MA_DSBPLAY_LOOPING); if (FAILED(hr)) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundBuffer_Play() failed.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundBuffer_Play() failed."); + return ma_result_from_HRESULT(hr); } isPlaybackDeviceStarted = MA_TRUE; } else { @@ -18380,7 +23865,8 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) hr = ma_IDirectSoundBuffer_Lock((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, lockOffsetInBytesPlayback, lockSizeInBytesPlayback, &pMappedDeviceBufferPlayback, &mappedSizeInBytesPlayback, NULL, NULL, 0); if (FAILED(hr)) { - result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to map buffer from playback device in preparation for writing to the device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to map buffer from playback device in preparation for writing to the device."); + result = ma_result_from_HRESULT(hr); break; } @@ -18389,7 +23875,8 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) hr = ma_IDirectSoundBuffer_Unlock((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, pMappedDeviceBufferPlayback, mappedSizeInBytesPlayback, NULL, 0); if (FAILED(hr)) { - result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to unlock internal buffer from playback device after writing to the device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to unlock internal buffer from playback device after writing to the device."); + result = ma_result_from_HRESULT(hr); break; } @@ -18407,7 +23894,8 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) if (!isPlaybackDeviceStarted && framesWrittenToPlaybackDevice >= pDevice->playback.internalPeriodSizeInFrames) { hr = ma_IDirectSoundBuffer_Play((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, 0, 0, MA_DSBPLAY_LOOPING); if (FAILED(hr)) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundBuffer_Play() failed.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundBuffer_Play() failed."); + return ma_result_from_HRESULT(hr); } isPlaybackDeviceStarted = MA_TRUE; } @@ -18426,7 +23914,8 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { hr = ma_IDirectSoundCaptureBuffer_Stop((ma_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer); if (FAILED(hr)) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundCaptureBuffer_Stop() failed.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundCaptureBuffer_Stop() failed."); + return ma_result_from_HRESULT(hr); } } @@ -18474,7 +23963,8 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) hr = ma_IDirectSoundBuffer_Stop((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer); if (FAILED(hr)) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundBuffer_Stop() failed.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundBuffer_Stop() failed."); + return ma_result_from_HRESULT(hr); } ma_IDirectSoundBuffer_SetCurrentPosition((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, 0); @@ -18789,10 +24279,10 @@ static ma_result ma_context_get_device_info_from_WAVECAPS(ma_context* pContext, if (((MA_PFN_RegOpenKeyExA)pContext->win32.RegOpenKeyExA)(HKEY_LOCAL_MACHINE, keyStr, 0, KEY_READ, &hKey) == ERROR_SUCCESS) { BYTE nameFromReg[512]; DWORD nameFromRegSize = sizeof(nameFromReg); - result = ((MA_PFN_RegQueryValueExA)pContext->win32.RegQueryValueExA)(hKey, "Name", 0, NULL, (LPBYTE)nameFromReg, (LPDWORD)&nameFromRegSize); + LONG resultWin32 = ((MA_PFN_RegQueryValueExA)pContext->win32.RegQueryValueExA)(hKey, "Name", 0, NULL, (LPBYTE)nameFromReg, (LPDWORD)&nameFromRegSize); ((MA_PFN_RegCloseKey)pContext->win32.RegCloseKey)(hKey); - if (result == ERROR_SUCCESS) { + if (resultWin32 == ERROR_SUCCESS) { /* We have the value from the registry, so now we need to construct the name string. */ char name[1024]; if (ma_strcpy_s(name, sizeof(name), pDeviceInfo->name) == 0) { @@ -19000,7 +24490,7 @@ static ma_result ma_device_uninit__winmm(ma_device* pDevice) CloseHandle((HANDLE)pDevice->winmm.hEventPlayback); } - ma__free_from_callbacks(pDevice->winmm._pHeapData, &pDevice->pContext->allocationCallbacks); + ma_free(pDevice->winmm._pHeapData, &pDevice->pContext->allocationCallbacks); MA_ZERO_OBJECT(&pDevice->winmm); /* Safety. */ @@ -19085,7 +24575,7 @@ static ma_result ma_device_init__winmm(ma_device* pDevice, const ma_device_confi pDescriptorCapture->format = ma_format_from_WAVEFORMATEX(&wf); pDescriptorCapture->channels = wf.nChannels; pDescriptorCapture->sampleRate = wf.nSamplesPerSec; - ma_get_standard_channel_map(ma_standard_channel_map_microsoft, pDescriptorCapture->channels, pDescriptorCapture->channelMap); + ma_channel_map_init_standard(ma_standard_channel_map_microsoft, pDescriptorCapture->channelMap, ma_countof(pDescriptorCapture->channelMap), pDescriptorCapture->channels); pDescriptorCapture->periodCount = pDescriptorCapture->periodCount; pDescriptorCapture->periodSizeInFrames = ma_calculate_period_size_in_frames_from_descriptor__winmm(pDescriptorCapture, pDescriptorCapture->sampleRate, pConfig->performanceProfile); } @@ -19096,7 +24586,7 @@ static ma_result ma_device_init__winmm(ma_device* pDevice, const ma_device_confi MMRESULT resultMM; /* We use an event to know when a new fragment needs to be enqueued. */ - pDevice->winmm.hEventPlayback = (ma_handle)CreateEvent(NULL, TRUE, TRUE, NULL); + pDevice->winmm.hEventPlayback = (ma_handle)CreateEventW(NULL, TRUE, TRUE, NULL); if (pDevice->winmm.hEventPlayback == NULL) { errorMsg = "[WinMM] Failed to create event for fragment enqueing for the playback device.", errorCode = ma_result_from_GetLastError(GetLastError()); goto on_error; @@ -19123,7 +24613,7 @@ static ma_result ma_device_init__winmm(ma_device* pDevice, const ma_device_confi pDescriptorPlayback->format = ma_format_from_WAVEFORMATEX(&wf); pDescriptorPlayback->channels = wf.nChannels; pDescriptorPlayback->sampleRate = wf.nSamplesPerSec; - ma_get_standard_channel_map(ma_standard_channel_map_microsoft, pDescriptorPlayback->channels, pDescriptorPlayback->channelMap); + ma_channel_map_init_standard(ma_standard_channel_map_microsoft, pDescriptorPlayback->channelMap, ma_countof(pDescriptorPlayback->channelMap), pDescriptorPlayback->channels); pDescriptorPlayback->periodCount = pDescriptorPlayback->periodCount; pDescriptorPlayback->periodSizeInFrames = ma_calculate_period_size_in_frames_from_descriptor__winmm(pDescriptorPlayback, pDescriptorPlayback->sampleRate, pConfig->performanceProfile); } @@ -19141,7 +24631,7 @@ static ma_result ma_device_init__winmm(ma_device* pDevice, const ma_device_confi heapSize += sizeof(WAVEHDR)*pDescriptorPlayback->periodCount + (pDescriptorPlayback->periodSizeInFrames * pDescriptorPlayback->periodCount * ma_get_bytes_per_frame(pDescriptorPlayback->format, pDescriptorPlayback->channels)); } - pDevice->winmm._pHeapData = (ma_uint8*)ma__calloc_from_callbacks(heapSize, &pDevice->pContext->allocationCallbacks); + pDevice->winmm._pHeapData = (ma_uint8*)ma_calloc(heapSize, &pDevice->pContext->allocationCallbacks); if (pDevice->winmm._pHeapData == NULL) { errorMsg = "[WinMM] Failed to allocate memory for the intermediary buffer.", errorCode = MA_OUT_OF_MEMORY; goto on_error; @@ -19232,8 +24722,13 @@ on_error: ((MA_PFN_waveOutClose)pDevice->pContext->winmm.waveOutClose)((HWAVEOUT)pDevice->winmm.hDevicePlayback); } - ma__free_from_callbacks(pDevice->winmm._pHeapData, &pDevice->pContext->allocationCallbacks); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, errorMsg, errorCode); + ma_free(pDevice->winmm._pHeapData, &pDevice->pContext->allocationCallbacks); + + if (errorMsg != NULL && errorMsg[0] != '\0') { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "%s", errorMsg); + } + + return errorCode; } static ma_result ma_device_start__winmm(ma_device* pDevice) @@ -19254,7 +24749,8 @@ static ma_result ma_device_start__winmm(ma_device* pDevice) for (iPeriod = 0; iPeriod < pDevice->capture.internalPeriods; ++iPeriod) { resultMM = ((MA_PFN_waveInAddBuffer)pDevice->pContext->winmm.waveInAddBuffer)((HWAVEIN)pDevice->winmm.hDeviceCapture, &((LPWAVEHDR)pDevice->winmm.pWAVEHDRCapture)[iPeriod], sizeof(WAVEHDR)); if (resultMM != MMSYSERR_NOERROR) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WinMM] Failed to attach input buffers to capture device in preparation for capture.", ma_result_from_MMRESULT(resultMM)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WinMM] Failed to attach input buffers to capture device in preparation for capture."); + return ma_result_from_MMRESULT(resultMM); } /* Make sure all of the buffers start out locked. We don't want to access them until the backend tells us we can. */ @@ -19264,7 +24760,8 @@ static ma_result ma_device_start__winmm(ma_device* pDevice) /* Capture devices need to be explicitly started, unlike playback devices. */ resultMM = ((MA_PFN_waveInStart)pDevice->pContext->winmm.waveInStart)((HWAVEIN)pDevice->winmm.hDeviceCapture); if (resultMM != MMSYSERR_NOERROR) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WinMM] Failed to start backend device.", ma_result_from_MMRESULT(resultMM)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WinMM] Failed to start backend device."); + return ma_result_from_MMRESULT(resultMM); } } @@ -19288,7 +24785,7 @@ static ma_result ma_device_stop__winmm(ma_device* pDevice) resultMM = ((MA_PFN_waveInReset)pDevice->pContext->winmm.waveInReset)((HWAVEIN)pDevice->winmm.hDeviceCapture); if (resultMM != MMSYSERR_NOERROR) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WinMM] WARNING: Failed to reset capture device.", ma_result_from_MMRESULT(resultMM)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[WinMM] WARNING: Failed to reset capture device."); } } @@ -19314,7 +24811,7 @@ static ma_result ma_device_stop__winmm(ma_device* pDevice) resultMM = ((MA_PFN_waveOutReset)pDevice->pContext->winmm.waveOutReset)((HWAVEOUT)pDevice->winmm.hDevicePlayback); if (resultMM != MMSYSERR_NOERROR) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WinMM] WARNING: Failed to reset playback device.", ma_result_from_MMRESULT(resultMM)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[WinMM] WARNING: Failed to reset playback device."); } } @@ -19369,7 +24866,7 @@ static ma_result ma_device_write__winmm(ma_device* pDevice, const void* pPCMFram resultMM = ((MA_PFN_waveOutWrite)pDevice->pContext->winmm.waveOutWrite)((HWAVEOUT)pDevice->winmm.hDevicePlayback, &pWAVEHDR[pDevice->winmm.iNextHeaderPlayback], sizeof(WAVEHDR)); if (resultMM != MMSYSERR_NOERROR) { result = ma_result_from_MMRESULT(resultMM); - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WinMM] waveOutWrite() failed.", result); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WinMM] waveOutWrite() failed."); break; } @@ -19401,7 +24898,7 @@ static ma_result ma_device_write__winmm(ma_device* pDevice, const void* pPCMFram } /* If the device has been stopped we need to break. */ - if (ma_device_get_state(pDevice) != MA_STATE_STARTED) { + if (ma_device_get_state(pDevice) != ma_device_state_started) { break; } } @@ -19458,7 +24955,7 @@ static ma_result ma_device_read__winmm(ma_device* pDevice, void* pPCMFrames, ma_ resultMM = ((MA_PFN_waveInAddBuffer)pDevice->pContext->winmm.waveInAddBuffer)((HWAVEIN)pDevice->winmm.hDeviceCapture, &((LPWAVEHDR)pDevice->winmm.pWAVEHDRCapture)[pDevice->winmm.iNextHeaderCapture], sizeof(WAVEHDR)); if (resultMM != MMSYSERR_NOERROR) { result = ma_result_from_MMRESULT(resultMM); - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WinMM] waveInAddBuffer() failed.", result); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WinMM] waveInAddBuffer() failed."); break; } @@ -19490,7 +24987,7 @@ static ma_result ma_device_read__winmm(ma_device* pDevice, void* pPCMFrames, ma_ } /* If the device has been stopped we need to break. */ - if (ma_device_get_state(pDevice) != MA_STATE_STARTED) { + if (ma_device_get_state(pDevice) != ma_device_state_started) { break; } } @@ -20206,7 +25703,8 @@ static ma_result ma_context_open_pcm__alsa(ma_context* pContext, ma_share_mode s } if (!isDeviceOpen) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[ALSA] snd_pcm_open() failed when trying to open an appropriate default device.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] snd_pcm_open() failed when trying to open an appropriate default device."); + return MA_FAILED_TO_OPEN_BACKEND_DEVICE; } } else { /* @@ -20254,7 +25752,8 @@ static ma_result ma_context_open_pcm__alsa(ma_context* pContext, ma_share_mode s } if (resultALSA < 0) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[ALSA] snd_pcm_open() failed.", ma_result_from_errno(-resultALSA)); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] snd_pcm_open() failed."); + return ma_result_from_errno(-resultALSA); } } @@ -20325,9 +25824,8 @@ static ma_result ma_context_enumerate_devices__alsa(ma_context* pContext, ma_enu goto next_device; /* The device has already been enumerated. Move on to the next one. */ } else { /* The device has not yet been enumerated. Make sure it's added to our list so that it's not enumerated again. */ - size_t oldCapacity = sizeof(*pUniqueIDs) * uniqueIDCount; size_t newCapacity = sizeof(*pUniqueIDs) * (uniqueIDCount + 1); - ma_device_id* pNewUniqueIDs = (ma_device_id*)ma__realloc_from_callbacks(pUniqueIDs, newCapacity, oldCapacity, &pContext->allocationCallbacks); + ma_device_id* pNewUniqueIDs = (ma_device_id*)ma_realloc(pUniqueIDs, newCapacity, &pContext->allocationCallbacks); if (pNewUniqueIDs == NULL) { goto next_device; /* Failed to allocate memory. */ } @@ -20392,10 +25890,11 @@ static ma_result ma_context_enumerate_devices__alsa(ma_context* pContext, ma_enu /* Some devices are both playback and capture, but they are only enumerated by ALSA once. We need to fire the callback - again for the other device type in this case. We do this for known devices. + again for the other device type in this case. We do this for known devices and where the IOID hint is NULL, which + means both Input and Output. */ if (cbResult) { - if (ma_is_common_device_name__alsa(NAME)) { + if (ma_is_common_device_name__alsa(NAME) || IOID == NULL) { if (deviceType == ma_device_type_playback) { if (!ma_is_capture_device_blacklisted__alsa(NAME)) { cbResult = callback(pContext, ma_device_type_capture, &deviceInfo, pUserData); @@ -20424,7 +25923,7 @@ static ma_result ma_context_enumerate_devices__alsa(ma_context* pContext, ma_enu } } - ma__free_from_callbacks(pUniqueIDs, &pContext->allocationCallbacks); + ma_free(pUniqueIDs, &pContext->allocationCallbacks); ((ma_snd_device_name_free_hint_proc)pContext->alsa.snd_device_name_free_hint)((void**)ppDeviceHints); ma_mutex_unlock(&pContext->alsa.internalDeviceEnumLock); @@ -20548,7 +26047,7 @@ static ma_result ma_context_get_device_info__alsa(ma_context* pContext, ma_devic } /* We need to initialize a HW parameters object in order to know what formats are supported. */ - pHWParams = (ma_snd_pcm_hw_params_t*)ma__calloc_from_callbacks(((ma_snd_pcm_hw_params_sizeof_proc)pContext->alsa.snd_pcm_hw_params_sizeof)(), &pContext->allocationCallbacks); + pHWParams = (ma_snd_pcm_hw_params_t*)ma_calloc(((ma_snd_pcm_hw_params_sizeof_proc)pContext->alsa.snd_pcm_hw_params_sizeof)(), &pContext->allocationCallbacks); if (pHWParams == NULL) { ((ma_snd_pcm_close_proc)pContext->alsa.snd_pcm_close)(pPCM); return MA_OUT_OF_MEMORY; @@ -20556,9 +26055,10 @@ static ma_result ma_context_get_device_info__alsa(ma_context* pContext, ma_devic resultALSA = ((ma_snd_pcm_hw_params_any_proc)pContext->alsa.snd_pcm_hw_params_any)(pPCM, pHWParams); if (resultALSA < 0) { - ma__free_from_callbacks(pHWParams, &pContext->allocationCallbacks); + ma_free(pHWParams, &pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pContext->alsa.snd_pcm_close)(pPCM); - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to initialize hardware parameters. snd_pcm_hw_params_any() failed.", ma_result_from_errno(-resultALSA)); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to initialize hardware parameters. snd_pcm_hw_params_any() failed."); + return ma_result_from_errno(-resultALSA); } /* @@ -20642,7 +26142,7 @@ static ma_result ma_context_get_device_info__alsa(ma_context* pContext, ma_devic } } - ma__free_from_callbacks(pHWParams, &pContext->allocationCallbacks); + ma_free(pHWParams, &pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pContext->alsa.snd_pcm_close)(pPCM); return MA_SUCCESS; @@ -20712,16 +26212,19 @@ static ma_result ma_device_init_by_type__alsa(ma_device* pDevice, const ma_devic /* Hardware parameters. */ - pHWParams = (ma_snd_pcm_hw_params_t*)ma__calloc_from_callbacks(((ma_snd_pcm_hw_params_sizeof_proc)pDevice->pContext->alsa.snd_pcm_hw_params_sizeof)(), &pDevice->pContext->allocationCallbacks); + pHWParams = (ma_snd_pcm_hw_params_t*)ma_calloc(((ma_snd_pcm_hw_params_sizeof_proc)pDevice->pContext->alsa.snd_pcm_hw_params_sizeof)(), &pDevice->pContext->allocationCallbacks); if (pHWParams == NULL) { + ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to allocate memory for hardware parameters."); return MA_OUT_OF_MEMORY; } resultALSA = ((ma_snd_pcm_hw_params_any_proc)pDevice->pContext->alsa.snd_pcm_hw_params_any)(pPCM, pHWParams); if (resultALSA < 0) { - ma__free_from_callbacks(pHWParams, &pDevice->pContext->allocationCallbacks); + ma_free(pHWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to initialize hardware parameters. snd_pcm_hw_params_any() failed.", ma_result_from_errno(-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to initialize hardware parameters. snd_pcm_hw_params_any() failed."); + return ma_result_from_errno(-resultALSA); } /* MMAP Mode. Try using interleaved MMAP access. If this fails, fall back to standard readi/writei. */ @@ -20739,9 +26242,10 @@ static ma_result ma_device_init_by_type__alsa(ma_device* pDevice, const ma_devic if (!isUsingMMap) { resultALSA = ((ma_snd_pcm_hw_params_set_access_proc)pDevice->pContext->alsa.snd_pcm_hw_params_set_access)(pPCM, pHWParams, MA_SND_PCM_ACCESS_RW_INTERLEAVED); if (resultALSA < 0) { - ma__free_from_callbacks(pHWParams, &pDevice->pContext->allocationCallbacks); + ma_free(pHWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set access mode to neither SND_PCM_ACCESS_MMAP_INTERLEAVED nor SND_PCM_ACCESS_RW_INTERLEAVED. snd_pcm_hw_params_set_access() failed.", ma_result_from_errno(-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set access mode to neither SND_PCM_ACCESS_MMAP_INTERLEAVED nor SND_PCM_ACCESS_RW_INTERLEAVED. snd_pcm_hw_params_set_access() failed."); + return ma_result_from_errno(-resultALSA); } } @@ -20769,24 +26273,27 @@ static ma_result ma_device_init_by_type__alsa(ma_device* pDevice, const ma_devic } if (formatALSA == MA_SND_PCM_FORMAT_UNKNOWN) { - ma__free_from_callbacks(pHWParams, &pDevice->pContext->allocationCallbacks); + ma_free(pHWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Format not supported. The device does not support any miniaudio formats.", MA_FORMAT_NOT_SUPPORTED); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Format not supported. The device does not support any miniaudio formats."); + return MA_FORMAT_NOT_SUPPORTED; } } resultALSA = ((ma_snd_pcm_hw_params_set_format_proc)pDevice->pContext->alsa.snd_pcm_hw_params_set_format)(pPCM, pHWParams, formatALSA); if (resultALSA < 0) { - ma__free_from_callbacks(pHWParams, &pDevice->pContext->allocationCallbacks); + ma_free(pHWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Format not supported. snd_pcm_hw_params_set_format() failed.", ma_result_from_errno(-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Format not supported. snd_pcm_hw_params_set_format() failed."); + return ma_result_from_errno(-resultALSA); } internalFormat = ma_format_from_alsa(formatALSA); if (internalFormat == ma_format_unknown) { - ma__free_from_callbacks(pHWParams, &pDevice->pContext->allocationCallbacks); + ma_free(pHWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] The chosen format is not supported by miniaudio.", MA_FORMAT_NOT_SUPPORTED); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] The chosen format is not supported by miniaudio."); + return MA_FORMAT_NOT_SUPPORTED; } } @@ -20799,9 +26306,10 @@ static ma_result ma_device_init_by_type__alsa(ma_device* pDevice, const ma_devic resultALSA = ((ma_snd_pcm_hw_params_set_channels_near_proc)pDevice->pContext->alsa.snd_pcm_hw_params_set_channels_near)(pPCM, pHWParams, &channels); if (resultALSA < 0) { - ma__free_from_callbacks(pHWParams, &pDevice->pContext->allocationCallbacks); + ma_free(pHWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set channel count. snd_pcm_hw_params_set_channels_near() failed.", ma_result_from_errno(-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set channel count. snd_pcm_hw_params_set_channels_near() failed."); + return ma_result_from_errno(-resultALSA); } internalChannels = (ma_uint32)channels; @@ -20837,9 +26345,10 @@ static ma_result ma_device_init_by_type__alsa(ma_device* pDevice, const ma_devic resultALSA = ((ma_snd_pcm_hw_params_set_rate_near_proc)pDevice->pContext->alsa.snd_pcm_hw_params_set_rate_near)(pPCM, pHWParams, &sampleRate, 0); if (resultALSA < 0) { - ma__free_from_callbacks(pHWParams, &pDevice->pContext->allocationCallbacks); + ma_free(pHWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Sample rate not supported. snd_pcm_hw_params_set_rate_near() failed.", ma_result_from_errno(-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Sample rate not supported. snd_pcm_hw_params_set_rate_near() failed."); + return ma_result_from_errno(-resultALSA); } internalSampleRate = (ma_uint32)sampleRate; @@ -20851,9 +26360,10 @@ static ma_result ma_device_init_by_type__alsa(ma_device* pDevice, const ma_devic resultALSA = ((ma_snd_pcm_hw_params_set_periods_near_proc)pDevice->pContext->alsa.snd_pcm_hw_params_set_periods_near)(pPCM, pHWParams, &periods, NULL); if (resultALSA < 0) { - ma__free_from_callbacks(pHWParams, &pDevice->pContext->allocationCallbacks); + ma_free(pHWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set period count. snd_pcm_hw_params_set_periods_near() failed.", ma_result_from_errno(-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set period count. snd_pcm_hw_params_set_periods_near() failed."); + return ma_result_from_errno(-resultALSA); } internalPeriods = periods; @@ -20865,9 +26375,10 @@ static ma_result ma_device_init_by_type__alsa(ma_device* pDevice, const ma_devic resultALSA = ((ma_snd_pcm_hw_params_set_buffer_size_near_proc)pDevice->pContext->alsa.snd_pcm_hw_params_set_buffer_size_near)(pPCM, pHWParams, &actualBufferSizeInFrames); if (resultALSA < 0) { - ma__free_from_callbacks(pHWParams, &pDevice->pContext->allocationCallbacks); + ma_free(pHWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set buffer size for device. snd_pcm_hw_params_set_buffer_size() failed.", ma_result_from_errno(-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set buffer size for device. snd_pcm_hw_params_set_buffer_size() failed."); + return ma_result_from_errno(-resultALSA); } internalPeriodSizeInFrames = actualBufferSizeInFrames / internalPeriods; @@ -20876,34 +26387,38 @@ static ma_result ma_device_init_by_type__alsa(ma_device* pDevice, const ma_devic /* Apply hardware parameters. */ resultALSA = ((ma_snd_pcm_hw_params_proc)pDevice->pContext->alsa.snd_pcm_hw_params)(pPCM, pHWParams); if (resultALSA < 0) { - ma__free_from_callbacks(pHWParams, &pDevice->pContext->allocationCallbacks); + ma_free(pHWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set hardware parameters. snd_pcm_hw_params() failed.", ma_result_from_errno(-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set hardware parameters. snd_pcm_hw_params() failed."); + return ma_result_from_errno(-resultALSA); } - ma__free_from_callbacks(pHWParams, &pDevice->pContext->allocationCallbacks); + ma_free(pHWParams, &pDevice->pContext->allocationCallbacks); pHWParams = NULL; /* Software parameters. */ - pSWParams = (ma_snd_pcm_sw_params_t*)ma__calloc_from_callbacks(((ma_snd_pcm_sw_params_sizeof_proc)pDevice->pContext->alsa.snd_pcm_sw_params_sizeof)(), &pDevice->pContext->allocationCallbacks); + pSWParams = (ma_snd_pcm_sw_params_t*)ma_calloc(((ma_snd_pcm_sw_params_sizeof_proc)pDevice->pContext->alsa.snd_pcm_sw_params_sizeof)(), &pDevice->pContext->allocationCallbacks); if (pSWParams == NULL) { ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to allocate memory for software parameters."); return MA_OUT_OF_MEMORY; } resultALSA = ((ma_snd_pcm_sw_params_current_proc)pDevice->pContext->alsa.snd_pcm_sw_params_current)(pPCM, pSWParams); if (resultALSA < 0) { - ma__free_from_callbacks(pSWParams, &pDevice->pContext->allocationCallbacks); + ma_free(pSWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to initialize software parameters. snd_pcm_sw_params_current() failed.", ma_result_from_errno(-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to initialize software parameters. snd_pcm_sw_params_current() failed."); + return ma_result_from_errno(-resultALSA); } resultALSA = ((ma_snd_pcm_sw_params_set_avail_min_proc)pDevice->pContext->alsa.snd_pcm_sw_params_set_avail_min)(pPCM, pSWParams, ma_prev_power_of_2(internalPeriodSizeInFrames)); if (resultALSA < 0) { - ma__free_from_callbacks(pSWParams, &pDevice->pContext->allocationCallbacks); + ma_free(pSWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] snd_pcm_sw_params_set_avail_min() failed.", ma_result_from_errno(-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] snd_pcm_sw_params_set_avail_min() failed."); + return ma_result_from_errno(-resultALSA); } resultALSA = ((ma_snd_pcm_sw_params_get_boundary_proc)pDevice->pContext->alsa.snd_pcm_sw_params_get_boundary)(pSWParams, &bufferBoundary); @@ -20918,27 +26433,30 @@ static ma_result ma_device_init_by_type__alsa(ma_device* pDevice, const ma_devic */ resultALSA = ((ma_snd_pcm_sw_params_set_start_threshold_proc)pDevice->pContext->alsa.snd_pcm_sw_params_set_start_threshold)(pPCM, pSWParams, internalPeriodSizeInFrames*2); if (resultALSA < 0) { - ma__free_from_callbacks(pSWParams, &pDevice->pContext->allocationCallbacks); + ma_free(pSWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set start threshold for playback device. snd_pcm_sw_params_set_start_threshold() failed.", ma_result_from_errno(-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set start threshold for playback device. snd_pcm_sw_params_set_start_threshold() failed."); + return ma_result_from_errno(-resultALSA); } resultALSA = ((ma_snd_pcm_sw_params_set_stop_threshold_proc)pDevice->pContext->alsa.snd_pcm_sw_params_set_stop_threshold)(pPCM, pSWParams, bufferBoundary); if (resultALSA < 0) { /* Set to boundary to loop instead of stop in the event of an xrun. */ - ma__free_from_callbacks(pSWParams, &pDevice->pContext->allocationCallbacks); + ma_free(pSWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set stop threshold for playback device. snd_pcm_sw_params_set_stop_threshold() failed.", ma_result_from_errno(-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set stop threshold for playback device. snd_pcm_sw_params_set_stop_threshold() failed."); + return ma_result_from_errno(-resultALSA); } } resultALSA = ((ma_snd_pcm_sw_params_proc)pDevice->pContext->alsa.snd_pcm_sw_params)(pPCM, pSWParams); if (resultALSA < 0) { - ma__free_from_callbacks(pSWParams, &pDevice->pContext->allocationCallbacks); + ma_free(pSWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set software parameters. snd_pcm_sw_params() failed.", ma_result_from_errno(-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set software parameters. snd_pcm_sw_params() failed."); + return ma_result_from_errno(-resultALSA); } - ma__free_from_callbacks(pSWParams, &pDevice->pContext->allocationCallbacks); + ma_free(pSWParams, &pDevice->pContext->allocationCallbacks); pSWParams = NULL; @@ -20964,7 +26482,7 @@ static ma_result ma_device_init_by_type__alsa(ma_device* pDevice, const ma_devic ma_bool32 isValid = MA_TRUE; /* Fill with defaults. */ - ma_get_standard_channel_map(ma_standard_channel_map_alsa, internalChannels, internalChannelMap); + ma_channel_map_init_standard(ma_standard_channel_map_alsa, internalChannelMap, ma_countof(internalChannelMap), internalChannels); /* Overwrite first pChmap->channels channels. */ for (iChannel = 0; iChannel < pChmap->channels; ++iChannel) { @@ -20984,7 +26502,7 @@ static ma_result ma_device_init_by_type__alsa(ma_device* pDevice, const ma_devic /* If our channel map is invalid, fall back to defaults. */ if (!isValid) { - ma_get_standard_channel_map(ma_standard_channel_map_alsa, internalChannels, internalChannelMap); + ma_channel_map_init_standard(ma_standard_channel_map_alsa, internalChannelMap, ma_countof(internalChannelMap), internalChannels); } } @@ -20992,7 +26510,7 @@ static ma_result ma_device_init_by_type__alsa(ma_device* pDevice, const ma_devic pChmap = NULL; } else { /* Could not retrieve the channel map. Fall back to a hard-coded assumption. */ - ma_get_standard_channel_map(ma_standard_channel_map_alsa, internalChannels, internalChannelMap); + ma_channel_map_init_standard(ma_standard_channel_map_alsa, internalChannelMap, ma_countof(internalChannelMap), internalChannels); } } @@ -21005,13 +26523,15 @@ static ma_result ma_device_init_by_type__alsa(ma_device* pDevice, const ma_devic pollDescriptorCount = ((ma_snd_pcm_poll_descriptors_count_proc)pDevice->pContext->alsa.snd_pcm_poll_descriptors_count)(pPCM); if (pollDescriptorCount <= 0) { ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to retrieve poll descriptors count.", MA_ERROR); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to retrieve poll descriptors count."); + return MA_ERROR; } - pPollDescriptors = (struct pollfd*)ma_malloc(sizeof(*pPollDescriptors) * (pollDescriptorCount + 1), &pDevice->pContext->allocationCallbacks/*, MA_ALLOCATION_TYPE_GENERAL*/); /* +1 because we want room for the wakeup descriptor. */ + pPollDescriptors = (struct pollfd*)ma_malloc(sizeof(*pPollDescriptors) * (pollDescriptorCount + 1), &pDevice->pContext->allocationCallbacks); /* +1 because we want room for the wakeup descriptor. */ if (pPollDescriptors == NULL) { ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to allocate memory for poll descriptors.", MA_OUT_OF_MEMORY); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to allocate memory for poll descriptors."); + return MA_OUT_OF_MEMORY; } /* @@ -21022,7 +26542,8 @@ static ma_result ma_device_init_by_type__alsa(ma_device* pDevice, const ma_devic if (wakeupfd < 0) { ma_free(pPollDescriptors, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to create eventfd for poll wakeup.", ma_result_from_errno(errno)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to create eventfd for poll wakeup."); + return ma_result_from_errno(errno); } /* We'll place the wakeup fd at the start of the buffer. */ @@ -21036,7 +26557,8 @@ static ma_result ma_device_init_by_type__alsa(ma_device* pDevice, const ma_devic close(wakeupfd); ma_free(pPollDescriptors, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to retrieve poll descriptors.", MA_ERROR); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to retrieve poll descriptors."); + return MA_ERROR; } if (deviceType == ma_device_type_capture) { @@ -21056,7 +26578,8 @@ static ma_result ma_device_init_by_type__alsa(ma_device* pDevice, const ma_devic close(wakeupfd); ma_free(pPollDescriptors, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to prepare device.", ma_result_from_errno(-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to prepare device."); + return ma_result_from_errno(-resultALSA); } @@ -21112,7 +26635,8 @@ static ma_result ma_device_start__alsa(ma_device* pDevice) if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { resultALSA = ((ma_snd_pcm_start_proc)pDevice->pContext->alsa.snd_pcm_start)((ma_snd_pcm_t*)pDevice->alsa.pPCMCapture); if (resultALSA < 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to start capture device.", ma_result_from_errno(-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to start capture device."); + return ma_result_from_errno(-resultALSA); } } @@ -21126,30 +26650,30 @@ static ma_result ma_device_start__alsa(ma_device* pDevice) static ma_result ma_device_stop__alsa(ma_device* pDevice) { if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Dropping capture device... "); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Dropping capture device...\n"); ((ma_snd_pcm_drop_proc)pDevice->pContext->alsa.snd_pcm_drop)((ma_snd_pcm_t*)pDevice->alsa.pPCMCapture); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "Done\n"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Dropping capture device successful.\n"); /* We need to prepare the device again, otherwise we won't be able to restart the device. */ - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Preparing capture device... "); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Preparing capture device...\n"); if (((ma_snd_pcm_prepare_proc)pDevice->pContext->alsa.snd_pcm_prepare)((ma_snd_pcm_t*)pDevice->alsa.pPCMCapture) < 0) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "Failed\n"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Preparing capture device failed.\n"); } else { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "Done\n"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Preparing capture device successful.\n"); } } if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Dropping playback device... "); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Dropping playback device...\n"); ((ma_snd_pcm_drop_proc)pDevice->pContext->alsa.snd_pcm_drop)((ma_snd_pcm_t*)pDevice->alsa.pPCMPlayback); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "Done\n"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Dropping playback device successful.\n"); /* We need to prepare the device again, otherwise we won't be able to restart the device. */ - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Preparing playback device... "); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Preparing playback device...\n"); if (((ma_snd_pcm_prepare_proc)pDevice->pContext->alsa.snd_pcm_prepare)((ma_snd_pcm_t*)pDevice->alsa.pPCMPlayback) < 0) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "Failed\n"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Preparing playback device failed.\n"); } else { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "Done\n"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Preparing playback device successful.\n"); } } @@ -21163,7 +26687,8 @@ static ma_result ma_device_wait__alsa(ma_device* pDevice, ma_snd_pcm_t* pPCM, st int resultALSA; int resultPoll = poll(pPollDescriptors, pollDescriptorCount, -1); if (resultPoll < 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] poll() failed.", ma_result_from_errno(errno)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] poll() failed."); + return ma_result_from_errno(errno); } /* @@ -21173,10 +26698,13 @@ static ma_result ma_device_wait__alsa(ma_device* pDevice, ma_snd_pcm_t* pPCM, st */ if ((pPollDescriptors[0].revents & POLLIN) != 0) { ma_uint64 t; - read(pPollDescriptors[0].fd, &t, sizeof(t)); /* <-- Important that we read here so that the next write() does not block. */ + int resultRead = read(pPollDescriptors[0].fd, &t, sizeof(t)); /* <-- Important that we read here so that the next write() does not block. */ + if (resultRead < 0) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] read() failed."); + return ma_result_from_errno(errno); + } ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] POLLIN set for wakeupfd\n"); - return MA_DEVICE_NOT_STARTED; } @@ -21186,11 +26714,13 @@ static ma_result ma_device_wait__alsa(ma_device* pDevice, ma_snd_pcm_t* pPCM, st */ resultALSA = ((ma_snd_pcm_poll_descriptors_revents_proc)pDevice->pContext->alsa.snd_pcm_poll_descriptors_revents)(pPCM, pPollDescriptors + 1, pollDescriptorCount - 1, &revents); /* +1, -1 to ignore the wakeup descriptor. */ if (resultALSA < 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] snd_pcm_poll_descriptors_revents() failed.", ma_result_from_errno(-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] snd_pcm_poll_descriptors_revents() failed."); + return ma_result_from_errno(-resultALSA); } if ((revents & POLLERR) != 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] POLLERR detected.", ma_result_from_errno(errno)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] POLLERR detected."); + return ma_result_from_errno(errno); } if ((revents & requiredEvent) == requiredEvent) { @@ -21213,7 +26743,7 @@ static ma_result ma_device_wait_write__alsa(ma_device* pDevice) static ma_result ma_device_read__alsa(ma_device* pDevice, void* pFramesOut, ma_uint32 frameCount, ma_uint32* pFramesRead) { - ma_snd_pcm_sframes_t resultALSA; + ma_snd_pcm_sframes_t resultALSA = 0; MA_ASSERT(pDevice != NULL); MA_ASSERT(pFramesOut != NULL); @@ -21222,7 +26752,7 @@ static ma_result ma_device_read__alsa(ma_device* pDevice, void* pFramesOut, ma_u *pFramesRead = 0; } - while (ma_device_get_state(pDevice) == MA_STATE_STARTED) { + while (ma_device_get_state(pDevice) == ma_device_state_started) { ma_result result; /* The first thing to do is wait for data to become available for reading. This will return an error code if the device has been stopped. */ @@ -21237,20 +26767,22 @@ static ma_result ma_device_read__alsa(ma_device* pDevice, void* pFramesOut, ma_u break; /* Success. */ } else { if (resultALSA == -EAGAIN) { - /*ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "TRACE: EGAIN (read)\n");*/ + /*ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "EGAIN (read)\n");*/ continue; /* Try again. */ } else if (resultALSA == -EPIPE) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "TRACE: EPIPE (read)\n"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "EPIPE (read)\n"); /* Overrun. Recover and try again. If this fails we need to return an error. */ resultALSA = ((ma_snd_pcm_recover_proc)pDevice->pContext->alsa.snd_pcm_recover)((ma_snd_pcm_t*)pDevice->alsa.pPCMCapture, resultALSA, MA_TRUE); if (resultALSA < 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to recover device after overrun.", ma_result_from_errno((int)-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to recover device after overrun."); + return ma_result_from_errno((int)-resultALSA); } resultALSA = ((ma_snd_pcm_start_proc)pDevice->pContext->alsa.snd_pcm_start)((ma_snd_pcm_t*)pDevice->alsa.pPCMCapture); if (resultALSA < 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to start device after underrun.", ma_result_from_errno((int)-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to start device after underrun."); + return ma_result_from_errno((int)-resultALSA); } continue; /* Try reading again. */ @@ -21267,7 +26799,7 @@ static ma_result ma_device_read__alsa(ma_device* pDevice, void* pFramesOut, ma_u static ma_result ma_device_write__alsa(ma_device* pDevice, const void* pFrames, ma_uint32 frameCount, ma_uint32* pFramesWritten) { - ma_snd_pcm_sframes_t resultALSA; + ma_snd_pcm_sframes_t resultALSA = 0; MA_ASSERT(pDevice != NULL); MA_ASSERT(pFrames != NULL); @@ -21276,7 +26808,7 @@ static ma_result ma_device_write__alsa(ma_device* pDevice, const void* pFrames, *pFramesWritten = 0; } - while (ma_device_get_state(pDevice) == MA_STATE_STARTED) { + while (ma_device_get_state(pDevice) == ma_device_state_started) { ma_result result; /* The first thing to do is wait for space to become available for writing. This will return an error code if the device has been stopped. */ @@ -21290,15 +26822,16 @@ static ma_result ma_device_write__alsa(ma_device* pDevice, const void* pFrames, break; /* Success. */ } else { if (resultALSA == -EAGAIN) { - /*ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "TRACE: EGAIN (write)\n");*/ + /*ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "EGAIN (write)\n");*/ continue; /* Try again. */ } else if (resultALSA == -EPIPE) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "TRACE: EPIPE (write)\n"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "EPIPE (write)\n"); /* Underrun. Recover and try again. If this fails we need to return an error. */ resultALSA = ((ma_snd_pcm_recover_proc)pDevice->pContext->alsa.snd_pcm_recover)((ma_snd_pcm_t*)pDevice->alsa.pPCMPlayback, resultALSA, MA_TRUE); /* MA_TRUE=silent (don't print anything on error). */ if (resultALSA < 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to recover device after underrun.", ma_result_from_errno((int)-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to recover device after underrun."); + return ma_result_from_errno((int)-resultALSA); } /* @@ -21310,7 +26843,8 @@ static ma_result ma_device_write__alsa(ma_device* pDevice, const void* pFrames, */ resultALSA = ((ma_snd_pcm_start_proc)pDevice->pContext->alsa.snd_pcm_start)((ma_snd_pcm_t*)pDevice->alsa.pPCMPlayback); if (resultALSA < 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to start device after underrun.", ma_result_from_errno((int)-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to start device after underrun."); + return ma_result_from_errno((int)-resultALSA); } continue; /* Try writing again. */ @@ -21328,20 +26862,26 @@ static ma_result ma_device_write__alsa(ma_device* pDevice, const void* pFrames, static ma_result ma_device_data_loop_wakeup__alsa(ma_device* pDevice) { ma_uint64 t = 1; + int resultWrite = 0; MA_ASSERT(pDevice != NULL); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Waking up... "); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Waking up...\n"); /* Write to an eventfd to trigger a wakeup from poll() and abort any reading or writing. */ if (pDevice->alsa.pPollDescriptorsCapture != NULL) { - write(pDevice->alsa.wakeupfdCapture, &t, sizeof(t)); + resultWrite = write(pDevice->alsa.wakeupfdCapture, &t, sizeof(t)); } if (pDevice->alsa.pPollDescriptorsPlayback != NULL) { - write(pDevice->alsa.wakeupfdPlayback, &t, sizeof(t)); + resultWrite = write(pDevice->alsa.wakeupfdPlayback, &t, sizeof(t)); } - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "Done\n"); + if (resultWrite < 0) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] write() failed.\n"); + return ma_result_from_errno(errno); + } + + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Waking up completed successfully.\n"); return MA_SUCCESS; } @@ -21365,6 +26905,7 @@ static ma_result ma_context_uninit__alsa(ma_context* pContext) static ma_result ma_context_init__alsa(ma_context* pContext, const ma_context_config* pConfig, ma_backend_callbacks* pCallbacks) { + ma_result result; #ifndef MA_NO_RUNTIME_LINKING const char* libasoundNames[] = { "libasound.so.2", @@ -21589,8 +27130,10 @@ static ma_result ma_context_init__alsa(ma_context* pContext, const ma_context_co pContext->alsa.useVerboseDeviceEnumeration = pConfig->alsa.useVerboseDeviceEnumeration; - if (ma_mutex_init(&pContext->alsa.internalDeviceEnumLock) != MA_SUCCESS) { - ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[ALSA] WARNING: Failed to initialize mutex for internal device enumeration.", MA_ERROR); + result = ma_mutex_init(&pContext->alsa.internalDeviceEnumLock); + if (result != MA_SUCCESS) { + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] WARNING: Failed to initialize mutex for internal device enumeration."); + return result; } pCallbacks->onContextInit = ma_context_init__alsa; @@ -21692,7 +27235,7 @@ that point (it may still need to load files or whatnot). Instead, this callback stream be started which is how it works with literally *every* other callback-based audio API. Since miniaudio forbids firing of the data callback until the device has been started (as it should be with *all* callback based APIs), logic needs to be added to ensure miniaudio doesn't just blindly fire the application-defined data callback from within the PulseAudio callback before the stream has actually been -started. The device state is used for this - if the state is anything other than `MA_STATE_STARTING` or `MA_STATE_STARTED`, the main data +started. The device state is used for this - if the state is anything other than `ma_device_state_starting` or `ma_device_state_started`, the main data callback is not fired. This, unfortunately, is not the end of the problems with the PulseAudio write callback. Any normal callback based audio API will @@ -22288,6 +27831,7 @@ typedef const char* (* ma_pa_stream_get_device_name_proc) ( typedef void (* ma_pa_stream_set_write_callback_proc) (ma_pa_stream* s, ma_pa_stream_request_cb_t cb, void* userdata); typedef void (* ma_pa_stream_set_read_callback_proc) (ma_pa_stream* s, ma_pa_stream_request_cb_t cb, void* userdata); typedef void (* ma_pa_stream_set_suspended_callback_proc) (ma_pa_stream* s, ma_pa_stream_notify_cb_t cb, void* userdata); +typedef void (* ma_pa_stream_set_moved_callback_proc) (ma_pa_stream* s, ma_pa_stream_notify_cb_t cb, void* userdata); typedef int (* ma_pa_stream_is_suspended_proc) (const ma_pa_stream* s); typedef ma_pa_operation* (* ma_pa_stream_flush_proc) (ma_pa_stream* s, ma_pa_stream_success_cb_t cb, void* userdata); typedef ma_pa_operation* (* ma_pa_stream_drain_proc) (ma_pa_stream* s, ma_pa_stream_success_cb_t cb, void* userdata); @@ -22482,7 +28026,7 @@ static ma_pa_channel_position_t ma_channel_position_to_pulse(ma_channel position } #endif -static ma_result ma_wait_for_operation__pulse(ma_context* pContext, ma_pa_operation* pOP) +static ma_result ma_wait_for_operation__pulse(ma_context* pContext, ma_ptr pMainLoop, ma_pa_operation* pOP) { int resultPA; ma_pa_operation_state_t state; @@ -22496,7 +28040,7 @@ static ma_result ma_wait_for_operation__pulse(ma_context* pContext, ma_pa_operat break; /* Done. */ } - resultPA = ((ma_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pContext->pulse.pMainLoop, 1, NULL); + resultPA = ((ma_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pMainLoop, 1, NULL); if (resultPA < 0) { return ma_result_from_pulse(resultPA); } @@ -22505,7 +28049,7 @@ static ma_result ma_wait_for_operation__pulse(ma_context* pContext, ma_pa_operat return MA_SUCCESS; } -static ma_result ma_wait_for_operation_and_unref__pulse(ma_context* pContext, ma_pa_operation* pOP) +static ma_result ma_wait_for_operation_and_unref__pulse(ma_context* pContext, ma_ptr pMainLoop, ma_pa_operation* pOP) { ma_result result; @@ -22513,28 +28057,29 @@ static ma_result ma_wait_for_operation_and_unref__pulse(ma_context* pContext, ma return MA_INVALID_ARGS; } - result = ma_wait_for_operation__pulse(pContext, pOP); + result = ma_wait_for_operation__pulse(pContext, pMainLoop, pOP); ((ma_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); return result; } -static ma_result ma_context_wait_for_pa_context_to_connect__pulse(ma_context* pContext) +static ma_result ma_wait_for_pa_context_to_connect__pulse(ma_context* pContext, ma_ptr pMainLoop, ma_ptr pPulseContext) { int resultPA; ma_pa_context_state_t state; for (;;) { - state = ((ma_pa_context_get_state_proc)pContext->pulse.pa_context_get_state)((ma_pa_context*)pContext->pulse.pPulseContext); + state = ((ma_pa_context_get_state_proc)pContext->pulse.pa_context_get_state)((ma_pa_context*)pPulseContext); if (state == MA_PA_CONTEXT_READY) { break; /* Done. */ } if (state == MA_PA_CONTEXT_FAILED || state == MA_PA_CONTEXT_TERMINATED) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[PulseAudio] An error occurred while connecting the PulseAudio context.", MA_ERROR); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[PulseAudio] An error occurred while connecting the PulseAudio context."); + return MA_ERROR; } - resultPA = ((ma_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pContext->pulse.pMainLoop, 1, NULL); + resultPA = ((ma_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pMainLoop, 1, NULL); if (resultPA < 0) { return ma_result_from_pulse(resultPA); } @@ -22544,22 +28089,23 @@ static ma_result ma_context_wait_for_pa_context_to_connect__pulse(ma_context* pC return MA_SUCCESS; } -static ma_result ma_context_wait_for_pa_stream_to_connect__pulse(ma_context* pContext, ma_pa_stream* pStream) +static ma_result ma_wait_for_pa_stream_to_connect__pulse(ma_context* pContext, ma_ptr pMainLoop, ma_ptr pStream) { int resultPA; ma_pa_stream_state_t state; for (;;) { - state = ((ma_pa_stream_get_state_proc)pContext->pulse.pa_stream_get_state)(pStream); + state = ((ma_pa_stream_get_state_proc)pContext->pulse.pa_stream_get_state)((ma_pa_stream*)pStream); if (state == MA_PA_STREAM_READY) { break; /* Done. */ } if (state == MA_PA_STREAM_FAILED || state == MA_PA_STREAM_TERMINATED) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[PulseAudio] An error occurred while connecting the PulseAudio stream.", MA_ERROR); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[PulseAudio] An error occurred while connecting the PulseAudio stream."); + return MA_ERROR; } - resultPA = ((ma_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pContext->pulse.pMainLoop, 1, NULL); + resultPA = ((ma_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pMainLoop, 1, NULL); if (resultPA < 0) { return ma_result_from_pulse(resultPA); } @@ -22569,6 +28115,52 @@ static ma_result ma_context_wait_for_pa_stream_to_connect__pulse(ma_context* pCo } +static ma_result ma_init_pa_mainloop_and_pa_context__pulse(ma_context* pContext, const char* pApplicationName, const char* pServerName, ma_bool32 tryAutoSpawn, ma_ptr* ppMainLoop, ma_ptr* ppPulseContext) +{ + ma_result result; + ma_ptr pMainLoop; + ma_ptr pPulseContext; + + MA_ASSERT(ppMainLoop != NULL); + MA_ASSERT(ppPulseContext != NULL); + + /* The PulseAudio context maps well to miniaudio's notion of a context. The pa_context object will be initialized as part of the ma_context. */ + pMainLoop = ((ma_pa_mainloop_new_proc)pContext->pulse.pa_mainloop_new)(); + if (pMainLoop == NULL) { + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create mainloop."); + return MA_FAILED_TO_INIT_BACKEND; + } + + pPulseContext = ((ma_pa_context_new_proc)pContext->pulse.pa_context_new)(((ma_pa_mainloop_get_api_proc)pContext->pulse.pa_mainloop_get_api)((ma_pa_mainloop*)pMainLoop), pApplicationName); + if (pPulseContext == NULL) { + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create PulseAudio context."); + ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)((ma_pa_mainloop*)(pMainLoop)); + return MA_FAILED_TO_INIT_BACKEND; + } + + /* Now we need to connect to the context. Everything is asynchronous so we need to wait for it to connect before returning. */ + result = ma_result_from_pulse(((ma_pa_context_connect_proc)pContext->pulse.pa_context_connect)((ma_pa_context*)pPulseContext, pServerName, (tryAutoSpawn) ? 0 : MA_PA_CONTEXT_NOAUTOSPAWN, NULL)); + if (result != MA_SUCCESS) { + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to connect PulseAudio context."); + ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)((ma_pa_mainloop*)(pMainLoop)); + return result; + } + + /* Since ma_context_init() runs synchronously we need to wait for the PulseAudio context to connect before we return. */ + result = ma_wait_for_pa_context_to_connect__pulse(pContext, pMainLoop, pPulseContext); + if (result != MA_SUCCESS) { + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[PulseAudio] Waiting for connection failed."); + ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)((ma_pa_mainloop*)(pMainLoop)); + return result; + } + + *ppMainLoop = pMainLoop; + *ppPulseContext = pPulseContext; + + return MA_SUCCESS; +} + + static void ma_device_sink_info_callback(ma_pa_context* pPulseContext, const ma_pa_sink_info* pInfo, int endOfList, void* pUserData) { ma_pa_sink_info* pInfoOut; @@ -22601,6 +28193,7 @@ static void ma_device_source_info_callback(ma_pa_context* pPulseContext, const m (void)pPulseContext; /* Unused. */ } +#if 0 static void ma_device_sink_name_callback(ma_pa_context* pPulseContext, const ma_pa_sink_info* pInfo, int endOfList, void* pUserData) { ma_device* pDevice; @@ -22632,7 +28225,7 @@ static void ma_device_source_name_callback(ma_pa_context* pPulseContext, const m (void)pPulseContext; /* Unused. */ } - +#endif static ma_result ma_context_get_sink_info__pulse(ma_context* pContext, const char* pDeviceName, ma_pa_sink_info* pSinkInfo) { @@ -22643,7 +28236,7 @@ static ma_result ma_context_get_sink_info__pulse(ma_context* pContext, const cha return MA_ERROR; } - return ma_wait_for_operation_and_unref__pulse(pContext, pOP); + return ma_wait_for_operation_and_unref__pulse(pContext, pContext->pulse.pMainLoop, pOP); } static ma_result ma_context_get_source_info__pulse(ma_context* pContext, const char* pDeviceName, ma_pa_source_info* pSourceInfo) @@ -22655,7 +28248,7 @@ static ma_result ma_context_get_source_info__pulse(ma_context* pContext, const c return MA_ERROR; } - return ma_wait_for_operation_and_unref__pulse(pContext, pOP);; + return ma_wait_for_operation_and_unref__pulse(pContext, pContext->pulse.pMainLoop, pOP); } static ma_result ma_context_get_default_device_index__pulse(ma_context* pContext, ma_device_type deviceType, ma_uint32* pIndex) @@ -22799,7 +28392,7 @@ static ma_result ma_context_enumerate_devices__pulse(ma_context* pContext, ma_en goto done; } - result = ma_wait_for_operation__pulse(pContext, pOP); + result = ma_wait_for_operation__pulse(pContext, pContext->pulse.pMainLoop, pOP); ((ma_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); if (result != MA_SUCCESS) { @@ -22816,7 +28409,7 @@ static ma_result ma_context_enumerate_devices__pulse(ma_context* pContext, ma_en goto done; } - result = ma_wait_for_operation__pulse(pContext, pOP); + result = ma_wait_for_operation__pulse(pContext, pContext->pulse.pMainLoop, pOP); ((ma_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); if (result != MA_SUCCESS) { @@ -22937,7 +28530,7 @@ static ma_result ma_context_get_device_info__pulse(ma_context* pContext, ma_devi } if (pOP != NULL) { - ma_wait_for_operation_and_unref__pulse(pContext, pOP); + ma_wait_for_operation_and_unref__pulse(pContext, pContext->pulse.pMainLoop, pOP); } else { result = MA_ERROR; goto done; @@ -22975,6 +28568,10 @@ static ma_result ma_device_uninit__pulse(ma_device* pDevice) ma_duplex_rb_uninit(&pDevice->duplexRB); } + ((ma_pa_context_disconnect_proc)pContext->pulse.pa_context_disconnect)((ma_pa_context*)pDevice->pulse.pPulseContext); + ((ma_pa_context_unref_proc)pContext->pulse.pa_context_unref)((ma_pa_context*)pDevice->pulse.pPulseContext); + ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)((ma_pa_mainloop*)pDevice->pulse.pMainLoop); + return MA_SUCCESS; } @@ -22990,7 +28587,7 @@ static ma_pa_buffer_attr ma_device__pa_buffer_attr_new(ma_uint32 periodSizeInFra return attr; } -static ma_pa_stream* ma_context__pa_stream_new__pulse(ma_context* pContext, const char* pStreamName, const ma_pa_sample_spec* ss, const ma_pa_channel_map* cmap) +static ma_pa_stream* ma_device__pa_stream_new__pulse(ma_device* pDevice, const char* pStreamName, const ma_pa_sample_spec* ss, const ma_pa_channel_map* cmap) { static int g_StreamCounter = 0; char actualStreamName[256]; @@ -23003,7 +28600,7 @@ static ma_pa_stream* ma_context__pa_stream_new__pulse(ma_context* pContext, cons } g_StreamCounter += 1; - return ((ma_pa_stream_new_proc)pContext->pulse.pa_stream_new)((ma_pa_context*)pContext->pulse.pPulseContext, actualStreamName, ss, cmap); + return ((ma_pa_stream_new_proc)pDevice->pContext->pulse.pa_stream_new)((ma_pa_context*)pDevice->pulse.pPulseContext, actualStreamName, ss, cmap); } @@ -23022,7 +28619,7 @@ static void ma_device_on_read__pulse(ma_pa_stream* pStream, size_t byteCount, vo can fire this callback before the stream has even started. Ridiculous. */ deviceState = ma_device_get_state(pDevice); - if (deviceState != MA_STATE_STARTING && deviceState != MA_STATE_STARTED) { + if (deviceState != ma_device_state_starting && deviceState != ma_device_state_started) { return; } @@ -23032,7 +28629,7 @@ static void ma_device_on_read__pulse(ma_pa_stream* pStream, size_t byteCount, vo frameCount = byteCount / bpf; framesProcessed = 0; - while (ma_device_get_state(pDevice) == MA_STATE_STARTED && framesProcessed < frameCount) { + while (ma_device_get_state(pDevice) == ma_device_state_started && framesProcessed < frameCount) { const void* pMappedPCMFrames; size_t bytesMapped; ma_uint64 framesMapped; @@ -23094,7 +28691,7 @@ static ma_result ma_device_write_to_stream__pulse(ma_device* pDevice, ma_pa_stre framesMapped = bytesMapped / bpf; - if (deviceState == MA_STATE_STARTED || deviceState == MA_STATE_STARTING) { /* Check for starting state just in case this is being used to do the initial fill. */ + if (deviceState == ma_device_state_started || deviceState == ma_device_state_starting) { /* Check for starting state just in case this is being used to do the initial fill. */ ma_device_handle_backend_data_callback(pDevice, pMappedPCMFrames, NULL, framesMapped); } else { /* Device is not started. Write silence. */ @@ -23109,7 +28706,7 @@ static ma_result ma_device_write_to_stream__pulse(ma_device* pDevice, ma_pa_stre framesProcessed += framesMapped; } else { - result = MA_ERROR; /* No data available. Abort. */ + result = MA_SUCCESS; /* No data available for writing. */ goto done; } } else { @@ -23141,7 +28738,7 @@ static void ma_device_on_write__pulse(ma_pa_stream* pStream, size_t byteCount, v can fire this callback before the stream has even started. Ridiculous. */ deviceState = ma_device_get_state(pDevice); - if (deviceState != MA_STATE_STARTING && deviceState != MA_STATE_STARTED) { + if (deviceState != ma_device_state_starting && deviceState != ma_device_state_started) { return; } @@ -23156,7 +28753,7 @@ static void ma_device_on_write__pulse(ma_pa_stream* pStream, size_t byteCount, v /* Don't keep trying to process frames if the device isn't started. */ deviceState = ma_device_get_state(pDevice); - if (deviceState != MA_STATE_STARTING && deviceState != MA_STATE_STARTED) { + if (deviceState != ma_device_state_starting && deviceState != ma_device_state_started) { break; } @@ -23185,13 +28782,47 @@ static void ma_device_on_suspended__pulse(ma_pa_stream* pStream, void* pUserData if (suspended == 1) { ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[Pulse] Device suspended state changed. Suspended.\n"); - - if (pDevice->onStop) { - pDevice->onStop(pDevice); - } + ma_device__on_notification_stopped(pDevice); } else { ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[Pulse] Device suspended state changed. Resumed.\n"); - } + ma_device__on_notification_started(pDevice); + } +} + +static void ma_device_on_rerouted__pulse(ma_pa_stream* pStream, void* pUserData) +{ + ma_device* pDevice = (ma_device*)pUserData; + + (void)pStream; + (void)pUserData; + + ma_device__on_notification_rerouted(pDevice); +} + +static ma_uint32 ma_calculate_period_size_in_frames_from_descriptor__pulse(const ma_device_descriptor* pDescriptor, ma_uint32 nativeSampleRate, ma_performance_profile performanceProfile) +{ + /* + There have been reports from users where buffers of < ~20ms result glitches when running through + PipeWire. To work around this we're going to have to use a different default buffer size. + */ + const ma_uint32 defaultPeriodSizeInMilliseconds_LowLatency = 25; + const ma_uint32 defaultPeriodSizeInMilliseconds_Conservative = MA_DEFAULT_PERIOD_SIZE_IN_MILLISECONDS_CONSERVATIVE; + + MA_ASSERT(nativeSampleRate != 0); + + if (pDescriptor->periodSizeInFrames == 0) { + if (pDescriptor->periodSizeInMilliseconds == 0) { + if (performanceProfile == ma_performance_profile_low_latency) { + return ma_calculate_buffer_size_in_frames_from_milliseconds(defaultPeriodSizeInMilliseconds_LowLatency, nativeSampleRate); + } else { + return ma_calculate_buffer_size_in_frames_from_milliseconds(defaultPeriodSizeInMilliseconds_Conservative, nativeSampleRate); + } + } else { + return ma_calculate_buffer_size_in_frames_from_milliseconds(pDescriptor->periodSizeInMilliseconds, nativeSampleRate); + } + } else { + return pDescriptor->periodSizeInFrames; + } } static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture) @@ -23263,10 +28894,18 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi sampleRate = pDescriptorCapture->sampleRate; } + + + result = ma_init_pa_mainloop_and_pa_context__pulse(pDevice->pContext, pDevice->pContext->pulse.pApplicationName, pDevice->pContext->pulse.pServerName, MA_FALSE, &pDevice->pulse.pMainLoop, &pDevice->pulse.pPulseContext); + if (result != MA_SUCCESS) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to initialize PA mainloop and context for device.\n"); + return result; + } + if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { result = ma_context_get_source_info__pulse(pDevice->pContext, devCapture, &sourceInfo); if (result != MA_SUCCESS) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to retrieve source info for capture device.", result); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to retrieve source info for capture device."); goto on_error0; } @@ -23279,26 +28918,27 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi } else { ss.format = MA_PA_SAMPLE_FLOAT32BE; } - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[PulseAudio] WARNING: sample_spec.format not supported by miniaudio. Defaulting to PA_SAMPLE_RATE_FLOAT32\n"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] sample_spec.format not supported by miniaudio. Defaulting to PA_SAMPLE_FLOAT32.\n"); } if (ss.rate == 0) { ss.rate = MA_DEFAULT_SAMPLE_RATE; - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[PulseAudio] WARNING: sample_spec.rate = 0. Defaulting to %d\n", ss.rate); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] sample_spec.rate = 0. Defaulting to %d.\n", ss.rate); } if (ss.channels == 0) { ss.channels = MA_DEFAULT_CHANNELS; - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[PulseAudio] WARNING: sample_spec.channels = 0. Defaulting to %d\n", ss.channels); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] sample_spec.channels = 0. Defaulting to %d.\n", ss.channels); } /* We now have enough information to calculate our actual period size in frames. */ - pDescriptorCapture->periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptorCapture, ss.rate, pConfig->performanceProfile); + pDescriptorCapture->periodSizeInFrames = ma_calculate_period_size_in_frames_from_descriptor__pulse(pDescriptorCapture, ss.rate, pConfig->performanceProfile); attr = ma_device__pa_buffer_attr_new(pDescriptorCapture->periodSizeInFrames, pDescriptorCapture->periodCount, &ss); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[PulseAudio] Capture attr: maxlength=%d, tlength=%d, prebuf=%d, minreq=%d, fragsize=%d; periodSizeInFrames=%d\n", attr.maxlength, attr.tlength, attr.prebuf, attr.minreq, attr.fragsize, pDescriptorCapture->periodSizeInFrames); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] Capture attr: maxlength=%d, tlength=%d, prebuf=%d, minreq=%d, fragsize=%d; periodSizeInFrames=%d\n", attr.maxlength, attr.tlength, attr.prebuf, attr.minreq, attr.fragsize, pDescriptorCapture->periodSizeInFrames); - pDevice->pulse.pStreamCapture = ma_context__pa_stream_new__pulse(pDevice->pContext, pConfig->pulse.pStreamNameCapture, &ss, &cmap); + pDevice->pulse.pStreamCapture = ma_device__pa_stream_new__pulse(pDevice, pConfig->pulse.pStreamNameCapture, &ss, &cmap); if (pDevice->pulse.pStreamCapture == NULL) { - result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create PulseAudio capture stream.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create PulseAudio capture stream.\n"); + result = MA_ERROR; goto on_error0; } @@ -23309,6 +28949,9 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi /* State callback for checking when the device has been corked. */ ((ma_pa_stream_set_suspended_callback_proc)pDevice->pContext->pulse.pa_stream_set_suspended_callback)((ma_pa_stream*)pDevice->pulse.pStreamCapture, ma_device_on_suspended__pulse, pDevice); + /* Rerouting notification. */ + ((ma_pa_stream_set_moved_callback_proc)pDevice->pContext->pulse.pa_stream_set_moved_callback)((ma_pa_stream*)pDevice->pulse.pStreamCapture, ma_device_on_rerouted__pulse, pDevice); + /* Connect after we've got all of our internal state set up. */ streamFlags = MA_PA_STREAM_START_CORKED | MA_PA_STREAM_ADJUST_LATENCY | MA_PA_STREAM_FIX_FORMAT | MA_PA_STREAM_FIX_RATE | MA_PA_STREAM_FIX_CHANNELS; @@ -23318,33 +28961,64 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi error = ((ma_pa_stream_connect_record_proc)pDevice->pContext->pulse.pa_stream_connect_record)((ma_pa_stream*)pDevice->pulse.pStreamCapture, devCapture, &attr, streamFlags); if (error != MA_PA_OK) { - result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to connect PulseAudio capture stream.", ma_result_from_pulse(error)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to connect PulseAudio capture stream."); + result = ma_result_from_pulse(error); goto on_error1; } - result = ma_context_wait_for_pa_stream_to_connect__pulse(pDevice->pContext, (ma_pa_stream*)pDevice->pulse.pStreamCapture); + result = ma_wait_for_pa_stream_to_connect__pulse(pDevice->pContext, pDevice->pulse.pMainLoop, (ma_pa_stream*)pDevice->pulse.pStreamCapture); if (result != MA_SUCCESS) { goto on_error2; } + /* Internal format. */ pActualSS = ((ma_pa_stream_get_sample_spec_proc)pDevice->pContext->pulse.pa_stream_get_sample_spec)((ma_pa_stream*)pDevice->pulse.pStreamCapture); if (pActualSS != NULL) { ss = *pActualSS; + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] Capture sample spec: format=%s, channels=%d, rate=%d\n", ma_get_format_name(ma_format_from_pulse(ss.format)), ss.channels, ss.rate); + } else { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] Failed to retrieve capture sample spec.\n"); } pDescriptorCapture->format = ma_format_from_pulse(ss.format); pDescriptorCapture->channels = ss.channels; pDescriptorCapture->sampleRate = ss.rate; - /* Internal channel map. */ - pActualCMap = ((ma_pa_stream_get_channel_map_proc)pDevice->pContext->pulse.pa_stream_get_channel_map)((ma_pa_stream*)pDevice->pulse.pStreamCapture); - if (pActualCMap != NULL) { - cmap = *pActualCMap; + if (pDescriptorCapture->format == ma_format_unknown || pDescriptorCapture->channels == 0 || pDescriptorCapture->sampleRate == 0) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Capture sample spec is invalid. Device unusable by miniaudio. format=%s, channels=%d, sampleRate=%d.\n", ma_get_format_name(pDescriptorCapture->format), pDescriptorCapture->channels, pDescriptorCapture->sampleRate); + result = MA_ERROR; + goto on_error4; } - for (iChannel = 0; iChannel < pDescriptorCapture->channels; ++iChannel) { - pDescriptorCapture->channelMap[iChannel] = ma_channel_position_from_pulse(cmap.map[iChannel]); + /* Internal channel map. */ + + /* + Bug in PipeWire. There have been reports that PipeWire is returning AUX channels when reporting + the channel map. To somewhat workaround this, I'm hacking in a hard coded channel map for mono + and stereo. In this case it should be safe to assume mono = MONO and stereo = LEFT/RIGHT. For + all other channel counts we need to just put up with whatever PipeWire reports and hope it gets + fixed sooner than later. I might remove this hack later. + */ + if (pDescriptorCapture->channels > 2) { + pActualCMap = ((ma_pa_stream_get_channel_map_proc)pDevice->pContext->pulse.pa_stream_get_channel_map)((ma_pa_stream*)pDevice->pulse.pStreamCapture); + if (pActualCMap != NULL) { + cmap = *pActualCMap; + } + + for (iChannel = 0; iChannel < pDescriptorCapture->channels; ++iChannel) { + pDescriptorCapture->channelMap[iChannel] = ma_channel_position_from_pulse(cmap.map[iChannel]); + } + } else { + /* Hack for mono and stereo. */ + if (pDescriptorCapture->channels == 1) { + pDescriptorCapture->channelMap[0] = MA_CHANNEL_MONO; + } else if (pDescriptorCapture->channels == 2) { + pDescriptorCapture->channelMap[0] = MA_CHANNEL_FRONT_LEFT; + pDescriptorCapture->channelMap[1] = MA_CHANNEL_FRONT_RIGHT; + } else { + MA_ASSERT(MA_FALSE); /* Should never hit this. */ + } } @@ -23354,24 +29028,20 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi attr = *pActualAttr; } - pDescriptorCapture->periodCount = attr.maxlength / attr.fragsize; - pDescriptorCapture->periodSizeInFrames = attr.maxlength / ma_get_bytes_per_frame(pDescriptorCapture->format, pDescriptorCapture->channels) / pDescriptorCapture->periodCount; - - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[PulseAudio] Capture actual attr: maxlength=%d, tlength=%d, prebuf=%d, minreq=%d, fragsize=%d; periodSizeInFrames=%d\n", attr.maxlength, attr.tlength, attr.prebuf, attr.minreq, attr.fragsize, pDescriptorCapture->periodSizeInFrames); - - - /* Name. */ - devCapture = ((ma_pa_stream_get_device_name_proc)pDevice->pContext->pulse.pa_stream_get_device_name)((ma_pa_stream*)pDevice->pulse.pStreamCapture); - if (devCapture != NULL) { - ma_pa_operation* pOP = ((ma_pa_context_get_source_info_by_name_proc)pDevice->pContext->pulse.pa_context_get_source_info_by_name)((ma_pa_context*)pDevice->pContext->pulse.pPulseContext, devCapture, ma_device_source_name_callback, pDevice); - ma_wait_for_operation_and_unref__pulse(pDevice->pContext, pOP); + if (attr.fragsize > 0) { + pDescriptorCapture->periodCount = ma_max(attr.maxlength / attr.fragsize, 1); + } else { + pDescriptorCapture->periodCount = 1; } + + pDescriptorCapture->periodSizeInFrames = attr.maxlength / ma_get_bytes_per_frame(pDescriptorCapture->format, pDescriptorCapture->channels) / pDescriptorCapture->periodCount; + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] Capture actual attr: maxlength=%d, tlength=%d, prebuf=%d, minreq=%d, fragsize=%d; periodSizeInFrames=%d\n", attr.maxlength, attr.tlength, attr.prebuf, attr.minreq, attr.fragsize, pDescriptorCapture->periodSizeInFrames); } if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { result = ma_context_get_sink_info__pulse(pDevice->pContext, devPlayback, &sinkInfo); if (result != MA_SUCCESS) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to retrieve sink info for playback device.", result); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to retrieve sink info for playback device.\n"); goto on_error2; } @@ -23384,40 +29054,44 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi } else { ss.format = MA_PA_SAMPLE_FLOAT32BE; } - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[PulseAudio] WARNING: sample_spec.format not supported by miniaudio. Defaulting to PA_SAMPLE_RATE_FLOAT32\n"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] sample_spec.format not supported by miniaudio. Defaulting to PA_SAMPLE_FLOAT32.\n"); } if (ss.rate == 0) { ss.rate = MA_DEFAULT_SAMPLE_RATE; - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[PulseAudio] WARNING: sample_spec.rate = 0. Defaulting to %d\n", ss.rate); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] sample_spec.rate = 0. Defaulting to %d.\n", ss.rate); } if (ss.channels == 0) { ss.channels = MA_DEFAULT_CHANNELS; - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[PulseAudio] WARNING: sample_spec.channels = 0. Defaulting to %d\n", ss.channels); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] sample_spec.channels = 0. Defaulting to %d.\n", ss.channels); } /* We now have enough information to calculate the actual buffer size in frames. */ - pDescriptorPlayback->periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptorPlayback, ss.rate, pConfig->performanceProfile); + pDescriptorPlayback->periodSizeInFrames = ma_calculate_period_size_in_frames_from_descriptor__pulse(pDescriptorPlayback, ss.rate, pConfig->performanceProfile); attr = ma_device__pa_buffer_attr_new(pDescriptorPlayback->periodSizeInFrames, pDescriptorPlayback->periodCount, &ss); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[PulseAudio] Playback attr: maxlength=%d, tlength=%d, prebuf=%d, minreq=%d, fragsize=%d; periodSizeInFrames=%d\n", attr.maxlength, attr.tlength, attr.prebuf, attr.minreq, attr.fragsize, pDescriptorPlayback->periodSizeInFrames); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] Playback attr: maxlength=%d, tlength=%d, prebuf=%d, minreq=%d, fragsize=%d; periodSizeInFrames=%d\n", attr.maxlength, attr.tlength, attr.prebuf, attr.minreq, attr.fragsize, pDescriptorPlayback->periodSizeInFrames); - pDevice->pulse.pStreamPlayback = ma_context__pa_stream_new__pulse(pDevice->pContext, pConfig->pulse.pStreamNamePlayback, &ss, &cmap); + pDevice->pulse.pStreamPlayback = ma_device__pa_stream_new__pulse(pDevice, pConfig->pulse.pStreamNamePlayback, &ss, &cmap); if (pDevice->pulse.pStreamPlayback == NULL) { - result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create PulseAudio playback stream.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create PulseAudio playback stream.\n"); + result = MA_ERROR; goto on_error2; } /* Note that this callback will be fired as soon as the stream is connected, even though it's started as corked. The callback needs to handle a - device state of MA_STATE_UNINITIALIZED. + device state of ma_device_state_uninitialized. */ ((ma_pa_stream_set_write_callback_proc)pDevice->pContext->pulse.pa_stream_set_write_callback)((ma_pa_stream*)pDevice->pulse.pStreamPlayback, ma_device_on_write__pulse, pDevice); /* State callback for checking when the device has been corked. */ ((ma_pa_stream_set_suspended_callback_proc)pDevice->pContext->pulse.pa_stream_set_suspended_callback)((ma_pa_stream*)pDevice->pulse.pStreamPlayback, ma_device_on_suspended__pulse, pDevice); + /* Rerouting notification. */ + ((ma_pa_stream_set_moved_callback_proc)pDevice->pContext->pulse.pa_stream_set_moved_callback)((ma_pa_stream*)pDevice->pulse.pStreamPlayback, ma_device_on_rerouted__pulse, pDevice); + /* Connect after we've got all of our internal state set up. */ streamFlags = MA_PA_STREAM_START_CORKED | MA_PA_STREAM_ADJUST_LATENCY | MA_PA_STREAM_FIX_FORMAT | MA_PA_STREAM_FIX_RATE | MA_PA_STREAM_FIX_CHANNELS; @@ -23427,11 +29101,12 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi error = ((ma_pa_stream_connect_playback_proc)pDevice->pContext->pulse.pa_stream_connect_playback)((ma_pa_stream*)pDevice->pulse.pStreamPlayback, devPlayback, &attr, streamFlags, NULL, NULL); if (error != MA_PA_OK) { - result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to connect PulseAudio playback stream.", ma_result_from_pulse(error)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to connect PulseAudio playback stream."); + result = ma_result_from_pulse(error); goto on_error3; } - result = ma_context_wait_for_pa_stream_to_connect__pulse(pDevice->pContext, (ma_pa_stream*)pDevice->pulse.pStreamPlayback); + result = ma_wait_for_pa_stream_to_connect__pulse(pDevice->pContext, pDevice->pulse.pMainLoop, (ma_pa_stream*)pDevice->pulse.pStreamPlayback); if (result != MA_SUCCESS) { goto on_error3; } @@ -23441,20 +29116,49 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi pActualSS = ((ma_pa_stream_get_sample_spec_proc)pDevice->pContext->pulse.pa_stream_get_sample_spec)((ma_pa_stream*)pDevice->pulse.pStreamPlayback); if (pActualSS != NULL) { ss = *pActualSS; + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] Playback sample spec: format=%s, channels=%d, rate=%d\n", ma_get_format_name(ma_format_from_pulse(ss.format)), ss.channels, ss.rate); + } else { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] Failed to retrieve playback sample spec.\n"); } pDescriptorPlayback->format = ma_format_from_pulse(ss.format); pDescriptorPlayback->channels = ss.channels; pDescriptorPlayback->sampleRate = ss.rate; - /* Internal channel map. */ - pActualCMap = ((ma_pa_stream_get_channel_map_proc)pDevice->pContext->pulse.pa_stream_get_channel_map)((ma_pa_stream*)pDevice->pulse.pStreamPlayback); - if (pActualCMap != NULL) { - cmap = *pActualCMap; + if (pDescriptorPlayback->format == ma_format_unknown || pDescriptorPlayback->channels == 0 || pDescriptorPlayback->sampleRate == 0) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Playback sample spec is invalid. Device unusable by miniaudio. format=%s, channels=%d, sampleRate=%d.\n", ma_get_format_name(pDescriptorPlayback->format), pDescriptorPlayback->channels, pDescriptorPlayback->sampleRate); + result = MA_ERROR; + goto on_error4; } - for (iChannel = 0; iChannel < pDescriptorPlayback->channels; ++iChannel) { - pDescriptorPlayback->channelMap[iChannel] = ma_channel_position_from_pulse(cmap.map[iChannel]); + /* Internal channel map. */ + + /* + Bug in PipeWire. There have been reports that PipeWire is returning AUX channels when reporting + the channel map. To somewhat workaround this, I'm hacking in a hard coded channel map for mono + and stereo. In this case it should be safe to assume mono = MONO and stereo = LEFT/RIGHT. For + all other channel counts we need to just put up with whatever PipeWire reports and hope it gets + fixed sooner than later. I might remove this hack later. + */ + if (pDescriptorPlayback->channels > 2) { + pActualCMap = ((ma_pa_stream_get_channel_map_proc)pDevice->pContext->pulse.pa_stream_get_channel_map)((ma_pa_stream*)pDevice->pulse.pStreamPlayback); + if (pActualCMap != NULL) { + cmap = *pActualCMap; + } + + for (iChannel = 0; iChannel < pDescriptorPlayback->channels; ++iChannel) { + pDescriptorPlayback->channelMap[iChannel] = ma_channel_position_from_pulse(cmap.map[iChannel]); + } + } else { + /* Hack for mono and stereo. */ + if (pDescriptorPlayback->channels == 1) { + pDescriptorPlayback->channelMap[0] = MA_CHANNEL_MONO; + } else if (pDescriptorPlayback->channels == 2) { + pDescriptorPlayback->channelMap[0] = MA_CHANNEL_FRONT_LEFT; + pDescriptorPlayback->channelMap[1] = MA_CHANNEL_FRONT_RIGHT; + } else { + MA_ASSERT(MA_FALSE); /* Should never hit this. */ + } } @@ -23464,17 +29168,14 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi attr = *pActualAttr; } - pDescriptorPlayback->periodCount = attr.maxlength / attr.tlength; - pDescriptorPlayback->periodSizeInFrames = attr.maxlength / ma_get_bytes_per_frame(pDescriptorPlayback->format, pDescriptorPlayback->channels) / pDescriptorPlayback->periodCount; - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[PulseAudio] Playback actual attr: maxlength=%d, tlength=%d, prebuf=%d, minreq=%d, fragsize=%d; internalPeriodSizeInFrames=%d\n", attr.maxlength, attr.tlength, attr.prebuf, attr.minreq, attr.fragsize, pDescriptorPlayback->periodSizeInFrames); - - - /* Name. */ - devPlayback = ((ma_pa_stream_get_device_name_proc)pDevice->pContext->pulse.pa_stream_get_device_name)((ma_pa_stream*)pDevice->pulse.pStreamPlayback); - if (devPlayback != NULL) { - ma_pa_operation* pOP = ((ma_pa_context_get_sink_info_by_name_proc)pDevice->pContext->pulse.pa_context_get_sink_info_by_name)((ma_pa_context*)pDevice->pContext->pulse.pPulseContext, devPlayback, ma_device_sink_name_callback, pDevice); - ma_wait_for_operation_and_unref__pulse(pDevice->pContext, pOP); + if (attr.tlength > 0) { + pDescriptorPlayback->periodCount = ma_max(attr.maxlength / attr.tlength, 1); + } else { + pDescriptorPlayback->periodCount = 1; } + + pDescriptorPlayback->periodSizeInFrames = attr.maxlength / ma_get_bytes_per_frame(pDescriptorPlayback->format, pDescriptorPlayback->channels) / pDescriptorPlayback->periodCount; + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] Playback actual attr: maxlength=%d, tlength=%d, prebuf=%d, minreq=%d, fragsize=%d; internalPeriodSizeInFrames=%d\n", attr.maxlength, attr.tlength, attr.prebuf, attr.minreq, attr.fragsize, pDescriptorPlayback->periodSizeInFrames); } @@ -23485,9 +29186,13 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi onDeviceDataLoop callback is NULL, which is not the case for PulseAudio. */ if (pConfig->deviceType == ma_device_type_duplex) { - result = ma_duplex_rb_init(format, channels, sampleRate, pDescriptorCapture->sampleRate, pDescriptorCapture->periodSizeInFrames, &pDevice->pContext->allocationCallbacks, &pDevice->duplexRB); + ma_format rbFormat = (format != ma_format_unknown) ? format : pDescriptorCapture->format; + ma_uint32 rbChannels = (channels > 0) ? channels : pDescriptorCapture->channels; + ma_uint32 rbSampleRate = (sampleRate > 0) ? sampleRate : pDescriptorCapture->sampleRate; + + result = ma_duplex_rb_init(rbFormat, rbChannels, rbSampleRate, pDescriptorCapture->sampleRate, pDescriptorCapture->periodSizeInFrames, &pDevice->pContext->allocationCallbacks, &pDevice->duplexRB); if (result != MA_SUCCESS) { - result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to initialize ring buffer.", result); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to initialize ring buffer. %s.\n", ma_result_description(result)); goto on_error4; } } @@ -23546,20 +29251,19 @@ static ma_result ma_device__cork_stream__pulse(ma_device* pDevice, ma_device_typ pOP = ((ma_pa_stream_cork_proc)pContext->pulse.pa_stream_cork)(pStream, cork, ma_pulse_operation_complete_callback, &wasSuccessful); if (pOP == NULL) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to cork PulseAudio stream.", (cork == 0) ? MA_FAILED_TO_START_BACKEND_DEVICE : MA_FAILED_TO_STOP_BACKEND_DEVICE); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to cork PulseAudio stream."); + return MA_ERROR; } - result = ma_wait_for_operation_and_unref__pulse(pDevice->pContext, pOP); + result = ma_wait_for_operation_and_unref__pulse(pDevice->pContext, pDevice->pulse.pMainLoop, pOP); if (result != MA_SUCCESS) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] An error occurred while waiting for the PulseAudio stream to cork.", result); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] An error occurred while waiting for the PulseAudio stream to cork."); + return result; } if (!wasSuccessful) { - if (cork) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to stop PulseAudio stream.", MA_FAILED_TO_STOP_BACKEND_DEVICE); - } else { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to start PulseAudio stream.", MA_FAILED_TO_START_BACKEND_DEVICE); - } + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to %s PulseAudio stream.", (cork) ? "stop" : "start"); + return MA_ERROR; } return MA_SUCCESS; @@ -23579,11 +29283,12 @@ static ma_result ma_device_start__pulse(ma_device* pDevice) } if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - /* We need to fill some data before uncorking. Not doing this will result in the write callback never getting fired. */ - result = ma_device_write_to_stream__pulse(pDevice, (ma_pa_stream*)(pDevice->pulse.pStreamPlayback), NULL); - if (result != MA_SUCCESS) { - return result; /* Failed to write data. Not sure what to do here... Just aborting. */ - } + /* + We need to fill some data before uncorking. Not doing this will result in the write callback + never getting fired. We're not going to abort if writing fails because I still want the device + to get uncorked. + */ + ma_device_write_to_stream__pulse(pDevice, (ma_pa_stream*)(pDevice->pulse.pStreamPlayback), NULL); /* No need to check the result here. Always want to fall through an uncork.*/ result = ma_device__cork_stream__pulse(pDevice, ma_device_type_playback, 0); if (result != MA_SUCCESS) { @@ -23597,7 +29302,6 @@ static ma_result ma_device_start__pulse(ma_device* pDevice) static ma_result ma_device_stop__pulse(ma_device* pDevice) { ma_result result; - ma_bool32 wasSuccessful; MA_ASSERT(pDevice != NULL); @@ -23609,9 +29313,16 @@ static ma_result ma_device_stop__pulse(ma_device* pDevice) } if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - /* The stream needs to be drained if it's a playback device. */ + /* + Ideally we would drain the device here, but there's been cases where PulseAudio seems to be + broken on some systems to the point where no audio processing seems to happen. When this + happens, draining never completes and we get stuck here. For now I'm disabling draining of + the device so we don't just freeze the application. + */ + #if 0 ma_pa_operation* pOP = ((ma_pa_stream_drain_proc)pDevice->pContext->pulse.pa_stream_drain)((ma_pa_stream*)pDevice->pulse.pStreamPlayback, ma_pulse_operation_complete_callback, &wasSuccessful); - ma_wait_for_operation_and_unref__pulse(pDevice->pContext, pOP); + ma_wait_for_operation_and_unref__pulse(pDevice->pContext, pDevice->pulse.pMainLoop, pOP); + #endif result = ma_device__cork_stream__pulse(pDevice, ma_device_type_playback, 1); if (result != MA_SUCCESS) { @@ -23634,8 +29345,8 @@ static ma_result ma_device_data_loop__pulse(ma_device* pDevice) All data is handled through callbacks. All we need to do is iterate over the main loop and let the callbacks deal with it. */ - while (ma_device_get_state(pDevice) == MA_STATE_STARTED) { - resultPA = ((ma_pa_mainloop_iterate_proc)pDevice->pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pDevice->pContext->pulse.pMainLoop, 1, NULL); + while (ma_device_get_state(pDevice) == ma_device_state_started) { + resultPA = ((ma_pa_mainloop_iterate_proc)pDevice->pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pDevice->pulse.pMainLoop, 1, NULL); if (resultPA < 0) { break; } @@ -23649,7 +29360,7 @@ static ma_result ma_device_data_loop_wakeup__pulse(ma_device* pDevice) { MA_ASSERT(pDevice != NULL); - ((ma_pa_mainloop_wakeup_proc)pDevice->pContext->pulse.pa_mainloop_wakeup)((ma_pa_mainloop*)pDevice->pContext->pulse.pMainLoop); + ((ma_pa_mainloop_wakeup_proc)pDevice->pContext->pulse.pa_mainloop_wakeup)((ma_pa_mainloop*)pDevice->pulse.pMainLoop); return MA_SUCCESS; } @@ -23663,6 +29374,9 @@ static ma_result ma_context_uninit__pulse(ma_context* pContext) ((ma_pa_context_unref_proc)pContext->pulse.pa_context_unref)((ma_pa_context*)pContext->pulse.pPulseContext); ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)((ma_pa_mainloop*)pContext->pulse.pMainLoop); + ma_free(pContext->pulse.pServerName, &pContext->allocationCallbacks); + ma_free(pContext->pulse.pApplicationName, &pContext->allocationCallbacks); + #ifndef MA_NO_RUNTIME_LINKING ma_dlclose(pContext, pContext->pulse.pulseSO); #endif @@ -23739,6 +29453,7 @@ static ma_result ma_context_init__pulse(ma_context* pContext, const ma_context_c pContext->pulse.pa_stream_set_write_callback = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_stream_set_write_callback"); pContext->pulse.pa_stream_set_read_callback = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_stream_set_read_callback"); pContext->pulse.pa_stream_set_suspended_callback = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_stream_set_suspended_callback"); + pContext->pulse.pa_stream_set_moved_callback = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_stream_set_moved_callback"); pContext->pulse.pa_stream_is_suspended = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_stream_is_suspended"); pContext->pulse.pa_stream_flush = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_stream_flush"); pContext->pulse.pa_stream_drain = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_stream_drain"); @@ -23801,6 +29516,7 @@ static ma_result ma_context_init__pulse(ma_context* pContext, const ma_context_c ma_pa_stream_set_write_callback_proc _pa_stream_set_write_callback = pa_stream_set_write_callback; ma_pa_stream_set_read_callback_proc _pa_stream_set_read_callback = pa_stream_set_read_callback; ma_pa_stream_set_suspended_callback_proc _pa_stream_set_suspended_callback = pa_stream_set_suspended_callback; + ma_pa_stream_set_moved_callback_proc _pa_stream_set_moved_callback = pa_stream_set_moved_callback; ma_pa_stream_is_suspended_proc _pa_stream_is_suspended = pa_stream_is_suspended; ma_pa_stream_flush_proc _pa_stream_flush = pa_stream_flush; ma_pa_stream_drain_proc _pa_stream_drain = pa_stream_drain; @@ -23862,6 +29578,7 @@ static ma_result ma_context_init__pulse(ma_context* pContext, const ma_context_c pContext->pulse.pa_stream_set_write_callback = (ma_proc)_pa_stream_set_write_callback; pContext->pulse.pa_stream_set_read_callback = (ma_proc)_pa_stream_set_read_callback; pContext->pulse.pa_stream_set_suspended_callback = (ma_proc)_pa_stream_set_suspended_callback; + pContext->pulse.pa_stream_set_moved_callback = (ma_proc)_pa_stream_set_moved_callback; pContext->pulse.pa_stream_is_suspended = (ma_proc)_pa_stream_is_suspended; pContext->pulse.pa_stream_flush = (ma_proc)_pa_stream_flush; pContext->pulse.pa_stream_drain = (ma_proc)_pa_stream_drain; @@ -23876,48 +29593,28 @@ static ma_result ma_context_init__pulse(ma_context* pContext, const ma_context_c pContext->pulse.pa_stream_readable_size = (ma_proc)_pa_stream_readable_size; #endif - /* The PulseAudio context maps well to miniaudio's notion of a context. The pa_context object will be initialized as part of the ma_context. */ - pContext->pulse.pMainLoop = ((ma_pa_mainloop_new_proc)pContext->pulse.pa_mainloop_new)(); - if (pContext->pulse.pMainLoop == NULL) { - result = ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create mainloop.", MA_FAILED_TO_INIT_BACKEND); - #ifndef MA_NO_RUNTIME_LINKING - ma_dlclose(pContext, pContext->pulse.pulseSO); - #endif - return result; + /* We need to make a copy of the application and server names so we can pass them to the pa_context of each device. */ + pContext->pulse.pApplicationName = ma_copy_string(pConfig->pulse.pApplicationName, &pContext->allocationCallbacks); + if (pContext->pulse.pApplicationName == NULL && pConfig->pulse.pApplicationName != NULL) { + return MA_OUT_OF_MEMORY; } - pContext->pulse.pPulseContext = ((ma_pa_context_new_proc)pContext->pulse.pa_context_new)(((ma_pa_mainloop_get_api_proc)pContext->pulse.pa_mainloop_get_api)((ma_pa_mainloop*)pContext->pulse.pMainLoop), pConfig->pulse.pApplicationName); - if (pContext->pulse.pPulseContext == NULL) { - result = ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create PulseAudio context.", MA_FAILED_TO_INIT_BACKEND); - ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)((ma_pa_mainloop*)(pContext->pulse.pMainLoop)); - #ifndef MA_NO_RUNTIME_LINKING - ma_dlclose(pContext, pContext->pulse.pulseSO); - #endif - return result; + pContext->pulse.pServerName = ma_copy_string(pConfig->pulse.pServerName, &pContext->allocationCallbacks); + if (pContext->pulse.pServerName == NULL && pConfig->pulse.pServerName != NULL) { + ma_free(pContext->pulse.pApplicationName, &pContext->allocationCallbacks); + return MA_OUT_OF_MEMORY; } - /* Now we need to connect to the context. Everything is asynchronous so we need to wait for it to connect before returning. */ - result = ma_result_from_pulse(((ma_pa_context_connect_proc)pContext->pulse.pa_context_connect)((ma_pa_context*)pContext->pulse.pPulseContext, pConfig->pulse.pServerName, (pConfig->pulse.tryAutoSpawn) ? 0 : MA_PA_CONTEXT_NOAUTOSPAWN, NULL)); + result = ma_init_pa_mainloop_and_pa_context__pulse(pContext, pConfig->pulse.pApplicationName, pConfig->pulse.pServerName, pConfig->pulse.tryAutoSpawn, &pContext->pulse.pMainLoop, &pContext->pulse.pPulseContext); if (result != MA_SUCCESS) { - ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to connect PulseAudio context.", result); - ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)((ma_pa_mainloop*)(pContext->pulse.pMainLoop)); + ma_free(pContext->pulse.pServerName, &pContext->allocationCallbacks); + ma_free(pContext->pulse.pApplicationName, &pContext->allocationCallbacks); #ifndef MA_NO_RUNTIME_LINKING ma_dlclose(pContext, pContext->pulse.pulseSO); #endif return result; } - /* Since ma_context_init() runs synchronously we need to wait for the PulseAudio context to connect before we return. */ - result = ma_context_wait_for_pa_context_to_connect__pulse(pContext); - if (result != MA_SUCCESS) { - ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)((ma_pa_mainloop*)(pContext->pulse.pMainLoop)); - #ifndef MA_NO_RUNTIME_LINKING - ma_dlclose(pContext, pContext->pulse.pulseSO); - #endif - return result; - } - - /* With pa_mainloop we run a synchronous backend, but we implement our own main loop. */ pCallbacks->onContextInit = ma_context_init__pulse; pCallbacks->onContextUninit = ma_context_uninit__pulse; @@ -24082,7 +29779,8 @@ static ma_result ma_context_get_device_info__jack(ma_context* pContext, ma_devic /* The channel count and sample rate can only be determined by opening the device. */ result = ma_context_open_client__jack(pContext, &pClient); if (result != MA_SUCCESS) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[JACK] Failed to open client.", result); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[JACK] Failed to open client."); + return result; } pDeviceInfo->nativeDataFormats[0].sampleRate = ((ma_jack_get_sample_rate_proc)pContext->jack.jack_get_sample_rate)((ma_jack_client_t*)pClient); @@ -24091,7 +29789,8 @@ static ma_result ma_context_get_device_info__jack(ma_context* pContext, ma_devic ppPorts = ((ma_jack_get_ports_proc)pContext->jack.jack_get_ports)((ma_jack_client_t*)pClient, NULL, MA_JACK_DEFAULT_AUDIO_TYPE, ma_JackPortIsPhysical | ((deviceType == ma_device_type_playback) ? ma_JackPortIsInput : ma_JackPortIsOutput)); if (ppPorts == NULL) { ((ma_jack_client_close_proc)pContext->jack.jack_client_close)((ma_jack_client_t*)pClient); - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[JACK] Failed to query physical ports.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[JACK] Failed to query physical ports."); + return MA_FAILED_TO_OPEN_BACKEND_DEVICE; } while (ppPorts[pDeviceInfo->nativeDataFormats[0].channels] != NULL) { @@ -24123,11 +29822,13 @@ static ma_result ma_device_uninit__jack(ma_device* pDevice) } if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - ma__free_from_callbacks(pDevice->jack.pIntermediaryBufferCapture, &pDevice->pContext->allocationCallbacks); + ma_free(pDevice->jack.pIntermediaryBufferCapture, &pDevice->pContext->allocationCallbacks); + ma_free(pDevice->jack.ppPortsCapture, &pDevice->pContext->allocationCallbacks); } if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - ma__free_from_callbacks(pDevice->jack.pIntermediaryBufferPlayback, &pDevice->pContext->allocationCallbacks); + ma_free(pDevice->jack.pIntermediaryBufferPlayback, &pDevice->pContext->allocationCallbacks); + ma_free(pDevice->jack.ppPortsPlayback, &pDevice->pContext->allocationCallbacks); } return MA_SUCCESS; @@ -24149,12 +29850,12 @@ static int ma_device__jack_buffer_size_callback(ma_jack_nframes_t frameCount, vo if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { size_t newBufferSize = frameCount * (pDevice->capture.internalChannels * ma_get_bytes_per_sample(pDevice->capture.internalFormat)); - float* pNewBuffer = (float*)ma__calloc_from_callbacks(newBufferSize, &pDevice->pContext->allocationCallbacks); + float* pNewBuffer = (float*)ma_calloc(newBufferSize, &pDevice->pContext->allocationCallbacks); if (pNewBuffer == NULL) { return MA_OUT_OF_MEMORY; } - ma__free_from_callbacks(pDevice->jack.pIntermediaryBufferCapture, &pDevice->pContext->allocationCallbacks); + ma_free(pDevice->jack.pIntermediaryBufferCapture, &pDevice->pContext->allocationCallbacks); pDevice->jack.pIntermediaryBufferCapture = pNewBuffer; pDevice->playback.internalPeriodSizeInFrames = frameCount; @@ -24162,12 +29863,12 @@ static int ma_device__jack_buffer_size_callback(ma_jack_nframes_t frameCount, vo if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { size_t newBufferSize = frameCount * (pDevice->playback.internalChannels * ma_get_bytes_per_sample(pDevice->playback.internalFormat)); - float* pNewBuffer = (float*)ma__calloc_from_callbacks(newBufferSize, &pDevice->pContext->allocationCallbacks); + float* pNewBuffer = (float*)ma_calloc(newBufferSize, &pDevice->pContext->allocationCallbacks); if (pNewBuffer == NULL) { return MA_OUT_OF_MEMORY; } - ma__free_from_callbacks(pDevice->jack.pIntermediaryBufferPlayback, &pDevice->pContext->allocationCallbacks); + ma_free(pDevice->jack.pIntermediaryBufferPlayback, &pDevice->pContext->allocationCallbacks); pDevice->jack.pIntermediaryBufferPlayback = pNewBuffer; pDevice->playback.internalPeriodSizeInFrames = frameCount; @@ -24191,7 +29892,7 @@ static int ma_device__jack_process_callback(ma_jack_nframes_t frameCount, void* if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { /* Channels need to be interleaved. */ for (iChannel = 0; iChannel < pDevice->capture.internalChannels; ++iChannel) { - const float* pSrc = (const float*)((ma_jack_port_get_buffer_proc)pContext->jack.jack_port_get_buffer)((ma_jack_port_t*)pDevice->jack.pPortsCapture[iChannel], frameCount); + const float* pSrc = (const float*)((ma_jack_port_get_buffer_proc)pContext->jack.jack_port_get_buffer)((ma_jack_port_t*)pDevice->jack.ppPortsCapture[iChannel], frameCount); if (pSrc != NULL) { float* pDst = pDevice->jack.pIntermediaryBufferCapture + iChannel; ma_jack_nframes_t iFrame; @@ -24212,7 +29913,7 @@ static int ma_device__jack_process_callback(ma_jack_nframes_t frameCount, void* /* Channels need to be deinterleaved. */ for (iChannel = 0; iChannel < pDevice->playback.internalChannels; ++iChannel) { - float* pDst = (float*)((ma_jack_port_get_buffer_proc)pContext->jack.jack_port_get_buffer)((ma_jack_port_t*)pDevice->jack.pPortsPlayback[iChannel], frameCount); + float* pDst = (float*)((ma_jack_port_get_buffer_proc)pContext->jack.jack_port_get_buffer)((ma_jack_port_t*)pDevice->jack.ppPortsPlayback[iChannel], frameCount); if (pDst != NULL) { const float* pSrc = pDevice->jack.pIntermediaryBufferPlayback + iChannel; ma_jack_nframes_t iFrame; @@ -24238,33 +29939,39 @@ static ma_result ma_device_init__jack(ma_device* pDevice, const ma_device_config MA_ASSERT(pDevice != NULL); if (pConfig->deviceType == ma_device_type_loopback) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Loopback mode not supported."); return MA_DEVICE_TYPE_NOT_SUPPORTED; } /* Only supporting default devices with JACK. */ if (((pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) && pDescriptorPlayback->pDeviceID != NULL && pDescriptorPlayback->pDeviceID->jack != 0) || ((pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) && pDescriptorCapture->pDeviceID != NULL && pDescriptorCapture->pDeviceID->jack != 0)) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Only default devices are supported."); return MA_NO_DEVICE; } /* No exclusive mode with the JACK backend. */ if (((pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) && pDescriptorPlayback->shareMode == ma_share_mode_exclusive) || ((pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) && pDescriptorCapture->shareMode == ma_share_mode_exclusive)) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Exclusive mode not supported."); return MA_SHARE_MODE_NOT_SUPPORTED; } /* Open the client. */ result = ma_context_open_client__jack(pDevice->pContext, (ma_jack_client_t**)&pDevice->jack.pClient); if (result != MA_SUCCESS) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[JACK] Failed to open client.", result); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Failed to open client."); + return result; } /* Callbacks. */ if (((ma_jack_set_process_callback_proc)pDevice->pContext->jack.jack_set_process_callback)((ma_jack_client_t*)pDevice->jack.pClient, ma_device__jack_process_callback, pDevice) != 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[JACK] Failed to set process callback.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Failed to set process callback."); + return MA_FAILED_TO_OPEN_BACKEND_DEVICE; } if (((ma_jack_set_buffer_size_callback_proc)pDevice->pContext->jack.jack_set_buffer_size_callback)((ma_jack_client_t*)pDevice->jack.pClient, ma_device__jack_buffer_size_callback, pDevice) != 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[JACK] Failed to set buffer size callback.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Failed to set buffer size callback."); + return MA_FAILED_TO_OPEN_BACKEND_DEVICE; } ((ma_jack_on_shutdown_proc)pDevice->pContext->jack.jack_on_shutdown)((ma_jack_client_t*)pDevice->jack.pClient, ma_device__jack_shutdown_callback, pDevice); @@ -24274,31 +29981,42 @@ static ma_result ma_device_init__jack(ma_device* pDevice, const ma_device_config periodSizeInFrames = ((ma_jack_get_buffer_size_proc)pDevice->pContext->jack.jack_get_buffer_size)((ma_jack_client_t*)pDevice->jack.pClient); if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { + ma_uint32 iPort; const char** ppPorts; pDescriptorCapture->format = ma_format_f32; pDescriptorCapture->channels = 0; pDescriptorCapture->sampleRate = ((ma_jack_get_sample_rate_proc)pDevice->pContext->jack.jack_get_sample_rate)((ma_jack_client_t*)pDevice->jack.pClient); - ma_get_standard_channel_map(ma_standard_channel_map_alsa, pDescriptorCapture->channels, pDescriptorCapture->channelMap); + ma_channel_map_init_standard(ma_standard_channel_map_alsa, pDescriptorCapture->channelMap, ma_countof(pDescriptorCapture->channelMap), pDescriptorCapture->channels); ppPorts = ((ma_jack_get_ports_proc)pDevice->pContext->jack.jack_get_ports)((ma_jack_client_t*)pDevice->jack.pClient, NULL, MA_JACK_DEFAULT_AUDIO_TYPE, ma_JackPortIsPhysical | ma_JackPortIsOutput); if (ppPorts == NULL) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[JACK] Failed to query physical ports.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Failed to query physical ports."); + return MA_FAILED_TO_OPEN_BACKEND_DEVICE; } + /* Need to count the number of ports first so we can allocate some memory. */ while (ppPorts[pDescriptorCapture->channels] != NULL) { + pDescriptorCapture->channels += 1; + } + + pDevice->jack.ppPortsCapture = (ma_ptr*)ma_malloc(sizeof(*pDevice->jack.ppPortsCapture) * pDescriptorCapture->channels, &pDevice->pContext->allocationCallbacks); + if (pDevice->jack.ppPortsCapture == NULL) { + return MA_OUT_OF_MEMORY; + } + + for (iPort = 0; iPort < pDescriptorCapture->channels; iPort += 1) { char name[64]; ma_strcpy_s(name, sizeof(name), "capture"); - ma_itoa_s((int)pDescriptorCapture->channels, name+7, sizeof(name)-7, 10); /* 7 = length of "capture" */ + ma_itoa_s((int)iPort, name+7, sizeof(name)-7, 10); /* 7 = length of "capture" */ - pDevice->jack.pPortsCapture[pDescriptorCapture->channels] = ((ma_jack_port_register_proc)pDevice->pContext->jack.jack_port_register)((ma_jack_client_t*)pDevice->jack.pClient, name, MA_JACK_DEFAULT_AUDIO_TYPE, ma_JackPortIsInput, 0); - if (pDevice->jack.pPortsCapture[pDescriptorCapture->channels] == NULL) { + pDevice->jack.ppPortsCapture[iPort] = ((ma_jack_port_register_proc)pDevice->pContext->jack.jack_port_register)((ma_jack_client_t*)pDevice->jack.pClient, name, MA_JACK_DEFAULT_AUDIO_TYPE, ma_JackPortIsInput, 0); + if (pDevice->jack.ppPortsCapture[iPort] == NULL) { ((ma_jack_free_proc)pDevice->pContext->jack.jack_free)((void*)ppPorts); ma_device_uninit__jack(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[JACK] Failed to register ports.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Failed to register ports."); + return MA_FAILED_TO_OPEN_BACKEND_DEVICE; } - - pDescriptorCapture->channels += 1; } ((ma_jack_free_proc)pDevice->pContext->jack.jack_free)((void*)ppPorts); @@ -24306,7 +30024,7 @@ static ma_result ma_device_init__jack(ma_device* pDevice, const ma_device_config pDescriptorCapture->periodSizeInFrames = periodSizeInFrames; pDescriptorCapture->periodCount = 1; /* There's no notion of a period in JACK. Just set to 1. */ - pDevice->jack.pIntermediaryBufferCapture = (float*)ma__calloc_from_callbacks(pDescriptorCapture->periodSizeInFrames * ma_get_bytes_per_frame(pDescriptorCapture->format, pDescriptorCapture->channels), &pDevice->pContext->allocationCallbacks); + pDevice->jack.pIntermediaryBufferCapture = (float*)ma_calloc(pDescriptorCapture->periodSizeInFrames * ma_get_bytes_per_frame(pDescriptorCapture->format, pDescriptorCapture->channels), &pDevice->pContext->allocationCallbacks); if (pDevice->jack.pIntermediaryBufferCapture == NULL) { ma_device_uninit__jack(pDevice); return MA_OUT_OF_MEMORY; @@ -24314,31 +30032,43 @@ static ma_result ma_device_init__jack(ma_device* pDevice, const ma_device_config } if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { + ma_uint32 iPort; const char** ppPorts; pDescriptorPlayback->format = ma_format_f32; pDescriptorPlayback->channels = 0; pDescriptorPlayback->sampleRate = ((ma_jack_get_sample_rate_proc)pDevice->pContext->jack.jack_get_sample_rate)((ma_jack_client_t*)pDevice->jack.pClient); - ma_get_standard_channel_map(ma_standard_channel_map_alsa, pDescriptorPlayback->channels, pDescriptorPlayback->channelMap); + ma_channel_map_init_standard(ma_standard_channel_map_alsa, pDescriptorPlayback->channelMap, ma_countof(pDescriptorPlayback->channelMap), pDescriptorPlayback->channels); ppPorts = ((ma_jack_get_ports_proc)pDevice->pContext->jack.jack_get_ports)((ma_jack_client_t*)pDevice->jack.pClient, NULL, MA_JACK_DEFAULT_AUDIO_TYPE, ma_JackPortIsPhysical | ma_JackPortIsInput); if (ppPorts == NULL) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[JACK] Failed to query physical ports.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Failed to query physical ports."); + return MA_FAILED_TO_OPEN_BACKEND_DEVICE; } + /* Need to count the number of ports first so we can allocate some memory. */ while (ppPorts[pDescriptorPlayback->channels] != NULL) { + pDescriptorPlayback->channels += 1; + } + + pDevice->jack.ppPortsPlayback = (ma_ptr*)ma_malloc(sizeof(*pDevice->jack.ppPortsPlayback) * pDescriptorPlayback->channels, &pDevice->pContext->allocationCallbacks); + if (pDevice->jack.ppPortsPlayback == NULL) { + ma_free(pDevice->jack.ppPortsCapture, &pDevice->pContext->allocationCallbacks); + return MA_OUT_OF_MEMORY; + } + + for (iPort = 0; iPort < pDescriptorPlayback->channels; iPort += 1) { char name[64]; ma_strcpy_s(name, sizeof(name), "playback"); - ma_itoa_s((int)pDescriptorPlayback->channels, name+8, sizeof(name)-8, 10); /* 8 = length of "playback" */ + ma_itoa_s((int)iPort, name+8, sizeof(name)-8, 10); /* 8 = length of "playback" */ - pDevice->jack.pPortsPlayback[pDescriptorPlayback->channels] = ((ma_jack_port_register_proc)pDevice->pContext->jack.jack_port_register)((ma_jack_client_t*)pDevice->jack.pClient, name, MA_JACK_DEFAULT_AUDIO_TYPE, ma_JackPortIsOutput, 0); - if (pDevice->jack.pPortsPlayback[pDescriptorPlayback->channels] == NULL) { + pDevice->jack.ppPortsPlayback[iPort] = ((ma_jack_port_register_proc)pDevice->pContext->jack.jack_port_register)((ma_jack_client_t*)pDevice->jack.pClient, name, MA_JACK_DEFAULT_AUDIO_TYPE, ma_JackPortIsOutput, 0); + if (pDevice->jack.ppPortsPlayback[iPort] == NULL) { ((ma_jack_free_proc)pDevice->pContext->jack.jack_free)((void*)ppPorts); ma_device_uninit__jack(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[JACK] Failed to register ports.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Failed to register ports."); + return MA_FAILED_TO_OPEN_BACKEND_DEVICE; } - - pDescriptorPlayback->channels += 1; } ((ma_jack_free_proc)pDevice->pContext->jack.jack_free)((void*)ppPorts); @@ -24346,7 +30076,7 @@ static ma_result ma_device_init__jack(ma_device* pDevice, const ma_device_config pDescriptorPlayback->periodSizeInFrames = periodSizeInFrames; pDescriptorPlayback->periodCount = 1; /* There's no notion of a period in JACK. Just set to 1. */ - pDevice->jack.pIntermediaryBufferPlayback = (float*)ma__calloc_from_callbacks(pDescriptorPlayback->periodSizeInFrames * ma_get_bytes_per_frame(pDescriptorPlayback->format, pDescriptorPlayback->channels), &pDevice->pContext->allocationCallbacks); + pDevice->jack.pIntermediaryBufferPlayback = (float*)ma_calloc(pDescriptorPlayback->periodSizeInFrames * ma_get_bytes_per_frame(pDescriptorPlayback->format, pDescriptorPlayback->channels), &pDevice->pContext->allocationCallbacks); if (pDevice->jack.pIntermediaryBufferPlayback == NULL) { ma_device_uninit__jack(pDevice); return MA_OUT_OF_MEMORY; @@ -24365,25 +30095,28 @@ static ma_result ma_device_start__jack(ma_device* pDevice) resultJACK = ((ma_jack_activate_proc)pContext->jack.jack_activate)((ma_jack_client_t*)pDevice->jack.pClient); if (resultJACK != 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[JACK] Failed to activate the JACK client.", MA_FAILED_TO_START_BACKEND_DEVICE); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Failed to activate the JACK client."); + return MA_FAILED_TO_START_BACKEND_DEVICE; } if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { const char** ppServerPorts = ((ma_jack_get_ports_proc)pContext->jack.jack_get_ports)((ma_jack_client_t*)pDevice->jack.pClient, NULL, MA_JACK_DEFAULT_AUDIO_TYPE, ma_JackPortIsPhysical | ma_JackPortIsOutput); if (ppServerPorts == NULL) { ((ma_jack_deactivate_proc)pContext->jack.jack_deactivate)((ma_jack_client_t*)pDevice->jack.pClient); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[JACK] Failed to retrieve physical ports.", MA_ERROR); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Failed to retrieve physical ports."); + return MA_ERROR; } for (i = 0; ppServerPorts[i] != NULL; ++i) { const char* pServerPort = ppServerPorts[i]; - const char* pClientPort = ((ma_jack_port_name_proc)pContext->jack.jack_port_name)((ma_jack_port_t*)pDevice->jack.pPortsCapture[i]); + const char* pClientPort = ((ma_jack_port_name_proc)pContext->jack.jack_port_name)((ma_jack_port_t*)pDevice->jack.ppPortsCapture[i]); resultJACK = ((ma_jack_connect_proc)pContext->jack.jack_connect)((ma_jack_client_t*)pDevice->jack.pClient, pServerPort, pClientPort); if (resultJACK != 0) { ((ma_jack_free_proc)pContext->jack.jack_free)((void*)ppServerPorts); ((ma_jack_deactivate_proc)pContext->jack.jack_deactivate)((ma_jack_client_t*)pDevice->jack.pClient); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[JACK] Failed to connect ports.", MA_ERROR); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Failed to connect ports."); + return MA_ERROR; } } @@ -24394,18 +30127,20 @@ static ma_result ma_device_start__jack(ma_device* pDevice) const char** ppServerPorts = ((ma_jack_get_ports_proc)pContext->jack.jack_get_ports)((ma_jack_client_t*)pDevice->jack.pClient, NULL, MA_JACK_DEFAULT_AUDIO_TYPE, ma_JackPortIsPhysical | ma_JackPortIsInput); if (ppServerPorts == NULL) { ((ma_jack_deactivate_proc)pContext->jack.jack_deactivate)((ma_jack_client_t*)pDevice->jack.pClient); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[JACK] Failed to retrieve physical ports.", MA_ERROR); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Failed to retrieve physical ports."); + return MA_ERROR; } for (i = 0; ppServerPorts[i] != NULL; ++i) { const char* pServerPort = ppServerPorts[i]; - const char* pClientPort = ((ma_jack_port_name_proc)pContext->jack.jack_port_name)((ma_jack_port_t*)pDevice->jack.pPortsPlayback[i]); + const char* pClientPort = ((ma_jack_port_name_proc)pContext->jack.jack_port_name)((ma_jack_port_t*)pDevice->jack.ppPortsPlayback[i]); resultJACK = ((ma_jack_connect_proc)pContext->jack.jack_connect)((ma_jack_client_t*)pDevice->jack.pClient, pClientPort, pServerPort); if (resultJACK != 0) { ((ma_jack_free_proc)pContext->jack.jack_free)((void*)ppServerPorts); ((ma_jack_deactivate_proc)pContext->jack.jack_deactivate)((ma_jack_client_t*)pDevice->jack.pClient); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[JACK] Failed to connect ports.", MA_ERROR); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Failed to connect ports."); + return MA_ERROR; } } @@ -24418,16 +30153,13 @@ static ma_result ma_device_start__jack(ma_device* pDevice) static ma_result ma_device_stop__jack(ma_device* pDevice) { ma_context* pContext = pDevice->pContext; - ma_stop_proc onStop; if (((ma_jack_deactivate_proc)pContext->jack.jack_deactivate)((ma_jack_client_t*)pDevice->jack.pClient) != 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[JACK] An error occurred when deactivating the JACK client.", MA_ERROR); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] An error occurred when deactivating the JACK client."); + return MA_ERROR; } - onStop = pDevice->onStop; - if (onStop) { - onStop(pDevice); - } + ma_device__on_notification_stopped(pDevice); return MA_SUCCESS; } @@ -24592,6 +30324,13 @@ References #if defined(TARGET_OS_WATCH) && TARGET_OS_WATCH == 1 #define MA_APPLE_WATCH #endif + #if __has_feature(objc_arc) + #define MA_BRIDGE_TRANSFER __bridge_transfer + #define MA_BRIDGE_RETAINED __bridge_retained + #else + #define MA_BRIDGE_TRANSFER + #define MA_BRIDGE_RETAINED + #endif #else #define MA_APPLE_DESKTOP #endif @@ -24930,7 +30669,7 @@ static ma_result ma_get_channel_map_from_AudioChannelLayout(AudioChannelLayout* case kAudioChannelLayoutTag_Binaural: case kAudioChannelLayoutTag_Ambisonic_B_Format: { - ma_get_standard_channel_map(ma_standard_channel_map_default, channelCount, pChannelMap); + ma_channel_map_init_standard(ma_standard_channel_map_default, pChannelMap, channelMapCap, channelCount); } break; case kAudioChannelLayoutTag_Octagonal: @@ -24958,7 +30697,7 @@ static ma_result ma_get_channel_map_from_AudioChannelLayout(AudioChannelLayout* default: { - ma_get_standard_channel_map(ma_standard_channel_map_default, channelCount, pChannelMap); + ma_channel_map_init_standard(ma_standard_channel_map_default, pChannelMap, channelMapCap, channelCount); } break; } } @@ -25095,14 +30834,14 @@ static ma_bool32 ma_does_AudioObject_support_scope(ma_context* pContext, AudioOb return MA_FALSE; } - pBufferList = (AudioBufferList*)ma__malloc_from_callbacks(dataSize, &pContext->allocationCallbacks); + pBufferList = (AudioBufferList*)ma_malloc(dataSize, &pContext->allocationCallbacks); if (pBufferList == NULL) { return MA_FALSE; /* Out of memory. */ } status = ((ma_AudioObjectGetPropertyData_proc)pContext->coreaudio.AudioObjectGetPropertyData)(deviceObjectID, &propAddress, 0, NULL, &dataSize, pBufferList); if (status != noErr) { - ma__free_from_callbacks(pBufferList, &pContext->allocationCallbacks); + ma_free(pBufferList, &pContext->allocationCallbacks); return MA_FALSE; } @@ -25111,7 +30850,7 @@ static ma_bool32 ma_does_AudioObject_support_scope(ma_context* pContext, AudioOb isSupported = MA_TRUE; } - ma__free_from_callbacks(pBufferList, &pContext->allocationCallbacks); + ma_free(pBufferList, &pContext->allocationCallbacks); return isSupported; } @@ -25740,24 +31479,24 @@ static ma_result ma_get_AudioUnit_channel_map(ma_context* pContext, AudioUnit au return ma_result_from_OSStatus(status); } - pChannelLayout = (AudioChannelLayout*)ma__malloc_from_callbacks(channelLayoutSize, &pContext->allocationCallbacks); + pChannelLayout = (AudioChannelLayout*)ma_malloc(channelLayoutSize, &pContext->allocationCallbacks); if (pChannelLayout == NULL) { return MA_OUT_OF_MEMORY; } status = ((ma_AudioUnitGetProperty_proc)pContext->coreaudio.AudioUnitGetProperty)(audioUnit, kAudioUnitProperty_AudioChannelLayout, deviceScope, deviceBus, pChannelLayout, &channelLayoutSize); if (status != noErr) { - ma__free_from_callbacks(pChannelLayout, &pContext->allocationCallbacks); + ma_free(pChannelLayout, &pContext->allocationCallbacks); return ma_result_from_OSStatus(status); } result = ma_get_channel_map_from_AudioChannelLayout(pChannelLayout, pChannelMap, channelMapCap); if (result != MA_SUCCESS) { - ma__free_from_callbacks(pChannelLayout, &pContext->allocationCallbacks); + ma_free(pChannelLayout, &pContext->allocationCallbacks); return result; } - ma__free_from_callbacks(pChannelLayout, &pContext->allocationCallbacks); + ma_free(pChannelLayout, &pContext->allocationCallbacks); return MA_SUCCESS; } #endif /* MA_APPLE_DESKTOP */ @@ -25993,7 +31732,7 @@ static ma_result ma_context_get_device_info__coreaudio(ma_context* pContext, ma_ UInt32 propSize; /* We want to ensure we use a consistent device name to device enumeration. */ - if (pDeviceID != NULL) { + if (pDeviceID != NULL && pDeviceID->coreaudio[0] != '\0') { ma_bool32 found = MA_FALSE; if (deviceType == ma_device_type_playback) { NSArray *pOutputs = [[[AVAudioSession sharedInstance] currentRoute] outputs]; @@ -26109,7 +31848,7 @@ static AudioBufferList* ma_allocate_AudioBufferList__coreaudio(ma_uint32 sizeInF allocationSize += sizeInFrames * ma_get_bytes_per_frame(format, channels); - pBufferList = (AudioBufferList*)ma__malloc_from_callbacks(allocationSize, pAllocationCallbacks); + pBufferList = (AudioBufferList*)ma_malloc(allocationSize, pAllocationCallbacks); if (pBufferList == NULL) { return NULL; } @@ -26145,12 +31884,12 @@ static ma_result ma_device_realloc_AudioBufferList__coreaudio(ma_device* pDevice AudioBufferList* pNewAudioBufferList; pNewAudioBufferList = ma_allocate_AudioBufferList__coreaudio(sizeInFrames, format, channels, layout, &pDevice->pContext->allocationCallbacks); - if (pNewAudioBufferList != NULL) { + if (pNewAudioBufferList == NULL) { return MA_OUT_OF_MEMORY; } /* At this point we'll have a new AudioBufferList and we can free the old one. */ - ma__free_from_callbacks(pDevice->coreaudio.pAudioBufferList, &pDevice->pContext->allocationCallbacks); + ma_free(pDevice->coreaudio.pAudioBufferList, &pDevice->pContext->allocationCallbacks); pDevice->coreaudio.pAudioBufferList = pNewAudioBufferList; pDevice->coreaudio.audioBufferCapInFrames = sizeInFrames; } @@ -26167,7 +31906,7 @@ static OSStatus ma_on_output__coreaudio(void* pUserData, AudioUnitRenderActionFl MA_ASSERT(pDevice != NULL); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "INFO: Output Callback: busNumber=%d, frameCount=%d, mNumberBuffers=%d\n", busNumber, frameCount, pBufferList->mNumberBuffers); + /*ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "INFO: Output Callback: busNumber=%d, frameCount=%d, mNumberBuffers=%d\n", (int)busNumber, (int)frameCount, (int)pBufferList->mNumberBuffers);*/ /* We need to check whether or not we are outputting interleaved or non-interleaved samples. The way we do this is slightly different for each type. */ layout = ma_stream_layout_interleaved; @@ -26185,7 +31924,7 @@ static OSStatus ma_on_output__coreaudio(void* pUserData, AudioUnitRenderActionFl ma_device_handle_backend_data_callback(pDevice, pBufferList->mBuffers[iBuffer].mData, NULL, frameCountForThisBuffer); } - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, " frameCount=%d, mNumberChannels=%d, mDataByteSize=%d\n", frameCount, pBufferList->mBuffers[iBuffer].mNumberChannels, pBufferList->mBuffers[iBuffer].mDataByteSize); + /*a_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, " frameCount=%d, mNumberChannels=%d, mDataByteSize=%d\n", (int)frameCount, (int)pBufferList->mBuffers[iBuffer].mNumberChannels, (int)pBufferList->mBuffers[iBuffer].mDataByteSize);*/ } else { /* This case is where the number of channels in the output buffer do not match our internal channels. It could mean that it's @@ -26193,7 +31932,7 @@ static OSStatus ma_on_output__coreaudio(void* pUserData, AudioUnitRenderActionFl output silence here. */ MA_ZERO_MEMORY(pBufferList->mBuffers[iBuffer].mData, pBufferList->mBuffers[iBuffer].mDataByteSize); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, " WARNING: Outputting silence. frameCount=%d, mNumberChannels=%d, mDataByteSize=%d\n", frameCount, pBufferList->mBuffers[iBuffer].mNumberChannels, pBufferList->mBuffers[iBuffer].mDataByteSize); + /*ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, " WARNING: Outputting silence. frameCount=%d, mNumberChannels=%d, mDataByteSize=%d\n", (int)frameCount, (int)pBufferList->mBuffers[iBuffer].mNumberChannels, (int)pBufferList->mBuffers[iBuffer].mDataByteSize);*/ } } } else { @@ -26262,7 +32001,7 @@ static OSStatus ma_on_input__coreaudio(void* pUserData, AudioUnitRenderActionFla layout = ma_stream_layout_deinterleaved; } - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "INFO: Input Callback: busNumber=%d, frameCount=%d, mNumberBuffers=%d\n", busNumber, frameCount, pRenderedBufferList->mNumberBuffers); + /*ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "INFO: Input Callback: busNumber=%d, frameCount=%d, mNumberBuffers=%d\n", (int)busNumber, (int)frameCount, (int)pRenderedBufferList->mNumberBuffers);*/ /* There has been a situation reported where frame count passed into this function is greater than the capacity of @@ -26272,9 +32011,12 @@ static OSStatus ma_on_input__coreaudio(void* pUserData, AudioUnitRenderActionFla */ result = ma_device_realloc_AudioBufferList__coreaudio(pDevice, frameCount, pDevice->capture.internalFormat, pDevice->capture.internalChannels, layout); if (result != MA_SUCCESS) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "Failed to allocate AudioBufferList for capture."); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "Failed to allocate AudioBufferList for capture.\n"); return noErr; } + + pRenderedBufferList = (AudioBufferList*)pDevice->coreaudio.pAudioBufferList; + MA_ASSERT(pRenderedBufferList); /* When you call AudioUnitRender(), Core Audio tries to be helpful by setting the mDataByteSize to the number of bytes @@ -26290,7 +32032,7 @@ static OSStatus ma_on_input__coreaudio(void* pUserData, AudioUnitRenderActionFla status = ((ma_AudioUnitRender_proc)pDevice->pContext->coreaudio.AudioUnitRender)((AudioUnit)pDevice->coreaudio.audioUnitCapture, pActionFlags, pTimeStamp, busNumber, frameCount, pRenderedBufferList); if (status != noErr) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, " ERROR: AudioUnitRender() failed with %d\n", status); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, " ERROR: AudioUnitRender() failed with %d.\n", (int)status); return status; } @@ -26298,7 +32040,7 @@ static OSStatus ma_on_input__coreaudio(void* pUserData, AudioUnitRenderActionFla for (iBuffer = 0; iBuffer < pRenderedBufferList->mNumberBuffers; ++iBuffer) { if (pRenderedBufferList->mBuffers[iBuffer].mNumberChannels == pDevice->capture.internalChannels) { ma_device_handle_backend_data_callback(pDevice, NULL, pRenderedBufferList->mBuffers[iBuffer].mData, frameCount); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, " mDataByteSize=%d\n", pRenderedBufferList->mBuffers[iBuffer].mDataByteSize); + /*ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, " mDataByteSize=%d.\n", (int)pRenderedBufferList->mBuffers[iBuffer].mDataByteSize);*/ } else { /* This case is where the number of channels in the output buffer do not match our internal channels. It could mean that it's @@ -26321,7 +32063,7 @@ static OSStatus ma_on_input__coreaudio(void* pUserData, AudioUnitRenderActionFla framesRemaining -= framesToSend; } - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, " WARNING: Outputting silence. frameCount=%d, mNumberChannels=%d, mDataByteSize=%d\n", frameCount, pRenderedBufferList->mBuffers[iBuffer].mNumberChannels, pRenderedBufferList->mBuffers[iBuffer].mDataByteSize); + /*ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, " WARNING: Outputting silence. frameCount=%d, mNumberChannels=%d, mDataByteSize=%d\n", (int)frameCount, (int)pRenderedBufferList->mBuffers[iBuffer].mNumberChannels, (int)pRenderedBufferList->mBuffers[iBuffer].mDataByteSize);*/ } } } else { @@ -26383,24 +32125,17 @@ static void on_start_stop__coreaudio(void* pUserData, AudioUnit audioUnit, Audio can try waiting on the same lock. I'm going to try working around this by not calling any Core Audio APIs in the callback when the device has been stopped or uninitialized. */ - if (ma_device_get_state(pDevice) == MA_STATE_UNINITIALIZED || ma_device_get_state(pDevice) == MA_STATE_STOPPING || ma_device_get_state(pDevice) == MA_STATE_STOPPED) { - ma_stop_proc onStop = pDevice->onStop; - if (onStop) { - onStop(pDevice); - } - - ma_event_signal(&pDevice->coreaudio.stopEvent); + if (ma_device_get_state(pDevice) == ma_device_state_uninitialized || ma_device_get_state(pDevice) == ma_device_state_stopping || ma_device_get_state(pDevice) == ma_device_state_stopped) { + ma_device__on_notification_stopped(pDevice); } else { UInt32 isRunning; UInt32 isRunningSize = sizeof(isRunning); OSStatus status = ((ma_AudioUnitGetProperty_proc)pDevice->pContext->coreaudio.AudioUnitGetProperty)(audioUnit, kAudioOutputUnitProperty_IsRunning, scope, element, &isRunning, &isRunningSize); if (status != noErr) { - return; /* Don't really know what to do in this case... just ignore it, I suppose... */ + goto done; /* Don't really know what to do in this case... just ignore it, I suppose... */ } if (!isRunning) { - ma_stop_proc onStop; - /* The stop event is a bit annoying in Core Audio because it will be called when we automatically switch the default device. Some scenarios to consider: @@ -26414,12 +32149,12 @@ static void on_start_stop__coreaudio(void* pUserData, AudioUnit audioUnit, Audio /* It looks like the device is switching through an external event, such as the user unplugging the device or changing the default device via the operating system's sound settings. If we're re-initializing the device, we just terminate because we want the stopping of the - device to be seamless to the client (we don't want them receiving the onStop event and thinking that the device has stopped when it + device to be seamless to the client (we don't want them receiving the stopped event and thinking that the device has stopped when it hasn't!). */ if (((audioUnit == pDevice->coreaudio.audioUnitPlayback) && pDevice->coreaudio.isSwitchingPlaybackDevice) || ((audioUnit == pDevice->coreaudio.audioUnitCapture) && pDevice->coreaudio.isSwitchingCaptureDevice)) { - return; + goto done; } /* @@ -26427,20 +32162,21 @@ static void on_start_stop__coreaudio(void* pUserData, AudioUnit audioUnit, Audio will try switching to the new default device seamlessly. We need to somehow find a way to determine whether or not Core Audio will most likely be successful in switching to the new device. - TODO: Try to predict if Core Audio will switch devices. If not, the onStop callback needs to be posted. + TODO: Try to predict if Core Audio will switch devices. If not, the stopped callback needs to be posted. */ - return; + goto done; } /* Getting here means we need to stop the device. */ - onStop = pDevice->onStop; - if (onStop) { - onStop(pDevice); - } + ma_device__on_notification_stopped(pDevice); } } (void)propertyID; /* Unused. */ + +done: + /* Always signal the stop event. It's possible for the "else" case to get hit which can happen during an interruption. */ + ma_event_signal(&pDevice->coreaudio.stopEvent); } #if defined(MA_APPLE_DESKTOP) @@ -26491,7 +32227,7 @@ static OSStatus ma_default_device_changed__coreaudio(AudioObjectID objectID, UIn ma_device__post_init_setup(pDevice, deviceType); /* Restart the device if required. If this fails we need to stop the device entirely. */ - if (ma_device_get_state(pDevice) == MA_STATE_STARTED) { + if (ma_device_get_state(pDevice) == ma_device_state_started) { OSStatus status; if (deviceType == ma_device_type_playback) { status = ((ma_AudioOutputUnitStart_proc)pDevice->pContext->coreaudio.AudioOutputUnitStart)((AudioUnit)pDevice->coreaudio.audioUnitPlayback); @@ -26499,7 +32235,7 @@ static OSStatus ma_default_device_changed__coreaudio(AudioObjectID objectID, UIn if (pDevice->type == ma_device_type_duplex) { ((ma_AudioOutputUnitStop_proc)pDevice->pContext->coreaudio.AudioOutputUnitStop)((AudioUnit)pDevice->coreaudio.audioUnitCapture); } - ma_device__set_state(pDevice, MA_STATE_STOPPED); + ma_device__set_state(pDevice, ma_device_state_stopped); } } else if (deviceType == ma_device_type_capture) { status = ((ma_AudioOutputUnitStart_proc)pDevice->pContext->coreaudio.AudioOutputUnitStart)((AudioUnit)pDevice->coreaudio.audioUnitCapture); @@ -26507,10 +32243,12 @@ static OSStatus ma_default_device_changed__coreaudio(AudioObjectID objectID, UIn if (pDevice->type == ma_device_type_duplex) { ((ma_AudioOutputUnitStop_proc)pDevice->pContext->coreaudio.AudioOutputUnitStop)((AudioUnit)pDevice->coreaudio.audioUnitPlayback); } - ma_device__set_state(pDevice, MA_STATE_STOPPED); + ma_device__set_state(pDevice, ma_device_state_stopped); } } } + + ma_device__on_notification_rerouted(pDevice); } } } @@ -26574,7 +32312,7 @@ static ma_result ma_context__uninit_device_tracking__coreaudio(ma_context* pCont /* At this point there should be no tracked devices. If not there's an error somewhere. */ if (g_ppTrackedDevices_CoreAudio != NULL) { - ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_WARNING, "You have uninitialized all contexts while an associated device is still active.", MA_INVALID_OPERATION); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_WARNING, "You have uninitialized all contexts while an associated device is still active."); ma_spinlock_unlock(&g_DeviceTrackingInitLock_CoreAudio); return MA_INVALID_OPERATION; } @@ -26595,17 +32333,15 @@ static ma_result ma_device__track__coreaudio(ma_device* pDevice) { /* Allocate memory if required. */ if (g_TrackedDeviceCap_CoreAudio <= g_TrackedDeviceCount_CoreAudio) { - ma_uint32 oldCap; ma_uint32 newCap; ma_device** ppNewDevices; - oldCap = g_TrackedDeviceCap_CoreAudio; newCap = g_TrackedDeviceCap_CoreAudio * 2; if (newCap == 0) { newCap = 1; } - ppNewDevices = (ma_device**)ma__realloc_from_callbacks(g_ppTrackedDevices_CoreAudio, sizeof(*g_ppTrackedDevices_CoreAudio)*newCap, sizeof(*g_ppTrackedDevices_CoreAudio)*oldCap, &pDevice->pContext->allocationCallbacks); + ppNewDevices = (ma_device**)ma_realloc(g_ppTrackedDevices_CoreAudio, sizeof(*g_ppTrackedDevices_CoreAudio)*newCap, &pDevice->pContext->allocationCallbacks); if (ppNewDevices == NULL) { ma_mutex_unlock(&g_DeviceTrackingMutex_CoreAudio); return MA_OUT_OF_MEMORY; @@ -26642,7 +32378,7 @@ static ma_result ma_device__untrack__coreaudio(ma_device* pDevice) /* If there's nothing else in the list we need to free memory. */ if (g_TrackedDeviceCount_CoreAudio == 0) { - ma__free_from_callbacks(g_ppTrackedDevices_CoreAudio, &pDevice->pContext->allocationCallbacks); + ma_free(g_ppTrackedDevices_CoreAudio, &pDevice->pContext->allocationCallbacks); g_ppTrackedDevices_CoreAudio = NULL; g_TrackedDeviceCap_CoreAudio = 0; } @@ -26658,30 +32394,71 @@ static ma_result ma_device__untrack__coreaudio(ma_device* pDevice) #endif #if defined(MA_APPLE_MOBILE) -@interface ma_router_change_handler:NSObject { +@interface ma_ios_notification_handler:NSObject { ma_device* m_pDevice; } @end -@implementation ma_router_change_handler +@implementation ma_ios_notification_handler -(id)init:(ma_device*)pDevice { self = [super init]; m_pDevice = pDevice; + /* For route changes. */ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handle_route_change:) name:AVAudioSessionRouteChangeNotification object:[AVAudioSession sharedInstance]]; + /* For interruptions. */ + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handle_interruption:) name:AVAudioSessionInterruptionNotification object:[AVAudioSession sharedInstance]]; + return self; } -(void)dealloc { [self remove_handler]; + + #if defined(__has_feature) + #if !__has_feature(objc_arc) + [super dealloc]; + #endif + #endif } -(void)remove_handler { [[NSNotificationCenter defaultCenter] removeObserver:self name:AVAudioSessionRouteChangeNotification object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:AVAudioSessionInterruptionNotification object:nil]; +} + +-(void)handle_interruption:(NSNotification*)pNotification +{ + NSInteger type = [[[pNotification userInfo] objectForKey:AVAudioSessionInterruptionTypeKey] integerValue]; + switch (type) + { + case AVAudioSessionInterruptionTypeBegan: + { + ma_log_postf(ma_device_get_log(m_pDevice), MA_LOG_LEVEL_INFO, "[Core Audio] Interruption: AVAudioSessionInterruptionTypeBegan\n"); + + /* + Core Audio will have stopped the internal device automatically, but we need explicitly + stop it at a higher level to ensure miniaudio-specific state is updated for consistency. + */ + ma_device_stop(m_pDevice); + + /* + Fire the notification after the device has been stopped to ensure it's in the correct + state when the notification handler is invoked. + */ + ma_device__on_notification_interruption_began(m_pDevice); + } break; + + case AVAudioSessionInterruptionTypeEnded: + { + ma_log_postf(ma_device_get_log(m_pDevice), MA_LOG_LEVEL_INFO, "[Core Audio] Interruption: AVAudioSessionInterruptionTypeEnded\n"); + ma_device__on_notification_interruption_ended(m_pDevice); + } break; + } } -(void)handle_route_change:(NSNotification*)pNotification @@ -26693,66 +32470,45 @@ static ma_result ma_device__untrack__coreaudio(ma_device* pDevice) { case AVAudioSessionRouteChangeReasonOldDeviceUnavailable: { - ma_log_postf(ma_device_get_log(m_pDevice), MA_LOG_LEVEL_DEBUG, "[Core Audio] Route Changed: AVAudioSessionRouteChangeReasonOldDeviceUnavailable\n"); + ma_log_postf(ma_device_get_log(m_pDevice), MA_LOG_LEVEL_INFO, "[Core Audio] Route Changed: AVAudioSessionRouteChangeReasonOldDeviceUnavailable\n"); } break; case AVAudioSessionRouteChangeReasonNewDeviceAvailable: { - ma_log_postf(ma_device_get_log(m_pDevice), MA_LOG_LEVEL_DEBUG, "[Core Audio] Route Changed: AVAudioSessionRouteChangeReasonNewDeviceAvailable\n"); + ma_log_postf(ma_device_get_log(m_pDevice), MA_LOG_LEVEL_INFO, "[Core Audio] Route Changed: AVAudioSessionRouteChangeReasonNewDeviceAvailable\n"); } break; case AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory: { - ma_log_postf(ma_device_get_log(m_pDevice), MA_LOG_LEVEL_DEBUG, "[Core Audio] Route Changed: AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory\n"); + ma_log_postf(ma_device_get_log(m_pDevice), MA_LOG_LEVEL_INFO, "[Core Audio] Route Changed: AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory\n"); } break; case AVAudioSessionRouteChangeReasonWakeFromSleep: { - ma_log_postf(ma_device_get_log(m_pDevice), MA_LOG_LEVEL_DEBUG, "[Core Audio] Route Changed: AVAudioSessionRouteChangeReasonWakeFromSleep\n"); + ma_log_postf(ma_device_get_log(m_pDevice), MA_LOG_LEVEL_INFO, "[Core Audio] Route Changed: AVAudioSessionRouteChangeReasonWakeFromSleep\n"); } break; case AVAudioSessionRouteChangeReasonOverride: { - ma_log_postf(ma_device_get_log(m_pDevice), MA_LOG_LEVEL_DEBUG, "[Core Audio] Route Changed: AVAudioSessionRouteChangeReasonOverride\n"); + ma_log_postf(ma_device_get_log(m_pDevice), MA_LOG_LEVEL_INFO, "[Core Audio] Route Changed: AVAudioSessionRouteChangeReasonOverride\n"); } break; case AVAudioSessionRouteChangeReasonCategoryChange: { - ma_log_postf(ma_device_get_log(m_pDevice), MA_LOG_LEVEL_DEBUG, "[Core Audio] Route Changed: AVAudioSessionRouteChangeReasonCategoryChange\n"); + ma_log_postf(ma_device_get_log(m_pDevice), MA_LOG_LEVEL_INFO, "[Core Audio] Route Changed: AVAudioSessionRouteChangeReasonCategoryChange\n"); } break; case AVAudioSessionRouteChangeReasonUnknown: default: { - ma_log_postf(ma_device_get_log(m_pDevice), MA_LOG_LEVEL_DEBUG, "[Core Audio] Route Changed: AVAudioSessionRouteChangeReasonUnknown\n"); + ma_log_postf(ma_device_get_log(m_pDevice), MA_LOG_LEVEL_INFO, "[Core Audio] Route Changed: AVAudioSessionRouteChangeReasonUnknown\n"); } break; } ma_log_postf(ma_device_get_log(m_pDevice), MA_LOG_LEVEL_DEBUG, "[Core Audio] Changing Route. inputNumberChannels=%d; outputNumberOfChannels=%d\n", (int)pSession.inputNumberOfChannels, (int)pSession.outputNumberOfChannels); - - /* Temporarily disabling this section of code because it appears to be causing errors. */ -#if 0 - ma_uint32 previousState = ma_device_get_state(m_pDevice); - - if (previousState == MA_STATE_STARTED) { - ma_device_stop(m_pDevice); - } - - if (m_pDevice->type == ma_device_type_capture || m_pDevice->type == ma_device_type_duplex) { - m_pDevice->capture.internalChannels = (ma_uint32)pSession.inputNumberOfChannels; - m_pDevice->capture.internalSampleRate = (ma_uint32)pSession.sampleRate; - ma_device__post_init_setup(m_pDevice, ma_device_type_capture); - } - if (m_pDevice->type == ma_device_type_playback || m_pDevice->type == ma_device_type_duplex) { - m_pDevice->playback.internalChannels = (ma_uint32)pSession.outputNumberOfChannels; - m_pDevice->playback.internalSampleRate = (ma_uint32)pSession.sampleRate; - ma_device__post_init_setup(m_pDevice, ma_device_type_playback); - } - - if (previousState == MA_STATE_STARTED) { - ma_device_start(m_pDevice); - } -#endif + + /* Let the application know about the route change. */ + ma_device__on_notification_rerouted(m_pDevice); } @end #endif @@ -26760,7 +32516,7 @@ static ma_result ma_device__untrack__coreaudio(ma_device* pDevice) static ma_result ma_device_uninit__coreaudio(ma_device* pDevice) { MA_ASSERT(pDevice != NULL); - MA_ASSERT(ma_device_get_state(pDevice) == MA_STATE_UNINITIALIZED); + MA_ASSERT(ma_device_get_state(pDevice) == ma_device_state_uninitialized); #if defined(MA_APPLE_DESKTOP) /* @@ -26770,9 +32526,9 @@ static ma_result ma_device_uninit__coreaudio(ma_device* pDevice) ma_device__untrack__coreaudio(pDevice); #endif #if defined(MA_APPLE_MOBILE) - if (pDevice->coreaudio.pRouteChangeHandler != NULL) { - ma_router_change_handler* pRouteChangeHandler = (__bridge_transfer ma_router_change_handler*)pDevice->coreaudio.pRouteChangeHandler; - [pRouteChangeHandler remove_handler]; + if (pDevice->coreaudio.pNotificationHandler != NULL) { + ma_ios_notification_handler* pNotificationHandler = (MA_BRIDGE_TRANSFER ma_ios_notification_handler*)pDevice->coreaudio.pNotificationHandler; + [pNotificationHandler remove_handler]; } #endif @@ -26784,7 +32540,7 @@ static ma_result ma_device_uninit__coreaudio(ma_device* pDevice) } if (pDevice->coreaudio.pAudioBufferList) { - ma__free_from_callbacks(pDevice->coreaudio.pAudioBufferList, &pDevice->pContext->allocationCallbacks); + ma_free(pDevice->coreaudio.pAudioBufferList, &pDevice->pContext->allocationCallbacks); } return MA_SUCCESS; @@ -26832,8 +32588,6 @@ static ma_result ma_device_init_internal__coreaudio(ma_context* pContext, ma_dev AURenderCallbackStruct callbackInfo; #if defined(MA_APPLE_DESKTOP) AudioObjectID deviceObjectID; -#else - UInt32 actualPeriodSizeInFramesSize = sizeof(actualPeriodSizeInFrames); #endif /* This API should only be used for a single device type: playback or capture. No full-duplex mode. */ @@ -27087,12 +32841,12 @@ static ma_result ma_device_init_internal__coreaudio(ma_context* pContext, ma_dev } #else /* Fall back to default assumptions. */ - ma_get_standard_channel_map(ma_standard_channel_map_default, pData->channelsOut, pData->channelMapOut); + ma_channel_map_init_standard(ma_standard_channel_map_default, pData->channelMapOut, ma_countof(pData->channelMapOut), pData->channelsOut); #endif } #else /* TODO: Figure out how to get the channel map using AVAudioSession. */ - ma_get_standard_channel_map(ma_standard_channel_map_default, pData->channelsOut, pData->channelMapOut); + ma_channel_map_init_standard(ma_standard_channel_map_default, pData->channelMapOut, ma_countof(pData->channelMapOut), pData->channelsOut); #endif @@ -27118,13 +32872,16 @@ static ma_result ma_device_init_internal__coreaudio(ma_context* pContext, ma_dev } #else /* - I don't know how to configure buffer sizes on iOS so for now we're not allowing it to be configured. Instead we're - just going to set it to the value of kAudioUnitProperty_MaximumFramesPerSlice. + On iOS, the size of the IO buffer needs to be specified in seconds and is a floating point + number. I don't trust any potential truncation errors due to converting from float to integer + so I'm going to explicitly set the actual period size to the next power of 2. */ - status = ((ma_AudioUnitGetProperty_proc)pContext->coreaudio.AudioUnitGetProperty)(pData->audioUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0, &actualPeriodSizeInFrames, &actualPeriodSizeInFramesSize); - if (status != noErr) { - ((ma_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)(pData->audioUnit); - return ma_result_from_OSStatus(status); + @autoreleasepool { + AVAudioSession* pAudioSession = [AVAudioSession sharedInstance]; + MA_ASSERT(pAudioSession != NULL); + + [pAudioSession setPreferredIOBufferDuration:((float)actualPeriodSizeInFrames / pAudioSession.sampleRate) error:nil]; + actualPeriodSizeInFrames = ma_next_power_of_2((ma_uint32)(pAudioSession.IOBufferDuration * pAudioSession.sampleRate)); } #endif @@ -27189,7 +32946,7 @@ static ma_result ma_device_init_internal__coreaudio(ma_context* pContext, ma_dev /* Initialize the audio unit. */ status = ((ma_AudioUnitInitialize_proc)pContext->coreaudio.AudioUnitInitialize)(pData->audioUnit); if (status != noErr) { - ma__free_from_callbacks(pData->pAudioBufferList, &pContext->allocationCallbacks); + ma_free(pData->pAudioBufferList, &pContext->allocationCallbacks); pData->pAudioBufferList = NULL; ((ma_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)(pData->audioUnit); return ma_result_from_OSStatus(status); @@ -27236,7 +32993,7 @@ static ma_result ma_device_reinit_internal__coreaudio(ma_device* pDevice, ma_dev ((ma_AudioComponentInstanceDispose_proc)pDevice->pContext->coreaudio.AudioComponentInstanceDispose)((AudioUnit)pDevice->coreaudio.audioUnitCapture); } if (pDevice->coreaudio.pAudioBufferList) { - ma__free_from_callbacks(pDevice->coreaudio.pAudioBufferList, &pDevice->pContext->allocationCallbacks); + ma_free(pDevice->coreaudio.pAudioBufferList, &pDevice->pContext->allocationCallbacks); } } else if (deviceType == ma_device_type_playback) { data.formatIn = pDevice->playback.format; @@ -27269,6 +33026,7 @@ static ma_result ma_device_reinit_internal__coreaudio(ma_device* pDevice, ma_dev if (deviceType == ma_device_type_capture) { #if defined(MA_APPLE_DESKTOP) pDevice->coreaudio.deviceObjectIDCapture = (ma_uint32)data.deviceObjectID; + ma_get_AudioObject_uid(pDevice->pContext, pDevice->coreaudio.deviceObjectIDCapture, sizeof(pDevice->capture.id.coreaudio), pDevice->capture.id.coreaudio); #endif pDevice->coreaudio.audioUnitCapture = (ma_ptr)data.audioUnit; pDevice->coreaudio.pAudioBufferList = (ma_ptr)data.pAudioBufferList; @@ -27283,6 +33041,7 @@ static ma_result ma_device_reinit_internal__coreaudio(ma_device* pDevice, ma_dev } else if (deviceType == ma_device_type_playback) { #if defined(MA_APPLE_DESKTOP) pDevice->coreaudio.deviceObjectIDPlayback = (ma_uint32)data.deviceObjectID; + ma_get_AudioObject_uid(pDevice->pContext, pDevice->coreaudio.deviceObjectIDPlayback, sizeof(pDevice->playback.id.coreaudio), pDevice->playback.id.coreaudio); #endif pDevice->coreaudio.audioUnitPlayback = (ma_ptr)data.audioUnit; @@ -27360,6 +33119,8 @@ static ma_result ma_device_init__coreaudio(ma_device* pDevice, const ma_device_c pDescriptorCapture->periodCount = data.periodsOut; #if defined(MA_APPLE_DESKTOP) + ma_get_AudioObject_uid(pDevice->pContext, pDevice->coreaudio.deviceObjectIDCapture, sizeof(pDevice->capture.id.coreaudio), pDevice->capture.id.coreaudio); + /* If we are using the default device we'll need to listen for changes to the system's default device so we can seemlessly switch the device in the background. @@ -27398,7 +33159,7 @@ static ma_result ma_device_init__coreaudio(ma_device* pDevice, const ma_device_c if (pConfig->deviceType == ma_device_type_duplex) { ((ma_AudioComponentInstanceDispose_proc)pDevice->pContext->coreaudio.AudioComponentInstanceDispose)((AudioUnit)pDevice->coreaudio.audioUnitCapture); if (pDevice->coreaudio.pAudioBufferList) { - ma__free_from_callbacks(pDevice->coreaudio.pAudioBufferList, &pDevice->pContext->allocationCallbacks); + ma_free(pDevice->coreaudio.pAudioBufferList, &pDevice->pContext->allocationCallbacks); } } return result; @@ -27422,6 +33183,8 @@ static ma_result ma_device_init__coreaudio(ma_device* pDevice, const ma_device_c pDescriptorPlayback->periodCount = data.periodsOut; #if defined(MA_APPLE_DESKTOP) + ma_get_AudioObject_uid(pDevice->pContext, pDevice->coreaudio.deviceObjectIDPlayback, sizeof(pDevice->playback.id.coreaudio), pDevice->playback.id.coreaudio); + /* If we are using the default device we'll need to listen for changes to the system's default device so we can seemlessly switch the device in the background. @@ -27445,7 +33208,7 @@ static ma_result ma_device_init__coreaudio(ma_device* pDevice, const ma_device_c differently on non-Desktop Apple platforms. */ #if defined(MA_APPLE_MOBILE) - pDevice->coreaudio.pRouteChangeHandler = (__bridge_retained void*)[[ma_router_change_handler alloc] init:pDevice]; + pDevice->coreaudio.pNotificationHandler = (MA_BRIDGE_RETAINED void*)[[ma_ios_notification_handler alloc] init:pDevice]; #endif return MA_SUCCESS; @@ -27510,7 +33273,8 @@ static ma_result ma_context_uninit__coreaudio(ma_context* pContext) #if defined(MA_APPLE_MOBILE) if (!pContext->coreaudio.noAudioSessionDeactivate) { if (![[AVAudioSession sharedInstance] setActive:false error:nil]) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "Failed to deactivate audio session.", MA_FAILED_TO_INIT_BACKEND); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "Failed to deactivate audio session."); + return MA_FAILED_TO_INIT_BACKEND; } } #endif @@ -27529,7 +33293,7 @@ static ma_result ma_context_uninit__coreaudio(ma_context* pContext) return MA_SUCCESS; } -#if defined(MA_APPLE_MOBILE) +#if defined(MA_APPLE_MOBILE) && defined(__IPHONE_12_0) static AVAudioSessionCategory ma_to_AVAudioSessionCategory(ma_ios_session_category category) { /* The "default" and "none" categories are treated different and should not be used as an input into this function. */ @@ -27586,15 +33350,21 @@ static ma_result ma_context_init__coreaudio(ma_context* pContext, const ma_conte } } else { if (pConfig->coreaudio.sessionCategory != ma_ios_session_category_none) { + #if defined(__IPHONE_12_0) if (![pAudioSession setCategory: ma_to_AVAudioSessionCategory(pConfig->coreaudio.sessionCategory) withOptions:options error:nil]) { return MA_INVALID_OPERATION; /* Failed to set session category. */ } + #else + /* Ignore the session category on version 11 and older, but post a warning. */ + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_WARNING, "Session category only supported in iOS 12 and newer."); + #endif } } if (!pConfig->coreaudio.noAudioSessionActivate) { if (![pAudioSession setActive:true error:nil]) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "Failed to activate audio session.", MA_FAILED_TO_INIT_BACKEND); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "Failed to activate audio session."); + return MA_FAILED_TO_INIT_BACKEND; } } } @@ -28255,7 +34025,7 @@ static ma_result ma_device_init_handle__sndio(ma_device* pDevice, const ma_devic MA_ASSERT(pDevice != NULL); if (deviceType == ma_device_type_capture) { - openFlags = MA_SIO_REC; + openFlags = MA_SIO_REC; } else { openFlags = MA_SIO_PLAY; } @@ -28272,13 +34042,15 @@ static ma_result ma_device_init_handle__sndio(ma_device* pDevice, const ma_devic handle = (ma_ptr)((ma_sio_open_proc)pDevice->pContext->sndio.sio_open)(pDeviceName, openFlags, 0); if (handle == NULL) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[sndio] Failed to open device.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[sndio] Failed to open device."); + return MA_FAILED_TO_OPEN_BACKEND_DEVICE; } /* We need to retrieve the device caps to determine the most appropriate format to use. */ if (((ma_sio_getcap_proc)pDevice->pContext->sndio.sio_getcap)((struct ma_sio_hdl*)handle, &caps) == 0) { ((ma_sio_close_proc)pDevice->pContext->sndio.sio_close)((struct ma_sio_hdl*)handle); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[sndio] Failed to retrieve device caps.", MA_ERROR); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[sndio] Failed to retrieve device caps."); + return MA_ERROR; } /* @@ -28372,12 +34144,14 @@ static ma_result ma_device_init_handle__sndio(ma_device* pDevice, const ma_devic if (((ma_sio_setpar_proc)pDevice->pContext->sndio.sio_setpar)((struct ma_sio_hdl*)handle, &par) == 0) { ((ma_sio_close_proc)pDevice->pContext->sndio.sio_close)((struct ma_sio_hdl*)handle); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[sndio] Failed to set buffer size.", MA_FORMAT_NOT_SUPPORTED); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[sndio] Failed to set buffer size."); + return MA_ERROR; } if (((ma_sio_getpar_proc)pDevice->pContext->sndio.sio_getpar)((struct ma_sio_hdl*)handle, &par) == 0) { ((ma_sio_close_proc)pDevice->pContext->sndio.sio_close)((struct ma_sio_hdl*)handle); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[sndio] Failed to retrieve buffer size.", MA_FORMAT_NOT_SUPPORTED); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[sndio] Failed to retrieve buffer size."); + return MA_ERROR; } internalFormat = ma_format_from_sio_enc__sndio(par.bits, par.bps, par.sig, par.le, par.msb); @@ -28395,23 +34169,10 @@ static ma_result ma_device_init_handle__sndio(ma_device* pDevice, const ma_devic pDescriptor->format = internalFormat; pDescriptor->channels = internalChannels; pDescriptor->sampleRate = internalSampleRate; - ma_get_standard_channel_map(ma_standard_channel_map_sndio, pDevice->playback.internalChannels, pDevice->playback.internalChannelMap); + ma_channel_map_init_standard(ma_standard_channel_map_sndio, pDescriptor->channelMap, ma_countof(pDescriptor->channelMap), internalChannels); pDescriptor->periodSizeInFrames = internalPeriodSizeInFrames; pDescriptor->periodCount = internalPeriods; - #ifdef MA_DEBUG_OUTPUT - { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "DEVICE INFO\n"); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, " Format: %s\n", ma_get_format_name(internalFormat)); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, " Channels: %d\n", internalChannels); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, " Sample Rate: %d\n", internalSampleRate); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, " Period Size: %d\n", internalPeriodSizeInFrames); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, " Periods: %d\n", internalPeriods); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, " appbufsz: %d\n", par.appbufsz); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, " round: %d\n", par.round); - } - #endif - return MA_SUCCESS; } @@ -28492,7 +34253,8 @@ static ma_result ma_device_write__sndio(ma_device* pDevice, const void* pPCMFram result = ((ma_sio_write_proc)pDevice->pContext->sndio.sio_write)((struct ma_sio_hdl*)pDevice->sndio.handlePlayback, pPCMFrames, frameCount * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels)); if (result == 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[sndio] Failed to send data from the client to the device.", MA_IO_ERROR); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[sndio] Failed to send data from the client to the device."); + return MA_IO_ERROR; } if (pFramesWritten != NULL) { @@ -28512,7 +34274,8 @@ static ma_result ma_device_read__sndio(ma_device* pDevice, void* pPCMFrames, ma_ result = ((ma_sio_read_proc)pDevice->pContext->sndio.sio_read)((struct ma_sio_hdl*)pDevice->sndio.handleCapture, pPCMFrames, frameCount * ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels)); if (result == 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[sndio] Failed to read data from the device to be sent to the device.", MA_IO_ERROR); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[sndio] Failed to read data from the device to be sent to the device."); + return MA_IO_ERROR; } if (pFramesRead != NULL) { @@ -29055,7 +34818,8 @@ static ma_result ma_device_init_fd__audio4(ma_device* pDevice, const ma_device_c } if (fd == -1) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] Failed to open device.", ma_result_from_errno(errno)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] Failed to open device."); + return ma_result_from_errno(errno); } #if !defined(MA_AUDIO4_USE_NEW_API) /* Old API */ @@ -29107,12 +34871,14 @@ static ma_result ma_device_init_fd__audio4(ma_device* pDevice, const ma_device_c if (ioctl(fd, AUDIO_SETINFO, &fdInfo) < 0) { close(fd); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] Failed to set device format. AUDIO_SETINFO failed.", MA_FORMAT_NOT_SUPPORTED); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] Failed to set device format. AUDIO_SETINFO failed."); + return ma_result_from_errno(errno); } if (ioctl(fd, AUDIO_GETINFO, &fdInfo) < 0) { close(fd); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] AUDIO_GETINFO failed.", MA_FORMAT_NOT_SUPPORTED); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] AUDIO_GETINFO failed."); + return ma_result_from_errno(errno); } if (deviceType == ma_device_type_capture) { @@ -29127,7 +34893,8 @@ static ma_result ma_device_init_fd__audio4(ma_device* pDevice, const ma_device_c if (internalFormat == ma_format_unknown) { close(fd); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] The device's internal device format is not supported by miniaudio. The device is unusable.", MA_FORMAT_NOT_SUPPORTED); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] The device's internal device format is not supported by miniaudio. The device is unusable."); + return MA_FORMAT_NOT_SUPPORTED; } /* Buffer. */ @@ -29153,7 +34920,8 @@ static ma_result ma_device_init_fd__audio4(ma_device* pDevice, const ma_device_c fdInfo.blocksize = internalPeriodSizeInBytes; if (ioctl(fd, AUDIO_SETINFO, &fdInfo) < 0) { close(fd); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] Failed to set internal buffer size. AUDIO_SETINFO failed.", MA_FORMAT_NOT_SUPPORTED); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] Failed to set internal buffer size. AUDIO_SETINFO failed."); + return ma_result_from_errno(errno); } internalPeriods = fdInfo.hiwat; @@ -29167,7 +34935,8 @@ static ma_result ma_device_init_fd__audio4(ma_device* pDevice, const ma_device_c /* We need to retrieve the format of the device so we can know the channel count and sample rate. Then we can calculate the buffer size. */ if (ioctl(fd, AUDIO_GETPAR, &fdPar) < 0) { close(fd); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] Failed to retrieve initial device parameters.", MA_FORMAT_NOT_SUPPORTED); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] Failed to retrieve initial device parameters."); + return ma_result_from_errno(errno); } internalFormat = ma_format_from_swpar__audio4(&fdPar); @@ -29176,7 +34945,8 @@ static ma_result ma_device_init_fd__audio4(ma_device* pDevice, const ma_device_c if (internalFormat == ma_format_unknown) { close(fd); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] The device's internal device format is not supported by miniaudio. The device is unusable.", MA_FORMAT_NOT_SUPPORTED); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] The device's internal device format is not supported by miniaudio. The device is unusable."); + return MA_FORMAT_NOT_SUPPORTED; } /* Buffer. */ @@ -29196,12 +34966,14 @@ static ma_result ma_device_init_fd__audio4(ma_device* pDevice, const ma_device_c if (ioctl(fd, AUDIO_SETPAR, &fdPar) < 0) { close(fd); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] Failed to set device parameters.", MA_FORMAT_NOT_SUPPORTED); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] Failed to set device parameters."); + return ma_result_from_errno(errno); } if (ioctl(fd, AUDIO_GETPAR, &fdPar) < 0) { close(fd); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] Failed to retrieve actual device parameters.", MA_FORMAT_NOT_SUPPORTED); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] Failed to retrieve actual device parameters."); + return ma_result_from_errno(errno); } } @@ -29215,7 +34987,8 @@ static ma_result ma_device_init_fd__audio4(ma_device* pDevice, const ma_device_c if (internalFormat == ma_format_unknown) { close(fd); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] The device's internal device format is not supported by miniaudio. The device is unusable.", MA_FORMAT_NOT_SUPPORTED); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] The device's internal device format is not supported by miniaudio. The device is unusable."); + return MA_FORMAT_NOT_SUPPORTED; } if (deviceType == ma_device_type_capture) { @@ -29227,7 +35000,7 @@ static ma_result ma_device_init_fd__audio4(ma_device* pDevice, const ma_device_c pDescriptor->format = internalFormat; pDescriptor->channels = internalChannels; pDescriptor->sampleRate = internalSampleRate; - ma_get_standard_channel_map(ma_standard_channel_map_sound4, internalChannels, pDescriptor->channelMap); + ma_channel_map_init_standard(ma_standard_channel_map_sound4, pDescriptor->channelMap, ma_countof(pDescriptor->channelMap), internalChannels); pDescriptor->periodSizeInFrames = internalPeriodSizeInFrames; pDescriptor->periodCount = internalPeriods; @@ -29309,11 +35082,13 @@ static ma_result ma_device_stop_fd__audio4(ma_device* pDevice, int fd) #if !defined(MA_AUDIO4_USE_NEW_API) if (ioctl(fd, AUDIO_FLUSH, 0) < 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] Failed to stop device. AUDIO_FLUSH failed.", ma_result_from_errno(errno)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] Failed to stop device. AUDIO_FLUSH failed."); + return ma_result_from_errno(errno); } #else if (ioctl(fd, AUDIO_STOP, 0) < 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] Failed to stop device. AUDIO_STOP failed.", ma_result_from_errno(errno)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] Failed to stop device. AUDIO_STOP failed."); + return ma_result_from_errno(errno); } #endif @@ -29361,7 +35136,8 @@ static ma_result ma_device_write__audio4(ma_device* pDevice, const void* pPCMFra result = write(pDevice->audio4.fdPlayback, pPCMFrames, frameCount * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels)); if (result < 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] Failed to write data to the device.", ma_result_from_errno(errno)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] Failed to write data to the device."); + return ma_result_from_errno(errno); } if (pFramesWritten != NULL) { @@ -29381,7 +35157,8 @@ static ma_result ma_device_read__audio4(ma_device* pDevice, void* pPCMFrames, ma result = read(pDevice->audio4.fdCapture, pPCMFrames, frameCount * ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels)); if (result < 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] Failed to read data from the device.", ma_result_from_errno(errno)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] Failed to read data from the device."); + return ma_result_from_errno(errno); } if (pFramesRead != NULL) { @@ -29496,7 +35273,8 @@ static ma_result ma_context_enumerate_devices__oss(ma_context* pContext, ma_enum fd = ma_open_temp_device__oss(); if (fd == -1) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[OSS] Failed to open a temporary device for retrieving system information used for device enumeration.", MA_NO_BACKEND); + ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[OSS] Failed to open a temporary device for retrieving system information used for device enumeration."); + return MA_NO_BACKEND; } result = ioctl(fd, SNDCTL_SYSINFO, &si); @@ -29543,7 +35321,8 @@ static ma_result ma_context_enumerate_devices__oss(ma_context* pContext, ma_enum } } else { close(fd); - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[OSS] Failed to retrieve system information for device enumeration.", MA_NO_BACKEND); + ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[OSS] Failed to retrieve system information for device enumeration."); + return MA_NO_BACKEND; } close(fd); @@ -29626,7 +35405,8 @@ static ma_result ma_context_get_device_info__oss(ma_context* pContext, ma_device fdTemp = ma_open_temp_device__oss(); if (fdTemp == -1) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[OSS] Failed to open a temporary device for retrieving system information used for device enumeration.", MA_NO_BACKEND); + ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[OSS] Failed to open a temporary device for retrieving system information used for device enumeration."); + return MA_NO_BACKEND; } result = ioctl(fdTemp, SNDCTL_SYSINFO, &si); @@ -29684,7 +35464,8 @@ static ma_result ma_context_get_device_info__oss(ma_context* pContext, ma_device } } else { close(fdTemp); - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[OSS] Failed to retrieve system information for device enumeration.", MA_NO_BACKEND); + ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[OSS] Failed to retrieve system information for device enumeration."); + return MA_NO_BACKEND; } @@ -29774,7 +35555,8 @@ static ma_result ma_device_init_fd__oss(ma_device* pDevice, const ma_device_conf result = ma_context_open_device__oss(pDevice->pContext, deviceType, pDeviceID, shareMode, &fd); if (result != MA_SUCCESS) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OSS] Failed to open device.", result); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OSS] Failed to open device."); + return result; } /* @@ -29788,21 +35570,24 @@ static ma_result ma_device_init_fd__oss(ma_device* pDevice, const ma_device_conf ossResult = ioctl(fd, SNDCTL_DSP_SETFMT, &ossFormat); if (ossResult == -1) { close(fd); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OSS] Failed to set format.", MA_FORMAT_NOT_SUPPORTED); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OSS] Failed to set format."); + return ma_result_from_errno(errno); } /* Channels. */ ossResult = ioctl(fd, SNDCTL_DSP_CHANNELS, &ossChannels); if (ossResult == -1) { close(fd); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OSS] Failed to set channel count.", MA_FORMAT_NOT_SUPPORTED); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OSS] Failed to set channel count."); + return ma_result_from_errno(errno); } /* Sample Rate. */ ossResult = ioctl(fd, SNDCTL_DSP_SPEED, &ossSampleRate); if (ossResult == -1) { close(fd); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OSS] Failed to set sample rate.", MA_FORMAT_NOT_SUPPORTED); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OSS] Failed to set sample rate."); + return ma_result_from_errno(errno); } /* @@ -29836,7 +35621,8 @@ static ma_result ma_device_init_fd__oss(ma_device* pDevice, const ma_device_conf ossResult = ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &ossFragment); if (ossResult == -1) { close(fd); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OSS] Failed to set fragment size and period count.", MA_FORMAT_NOT_SUPPORTED); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OSS] Failed to set fragment size and period count."); + return ma_result_from_errno(errno); } } @@ -29850,12 +35636,13 @@ static ma_result ma_device_init_fd__oss(ma_device* pDevice, const ma_device_conf pDescriptor->format = ma_format_from_oss(ossFormat); pDescriptor->channels = ossChannels; pDescriptor->sampleRate = ossSampleRate; - ma_get_standard_channel_map(ma_standard_channel_map_sound4, pDescriptor->channels, pDescriptor->channelMap); + ma_channel_map_init_standard(ma_standard_channel_map_sound4, pDescriptor->channelMap, ma_countof(pDescriptor->channelMap), pDescriptor->channels); pDescriptor->periodCount = (ma_uint32)(ossFragment >> 16); pDescriptor->periodSizeInFrames = (ma_uint32)(1 << (ossFragment & 0xFFFF)) / ma_get_bytes_per_frame(pDescriptor->format, pDescriptor->channels); if (pDescriptor->format == ma_format_unknown) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OSS] The device's internal format is not supported by miniaudio.", MA_FORMAT_NOT_SUPPORTED); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OSS] The device's internal format is not supported by miniaudio."); + return MA_FORMAT_NOT_SUPPORTED; } return MA_SUCCESS; @@ -29875,14 +35662,16 @@ static ma_result ma_device_init__oss(ma_device* pDevice, const ma_device_config* if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { ma_result result = ma_device_init_fd__oss(pDevice, pConfig, pDescriptorCapture, ma_device_type_capture); if (result != MA_SUCCESS) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OSS] Failed to open device.", result); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OSS] Failed to open device."); + return result; } } if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { ma_result result = ma_device_init_fd__oss(pDevice, pConfig, pDescriptorPlayback, ma_device_type_playback); if (result != MA_SUCCESS) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OSS] Failed to open device.", result); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OSS] Failed to open device."); + return result; } } @@ -29939,13 +35728,14 @@ static ma_result ma_device_write__oss(ma_device* pDevice, const void* pPCMFrames /* Don't do any processing if the device is stopped. */ deviceState = ma_device_get_state(pDevice); - if (deviceState != MA_STATE_STARTED && deviceState != MA_STATE_STARTING) { + if (deviceState != ma_device_state_started && deviceState != ma_device_state_starting) { return MA_SUCCESS; } resultOSS = write(pDevice->oss.fdPlayback, pPCMFrames, frameCount * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels)); if (resultOSS < 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OSS] Failed to send data from the client to the device.", ma_result_from_errno(errno)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OSS] Failed to send data from the client to the device."); + return ma_result_from_errno(errno); } if (pFramesWritten != NULL) { @@ -29966,13 +35756,14 @@ static ma_result ma_device_read__oss(ma_device* pDevice, void* pPCMFrames, ma_ui /* Don't do any processing if the device is stopped. */ deviceState = ma_device_get_state(pDevice); - if (deviceState != MA_STATE_STARTED && deviceState != MA_STATE_STARTING) { + if (deviceState != ma_device_state_started && deviceState != ma_device_state_starting) { return MA_SUCCESS; } resultOSS = read(pDevice->oss.fdCapture, pPCMFrames, frameCount * ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels)); if (resultOSS < 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OSS] Failed to read data from the device to be sent to the client.", ma_result_from_errno(errno)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OSS] Failed to read data from the device to be sent to the client."); + return ma_result_from_errno(errno); } if (pFramesRead != NULL) { @@ -30004,7 +35795,8 @@ static ma_result ma_context_init__oss(ma_context* pContext, const ma_context_con /* Try opening a temporary device first so we can get version information. This is closed at the end. */ fd = ma_open_temp_device__oss(); if (fd == -1) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[OSS] Failed to open temporary device for retrieving system properties.", MA_NO_BACKEND); /* Looks liks OSS isn't installed, or there are no available devices. */ + ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[OSS] Failed to open temporary device for retrieving system properties."); /* Looks liks OSS isn't installed, or there are no available devices. */ + return MA_NO_BACKEND; } /* Grab the OSS version. */ @@ -30012,7 +35804,8 @@ static ma_result ma_context_init__oss(ma_context* pContext, const ma_context_con result = ioctl(fd, OSS_GETVERSION, &ossVersion); if (result == -1) { close(fd); - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[OSS] Failed to retrieve OSS version.", MA_NO_BACKEND); + ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[OSS] Failed to retrieve OSS version."); + return MA_NO_BACKEND; } /* The file handle to temp device is no longer needed. Close ASAP. */ @@ -30238,14 +36031,29 @@ static void ma_stream_error_callback__aaudio(ma_AAudioStream* pStream, void* pUs (void)error; - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[AAudio] ERROR CALLBACK: error=%d, AAudioStream_getState()=%d\n", error, ((MA_PFN_AAudioStream_getState)pDevice->pContext->aaudio.AAudioStream_getState)(pStream)); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[AAudio] ERROR CALLBACK: error=%d, AAudioStream_getState()=%d\n", error, ((MA_PFN_AAudioStream_getState)pDevice->pContext->aaudio.AAudioStream_getState)(pStream)); /* From the documentation for AAudio, when a device is disconnected all we can do is stop it. However, we cannot stop it from the callback - we need to do it from another thread. Therefore we are going to use an event thread for the AAudio backend to do this cleanly and safely. */ if (((MA_PFN_AAudioStream_getState)pDevice->pContext->aaudio.AAudioStream_getState)(pStream) == MA_AAUDIO_STREAM_STATE_DISCONNECTED) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[AAudio] Device Disconnected.\n"); + /* We need to post a job to the job thread for processing. This will reroute the device by reinitializing the stream. */ + ma_result result; + ma_job job = ma_job_init(MA_JOB_TYPE_DEVICE_AAUDIO_REROUTE); + job.data.device.aaudio.reroute.pDevice = pDevice; + + if (pStream == pDevice->aaudio.pStreamCapture) { + job.data.device.aaudio.reroute.deviceType = ma_device_type_capture; + } else { + job.data.device.aaudio.reroute.deviceType = ma_device_type_playback; + } + + result = ma_device_job_thread_post(&pDevice->pContext->aaudio.jobThread, &job); + if (result != MA_SUCCESS) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[AAudio] Device Disconnected. Failed to post job for rerouting.\n"); + return; + } } } @@ -30391,8 +36199,9 @@ static ma_result ma_open_stream__aaudio(ma_device* pDevice, const ma_device_conf ma_result result; ma_AAudioStreamBuilder* pBuilder; - MA_ASSERT(pConfig != NULL); - MA_ASSERT(pConfig->deviceType != ma_device_type_duplex); /* This function should not be called for a full-duplex device type. */ + MA_ASSERT(pDevice != NULL); + MA_ASSERT(pDescriptor != NULL); + MA_ASSERT(deviceType != ma_device_type_duplex); /* This function should not be called for a full-duplex device type. */ *ppStream = NULL; @@ -30576,9 +36385,9 @@ static ma_result ma_device_init_by_type__aaudio(ma_device* pDevice, const ma_dev /* For the channel map we need to be sure we don't overflow any buffers. */ if (pDescriptor->channels <= MA_MAX_CHANNELS) { - ma_get_standard_channel_map(ma_standard_channel_map_default, pDescriptor->channels, pDescriptor->channelMap); /* <-- Cannot find info on channel order, so assuming a default. */ + ma_channel_map_init_standard(ma_standard_channel_map_default, pDescriptor->channelMap, ma_countof(pDescriptor->channelMap), pDescriptor->channels); /* <-- Cannot find info on channel order, so assuming a default. */ } else { - ma_channel_map_init_blank(MA_MAX_CHANNELS, pDescriptor->channelMap); /* Too many channels. Use a blank channel map. */ + ma_channel_map_init_blank(pDescriptor->channelMap, MA_MAX_CHANNELS); /* Too many channels. Use a blank channel map. */ } bufferCapacityInFrames = ((MA_PFN_AAudioStream_getBufferCapacityInFrames)pDevice->pContext->aaudio.AAudioStream_getBufferCapacityInFrames)(pStream); @@ -30607,11 +36416,10 @@ static ma_result ma_device_init__aaudio(ma_device* pDevice, const ma_device_conf return MA_DEVICE_TYPE_NOT_SUPPORTED; } - /* No exclusive mode with AAudio. */ - if (((pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) && pDescriptorPlayback->shareMode == ma_share_mode_exclusive) || - ((pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) && pDescriptorCapture->shareMode == ma_share_mode_exclusive)) { - return MA_SHARE_MODE_NOT_SUPPORTED; - } + pDevice->aaudio.usage = pConfig->aaudio.usage; + pDevice->aaudio.contentType = pConfig->aaudio.contentType; + pDevice->aaudio.inputPreset = pConfig->aaudio.inputPreset; + pDevice->aaudio.noAutoStartAfterReroute = pConfig->aaudio.noAutoStartAfterReroute; if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { result = ma_device_init_by_type__aaudio(pDevice, pConfig, ma_device_type_capture, pDescriptorCapture, (ma_AAudioStream**)&pDevice->aaudio.pStreamCapture); @@ -30676,6 +36484,10 @@ static ma_result ma_device_stop_stream__aaudio(ma_device* pDevice, ma_AAudioStre This maps with miniaudio's requirement that device's be drained which means we don't need to implement any draining logic. */ + currentState = ((MA_PFN_AAudioStream_getState)pDevice->pContext->aaudio.AAudioStream_getState)(pStream); + if (currentState == MA_AAUDIO_STREAM_STATE_DISCONNECTED) { + return MA_SUCCESS; /* The device is disconnected. Don't try stopping it. */ + } resultAA = ((MA_PFN_AAudioStream_requestStop)pDevice->pContext->aaudio.AAudioStream_requestStop)(pStream); if (resultAA != MA_AAUDIO_OK) { @@ -30726,8 +36538,6 @@ static ma_result ma_device_start__aaudio(ma_device* pDevice) static ma_result ma_device_stop__aaudio(ma_device* pDevice) { - ma_stop_proc onStop; - MA_ASSERT(pDevice != NULL); if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { @@ -30744,11 +36554,131 @@ static ma_result ma_device_stop__aaudio(ma_device* pDevice) } } - onStop = pDevice->onStop; - if (onStop) { - onStop(pDevice); + ma_device__on_notification_stopped(pDevice); + + return MA_SUCCESS; +} + +static ma_result ma_device_reinit__aaudio(ma_device* pDevice, ma_device_type deviceType) +{ + ma_result result; + + MA_ASSERT(pDevice != NULL); + + /* The first thing to do is close the streams. */ + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + ma_close_stream__aaudio(pDevice->pContext, (ma_AAudioStream*)pDevice->aaudio.pStreamCapture); + pDevice->aaudio.pStreamCapture = NULL; } + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + ma_close_stream__aaudio(pDevice->pContext, (ma_AAudioStream*)pDevice->aaudio.pStreamPlayback); + pDevice->aaudio.pStreamPlayback = NULL; + } + + /* Now we need to reinitialize each streams. The hardest part with this is just filling output the config and descriptors. */ + { + ma_device_config deviceConfig; + ma_device_descriptor descriptorPlayback; + ma_device_descriptor descriptorCapture; + + deviceConfig = ma_device_config_init(deviceType); + deviceConfig.playback.pDeviceID = NULL; /* Only doing rerouting with default devices. */ + deviceConfig.playback.shareMode = pDevice->playback.shareMode; + deviceConfig.playback.format = pDevice->playback.format; + deviceConfig.playback.channels = pDevice->playback.channels; + deviceConfig.capture.pDeviceID = NULL; /* Only doing rerouting with default devices. */ + deviceConfig.capture.shareMode = pDevice->capture.shareMode; + deviceConfig.capture.format = pDevice->capture.format; + deviceConfig.capture.channels = pDevice->capture.channels; + deviceConfig.sampleRate = pDevice->sampleRate; + deviceConfig.aaudio.usage = pDevice->aaudio.usage; + deviceConfig.aaudio.contentType = pDevice->aaudio.contentType; + deviceConfig.aaudio.inputPreset = pDevice->aaudio.inputPreset; + deviceConfig.aaudio.noAutoStartAfterReroute = pDevice->aaudio.noAutoStartAfterReroute; + deviceConfig.periods = 1; + + /* Try to get an accurate period size. */ + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + deviceConfig.periodSizeInFrames = pDevice->playback.internalPeriodSizeInFrames; + } else { + deviceConfig.periodSizeInFrames = pDevice->capture.internalPeriodSizeInFrames; + } + + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex || deviceType == ma_device_type_loopback) { + descriptorCapture.pDeviceID = deviceConfig.capture.pDeviceID; + descriptorCapture.shareMode = deviceConfig.capture.shareMode; + descriptorCapture.format = deviceConfig.capture.format; + descriptorCapture.channels = deviceConfig.capture.channels; + descriptorCapture.sampleRate = deviceConfig.sampleRate; + descriptorCapture.periodSizeInFrames = deviceConfig.periodSizeInFrames; + descriptorCapture.periodCount = deviceConfig.periods; + } + + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + descriptorPlayback.pDeviceID = deviceConfig.playback.pDeviceID; + descriptorPlayback.shareMode = deviceConfig.playback.shareMode; + descriptorPlayback.format = deviceConfig.playback.format; + descriptorPlayback.channels = deviceConfig.playback.channels; + descriptorPlayback.sampleRate = deviceConfig.sampleRate; + descriptorPlayback.periodSizeInFrames = deviceConfig.periodSizeInFrames; + descriptorPlayback.periodCount = deviceConfig.periods; + } + + result = ma_device_init__aaudio(pDevice, &deviceConfig, &descriptorPlayback, &descriptorCapture); + if (result != MA_SUCCESS) { + return result; + } + + result = ma_device_post_init(pDevice, deviceType, &descriptorPlayback, &descriptorCapture); + if (result != MA_SUCCESS) { + ma_device_uninit__aaudio(pDevice); + return result; + } + + /* We'll only ever do this in response to a reroute. */ + ma_device__on_notification_rerouted(pDevice); + + /* If the device is started, start the streams. Maybe make this configurable? */ + if (ma_device_get_state(pDevice) == ma_device_state_started) { + if (pDevice->aaudio.noAutoStartAfterReroute == MA_FALSE) { + ma_device_start__aaudio(pDevice); + } else { + ma_device_stop(pDevice); /* Do a full device stop so we set internal state correctly. */ + } + } + + return MA_SUCCESS; + } +} + +static ma_result ma_device_get_info__aaudio(ma_device* pDevice, ma_device_type type, ma_device_info* pDeviceInfo) +{ + ma_AAudioStream* pStream = NULL; + + MA_ASSERT(pDevice != NULL); + MA_ASSERT(type != ma_device_type_duplex); + MA_ASSERT(pDeviceInfo != NULL); + + if (type == ma_device_type_playback) { + pStream = (ma_AAudioStream*)pDevice->aaudio.pStreamCapture; + pDeviceInfo->id.aaudio = pDevice->capture.id.aaudio; + ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); /* Only supporting default devices. */ + } + if (type == ma_device_type_capture) { + pStream = (ma_AAudioStream*)pDevice->aaudio.pStreamPlayback; + pDeviceInfo->id.aaudio = pDevice->playback.id.aaudio; + ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); /* Only supporting default devices. */ + } + + /* Safety. Should never happen. */ + if (pStream == NULL) { + return MA_INVALID_OPERATION; + } + + pDeviceInfo->nativeDataFormatCount = 0; + ma_context_add_native_data_format_from_AAudioStream__aaudio(pDevice->pContext, pStream, 0, pDeviceInfo); + return MA_SUCCESS; } @@ -30758,6 +36688,8 @@ static ma_result ma_context_uninit__aaudio(ma_context* pContext) MA_ASSERT(pContext != NULL); MA_ASSERT(pContext->backend == ma_backend_aaudio); + ma_device_job_thread_uninit(&pContext->aaudio.jobThread, &pContext->allocationCallbacks); + ma_dlclose(pContext, pContext->aaudio.hAAudio); pContext->aaudio.hAAudio = NULL; @@ -30823,10 +36755,47 @@ static ma_result ma_context_init__aaudio(ma_context* pContext, const ma_context_ pCallbacks->onDeviceRead = NULL; /* Not used because AAudio is asynchronous. */ pCallbacks->onDeviceWrite = NULL; /* Not used because AAudio is asynchronous. */ pCallbacks->onDeviceDataLoop = NULL; /* Not used because AAudio is asynchronous. */ + pCallbacks->onDeviceGetInfo = ma_device_get_info__aaudio; + + + /* We need a job thread so we can deal with rerouting. */ + { + ma_result result; + ma_device_job_thread_config jobThreadConfig; + + jobThreadConfig = ma_device_job_thread_config_init(); + + result = ma_device_job_thread_init(&jobThreadConfig, &pContext->allocationCallbacks, &pContext->aaudio.jobThread); + if (result != MA_SUCCESS) { + ma_dlclose(pContext, pContext->aaudio.hAAudio); + pContext->aaudio.hAAudio = NULL; + return result; + } + } + (void)pConfig; return MA_SUCCESS; } + +static ma_result ma_job_process__device__aaudio_reroute(ma_job* pJob) +{ + ma_device* pDevice; + + MA_ASSERT(pJob != NULL); + + pDevice = (ma_device*)pJob->data.device.aaudio.reroute.pDevice; + MA_ASSERT(pDevice != NULL); + + /* Here is where we need to reroute the device. To do this we need to uninitialize the stream and reinitialize it. */ + return ma_device_reinit__aaudio(pDevice, (ma_device_type)pJob->data.device.aaudio.reroute.deviceType); +} +#else +/* Getting here means there is no AAudio backend so we need a no-op job implementation. */ +static ma_result ma_job_process__device__aaudio_reroute(ma_job* pJob) +{ + return ma_job_process__noop(pJob); +} #endif /* AAudio */ @@ -31153,6 +37122,7 @@ return_default_device:; if (cbResult) { ma_device_info deviceInfo; MA_ZERO_OBJECT(&deviceInfo); + deviceInfo.id.opensl = SL_DEFAULTDEVICEID_AUDIOOUTPUT; ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), MA_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); cbResult = callback(pContext, ma_device_type_playback, &deviceInfo, pUserData); } @@ -31161,6 +37131,7 @@ return_default_device:; if (cbResult) { ma_device_info deviceInfo; MA_ZERO_OBJECT(&deviceInfo); + deviceInfo.id.opensl = SL_DEFAULTDEVICEID_AUDIOINPUT; ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); cbResult = callback(pContext, ma_device_type_capture, &deviceInfo, pUserData); } @@ -31259,10 +37230,12 @@ return_default_device: } } - /* Name / Description */ + /* ID and Name / Description */ if (deviceType == ma_device_type_playback) { + pDeviceInfo->id.opensl = SL_DEFAULTDEVICEID_AUDIOOUTPUT; ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); } else { + pDeviceInfo->id.opensl = SL_DEFAULTDEVICEID_AUDIOINPUT; ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); } @@ -31309,7 +37282,7 @@ static void ma_buffer_queue_callback_capture__opensl_android(SLAndroidSimpleBuff */ /* Don't do anything if the device is not started. */ - if (ma_device_get_state(pDevice) != MA_STATE_STARTED) { + if (ma_device_get_state(pDevice) != ma_device_state_started) { return; } @@ -31343,7 +37316,7 @@ static void ma_buffer_queue_callback_playback__opensl_android(SLAndroidSimpleBuf (void)pBufferQueue; /* Don't do anything if the device is not started. */ - if (ma_device_get_state(pDevice) != MA_STATE_STARTED) { + if (ma_device_get_state(pDevice) != ma_device_state_started) { return; } @@ -31380,7 +37353,7 @@ static ma_result ma_device_uninit__opensl(ma_device* pDevice) MA_OPENSL_OBJ(pDevice->opensl.pAudioRecorderObj)->Destroy((SLObjectItf)pDevice->opensl.pAudioRecorderObj); } - ma__free_from_callbacks(pDevice->opensl.pBufferCapture, &pDevice->pContext->allocationCallbacks); + ma_free(pDevice->opensl.pBufferCapture, &pDevice->pContext->allocationCallbacks); } if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { @@ -31391,7 +37364,7 @@ static ma_result ma_device_uninit__opensl(ma_device* pDevice) MA_OPENSL_OBJ(pDevice->opensl.pOutputMixObj)->Destroy((SLObjectItf)pDevice->opensl.pOutputMixObj); } - ma__free_from_callbacks(pDevice->opensl.pBufferPlayback, &pDevice->pContext->allocationCallbacks); + ma_free(pDevice->opensl.pBufferPlayback, &pDevice->pContext->allocationCallbacks); } return MA_SUCCESS; @@ -31428,8 +37401,8 @@ static ma_result ma_SLDataFormat_PCM_init__opensl(ma_format format, ma_uint32 ch #endif pDataFormat->numChannels = channels; - ((SLDataFormat_PCM*)pDataFormat)->samplesPerSec = ma_round_to_standard_sample_rate__opensl(sampleRate) * 1000; /* In millihertz. Annoyingly, the sample rate variable is named differently between SLAndroidDataFormat_PCM_EX and SLDataFormat_PCM */ - pDataFormat->bitsPerSample = ma_get_bytes_per_sample(format)*8; + ((SLDataFormat_PCM*)pDataFormat)->samplesPerSec = ma_round_to_standard_sample_rate__opensl(sampleRate * 1000); /* In millihertz. Annoyingly, the sample rate variable is named differently between SLAndroidDataFormat_PCM_EX and SLDataFormat_PCM */ + pDataFormat->bitsPerSample = ma_get_bytes_per_sample(format) * 8; pDataFormat->channelMask = ma_channel_map_to_channel_mask__opensl(channelMap, channels); pDataFormat->endianness = (ma_is_little_endian()) ? SL_BYTEORDER_LITTLEENDIAN : SL_BYTEORDER_BIGENDIAN; @@ -31556,7 +37529,7 @@ static ma_result ma_device_init__opensl(ma_device* pDevice, const ma_device_conf locatorDevice.locatorType = SL_DATALOCATOR_IODEVICE; locatorDevice.deviceType = SL_IODEVICE_AUDIOINPUT; - locatorDevice.deviceID = (pDescriptorCapture->pDeviceID == NULL) ? SL_DEFAULTDEVICEID_AUDIOINPUT : pDescriptorCapture->pDeviceID->opensl; + locatorDevice.deviceID = SL_DEFAULTDEVICEID_AUDIOINPUT; /* Must always use the default device with Android. */ locatorDevice.device = NULL; source.pLocator = &locatorDevice; @@ -31568,20 +37541,21 @@ static ma_result ma_device_init__opensl(ma_device* pDevice, const ma_device_conf sink.pFormat = (SLDataFormat_PCM*)&pcm; resultSL = (*g_maEngineSL)->CreateAudioRecorder(g_maEngineSL, (SLObjectItf*)&pDevice->opensl.pAudioRecorderObj, &source, &sink, ma_countof(itfIDs), itfIDs, itfIDsRequired); - if (resultSL == SL_RESULT_CONTENT_UNSUPPORTED) { + if (resultSL == SL_RESULT_CONTENT_UNSUPPORTED || resultSL == SL_RESULT_PARAMETER_INVALID) { /* Unsupported format. Fall back to something safer and try again. If this fails, just abort. */ pcm.formatType = SL_DATAFORMAT_PCM; pcm.numChannels = 1; ((SLDataFormat_PCM*)&pcm)->samplesPerSec = SL_SAMPLINGRATE_16; /* The name of the sample rate variable is different between SLAndroidDataFormat_PCM_EX and SLDataFormat_PCM. */ pcm.bitsPerSample = 16; pcm.containerSize = pcm.bitsPerSample; /* Always tightly packed for now. */ - pcm.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT; + pcm.channelMask = 0; resultSL = (*g_maEngineSL)->CreateAudioRecorder(g_maEngineSL, (SLObjectItf*)&pDevice->opensl.pAudioRecorderObj, &source, &sink, ma_countof(itfIDs), itfIDs, itfIDsRequired); } if (resultSL != SL_RESULT_SUCCESS) { ma_device_uninit__opensl(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to create audio recorder.", ma_result_from_OpenSL(resultSL)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to create audio recorder."); + return ma_result_from_OpenSL(resultSL); } @@ -31600,25 +37574,29 @@ static ma_result ma_device_init__opensl(ma_device* pDevice, const ma_device_conf resultSL = MA_OPENSL_OBJ(pDevice->opensl.pAudioRecorderObj)->Realize((SLObjectItf)pDevice->opensl.pAudioRecorderObj, SL_BOOLEAN_FALSE); if (resultSL != SL_RESULT_SUCCESS) { ma_device_uninit__opensl(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to realize audio recorder.", ma_result_from_OpenSL(resultSL)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to realize audio recorder."); + return ma_result_from_OpenSL(resultSL); } resultSL = MA_OPENSL_OBJ(pDevice->opensl.pAudioRecorderObj)->GetInterface((SLObjectItf)pDevice->opensl.pAudioRecorderObj, (SLInterfaceID)pDevice->pContext->opensl.SL_IID_RECORD, &pDevice->opensl.pAudioRecorder); if (resultSL != SL_RESULT_SUCCESS) { ma_device_uninit__opensl(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to retrieve SL_IID_RECORD interface.", ma_result_from_OpenSL(resultSL)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to retrieve SL_IID_RECORD interface."); + return ma_result_from_OpenSL(resultSL); } resultSL = MA_OPENSL_OBJ(pDevice->opensl.pAudioRecorderObj)->GetInterface((SLObjectItf)pDevice->opensl.pAudioRecorderObj, (SLInterfaceID)pDevice->pContext->opensl.SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &pDevice->opensl.pBufferQueueCapture); if (resultSL != SL_RESULT_SUCCESS) { ma_device_uninit__opensl(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to retrieve SL_IID_ANDROIDSIMPLEBUFFERQUEUE interface.", ma_result_from_OpenSL(resultSL)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to retrieve SL_IID_ANDROIDSIMPLEBUFFERQUEUE interface."); + return ma_result_from_OpenSL(resultSL); } resultSL = MA_OPENSL_BUFFERQUEUE(pDevice->opensl.pBufferQueueCapture)->RegisterCallback((SLAndroidSimpleBufferQueueItf)pDevice->opensl.pBufferQueueCapture, ma_buffer_queue_callback_capture__opensl_android, pDevice); if (resultSL != SL_RESULT_SUCCESS) { ma_device_uninit__opensl(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to register buffer queue callback.", ma_result_from_OpenSL(resultSL)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to register buffer queue callback."); + return ma_result_from_OpenSL(resultSL); } /* The internal format is determined by the "pcm" object. */ @@ -31629,10 +37607,11 @@ static ma_result ma_device_init__opensl(ma_device* pDevice, const ma_device_conf pDevice->opensl.currentBufferIndexCapture = 0; bufferSizeInBytes = pDescriptorCapture->periodSizeInFrames * ma_get_bytes_per_frame(pDescriptorCapture->format, pDescriptorCapture->channels) * pDescriptorCapture->periodCount; - pDevice->opensl.pBufferCapture = (ma_uint8*)ma__calloc_from_callbacks(bufferSizeInBytes, &pDevice->pContext->allocationCallbacks); + pDevice->opensl.pBufferCapture = (ma_uint8*)ma_calloc(bufferSizeInBytes, &pDevice->pContext->allocationCallbacks); if (pDevice->opensl.pBufferCapture == NULL) { ma_device_uninit__opensl(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to allocate memory for data buffer.", MA_OUT_OF_MEMORY); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to allocate memory for data buffer."); + return MA_OUT_OF_MEMORY; } MA_ZERO_MEMORY(pDevice->opensl.pBufferCapture, bufferSizeInBytes); } @@ -31649,19 +37628,22 @@ static ma_result ma_device_init__opensl(ma_device* pDevice, const ma_device_conf resultSL = (*g_maEngineSL)->CreateOutputMix(g_maEngineSL, (SLObjectItf*)&pDevice->opensl.pOutputMixObj, 0, NULL, NULL); if (resultSL != SL_RESULT_SUCCESS) { ma_device_uninit__opensl(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to create output mix.", ma_result_from_OpenSL(resultSL)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to create output mix."); + return ma_result_from_OpenSL(resultSL); } resultSL = MA_OPENSL_OBJ(pDevice->opensl.pOutputMixObj)->Realize((SLObjectItf)pDevice->opensl.pOutputMixObj, SL_BOOLEAN_FALSE); if (resultSL != SL_RESULT_SUCCESS) { ma_device_uninit__opensl(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to realize output mix object.", ma_result_from_OpenSL(resultSL)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to realize output mix object."); + return ma_result_from_OpenSL(resultSL); } resultSL = MA_OPENSL_OBJ(pDevice->opensl.pOutputMixObj)->GetInterface((SLObjectItf)pDevice->opensl.pOutputMixObj, (SLInterfaceID)pDevice->pContext->opensl.SL_IID_OUTPUTMIX, &pDevice->opensl.pOutputMix); if (resultSL != SL_RESULT_SUCCESS) { ma_device_uninit__opensl(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to retrieve SL_IID_OUTPUTMIX interface.", ma_result_from_OpenSL(resultSL)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to retrieve SL_IID_OUTPUTMIX interface."); + return ma_result_from_OpenSL(resultSL); } /* Set the output device. */ @@ -31682,7 +37664,7 @@ static ma_result ma_device_init__opensl(ma_device* pDevice, const ma_device_conf sink.pFormat = NULL; resultSL = (*g_maEngineSL)->CreateAudioPlayer(g_maEngineSL, (SLObjectItf*)&pDevice->opensl.pAudioPlayerObj, &source, &sink, ma_countof(itfIDs), itfIDs, itfIDsRequired); - if (resultSL == SL_RESULT_CONTENT_UNSUPPORTED) { + if (resultSL == SL_RESULT_CONTENT_UNSUPPORTED || resultSL == SL_RESULT_PARAMETER_INVALID) { /* Unsupported format. Fall back to something safer and try again. If this fails, just abort. */ pcm.formatType = SL_DATAFORMAT_PCM; pcm.numChannels = 2; @@ -31695,7 +37677,8 @@ static ma_result ma_device_init__opensl(ma_device* pDevice, const ma_device_conf if (resultSL != SL_RESULT_SUCCESS) { ma_device_uninit__opensl(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to create audio player.", ma_result_from_OpenSL(resultSL)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to create audio player."); + return ma_result_from_OpenSL(resultSL); } @@ -31714,25 +37697,29 @@ static ma_result ma_device_init__opensl(ma_device* pDevice, const ma_device_conf resultSL = MA_OPENSL_OBJ(pDevice->opensl.pAudioPlayerObj)->Realize((SLObjectItf)pDevice->opensl.pAudioPlayerObj, SL_BOOLEAN_FALSE); if (resultSL != SL_RESULT_SUCCESS) { ma_device_uninit__opensl(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to realize audio player.", ma_result_from_OpenSL(resultSL)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to realize audio player."); + return ma_result_from_OpenSL(resultSL); } resultSL = MA_OPENSL_OBJ(pDevice->opensl.pAudioPlayerObj)->GetInterface((SLObjectItf)pDevice->opensl.pAudioPlayerObj, (SLInterfaceID)pDevice->pContext->opensl.SL_IID_PLAY, &pDevice->opensl.pAudioPlayer); if (resultSL != SL_RESULT_SUCCESS) { ma_device_uninit__opensl(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to retrieve SL_IID_PLAY interface.", ma_result_from_OpenSL(resultSL)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to retrieve SL_IID_PLAY interface."); + return ma_result_from_OpenSL(resultSL); } resultSL = MA_OPENSL_OBJ(pDevice->opensl.pAudioPlayerObj)->GetInterface((SLObjectItf)pDevice->opensl.pAudioPlayerObj, (SLInterfaceID)pDevice->pContext->opensl.SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &pDevice->opensl.pBufferQueuePlayback); if (resultSL != SL_RESULT_SUCCESS) { ma_device_uninit__opensl(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to retrieve SL_IID_ANDROIDSIMPLEBUFFERQUEUE interface.", ma_result_from_OpenSL(resultSL)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to retrieve SL_IID_ANDROIDSIMPLEBUFFERQUEUE interface."); + return ma_result_from_OpenSL(resultSL); } resultSL = MA_OPENSL_BUFFERQUEUE(pDevice->opensl.pBufferQueuePlayback)->RegisterCallback((SLAndroidSimpleBufferQueueItf)pDevice->opensl.pBufferQueuePlayback, ma_buffer_queue_callback_playback__opensl_android, pDevice); if (resultSL != SL_RESULT_SUCCESS) { ma_device_uninit__opensl(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to register buffer queue callback.", ma_result_from_OpenSL(resultSL)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to register buffer queue callback."); + return ma_result_from_OpenSL(resultSL); } /* The internal format is determined by the "pcm" object. */ @@ -31743,10 +37730,11 @@ static ma_result ma_device_init__opensl(ma_device* pDevice, const ma_device_conf pDevice->opensl.currentBufferIndexPlayback = 0; bufferSizeInBytes = pDescriptorPlayback->periodSizeInFrames * ma_get_bytes_per_frame(pDescriptorPlayback->format, pDescriptorPlayback->channels) * pDescriptorPlayback->periodCount; - pDevice->opensl.pBufferPlayback = (ma_uint8*)ma__calloc_from_callbacks(bufferSizeInBytes, &pDevice->pContext->allocationCallbacks); + pDevice->opensl.pBufferPlayback = (ma_uint8*)ma_calloc(bufferSizeInBytes, &pDevice->pContext->allocationCallbacks); if (pDevice->opensl.pBufferPlayback == NULL) { ma_device_uninit__opensl(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to allocate memory for data buffer.", MA_OUT_OF_MEMORY); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to allocate memory for data buffer."); + return MA_OUT_OF_MEMORY; } MA_ZERO_MEMORY(pDevice->opensl.pBufferPlayback, bufferSizeInBytes); } @@ -31773,7 +37761,8 @@ static ma_result ma_device_start__opensl(ma_device* pDevice) if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { resultSL = MA_OPENSL_RECORD(pDevice->opensl.pAudioRecorder)->SetRecordState((SLRecordItf)pDevice->opensl.pAudioRecorder, SL_RECORDSTATE_RECORDING); if (resultSL != SL_RESULT_SUCCESS) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to start internal capture device.", ma_result_from_OpenSL(resultSL)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to start internal capture device."); + return ma_result_from_OpenSL(resultSL); } periodSizeInBytes = pDevice->capture.internalPeriodSizeInFrames * ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); @@ -31781,7 +37770,8 @@ static ma_result ma_device_start__opensl(ma_device* pDevice) resultSL = MA_OPENSL_BUFFERQUEUE(pDevice->opensl.pBufferQueueCapture)->Enqueue((SLAndroidSimpleBufferQueueItf)pDevice->opensl.pBufferQueueCapture, pDevice->opensl.pBufferCapture + (periodSizeInBytes * iPeriod), periodSizeInBytes); if (resultSL != SL_RESULT_SUCCESS) { MA_OPENSL_RECORD(pDevice->opensl.pAudioRecorder)->SetRecordState((SLRecordItf)pDevice->opensl.pAudioRecorder, SL_RECORDSTATE_STOPPED); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to enqueue buffer for capture device.", ma_result_from_OpenSL(resultSL)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to enqueue buffer for capture device."); + return ma_result_from_OpenSL(resultSL); } } } @@ -31789,7 +37779,8 @@ static ma_result ma_device_start__opensl(ma_device* pDevice) if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { resultSL = MA_OPENSL_PLAY(pDevice->opensl.pAudioPlayer)->SetPlayState((SLPlayItf)pDevice->opensl.pAudioPlayer, SL_PLAYSTATE_PLAYING); if (resultSL != SL_RESULT_SUCCESS) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to start internal playback device.", ma_result_from_OpenSL(resultSL)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to start internal playback device."); + return ma_result_from_OpenSL(resultSL); } /* In playback mode (no duplex) we need to load some initial buffers. In duplex mode we need to enqueu silent buffers. */ @@ -31804,7 +37795,8 @@ static ma_result ma_device_start__opensl(ma_device* pDevice) resultSL = MA_OPENSL_BUFFERQUEUE(pDevice->opensl.pBufferQueuePlayback)->Enqueue((SLAndroidSimpleBufferQueueItf)pDevice->opensl.pBufferQueuePlayback, pDevice->opensl.pBufferPlayback + (periodSizeInBytes * iPeriod), periodSizeInBytes); if (resultSL != SL_RESULT_SUCCESS) { MA_OPENSL_PLAY(pDevice->opensl.pAudioPlayer)->SetPlayState((SLPlayItf)pDevice->opensl.pAudioPlayer, SL_PLAYSTATE_STOPPED); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to enqueue buffer for playback device.", ma_result_from_OpenSL(resultSL)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to enqueue buffer for playback device."); + return ma_result_from_OpenSL(resultSL); } } } @@ -31849,7 +37841,6 @@ static ma_result ma_device_drain__opensl(ma_device* pDevice, ma_device_type devi static ma_result ma_device_stop__opensl(ma_device* pDevice) { SLresult resultSL; - ma_stop_proc onStop; MA_ASSERT(pDevice != NULL); @@ -31863,7 +37854,8 @@ static ma_result ma_device_stop__opensl(ma_device* pDevice) resultSL = MA_OPENSL_RECORD(pDevice->opensl.pAudioRecorder)->SetRecordState((SLRecordItf)pDevice->opensl.pAudioRecorder, SL_RECORDSTATE_STOPPED); if (resultSL != SL_RESULT_SUCCESS) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to stop internal capture device.", ma_result_from_OpenSL(resultSL)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to stop internal capture device."); + return ma_result_from_OpenSL(resultSL); } MA_OPENSL_BUFFERQUEUE(pDevice->opensl.pBufferQueueCapture)->Clear((SLAndroidSimpleBufferQueueItf)pDevice->opensl.pBufferQueueCapture); @@ -31874,17 +37866,15 @@ static ma_result ma_device_stop__opensl(ma_device* pDevice) resultSL = MA_OPENSL_PLAY(pDevice->opensl.pAudioPlayer)->SetPlayState((SLPlayItf)pDevice->opensl.pAudioPlayer, SL_PLAYSTATE_STOPPED); if (resultSL != SL_RESULT_SUCCESS) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to stop internal playback device.", ma_result_from_OpenSL(resultSL)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to stop internal playback device."); + return ma_result_from_OpenSL(resultSL); } MA_OPENSL_BUFFERQUEUE(pDevice->opensl.pBufferQueuePlayback)->Clear((SLAndroidSimpleBufferQueueItf)pDevice->opensl.pBufferQueuePlayback); } /* Make sure the client is aware that the device has stopped. There may be an OpenSL|ES callback for this, but I haven't found it. */ - onStop = pDevice->onStop; - if (onStop) { - onStop(pDevice); - } + ma_device__on_notification_stopped(pDevice); return MA_SUCCESS; } @@ -31916,7 +37906,7 @@ static ma_result ma_dlsym_SLInterfaceID__opensl(ma_context* pContext, const char /* We need to return an error if the symbol cannot be found. This is important because there have been reports that some symbols do not exist. */ ma_handle* p = (ma_handle*)ma_dlsym(pContext, pContext->opensl.libOpenSLES, pName); if (p == NULL) { - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_INFO, "[OpenSL|ES] Cannot find symbol %s", pName); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_INFO, "[OpenSL] Cannot find symbol %s", pName); return MA_NO_BACKEND; } @@ -31979,7 +37969,7 @@ static ma_result ma_context_init__opensl(ma_context* pContext, const ma_context_ } if (pContext->opensl.libOpenSLES == NULL) { - ma_post_log_message(pContext, NULL, MA_LOG_LEVEL_INFO, "[OpenSL|ES] Could not find libOpenSLES.so"); + ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_INFO, "[OpenSL] Could not find libOpenSLES.so"); return MA_NO_BACKEND; } @@ -32028,7 +38018,7 @@ static ma_result ma_context_init__opensl(ma_context* pContext, const ma_context_ pContext->opensl.slCreateEngine = (ma_proc)ma_dlsym(pContext, pContext->opensl.libOpenSLES, "slCreateEngine"); if (pContext->opensl.slCreateEngine == NULL) { ma_dlclose(pContext, pContext->opensl.libOpenSLES); - ma_post_log_message(pContext, NULL, MA_LOG_LEVEL_INFO, "[OpenSL|ES] Cannot find symbol slCreateEngine."); + ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_INFO, "[OpenSL] Cannot find symbol slCreateEngine."); return MA_NO_BACKEND; } #else @@ -32052,7 +38042,7 @@ static ma_result ma_context_init__opensl(ma_context* pContext, const ma_context_ if (result != MA_SUCCESS) { ma_dlclose(pContext, pContext->opensl.libOpenSLES); - ma_post_log_message(pContext, NULL, MA_LOG_LEVEL_INFO, "[OpenSL|ES] Failed to initialize OpenSL engine."); + ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_INFO, "[OpenSL] Failed to initialize OpenSL engine."); return result; } @@ -32290,6 +38280,7 @@ static ma_result ma_device_init_by_type__webaudio(ma_device* pDevice, const ma_d sampleRate = (pDescriptor->sampleRate > 0) ? pDescriptor->sampleRate : MA_DEFAULT_SAMPLE_RATE; periodSizeInFrames = ma_calculate_period_size_in_frames_from_descriptor__webaudio(pDescriptor, sampleRate, pConfig->performanceProfile); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "periodSizeInFrames = %d\n", (int)periodSizeInFrames); /* We create the device on the JavaScript side and reference it using an index. We use this to make it possible to reference the device between JavaScript and C. */ deviceIndex = EM_ASM_INT({ @@ -32299,7 +38290,7 @@ static ma_result ma_device_init_by_type__webaudio(ma_device* pDevice, const ma_d var isCapture = $3; var pDevice = $4; - if (typeof(miniaudio) === 'undefined') { + if (typeof(window.miniaudio) === 'undefined') { return -1; /* Context not initialized. */ } @@ -32308,7 +38299,7 @@ static ma_result ma_device_init_by_type__webaudio(ma_device* pDevice, const ma_d /* The AudioContext must be created in a suspended state. */ device.webaudio = new (window.AudioContext || window.webkitAudioContext)({sampleRate:sampleRate}); device.webaudio.suspend(); - device.state = 1; /* MA_STATE_STOPPED */ + device.state = 1; /* ma_device_state_stopped */ /* We need an intermediary buffer which we use for JavaScript and C interop. This buffer stores interleaved f32 PCM data. Because it's passed between @@ -32334,7 +38325,7 @@ static ma_result ma_device_init_by_type__webaudio(ma_device* pDevice, const ma_d how well this would work. Although ScriptProccessorNode is deprecated, in practice it seems to have pretty good browser support so I'm leaving it like this for now. If anyone knows how I could get raw PCM data using the MediaRecorder API please let me know! */ - device.scriptNode = device.webaudio.createScriptProcessor(bufferSize, channels, channels); + device.scriptNode = device.webaudio.createScriptProcessor(bufferSize, (isCapture) ? channels : 0, (isCapture) ? 0 : channels); if (isCapture) { device.scriptNode.onaudioprocess = function(e) { @@ -32342,7 +38333,7 @@ static ma_result ma_device_init_by_type__webaudio(ma_device* pDevice, const ma_d return; /* This means the device has been uninitialized. */ } - if(device.intermediaryBufferView.length == 0) { + if (device.intermediaryBufferView.length == 0) { /* Recreate intermediaryBufferView when losing reference to the underlying buffer, probably due to emscripten resizing heap. */ device.intermediaryBufferView = new Float32Array(Module.HEAPF32.buffer, device.intermediaryBuffer, device.intermediaryBufferSizeInBytes); } @@ -32440,8 +38431,10 @@ static ma_result ma_device_init_by_type__webaudio(ma_device* pDevice, const ma_d } } else { for (var iChannel = 0; iChannel < e.outputBuffer.numberOfChannels; ++iChannel) { + var outputBuffer = e.outputBuffer.getChannelData(iChannel); + var intermediaryBuffer = device.intermediaryBufferView; for (var iFrame = 0; iFrame < framesToProcess; ++iFrame) { - e.outputBuffer.getChannelData(iChannel)[totalFramesProcessed + iFrame] = device.intermediaryBufferView[iFrame*channels + iChannel]; + outputBuffer[totalFramesProcessed + iFrame] = intermediaryBuffer[iFrame*channels + iChannel]; } } } @@ -32466,12 +38459,12 @@ static ma_result ma_device_init_by_type__webaudio(ma_device* pDevice, const ma_d pDevice->webaudio.indexPlayback = deviceIndex; } - pDescriptor->format = ma_format_f32; - pDescriptor->channels = channels; - ma_get_standard_channel_map(ma_standard_channel_map_webaudio, pDescriptor->channels, pDescriptor->channelMap); - pDescriptor->sampleRate = EM_ASM_INT({ return miniaudio.get_device_by_index($0).webaudio.sampleRate; }, deviceIndex); - pDescriptor->periodSizeInFrames = periodSizeInFrames; - pDescriptor->periodCount = 1; + pDescriptor->format = ma_format_f32; + pDescriptor->channels = channels; + ma_channel_map_init_standard(ma_standard_channel_map_webaudio, pDescriptor->channelMap, ma_countof(pDescriptor->channelMap), pDescriptor->channels); + pDescriptor->sampleRate = EM_ASM_INT({ return miniaudio.get_device_by_index($0).webaudio.sampleRate; }, deviceIndex); + pDescriptor->periodSizeInFrames = periodSizeInFrames; + pDescriptor->periodCount = 1; return MA_SUCCESS; } @@ -32518,7 +38511,7 @@ static ma_result ma_device_start__webaudio(ma_device* pDevice) EM_ASM({ var device = miniaudio.get_device_by_index($0); device.webaudio.resume(); - device.state = 2; /* MA_STATE_STARTED */ + device.state = 2; /* ma_device_state_started */ }, pDevice->webaudio.indexCapture); } @@ -32526,7 +38519,7 @@ static ma_result ma_device_start__webaudio(ma_device* pDevice) EM_ASM({ var device = miniaudio.get_device_by_index($0); device.webaudio.resume(); - device.state = 2; /* MA_STATE_STARTED */ + device.state = 2; /* ma_device_state_started */ }, pDevice->webaudio.indexPlayback); } @@ -32551,7 +38544,7 @@ static ma_result ma_device_stop__webaudio(ma_device* pDevice) EM_ASM({ var device = miniaudio.get_device_by_index($0); device.webaudio.suspend(); - device.state = 1; /* MA_STATE_STOPPED */ + device.state = 1; /* ma_device_state_stopped */ }, pDevice->webaudio.indexCapture); } @@ -32559,14 +38552,11 @@ static ma_result ma_device_stop__webaudio(ma_device* pDevice) EM_ASM({ var device = miniaudio.get_device_by_index($0); device.webaudio.suspend(); - device.state = 1; /* MA_STATE_STOPPED */ + device.state = 1; /* ma_device_state_stopped */ }, pDevice->webaudio.indexPlayback); } - ma_stop_proc onStop = pDevice->onStop; - if (onStop) { - onStop(pDevice); - } + ma_device__on_notification_stopped(pDevice); return MA_SUCCESS; } @@ -32596,8 +38586,8 @@ static ma_result ma_context_init__webaudio(ma_context* pContext, const ma_contex return 0; /* Web Audio not supported. */ } - if (typeof(miniaudio) === 'undefined') { - miniaudio = {}; + if (typeof(window.miniaudio) === 'undefined') { + window.miniaudio = {}; miniaudio.devices = []; /* Device cache for mapping devices to indexes for JavaScript/C interop. */ miniaudio.track_device = function(device) { @@ -32647,7 +38637,7 @@ static ma_result ma_context_init__webaudio(ma_context* pContext, const ma_contex miniaudio.unlock = function() { for(var i = 0; i < miniaudio.devices.length; ++i) { var device = miniaudio.devices[i]; - if (device != null && device.webaudio != null && device.state === 2 /* MA_STATE_STARTED */) { + if (device != null && device.webaudio != null && device.state === 2 /* ma_device_state_started */) { device.webaudio.resume(); } } @@ -32686,10 +38676,10 @@ static ma_result ma_context_init__webaudio(ma_context* pContext, const ma_contex -static ma_bool32 ma__is_channel_map_valid(const ma_channel* channelMap, ma_uint32 channels) +static ma_bool32 ma__is_channel_map_valid(const ma_channel* pChannelMap, ma_uint32 channels) { /* A blank channel map should be allowed, in which case it should use an appropriate default which will depend on context. */ - if (channelMap[0] != MA_CHANNEL_NONE) { + if (pChannelMap != NULL && pChannelMap[0] != MA_CHANNEL_NONE) { ma_uint32 iChannel; if (channels == 0 || channels > MA_MAX_CHANNELS) { @@ -32700,7 +38690,7 @@ static ma_bool32 ma__is_channel_map_valid(const ma_channel* channelMap, ma_uint3 for (iChannel = 0; iChannel < channels; ++iChannel) { ma_uint32 jChannel; for (jChannel = iChannel + 1; jChannel < channels; ++jChannel) { - if (channelMap[iChannel] == channelMap[jChannel]) { + if (pChannelMap[iChannel] == pChannelMap[jChannel]) { return MA_FALSE; } } @@ -32730,9 +38720,9 @@ static ma_result ma_device__post_init_setup(ma_device* pDevice, ma_device_type d ma_channel_map_copy(pDevice->capture.channelMap, pDevice->capture.internalChannelMap, pDevice->capture.channels); } else { if (pDevice->capture.channelMixMode == ma_channel_mix_mode_simple) { - ma_channel_map_init_blank(pDevice->capture.channels, pDevice->capture.channelMap); + ma_channel_map_init_blank(pDevice->capture.channelMap, pDevice->capture.channels); } else { - ma_get_standard_channel_map(ma_standard_channel_map_default, pDevice->capture.channels, pDevice->capture.channelMap); + ma_channel_map_init_standard(ma_standard_channel_map_default, pDevice->capture.channelMap, ma_countof(pDevice->capture.channelMap), pDevice->capture.channels); } } } @@ -32751,9 +38741,9 @@ static ma_result ma_device__post_init_setup(ma_device* pDevice, ma_device_type d ma_channel_map_copy(pDevice->playback.channelMap, pDevice->playback.internalChannelMap, pDevice->playback.channels); } else { if (pDevice->playback.channelMixMode == ma_channel_mix_mode_simple) { - ma_channel_map_init_blank(pDevice->playback.channels, pDevice->playback.channelMap); + ma_channel_map_init_blank(pDevice->playback.channelMap, pDevice->playback.channels); } else { - ma_get_standard_channel_map(ma_standard_channel_map_default, pDevice->playback.channels, pDevice->playback.channelMap); + ma_channel_map_init_standard(ma_standard_channel_map_default, pDevice->playback.channelMap, ma_countof(pDevice->playback.channelMap), pDevice->playback.channels); } } } @@ -32771,21 +38761,27 @@ static ma_result ma_device__post_init_setup(ma_device* pDevice, ma_device_type d if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex || deviceType == ma_device_type_loopback) { /* Converting from internal device format to client format. */ ma_data_converter_config converterConfig = ma_data_converter_config_init_default(); - converterConfig.formatIn = pDevice->capture.internalFormat; - converterConfig.channelsIn = pDevice->capture.internalChannels; - converterConfig.sampleRateIn = pDevice->capture.internalSampleRate; - ma_channel_map_copy(converterConfig.channelMapIn, pDevice->capture.internalChannelMap, ma_min(pDevice->capture.internalChannels, MA_MAX_CHANNELS)); - converterConfig.formatOut = pDevice->capture.format; - converterConfig.channelsOut = pDevice->capture.channels; - converterConfig.sampleRateOut = pDevice->sampleRate; - ma_channel_map_copy(converterConfig.channelMapOut, pDevice->capture.channelMap, ma_min(pDevice->capture.channels, MA_MAX_CHANNELS)); - converterConfig.channelMixMode = pDevice->capture.channelMixMode; - converterConfig.resampling.allowDynamicSampleRate = MA_FALSE; - converterConfig.resampling.algorithm = pDevice->resampling.algorithm; - converterConfig.resampling.linear.lpfOrder = pDevice->resampling.linear.lpfOrder; - converterConfig.resampling.speex.quality = pDevice->resampling.speex.quality; + converterConfig.formatIn = pDevice->capture.internalFormat; + converterConfig.channelsIn = pDevice->capture.internalChannels; + converterConfig.sampleRateIn = pDevice->capture.internalSampleRate; + converterConfig.pChannelMapIn = pDevice->capture.internalChannelMap; + converterConfig.formatOut = pDevice->capture.format; + converterConfig.channelsOut = pDevice->capture.channels; + converterConfig.sampleRateOut = pDevice->sampleRate; + converterConfig.pChannelMapOut = pDevice->capture.channelMap; + converterConfig.channelMixMode = pDevice->capture.channelMixMode; + converterConfig.allowDynamicSampleRate = MA_FALSE; + converterConfig.resampling.algorithm = pDevice->resampling.algorithm; + converterConfig.resampling.linear.lpfOrder = pDevice->resampling.linear.lpfOrder; + converterConfig.resampling.pBackendVTable = pDevice->resampling.pBackendVTable; + converterConfig.resampling.pBackendUserData = pDevice->resampling.pBackendUserData; - result = ma_data_converter_init(&converterConfig, &pDevice->capture.converter); + /* Make sure the old converter is uninitialized first. */ + if (ma_device_get_state(pDevice) != ma_device_state_uninitialized) { + ma_data_converter_uninit(&pDevice->capture.converter, &pDevice->pContext->allocationCallbacks); + } + + result = ma_data_converter_init(&converterConfig, &pDevice->pContext->allocationCallbacks, &pDevice->capture.converter); if (result != MA_SUCCESS) { return result; } @@ -32794,29 +38790,164 @@ static ma_result ma_device__post_init_setup(ma_device* pDevice, ma_device_type d if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { /* Converting from client format to device format. */ ma_data_converter_config converterConfig = ma_data_converter_config_init_default(); - converterConfig.formatIn = pDevice->playback.format; - converterConfig.channelsIn = pDevice->playback.channels; - converterConfig.sampleRateIn = pDevice->sampleRate; - ma_channel_map_copy(converterConfig.channelMapIn, pDevice->playback.channelMap, ma_min(pDevice->playback.channels, MA_MAX_CHANNELS)); - converterConfig.formatOut = pDevice->playback.internalFormat; - converterConfig.channelsOut = pDevice->playback.internalChannels; - converterConfig.sampleRateOut = pDevice->playback.internalSampleRate; - ma_channel_map_copy(converterConfig.channelMapOut, pDevice->playback.internalChannelMap, ma_min(pDevice->playback.internalChannels, MA_MAX_CHANNELS)); - converterConfig.channelMixMode = pDevice->playback.channelMixMode; - converterConfig.resampling.allowDynamicSampleRate = MA_FALSE; - converterConfig.resampling.algorithm = pDevice->resampling.algorithm; - converterConfig.resampling.linear.lpfOrder = pDevice->resampling.linear.lpfOrder; - converterConfig.resampling.speex.quality = pDevice->resampling.speex.quality; + converterConfig.formatIn = pDevice->playback.format; + converterConfig.channelsIn = pDevice->playback.channels; + converterConfig.sampleRateIn = pDevice->sampleRate; + converterConfig.pChannelMapIn = pDevice->playback.channelMap; + converterConfig.formatOut = pDevice->playback.internalFormat; + converterConfig.channelsOut = pDevice->playback.internalChannels; + converterConfig.sampleRateOut = pDevice->playback.internalSampleRate; + converterConfig.pChannelMapOut = pDevice->playback.internalChannelMap; + converterConfig.channelMixMode = pDevice->playback.channelMixMode; + converterConfig.allowDynamicSampleRate = MA_FALSE; + converterConfig.resampling.algorithm = pDevice->resampling.algorithm; + converterConfig.resampling.linear.lpfOrder = pDevice->resampling.linear.lpfOrder; + converterConfig.resampling.pBackendVTable = pDevice->resampling.pBackendVTable; + converterConfig.resampling.pBackendUserData = pDevice->resampling.pBackendUserData; - result = ma_data_converter_init(&converterConfig, &pDevice->playback.converter); + /* Make sure the old converter is uninitialized first. */ + if (ma_device_get_state(pDevice) != ma_device_state_uninitialized) { + ma_data_converter_uninit(&pDevice->playback.converter, &pDevice->pContext->allocationCallbacks); + } + + result = ma_data_converter_init(&converterConfig, &pDevice->pContext->allocationCallbacks, &pDevice->playback.converter); if (result != MA_SUCCESS) { return result; } } + + /* + In playback mode, if the data converter does not support retrieval of the required number of + input frames given a number of output frames, we need to fall back to a heap-allocated cache. + */ + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + ma_uint64 unused; + + pDevice->playback.inputCacheConsumed = 0; + pDevice->playback.inputCacheRemaining = 0; + + if (deviceType == ma_device_type_duplex || ma_data_converter_get_required_input_frame_count(&pDevice->playback.converter, 1, &unused) != MA_SUCCESS) { + /* We need a heap allocated cache. We want to size this based on the period size. */ + void* pNewInputCache; + ma_uint64 newInputCacheCap; + ma_uint64 newInputCacheSizeInBytes; + + newInputCacheCap = ma_calculate_frame_count_after_resampling(pDevice->playback.internalSampleRate, pDevice->sampleRate, pDevice->playback.internalPeriodSizeInFrames); + + newInputCacheSizeInBytes = newInputCacheCap * ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels); + if (newInputCacheSizeInBytes > MA_SIZE_MAX) { + ma_free(pDevice->playback.pInputCache, &pDevice->pContext->allocationCallbacks); + pDevice->playback.pInputCache = NULL; + pDevice->playback.inputCacheCap = 0; + return MA_OUT_OF_MEMORY; /* Allocation too big. Should never hit this, but makes the cast below safer for 32-bit builds. */ + } + + pNewInputCache = ma_realloc(pDevice->playback.pInputCache, (size_t)newInputCacheSizeInBytes, &pDevice->pContext->allocationCallbacks); + if (pNewInputCache == NULL) { + ma_free(pDevice->playback.pInputCache, &pDevice->pContext->allocationCallbacks); + pDevice->playback.pInputCache = NULL; + pDevice->playback.inputCacheCap = 0; + return MA_OUT_OF_MEMORY; + } + + pDevice->playback.pInputCache = pNewInputCache; + pDevice->playback.inputCacheCap = newInputCacheCap; + } else { + /* Heap allocation not required. Make sure we clear out the old cache just in case this function was called in response to a route change. */ + ma_free(pDevice->playback.pInputCache, &pDevice->pContext->allocationCallbacks); + pDevice->playback.pInputCache = NULL; + pDevice->playback.inputCacheCap = 0; + } + } + return MA_SUCCESS; } +MA_API ma_result ma_device_post_init(ma_device* pDevice, ma_device_type deviceType, const ma_device_descriptor* pDescriptorPlayback, const ma_device_descriptor* pDescriptorCapture) +{ + ma_result result; + + if (pDevice == NULL) { + return MA_INVALID_ARGS; + } + + /* Capture. */ + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex || deviceType == ma_device_type_loopback) { + if (ma_device_descriptor_is_valid(pDescriptorCapture) == MA_FALSE) { + return MA_INVALID_ARGS; + } + + pDevice->capture.internalFormat = pDescriptorCapture->format; + pDevice->capture.internalChannels = pDescriptorCapture->channels; + pDevice->capture.internalSampleRate = pDescriptorCapture->sampleRate; + MA_COPY_MEMORY(pDevice->capture.internalChannelMap, pDescriptorCapture->channelMap, sizeof(pDescriptorCapture->channelMap)); + pDevice->capture.internalPeriodSizeInFrames = pDescriptorCapture->periodSizeInFrames; + pDevice->capture.internalPeriods = pDescriptorCapture->periodCount; + + if (pDevice->capture.internalPeriodSizeInFrames == 0) { + pDevice->capture.internalPeriodSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(pDescriptorCapture->periodSizeInMilliseconds, pDescriptorCapture->sampleRate); + } + } + + /* Playback. */ + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + if (ma_device_descriptor_is_valid(pDescriptorPlayback) == MA_FALSE) { + return MA_INVALID_ARGS; + } + + pDevice->playback.internalFormat = pDescriptorPlayback->format; + pDevice->playback.internalChannels = pDescriptorPlayback->channels; + pDevice->playback.internalSampleRate = pDescriptorPlayback->sampleRate; + MA_COPY_MEMORY(pDevice->playback.internalChannelMap, pDescriptorPlayback->channelMap, sizeof(pDescriptorPlayback->channelMap)); + pDevice->playback.internalPeriodSizeInFrames = pDescriptorPlayback->periodSizeInFrames; + pDevice->playback.internalPeriods = pDescriptorPlayback->periodCount; + + if (pDevice->playback.internalPeriodSizeInFrames == 0) { + pDevice->playback.internalPeriodSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(pDescriptorPlayback->periodSizeInMilliseconds, pDescriptorPlayback->sampleRate); + } + } + + /* + The name of the device can be retrieved from device info. This may be temporary and replaced with a `ma_device_get_info(pDevice, deviceType)` instead. + For loopback devices, we need to retrieve the name of the playback device. + */ + { + ma_device_info deviceInfo; + + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex || deviceType == ma_device_type_loopback) { + result = ma_device_get_info(pDevice, (deviceType == ma_device_type_loopback) ? ma_device_type_playback : ma_device_type_capture, &deviceInfo); + if (result == MA_SUCCESS) { + ma_strncpy_s(pDevice->capture.name, sizeof(pDevice->capture.name), deviceInfo.name, (size_t)-1); + } else { + /* We failed to retrieve the device info. Fall back to a default name. */ + if (pDescriptorCapture->pDeviceID == NULL) { + ma_strncpy_s(pDevice->capture.name, sizeof(pDevice->capture.name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); + } else { + ma_strncpy_s(pDevice->capture.name, sizeof(pDevice->capture.name), "Capture Device", (size_t)-1); + } + } + } + + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + result = ma_device_get_info(pDevice, ma_device_type_playback, &deviceInfo); + if (result == MA_SUCCESS) { + ma_strncpy_s(pDevice->playback.name, sizeof(pDevice->playback.name), deviceInfo.name, (size_t)-1); + } else { + /* We failed to retrieve the device info. Fall back to a default name. */ + if (pDescriptorPlayback->pDeviceID == NULL) { + ma_strncpy_s(pDevice->playback.name, sizeof(pDevice->playback.name), MA_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); + } else { + ma_strncpy_s(pDevice->playback.name, sizeof(pDevice->playback.name), "Playback Device", (size_t)-1); + } + } + } + } + + /* Update data conversion. */ + return ma_device__post_init_setup(pDevice, deviceType); /* TODO: Should probably rename ma_device__post_init_setup() to something better. */ +} + static ma_thread_result MA_THREADCALL ma_worker_thread(void* pData) { @@ -32828,17 +38959,17 @@ static ma_thread_result MA_THREADCALL ma_worker_thread(void* pData) #endif /* - When the device is being initialized it's initial state is set to MA_STATE_UNINITIALIZED. Before returning from + When the device is being initialized it's initial state is set to ma_device_state_uninitialized. Before returning from ma_device_init(), the state needs to be set to something valid. In miniaudio the device's default state immediately after initialization is stopped, so therefore we need to mark the device as such. miniaudio will wait on the worker thread to signal an event to know when the worker thread is ready for action. */ - ma_device__set_state(pDevice, MA_STATE_STOPPED); + ma_device__set_state(pDevice, ma_device_state_stopped); ma_event_signal(&pDevice->stopEvent); for (;;) { /* <-- This loop just keeps the thread alive. The main audio loop is inside. */ ma_result startResult; - ma_result stopResult; /* <-- This will store the result from onDeviceStop(). If it returns an error, we don't fire the onStop callback. */ + ma_result stopResult; /* <-- This will store the result from onDeviceStop(). If it returns an error, we don't fire the stopped notification callback. */ /* We wait on an event to know when something has requested that the device be started and the main loop entered. */ ma_event_wait(&pDevice->wakeupEvent); @@ -32847,7 +38978,7 @@ static ma_thread_result MA_THREADCALL ma_worker_thread(void* pData) pDevice->workResult = MA_SUCCESS; /* If the reason for the wake up is that we are terminating, just break from the loop. */ - if (ma_device_get_state(pDevice) == MA_STATE_UNINITIALIZED) { + if (ma_device_get_state(pDevice) == ma_device_state_uninitialized) { break; } @@ -32856,7 +38987,7 @@ static ma_thread_result MA_THREADCALL ma_worker_thread(void* pData) be started will be waiting on an event (pDevice->startEvent) which means we need to make sure we signal the event in both the success and error case. It's important that the state of the device is set _before_ signaling the event. */ - MA_ASSERT(ma_device_get_state(pDevice) == MA_STATE_STARTING); + MA_ASSERT(ma_device_get_state(pDevice) == ma_device_state_starting); /* If the device has a start callback, start it now. */ if (pDevice->pContext->callbacks.onDeviceStart != NULL) { @@ -32865,15 +38996,22 @@ static ma_thread_result MA_THREADCALL ma_worker_thread(void* pData) startResult = MA_SUCCESS; } + /* + If starting was not successful we'll need to loop back to the start and wait for something + to happen (pDevice->wakeupEvent). + */ if (startResult != MA_SUCCESS) { pDevice->workResult = startResult; - continue; /* Failed to start. Loop back to the start and wait for something to happen (pDevice->wakeupEvent). */ + ma_event_signal(&pDevice->startEvent); /* <-- Always signal the start event so ma_device_start() can return as it'll be waiting on it. */ + continue; } /* Make sure the state is set appropriately. */ - ma_device__set_state(pDevice, MA_STATE_STARTED); + ma_device__set_state(pDevice, ma_device_state_started); /* <-- Set this before signaling the event so that the state is always guaranteed to be good after ma_device_start() has returned. */ ma_event_signal(&pDevice->startEvent); + ma_device__on_notification_started(pDevice); + if (pDevice->pContext->callbacks.onDeviceDataLoop != NULL) { pDevice->pContext->callbacks.onDeviceDataLoop(pDevice); } else { @@ -32889,16 +39027,16 @@ static ma_thread_result MA_THREADCALL ma_worker_thread(void* pData) } /* - After the device has stopped, make sure an event is posted. Don't post an onStop event if + After the device has stopped, make sure an event is posted. Don't post a stopped event if stopping failed. This can happen on some backends when the underlying stream has been stopped due to the device being physically unplugged or disabled via an OS setting. */ - if (pDevice->onStop && stopResult != MA_SUCCESS) { - pDevice->onStop(pDevice); + if (stopResult == MA_SUCCESS) { + ma_device__on_notification_stopped(pDevice); } /* A function somewhere is waiting for the device to have stopped for real so we need to signal an event to allow it to continue. */ - ma_device__set_state(pDevice, MA_STATE_STOPPED); + ma_device__set_state(pDevice, ma_device_state_stopped); ma_event_signal(&pDevice->stopEvent); } @@ -32917,17 +39055,22 @@ static ma_bool32 ma_device__is_initialized(ma_device* pDevice) return MA_FALSE; } - return ma_device_get_state(pDevice) != MA_STATE_UNINITIALIZED; + return ma_device_get_state(pDevice) != ma_device_state_uninitialized; } #ifdef MA_WIN32 static ma_result ma_context_uninit_backend_apis__win32(ma_context* pContext) { + /* For some reason UWP complains when CoUninitialize() is called. I'm just not going to call it on UWP. */ +#ifdef MA_WIN32_DESKTOP ma_CoUninitialize(pContext); ma_dlclose(pContext, pContext->win32.hUser32DLL); ma_dlclose(pContext, pContext->win32.hOle32DLL); ma_dlclose(pContext, pContext->win32.hAdvapi32DLL); +#else + (void)pContext; +#endif return MA_SUCCESS; } @@ -33087,7 +39230,138 @@ static ma_bool32 ma_context_is_backend_asynchronous(ma_context* pContext) } -MA_API ma_context_config ma_context_config_init() +/* The default capacity doesn't need to be too big. */ +#ifndef MA_DEFAULT_DEVICE_JOB_QUEUE_CAPACITY +#define MA_DEFAULT_DEVICE_JOB_QUEUE_CAPACITY 32 +#endif + +MA_API ma_device_job_thread_config ma_device_job_thread_config_init(void) +{ + ma_device_job_thread_config config; + + MA_ZERO_OBJECT(&config); + config.noThread = MA_FALSE; + config.jobQueueCapacity = MA_DEFAULT_DEVICE_JOB_QUEUE_CAPACITY; + config.jobQueueFlags = 0; + + return config; +} + + +static ma_thread_result MA_THREADCALL ma_device_job_thread_entry(void* pUserData) +{ + ma_device_job_thread* pJobThread = (ma_device_job_thread*)pUserData; + MA_ASSERT(pJobThread != NULL); + + for (;;) { + ma_result result; + ma_job job; + + result = ma_device_job_thread_next(pJobThread, &job); + if (result != MA_SUCCESS) { + break; + } + + if (job.toc.breakup.code == MA_JOB_TYPE_QUIT) { + break; + } + + ma_job_process(&job); + } + + return (ma_thread_result)0; +} + +MA_API ma_result ma_device_job_thread_init(const ma_device_job_thread_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_device_job_thread* pJobThread) +{ + ma_result result; + ma_job_queue_config jobQueueConfig; + + if (pJobThread == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pJobThread); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + + /* Initialize the job queue before the thread to ensure it's in a valid state. */ + jobQueueConfig = ma_job_queue_config_init(pConfig->jobQueueFlags, pConfig->jobQueueCapacity); + + result = ma_job_queue_init(&jobQueueConfig, pAllocationCallbacks, &pJobThread->jobQueue); + if (result != MA_SUCCESS) { + return result; /* Failed to initialize job queue. */ + } + + + /* The thread needs to be initialized after the job queue to ensure the thread doesn't try to access it prematurely. */ + if (pConfig->noThread == MA_FALSE) { + result = ma_thread_create(&pJobThread->thread, ma_thread_priority_normal, 0, ma_device_job_thread_entry, pJobThread, pAllocationCallbacks); + if (result != MA_SUCCESS) { + ma_job_queue_uninit(&pJobThread->jobQueue, pAllocationCallbacks); + return result; /* Failed to create the job thread. */ + } + + pJobThread->_hasThread = MA_TRUE; + } else { + pJobThread->_hasThread = MA_FALSE; + } + + + return MA_SUCCESS; +} + +MA_API void ma_device_job_thread_uninit(ma_device_job_thread* pJobThread, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pJobThread == NULL) { + return; + } + + /* The first thing to do is post a quit message to the job queue. If we're using a thread we'll need to wait for it. */ + { + ma_job job = ma_job_init(MA_JOB_TYPE_QUIT); + ma_device_job_thread_post(pJobThread, &job); + } + + /* Wait for the thread to terminate naturally. */ + if (pJobThread->_hasThread) { + ma_thread_wait(&pJobThread->thread); + } + + /* At this point the thread should be terminated so we can safely uninitialize the job queue. */ + ma_job_queue_uninit(&pJobThread->jobQueue, pAllocationCallbacks); +} + +MA_API ma_result ma_device_job_thread_post(ma_device_job_thread* pJobThread, const ma_job* pJob) +{ + if (pJobThread == NULL || pJob == NULL) { + return MA_INVALID_ARGS; + } + + return ma_job_queue_post(&pJobThread->jobQueue, pJob); +} + +MA_API ma_result ma_device_job_thread_next(ma_device_job_thread* pJobThread, ma_job* pJob) +{ + if (pJob == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pJob); + + if (pJobThread == NULL) { + return MA_INVALID_ARGS; + } + + return ma_job_queue_next(&pJobThread->jobQueue, pJob); +} + + + +MA_API ma_context_config ma_context_config_init(void) { ma_context_config config; MA_ZERO_OBJECT(&config); @@ -33134,7 +39408,6 @@ MA_API ma_result ma_context_init(const ma_backend backends[], ma_uint32 backendC } } - pContext->logCallback = pConfig->logCallback; pContext->threadPriority = pConfig->threadPriority; pContext->threadStackSize = pConfig->threadStackSize; pContext->pUserData = pConfig->pUserData; @@ -33272,23 +39545,19 @@ MA_API ma_result ma_context_init(const ma_backend backends[], ma_uint32 backendC if (result == MA_SUCCESS) { result = ma_mutex_init(&pContext->deviceEnumLock); if (result != MA_SUCCESS) { - ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_WARNING, "Failed to initialize mutex for device enumeration. ma_context_get_devices() is not thread safe.\n", result); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_WARNING, "Failed to initialize mutex for device enumeration. ma_context_get_devices() is not thread safe.\n"); } result = ma_mutex_init(&pContext->deviceInfoLock); if (result != MA_SUCCESS) { - ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_WARNING, "Failed to initialize mutex for device info retrieval. ma_context_get_device_info() is not thread safe.\n", result); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_WARNING, "Failed to initialize mutex for device info retrieval. ma_context_get_device_info() is not thread safe.\n"); } - #ifdef MA_DEBUG_OUTPUT - { - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "[miniaudio] Endian: %s\n", ma_is_little_endian() ? "LE" : "BE"); - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "[miniaudio] SSE2: %s\n", ma_has_sse2() ? "YES" : "NO"); - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "[miniaudio] AVX2: %s\n", ma_has_avx2() ? "YES" : "NO"); - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "[miniaudio] AVX512F: %s\n", ma_has_avx512f() ? "YES" : "NO"); - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "[miniaudio] NEON: %s\n", ma_has_neon() ? "YES" : "NO"); - } - #endif + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "System Architecture:\n"); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, " Endian: %s\n", ma_is_little_endian() ? "LE" : "BE"); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, " SSE2: %s\n", ma_has_sse2() ? "YES" : "NO"); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, " AVX2: %s\n", ma_has_avx2() ? "YES" : "NO"); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, " NEON: %s\n", ma_has_neon() ? "YES" : "NO"); pContext->backend = backend; return result; @@ -33314,7 +39583,7 @@ MA_API ma_result ma_context_uninit(ma_context* pContext) ma_mutex_uninit(&pContext->deviceEnumLock); ma_mutex_uninit(&pContext->deviceInfoLock); - ma__free_from_callbacks(pContext->pDeviceInfos, &pContext->allocationCallbacks); + ma_free(pContext->pDeviceInfos, &pContext->allocationCallbacks); ma_context_uninit_backend_apis(pContext); if (pContext->pLog == &pContext->log) { @@ -33377,9 +39646,8 @@ static ma_bool32 ma_context_get_devices__enum_callback(ma_context* pContext, ma_ const ma_uint32 totalDeviceInfoCount = pContext->playbackDeviceInfoCount + pContext->captureDeviceInfoCount; if (totalDeviceInfoCount >= pContext->deviceInfoCapacity) { - ma_uint32 oldCapacity = pContext->deviceInfoCapacity; - ma_uint32 newCapacity = oldCapacity + bufferExpansionCount; - ma_device_info* pNewInfos = (ma_device_info*)ma__realloc_from_callbacks(pContext->pDeviceInfos, sizeof(*pContext->pDeviceInfos)*newCapacity, sizeof(*pContext->pDeviceInfos)*oldCapacity, &pContext->allocationCallbacks); + ma_uint32 newCapacity = pContext->deviceInfoCapacity + bufferExpansionCount; + ma_device_info* pNewInfos = (ma_device_info*)ma_realloc(pContext->pDeviceInfos, sizeof(*pContext->pDeviceInfos)*newCapacity, &pContext->allocationCallbacks); if (pNewInfos == NULL) { return MA_FALSE; /* Out of memory. */ } @@ -33461,13 +39729,11 @@ MA_API ma_result ma_context_get_devices(ma_context* pContext, ma_device_info** p return result; } -MA_API ma_result ma_context_get_device_info(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_share_mode shareMode, ma_device_info* pDeviceInfo) +MA_API ma_result ma_context_get_device_info(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_device_info* pDeviceInfo) { ma_result result; ma_device_info deviceInfo; - (void)shareMode; /* Unused. This parameter will be removed in version 0.11. */ - /* NOTE: Do not clear pDeviceInfo on entry. The reason is the pDeviceID may actually point to pDeviceInfo->id which will break things. */ if (pContext == NULL || pDeviceInfo == NULL) { return MA_INVALID_ARGS; @@ -33490,81 +39756,6 @@ MA_API ma_result ma_context_get_device_info(ma_context* pContext, ma_device_type } ma_mutex_unlock(&pContext->deviceInfoLock); - /* - If the backend is using the new device info system, do a pass to fill out the old settings for backwards compatibility. This will be removed in - the future when all backends have implemented the new device info system. - */ - if (deviceInfo.nativeDataFormatCount > 0) { - ma_uint32 iNativeFormat; - ma_uint32 iSampleFormat; - - deviceInfo.minChannels = 0xFFFFFFFF; - deviceInfo.maxChannels = 0; - deviceInfo.minSampleRate = 0xFFFFFFFF; - deviceInfo.maxSampleRate = 0; - - for (iNativeFormat = 0; iNativeFormat < deviceInfo.nativeDataFormatCount; iNativeFormat += 1) { - /* Formats. */ - if (deviceInfo.nativeDataFormats[iNativeFormat].format == ma_format_unknown) { - /* All formats are supported. */ - deviceInfo.formats[0] = ma_format_u8; - deviceInfo.formats[1] = ma_format_s16; - deviceInfo.formats[2] = ma_format_s24; - deviceInfo.formats[3] = ma_format_s32; - deviceInfo.formats[4] = ma_format_f32; - deviceInfo.formatCount = 5; - } else { - /* Make sure the format isn't already in the list. If so, skip. */ - ma_bool32 alreadyExists = MA_FALSE; - for (iSampleFormat = 0; iSampleFormat < deviceInfo.formatCount; iSampleFormat += 1) { - if (deviceInfo.formats[iSampleFormat] == deviceInfo.nativeDataFormats[iNativeFormat].format) { - alreadyExists = MA_TRUE; - break; - } - } - - if (!alreadyExists) { - deviceInfo.formats[deviceInfo.formatCount++] = deviceInfo.nativeDataFormats[iNativeFormat].format; - } - } - - /* Channels. */ - if (deviceInfo.nativeDataFormats[iNativeFormat].channels == 0) { - /* All channels supported. */ - deviceInfo.minChannels = MA_MIN_CHANNELS; - deviceInfo.maxChannels = MA_MAX_CHANNELS; - } else { - if (deviceInfo.minChannels > deviceInfo.nativeDataFormats[iNativeFormat].channels) { - deviceInfo.minChannels = deviceInfo.nativeDataFormats[iNativeFormat].channels; - } - if (deviceInfo.maxChannels < deviceInfo.nativeDataFormats[iNativeFormat].channels) { - deviceInfo.maxChannels = deviceInfo.nativeDataFormats[iNativeFormat].channels; - } - } - - /* Sample rate. */ - if (deviceInfo.nativeDataFormats[iNativeFormat].sampleRate == 0) { - /* All sample rates supported. */ - deviceInfo.minSampleRate = (ma_uint32)ma_standard_sample_rate_min; - deviceInfo.maxSampleRate = (ma_uint32)ma_standard_sample_rate_max; - } else { - if (deviceInfo.minSampleRate > deviceInfo.nativeDataFormats[iNativeFormat].sampleRate) { - deviceInfo.minSampleRate = deviceInfo.nativeDataFormats[iNativeFormat].sampleRate; - } - if (deviceInfo.maxSampleRate < deviceInfo.nativeDataFormats[iNativeFormat].sampleRate) { - deviceInfo.maxSampleRate = deviceInfo.nativeDataFormats[iNativeFormat].sampleRate; - } - } - } - } - - - /* Clamp ranges. */ - deviceInfo.minChannels = ma_max(deviceInfo.minChannels, MA_MIN_CHANNELS); - deviceInfo.maxChannels = ma_min(deviceInfo.maxChannels, MA_MAX_CHANNELS); - deviceInfo.minSampleRate = ma_max(deviceInfo.minSampleRate, (ma_uint32)ma_standard_sample_rate_min); - deviceInfo.maxSampleRate = ma_min(deviceInfo.maxSampleRate, (ma_uint32)ma_standard_sample_rate_max); - *pDeviceInfo = deviceInfo; return result; } @@ -33584,11 +39775,7 @@ MA_API ma_device_config ma_device_config_init(ma_device_type deviceType) ma_device_config config; MA_ZERO_OBJECT(&config); config.deviceType = deviceType; - - /* Resampling defaults. We must never use the Speex backend by default because it uses licensed third party code. */ - config.resampling.algorithm = ma_resample_algorithm_linear; - config.resampling.linear.lpfOrder = ma_min(MA_DEFAULT_RESAMPLER_LPF_ORDER, MA_MAX_FILTER_ORDER); - config.resampling.speex.quality = 3; + config.resampling = ma_resampler_config_init(ma_format_unknown, 0, 0, 0, ma_resample_algorithm_linear); /* Format/channels/rate don't matter here. */ return config; } @@ -33605,92 +39792,92 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC } if (pDevice == NULL) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "ma_device_init() called with invalid arguments (pDevice == NULL).", MA_INVALID_ARGS); + return MA_INVALID_ARGS; } MA_ZERO_OBJECT(pDevice); if (pConfig == NULL) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "ma_device_init() called with invalid arguments (pConfig == NULL).", MA_INVALID_ARGS); + return MA_INVALID_ARGS; } - /* Check that we have our callbacks defined. */ if (pContext->callbacks.onDeviceInit == NULL) { return MA_INVALID_OPERATION; } - /* Basic config validation. */ - if (pConfig->deviceType != ma_device_type_playback && pConfig->deviceType != ma_device_type_capture && pConfig->deviceType != ma_device_type_duplex && pConfig->deviceType != ma_device_type_loopback) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "ma_device_init() called with an invalid config. Device type is invalid. Make sure the device type has been set in the config.", MA_INVALID_DEVICE_CONFIG); - } - if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { if (pConfig->capture.channels > MA_MAX_CHANNELS) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "ma_device_init() called with an invalid config. Capture channel count cannot exceed 32.", MA_INVALID_DEVICE_CONFIG); + return MA_INVALID_ARGS; } - if (!ma__is_channel_map_valid(pConfig->capture.channelMap, pConfig->capture.channels)) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "ma_device_init() called with invalid config. Capture channel map is invalid.", MA_INVALID_DEVICE_CONFIG); + + if (!ma__is_channel_map_valid(pConfig->capture.pChannelMap, pConfig->capture.channels)) { + return MA_INVALID_ARGS; } } if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex || pConfig->deviceType == ma_device_type_loopback) { if (pConfig->playback.channels > MA_MAX_CHANNELS) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "ma_device_init() called with an invalid config. Playback channel count cannot exceed 32.", MA_INVALID_DEVICE_CONFIG); + return MA_INVALID_ARGS; } - if (!ma__is_channel_map_valid(pConfig->playback.channelMap, pConfig->playback.channels)) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "ma_device_init() called with invalid config. Playback channel map is invalid.", MA_INVALID_DEVICE_CONFIG); + + if (!ma__is_channel_map_valid(pConfig->playback.pChannelMap, pConfig->playback.channels)) { + return MA_INVALID_ARGS; } } pDevice->pContext = pContext; /* Set the user data and log callback ASAP to ensure it is available for the entire initialization process. */ - pDevice->pUserData = pConfig->pUserData; - pDevice->onData = pConfig->dataCallback; - pDevice->onStop = pConfig->stopCallback; - - if (((ma_uintptr)pDevice % sizeof(pDevice)) != 0) { - if (pContext->logCallback) { - pContext->logCallback(pContext, pDevice, MA_LOG_LEVEL_WARNING, "WARNING: ma_device_init() called for a device that is not properly aligned. Thread safety is not supported."); - } - } + pDevice->pUserData = pConfig->pUserData; + pDevice->onData = pConfig->dataCallback; + pDevice->onNotification = pConfig->notificationCallback; + pDevice->onStop = pConfig->stopCallback; if (pConfig->playback.pDeviceID != NULL) { MA_COPY_MEMORY(&pDevice->playback.id, pConfig->playback.pDeviceID, sizeof(pDevice->playback.id)); + pDevice->playback.pID = &pDevice->playback.id; + } else { + pDevice->playback.pID = NULL; } if (pConfig->capture.pDeviceID != NULL) { MA_COPY_MEMORY(&pDevice->capture.id, pConfig->capture.pDeviceID, sizeof(pDevice->capture.id)); + pDevice->capture.pID = &pDevice->capture.id; + } else { + pDevice->capture.pID = NULL; } - pDevice->noPreZeroedOutputBuffer = pConfig->noPreZeroedOutputBuffer; - pDevice->noClip = pConfig->noClip; - pDevice->masterVolumeFactor = 1; + pDevice->noPreSilencedOutputBuffer = pConfig->noPreSilencedOutputBuffer; + pDevice->noClip = pConfig->noClip; + pDevice->noDisableDenormals = pConfig->noDisableDenormals; + pDevice->noFixedSizedCallback = pConfig->noFixedSizedCallback; + pDevice->masterVolumeFactor = 1; - pDevice->type = pConfig->deviceType; - pDevice->sampleRate = pConfig->sampleRate; - pDevice->resampling.algorithm = pConfig->resampling.algorithm; - pDevice->resampling.linear.lpfOrder = pConfig->resampling.linear.lpfOrder; - pDevice->resampling.speex.quality = pConfig->resampling.speex.quality; + pDevice->type = pConfig->deviceType; + pDevice->sampleRate = pConfig->sampleRate; + pDevice->resampling.algorithm = pConfig->resampling.algorithm; + pDevice->resampling.linear.lpfOrder = pConfig->resampling.linear.lpfOrder; + pDevice->resampling.pBackendVTable = pConfig->resampling.pBackendVTable; + pDevice->resampling.pBackendUserData = pConfig->resampling.pBackendUserData; - pDevice->capture.shareMode = pConfig->capture.shareMode; - pDevice->capture.format = pConfig->capture.format; - pDevice->capture.channels = pConfig->capture.channels; - ma_channel_map_copy(pDevice->capture.channelMap, pConfig->capture.channelMap, pConfig->capture.channels); - pDevice->capture.channelMixMode = pConfig->capture.channelMixMode; + pDevice->capture.shareMode = pConfig->capture.shareMode; + pDevice->capture.format = pConfig->capture.format; + pDevice->capture.channels = pConfig->capture.channels; + ma_channel_map_copy_or_default(pDevice->capture.channelMap, ma_countof(pDevice->capture.channelMap), pConfig->capture.pChannelMap, pConfig->capture.channels); + pDevice->capture.channelMixMode = pConfig->capture.channelMixMode; - pDevice->playback.shareMode = pConfig->playback.shareMode; - pDevice->playback.format = pConfig->playback.format; - pDevice->playback.channels = pConfig->playback.channels; - ma_channel_map_copy(pDevice->playback.channelMap, pConfig->playback.channelMap, pConfig->playback.channels); - pDevice->playback.channelMixMode = pConfig->playback.channelMixMode; + pDevice->playback.shareMode = pConfig->playback.shareMode; + pDevice->playback.format = pConfig->playback.format; + pDevice->playback.channels = pConfig->playback.channels; + ma_channel_map_copy_or_default(pDevice->playback.channelMap, ma_countof(pDevice->playback.channelMap), pConfig->playback.pChannelMap, pConfig->playback.channels); + pDevice->playback.channelMixMode = pConfig->playback.channelMixMode; result = ma_mutex_init(&pDevice->startStopLock); if (result != MA_SUCCESS) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "Failed to create mutex.", result); + return result; } /* @@ -33703,14 +39890,14 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC result = ma_event_init(&pDevice->wakeupEvent); if (result != MA_SUCCESS) { ma_mutex_uninit(&pDevice->startStopLock); - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "Failed to create worker thread wakeup event.", result); + return result; } result = ma_event_init(&pDevice->startEvent); if (result != MA_SUCCESS) { ma_event_uninit(&pDevice->wakeupEvent); ma_mutex_uninit(&pDevice->startStopLock); - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "Failed to create worker thread start event.", result); + return result; } result = ma_event_init(&pDevice->stopEvent); @@ -33718,7 +39905,7 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC ma_event_uninit(&pDevice->startEvent); ma_event_uninit(&pDevice->wakeupEvent); ma_mutex_uninit(&pDevice->startStopLock); - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "Failed to create worker thread stop event.", result); + return result; } @@ -33728,7 +39915,7 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC descriptorPlayback.format = pConfig->playback.format; descriptorPlayback.channels = pConfig->playback.channels; descriptorPlayback.sampleRate = pConfig->sampleRate; - ma_channel_map_copy(descriptorPlayback.channelMap, pConfig->playback.channelMap, pConfig->playback.channels); + ma_channel_map_copy_or_default(descriptorPlayback.channelMap, ma_countof(descriptorPlayback.channelMap), pConfig->playback.pChannelMap, pConfig->playback.channels); descriptorPlayback.periodSizeInFrames = pConfig->periodSizeInFrames; descriptorPlayback.periodSizeInMilliseconds = pConfig->periodSizeInMilliseconds; descriptorPlayback.periodCount = pConfig->periods; @@ -33744,7 +39931,7 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC descriptorCapture.format = pConfig->capture.format; descriptorCapture.channels = pConfig->capture.channels; descriptorCapture.sampleRate = pConfig->sampleRate; - ma_channel_map_copy(descriptorCapture.channelMap, pConfig->capture.channelMap, pConfig->capture.channels); + ma_channel_map_copy_or_default(descriptorCapture.channelMap, ma_countof(descriptorCapture.channelMap), pConfig->capture.pChannelMap, pConfig->capture.channels); descriptorCapture.periodSizeInFrames = pConfig->periodSizeInFrames; descriptorCapture.periodSizeInMilliseconds = pConfig->periodSizeInMilliseconds; descriptorCapture.periodCount = pConfig->periods; @@ -33762,7 +39949,7 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC return result; } - +#if 0 /* On output the descriptors will contain the *actual* data format of the device. We need this to know how to convert the data between the requested format and the internal format. @@ -33812,7 +39999,7 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC ma_device_info deviceInfo; if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex || pConfig->deviceType == ma_device_type_loopback) { - result = ma_context_get_device_info(pContext, (pConfig->deviceType == ma_device_type_loopback) ? ma_device_type_playback : ma_device_type_capture, descriptorCapture.pDeviceID, descriptorCapture.shareMode, &deviceInfo); + result = ma_device_get_info(pDevice, (pConfig->deviceType == ma_device_type_loopback) ? ma_device_type_playback : ma_device_type_capture, &deviceInfo); if (result == MA_SUCCESS) { ma_strncpy_s(pDevice->capture.name, sizeof(pDevice->capture.name), deviceInfo.name, (size_t)-1); } else { @@ -33826,7 +40013,7 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC } if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { - result = ma_context_get_device_info(pContext, ma_device_type_playback, descriptorPlayback.pDeviceID, descriptorPlayback.shareMode, &deviceInfo); + result = ma_device_get_info(pDevice, ma_device_type_playback, &deviceInfo); if (result == MA_SUCCESS) { ma_strncpy_s(pDevice->playback.name, sizeof(pDevice->playback.name), deviceInfo.name, (size_t)-1); } else { @@ -33842,6 +40029,77 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC ma_device__post_init_setup(pDevice, pConfig->deviceType); +#endif + + result = ma_device_post_init(pDevice, pConfig->deviceType, &descriptorPlayback, &descriptorCapture); + if (result != MA_SUCCESS) { + ma_device_uninit(pDevice); + return result; + } + + + + /* + If we're using fixed sized callbacks we'll need to make use of an intermediary buffer. Needs to + be done after post_init_setup() because we'll need access to the sample rate. + */ + if (pConfig->noFixedSizedCallback == MA_FALSE) { + /* We're using a fixed sized data callback so we'll need an intermediary buffer. */ + ma_uint32 intermediaryBufferCap = pConfig->periodSizeInFrames; + if (intermediaryBufferCap == 0) { + intermediaryBufferCap = ma_calculate_buffer_size_in_frames_from_milliseconds(pConfig->periodSizeInMilliseconds, pDevice->sampleRate); + } + + if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex || pConfig->deviceType == ma_device_type_loopback) { + ma_uint32 intermediaryBufferSizeInBytes; + + pDevice->capture.intermediaryBufferLen = 0; + pDevice->capture.intermediaryBufferCap = intermediaryBufferCap; + if (pDevice->capture.intermediaryBufferCap == 0) { + pDevice->capture.intermediaryBufferCap = pDevice->capture.internalPeriodSizeInFrames; + } + + intermediaryBufferSizeInBytes = pDevice->capture.intermediaryBufferCap * ma_get_bytes_per_frame(pDevice->capture.format, pDevice->capture.channels); + + pDevice->capture.pIntermediaryBuffer = ma_malloc((size_t)intermediaryBufferSizeInBytes, &pContext->allocationCallbacks); + if (pDevice->capture.pIntermediaryBuffer == NULL) { + ma_device_uninit(pDevice); + return MA_OUT_OF_MEMORY; + } + + /* Silence the buffer for safety. */ + ma_silence_pcm_frames(pDevice->capture.pIntermediaryBuffer, pDevice->capture.intermediaryBufferCap, pDevice->capture.format, pDevice->capture.channels); + pDevice->capture.intermediaryBufferLen = pDevice->capture.intermediaryBufferCap; + } + + if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { + ma_uint64 intermediaryBufferSizeInBytes; + + pDevice->playback.intermediaryBufferLen = 0; + if (pConfig->deviceType == ma_device_type_duplex) { + pDevice->playback.intermediaryBufferCap = pDevice->capture.intermediaryBufferCap; /* In duplex mode, make sure the intermediary buffer is always the same size as the capture side. */ + } else { + pDevice->playback.intermediaryBufferCap = intermediaryBufferCap; + if (pDevice->playback.intermediaryBufferCap == 0) { + pDevice->playback.intermediaryBufferCap = pDevice->playback.internalPeriodSizeInFrames; + } + } + + intermediaryBufferSizeInBytes = pDevice->playback.intermediaryBufferCap * ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels); + + pDevice->playback.pIntermediaryBuffer = ma_malloc((size_t)intermediaryBufferSizeInBytes, &pContext->allocationCallbacks); + if (pDevice->playback.pIntermediaryBuffer == NULL) { + ma_device_uninit(pDevice); + return MA_OUT_OF_MEMORY; + } + + /* Silence the buffer for safety. */ + ma_silence_pcm_frames(pDevice->playback.pIntermediaryBuffer, pDevice->playback.intermediaryBufferCap, pDevice->playback.format, pDevice->playback.channels); + pDevice->playback.intermediaryBufferLen = 0; + } + } else { + /* Not using a fixed sized data callback so no need for an intermediary buffer. */ + } /* Some backends don't require the worker thread. */ @@ -33850,12 +40108,12 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC result = ma_thread_create(&pDevice->thread, pContext->threadPriority, pContext->threadStackSize, ma_worker_thread, pDevice, &pContext->allocationCallbacks); if (result != MA_SUCCESS) { ma_device_uninit(pDevice); - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "Failed to create worker thread.", result); + return result; } /* Wait for the worker thread to put the device into it's stopped state for real. */ ma_event_wait(&pDevice->stopEvent); - MA_ASSERT(ma_device_get_state(pDevice) == MA_STATE_STOPPED); + MA_ASSERT(ma_device_get_state(pDevice) == ma_device_state_stopped); } else { /* If the backend is asynchronous and the device is duplex, we'll need an intermediary ring buffer. Note that this needs to be done @@ -33871,39 +40129,47 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC } } - ma_device__set_state(pDevice, MA_STATE_STOPPED); + ma_device__set_state(pDevice, ma_device_state_stopped); } + /* Log device information. */ + { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[%s]\n", ma_get_backend_name(pDevice->pContext->backend)); + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { + char name[MA_MAX_DEVICE_NAME_LENGTH + 1]; + ma_device_get_name(pDevice, ma_device_type_capture, name, sizeof(name), NULL); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[%s]\n", ma_get_backend_name(pDevice->pContext->backend)); - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " %s (%s)\n", pDevice->capture.name, "Capture"); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Format: %s -> %s\n", ma_get_format_name(pDevice->capture.internalFormat), ma_get_format_name(pDevice->capture.format)); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Channels: %d -> %d\n", pDevice->capture.internalChannels, pDevice->capture.channels); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Sample Rate: %d -> %d\n", pDevice->capture.internalSampleRate, pDevice->sampleRate); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Buffer Size: %d*%d (%d)\n", pDevice->capture.internalPeriodSizeInFrames, pDevice->capture.internalPeriods, (pDevice->capture.internalPeriodSizeInFrames * pDevice->capture.internalPeriods)); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Conversion:\n"); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Pre Format Conversion: %s\n", pDevice->capture.converter.hasPreFormatConversion ? "YES" : "NO"); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Post Format Conversion: %s\n", pDevice->capture.converter.hasPostFormatConversion ? "YES" : "NO"); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Channel Routing: %s\n", pDevice->capture.converter.hasChannelConverter ? "YES" : "NO"); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Resampling: %s\n", pDevice->capture.converter.hasResampler ? "YES" : "NO"); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Passthrough: %s\n", pDevice->capture.converter.isPassthrough ? "YES" : "NO"); - } - if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " %s (%s)\n", pDevice->playback.name, "Playback"); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Format: %s -> %s\n", ma_get_format_name(pDevice->playback.format), ma_get_format_name(pDevice->playback.internalFormat)); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Channels: %d -> %d\n", pDevice->playback.channels, pDevice->playback.internalChannels); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Sample Rate: %d -> %d\n", pDevice->sampleRate, pDevice->playback.internalSampleRate); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Buffer Size: %d*%d (%d)\n", pDevice->playback.internalPeriodSizeInFrames, pDevice->playback.internalPeriods, (pDevice->playback.internalPeriodSizeInFrames * pDevice->playback.internalPeriods)); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Conversion:\n"); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Pre Format Conversion: %s\n", pDevice->playback.converter.hasPreFormatConversion ? "YES" : "NO"); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Post Format Conversion: %s\n", pDevice->playback.converter.hasPostFormatConversion ? "YES" : "NO"); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Channel Routing: %s\n", pDevice->playback.converter.hasChannelConverter ? "YES" : "NO"); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Resampling: %s\n", pDevice->playback.converter.hasResampler ? "YES" : "NO"); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Passthrough: %s\n", pDevice->playback.converter.isPassthrough ? "YES" : "NO"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " %s (%s)\n", name, "Capture"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Format: %s -> %s\n", ma_get_format_name(pDevice->capture.internalFormat), ma_get_format_name(pDevice->capture.format)); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Channels: %d -> %d\n", pDevice->capture.internalChannels, pDevice->capture.channels); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Sample Rate: %d -> %d\n", pDevice->capture.internalSampleRate, pDevice->sampleRate); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Buffer Size: %d*%d (%d)\n", pDevice->capture.internalPeriodSizeInFrames, pDevice->capture.internalPeriods, (pDevice->capture.internalPeriodSizeInFrames * pDevice->capture.internalPeriods)); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Conversion:\n"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Pre Format Conversion: %s\n", pDevice->capture.converter.hasPreFormatConversion ? "YES" : "NO"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Post Format Conversion: %s\n", pDevice->capture.converter.hasPostFormatConversion ? "YES" : "NO"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Channel Routing: %s\n", pDevice->capture.converter.hasChannelConverter ? "YES" : "NO"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Resampling: %s\n", pDevice->capture.converter.hasResampler ? "YES" : "NO"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Passthrough: %s\n", pDevice->capture.converter.isPassthrough ? "YES" : "NO"); + } + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { + char name[MA_MAX_DEVICE_NAME_LENGTH + 1]; + ma_device_get_name(pDevice, ma_device_type_playback, name, sizeof(name), NULL); + + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " %s (%s)\n", name, "Playback"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Format: %s -> %s\n", ma_get_format_name(pDevice->playback.format), ma_get_format_name(pDevice->playback.internalFormat)); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Channels: %d -> %d\n", pDevice->playback.channels, pDevice->playback.internalChannels); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Sample Rate: %d -> %d\n", pDevice->sampleRate, pDevice->playback.internalSampleRate); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Buffer Size: %d*%d (%d)\n", pDevice->playback.internalPeriodSizeInFrames, pDevice->playback.internalPeriods, (pDevice->playback.internalPeriodSizeInFrames * pDevice->playback.internalPeriods)); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Conversion:\n"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Pre Format Conversion: %s\n", pDevice->playback.converter.hasPreFormatConversion ? "YES" : "NO"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Post Format Conversion: %s\n", pDevice->playback.converter.hasPostFormatConversion ? "YES" : "NO"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Channel Routing: %s\n", pDevice->playback.converter.hasChannelConverter ? "YES" : "NO"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Resampling: %s\n", pDevice->playback.converter.hasResampler ? "YES" : "NO"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Passthrough: %s\n", pDevice->playback.converter.isPassthrough ? "YES" : "NO"); + } } - MA_ASSERT(ma_device_get_state(pDevice) == MA_STATE_STOPPED); + MA_ASSERT(ma_device_get_state(pDevice) == ma_device_state_stopped); return MA_SUCCESS; } @@ -33931,7 +40197,7 @@ MA_API ma_result ma_device_init_ex(const ma_backend backends[], ma_uint32 backen } - pContext = (ma_context*)ma__malloc_from_callbacks(sizeof(*pContext), &allocationCallbacks); + pContext = (ma_context*)ma_malloc(sizeof(*pContext), &allocationCallbacks); if (pContext == NULL) { return MA_OUT_OF_MEMORY; } @@ -33962,7 +40228,7 @@ MA_API ma_result ma_device_init_ex(const ma_backend backends[], ma_uint32 backen } if (result != MA_SUCCESS) { - ma__free_from_callbacks(pContext, &allocationCallbacks); + ma_free(pContext, &allocationCallbacks); return result; } @@ -33982,7 +40248,7 @@ MA_API void ma_device_uninit(ma_device* pDevice) } /* Putting the device into an uninitialized state will make the worker thread return. */ - ma_device__set_state(pDevice, MA_STATE_UNINITIALIZED); + ma_device__set_state(pDevice, ma_device_state_uninitialized); /* Wake up the worker thread and wait for it to properly terminate. */ if (!ma_context_is_backend_asynchronous(pDevice->pContext)) { @@ -34006,11 +40272,29 @@ MA_API void ma_device_uninit(ma_device* pDevice) } } + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex || pDevice->type == ma_device_type_loopback) { + ma_data_converter_uninit(&pDevice->capture.converter, &pDevice->pContext->allocationCallbacks); + } + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { + ma_data_converter_uninit(&pDevice->playback.converter, &pDevice->pContext->allocationCallbacks); + } + + if (pDevice->playback.pInputCache != NULL) { + ma_free(pDevice->playback.pInputCache, &pDevice->pContext->allocationCallbacks); + } + + if (pDevice->capture.pIntermediaryBuffer != NULL) { + ma_free(pDevice->capture.pIntermediaryBuffer, &pDevice->pContext->allocationCallbacks); + } + if (pDevice->playback.pIntermediaryBuffer != NULL) { + ma_free(pDevice->playback.pIntermediaryBuffer, &pDevice->pContext->allocationCallbacks); + } + if (pDevice->isOwnerOfContext) { ma_allocation_callbacks allocationCallbacks = pDevice->pContext->allocationCallbacks; ma_context_uninit(pDevice->pContext); - ma__free_from_callbacks(pDevice->pContext, &allocationCallbacks); + ma_free(pDevice->pContext, &allocationCallbacks); } MA_ZERO_OBJECT(pDevice); @@ -34030,28 +40314,92 @@ MA_API ma_log* ma_device_get_log(ma_device* pDevice) return ma_context_get_log(ma_device_get_context(pDevice)); } +MA_API ma_result ma_device_get_info(ma_device* pDevice, ma_device_type type, ma_device_info* pDeviceInfo) +{ + if (pDeviceInfo == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pDeviceInfo); + + if (pDevice == NULL) { + return MA_INVALID_ARGS; + } + + /* If the onDeviceGetInfo() callback is set, use that. Otherwise we'll fall back to ma_context_get_device_info(). */ + if (pDevice->pContext->callbacks.onDeviceGetInfo != NULL) { + return pDevice->pContext->callbacks.onDeviceGetInfo(pDevice, type, pDeviceInfo); + } + + /* Getting here means onDeviceGetInfo is not implemented so we need to fall back to an alternative. */ + if (type == ma_device_type_playback) { + return ma_context_get_device_info(pDevice->pContext, type, pDevice->playback.pID, pDeviceInfo); + } else { + return ma_context_get_device_info(pDevice->pContext, type, pDevice->capture.pID, pDeviceInfo); + } +} + +MA_API ma_result ma_device_get_name(ma_device* pDevice, ma_device_type type, char* pName, size_t nameCap, size_t* pLengthNotIncludingNullTerminator) +{ + ma_result result; + ma_device_info deviceInfo; + + if (pLengthNotIncludingNullTerminator != NULL) { + *pLengthNotIncludingNullTerminator = 0; + } + + if (pName != NULL && nameCap > 0) { + pName[0] = '\0'; + } + + result = ma_device_get_info(pDevice, type, &deviceInfo); + if (result != MA_SUCCESS) { + return result; + } + + if (pName != NULL) { + ma_strncpy_s(pName, nameCap, deviceInfo.name, (size_t)-1); + + /* + For safety, make sure the length is based on the truncated output string rather than the + source. Otherwise the caller might assume the output buffer contains more content than it + actually does. + */ + if (pLengthNotIncludingNullTerminator != NULL) { + *pLengthNotIncludingNullTerminator = strlen(pName); + } + } else { + /* Name not specified. Just report the length of the source string. */ + if (pLengthNotIncludingNullTerminator != NULL) { + *pLengthNotIncludingNullTerminator = strlen(deviceInfo.name); + } + } + + return MA_SUCCESS; +} + MA_API ma_result ma_device_start(ma_device* pDevice) { ma_result result; if (pDevice == NULL) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "ma_device_start() called with invalid arguments (pDevice == NULL).", MA_INVALID_ARGS); + return MA_INVALID_ARGS; } - if (ma_device_get_state(pDevice) == MA_STATE_UNINITIALIZED) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "ma_device_start() called for an uninitialized device.", MA_DEVICE_NOT_INITIALIZED); + if (ma_device_get_state(pDevice) == ma_device_state_uninitialized) { + return MA_INVALID_OPERATION; /* Not initialized. */ } - if (ma_device_get_state(pDevice) == MA_STATE_STARTED) { - return ma_post_error(pDevice, MA_LOG_LEVEL_WARNING, "ma_device_start() called when the device is already started.", MA_INVALID_OPERATION); /* Already started. Returning an error to let the application know because it probably means they're doing something wrong. */ + if (ma_device_get_state(pDevice) == ma_device_state_started) { + return MA_SUCCESS; /* Already started. */ } ma_mutex_lock(&pDevice->startStopLock); { /* Starting and stopping are wrapped in a mutex which means we can assert that the device is in a stopped or paused state. */ - MA_ASSERT(ma_device_get_state(pDevice) == MA_STATE_STOPPED); + MA_ASSERT(ma_device_get_state(pDevice) == ma_device_state_stopped); - ma_device__set_state(pDevice, MA_STATE_STARTING); + ma_device__set_state(pDevice, ma_device_state_starting); /* Asynchronous backends need to be handled differently. */ if (ma_context_is_backend_asynchronous(pDevice->pContext)) { @@ -34062,7 +40410,8 @@ MA_API ma_result ma_device_start(ma_device* pDevice) } if (result == MA_SUCCESS) { - ma_device__set_state(pDevice, MA_STATE_STARTED); + ma_device__set_state(pDevice, ma_device_state_started); + ma_device__on_notification_started(pDevice); } } else { /* @@ -34081,7 +40430,7 @@ MA_API ma_result ma_device_start(ma_device* pDevice) /* We changed the state from stopped to started, so if we failed, make sure we put the state back to stopped. */ if (result != MA_SUCCESS) { - ma_device__set_state(pDevice, MA_STATE_STOPPED); + ma_device__set_state(pDevice, ma_device_state_stopped); } } ma_mutex_unlock(&pDevice->startStopLock); @@ -34094,23 +40443,23 @@ MA_API ma_result ma_device_stop(ma_device* pDevice) ma_result result; if (pDevice == NULL) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "ma_device_stop() called with invalid arguments (pDevice == NULL).", MA_INVALID_ARGS); + return MA_INVALID_ARGS; } - if (ma_device_get_state(pDevice) == MA_STATE_UNINITIALIZED) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "ma_device_stop() called for an uninitialized device.", MA_DEVICE_NOT_INITIALIZED); + if (ma_device_get_state(pDevice) == ma_device_state_uninitialized) { + return MA_INVALID_OPERATION; /* Not initialized. */ } - if (ma_device_get_state(pDevice) == MA_STATE_STOPPED) { - return ma_post_error(pDevice, MA_LOG_LEVEL_WARNING, "ma_device_stop() called when the device is already stopped.", MA_INVALID_OPERATION); /* Already stopped. Returning an error to let the application know because it probably means they're doing something wrong. */ + if (ma_device_get_state(pDevice) == ma_device_state_stopped) { + return MA_SUCCESS; /* Already stopped. */ } ma_mutex_lock(&pDevice->startStopLock); { /* Starting and stopping are wrapped in a mutex which means we can assert that the device is in a started or paused state. */ - MA_ASSERT(ma_device_get_state(pDevice) == MA_STATE_STARTED); + MA_ASSERT(ma_device_get_state(pDevice) == ma_device_state_started); - ma_device__set_state(pDevice, MA_STATE_STOPPING); + ma_device__set_state(pDevice, ma_device_state_stopping); /* Asynchronous backends need to be handled differently. */ if (ma_context_is_backend_asynchronous(pDevice->pContext)) { @@ -34121,7 +40470,7 @@ MA_API ma_result ma_device_stop(ma_device* pDevice) result = MA_INVALID_OPERATION; } - ma_device__set_state(pDevice, MA_STATE_STOPPED); + ma_device__set_state(pDevice, ma_device_state_stopped); } else { /* Synchronous backends. The stop callback is always called from the worker thread. Do not call the stop callback here. If @@ -34129,7 +40478,7 @@ MA_API ma_result ma_device_stop(ma_device* pDevice) sure the state of the device is *not* playing right now, which it shouldn't be since we set it above. This is super important though, so I'm asserting it here as well for extra safety in case we accidentally change something later. */ - MA_ASSERT(ma_device_get_state(pDevice) != MA_STATE_STARTED); + MA_ASSERT(ma_device_get_state(pDevice) != ma_device_state_started); if (pDevice->pContext->callbacks.onDeviceDataLoopWakeup != NULL) { pDevice->pContext->callbacks.onDeviceDataLoopWakeup(pDevice); @@ -34150,16 +40499,16 @@ MA_API ma_result ma_device_stop(ma_device* pDevice) MA_API ma_bool32 ma_device_is_started(const ma_device* pDevice) { - return ma_device_get_state(pDevice) == MA_STATE_STARTED; + return ma_device_get_state(pDevice) == ma_device_state_started; } -MA_API ma_uint32 ma_device_get_state(const ma_device* pDevice) +MA_API ma_device_state ma_device_get_state(const ma_device* pDevice) { if (pDevice == NULL) { - return MA_STATE_UNINITIALIZED; + return ma_device_state_uninitialized; } - return c89atomic_load_32((ma_uint32*)&pDevice->state); /* Naughty cast to get rid of a const warning. */ + return (ma_device_state)c89atomic_load_i32((ma_int32*)&pDevice->state); /* Naughty cast to get rid of a const warning. */ } MA_API ma_result ma_device_set_master_volume(ma_device* pDevice, float volume) @@ -34168,7 +40517,7 @@ MA_API ma_result ma_device_set_master_volume(ma_device* pDevice, float volume) return MA_INVALID_ARGS; } - if (volume < 0.0f || volume > 1.0f) { + if (volume < 0.0f) { return MA_INVALID_ARGS; } @@ -34193,16 +40542,16 @@ MA_API ma_result ma_device_get_master_volume(ma_device* pDevice, float* pVolume) return MA_SUCCESS; } -MA_API ma_result ma_device_set_master_gain_db(ma_device* pDevice, float gainDB) +MA_API ma_result ma_device_set_master_volume_db(ma_device* pDevice, float gainDB) { if (gainDB > 0) { return MA_INVALID_ARGS; } - return ma_device_set_master_volume(pDevice, ma_gain_db_to_factor(gainDB)); + return ma_device_set_master_volume(pDevice, ma_volume_db_to_linear(gainDB)); } -MA_API ma_result ma_device_get_master_gain_db(ma_device* pDevice, float* pGainDB) +MA_API ma_result ma_device_get_master_volume_db(ma_device* pDevice, float* pGainDB) { float factor; ma_result result; @@ -34217,7 +40566,7 @@ MA_API ma_result ma_device_get_master_gain_db(ma_device* pDevice, float* pGainDB return result; } - *pGainDB = ma_factor_to_gain_db(factor); + *pGainDB = ma_volume_linear_to_db(factor); return MA_SUCCESS; } @@ -34300,11 +40649,6 @@ MA_API ma_uint32 ma_calculate_buffer_size_in_frames_from_descriptor(const ma_dev #endif /* MA_NO_DEVICE_IO */ -MA_API ma_uint32 ma_scale_buffer_size(ma_uint32 baseBufferSize, float scale) -{ - return ma_max(1, (ma_uint32)(baseBufferSize*scale)); -} - MA_API ma_uint32 ma_calculate_buffer_size_in_milliseconds_from_frames(ma_uint32 bufferSizeInFrames, ma_uint32 sampleRate) { /* Prevent a division by zero. */ @@ -34358,13 +40702,89 @@ MA_API const void* ma_offset_pcm_frames_const_ptr(const void* p, ma_uint64 offse } -MA_API void ma_clip_samples_f32(float* p, ma_uint64 sampleCount) +MA_API void ma_clip_samples_u8(ma_uint8* pDst, const ma_int16* pSrc, ma_uint64 count) { - ma_uint32 iSample; + ma_uint64 iSample; - /* TODO: Research a branchless SSE implementation. */ - for (iSample = 0; iSample < sampleCount; iSample += 1) { - p[iSample] = ma_clip_f32(p[iSample]); + MA_ASSERT(pDst != NULL); + MA_ASSERT(pSrc != NULL); + + for (iSample = 0; iSample < count; iSample += 1) { + pDst[iSample] = ma_clip_u8(pSrc[iSample]); + } +} + +MA_API void ma_clip_samples_s16(ma_int16* pDst, const ma_int32* pSrc, ma_uint64 count) +{ + ma_uint64 iSample; + + MA_ASSERT(pDst != NULL); + MA_ASSERT(pSrc != NULL); + + for (iSample = 0; iSample < count; iSample += 1) { + pDst[iSample] = ma_clip_s16(pSrc[iSample]); + } +} + +MA_API void ma_clip_samples_s24(ma_uint8* pDst, const ma_int64* pSrc, ma_uint64 count) +{ + ma_uint64 iSample; + + MA_ASSERT(pDst != NULL); + MA_ASSERT(pSrc != NULL); + + for (iSample = 0; iSample < count; iSample += 1) { + ma_int64 s = ma_clip_s24(pSrc[iSample]); + pDst[iSample*3 + 0] = (ma_uint8)((s & 0x000000FF) >> 0); + pDst[iSample*3 + 1] = (ma_uint8)((s & 0x0000FF00) >> 8); + pDst[iSample*3 + 2] = (ma_uint8)((s & 0x00FF0000) >> 16); + } +} + +MA_API void ma_clip_samples_s32(ma_int32* pDst, const ma_int64* pSrc, ma_uint64 count) +{ + ma_uint64 iSample; + + MA_ASSERT(pDst != NULL); + MA_ASSERT(pSrc != NULL); + + for (iSample = 0; iSample < count; iSample += 1) { + pDst[iSample] = ma_clip_s32(pSrc[iSample]); + } +} + +MA_API void ma_clip_samples_f32(float* pDst, const float* pSrc, ma_uint64 count) +{ + ma_uint64 iSample; + + MA_ASSERT(pDst != NULL); + MA_ASSERT(pSrc != NULL); + + for (iSample = 0; iSample < count; iSample += 1) { + pDst[iSample] = ma_clip_f32(pSrc[iSample]); + } +} + +MA_API void ma_clip_pcm_frames(void* pDst, const void* pSrc, ma_uint64 frameCount, ma_format format, ma_uint32 channels) +{ + ma_uint64 sampleCount; + + MA_ASSERT(pDst != NULL); + MA_ASSERT(pSrc != NULL); + + sampleCount = frameCount * channels; + + switch (format) { + case ma_format_u8: ma_clip_samples_u8( (ma_uint8*)pDst, (const ma_int16*)pSrc, sampleCount); break; + case ma_format_s16: ma_clip_samples_s16((ma_int16*)pDst, (const ma_int32*)pSrc, sampleCount); break; + case ma_format_s24: ma_clip_samples_s24((ma_uint8*)pDst, (const ma_int64*)pSrc, sampleCount); break; + case ma_format_s32: ma_clip_samples_s32((ma_int32*)pDst, (const ma_int64*)pSrc, sampleCount); break; + case ma_format_f32: ma_clip_samples_f32(( float*)pDst, (const float*)pSrc, sampleCount); break; + + /* Do nothing if we don't know the format. We're including these here to silence a compiler warning about enums not being handled by the switch. */ + case ma_format_unknown: + case ma_format_count: + break; } } @@ -34441,8 +40861,19 @@ MA_API void ma_copy_and_apply_volume_factor_f32(float* pSamplesOut, const float* return; } - for (iSample = 0; iSample < sampleCount; iSample += 1) { - pSamplesOut[iSample] = pSamplesIn[iSample] * factor; + if (factor == 1) { + if (pSamplesOut == pSamplesIn) { + /* In place. No-op. */ + } else { + /* Just a copy. */ + for (iSample = 0; iSample < sampleCount; iSample += 1) { + pSamplesOut[iSample] = pSamplesIn[iSample]; + } + } + } else { + for (iSample = 0; iSample < sampleCount; iSample += 1) { + pSamplesOut[iSample] = pSamplesIn[iSample] * factor; + } } } @@ -34471,85 +40902,236 @@ MA_API void ma_apply_volume_factor_f32(float* pSamples, ma_uint64 sampleCount, f ma_copy_and_apply_volume_factor_f32(pSamples, pSamples, sampleCount, factor); } -MA_API void ma_copy_and_apply_volume_factor_pcm_frames_u8(ma_uint8* pPCMFramesOut, const ma_uint8* pPCMFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor) +MA_API void ma_copy_and_apply_volume_factor_pcm_frames_u8(ma_uint8* pFramesOut, const ma_uint8* pFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor) { - ma_copy_and_apply_volume_factor_u8(pPCMFramesOut, pPCMFramesIn, frameCount*channels, factor); + ma_copy_and_apply_volume_factor_u8(pFramesOut, pFramesIn, frameCount*channels, factor); } -MA_API void ma_copy_and_apply_volume_factor_pcm_frames_s16(ma_int16* pPCMFramesOut, const ma_int16* pPCMFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor) +MA_API void ma_copy_and_apply_volume_factor_pcm_frames_s16(ma_int16* pFramesOut, const ma_int16* pFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor) { - ma_copy_and_apply_volume_factor_s16(pPCMFramesOut, pPCMFramesIn, frameCount*channels, factor); + ma_copy_and_apply_volume_factor_s16(pFramesOut, pFramesIn, frameCount*channels, factor); } -MA_API void ma_copy_and_apply_volume_factor_pcm_frames_s24(void* pPCMFramesOut, const void* pPCMFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor) +MA_API void ma_copy_and_apply_volume_factor_pcm_frames_s24(void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor) { - ma_copy_and_apply_volume_factor_s24(pPCMFramesOut, pPCMFramesIn, frameCount*channels, factor); + ma_copy_and_apply_volume_factor_s24(pFramesOut, pFramesIn, frameCount*channels, factor); } -MA_API void ma_copy_and_apply_volume_factor_pcm_frames_s32(ma_int32* pPCMFramesOut, const ma_int32* pPCMFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor) +MA_API void ma_copy_and_apply_volume_factor_pcm_frames_s32(ma_int32* pFramesOut, const ma_int32* pFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor) { - ma_copy_and_apply_volume_factor_s32(pPCMFramesOut, pPCMFramesIn, frameCount*channels, factor); + ma_copy_and_apply_volume_factor_s32(pFramesOut, pFramesIn, frameCount*channels, factor); } -MA_API void ma_copy_and_apply_volume_factor_pcm_frames_f32(float* pPCMFramesOut, const float* pPCMFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor) +MA_API void ma_copy_and_apply_volume_factor_pcm_frames_f32(float* pFramesOut, const float* pFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor) { - ma_copy_and_apply_volume_factor_f32(pPCMFramesOut, pPCMFramesIn, frameCount*channels, factor); + ma_copy_and_apply_volume_factor_f32(pFramesOut, pFramesIn, frameCount*channels, factor); } -MA_API void ma_copy_and_apply_volume_factor_pcm_frames(void* pPCMFramesOut, const void* pPCMFramesIn, ma_uint64 frameCount, ma_format format, ma_uint32 channels, float factor) +MA_API void ma_copy_and_apply_volume_factor_pcm_frames(void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount, ma_format format, ma_uint32 channels, float factor) { switch (format) { - case ma_format_u8: ma_copy_and_apply_volume_factor_pcm_frames_u8 ((ma_uint8*)pPCMFramesOut, (const ma_uint8*)pPCMFramesIn, frameCount, channels, factor); return; - case ma_format_s16: ma_copy_and_apply_volume_factor_pcm_frames_s16((ma_int16*)pPCMFramesOut, (const ma_int16*)pPCMFramesIn, frameCount, channels, factor); return; - case ma_format_s24: ma_copy_and_apply_volume_factor_pcm_frames_s24( pPCMFramesOut, pPCMFramesIn, frameCount, channels, factor); return; - case ma_format_s32: ma_copy_and_apply_volume_factor_pcm_frames_s32((ma_int32*)pPCMFramesOut, (const ma_int32*)pPCMFramesIn, frameCount, channels, factor); return; - case ma_format_f32: ma_copy_and_apply_volume_factor_pcm_frames_f32( (float*)pPCMFramesOut, (const float*)pPCMFramesIn, frameCount, channels, factor); return; + case ma_format_u8: ma_copy_and_apply_volume_factor_pcm_frames_u8 ((ma_uint8*)pFramesOut, (const ma_uint8*)pFramesIn, frameCount, channels, factor); return; + case ma_format_s16: ma_copy_and_apply_volume_factor_pcm_frames_s16((ma_int16*)pFramesOut, (const ma_int16*)pFramesIn, frameCount, channels, factor); return; + case ma_format_s24: ma_copy_and_apply_volume_factor_pcm_frames_s24( pFramesOut, pFramesIn, frameCount, channels, factor); return; + case ma_format_s32: ma_copy_and_apply_volume_factor_pcm_frames_s32((ma_int32*)pFramesOut, (const ma_int32*)pFramesIn, frameCount, channels, factor); return; + case ma_format_f32: ma_copy_and_apply_volume_factor_pcm_frames_f32( (float*)pFramesOut, (const float*)pFramesIn, frameCount, channels, factor); return; default: return; /* Do nothing. */ } } -MA_API void ma_apply_volume_factor_pcm_frames_u8(ma_uint8* pPCMFrames, ma_uint64 frameCount, ma_uint32 channels, float factor) +MA_API void ma_apply_volume_factor_pcm_frames_u8(ma_uint8* pFrames, ma_uint64 frameCount, ma_uint32 channels, float factor) { - ma_copy_and_apply_volume_factor_pcm_frames_u8(pPCMFrames, pPCMFrames, frameCount, channels, factor); + ma_copy_and_apply_volume_factor_pcm_frames_u8(pFrames, pFrames, frameCount, channels, factor); } -MA_API void ma_apply_volume_factor_pcm_frames_s16(ma_int16* pPCMFrames, ma_uint64 frameCount, ma_uint32 channels, float factor) +MA_API void ma_apply_volume_factor_pcm_frames_s16(ma_int16* pFrames, ma_uint64 frameCount, ma_uint32 channels, float factor) { - ma_copy_and_apply_volume_factor_pcm_frames_s16(pPCMFrames, pPCMFrames, frameCount, channels, factor); + ma_copy_and_apply_volume_factor_pcm_frames_s16(pFrames, pFrames, frameCount, channels, factor); } -MA_API void ma_apply_volume_factor_pcm_frames_s24(void* pPCMFrames, ma_uint64 frameCount, ma_uint32 channels, float factor) +MA_API void ma_apply_volume_factor_pcm_frames_s24(void* pFrames, ma_uint64 frameCount, ma_uint32 channels, float factor) { - ma_copy_and_apply_volume_factor_pcm_frames_s24(pPCMFrames, pPCMFrames, frameCount, channels, factor); + ma_copy_and_apply_volume_factor_pcm_frames_s24(pFrames, pFrames, frameCount, channels, factor); } -MA_API void ma_apply_volume_factor_pcm_frames_s32(ma_int32* pPCMFrames, ma_uint64 frameCount, ma_uint32 channels, float factor) +MA_API void ma_apply_volume_factor_pcm_frames_s32(ma_int32* pFrames, ma_uint64 frameCount, ma_uint32 channels, float factor) { - ma_copy_and_apply_volume_factor_pcm_frames_s32(pPCMFrames, pPCMFrames, frameCount, channels, factor); + ma_copy_and_apply_volume_factor_pcm_frames_s32(pFrames, pFrames, frameCount, channels, factor); } -MA_API void ma_apply_volume_factor_pcm_frames_f32(float* pPCMFrames, ma_uint64 frameCount, ma_uint32 channels, float factor) +MA_API void ma_apply_volume_factor_pcm_frames_f32(float* pFrames, ma_uint64 frameCount, ma_uint32 channels, float factor) { - ma_copy_and_apply_volume_factor_pcm_frames_f32(pPCMFrames, pPCMFrames, frameCount, channels, factor); + ma_copy_and_apply_volume_factor_pcm_frames_f32(pFrames, pFrames, frameCount, channels, factor); } -MA_API void ma_apply_volume_factor_pcm_frames(void* pPCMFrames, ma_uint64 frameCount, ma_format format, ma_uint32 channels, float factor) +MA_API void ma_apply_volume_factor_pcm_frames(void* pFramesOut, ma_uint64 frameCount, ma_format format, ma_uint32 channels, float factor) { - ma_copy_and_apply_volume_factor_pcm_frames(pPCMFrames, pPCMFrames, frameCount, format, channels, factor); + ma_copy_and_apply_volume_factor_pcm_frames(pFramesOut, pFramesOut, frameCount, format, channels, factor); } -MA_API float ma_factor_to_gain_db(float factor) +MA_API void ma_copy_and_apply_volume_factor_per_channel_f32(float* pFramesOut, const float* pFramesIn, ma_uint64 frameCount, ma_uint32 channels, float* pChannelGains) { - return (float)(20*ma_log10f(factor)); + ma_uint64 iFrame; + + if (channels == 2) { + /* TODO: Do an optimized implementation for stereo and mono. Can do a SIMD optimized implementation as well. */ + } + + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + ma_uint32 iChannel; + for (iChannel = 0; iChannel < channels; iChannel += 1) { + pFramesOut[iFrame * channels + iChannel] = pFramesIn[iFrame * channels + iChannel] * pChannelGains[iChannel]; + } + } } -MA_API float ma_gain_db_to_factor(float gain) + + +static MA_INLINE ma_int16 ma_apply_volume_unclipped_u8(ma_int16 x, ma_int16 volume) { - return (float)ma_powf(10, gain/20.0f); + return (ma_int16)(((ma_int32)x * (ma_int32)volume) >> 8); } +static MA_INLINE ma_int32 ma_apply_volume_unclipped_s16(ma_int32 x, ma_int16 volume) +{ + return (ma_int32)((x * volume) >> 8); +} + +static MA_INLINE ma_int64 ma_apply_volume_unclipped_s24(ma_int64 x, ma_int16 volume) +{ + return (ma_int64)((x * volume) >> 8); +} + +static MA_INLINE ma_int64 ma_apply_volume_unclipped_s32(ma_int64 x, ma_int16 volume) +{ + return (ma_int64)((x * volume) >> 8); +} + +static MA_INLINE float ma_apply_volume_unclipped_f32(float x, float volume) +{ + return x * volume; +} + + +MA_API void ma_copy_and_apply_volume_and_clip_samples_u8(ma_uint8* pDst, const ma_int16* pSrc, ma_uint64 count, float volume) +{ + ma_uint64 iSample; + ma_int16 volumeFixed; + + MA_ASSERT(pDst != NULL); + MA_ASSERT(pSrc != NULL); + + volumeFixed = ma_float_to_fixed_16(volume); + + for (iSample = 0; iSample < count; iSample += 1) { + pDst[iSample] = ma_clip_u8(ma_apply_volume_unclipped_u8(pSrc[iSample], volumeFixed)); + } +} + +MA_API void ma_copy_and_apply_volume_and_clip_samples_s16(ma_int16* pDst, const ma_int32* pSrc, ma_uint64 count, float volume) +{ + ma_uint64 iSample; + ma_int16 volumeFixed; + + MA_ASSERT(pDst != NULL); + MA_ASSERT(pSrc != NULL); + + volumeFixed = ma_float_to_fixed_16(volume); + + for (iSample = 0; iSample < count; iSample += 1) { + pDst[iSample] = ma_clip_s16(ma_apply_volume_unclipped_s16(pSrc[iSample], volumeFixed)); + } +} + +MA_API void ma_copy_and_apply_volume_and_clip_samples_s24(ma_uint8* pDst, const ma_int64* pSrc, ma_uint64 count, float volume) +{ + ma_uint64 iSample; + ma_int16 volumeFixed; + + MA_ASSERT(pDst != NULL); + MA_ASSERT(pSrc != NULL); + + volumeFixed = ma_float_to_fixed_16(volume); + + for (iSample = 0; iSample < count; iSample += 1) { + ma_int64 s = ma_clip_s24(ma_apply_volume_unclipped_s24(pSrc[iSample], volumeFixed)); + pDst[iSample*3 + 0] = (ma_uint8)((s & 0x000000FF) >> 0); + pDst[iSample*3 + 1] = (ma_uint8)((s & 0x0000FF00) >> 8); + pDst[iSample*3 + 2] = (ma_uint8)((s & 0x00FF0000) >> 16); + } +} + +MA_API void ma_copy_and_apply_volume_and_clip_samples_s32(ma_int32* pDst, const ma_int64* pSrc, ma_uint64 count, float volume) +{ + ma_uint64 iSample; + ma_int16 volumeFixed; + + MA_ASSERT(pDst != NULL); + MA_ASSERT(pSrc != NULL); + + volumeFixed = ma_float_to_fixed_16(volume); + + for (iSample = 0; iSample < count; iSample += 1) { + pDst[iSample] = ma_clip_s32(ma_apply_volume_unclipped_s32(pSrc[iSample], volumeFixed)); + } +} + +MA_API void ma_copy_and_apply_volume_and_clip_samples_f32(float* pDst, const float* pSrc, ma_uint64 count, float volume) +{ + ma_uint64 iSample; + + MA_ASSERT(pDst != NULL); + MA_ASSERT(pSrc != NULL); + + /* For the f32 case we need to make sure this supports in-place processing where the input and output buffers are the same. */ + + for (iSample = 0; iSample < count; iSample += 1) { + pDst[iSample] = ma_clip_f32(ma_apply_volume_unclipped_f32(pSrc[iSample], volume)); + } +} + +MA_API void ma_copy_and_apply_volume_and_clip_pcm_frames(void* pDst, const void* pSrc, ma_uint64 frameCount, ma_format format, ma_uint32 channels, float volume) +{ + MA_ASSERT(pDst != NULL); + MA_ASSERT(pSrc != NULL); + + if (volume == 1) { + ma_clip_pcm_frames(pDst, pSrc, frameCount, format, channels); /* Optimized case for volume = 1. */ + } else if (volume == 0) { + ma_silence_pcm_frames(pDst, frameCount, format, channels); /* Optimized case for volume = 0. */ + } else { + ma_uint64 sampleCount = frameCount * channels; + + switch (format) { + case ma_format_u8: ma_copy_and_apply_volume_and_clip_samples_u8( (ma_uint8*)pDst, (const ma_int16*)pSrc, sampleCount, volume); break; + case ma_format_s16: ma_copy_and_apply_volume_and_clip_samples_s16((ma_int16*)pDst, (const ma_int32*)pSrc, sampleCount, volume); break; + case ma_format_s24: ma_copy_and_apply_volume_and_clip_samples_s24((ma_uint8*)pDst, (const ma_int64*)pSrc, sampleCount, volume); break; + case ma_format_s32: ma_copy_and_apply_volume_and_clip_samples_s32((ma_int32*)pDst, (const ma_int64*)pSrc, sampleCount, volume); break; + case ma_format_f32: ma_copy_and_apply_volume_and_clip_samples_f32(( float*)pDst, (const float*)pSrc, sampleCount, volume); break; + + /* Do nothing if we don't know the format. We're including these here to silence a compiler warning about enums not being handled by the switch. */ + case ma_format_unknown: + case ma_format_count: + break; + } + } +} + + + +MA_API float ma_volume_linear_to_db(float factor) +{ + return 20*ma_log10f(factor); +} + +MA_API float ma_volume_db_to_linear(float gain) +{ + return ma_powf(10, gain/20.0f); +} + + /************************************************************************************************************************************************************** @@ -34580,33 +41162,6 @@ static MA_INLINE void ma_pcm_sample_s32_to_s24_no_scale(ma_int64 x, ma_uint8* s2 } -static MA_INLINE ma_uint8 ma_clip_u8(ma_int16 x) -{ - return (ma_uint8)(ma_clamp(x, -128, 127) + 128); -} - -static MA_INLINE ma_int16 ma_clip_s16(ma_int32 x) -{ - return (ma_int16)ma_clamp(x, -32768, 32767); -} - -static MA_INLINE ma_int64 ma_clip_s24(ma_int64 x) -{ - return (ma_int64)ma_clamp(x, -8388608, 8388607); -} - -static MA_INLINE ma_int32 ma_clip_s32(ma_int64 x) -{ - /* This dance is to silence warnings with -std=c89. A good compiler should be able to optimize this away. */ - ma_int64 clipMin; - ma_int64 clipMax; - clipMin = -((ma_int64)2147483647 + 1); - clipMax = (ma_int64)2147483647; - - return (ma_int32)ma_clamp(x, clipMin, clipMax); -} - - /* u8 */ MA_API void ma_pcm_u8_to_u8(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) { @@ -36985,25 +43540,131 @@ MA_API ma_biquad_config ma_biquad_config_init(ma_format format, ma_uint32 channe return config; } -MA_API ma_result ma_biquad_init(const ma_biquad_config* pConfig, ma_biquad* pBQ) + +typedef struct { + size_t sizeInBytes; + size_t r1Offset; + size_t r2Offset; +} ma_biquad_heap_layout; + +static ma_result ma_biquad_get_heap_layout(const ma_biquad_config* pConfig, ma_biquad_heap_layout* pHeapLayout) +{ + MA_ASSERT(pHeapLayout != NULL); + + MA_ZERO_OBJECT(pHeapLayout); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->channels == 0) { + return MA_INVALID_ARGS; + } + + pHeapLayout->sizeInBytes = 0; + + /* R0 */ + pHeapLayout->r1Offset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += sizeof(ma_biquad_coefficient) * pConfig->channels; + + /* R1 */ + pHeapLayout->r2Offset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += sizeof(ma_biquad_coefficient) * pConfig->channels; + + /* Make sure allocation size is aligned. */ + pHeapLayout->sizeInBytes = ma_align_64(pHeapLayout->sizeInBytes); + + return MA_SUCCESS; +} + +MA_API ma_result ma_biquad_get_heap_size(const ma_biquad_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_result result; + ma_biquad_heap_layout heapLayout; + + if (pHeapSizeInBytes == NULL) { + return MA_INVALID_ARGS; + } + + *pHeapSizeInBytes = 0; + + result = ma_biquad_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + *pHeapSizeInBytes = heapLayout.sizeInBytes; + + return MA_SUCCESS; +} + +MA_API ma_result ma_biquad_init_preallocated(const ma_biquad_config* pConfig, void* pHeap, ma_biquad* pBQ) +{ + ma_result result; + ma_biquad_heap_layout heapLayout; + if (pBQ == NULL) { return MA_INVALID_ARGS; } MA_ZERO_OBJECT(pBQ); - if (pConfig == NULL) { - return MA_INVALID_ARGS; + result = ma_biquad_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; } - if (pConfig->channels < MA_MIN_CHANNELS || pConfig->channels > MA_MAX_CHANNELS) { - return MA_INVALID_ARGS; - } + pBQ->_pHeap = pHeap; + MA_ZERO_MEMORY(pHeap, heapLayout.sizeInBytes); + + pBQ->pR1 = (ma_biquad_coefficient*)ma_offset_ptr(pHeap, heapLayout.r1Offset); + pBQ->pR2 = (ma_biquad_coefficient*)ma_offset_ptr(pHeap, heapLayout.r2Offset); return ma_biquad_reinit(pConfig, pBQ); } +MA_API ma_result ma_biquad_init(const ma_biquad_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_biquad* pBQ) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_biquad_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_biquad_init_preallocated(pConfig, pHeap, pBQ); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pBQ->_ownsHeap = MA_TRUE; + return MA_SUCCESS; +} + +MA_API void ma_biquad_uninit(ma_biquad* pBQ, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pBQ == NULL) { + return; + } + + if (pBQ->_ownsHeap) { + ma_free(pBQ->_pHeap, pAllocationCallbacks); + } +} + MA_API ma_result ma_biquad_reinit(const ma_biquad_config* pConfig, ma_biquad* pBQ) { if (pBQ == NULL || pConfig == NULL) { @@ -37051,6 +43712,23 @@ MA_API ma_result ma_biquad_reinit(const ma_biquad_config* pConfig, ma_biquad* pB return MA_SUCCESS; } +MA_API ma_result ma_biquad_clear_cache(ma_biquad* pBQ) +{ + if (pBQ == NULL) { + return MA_INVALID_ARGS; + } + + if (pBQ->format == ma_format_f32) { + pBQ->pR1->f32 = 0; + pBQ->pR2->f32 = 0; + } else { + pBQ->pR1->s32 = 0; + pBQ->pR2->s32 = 0; + } + + return MA_SUCCESS; +} + static MA_INLINE void ma_biquad_process_pcm_frame_f32__direct_form_2_transposed(ma_biquad* pBQ, float* pY, const float* pX) { ma_uint32 c; @@ -37061,10 +43739,10 @@ static MA_INLINE void ma_biquad_process_pcm_frame_f32__direct_form_2_transposed( const float a1 = pBQ->a1.f32; const float a2 = pBQ->a2.f32; - MA_ASSUME(channels >= MA_MIN_CHANNELS && channels <= MA_MAX_CHANNELS); + MA_ASSUME(channels > 0); for (c = 0; c < channels; c += 1) { - float r1 = pBQ->r1[c].f32; - float r2 = pBQ->r2[c].f32; + float r1 = pBQ->pR1[c].f32; + float r2 = pBQ->pR2[c].f32; float x = pX[c]; float y; @@ -37072,9 +43750,9 @@ static MA_INLINE void ma_biquad_process_pcm_frame_f32__direct_form_2_transposed( r1 = b1*x - a1*y + r2; r2 = b2*x - a2*y; - pY[c] = y; - pBQ->r1[c].f32 = r1; - pBQ->r2[c].f32 = r2; + pY[c] = y; + pBQ->pR1[c].f32 = r1; + pBQ->pR2[c].f32 = r2; } } @@ -37093,10 +43771,10 @@ static MA_INLINE void ma_biquad_process_pcm_frame_s16__direct_form_2_transposed( const ma_int32 a1 = pBQ->a1.s32; const ma_int32 a2 = pBQ->a2.s32; - MA_ASSUME(channels >= MA_MIN_CHANNELS && channels <= MA_MAX_CHANNELS); + MA_ASSUME(channels > 0); for (c = 0; c < channels; c += 1) { - ma_int32 r1 = pBQ->r1[c].s32; - ma_int32 r2 = pBQ->r2[c].s32; + ma_int32 r1 = pBQ->pR1[c].s32; + ma_int32 r2 = pBQ->pR2[c].s32; ma_int32 x = pX[c]; ma_int32 y; @@ -37104,9 +43782,9 @@ static MA_INLINE void ma_biquad_process_pcm_frame_s16__direct_form_2_transposed( r1 = (b1*x - a1*y + r2); r2 = (b2*x - a2*y); - pY[c] = (ma_int16)ma_clamp(y, -32768, 32767); - pBQ->r1[c].s32 = r1; - pBQ->r2[c].s32 = r2; + pY[c] = (ma_int16)ma_clamp(y, -32768, 32767); + pBQ->pR1[c].s32 = r1; + pBQ->pR2[c].s32 = r2; } } @@ -37200,25 +43878,122 @@ MA_API ma_lpf2_config ma_lpf2_config_init(ma_format format, ma_uint32 channels, } -MA_API ma_result ma_lpf1_init(const ma_lpf1_config* pConfig, ma_lpf1* pLPF) +typedef struct { + size_t sizeInBytes; + size_t r1Offset; +} ma_lpf1_heap_layout; + +static ma_result ma_lpf1_get_heap_layout(const ma_lpf1_config* pConfig, ma_lpf1_heap_layout* pHeapLayout) +{ + MA_ASSERT(pHeapLayout != NULL); + + MA_ZERO_OBJECT(pHeapLayout); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->channels == 0) { + return MA_INVALID_ARGS; + } + + pHeapLayout->sizeInBytes = 0; + + /* R1 */ + pHeapLayout->r1Offset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += sizeof(ma_biquad_coefficient) * pConfig->channels; + + /* Make sure allocation size is aligned. */ + pHeapLayout->sizeInBytes = ma_align_64(pHeapLayout->sizeInBytes); + + return MA_SUCCESS; +} + +MA_API ma_result ma_lpf1_get_heap_size(const ma_lpf1_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_result result; + ma_lpf1_heap_layout heapLayout; + + if (pHeapSizeInBytes == NULL) { + return MA_INVALID_ARGS; + } + + result = ma_lpf1_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + *pHeapSizeInBytes = heapLayout.sizeInBytes; + + return MA_SUCCESS; +} + +MA_API ma_result ma_lpf1_init_preallocated(const ma_lpf1_config* pConfig, void* pHeap, ma_lpf1* pLPF) +{ + ma_result result; + ma_lpf1_heap_layout heapLayout; + if (pLPF == NULL) { return MA_INVALID_ARGS; } MA_ZERO_OBJECT(pLPF); - if (pConfig == NULL) { - return MA_INVALID_ARGS; + result = ma_lpf1_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; } - if (pConfig->channels < MA_MIN_CHANNELS || pConfig->channels > MA_MAX_CHANNELS) { - return MA_INVALID_ARGS; - } + pLPF->_pHeap = pHeap; + MA_ZERO_MEMORY(pHeap, heapLayout.sizeInBytes); + + pLPF->pR1 = (ma_biquad_coefficient*)ma_offset_ptr(pHeap, heapLayout.r1Offset); return ma_lpf1_reinit(pConfig, pLPF); } +MA_API ma_result ma_lpf1_init(const ma_lpf1_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_lpf1* pLPF) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_lpf1_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_lpf1_init_preallocated(pConfig, pHeap, pLPF); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pLPF->_ownsHeap = MA_TRUE; + return MA_SUCCESS; +} + +MA_API void ma_lpf1_uninit(ma_lpf1* pLPF, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pLPF == NULL) { + return; + } + + if (pLPF->_ownsHeap) { + ma_free(pLPF->_pHeap, pAllocationCallbacks); + } +} + MA_API ma_result ma_lpf1_reinit(const ma_lpf1_config* pConfig, ma_lpf1* pLPF) { double a; @@ -37255,6 +44030,21 @@ MA_API ma_result ma_lpf1_reinit(const ma_lpf1_config* pConfig, ma_lpf1* pLPF) return MA_SUCCESS; } +MA_API ma_result ma_lpf1_clear_cache(ma_lpf1* pLPF) +{ + if (pLPF == NULL) { + return MA_INVALID_ARGS; + } + + if (pLPF->format == ma_format_f32) { + pLPF->a.f32 = 0; + } else { + pLPF->a.s32 = 0; + } + + return MA_SUCCESS; +} + static MA_INLINE void ma_lpf1_process_pcm_frame_f32(ma_lpf1* pLPF, float* pY, const float* pX) { ma_uint32 c; @@ -37262,16 +44052,16 @@ static MA_INLINE void ma_lpf1_process_pcm_frame_f32(ma_lpf1* pLPF, float* pY, co const float a = pLPF->a.f32; const float b = 1 - a; - MA_ASSUME(channels >= MA_MIN_CHANNELS && channels <= MA_MAX_CHANNELS); + MA_ASSUME(channels > 0); for (c = 0; c < channels; c += 1) { - float r1 = pLPF->r1[c].f32; + float r1 = pLPF->pR1[c].f32; float x = pX[c]; float y; y = b*x + a*r1; pY[c] = y; - pLPF->r1[c].f32 = y; + pLPF->pR1[c].f32 = y; } } @@ -37282,16 +44072,16 @@ static MA_INLINE void ma_lpf1_process_pcm_frame_s16(ma_lpf1* pLPF, ma_int16* pY, const ma_int32 a = pLPF->a.s32; const ma_int32 b = ((1 << MA_BIQUAD_FIXED_POINT_SHIFT) - a); - MA_ASSUME(channels >= MA_MIN_CHANNELS && channels <= MA_MAX_CHANNELS); + MA_ASSUME(channels > 0); for (c = 0; c < channels; c += 1) { - ma_int32 r1 = pLPF->r1[c].s32; + ma_int32 r1 = pLPF->pR1[c].s32; ma_int32 x = pX[c]; ma_int32 y; y = (b*x + a*r1) >> MA_BIQUAD_FIXED_POINT_SHIFT; - pY[c] = (ma_int16)y; - pLPF->r1[c].s32 = (ma_int32)y; + pY[c] = (ma_int16)y; + pLPF->pR1[c].s32 = (ma_int32)y; } } @@ -37371,7 +44161,15 @@ static MA_INLINE ma_biquad_config ma_lpf2__get_biquad_config(const ma_lpf2_confi return bqConfig; } -MA_API ma_result ma_lpf2_init(const ma_lpf2_config* pConfig, ma_lpf2* pLPF) +MA_API ma_result ma_lpf2_get_heap_size(const ma_lpf2_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_biquad_config bqConfig; + bqConfig = ma_lpf2__get_biquad_config(pConfig); + + return ma_biquad_get_heap_size(&bqConfig, pHeapSizeInBytes); +} + +MA_API ma_result ma_lpf2_init_preallocated(const ma_lpf2_config* pConfig, void* pHeap, ma_lpf2* pLPF) { ma_result result; ma_biquad_config bqConfig; @@ -37387,7 +44185,7 @@ MA_API ma_result ma_lpf2_init(const ma_lpf2_config* pConfig, ma_lpf2* pLPF) } bqConfig = ma_lpf2__get_biquad_config(pConfig); - result = ma_biquad_init(&bqConfig, &pLPF->bq); + result = ma_biquad_init_preallocated(&bqConfig, pHeap, &pLPF->bq); if (result != MA_SUCCESS) { return result; } @@ -37395,6 +44193,45 @@ MA_API ma_result ma_lpf2_init(const ma_lpf2_config* pConfig, ma_lpf2* pLPF) return MA_SUCCESS; } +MA_API ma_result ma_lpf2_init(const ma_lpf2_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_lpf2* pLPF) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_lpf2_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_lpf2_init_preallocated(pConfig, pHeap, pLPF); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pLPF->bq._ownsHeap = MA_TRUE; /* <-- This will cause the biquad to take ownership of the heap and free it when it's uninitialized. */ + return MA_SUCCESS; +} + +MA_API void ma_lpf2_uninit(ma_lpf2* pLPF, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pLPF == NULL) { + return; + } + + ma_biquad_uninit(&pLPF->bq, pAllocationCallbacks); /* <-- This will free the heap allocation. */ +} + MA_API ma_result ma_lpf2_reinit(const ma_lpf2_config* pConfig, ma_lpf2* pLPF) { ma_result result; @@ -37413,6 +44250,17 @@ MA_API ma_result ma_lpf2_reinit(const ma_lpf2_config* pConfig, ma_lpf2* pLPF) return MA_SUCCESS; } +MA_API ma_result ma_lpf2_clear_cache(ma_lpf2* pLPF) +{ + if (pLPF == NULL) { + return MA_INVALID_ARGS; + } + + ma_biquad_clear_cache(&pLPF->bq); + + return MA_SUCCESS; +} + static MA_INLINE void ma_lpf2_process_pcm_frame_s16(ma_lpf2* pLPF, ma_int16* pFrameOut, const ma_int16* pFrameIn) { ma_biquad_process_pcm_frame_s16(&pLPF->bq, pFrameOut, pFrameIn); @@ -37456,7 +44304,24 @@ MA_API ma_lpf_config ma_lpf_config_init(ma_format format, ma_uint32 channels, ma return config; } -static ma_result ma_lpf_reinit__internal(const ma_lpf_config* pConfig, ma_lpf* pLPF, ma_bool32 isNew) + +typedef struct +{ + size_t sizeInBytes; + size_t lpf1Offset; + size_t lpf2Offset; /* Offset of the first second order filter. Subsequent filters will come straight after, and will each have the same heap size. */ +} ma_lpf_heap_layout; + +static void ma_lpf_calculate_sub_lpf_counts(ma_uint32 order, ma_uint32* pLPF1Count, ma_uint32* pLPF2Count) +{ + MA_ASSERT(pLPF1Count != NULL); + MA_ASSERT(pLPF2Count != NULL); + + *pLPF1Count = order % 2; + *pLPF2Count = order / 2; +} + +static ma_result ma_lpf_get_heap_layout(const ma_lpf_config* pConfig, ma_lpf_heap_layout* pHeapLayout) { ma_result result; ma_uint32 lpf1Count; @@ -37464,6 +44329,69 @@ static ma_result ma_lpf_reinit__internal(const ma_lpf_config* pConfig, ma_lpf* p ma_uint32 ilpf1; ma_uint32 ilpf2; + MA_ASSERT(pHeapLayout != NULL); + + MA_ZERO_OBJECT(pHeapLayout); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->channels == 0) { + return MA_INVALID_ARGS; + } + + if (pConfig->order > MA_MAX_FILTER_ORDER) { + return MA_INVALID_ARGS; + } + + ma_lpf_calculate_sub_lpf_counts(pConfig->order, &lpf1Count, &lpf2Count); + + pHeapLayout->sizeInBytes = 0; + + /* LPF 1 */ + pHeapLayout->lpf1Offset = pHeapLayout->sizeInBytes; + for (ilpf1 = 0; ilpf1 < lpf1Count; ilpf1 += 1) { + size_t lpf1HeapSizeInBytes; + ma_lpf1_config lpf1Config = ma_lpf1_config_init(pConfig->format, pConfig->channels, pConfig->sampleRate, pConfig->cutoffFrequency); + + result = ma_lpf1_get_heap_size(&lpf1Config, &lpf1HeapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + pHeapLayout->sizeInBytes += sizeof(ma_lpf1) + lpf1HeapSizeInBytes; + } + + /* LPF 2*/ + pHeapLayout->lpf2Offset = pHeapLayout->sizeInBytes; + for (ilpf2 = 0; ilpf2 < lpf2Count; ilpf2 += 1) { + size_t lpf2HeapSizeInBytes; + ma_lpf2_config lpf2Config = ma_lpf2_config_init(pConfig->format, pConfig->channels, pConfig->sampleRate, pConfig->cutoffFrequency, 0.707107); /* <-- The "q" parameter does not matter for the purpose of calculating the heap size. */ + + result = ma_lpf2_get_heap_size(&lpf2Config, &lpf2HeapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + pHeapLayout->sizeInBytes += sizeof(ma_lpf2) + lpf2HeapSizeInBytes; + } + + /* Make sure allocation size is aligned. */ + pHeapLayout->sizeInBytes = ma_align_64(pHeapLayout->sizeInBytes); + + return MA_SUCCESS; +} + +static ma_result ma_lpf_reinit__internal(const ma_lpf_config* pConfig, void* pHeap, ma_lpf* pLPF, ma_bool32 isNew) +{ + ma_result result; + ma_uint32 lpf1Count; + ma_uint32 lpf2Count; + ma_uint32 ilpf1; + ma_uint32 ilpf2; + ma_lpf_heap_layout heapLayout; /* Only used if isNew is true. */ + if (pLPF == NULL || pConfig == NULL) { return MA_INVALID_ARGS; } @@ -37487,11 +44415,7 @@ static ma_result ma_lpf_reinit__internal(const ma_lpf_config* pConfig, ma_lpf* p return MA_INVALID_ARGS; } - lpf1Count = pConfig->order % 2; - lpf2Count = pConfig->order / 2; - - MA_ASSERT(lpf1Count <= ma_countof(pLPF->lpf1)); - MA_ASSERT(lpf2Count <= ma_countof(pLPF->lpf2)); + ma_lpf_calculate_sub_lpf_counts(pConfig->order, &lpf1Count, &lpf2Count); /* The filter order can't change between reinits. */ if (!isNew) { @@ -37500,16 +44424,42 @@ static ma_result ma_lpf_reinit__internal(const ma_lpf_config* pConfig, ma_lpf* p } } + if (isNew) { + result = ma_lpf_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + pLPF->_pHeap = pHeap; + MA_ZERO_MEMORY(pHeap, heapLayout.sizeInBytes); + + pLPF->pLPF1 = (ma_lpf1*)ma_offset_ptr(pHeap, heapLayout.lpf1Offset); + pLPF->pLPF2 = (ma_lpf2*)ma_offset_ptr(pHeap, heapLayout.lpf2Offset); + } else { + MA_ZERO_OBJECT(&heapLayout); /* To silence a compiler warning. */ + } + for (ilpf1 = 0; ilpf1 < lpf1Count; ilpf1 += 1) { ma_lpf1_config lpf1Config = ma_lpf1_config_init(pConfig->format, pConfig->channels, pConfig->sampleRate, pConfig->cutoffFrequency); if (isNew) { - result = ma_lpf1_init(&lpf1Config, &pLPF->lpf1[ilpf1]); + size_t lpf1HeapSizeInBytes; + + result = ma_lpf1_get_heap_size(&lpf1Config, &lpf1HeapSizeInBytes); + if (result == MA_SUCCESS) { + result = ma_lpf1_init_preallocated(&lpf1Config, ma_offset_ptr(pHeap, heapLayout.lpf1Offset + (sizeof(ma_lpf1) * lpf1Count) + (ilpf1 * lpf1HeapSizeInBytes)), &pLPF->pLPF1[ilpf1]); + } } else { - result = ma_lpf1_reinit(&lpf1Config, &pLPF->lpf1[ilpf1]); + result = ma_lpf1_reinit(&lpf1Config, &pLPF->pLPF1[ilpf1]); } if (result != MA_SUCCESS) { + ma_uint32 jlpf1; + + for (jlpf1 = 0; jlpf1 < ilpf1; jlpf1 += 1) { + ma_lpf1_uninit(&pLPF->pLPF1[jlpf1], NULL); /* No need for allocation callbacks here since we used a preallocated heap allocation. */ + } + return result; } } @@ -37530,12 +44480,28 @@ static ma_result ma_lpf_reinit__internal(const ma_lpf_config* pConfig, ma_lpf* p lpf2Config = ma_lpf2_config_init(pConfig->format, pConfig->channels, pConfig->sampleRate, pConfig->cutoffFrequency, q); if (isNew) { - result = ma_lpf2_init(&lpf2Config, &pLPF->lpf2[ilpf2]); + size_t lpf2HeapSizeInBytes; + + result = ma_lpf2_get_heap_size(&lpf2Config, &lpf2HeapSizeInBytes); + if (result == MA_SUCCESS) { + result = ma_lpf2_init_preallocated(&lpf2Config, ma_offset_ptr(pHeap, heapLayout.lpf2Offset + (sizeof(ma_lpf2) * lpf2Count) + (ilpf2 * lpf2HeapSizeInBytes)), &pLPF->pLPF2[ilpf2]); + } } else { - result = ma_lpf2_reinit(&lpf2Config, &pLPF->lpf2[ilpf2]); + result = ma_lpf2_reinit(&lpf2Config, &pLPF->pLPF2[ilpf2]); } if (result != MA_SUCCESS) { + ma_uint32 jlpf1; + ma_uint32 jlpf2; + + for (jlpf1 = 0; jlpf1 < lpf1Count; jlpf1 += 1) { + ma_lpf1_uninit(&pLPF->pLPF1[jlpf1], NULL); /* No need for allocation callbacks here since we used a preallocated heap allocation. */ + } + + for (jlpf2 = 0; jlpf2 < ilpf2; jlpf2 += 1) { + ma_lpf2_uninit(&pLPF->pLPF2[jlpf2], NULL); /* No need for allocation callbacks here since we used a preallocated heap allocation. */ + } + return result; } } @@ -37549,7 +44515,28 @@ static ma_result ma_lpf_reinit__internal(const ma_lpf_config* pConfig, ma_lpf* p return MA_SUCCESS; } -MA_API ma_result ma_lpf_init(const ma_lpf_config* pConfig, ma_lpf* pLPF) +MA_API ma_result ma_lpf_get_heap_size(const ma_lpf_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_result result; + ma_lpf_heap_layout heapLayout; + + if (pHeapSizeInBytes == NULL) { + return MA_INVALID_ARGS; + } + + *pHeapSizeInBytes = 0; + + result = ma_lpf_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + *pHeapSizeInBytes = heapLayout.sizeInBytes; + + return result; +} + +MA_API ma_result ma_lpf_init_preallocated(const ma_lpf_config* pConfig, void* pHeap, ma_lpf* pLPF) { if (pLPF == NULL) { return MA_INVALID_ARGS; @@ -37557,16 +44544,84 @@ MA_API ma_result ma_lpf_init(const ma_lpf_config* pConfig, ma_lpf* pLPF) MA_ZERO_OBJECT(pLPF); - if (pConfig == NULL) { - return MA_INVALID_ARGS; + return ma_lpf_reinit__internal(pConfig, pHeap, pLPF, /*isNew*/MA_TRUE); +} + +MA_API ma_result ma_lpf_init(const ma_lpf_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_lpf* pLPF) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_lpf_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; } - return ma_lpf_reinit__internal(pConfig, pLPF, /*isNew*/MA_TRUE); + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_lpf_init_preallocated(pConfig, pHeap, pLPF); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pLPF->_ownsHeap = MA_TRUE; + return MA_SUCCESS; +} + +MA_API void ma_lpf_uninit(ma_lpf* pLPF, const ma_allocation_callbacks* pAllocationCallbacks) +{ + ma_uint32 ilpf1; + ma_uint32 ilpf2; + + if (pLPF == NULL) { + return; + } + + for (ilpf1 = 0; ilpf1 < pLPF->lpf1Count; ilpf1 += 1) { + ma_lpf1_uninit(&pLPF->pLPF1[ilpf1], pAllocationCallbacks); + } + + for (ilpf2 = 0; ilpf2 < pLPF->lpf2Count; ilpf2 += 1) { + ma_lpf2_uninit(&pLPF->pLPF2[ilpf2], pAllocationCallbacks); + } + + if (pLPF->_ownsHeap) { + ma_free(pLPF->_pHeap, pAllocationCallbacks); + } } MA_API ma_result ma_lpf_reinit(const ma_lpf_config* pConfig, ma_lpf* pLPF) { - return ma_lpf_reinit__internal(pConfig, pLPF, /*isNew*/MA_FALSE); + return ma_lpf_reinit__internal(pConfig, NULL, pLPF, /*isNew*/MA_FALSE); +} + +MA_API ma_result ma_lpf_clear_cache(ma_lpf* pLPF) +{ + ma_uint32 ilpf1; + ma_uint32 ilpf2; + + if (pLPF == NULL) { + return MA_INVALID_ARGS; + } + + for (ilpf1 = 0; ilpf1 < pLPF->lpf1Count; ilpf1 += 1) { + ma_lpf1_clear_cache(&pLPF->pLPF1[ilpf1]); + } + + for (ilpf2 = 0; ilpf2 < pLPF->lpf2Count; ilpf2 += 1) { + ma_lpf2_clear_cache(&pLPF->pLPF2[ilpf2]); + } + + return MA_SUCCESS; } static MA_INLINE void ma_lpf_process_pcm_frame_f32(ma_lpf* pLPF, float* pY, const void* pX) @@ -37579,11 +44634,11 @@ static MA_INLINE void ma_lpf_process_pcm_frame_f32(ma_lpf* pLPF, float* pY, cons MA_COPY_MEMORY(pY, pX, ma_get_bytes_per_frame(pLPF->format, pLPF->channels)); for (ilpf1 = 0; ilpf1 < pLPF->lpf1Count; ilpf1 += 1) { - ma_lpf1_process_pcm_frame_f32(&pLPF->lpf1[ilpf1], pY, pY); + ma_lpf1_process_pcm_frame_f32(&pLPF->pLPF1[ilpf1], pY, pY); } for (ilpf2 = 0; ilpf2 < pLPF->lpf2Count; ilpf2 += 1) { - ma_lpf2_process_pcm_frame_f32(&pLPF->lpf2[ilpf2], pY, pY); + ma_lpf2_process_pcm_frame_f32(&pLPF->pLPF2[ilpf2], pY, pY); } } @@ -37597,11 +44652,11 @@ static MA_INLINE void ma_lpf_process_pcm_frame_s16(ma_lpf* pLPF, ma_int16* pY, c MA_COPY_MEMORY(pY, pX, ma_get_bytes_per_frame(pLPF->format, pLPF->channels)); for (ilpf1 = 0; ilpf1 < pLPF->lpf1Count; ilpf1 += 1) { - ma_lpf1_process_pcm_frame_s16(&pLPF->lpf1[ilpf1], pY, pY); + ma_lpf1_process_pcm_frame_s16(&pLPF->pLPF1[ilpf1], pY, pY); } for (ilpf2 = 0; ilpf2 < pLPF->lpf2Count; ilpf2 += 1) { - ma_lpf2_process_pcm_frame_s16(&pLPF->lpf2[ilpf2], pY, pY); + ma_lpf2_process_pcm_frame_s16(&pLPF->pLPF2[ilpf2], pY, pY); } } @@ -37618,14 +44673,14 @@ MA_API ma_result ma_lpf_process_pcm_frames(ma_lpf* pLPF, void* pFramesOut, const /* Faster path for in-place. */ if (pFramesOut == pFramesIn) { for (ilpf1 = 0; ilpf1 < pLPF->lpf1Count; ilpf1 += 1) { - result = ma_lpf1_process_pcm_frames(&pLPF->lpf1[ilpf1], pFramesOut, pFramesOut, frameCount); + result = ma_lpf1_process_pcm_frames(&pLPF->pLPF1[ilpf1], pFramesOut, pFramesOut, frameCount); if (result != MA_SUCCESS) { return result; } } for (ilpf2 = 0; ilpf2 < pLPF->lpf2Count; ilpf2 += 1) { - result = ma_lpf2_process_pcm_frames(&pLPF->lpf2[ilpf2], pFramesOut, pFramesOut, frameCount); + result = ma_lpf2_process_pcm_frames(&pLPF->pLPF2[ilpf2], pFramesOut, pFramesOut, frameCount); if (result != MA_SUCCESS) { return result; } @@ -37711,23 +44766,120 @@ MA_API ma_hpf2_config ma_hpf2_config_init(ma_format format, ma_uint32 channels, } -MA_API ma_result ma_hpf1_init(const ma_hpf1_config* pConfig, ma_hpf1* pHPF) +typedef struct { - if (pHPF == NULL) { - return MA_INVALID_ARGS; - } + size_t sizeInBytes; + size_t r1Offset; +} ma_hpf1_heap_layout; - MA_ZERO_OBJECT(pHPF); +static ma_result ma_hpf1_get_heap_layout(const ma_hpf1_config* pConfig, ma_hpf1_heap_layout* pHeapLayout) +{ + MA_ASSERT(pHeapLayout != NULL); + + MA_ZERO_OBJECT(pHeapLayout); if (pConfig == NULL) { return MA_INVALID_ARGS; } - if (pConfig->channels < MA_MIN_CHANNELS || pConfig->channels > MA_MAX_CHANNELS) { + if (pConfig->channels == 0) { return MA_INVALID_ARGS; } - return ma_hpf1_reinit(pConfig, pHPF); + pHeapLayout->sizeInBytes = 0; + + /* R1 */ + pHeapLayout->r1Offset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += sizeof(ma_biquad_coefficient) * pConfig->channels; + + /* Make sure allocation size is aligned. */ + pHeapLayout->sizeInBytes = ma_align_64(pHeapLayout->sizeInBytes); + + return MA_SUCCESS; +} + +MA_API ma_result ma_hpf1_get_heap_size(const ma_hpf1_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_result result; + ma_hpf1_heap_layout heapLayout; + + if (pHeapSizeInBytes == NULL) { + return MA_INVALID_ARGS; + } + + result = ma_hpf1_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + *pHeapSizeInBytes = heapLayout.sizeInBytes; + + return MA_SUCCESS; +} + +MA_API ma_result ma_hpf1_init_preallocated(const ma_hpf1_config* pConfig, void* pHeap, ma_hpf1* pLPF) +{ + ma_result result; + ma_hpf1_heap_layout heapLayout; + + if (pLPF == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pLPF); + + result = ma_hpf1_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + pLPF->_pHeap = pHeap; + MA_ZERO_MEMORY(pHeap, heapLayout.sizeInBytes); + + pLPF->pR1 = (ma_biquad_coefficient*)ma_offset_ptr(pHeap, heapLayout.r1Offset); + + return ma_hpf1_reinit(pConfig, pLPF); +} + +MA_API ma_result ma_hpf1_init(const ma_hpf1_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_hpf1* pLPF) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_hpf1_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_hpf1_init_preallocated(pConfig, pHeap, pLPF); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pLPF->_ownsHeap = MA_TRUE; + return MA_SUCCESS; +} + +MA_API void ma_hpf1_uninit(ma_hpf1* pHPF, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pHPF == NULL) { + return; + } + + if (pHPF->_ownsHeap) { + ma_free(pHPF->_pHeap, pAllocationCallbacks); + } } MA_API ma_result ma_hpf1_reinit(const ma_hpf1_config* pConfig, ma_hpf1* pHPF) @@ -37773,16 +44925,16 @@ static MA_INLINE void ma_hpf1_process_pcm_frame_f32(ma_hpf1* pHPF, float* pY, co const float a = 1 - pHPF->a.f32; const float b = 1 - a; - MA_ASSUME(channels >= MA_MIN_CHANNELS && channels <= MA_MAX_CHANNELS); + MA_ASSUME(channels > 0); for (c = 0; c < channels; c += 1) { - float r1 = pHPF->r1[c].f32; + float r1 = pHPF->pR1[c].f32; float x = pX[c]; float y; y = b*x - a*r1; - pY[c] = y; - pHPF->r1[c].f32 = y; + pY[c] = y; + pHPF->pR1[c].f32 = y; } } @@ -37793,16 +44945,16 @@ static MA_INLINE void ma_hpf1_process_pcm_frame_s16(ma_hpf1* pHPF, ma_int16* pY, const ma_int32 a = ((1 << MA_BIQUAD_FIXED_POINT_SHIFT) - pHPF->a.s32); const ma_int32 b = ((1 << MA_BIQUAD_FIXED_POINT_SHIFT) - a); - MA_ASSUME(channels >= MA_MIN_CHANNELS && channels <= MA_MAX_CHANNELS); + MA_ASSUME(channels > 0); for (c = 0; c < channels; c += 1) { - ma_int32 r1 = pHPF->r1[c].s32; + ma_int32 r1 = pHPF->pR1[c].s32; ma_int32 x = pX[c]; ma_int32 y; y = (b*x - a*r1) >> MA_BIQUAD_FIXED_POINT_SHIFT; - pY[c] = (ma_int16)y; - pHPF->r1[c].s32 = (ma_int32)y; + pY[c] = (ma_int16)y; + pHPF->pR1[c].s32 = (ma_int32)y; } } @@ -37882,7 +45034,15 @@ static MA_INLINE ma_biquad_config ma_hpf2__get_biquad_config(const ma_hpf2_confi return bqConfig; } -MA_API ma_result ma_hpf2_init(const ma_hpf2_config* pConfig, ma_hpf2* pHPF) +MA_API ma_result ma_hpf2_get_heap_size(const ma_hpf2_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_biquad_config bqConfig; + bqConfig = ma_hpf2__get_biquad_config(pConfig); + + return ma_biquad_get_heap_size(&bqConfig, pHeapSizeInBytes); +} + +MA_API ma_result ma_hpf2_init_preallocated(const ma_hpf2_config* pConfig, void* pHeap, ma_hpf2* pHPF) { ma_result result; ma_biquad_config bqConfig; @@ -37898,7 +45058,7 @@ MA_API ma_result ma_hpf2_init(const ma_hpf2_config* pConfig, ma_hpf2* pHPF) } bqConfig = ma_hpf2__get_biquad_config(pConfig); - result = ma_biquad_init(&bqConfig, &pHPF->bq); + result = ma_biquad_init_preallocated(&bqConfig, pHeap, &pHPF->bq); if (result != MA_SUCCESS) { return result; } @@ -37906,6 +45066,45 @@ MA_API ma_result ma_hpf2_init(const ma_hpf2_config* pConfig, ma_hpf2* pHPF) return MA_SUCCESS; } +MA_API ma_result ma_hpf2_init(const ma_hpf2_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_hpf2* pHPF) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_hpf2_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_hpf2_init_preallocated(pConfig, pHeap, pHPF); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pHPF->bq._ownsHeap = MA_TRUE; /* <-- This will cause the biquad to take ownership of the heap and free it when it's uninitialized. */ + return MA_SUCCESS; +} + +MA_API void ma_hpf2_uninit(ma_hpf2* pHPF, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pHPF == NULL) { + return; + } + + ma_biquad_uninit(&pHPF->bq, pAllocationCallbacks); /* <-- This will free the heap allocation. */ +} + MA_API ma_result ma_hpf2_reinit(const ma_hpf2_config* pConfig, ma_hpf2* pHPF) { ma_result result; @@ -37967,7 +45166,24 @@ MA_API ma_hpf_config ma_hpf_config_init(ma_format format, ma_uint32 channels, ma return config; } -static ma_result ma_hpf_reinit__internal(const ma_hpf_config* pConfig, ma_hpf* pHPF, ma_bool32 isNew) + +typedef struct +{ + size_t sizeInBytes; + size_t hpf1Offset; + size_t hpf2Offset; /* Offset of the first second order filter. Subsequent filters will come straight after, and will each have the same heap size. */ +} ma_hpf_heap_layout; + +static void ma_hpf_calculate_sub_hpf_counts(ma_uint32 order, ma_uint32* pHPF1Count, ma_uint32* pHPF2Count) +{ + MA_ASSERT(pHPF1Count != NULL); + MA_ASSERT(pHPF2Count != NULL); + + *pHPF1Count = order % 2; + *pHPF2Count = order / 2; +} + +static ma_result ma_hpf_get_heap_layout(const ma_hpf_config* pConfig, ma_hpf_heap_layout* pHeapLayout) { ma_result result; ma_uint32 hpf1Count; @@ -37975,6 +45191,69 @@ static ma_result ma_hpf_reinit__internal(const ma_hpf_config* pConfig, ma_hpf* p ma_uint32 ihpf1; ma_uint32 ihpf2; + MA_ASSERT(pHeapLayout != NULL); + + MA_ZERO_OBJECT(pHeapLayout); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->channels == 0) { + return MA_INVALID_ARGS; + } + + if (pConfig->order > MA_MAX_FILTER_ORDER) { + return MA_INVALID_ARGS; + } + + ma_hpf_calculate_sub_hpf_counts(pConfig->order, &hpf1Count, &hpf2Count); + + pHeapLayout->sizeInBytes = 0; + + /* HPF 1 */ + pHeapLayout->hpf1Offset = pHeapLayout->sizeInBytes; + for (ihpf1 = 0; ihpf1 < hpf1Count; ihpf1 += 1) { + size_t hpf1HeapSizeInBytes; + ma_hpf1_config hpf1Config = ma_hpf1_config_init(pConfig->format, pConfig->channels, pConfig->sampleRate, pConfig->cutoffFrequency); + + result = ma_hpf1_get_heap_size(&hpf1Config, &hpf1HeapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + pHeapLayout->sizeInBytes += sizeof(ma_hpf1) + hpf1HeapSizeInBytes; + } + + /* HPF 2*/ + pHeapLayout->hpf2Offset = pHeapLayout->sizeInBytes; + for (ihpf2 = 0; ihpf2 < hpf2Count; ihpf2 += 1) { + size_t hpf2HeapSizeInBytes; + ma_hpf2_config hpf2Config = ma_hpf2_config_init(pConfig->format, pConfig->channels, pConfig->sampleRate, pConfig->cutoffFrequency, 0.707107); /* <-- The "q" parameter does not matter for the purpose of calculating the heap size. */ + + result = ma_hpf2_get_heap_size(&hpf2Config, &hpf2HeapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + pHeapLayout->sizeInBytes += sizeof(ma_hpf2) + hpf2HeapSizeInBytes; + } + + /* Make sure allocation size is aligned. */ + pHeapLayout->sizeInBytes = ma_align_64(pHeapLayout->sizeInBytes); + + return MA_SUCCESS; +} + +static ma_result ma_hpf_reinit__internal(const ma_hpf_config* pConfig, void* pHeap, ma_hpf* pHPF, ma_bool32 isNew) +{ + ma_result result; + ma_uint32 hpf1Count; + ma_uint32 hpf2Count; + ma_uint32 ihpf1; + ma_uint32 ihpf2; + ma_hpf_heap_layout heapLayout; /* Only used if isNew is true. */ + if (pHPF == NULL || pConfig == NULL) { return MA_INVALID_ARGS; } @@ -37998,11 +45277,7 @@ static ma_result ma_hpf_reinit__internal(const ma_hpf_config* pConfig, ma_hpf* p return MA_INVALID_ARGS; } - hpf1Count = pConfig->order % 2; - hpf2Count = pConfig->order / 2; - - MA_ASSERT(hpf1Count <= ma_countof(pHPF->hpf1)); - MA_ASSERT(hpf2Count <= ma_countof(pHPF->hpf2)); + ma_hpf_calculate_sub_hpf_counts(pConfig->order, &hpf1Count, &hpf2Count); /* The filter order can't change between reinits. */ if (!isNew) { @@ -38011,16 +45286,42 @@ static ma_result ma_hpf_reinit__internal(const ma_hpf_config* pConfig, ma_hpf* p } } + if (isNew) { + result = ma_hpf_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + pHPF->_pHeap = pHeap; + MA_ZERO_MEMORY(pHeap, heapLayout.sizeInBytes); + + pHPF->pHPF1 = (ma_hpf1*)ma_offset_ptr(pHeap, heapLayout.hpf1Offset); + pHPF->pHPF2 = (ma_hpf2*)ma_offset_ptr(pHeap, heapLayout.hpf2Offset); + } else { + MA_ZERO_OBJECT(&heapLayout); /* To silence a compiler warning. */ + } + for (ihpf1 = 0; ihpf1 < hpf1Count; ihpf1 += 1) { ma_hpf1_config hpf1Config = ma_hpf1_config_init(pConfig->format, pConfig->channels, pConfig->sampleRate, pConfig->cutoffFrequency); if (isNew) { - result = ma_hpf1_init(&hpf1Config, &pHPF->hpf1[ihpf1]); + size_t hpf1HeapSizeInBytes; + + result = ma_hpf1_get_heap_size(&hpf1Config, &hpf1HeapSizeInBytes); + if (result == MA_SUCCESS) { + result = ma_hpf1_init_preallocated(&hpf1Config, ma_offset_ptr(pHeap, heapLayout.hpf1Offset + (sizeof(ma_hpf1) * hpf1Count) + (ihpf1 * hpf1HeapSizeInBytes)), &pHPF->pHPF1[ihpf1]); + } } else { - result = ma_hpf1_reinit(&hpf1Config, &pHPF->hpf1[ihpf1]); + result = ma_hpf1_reinit(&hpf1Config, &pHPF->pHPF1[ihpf1]); } if (result != MA_SUCCESS) { + ma_uint32 jhpf1; + + for (jhpf1 = 0; jhpf1 < ihpf1; jhpf1 += 1) { + ma_hpf1_uninit(&pHPF->pHPF1[jhpf1], NULL); /* No need for allocation callbacks here since we used a preallocated heap allocation. */ + } + return result; } } @@ -38041,12 +45342,28 @@ static ma_result ma_hpf_reinit__internal(const ma_hpf_config* pConfig, ma_hpf* p hpf2Config = ma_hpf2_config_init(pConfig->format, pConfig->channels, pConfig->sampleRate, pConfig->cutoffFrequency, q); if (isNew) { - result = ma_hpf2_init(&hpf2Config, &pHPF->hpf2[ihpf2]); + size_t hpf2HeapSizeInBytes; + + result = ma_hpf2_get_heap_size(&hpf2Config, &hpf2HeapSizeInBytes); + if (result == MA_SUCCESS) { + result = ma_hpf2_init_preallocated(&hpf2Config, ma_offset_ptr(pHeap, heapLayout.hpf2Offset + (sizeof(ma_hpf2) * hpf2Count) + (ihpf2 * hpf2HeapSizeInBytes)), &pHPF->pHPF2[ihpf2]); + } } else { - result = ma_hpf2_reinit(&hpf2Config, &pHPF->hpf2[ihpf2]); + result = ma_hpf2_reinit(&hpf2Config, &pHPF->pHPF2[ihpf2]); } if (result != MA_SUCCESS) { + ma_uint32 jhpf1; + ma_uint32 jhpf2; + + for (jhpf1 = 0; jhpf1 < hpf1Count; jhpf1 += 1) { + ma_hpf1_uninit(&pHPF->pHPF1[jhpf1], NULL); /* No need for allocation callbacks here since we used a preallocated heap allocation. */ + } + + for (jhpf2 = 0; jhpf2 < ihpf2; jhpf2 += 1) { + ma_hpf2_uninit(&pHPF->pHPF2[jhpf2], NULL); /* No need for allocation callbacks here since we used a preallocated heap allocation. */ + } + return result; } } @@ -38060,24 +45377,93 @@ static ma_result ma_hpf_reinit__internal(const ma_hpf_config* pConfig, ma_hpf* p return MA_SUCCESS; } -MA_API ma_result ma_hpf_init(const ma_hpf_config* pConfig, ma_hpf* pHPF) +MA_API ma_result ma_hpf_get_heap_size(const ma_hpf_config* pConfig, size_t* pHeapSizeInBytes) { + ma_result result; + ma_hpf_heap_layout heapLayout; + + if (pHeapSizeInBytes == NULL) { + return MA_INVALID_ARGS; + } + + *pHeapSizeInBytes = 0; + + result = ma_hpf_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + *pHeapSizeInBytes = heapLayout.sizeInBytes; + + return result; +} + +MA_API ma_result ma_hpf_init_preallocated(const ma_hpf_config* pConfig, void* pHeap, ma_hpf* pLPF) +{ + if (pLPF == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pLPF); + + return ma_hpf_reinit__internal(pConfig, pHeap, pLPF, /*isNew*/MA_TRUE); +} + +MA_API ma_result ma_hpf_init(const ma_hpf_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_hpf* pHPF) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_hpf_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_hpf_init_preallocated(pConfig, pHeap, pHPF); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pHPF->_ownsHeap = MA_TRUE; + return MA_SUCCESS; +} + +MA_API void ma_hpf_uninit(ma_hpf* pHPF, const ma_allocation_callbacks* pAllocationCallbacks) +{ + ma_uint32 ihpf1; + ma_uint32 ihpf2; + if (pHPF == NULL) { - return MA_INVALID_ARGS; + return; } - MA_ZERO_OBJECT(pHPF); - - if (pConfig == NULL) { - return MA_INVALID_ARGS; + for (ihpf1 = 0; ihpf1 < pHPF->hpf1Count; ihpf1 += 1) { + ma_hpf1_uninit(&pHPF->pHPF1[ihpf1], pAllocationCallbacks); } - return ma_hpf_reinit__internal(pConfig, pHPF, /*isNew*/MA_TRUE); + for (ihpf2 = 0; ihpf2 < pHPF->hpf2Count; ihpf2 += 1) { + ma_hpf2_uninit(&pHPF->pHPF2[ihpf2], pAllocationCallbacks); + } + + if (pHPF->_ownsHeap) { + ma_free(pHPF->_pHeap, pAllocationCallbacks); + } } MA_API ma_result ma_hpf_reinit(const ma_hpf_config* pConfig, ma_hpf* pHPF) { - return ma_hpf_reinit__internal(pConfig, pHPF, /*isNew*/MA_FALSE); + return ma_hpf_reinit__internal(pConfig, NULL, pHPF, /*isNew*/MA_FALSE); } MA_API ma_result ma_hpf_process_pcm_frames(ma_hpf* pHPF, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount) @@ -38093,14 +45479,14 @@ MA_API ma_result ma_hpf_process_pcm_frames(ma_hpf* pHPF, void* pFramesOut, const /* Faster path for in-place. */ if (pFramesOut == pFramesIn) { for (ihpf1 = 0; ihpf1 < pHPF->hpf1Count; ihpf1 += 1) { - result = ma_hpf1_process_pcm_frames(&pHPF->hpf1[ihpf1], pFramesOut, pFramesOut, frameCount); + result = ma_hpf1_process_pcm_frames(&pHPF->pHPF1[ihpf1], pFramesOut, pFramesOut, frameCount); if (result != MA_SUCCESS) { return result; } } for (ihpf2 = 0; ihpf2 < pHPF->hpf2Count; ihpf2 += 1) { - result = ma_hpf2_process_pcm_frames(&pHPF->hpf2[ihpf2], pFramesOut, pFramesOut, frameCount); + result = ma_hpf2_process_pcm_frames(&pHPF->pHPF2[ihpf2], pFramesOut, pFramesOut, frameCount); if (result != MA_SUCCESS) { return result; } @@ -38119,11 +45505,11 @@ MA_API ma_result ma_hpf_process_pcm_frames(ma_hpf* pHPF, void* pFramesOut, const MA_COPY_MEMORY(pFramesOutF32, pFramesInF32, ma_get_bytes_per_frame(pHPF->format, pHPF->channels)); for (ihpf1 = 0; ihpf1 < pHPF->hpf1Count; ihpf1 += 1) { - ma_hpf1_process_pcm_frame_f32(&pHPF->hpf1[ihpf1], pFramesOutF32, pFramesOutF32); + ma_hpf1_process_pcm_frame_f32(&pHPF->pHPF1[ihpf1], pFramesOutF32, pFramesOutF32); } for (ihpf2 = 0; ihpf2 < pHPF->hpf2Count; ihpf2 += 1) { - ma_hpf2_process_pcm_frame_f32(&pHPF->hpf2[ihpf2], pFramesOutF32, pFramesOutF32); + ma_hpf2_process_pcm_frame_f32(&pHPF->pHPF2[ihpf2], pFramesOutF32, pFramesOutF32); } pFramesOutF32 += pHPF->channels; @@ -38137,11 +45523,11 @@ MA_API ma_result ma_hpf_process_pcm_frames(ma_hpf* pHPF, void* pFramesOut, const MA_COPY_MEMORY(pFramesOutS16, pFramesInS16, ma_get_bytes_per_frame(pHPF->format, pHPF->channels)); for (ihpf1 = 0; ihpf1 < pHPF->hpf1Count; ihpf1 += 1) { - ma_hpf1_process_pcm_frame_s16(&pHPF->hpf1[ihpf1], pFramesOutS16, pFramesOutS16); + ma_hpf1_process_pcm_frame_s16(&pHPF->pHPF1[ihpf1], pFramesOutS16, pFramesOutS16); } for (ihpf2 = 0; ihpf2 < pHPF->hpf2Count; ihpf2 += 1) { - ma_hpf2_process_pcm_frame_s16(&pHPF->hpf2[ihpf2], pFramesOutS16, pFramesOutS16); + ma_hpf2_process_pcm_frame_s16(&pHPF->pHPF2[ihpf2], pFramesOutS16, pFramesOutS16); } pFramesOutS16 += pHPF->channels; @@ -38221,7 +45607,15 @@ static MA_INLINE ma_biquad_config ma_bpf2__get_biquad_config(const ma_bpf2_confi return bqConfig; } -MA_API ma_result ma_bpf2_init(const ma_bpf2_config* pConfig, ma_bpf2* pBPF) +MA_API ma_result ma_bpf2_get_heap_size(const ma_bpf2_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_biquad_config bqConfig; + bqConfig = ma_bpf2__get_biquad_config(pConfig); + + return ma_biquad_get_heap_size(&bqConfig, pHeapSizeInBytes); +} + +MA_API ma_result ma_bpf2_init_preallocated(const ma_bpf2_config* pConfig, void* pHeap, ma_bpf2* pBPF) { ma_result result; ma_biquad_config bqConfig; @@ -38237,7 +45631,7 @@ MA_API ma_result ma_bpf2_init(const ma_bpf2_config* pConfig, ma_bpf2* pBPF) } bqConfig = ma_bpf2__get_biquad_config(pConfig); - result = ma_biquad_init(&bqConfig, &pBPF->bq); + result = ma_biquad_init_preallocated(&bqConfig, pHeap, &pBPF->bq); if (result != MA_SUCCESS) { return result; } @@ -38245,6 +45639,45 @@ MA_API ma_result ma_bpf2_init(const ma_bpf2_config* pConfig, ma_bpf2* pBPF) return MA_SUCCESS; } +MA_API ma_result ma_bpf2_init(const ma_bpf2_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_bpf2* pBPF) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_bpf2_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_bpf2_init_preallocated(pConfig, pHeap, pBPF); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pBPF->bq._ownsHeap = MA_TRUE; /* <-- This will cause the biquad to take ownership of the heap and free it when it's uninitialized. */ + return MA_SUCCESS; +} + +MA_API void ma_bpf2_uninit(ma_bpf2* pBPF, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pBPF == NULL) { + return; + } + + ma_biquad_uninit(&pBPF->bq, pAllocationCallbacks); /* <-- This will free the heap allocation. */ +} + MA_API ma_result ma_bpf2_reinit(const ma_bpf2_config* pConfig, ma_bpf2* pBPF) { ma_result result; @@ -38306,12 +45739,67 @@ MA_API ma_bpf_config ma_bpf_config_init(ma_format format, ma_uint32 channels, ma return config; } -static ma_result ma_bpf_reinit__internal(const ma_bpf_config* pConfig, ma_bpf* pBPF, ma_bool32 isNew) + +typedef struct +{ + size_t sizeInBytes; + size_t bpf2Offset; +} ma_bpf_heap_layout; + +static ma_result ma_bpf_get_heap_layout(const ma_bpf_config* pConfig, ma_bpf_heap_layout* pHeapLayout) { ma_result result; ma_uint32 bpf2Count; ma_uint32 ibpf2; + MA_ASSERT(pHeapLayout != NULL); + + MA_ZERO_OBJECT(pHeapLayout); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->order > MA_MAX_FILTER_ORDER) { + return MA_INVALID_ARGS; + } + + /* We must have an even number of order. */ + if ((pConfig->order & 0x1) != 0) { + return MA_INVALID_ARGS; + } + + bpf2Count = pConfig->channels / 2; + + pHeapLayout->sizeInBytes = 0; + + /* BPF 2 */ + pHeapLayout->bpf2Offset = pHeapLayout->sizeInBytes; + for (ibpf2 = 0; ibpf2 < bpf2Count; ibpf2 += 1) { + size_t bpf2HeapSizeInBytes; + ma_bpf2_config bpf2Config = ma_bpf2_config_init(pConfig->format, pConfig->channels, pConfig->sampleRate, pConfig->cutoffFrequency, 0.707107); /* <-- The "q" parameter does not matter for the purpose of calculating the heap size. */ + + result = ma_bpf2_get_heap_size(&bpf2Config, &bpf2HeapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + pHeapLayout->sizeInBytes += sizeof(ma_bpf2) + bpf2HeapSizeInBytes; + } + + /* Make sure allocation size is aligned. */ + pHeapLayout->sizeInBytes = ma_align_64(pHeapLayout->sizeInBytes); + + return MA_SUCCESS; +} + +static ma_result ma_bpf_reinit__internal(const ma_bpf_config* pConfig, void* pHeap, ma_bpf* pBPF, ma_bool32 isNew) +{ + ma_result result; + ma_uint32 bpf2Count; + ma_uint32 ibpf2; + ma_bpf_heap_layout heapLayout; /* Only used if isNew is true. */ + if (pBPF == NULL || pConfig == NULL) { return MA_INVALID_ARGS; } @@ -38342,8 +45830,6 @@ static ma_result ma_bpf_reinit__internal(const ma_bpf_config* pConfig, ma_bpf* p bpf2Count = pConfig->order / 2; - MA_ASSERT(bpf2Count <= ma_countof(pBPF->bpf2)); - /* The filter order can't change between reinits. */ if (!isNew) { if (pBPF->bpf2Count != bpf2Count) { @@ -38351,6 +45837,20 @@ static ma_result ma_bpf_reinit__internal(const ma_bpf_config* pConfig, ma_bpf* p } } + if (isNew) { + result = ma_bpf_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + pBPF->_pHeap = pHeap; + MA_ZERO_MEMORY(pHeap, heapLayout.sizeInBytes); + + pBPF->pBPF2 = (ma_bpf2*)ma_offset_ptr(pHeap, heapLayout.bpf2Offset); + } else { + MA_ZERO_OBJECT(&heapLayout); + } + for (ibpf2 = 0; ibpf2 < bpf2Count; ibpf2 += 1) { ma_bpf2_config bpf2Config; double q; @@ -38361,9 +45861,14 @@ static ma_result ma_bpf_reinit__internal(const ma_bpf_config* pConfig, ma_bpf* p bpf2Config = ma_bpf2_config_init(pConfig->format, pConfig->channels, pConfig->sampleRate, pConfig->cutoffFrequency, q); if (isNew) { - result = ma_bpf2_init(&bpf2Config, &pBPF->bpf2[ibpf2]); + size_t bpf2HeapSizeInBytes; + + result = ma_bpf2_get_heap_size(&bpf2Config, &bpf2HeapSizeInBytes); + if (result == MA_SUCCESS) { + result = ma_bpf2_init_preallocated(&bpf2Config, ma_offset_ptr(pHeap, heapLayout.bpf2Offset + (sizeof(ma_bpf2) * bpf2Count) + (ibpf2 * bpf2HeapSizeInBytes)), &pBPF->pBPF2[ibpf2]); + } } else { - result = ma_bpf2_reinit(&bpf2Config, &pBPF->bpf2[ibpf2]); + result = ma_bpf2_reinit(&bpf2Config, &pBPF->pBPF2[ibpf2]); } if (result != MA_SUCCESS) { @@ -38378,7 +45883,29 @@ static ma_result ma_bpf_reinit__internal(const ma_bpf_config* pConfig, ma_bpf* p return MA_SUCCESS; } -MA_API ma_result ma_bpf_init(const ma_bpf_config* pConfig, ma_bpf* pBPF) + +MA_API ma_result ma_bpf_get_heap_size(const ma_bpf_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_result result; + ma_bpf_heap_layout heapLayout; + + if (pHeapSizeInBytes == NULL) { + return MA_INVALID_ARGS; + } + + *pHeapSizeInBytes = 0; + + result = ma_bpf_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + *pHeapSizeInBytes = heapLayout.sizeInBytes; + + return MA_SUCCESS; +} + +MA_API ma_result ma_bpf_init_preallocated(const ma_bpf_config* pConfig, void* pHeap, ma_bpf* pBPF) { if (pBPF == NULL) { return MA_INVALID_ARGS; @@ -38386,16 +45913,59 @@ MA_API ma_result ma_bpf_init(const ma_bpf_config* pConfig, ma_bpf* pBPF) MA_ZERO_OBJECT(pBPF); - if (pConfig == NULL) { - return MA_INVALID_ARGS; + return ma_bpf_reinit__internal(pConfig, pHeap, pBPF, /*isNew*/MA_TRUE); +} + +MA_API ma_result ma_bpf_init(const ma_bpf_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_bpf* pBPF) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_bpf_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; } - return ma_bpf_reinit__internal(pConfig, pBPF, /*isNew*/MA_TRUE); + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_bpf_init_preallocated(pConfig, pHeap, pBPF); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pBPF->_ownsHeap = MA_TRUE; + return MA_SUCCESS; +} + +MA_API void ma_bpf_uninit(ma_bpf* pBPF, const ma_allocation_callbacks* pAllocationCallbacks) +{ + ma_uint32 ibpf2; + + if (pBPF == NULL) { + return; + } + + for (ibpf2 = 0; ibpf2 < pBPF->bpf2Count; ibpf2 += 1) { + ma_bpf2_uninit(&pBPF->pBPF2[ibpf2], pAllocationCallbacks); + } + + if (pBPF->_ownsHeap) { + ma_free(pBPF->_pHeap, pAllocationCallbacks); + } } MA_API ma_result ma_bpf_reinit(const ma_bpf_config* pConfig, ma_bpf* pBPF) { - return ma_bpf_reinit__internal(pConfig, pBPF, /*isNew*/MA_FALSE); + return ma_bpf_reinit__internal(pConfig, NULL, pBPF, /*isNew*/MA_FALSE); } MA_API ma_result ma_bpf_process_pcm_frames(ma_bpf* pBPF, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount) @@ -38410,7 +45980,7 @@ MA_API ma_result ma_bpf_process_pcm_frames(ma_bpf* pBPF, void* pFramesOut, const /* Faster path for in-place. */ if (pFramesOut == pFramesIn) { for (ibpf2 = 0; ibpf2 < pBPF->bpf2Count; ibpf2 += 1) { - result = ma_bpf2_process_pcm_frames(&pBPF->bpf2[ibpf2], pFramesOut, pFramesOut, frameCount); + result = ma_bpf2_process_pcm_frames(&pBPF->pBPF2[ibpf2], pFramesOut, pFramesOut, frameCount); if (result != MA_SUCCESS) { return result; } @@ -38429,7 +45999,7 @@ MA_API ma_result ma_bpf_process_pcm_frames(ma_bpf* pBPF, void* pFramesOut, const MA_COPY_MEMORY(pFramesOutF32, pFramesInF32, ma_get_bytes_per_frame(pBPF->format, pBPF->channels)); for (ibpf2 = 0; ibpf2 < pBPF->bpf2Count; ibpf2 += 1) { - ma_bpf2_process_pcm_frame_f32(&pBPF->bpf2[ibpf2], pFramesOutF32, pFramesOutF32); + ma_bpf2_process_pcm_frame_f32(&pBPF->pBPF2[ibpf2], pFramesOutF32, pFramesOutF32); } pFramesOutF32 += pBPF->channels; @@ -38443,7 +46013,7 @@ MA_API ma_result ma_bpf_process_pcm_frames(ma_bpf* pBPF, void* pFramesOut, const MA_COPY_MEMORY(pFramesOutS16, pFramesInS16, ma_get_bytes_per_frame(pBPF->format, pBPF->channels)); for (ibpf2 = 0; ibpf2 < pBPF->bpf2Count; ibpf2 += 1) { - ma_bpf2_process_pcm_frame_s16(&pBPF->bpf2[ibpf2], pFramesOutS16, pFramesOutS16); + ma_bpf2_process_pcm_frame_s16(&pBPF->pBPF2[ibpf2], pFramesOutS16, pFramesOutS16); } pFramesOutS16 += pBPF->channels; @@ -38522,7 +46092,15 @@ static MA_INLINE ma_biquad_config ma_notch2__get_biquad_config(const ma_notch2_c return bqConfig; } -MA_API ma_result ma_notch2_init(const ma_notch2_config* pConfig, ma_notch2* pFilter) +MA_API ma_result ma_notch2_get_heap_size(const ma_notch2_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_biquad_config bqConfig; + bqConfig = ma_notch2__get_biquad_config(pConfig); + + return ma_biquad_get_heap_size(&bqConfig, pHeapSizeInBytes); +} + +MA_API ma_result ma_notch2_init_preallocated(const ma_notch2_config* pConfig, void* pHeap, ma_notch2* pFilter) { ma_result result; ma_biquad_config bqConfig; @@ -38538,7 +46116,7 @@ MA_API ma_result ma_notch2_init(const ma_notch2_config* pConfig, ma_notch2* pFil } bqConfig = ma_notch2__get_biquad_config(pConfig); - result = ma_biquad_init(&bqConfig, &pFilter->bq); + result = ma_biquad_init_preallocated(&bqConfig, pHeap, &pFilter->bq); if (result != MA_SUCCESS) { return result; } @@ -38546,6 +46124,45 @@ MA_API ma_result ma_notch2_init(const ma_notch2_config* pConfig, ma_notch2* pFil return MA_SUCCESS; } +MA_API ma_result ma_notch2_init(const ma_notch2_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_notch2* pFilter) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_notch2_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_notch2_init_preallocated(pConfig, pHeap, pFilter); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pFilter->bq._ownsHeap = MA_TRUE; /* <-- This will cause the biquad to take ownership of the heap and free it when it's uninitialized. */ + return MA_SUCCESS; +} + +MA_API void ma_notch2_uninit(ma_notch2* pFilter, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pFilter == NULL) { + return; + } + + ma_biquad_uninit(&pFilter->bq, pAllocationCallbacks); /* <-- This will free the heap allocation. */ +} + MA_API ma_result ma_notch2_reinit(const ma_notch2_config* pConfig, ma_notch2* pFilter) { ma_result result; @@ -38651,7 +46268,15 @@ static MA_INLINE ma_biquad_config ma_peak2__get_biquad_config(const ma_peak2_con return bqConfig; } -MA_API ma_result ma_peak2_init(const ma_peak2_config* pConfig, ma_peak2* pFilter) +MA_API ma_result ma_peak2_get_heap_size(const ma_peak2_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_biquad_config bqConfig; + bqConfig = ma_peak2__get_biquad_config(pConfig); + + return ma_biquad_get_heap_size(&bqConfig, pHeapSizeInBytes); +} + +MA_API ma_result ma_peak2_init_preallocated(const ma_peak2_config* pConfig, void* pHeap, ma_peak2* pFilter) { ma_result result; ma_biquad_config bqConfig; @@ -38667,7 +46292,7 @@ MA_API ma_result ma_peak2_init(const ma_peak2_config* pConfig, ma_peak2* pFilter } bqConfig = ma_peak2__get_biquad_config(pConfig); - result = ma_biquad_init(&bqConfig, &pFilter->bq); + result = ma_biquad_init_preallocated(&bqConfig, pHeap, &pFilter->bq); if (result != MA_SUCCESS) { return result; } @@ -38675,6 +46300,45 @@ MA_API ma_result ma_peak2_init(const ma_peak2_config* pConfig, ma_peak2* pFilter return MA_SUCCESS; } +MA_API ma_result ma_peak2_init(const ma_peak2_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_peak2* pFilter) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_peak2_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_peak2_init_preallocated(pConfig, pHeap, pFilter); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pFilter->bq._ownsHeap = MA_TRUE; /* <-- This will cause the biquad to take ownership of the heap and free it when it's uninitialized. */ + return MA_SUCCESS; +} + +MA_API void ma_peak2_uninit(ma_peak2* pFilter, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pFilter == NULL) { + return; + } + + ma_biquad_uninit(&pFilter->bq, pAllocationCallbacks); /* <-- This will free the heap allocation. */ +} + MA_API ma_result ma_peak2_reinit(const ma_peak2_config* pConfig, ma_peak2* pFilter) { ma_result result; @@ -38777,7 +46441,15 @@ static MA_INLINE ma_biquad_config ma_loshelf2__get_biquad_config(const ma_loshel return bqConfig; } -MA_API ma_result ma_loshelf2_init(const ma_loshelf2_config* pConfig, ma_loshelf2* pFilter) +MA_API ma_result ma_loshelf2_get_heap_size(const ma_loshelf2_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_biquad_config bqConfig; + bqConfig = ma_loshelf2__get_biquad_config(pConfig); + + return ma_biquad_get_heap_size(&bqConfig, pHeapSizeInBytes); +} + +MA_API ma_result ma_loshelf2_init_preallocated(const ma_loshelf2_config* pConfig, void* pHeap, ma_loshelf2* pFilter) { ma_result result; ma_biquad_config bqConfig; @@ -38793,7 +46465,7 @@ MA_API ma_result ma_loshelf2_init(const ma_loshelf2_config* pConfig, ma_loshelf2 } bqConfig = ma_loshelf2__get_biquad_config(pConfig); - result = ma_biquad_init(&bqConfig, &pFilter->bq); + result = ma_biquad_init_preallocated(&bqConfig, pHeap, &pFilter->bq); if (result != MA_SUCCESS) { return result; } @@ -38801,6 +46473,45 @@ MA_API ma_result ma_loshelf2_init(const ma_loshelf2_config* pConfig, ma_loshelf2 return MA_SUCCESS; } +MA_API ma_result ma_loshelf2_init(const ma_loshelf2_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_loshelf2* pFilter) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_loshelf2_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_loshelf2_init_preallocated(pConfig, pHeap, pFilter); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pFilter->bq._ownsHeap = MA_TRUE; /* <-- This will cause the biquad to take ownership of the heap and free it when it's uninitialized. */ + return MA_SUCCESS; +} + +MA_API void ma_loshelf2_uninit(ma_loshelf2* pFilter, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pFilter == NULL) { + return; + } + + ma_biquad_uninit(&pFilter->bq, pAllocationCallbacks); /* <-- This will free the heap allocation. */ +} + MA_API ma_result ma_loshelf2_reinit(const ma_loshelf2_config* pConfig, ma_loshelf2* pFilter) { ma_result result; @@ -38903,7 +46614,15 @@ static MA_INLINE ma_biquad_config ma_hishelf2__get_biquad_config(const ma_hishel return bqConfig; } -MA_API ma_result ma_hishelf2_init(const ma_hishelf2_config* pConfig, ma_hishelf2* pFilter) +MA_API ma_result ma_hishelf2_get_heap_size(const ma_hishelf2_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_biquad_config bqConfig; + bqConfig = ma_hishelf2__get_biquad_config(pConfig); + + return ma_biquad_get_heap_size(&bqConfig, pHeapSizeInBytes); +} + +MA_API ma_result ma_hishelf2_init_preallocated(const ma_hishelf2_config* pConfig, void* pHeap, ma_hishelf2* pFilter) { ma_result result; ma_biquad_config bqConfig; @@ -38919,7 +46638,7 @@ MA_API ma_result ma_hishelf2_init(const ma_hishelf2_config* pConfig, ma_hishelf2 } bqConfig = ma_hishelf2__get_biquad_config(pConfig); - result = ma_biquad_init(&bqConfig, &pFilter->bq); + result = ma_biquad_init_preallocated(&bqConfig, pHeap, &pFilter->bq); if (result != MA_SUCCESS) { return result; } @@ -38927,6 +46646,45 @@ MA_API ma_result ma_hishelf2_init(const ma_hishelf2_config* pConfig, ma_hishelf2 return MA_SUCCESS; } +MA_API ma_result ma_hishelf2_init(const ma_hishelf2_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_hishelf2* pFilter) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_hishelf2_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_hishelf2_init_preallocated(pConfig, pHeap, pFilter); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pFilter->bq._ownsHeap = MA_TRUE; /* <-- This will cause the biquad to take ownership of the heap and free it when it's uninitialized. */ + return MA_SUCCESS; +} + +MA_API void ma_hishelf2_uninit(ma_hishelf2* pFilter, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pFilter == NULL) { + return; + } + + ma_biquad_uninit(&pFilter->bq, pAllocationCallbacks); /* <-- This will free the heap allocation. */ +} + MA_API ma_result ma_hishelf2_reinit(const ma_hishelf2_config* pConfig, ma_hishelf2* pFilter) { ma_result result; @@ -38975,6 +46733,2286 @@ MA_API ma_uint32 ma_hishelf2_get_latency(const ma_hishelf2* pFilter) +/* +Delay +*/ +MA_API ma_delay_config ma_delay_config_init(ma_uint32 channels, ma_uint32 sampleRate, ma_uint32 delayInFrames, float decay) +{ + ma_delay_config config; + + MA_ZERO_OBJECT(&config); + config.channels = channels; + config.sampleRate = sampleRate; + config.delayInFrames = delayInFrames; + config.delayStart = (decay == 0) ? MA_TRUE : MA_FALSE; /* Delay the start if it looks like we're not configuring an echo. */ + config.wet = 1; + config.dry = 1; + config.decay = decay; + + return config; +} + + +MA_API ma_result ma_delay_init(const ma_delay_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_delay* pDelay) +{ + if (pDelay == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pDelay); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->decay < 0 || pConfig->decay > 1) { + return MA_INVALID_ARGS; + } + + pDelay->config = *pConfig; + pDelay->bufferSizeInFrames = pConfig->delayInFrames; + pDelay->cursor = 0; + + pDelay->pBuffer = (float*)ma_malloc((size_t)(pDelay->bufferSizeInFrames * ma_get_bytes_per_frame(ma_format_f32, pConfig->channels)), pAllocationCallbacks); + if (pDelay->pBuffer == NULL) { + return MA_OUT_OF_MEMORY; + } + + ma_silence_pcm_frames(pDelay->pBuffer, pDelay->bufferSizeInFrames, ma_format_f32, pConfig->channels); + + return MA_SUCCESS; +} + +MA_API void ma_delay_uninit(ma_delay* pDelay, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pDelay == NULL) { + return; + } + + ma_free(pDelay->pBuffer, pAllocationCallbacks); +} + +MA_API ma_result ma_delay_process_pcm_frames(ma_delay* pDelay, void* pFramesOut, const void* pFramesIn, ma_uint32 frameCount) +{ + ma_uint32 iFrame; + ma_uint32 iChannel; + float* pFramesOutF32 = (float*)pFramesOut; + const float* pFramesInF32 = (const float*)pFramesIn; + + if (pDelay == NULL || pFramesOut == NULL || pFramesIn == NULL) { + return MA_INVALID_ARGS; + } + + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + for (iChannel = 0; iChannel < pDelay->config.channels; iChannel += 1) { + ma_uint32 iBuffer = (pDelay->cursor * pDelay->config.channels) + iChannel; + + if (pDelay->config.delayStart) { + /* Delayed start. */ + + /* Read */ + pFramesOutF32[iChannel] = pDelay->pBuffer[iBuffer] * pDelay->config.wet; + + /* Feedback */ + pDelay->pBuffer[iBuffer] = (pDelay->pBuffer[iBuffer] * pDelay->config.decay) + (pFramesInF32[iChannel] * pDelay->config.dry); + } else { + /* Immediate start */ + + /* Feedback */ + pDelay->pBuffer[iBuffer] = (pDelay->pBuffer[iBuffer] * pDelay->config.decay) + (pFramesInF32[iChannel] * pDelay->config.dry); + + /* Read */ + pFramesOutF32[iChannel] = pDelay->pBuffer[iBuffer] * pDelay->config.wet; + } + } + + pDelay->cursor = (pDelay->cursor + 1) % pDelay->bufferSizeInFrames; + + pFramesOutF32 += pDelay->config.channels; + pFramesInF32 += pDelay->config.channels; + } + + return MA_SUCCESS; +} + +MA_API void ma_delay_set_wet(ma_delay* pDelay, float value) +{ + if (pDelay == NULL) { + return; + } + + pDelay->config.wet = value; +} + +MA_API float ma_delay_get_wet(const ma_delay* pDelay) +{ + if (pDelay == NULL) { + return 0; + } + + return pDelay->config.wet; +} + +MA_API void ma_delay_set_dry(ma_delay* pDelay, float value) +{ + if (pDelay == NULL) { + return; + } + + pDelay->config.dry = value; +} + +MA_API float ma_delay_get_dry(const ma_delay* pDelay) +{ + if (pDelay == NULL) { + return 0; + } + + return pDelay->config.dry; +} + +MA_API void ma_delay_set_decay(ma_delay* pDelay, float value) +{ + if (pDelay == NULL) { + return; + } + + pDelay->config.decay = value; +} + +MA_API float ma_delay_get_decay(const ma_delay* pDelay) +{ + if (pDelay == NULL) { + return 0; + } + + return pDelay->config.decay; +} + + +MA_API ma_gainer_config ma_gainer_config_init(ma_uint32 channels, ma_uint32 smoothTimeInFrames) +{ + ma_gainer_config config; + + MA_ZERO_OBJECT(&config); + config.channels = channels; + config.smoothTimeInFrames = smoothTimeInFrames; + + return config; +} + + +typedef struct +{ + size_t sizeInBytes; + size_t oldGainsOffset; + size_t newGainsOffset; +} ma_gainer_heap_layout; + +static ma_result ma_gainer_get_heap_layout(const ma_gainer_config* pConfig, ma_gainer_heap_layout* pHeapLayout) +{ + MA_ASSERT(pHeapLayout != NULL); + + MA_ZERO_OBJECT(pHeapLayout); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->channels == 0) { + return MA_INVALID_ARGS; + } + + pHeapLayout->sizeInBytes = 0; + + /* Old gains. */ + pHeapLayout->oldGainsOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += sizeof(float) * pConfig->channels; + + /* New gains. */ + pHeapLayout->newGainsOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += sizeof(float) * pConfig->channels; + + /* Alignment. */ + pHeapLayout->sizeInBytes = ma_align_64(pHeapLayout->sizeInBytes); + + return MA_SUCCESS; +} + + +MA_API ma_result ma_gainer_get_heap_size(const ma_gainer_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_result result; + ma_gainer_heap_layout heapLayout; + + if (pHeapSizeInBytes == NULL) { + return MA_INVALID_ARGS; + } + + *pHeapSizeInBytes = 0; + + result = ma_gainer_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return MA_INVALID_ARGS; + } + + *pHeapSizeInBytes = heapLayout.sizeInBytes; + + return MA_SUCCESS; +} + + +MA_API ma_result ma_gainer_init_preallocated(const ma_gainer_config* pConfig, void* pHeap, ma_gainer* pGainer) +{ + ma_result result; + ma_gainer_heap_layout heapLayout; + ma_uint32 iChannel; + + if (pGainer == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pGainer); + + if (pConfig == NULL || pHeap == NULL) { + return MA_INVALID_ARGS; + } + + result = ma_gainer_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + pGainer->_pHeap = pHeap; + MA_ZERO_MEMORY(pHeap, heapLayout.sizeInBytes); + + pGainer->pOldGains = (float*)ma_offset_ptr(pHeap, heapLayout.oldGainsOffset); + pGainer->pNewGains = (float*)ma_offset_ptr(pHeap, heapLayout.newGainsOffset); + + pGainer->config = *pConfig; + pGainer->t = (ma_uint32)-1; /* No interpolation by default. */ + + for (iChannel = 0; iChannel < pConfig->channels; iChannel += 1) { + pGainer->pOldGains[iChannel] = 1; + pGainer->pNewGains[iChannel] = 1; + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_gainer_init(const ma_gainer_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_gainer* pGainer) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_gainer_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; /* Failed to retrieve the size of the heap allocation. */ + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_gainer_init_preallocated(pConfig, pHeap, pGainer); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pGainer->_ownsHeap = MA_TRUE; + return MA_SUCCESS; +} + +MA_API void ma_gainer_uninit(ma_gainer* pGainer, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pGainer == NULL) { + return; + } + + if (pGainer->_ownsHeap) { + ma_free(pGainer->_pHeap, pAllocationCallbacks); + } +} + +static float ma_gainer_calculate_current_gain(const ma_gainer* pGainer, ma_uint32 channel) +{ + float a = (float)pGainer->t / pGainer->config.smoothTimeInFrames; + return ma_mix_f32_fast(pGainer->pOldGains[channel], pGainer->pNewGains[channel], a); +} + +MA_API ma_result ma_gainer_process_pcm_frames(ma_gainer* pGainer, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount) +{ + ma_uint64 iFrame; + ma_uint32 iChannel; + float* pFramesOutF32 = (float*)pFramesOut; + const float* pFramesInF32 = (const float*)pFramesIn; + + if (pGainer == NULL) { + return MA_INVALID_ARGS; + } + + if (pGainer->t >= pGainer->config.smoothTimeInFrames) { + /* Fast path. No gain calculation required. */ + ma_copy_and_apply_volume_factor_per_channel_f32(pFramesOutF32, pFramesInF32, frameCount, pGainer->config.channels, pGainer->pNewGains); + + /* Now that some frames have been processed we need to make sure future changes to the gain are interpolated. */ + if (pGainer->t == (ma_uint32)-1) { + pGainer->t = pGainer->config.smoothTimeInFrames; + } + } else { + /* Slow path. Need to interpolate the gain for each channel individually. */ + + /* We can allow the input and output buffers to be null in which case we'll just update the internal timer. */ + if (pFramesOut != NULL && pFramesIn != NULL) { + float a = (float)pGainer->t / pGainer->config.smoothTimeInFrames; + float d = 1.0f / pGainer->config.smoothTimeInFrames; + ma_uint32 channelCount = pGainer->config.channels; + + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + for (iChannel = 0; iChannel < channelCount; iChannel += 1) { + pFramesOutF32[iChannel] = pFramesInF32[iChannel] * ma_mix_f32_fast(pGainer->pOldGains[iChannel], pGainer->pNewGains[iChannel], a); + } + + pFramesOutF32 += channelCount; + pFramesInF32 += channelCount; + + a += d; + if (a > 1) { + a = 1; + } + } + } + + pGainer->t = (ma_uint32)ma_min(pGainer->t + frameCount, pGainer->config.smoothTimeInFrames); + + #if 0 /* Reference implementation. */ + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + /* We can allow the input and output buffers to be null in which case we'll just update the internal timer. */ + if (pFramesOut != NULL && pFramesIn != NULL) { + for (iChannel = 0; iChannel < pGainer->config.channels; iChannel += 1) { + pFramesOutF32[iFrame*pGainer->config.channels + iChannel] = pFramesInF32[iFrame*pGainer->config.channels + iChannel] * ma_gainer_calculate_current_gain(pGainer, iChannel); + } + } + + /* Move interpolation time forward, but don't go beyond our smoothing time. */ + pGainer->t = ma_min(pGainer->t + 1, pGainer->config.smoothTimeInFrames); + } + #endif + } + + return MA_SUCCESS; +} + +static void ma_gainer_set_gain_by_index(ma_gainer* pGainer, float newGain, ma_uint32 iChannel) +{ + pGainer->pOldGains[iChannel] = ma_gainer_calculate_current_gain(pGainer, iChannel); + pGainer->pNewGains[iChannel] = newGain; +} + +static void ma_gainer_reset_smoothing_time(ma_gainer* pGainer) +{ + if (pGainer->t == (ma_uint32)-1) { + pGainer->t = pGainer->config.smoothTimeInFrames; /* No smoothing required for initial gains setting. */ + } else { + pGainer->t = 0; + } +} + +MA_API ma_result ma_gainer_set_gain(ma_gainer* pGainer, float newGain) +{ + ma_uint32 iChannel; + + if (pGainer == NULL) { + return MA_INVALID_ARGS; + } + + for (iChannel = 0; iChannel < pGainer->config.channels; iChannel += 1) { + ma_gainer_set_gain_by_index(pGainer, newGain, iChannel); + } + + /* The smoothing time needs to be reset to ensure we always interpolate by the configured smoothing time, but only if it's not the first setting. */ + ma_gainer_reset_smoothing_time(pGainer); + + return MA_SUCCESS; +} + +MA_API ma_result ma_gainer_set_gains(ma_gainer* pGainer, float* pNewGains) +{ + ma_uint32 iChannel; + + if (pGainer == NULL || pNewGains == NULL) { + return MA_INVALID_ARGS; + } + + for (iChannel = 0; iChannel < pGainer->config.channels; iChannel += 1) { + ma_gainer_set_gain_by_index(pGainer, pNewGains[iChannel], iChannel); + } + + /* The smoothing time needs to be reset to ensure we always interpolate by the configured smoothing time, but only if it's not the first setting. */ + ma_gainer_reset_smoothing_time(pGainer); + + return MA_SUCCESS; +} + + +MA_API ma_panner_config ma_panner_config_init(ma_format format, ma_uint32 channels) +{ + ma_panner_config config; + + MA_ZERO_OBJECT(&config); + config.format = format; + config.channels = channels; + config.mode = ma_pan_mode_balance; /* Set to balancing mode by default because it's consistent with other audio engines and most likely what the caller is expecting. */ + config.pan = 0; + + return config; +} + + +MA_API ma_result ma_panner_init(const ma_panner_config* pConfig, ma_panner* pPanner) +{ + if (pPanner == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pPanner); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + pPanner->format = pConfig->format; + pPanner->channels = pConfig->channels; + pPanner->mode = pConfig->mode; + pPanner->pan = pConfig->pan; + + return MA_SUCCESS; +} + +static void ma_stereo_balance_pcm_frames_f32(float* pFramesOut, const float* pFramesIn, ma_uint64 frameCount, float pan) +{ + ma_uint64 iFrame; + + if (pan > 0) { + float factor = 1.0f - pan; + if (pFramesOut == pFramesIn) { + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + pFramesOut[iFrame*2 + 0] = pFramesIn[iFrame*2 + 0] * factor; + } + } else { + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + pFramesOut[iFrame*2 + 0] = pFramesIn[iFrame*2 + 0] * factor; + pFramesOut[iFrame*2 + 1] = pFramesIn[iFrame*2 + 1]; + } + } + } else { + float factor = 1.0f + pan; + if (pFramesOut == pFramesIn) { + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + pFramesOut[iFrame*2 + 1] = pFramesIn[iFrame*2 + 1] * factor; + } + } else { + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + pFramesOut[iFrame*2 + 0] = pFramesIn[iFrame*2 + 0]; + pFramesOut[iFrame*2 + 1] = pFramesIn[iFrame*2 + 1] * factor; + } + } + } +} + +static void ma_stereo_balance_pcm_frames(void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount, ma_format format, float pan) +{ + if (pan == 0) { + /* Fast path. No panning required. */ + if (pFramesOut == pFramesIn) { + /* No-op */ + } else { + ma_copy_pcm_frames(pFramesOut, pFramesIn, frameCount, format, 2); + } + + return; + } + + switch (format) { + case ma_format_f32: ma_stereo_balance_pcm_frames_f32((float*)pFramesOut, (float*)pFramesIn, frameCount, pan); break; + + /* Unknown format. Just copy. */ + default: + { + ma_copy_pcm_frames(pFramesOut, pFramesIn, frameCount, format, 2); + } break; + } +} + + +static void ma_stereo_pan_pcm_frames_f32(float* pFramesOut, const float* pFramesIn, ma_uint64 frameCount, float pan) +{ + ma_uint64 iFrame; + + if (pan > 0) { + float factorL0 = 1.0f - pan; + float factorL1 = 0.0f + pan; + + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + float sample0 = (pFramesIn[iFrame*2 + 0] * factorL0); + float sample1 = (pFramesIn[iFrame*2 + 0] * factorL1) + pFramesIn[iFrame*2 + 1]; + + pFramesOut[iFrame*2 + 0] = sample0; + pFramesOut[iFrame*2 + 1] = sample1; + } + } else { + float factorR0 = 0.0f - pan; + float factorR1 = 1.0f + pan; + + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + float sample0 = pFramesIn[iFrame*2 + 0] + (pFramesIn[iFrame*2 + 1] * factorR0); + float sample1 = (pFramesIn[iFrame*2 + 1] * factorR1); + + pFramesOut[iFrame*2 + 0] = sample0; + pFramesOut[iFrame*2 + 1] = sample1; + } + } +} + +static void ma_stereo_pan_pcm_frames(void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount, ma_format format, float pan) +{ + if (pan == 0) { + /* Fast path. No panning required. */ + if (pFramesOut == pFramesIn) { + /* No-op */ + } else { + ma_copy_pcm_frames(pFramesOut, pFramesIn, frameCount, format, 2); + } + + return; + } + + switch (format) { + case ma_format_f32: ma_stereo_pan_pcm_frames_f32((float*)pFramesOut, (float*)pFramesIn, frameCount, pan); break; + + /* Unknown format. Just copy. */ + default: + { + ma_copy_pcm_frames(pFramesOut, pFramesIn, frameCount, format, 2); + } break; + } +} + +MA_API ma_result ma_panner_process_pcm_frames(ma_panner* pPanner, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount) +{ + if (pPanner == NULL || pFramesOut == NULL || pFramesIn == NULL) { + return MA_INVALID_ARGS; + } + + if (pPanner->channels == 2) { + /* Stereo case. For now assume channel 0 is left and channel right is 1, but should probably add support for a channel map. */ + if (pPanner->mode == ma_pan_mode_balance) { + ma_stereo_balance_pcm_frames(pFramesOut, pFramesIn, frameCount, pPanner->format, pPanner->pan); + } else { + ma_stereo_pan_pcm_frames(pFramesOut, pFramesIn, frameCount, pPanner->format, pPanner->pan); + } + } else { + if (pPanner->channels == 1) { + /* Panning has no effect on mono streams. */ + ma_copy_pcm_frames(pFramesOut, pFramesIn, frameCount, pPanner->format, pPanner->channels); + } else { + /* For now we're not going to support non-stereo set ups. Not sure how I want to handle this case just yet. */ + ma_copy_pcm_frames(pFramesOut, pFramesIn, frameCount, pPanner->format, pPanner->channels); + } + } + + return MA_SUCCESS; +} + +MA_API void ma_panner_set_mode(ma_panner* pPanner, ma_pan_mode mode) +{ + if (pPanner == NULL) { + return; + } + + pPanner->mode = mode; +} + +MA_API ma_pan_mode ma_panner_get_mode(const ma_panner* pPanner) +{ + if (pPanner == NULL) { + return ma_pan_mode_balance; + } + + return pPanner->mode; +} + +MA_API void ma_panner_set_pan(ma_panner* pPanner, float pan) +{ + if (pPanner == NULL) { + return; + } + + pPanner->pan = ma_clamp(pan, -1.0f, 1.0f); +} + +MA_API float ma_panner_get_pan(const ma_panner* pPanner) +{ + if (pPanner == NULL) { + return 0; + } + + return pPanner->pan; +} + + + + +MA_API ma_fader_config ma_fader_config_init(ma_format format, ma_uint32 channels, ma_uint32 sampleRate) +{ + ma_fader_config config; + + MA_ZERO_OBJECT(&config); + config.format = format; + config.channels = channels; + config.sampleRate = sampleRate; + + return config; +} + + +MA_API ma_result ma_fader_init(const ma_fader_config* pConfig, ma_fader* pFader) +{ + if (pFader == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pFader); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + /* Only f32 is supported for now. */ + if (pConfig->format != ma_format_f32) { + return MA_INVALID_ARGS; + } + + pFader->config = *pConfig; + pFader->volumeBeg = 1; + pFader->volumeEnd = 1; + pFader->lengthInFrames = 0; + pFader->cursorInFrames = 0; + + return MA_SUCCESS; +} + +MA_API ma_result ma_fader_process_pcm_frames(ma_fader* pFader, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount) +{ + if (pFader == NULL) { + return MA_INVALID_ARGS; + } + + /* + For now we need to clamp frameCount so that the cursor never overflows 32-bits. This is required for + the conversion to a float which we use for the linear interpolation. This might be changed later. + */ + if (frameCount + pFader->cursorInFrames > UINT_MAX) { + frameCount = UINT_MAX - pFader->cursorInFrames; + } + + /* Optimized path if volumeBeg and volumeEnd are equal. */ + if (pFader->volumeBeg == pFader->volumeEnd) { + if (pFader->volumeBeg == 1) { + /* Straight copy. */ + ma_copy_pcm_frames(pFramesOut, pFramesIn, frameCount, pFader->config.format, pFader->config.channels); + } else { + /* Copy with volume. */ + ma_copy_and_apply_volume_and_clip_pcm_frames(pFramesOut, pFramesIn, frameCount, pFader->config.format, pFader->config.channels, pFader->volumeEnd); + } + } else { + /* Slower path. Volumes are different, so may need to do an interpolation. */ + if (pFader->cursorInFrames >= pFader->lengthInFrames) { + /* Fast path. We've gone past the end of the fade period so just apply the end volume to all samples. */ + ma_copy_and_apply_volume_and_clip_pcm_frames(pFramesOut, pFramesIn, frameCount, pFader->config.format, pFader->config.channels, pFader->volumeEnd); + } else { + /* Slow path. This is where we do the actual fading. */ + ma_uint64 iFrame; + ma_uint32 iChannel; + + /* For now we only support f32. Support for other formats will be added later. */ + if (pFader->config.format == ma_format_f32) { + const float* pFramesInF32 = (const float*)pFramesIn; + /* */ float* pFramesOutF32 = ( float*)pFramesOut; + + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + float a = (ma_uint32)ma_min(pFader->cursorInFrames + iFrame, pFader->lengthInFrames) / (float)((ma_uint32)pFader->lengthInFrames); /* Safe cast due to the frameCount clamp at the top of this function. */ + float volume = ma_mix_f32_fast(pFader->volumeBeg, pFader->volumeEnd, a); + + for (iChannel = 0; iChannel < pFader->config.channels; iChannel += 1) { + pFramesOutF32[iFrame*pFader->config.channels + iChannel] = pFramesInF32[iFrame*pFader->config.channels + iChannel] * volume; + } + } + } else { + return MA_NOT_IMPLEMENTED; + } + } + } + + pFader->cursorInFrames += frameCount; + + return MA_SUCCESS; +} + +MA_API void ma_fader_get_data_format(const ma_fader* pFader, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate) +{ + if (pFader == NULL) { + return; + } + + if (pFormat != NULL) { + *pFormat = pFader->config.format; + } + + if (pChannels != NULL) { + *pChannels = pFader->config.channels; + } + + if (pSampleRate != NULL) { + *pSampleRate = pFader->config.sampleRate; + } +} + +MA_API void ma_fader_set_fade(ma_fader* pFader, float volumeBeg, float volumeEnd, ma_uint64 lengthInFrames) +{ + if (pFader == NULL) { + return; + } + + /* If the volume is negative, use current volume. */ + if (volumeBeg < 0) { + volumeBeg = ma_fader_get_current_volume(pFader); + } + + /* + The length needs to be clamped to 32-bits due to how we convert it to a float for linear + interpolation reasons. I might change this requirement later, but for now it's not important. + */ + if (lengthInFrames > UINT_MAX) { + lengthInFrames = UINT_MAX; + } + + pFader->volumeBeg = volumeBeg; + pFader->volumeEnd = volumeEnd; + pFader->lengthInFrames = lengthInFrames; + pFader->cursorInFrames = 0; /* Reset cursor. */ +} + +MA_API float ma_fader_get_current_volume(ma_fader* pFader) +{ + if (pFader == NULL) { + return 0.0f; + } + + /* The current volume depends on the position of the cursor. */ + if (pFader->cursorInFrames == 0) { + return pFader->volumeBeg; + } else if (pFader->cursorInFrames >= pFader->lengthInFrames) { + return pFader->volumeEnd; + } else { + /* The cursor is somewhere inside the fading period. We can figure this out with a simple linear interpoluation between volumeBeg and volumeEnd based on our cursor position. */ + return ma_mix_f32_fast(pFader->volumeBeg, pFader->volumeEnd, (ma_uint32)pFader->cursorInFrames / (float)((ma_uint32)pFader->lengthInFrames)); /* Safe cast to uint32 because we clamp it in ma_fader_process_pcm_frames(). */ + } +} + + + + + +MA_API ma_vec3f ma_vec3f_init_3f(float x, float y, float z) +{ + ma_vec3f v; + + v.x = x; + v.y = y; + v.z = z; + + return v; +} + +MA_API ma_vec3f ma_vec3f_sub(ma_vec3f a, ma_vec3f b) +{ + return ma_vec3f_init_3f( + a.x - b.x, + a.y - b.y, + a.z - b.z + ); +} + +MA_API ma_vec3f ma_vec3f_neg(ma_vec3f a) +{ + return ma_vec3f_init_3f( + -a.x, + -a.y, + -a.z + ); +} + +MA_API float ma_vec3f_dot(ma_vec3f a, ma_vec3f b) +{ + return a.x*b.x + a.y*b.y + a.z*b.z; +} + +MA_API float ma_vec3f_len2(ma_vec3f v) +{ + return ma_vec3f_dot(v, v); +} + +MA_API float ma_vec3f_len(ma_vec3f v) +{ + return (float)ma_sqrtd(ma_vec3f_len2(v)); +} + +MA_API float ma_vec3f_dist(ma_vec3f a, ma_vec3f b) +{ + return ma_vec3f_len(ma_vec3f_sub(a, b)); +} + +MA_API ma_vec3f ma_vec3f_normalize(ma_vec3f v) +{ + float f; + float l = ma_vec3f_len(v); + if (l == 0) { + return ma_vec3f_init_3f(0, 0, 0); + } + + f = 1 / l; + v.x *= f; + v.y *= f; + v.z *= f; + + return v; +} + +MA_API ma_vec3f ma_vec3f_cross(ma_vec3f a, ma_vec3f b) +{ + return ma_vec3f_init_3f( + a.y*b.z - a.z*b.y, + a.z*b.x - a.x*b.z, + a.x*b.y - a.y*b.x + ); +} + + + +static void ma_channel_map_apply_f32(float* pFramesOut, const ma_channel* pChannelMapOut, ma_uint32 channelsOut, const float* pFramesIn, const ma_channel* pChannelMapIn, ma_uint32 channelsIn, ma_uint64 frameCount, ma_channel_mix_mode mode, ma_mono_expansion_mode monoExpansionMode); +static ma_bool32 ma_is_spatial_channel_position(ma_channel channelPosition); + + +#ifndef MA_DEFAULT_SPEED_OF_SOUND +#define MA_DEFAULT_SPEED_OF_SOUND 343.3f +#endif + +/* +These vectors represent the direction that speakers are facing from the center point. They're used +for panning in the spatializer. Must be normalized. +*/ +static ma_vec3f g_maChannelDirections[MA_CHANNEL_POSITION_COUNT] = { + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_NONE */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_MONO */ + {-0.7071f, 0.0f, -0.7071f }, /* MA_CHANNEL_FRONT_LEFT */ + {+0.7071f, 0.0f, -0.7071f }, /* MA_CHANNEL_FRONT_RIGHT */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_FRONT_CENTER */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_LFE */ + {-0.7071f, 0.0f, +0.7071f }, /* MA_CHANNEL_BACK_LEFT */ + {+0.7071f, 0.0f, +0.7071f }, /* MA_CHANNEL_BACK_RIGHT */ + {-0.3162f, 0.0f, -0.9487f }, /* MA_CHANNEL_FRONT_LEFT_CENTER */ + {+0.3162f, 0.0f, -0.9487f }, /* MA_CHANNEL_FRONT_RIGHT_CENTER */ + { 0.0f, 0.0f, +1.0f }, /* MA_CHANNEL_BACK_CENTER */ + {-1.0f, 0.0f, 0.0f }, /* MA_CHANNEL_SIDE_LEFT */ + {+1.0f, 0.0f, 0.0f }, /* MA_CHANNEL_SIDE_RIGHT */ + { 0.0f, +1.0f, 0.0f }, /* MA_CHANNEL_TOP_CENTER */ + {-0.5774f, +0.5774f, -0.5774f }, /* MA_CHANNEL_TOP_FRONT_LEFT */ + { 0.0f, +0.7071f, -0.7071f }, /* MA_CHANNEL_TOP_FRONT_CENTER */ + {+0.5774f, +0.5774f, -0.5774f }, /* MA_CHANNEL_TOP_FRONT_RIGHT */ + {-0.5774f, +0.5774f, +0.5774f }, /* MA_CHANNEL_TOP_BACK_LEFT */ + { 0.0f, +0.7071f, +0.7071f }, /* MA_CHANNEL_TOP_BACK_CENTER */ + {+0.5774f, +0.5774f, +0.5774f }, /* MA_CHANNEL_TOP_BACK_RIGHT */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_0 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_1 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_2 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_3 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_4 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_5 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_6 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_7 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_8 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_9 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_10 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_11 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_12 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_13 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_14 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_15 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_16 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_17 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_18 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_19 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_20 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_21 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_22 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_23 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_24 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_25 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_26 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_27 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_28 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_29 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_30 */ + { 0.0f, 0.0f, -1.0f } /* MA_CHANNEL_AUX_31 */ +}; + +static ma_vec3f ma_get_channel_direction(ma_channel channel) +{ + if (channel >= MA_CHANNEL_POSITION_COUNT) { + return ma_vec3f_init_3f(0, 0, -1); + } else { + return g_maChannelDirections[channel]; + } +} + + + +static float ma_attenuation_inverse(float distance, float minDistance, float maxDistance, float rolloff) +{ + if (minDistance >= maxDistance) { + return 1; /* To avoid division by zero. Do not attenuate. */ + } + + return minDistance / (minDistance + rolloff * (ma_clamp(distance, minDistance, maxDistance) - minDistance)); +} + +static float ma_attenuation_linear(float distance, float minDistance, float maxDistance, float rolloff) +{ + if (minDistance >= maxDistance) { + return 1; /* To avoid division by zero. Do not attenuate. */ + } + + return 1 - rolloff * (ma_clamp(distance, minDistance, maxDistance) - minDistance) / (maxDistance - minDistance); +} + +static float ma_attenuation_exponential(float distance, float minDistance, float maxDistance, float rolloff) +{ + if (minDistance >= maxDistance) { + return 1; /* To avoid division by zero. Do not attenuate. */ + } + + return (float)ma_powd(ma_clamp(distance, minDistance, maxDistance) / minDistance, -rolloff); +} + + +/* +Dopper Effect calculation taken from the OpenAL spec, with two main differences: + + 1) The source to listener vector will have already been calcualted at an earlier step so we can + just use that directly. We need only the position of the source relative to the origin. + + 2) We don't scale by a frequency because we actually just want the ratio which we'll plug straight + into the resampler directly. +*/ +static float ma_doppler_pitch(ma_vec3f relativePosition, ma_vec3f sourceVelocity, ma_vec3f listenVelocity, float speedOfSound, float dopplerFactor) +{ + float len; + float vls; + float vss; + + len = ma_vec3f_len(relativePosition); + + /* + There's a case where the position of the source will be right on top of the listener in which + case the length will be 0 and we'll end up with a division by zero. We can just return a ratio + of 1.0 in this case. This is not considered in the OpenAL spec, but is necessary. + */ + if (len == 0) { + return 1.0; + } + + vls = ma_vec3f_dot(relativePosition, listenVelocity) / len; + vss = ma_vec3f_dot(relativePosition, sourceVelocity) / len; + + vls = ma_min(vls, speedOfSound / dopplerFactor); + vss = ma_min(vss, speedOfSound / dopplerFactor); + + return (speedOfSound - dopplerFactor*vls) / (speedOfSound - dopplerFactor*vss); +} + + +static void ma_get_default_channel_map_for_spatializer(ma_channel* pChannelMap, size_t channelMapCap, ma_uint32 channelCount) +{ + /* + Special case for stereo. Want to default the left and right speakers to side left and side + right so that they're facing directly down the X axis rather than slightly forward. Not + doing this will result in sounds being quieter when behind the listener. This might + actually be good for some scenerios, but I don't think it's an appropriate default because + it can be a bit unexpected. + */ + if (channelCount == 2) { + pChannelMap[0] = MA_CHANNEL_SIDE_LEFT; + pChannelMap[1] = MA_CHANNEL_SIDE_RIGHT; + } else { + ma_channel_map_init_standard(ma_standard_channel_map_default, pChannelMap, channelMapCap, channelCount); + } +} + + +MA_API ma_spatializer_listener_config ma_spatializer_listener_config_init(ma_uint32 channelsOut) +{ + ma_spatializer_listener_config config; + + MA_ZERO_OBJECT(&config); + config.channelsOut = channelsOut; + config.pChannelMapOut = NULL; + config.handedness = ma_handedness_right; + config.worldUp = ma_vec3f_init_3f(0, 1, 0); + config.coneInnerAngleInRadians = 6.283185f; /* 360 degrees. */ + config.coneOuterAngleInRadians = 6.283185f; /* 360 degrees. */ + config.coneOuterGain = 0; + config.speedOfSound = 343.3f; /* Same as OpenAL. Used for doppler effect. */ + + return config; +} + + +typedef struct +{ + size_t sizeInBytes; + size_t channelMapOutOffset; +} ma_spatializer_listener_heap_layout; + +static ma_result ma_spatializer_listener_get_heap_layout(const ma_spatializer_listener_config* pConfig, ma_spatializer_listener_heap_layout* pHeapLayout) +{ + MA_ASSERT(pHeapLayout != NULL); + + MA_ZERO_OBJECT(pHeapLayout); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->channelsOut == 0) { + return MA_INVALID_ARGS; + } + + pHeapLayout->sizeInBytes = 0; + + /* Channel map. We always need this, even for passthroughs. */ + pHeapLayout->channelMapOutOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += ma_align_64(sizeof(*pConfig->pChannelMapOut) * pConfig->channelsOut); + + return MA_SUCCESS; +} + + +MA_API ma_result ma_spatializer_listener_get_heap_size(const ma_spatializer_listener_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_result result; + ma_spatializer_listener_heap_layout heapLayout; + + if (pHeapSizeInBytes == NULL) { + return MA_INVALID_ARGS; + } + + *pHeapSizeInBytes = 0; + + result = ma_spatializer_listener_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + *pHeapSizeInBytes = heapLayout.sizeInBytes; + + return MA_SUCCESS; +} + +MA_API ma_result ma_spatializer_listener_init_preallocated(const ma_spatializer_listener_config* pConfig, void* pHeap, ma_spatializer_listener* pListener) +{ + ma_result result; + ma_spatializer_listener_heap_layout heapLayout; + + if (pListener == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pListener); + + result = ma_spatializer_listener_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + pListener->_pHeap = pHeap; + MA_ZERO_MEMORY(pHeap, heapLayout.sizeInBytes); + + pListener->config = *pConfig; + pListener->position = ma_vec3f_init_3f(0, 0, 0); + pListener->direction = ma_vec3f_init_3f(0, 0, -1); + pListener->velocity = ma_vec3f_init_3f(0, 0, 0); + pListener->isEnabled = MA_TRUE; + + /* Swap the forward direction if we're left handed (it was initialized based on right handed). */ + if (pListener->config.handedness == ma_handedness_left) { + pListener->direction = ma_vec3f_neg(pListener->direction); + } + + + /* We must always have a valid channel map. */ + pListener->config.pChannelMapOut = (ma_channel*)ma_offset_ptr(pHeap, heapLayout.channelMapOutOffset); + + /* Use a slightly different default channel map for stereo. */ + if (pConfig->pChannelMapOut == NULL) { + ma_get_default_channel_map_for_spatializer(pListener->config.pChannelMapOut, pConfig->channelsOut, pConfig->channelsOut); + } else { + ma_channel_map_copy_or_default(pListener->config.pChannelMapOut, pConfig->channelsOut, pConfig->pChannelMapOut, pConfig->channelsOut); + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_spatializer_listener_init(const ma_spatializer_listener_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_spatializer_listener* pListener) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_spatializer_listener_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_spatializer_listener_init_preallocated(pConfig, pHeap, pListener); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pListener->_ownsHeap = MA_TRUE; + return MA_SUCCESS; +} + +MA_API void ma_spatializer_listener_uninit(ma_spatializer_listener* pListener, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pListener == NULL) { + return; + } + + if (pListener->_ownsHeap) { + ma_free(pListener->_pHeap, pAllocationCallbacks); + } +} + +MA_API ma_channel* ma_spatializer_listener_get_channel_map(ma_spatializer_listener* pListener) +{ + if (pListener == NULL) { + return NULL; + } + + return pListener->config.pChannelMapOut; +} + +MA_API void ma_spatializer_listener_set_cone(ma_spatializer_listener* pListener, float innerAngleInRadians, float outerAngleInRadians, float outerGain) +{ + if (pListener == NULL) { + return; + } + + pListener->config.coneInnerAngleInRadians = innerAngleInRadians; + pListener->config.coneOuterAngleInRadians = outerAngleInRadians; + pListener->config.coneOuterGain = outerGain; +} + +MA_API void ma_spatializer_listener_get_cone(const ma_spatializer_listener* pListener, float* pInnerAngleInRadians, float* pOuterAngleInRadians, float* pOuterGain) +{ + if (pListener == NULL) { + return; + } + + if (pInnerAngleInRadians != NULL) { + *pInnerAngleInRadians = pListener->config.coneInnerAngleInRadians; + } + + if (pOuterAngleInRadians != NULL) { + *pOuterAngleInRadians = pListener->config.coneOuterAngleInRadians; + } + + if (pOuterGain != NULL) { + *pOuterGain = pListener->config.coneOuterGain; + } +} + +MA_API void ma_spatializer_listener_set_position(ma_spatializer_listener* pListener, float x, float y, float z) +{ + if (pListener == NULL) { + return; + } + + pListener->position = ma_vec3f_init_3f(x, y, z); +} + +MA_API ma_vec3f ma_spatializer_listener_get_position(const ma_spatializer_listener* pListener) +{ + if (pListener == NULL) { + return ma_vec3f_init_3f(0, 0, 0); + } + + return pListener->position; +} + +MA_API void ma_spatializer_listener_set_direction(ma_spatializer_listener* pListener, float x, float y, float z) +{ + if (pListener == NULL) { + return; + } + + pListener->direction = ma_vec3f_init_3f(x, y, z); +} + +MA_API ma_vec3f ma_spatializer_listener_get_direction(const ma_spatializer_listener* pListener) +{ + if (pListener == NULL) { + return ma_vec3f_init_3f(0, 0, -1); + } + + return pListener->direction; +} + +MA_API void ma_spatializer_listener_set_velocity(ma_spatializer_listener* pListener, float x, float y, float z) +{ + if (pListener == NULL) { + return; + } + + pListener->velocity = ma_vec3f_init_3f(x, y, z); +} + +MA_API ma_vec3f ma_spatializer_listener_get_velocity(const ma_spatializer_listener* pListener) +{ + if (pListener == NULL) { + return ma_vec3f_init_3f(0, 0, 0); + } + + return pListener->velocity; +} + +MA_API void ma_spatializer_listener_set_speed_of_sound(ma_spatializer_listener* pListener, float speedOfSound) +{ + if (pListener == NULL) { + return; + } + + pListener->config.speedOfSound = speedOfSound; +} + +MA_API float ma_spatializer_listener_get_speed_of_sound(const ma_spatializer_listener* pListener) +{ + if (pListener == NULL) { + return 0; + } + + return pListener->config.speedOfSound; +} + +MA_API void ma_spatializer_listener_set_world_up(ma_spatializer_listener* pListener, float x, float y, float z) +{ + if (pListener == NULL) { + return; + } + + pListener->config.worldUp = ma_vec3f_init_3f(x, y, z); +} + +MA_API ma_vec3f ma_spatializer_listener_get_world_up(const ma_spatializer_listener* pListener) +{ + if (pListener == NULL) { + return ma_vec3f_init_3f(0, 1, 0); + } + + return pListener->config.worldUp; +} + +MA_API void ma_spatializer_listener_set_enabled(ma_spatializer_listener* pListener, ma_bool32 isEnabled) +{ + if (pListener == NULL) { + return; + } + + pListener->isEnabled = isEnabled; +} + +MA_API ma_bool32 ma_spatializer_listener_is_enabled(const ma_spatializer_listener* pListener) +{ + if (pListener == NULL) { + return MA_FALSE; + } + + return pListener->isEnabled; +} + + + + +MA_API ma_spatializer_config ma_spatializer_config_init(ma_uint32 channelsIn, ma_uint32 channelsOut) +{ + ma_spatializer_config config; + + MA_ZERO_OBJECT(&config); + config.channelsIn = channelsIn; + config.channelsOut = channelsOut; + config.pChannelMapIn = NULL; + config.attenuationModel = ma_attenuation_model_inverse; + config.positioning = ma_positioning_absolute; + config.handedness = ma_handedness_right; + config.minGain = 0; + config.maxGain = 1; + config.minDistance = 1; + config.maxDistance = MA_FLT_MAX; + config.rolloff = 1; + config.coneInnerAngleInRadians = 6.283185f; /* 360 degrees. */ + config.coneOuterAngleInRadians = 6.283185f; /* 360 degress. */ + config.coneOuterGain = 0.0f; + config.dopplerFactor = 1; + config.directionalAttenuationFactor = 1; + config.gainSmoothTimeInFrames = 360; /* 7.5ms @ 48K. */ + + return config; +} + + +static ma_gainer_config ma_spatializer_gainer_config_init(const ma_spatializer_config* pConfig) +{ + MA_ASSERT(pConfig != NULL); + return ma_gainer_config_init(pConfig->channelsOut, pConfig->gainSmoothTimeInFrames); +} + +static ma_result ma_spatializer_validate_config(const ma_spatializer_config* pConfig) +{ + MA_ASSERT(pConfig != NULL); + + if (pConfig->channelsIn == 0 || pConfig->channelsOut == 0) { + return MA_INVALID_ARGS; + } + + return MA_SUCCESS; +} + +typedef struct +{ + size_t sizeInBytes; + size_t channelMapInOffset; + size_t newChannelGainsOffset; + size_t gainerOffset; +} ma_spatializer_heap_layout; + +static ma_result ma_spatializer_get_heap_layout(const ma_spatializer_config* pConfig, ma_spatializer_heap_layout* pHeapLayout) +{ + ma_result result; + + MA_ASSERT(pHeapLayout != NULL); + + MA_ZERO_OBJECT(pHeapLayout); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + result = ma_spatializer_validate_config(pConfig); + if (result != MA_SUCCESS) { + return result; + } + + pHeapLayout->sizeInBytes = 0; + + /* Channel map. */ + pHeapLayout->channelMapInOffset = MA_SIZE_MAX; /* <-- MA_SIZE_MAX indicates no allocation necessary. */ + if (pConfig->pChannelMapIn != NULL) { + pHeapLayout->channelMapInOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += ma_align_64(sizeof(*pConfig->pChannelMapIn) * pConfig->channelsIn); + } + + /* New channel gains for output. */ + pHeapLayout->newChannelGainsOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += ma_align_64(sizeof(float) * pConfig->channelsOut); + + /* Gainer. */ + { + size_t gainerHeapSizeInBytes; + ma_gainer_config gainerConfig; + + gainerConfig = ma_spatializer_gainer_config_init(pConfig); + + result = ma_gainer_get_heap_size(&gainerConfig, &gainerHeapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + pHeapLayout->gainerOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += ma_align_64(gainerHeapSizeInBytes); + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_spatializer_get_heap_size(const ma_spatializer_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_result result; + ma_spatializer_heap_layout heapLayout; + + if (pHeapSizeInBytes == NULL) { + return MA_INVALID_ARGS; + } + + *pHeapSizeInBytes = 0; /* Safety. */ + + result = ma_spatializer_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + *pHeapSizeInBytes = heapLayout.sizeInBytes; + + return MA_SUCCESS; +} + + +MA_API ma_result ma_spatializer_init_preallocated(const ma_spatializer_config* pConfig, void* pHeap, ma_spatializer* pSpatializer) +{ + ma_result result; + ma_spatializer_heap_layout heapLayout; + ma_gainer_config gainerConfig; + + if (pSpatializer == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pSpatializer); + + if (pConfig == NULL || pHeap == NULL) { + return MA_INVALID_ARGS; + } + + result = ma_spatializer_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + pSpatializer->_pHeap = pHeap; + MA_ZERO_MEMORY(pHeap, heapLayout.sizeInBytes); + + pSpatializer->channelsIn = pConfig->channelsIn; + pSpatializer->channelsOut = pConfig->channelsOut; + pSpatializer->attenuationModel = pConfig->attenuationModel; + pSpatializer->positioning = pConfig->positioning; + pSpatializer->handedness = pConfig->handedness; + pSpatializer->minGain = pConfig->minGain; + pSpatializer->maxGain = pConfig->maxGain; + pSpatializer->minDistance = pConfig->minDistance; + pSpatializer->maxDistance = pConfig->maxDistance; + pSpatializer->rolloff = pConfig->rolloff; + pSpatializer->coneInnerAngleInRadians = pConfig->coneInnerAngleInRadians; + pSpatializer->coneOuterAngleInRadians = pConfig->coneOuterAngleInRadians; + pSpatializer->coneOuterGain = pConfig->coneOuterGain; + pSpatializer->dopplerFactor = pConfig->dopplerFactor; + pSpatializer->directionalAttenuationFactor = pConfig->directionalAttenuationFactor; + pSpatializer->gainSmoothTimeInFrames = pConfig->gainSmoothTimeInFrames; + pSpatializer->position = ma_vec3f_init_3f(0, 0, 0); + pSpatializer->direction = ma_vec3f_init_3f(0, 0, -1); + pSpatializer->velocity = ma_vec3f_init_3f(0, 0, 0); + pSpatializer->dopplerPitch = 1; + + /* Swap the forward direction if we're left handed (it was initialized based on right handed). */ + if (pSpatializer->handedness == ma_handedness_left) { + pSpatializer->direction = ma_vec3f_neg(pSpatializer->direction); + } + + /* Channel map. This will be on the heap. */ + if (pConfig->pChannelMapIn != NULL) { + pSpatializer->pChannelMapIn = (ma_channel*)ma_offset_ptr(pHeap, heapLayout.channelMapInOffset); + ma_channel_map_copy_or_default(pSpatializer->pChannelMapIn, pSpatializer->channelsIn, pConfig->pChannelMapIn, pSpatializer->channelsIn); + } + + /* New channel gains for output channels. */ + pSpatializer->pNewChannelGainsOut = (float*)ma_offset_ptr(pHeap, heapLayout.newChannelGainsOffset); + + /* Gainer. */ + gainerConfig = ma_spatializer_gainer_config_init(pConfig); + + result = ma_gainer_init_preallocated(&gainerConfig, ma_offset_ptr(pHeap, heapLayout.gainerOffset), &pSpatializer->gainer); + if (result != MA_SUCCESS) { + return result; /* Failed to initialize the gainer. */ + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_spatializer_init(const ma_spatializer_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_spatializer* pSpatializer) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + /* We'll need a heap allocation to retrieve the size. */ + result = ma_spatializer_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_spatializer_init_preallocated(pConfig, pHeap, pSpatializer); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pSpatializer->_ownsHeap = MA_TRUE; + return MA_SUCCESS; +} + +MA_API void ma_spatializer_uninit(ma_spatializer* pSpatializer, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pSpatializer == NULL) { + return; + } + + ma_gainer_uninit(&pSpatializer->gainer, pAllocationCallbacks); + + if (pSpatializer->_ownsHeap) { + ma_free(pSpatializer->_pHeap, pAllocationCallbacks); + } +} + +static float ma_calculate_angular_gain(ma_vec3f dirA, ma_vec3f dirB, float coneInnerAngleInRadians, float coneOuterAngleInRadians, float coneOuterGain) +{ + /* + Angular attenuation. + + Unlike distance gain, the math for this is not specified by the OpenAL spec so we'll just go ahead and figure + this out for ourselves at the expense of possibly being inconsistent with other implementations. + + To do cone attenuation, I'm just using the same math that we'd use to implement a basic spotlight in OpenGL. We + just need to get the direction from the source to the listener and then do a dot product against that and the + direction of the spotlight. Then we just compare that dot product against the cosine of the inner and outer + angles. If the dot product is greater than the the outer angle, we just use coneOuterGain. If it's less than + the inner angle, we just use a gain of 1. Otherwise we linearly interpolate between 1 and coneOuterGain. + */ + if (coneInnerAngleInRadians < 6.283185f) { + float angularGain = 1; + float cutoffInner = (float)ma_cosd(coneInnerAngleInRadians*0.5f); + float cutoffOuter = (float)ma_cosd(coneOuterAngleInRadians*0.5f); + float d; + + d = ma_vec3f_dot(dirA, dirB); + + if (d > cutoffInner) { + /* It's inside the inner angle. */ + angularGain = 1; + } else { + /* It's outside the inner angle. */ + if (d > cutoffOuter) { + /* It's between the inner and outer angle. We need to linearly interpolate between 1 and coneOuterGain. */ + angularGain = ma_mix_f32(coneOuterGain, 1, (d - cutoffOuter) / (cutoffInner - cutoffOuter)); + } else { + /* It's outside the outer angle. */ + angularGain = coneOuterGain; + } + } + + /*printf("d = %f; cutoffInner = %f; cutoffOuter = %f; angularGain = %f\n", d, cutoffInner, cutoffOuter, angularGain);*/ + return angularGain; + } else { + /* Inner angle is 360 degrees so no need to do any attenuation. */ + return 1; + } +} + +MA_API ma_result ma_spatializer_process_pcm_frames(ma_spatializer* pSpatializer, ma_spatializer_listener* pListener, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount) +{ + ma_channel* pChannelMapIn = pSpatializer->pChannelMapIn; + ma_channel* pChannelMapOut = pListener->config.pChannelMapOut; + + if (pSpatializer == NULL) { + return MA_INVALID_ARGS; + } + + /* If we're not spatializing we need to run an optimized path. */ + if (c89atomic_load_i32(&pSpatializer->attenuationModel) == ma_attenuation_model_none) { + if (ma_spatializer_listener_is_enabled(pListener)) { + /* No attenuation is required, but we'll need to do some channel conversion. */ + if (pSpatializer->channelsIn == pSpatializer->channelsOut) { + ma_copy_pcm_frames(pFramesOut, pFramesIn, frameCount, ma_format_f32, pSpatializer->channelsIn); + } else { + ma_channel_map_apply_f32((float*)pFramesOut, pChannelMapOut, pSpatializer->channelsOut, (const float*)pFramesIn, pChannelMapIn, pSpatializer->channelsIn, frameCount, ma_channel_mix_mode_rectangular, ma_mono_expansion_mode_default); /* Safe casts to float* because f32 is the only supported format. */ + } + } else { + /* The listener is disabled. Output silence. */ + ma_silence_pcm_frames(pFramesOut, frameCount, ma_format_f32, pSpatializer->channelsOut); + } + + /* + We're not doing attenuation so don't bother with doppler for now. I'm not sure if this is + the correct thinking so might need to review this later. + */ + pSpatializer->dopplerPitch = 1; + } else { + /* + Let's first determine which listener the sound is closest to. Need to keep in mind that we + might not have a world or any listeners, in which case we just spatializer based on the + listener being positioned at the origin (0, 0, 0). + */ + ma_vec3f relativePosNormalized; + ma_vec3f relativePos; /* The position relative to the listener. */ + ma_vec3f relativeDir; /* The direction of the sound, relative to the listener. */ + ma_vec3f listenerVel; /* The volocity of the listener. For doppler pitch calculation. */ + float speedOfSound; + float distance = 0; + float gain = 1; + ma_uint32 iChannel; + const ma_uint32 channelsOut = pSpatializer->channelsOut; + const ma_uint32 channelsIn = pSpatializer->channelsIn; + float minDistance = ma_spatializer_get_min_distance(pSpatializer); + float maxDistance = ma_spatializer_get_max_distance(pSpatializer); + float rolloff = ma_spatializer_get_rolloff(pSpatializer); + float dopplerFactor = ma_spatializer_get_doppler_factor(pSpatializer); + + /* + We'll need the listener velocity for doppler pitch calculations. The speed of sound is + defined by the listener, so we'll grab that here too. + */ + if (pListener != NULL) { + listenerVel = pListener->velocity; + speedOfSound = pListener->config.speedOfSound; + } else { + listenerVel = ma_vec3f_init_3f(0, 0, 0); + speedOfSound = MA_DEFAULT_SPEED_OF_SOUND; + } + + if (pListener == NULL || ma_spatializer_get_positioning(pSpatializer) == ma_positioning_relative) { + /* There's no listener or we're using relative positioning. */ + relativePos = pSpatializer->position; + relativeDir = pSpatializer->direction; + } else { + /* + We've found a listener and we're using absolute positioning. We need to transform the + sound's position and direction so that it's relative to listener. Later on we'll use + this for determining the factors to apply to each channel to apply the panning effect. + */ + ma_spatializer_get_relative_position_and_direction(pSpatializer, pListener, &relativePos, &relativeDir); + } + + distance = ma_vec3f_len(relativePos); + + /* We've gathered the data, so now we can apply some spatialization. */ + switch (ma_spatializer_get_attenuation_model(pSpatializer)) { + case ma_attenuation_model_inverse: + { + gain = ma_attenuation_inverse(distance, minDistance, maxDistance, rolloff); + } break; + case ma_attenuation_model_linear: + { + gain = ma_attenuation_linear(distance, minDistance, maxDistance, rolloff); + } break; + case ma_attenuation_model_exponential: + { + gain = ma_attenuation_exponential(distance, minDistance, maxDistance, rolloff); + } break; + case ma_attenuation_model_none: + default: + { + gain = 1; + } break; + } + + /* Normalize the position. */ + if (distance > 0.001f) { + float distanceInv = 1/distance; + relativePosNormalized = relativePos; + relativePosNormalized.x *= distanceInv; + relativePosNormalized.y *= distanceInv; + relativePosNormalized.z *= distanceInv; + } else { + distance = 0; + relativePosNormalized = ma_vec3f_init_3f(0, 0, 0); + } + + /* + Angular attenuation. + + Unlike distance gain, the math for this is not specified by the OpenAL spec so we'll just go ahead and figure + this out for ourselves at the expense of possibly being inconsistent with other implementations. + + To do cone attenuation, I'm just using the same math that we'd use to implement a basic spotlight in OpenGL. We + just need to get the direction from the source to the listener and then do a dot product against that and the + direction of the spotlight. Then we just compare that dot product against the cosine of the inner and outer + angles. If the dot product is greater than the the outer angle, we just use coneOuterGain. If it's less than + the inner angle, we just use a gain of 1. Otherwise we linearly interpolate between 1 and coneOuterGain. + */ + if (distance > 0) { + /* Source anglular gain. */ + float spatializerConeInnerAngle; + float spatializerConeOuterAngle; + float spatializerConeOuterGain; + ma_spatializer_get_cone(pSpatializer, &spatializerConeInnerAngle, &spatializerConeOuterAngle, &spatializerConeOuterGain); + + gain *= ma_calculate_angular_gain(relativeDir, ma_vec3f_neg(relativePosNormalized), spatializerConeInnerAngle, spatializerConeOuterAngle, spatializerConeOuterGain); + + /* + We're supporting angular gain on the listener as well for those who want to reduce the volume of sounds that + are positioned behind the listener. On default settings, this will have no effect. + */ + if (pListener != NULL && pListener->config.coneInnerAngleInRadians < 6.283185f) { + ma_vec3f listenerDirection; + float listenerInnerAngle; + float listenerOuterAngle; + float listenerOuterGain; + + if (pListener->config.handedness == ma_handedness_right) { + listenerDirection = ma_vec3f_init_3f(0, 0, -1); + } else { + listenerDirection = ma_vec3f_init_3f(0, 0, +1); + } + + listenerInnerAngle = pListener->config.coneInnerAngleInRadians; + listenerOuterAngle = pListener->config.coneOuterAngleInRadians; + listenerOuterGain = pListener->config.coneOuterGain; + + gain *= ma_calculate_angular_gain(listenerDirection, relativePosNormalized, listenerInnerAngle, listenerOuterAngle, listenerOuterGain); + } + } else { + /* The sound is right on top of the listener. Don't do any angular attenuation. */ + } + + + /* Clamp the gain. */ + gain = ma_clamp(gain, ma_spatializer_get_min_gain(pSpatializer), ma_spatializer_get_max_gain(pSpatializer)); + + /* + Panning. This is where we'll apply the gain and convert to the output channel count. We have an optimized path for + when we're converting to a mono stream. In that case we don't really need to do any panning - we just apply the + gain to the final output. + */ + /*printf("distance=%f; gain=%f\n", distance, gain);*/ + + /* We must have a valid channel map here to ensure we spatialize properly. */ + MA_ASSERT(pChannelMapOut != NULL); + + /* + We're not converting to mono so we'll want to apply some panning. This is where the feeling of something being + to the left, right, infront or behind the listener is calculated. I'm just using a basic model here. Note that + the code below is not based on any specific algorithm. I'm just implementing this off the top of my head and + seeing how it goes. There might be better ways to do this. + + To determine the direction of the sound relative to a speaker I'm using dot products. Each speaker is given a + direction. For example, the left channel in a stereo system will be -1 on the X axis and the right channel will + be +1 on the X axis. A dot product is performed against the direction vector of the channel and the normalized + position of the sound. + */ + for (iChannel = 0; iChannel < channelsOut; iChannel += 1) { + pSpatializer->pNewChannelGainsOut[iChannel] = gain; + } + + /* + Convert to our output channel count. If the listener is disabled we just output silence here. We cannot ignore + the whole section of code here because we need to update some internal spatialization state. + */ + if (ma_spatializer_listener_is_enabled(pListener)) { + ma_channel_map_apply_f32((float*)pFramesOut, pChannelMapOut, channelsOut, (const float*)pFramesIn, pChannelMapIn, channelsIn, frameCount, ma_channel_mix_mode_rectangular, ma_mono_expansion_mode_default); + } else { + ma_silence_pcm_frames(pFramesOut, frameCount, ma_format_f32, pSpatializer->channelsOut); + } + + /* + Calculate our per-channel gains. We do this based on the normalized relative position of the sound and it's + relation to the direction of the channel. + */ + if (distance > 0) { + ma_vec3f unitPos = relativePos; + float distanceInv = 1/distance; + unitPos.x *= distanceInv; + unitPos.y *= distanceInv; + unitPos.z *= distanceInv; + + for (iChannel = 0; iChannel < channelsOut; iChannel += 1) { + ma_channel channelOut; + float d; + float dMin; + + channelOut = ma_channel_map_get_channel(pChannelMapOut, channelsOut, iChannel); + if (ma_is_spatial_channel_position(channelOut)) { + d = ma_mix_f32_fast(1, ma_vec3f_dot(unitPos, ma_get_channel_direction(channelOut)), ma_spatializer_get_directional_attenuation_factor(pSpatializer)); + } else { + d = 1; /* It's not a spatial channel so there's no real notion of direction. */ + } + + /* + In my testing, if the panning effect is too aggressive it makes spatialization feel uncomfortable. + The "dMin" variable below is used to control the aggressiveness of the panning effect. When set to + 0, panning will be most extreme and any sounds that are positioned on the opposite side of the + speaker will be completely silent from that speaker. Not only does this feel uncomfortable, it + doesn't even remotely represent the real world at all because sounds that come from your right side + are still clearly audible from your left side. Setting "dMin" to 1 will result in no panning at + all, which is also not ideal. By setting it to something greater than 0, the spatialization effect + becomes much less dramatic and a lot more bearable. + + Summary: 0 = more extreme panning; 1 = no panning. + */ + dMin = 0.2f; /* TODO: Consider making this configurable. */ + + /* + At this point, "d" will be positive if the sound is on the same side as the channel and negative if + it's on the opposite side. It will be in the range of -1..1. There's two ways I can think of to + calculate a panning value. The first is to simply convert it to 0..1, however this has a problem + which I'm not entirely happy with. Considering a stereo system, when a sound is positioned right + in front of the listener it'll result in each speaker getting a gain of 0.5. I don't know if I like + the idea of having a scaling factor of 0.5 being applied to a sound when it's sitting right in front + of the listener. I would intuitively expect that to be played at full volume, or close to it. + + The second idea I think of is to only apply a reduction in gain when the sound is on the opposite + side of the speaker. That is, reduce the gain only when the dot product is negative. The problem + with this is that there will not be any attenuation as the sound sweeps around the 180 degrees + where the dot product is positive. The idea with this option is that you leave the gain at 1 when + the sound is being played on the same side as the speaker and then you just reduce the volume when + the sound is on the other side. + + The summarize, I think the first option should give a better sense of spatialization, but the second + option is better for preserving the sound's power. + + UPDATE: In my testing, I find the first option to sound better. You can feel the sense of space a + bit better, but you can also hear the reduction in volume when it's right in front. + */ + #if 1 + { + /* + Scale the dot product from -1..1 to 0..1. Will result in a sound directly in front losing power + by being played at 0.5 gain. + */ + d = (d + 1) * 0.5f; /* -1..1 to 0..1 */ + d = ma_max(d, dMin); + pSpatializer->pNewChannelGainsOut[iChannel] *= d; + } + #else + { + /* + Only reduce the volume of the sound if it's on the opposite side. This path keeps the volume more + consistent, but comes at the expense of a worse sense of space and positioning. + */ + if (d < 0) { + d += 1; /* Move into the positive range. */ + d = ma_max(d, dMin); + channelGainsOut[iChannel] *= d; + } + } + #endif + } + } else { + /* Assume the sound is right on top of us. Don't do any panning. */ + } + + /* Now we need to apply the volume to each channel. This needs to run through the gainer to ensure we get a smooth volume transition. */ + ma_gainer_set_gains(&pSpatializer->gainer, pSpatializer->pNewChannelGainsOut); + ma_gainer_process_pcm_frames(&pSpatializer->gainer, pFramesOut, pFramesOut, frameCount); + + /* + Before leaving we'll want to update our doppler pitch so that the caller can apply some + pitch shifting if they desire. Note that we need to negate the relative position here + because the doppler calculation needs to be source-to-listener, but ours is listener-to- + source. + */ + if (dopplerFactor > 0) { + pSpatializer->dopplerPitch = ma_doppler_pitch(ma_vec3f_sub(pListener->position, pSpatializer->position), pSpatializer->velocity, listenerVel, speedOfSound, dopplerFactor); + } else { + pSpatializer->dopplerPitch = 1; + } + } + + return MA_SUCCESS; +} + +MA_API ma_uint32 ma_spatializer_get_input_channels(const ma_spatializer* pSpatializer) +{ + if (pSpatializer == NULL) { + return 0; + } + + return pSpatializer->channelsIn; +} + +MA_API ma_uint32 ma_spatializer_get_output_channels(const ma_spatializer* pSpatializer) +{ + if (pSpatializer == NULL) { + return 0; + } + + return pSpatializer->channelsOut; +} + +MA_API void ma_spatializer_set_attenuation_model(ma_spatializer* pSpatializer, ma_attenuation_model attenuationModel) +{ + if (pSpatializer == NULL) { + return; + } + + c89atomic_exchange_i32(&pSpatializer->attenuationModel, attenuationModel); +} + +MA_API ma_attenuation_model ma_spatializer_get_attenuation_model(const ma_spatializer* pSpatializer) +{ + if (pSpatializer == NULL) { + return ma_attenuation_model_none; + } + + return (ma_attenuation_model)c89atomic_load_i32(&pSpatializer->attenuationModel); +} + +MA_API void ma_spatializer_set_positioning(ma_spatializer* pSpatializer, ma_positioning positioning) +{ + if (pSpatializer == NULL) { + return; + } + + c89atomic_exchange_i32(&pSpatializer->positioning, positioning); +} + +MA_API ma_positioning ma_spatializer_get_positioning(const ma_spatializer* pSpatializer) +{ + if (pSpatializer == NULL) { + return ma_positioning_absolute; + } + + return (ma_positioning)c89atomic_load_i32(&pSpatializer->positioning); +} + +MA_API void ma_spatializer_set_rolloff(ma_spatializer* pSpatializer, float rolloff) +{ + if (pSpatializer == NULL) { + return; + } + + c89atomic_exchange_f32(&pSpatializer->rolloff, rolloff); +} + +MA_API float ma_spatializer_get_rolloff(const ma_spatializer* pSpatializer) +{ + if (pSpatializer == NULL) { + return 0; + } + + return c89atomic_load_f32(&pSpatializer->rolloff); +} + +MA_API void ma_spatializer_set_min_gain(ma_spatializer* pSpatializer, float minGain) +{ + if (pSpatializer == NULL) { + return; + } + + c89atomic_exchange_f32(&pSpatializer->minGain, minGain); +} + +MA_API float ma_spatializer_get_min_gain(const ma_spatializer* pSpatializer) +{ + if (pSpatializer == NULL) { + return 0; + } + + return c89atomic_load_f32(&pSpatializer->minGain); +} + +MA_API void ma_spatializer_set_max_gain(ma_spatializer* pSpatializer, float maxGain) +{ + if (pSpatializer == NULL) { + return; + } + + c89atomic_exchange_f32(&pSpatializer->maxGain, maxGain); +} + +MA_API float ma_spatializer_get_max_gain(const ma_spatializer* pSpatializer) +{ + if (pSpatializer == NULL) { + return 0; + } + + return c89atomic_load_f32(&pSpatializer->maxGain); +} + +MA_API void ma_spatializer_set_min_distance(ma_spatializer* pSpatializer, float minDistance) +{ + if (pSpatializer == NULL) { + return; + } + + c89atomic_exchange_f32(&pSpatializer->minDistance, minDistance); +} + +MA_API float ma_spatializer_get_min_distance(const ma_spatializer* pSpatializer) +{ + if (pSpatializer == NULL) { + return 0; + } + + return c89atomic_load_f32(&pSpatializer->minDistance); +} + +MA_API void ma_spatializer_set_max_distance(ma_spatializer* pSpatializer, float maxDistance) +{ + if (pSpatializer == NULL) { + return; + } + + c89atomic_exchange_f32(&pSpatializer->maxDistance, maxDistance); +} + +MA_API float ma_spatializer_get_max_distance(const ma_spatializer* pSpatializer) +{ + if (pSpatializer == NULL) { + return 0; + } + + return c89atomic_load_f32(&pSpatializer->maxDistance); +} + +MA_API void ma_spatializer_set_cone(ma_spatializer* pSpatializer, float innerAngleInRadians, float outerAngleInRadians, float outerGain) +{ + if (pSpatializer == NULL) { + return; + } + + c89atomic_exchange_f32(&pSpatializer->coneInnerAngleInRadians, innerAngleInRadians); + c89atomic_exchange_f32(&pSpatializer->coneOuterAngleInRadians, outerAngleInRadians); + c89atomic_exchange_f32(&pSpatializer->coneOuterGain, outerGain); +} + +MA_API void ma_spatializer_get_cone(const ma_spatializer* pSpatializer, float* pInnerAngleInRadians, float* pOuterAngleInRadians, float* pOuterGain) +{ + if (pSpatializer == NULL) { + return; + } + + if (pInnerAngleInRadians != NULL) { + *pInnerAngleInRadians = c89atomic_load_f32(&pSpatializer->coneInnerAngleInRadians); + } + + if (pOuterAngleInRadians != NULL) { + *pOuterAngleInRadians = c89atomic_load_f32(&pSpatializer->coneOuterAngleInRadians); + } + + if (pOuterGain != NULL) { + *pOuterGain = c89atomic_load_f32(&pSpatializer->coneOuterGain); + } +} + +MA_API void ma_spatializer_set_doppler_factor(ma_spatializer* pSpatializer, float dopplerFactor) +{ + if (pSpatializer == NULL) { + return; + } + + c89atomic_exchange_f32(&pSpatializer->dopplerFactor, dopplerFactor); +} + +MA_API float ma_spatializer_get_doppler_factor(const ma_spatializer* pSpatializer) +{ + if (pSpatializer == NULL) { + return 1; + } + + return c89atomic_load_f32(&pSpatializer->dopplerFactor); +} + +MA_API void ma_spatializer_set_directional_attenuation_factor(ma_spatializer* pSpatializer, float directionalAttenuationFactor) +{ + if (pSpatializer == NULL) { + return; + } + + c89atomic_exchange_f32(&pSpatializer->directionalAttenuationFactor, directionalAttenuationFactor); +} + +MA_API float ma_spatializer_get_directional_attenuation_factor(const ma_spatializer* pSpatializer) +{ + if (pSpatializer == NULL) { + return 1; + } + + return c89atomic_load_f32(&pSpatializer->directionalAttenuationFactor); +} + +MA_API void ma_spatializer_set_position(ma_spatializer* pSpatializer, float x, float y, float z) +{ + if (pSpatializer == NULL) { + return; + } + + pSpatializer->position = ma_vec3f_init_3f(x, y, z); +} + +MA_API ma_vec3f ma_spatializer_get_position(const ma_spatializer* pSpatializer) +{ + if (pSpatializer == NULL) { + return ma_vec3f_init_3f(0, 0, 0); + } + + return pSpatializer->position; +} + +MA_API void ma_spatializer_set_direction(ma_spatializer* pSpatializer, float x, float y, float z) +{ + if (pSpatializer == NULL) { + return; + } + + pSpatializer->direction = ma_vec3f_init_3f(x, y, z); +} + +MA_API ma_vec3f ma_spatializer_get_direction(const ma_spatializer* pSpatializer) +{ + if (pSpatializer == NULL) { + return ma_vec3f_init_3f(0, 0, -1); + } + + return pSpatializer->direction; +} + +MA_API void ma_spatializer_set_velocity(ma_spatializer* pSpatializer, float x, float y, float z) +{ + if (pSpatializer == NULL) { + return; + } + + pSpatializer->velocity = ma_vec3f_init_3f(x, y, z); +} + +MA_API ma_vec3f ma_spatializer_get_velocity(const ma_spatializer* pSpatializer) +{ + if (pSpatializer == NULL) { + return ma_vec3f_init_3f(0, 0, 0); + } + + return pSpatializer->velocity; +} + +MA_API void ma_spatializer_get_relative_position_and_direction(const ma_spatializer* pSpatializer, const ma_spatializer_listener* pListener, ma_vec3f* pRelativePos, ma_vec3f* pRelativeDir) +{ + if (pRelativePos != NULL) { + pRelativePos->x = 0; + pRelativePos->y = 0; + pRelativePos->z = 0; + } + + if (pRelativeDir != NULL) { + pRelativeDir->x = 0; + pRelativeDir->y = 0; + pRelativeDir->z = -1; + } + + if (pSpatializer == NULL) { + return; + } + + if (pListener == NULL || ma_spatializer_get_positioning(pSpatializer) == ma_positioning_relative) { + /* There's no listener or we're using relative positioning. */ + if (pRelativePos != NULL) { + *pRelativePos = pSpatializer->position; + } + if (pRelativeDir != NULL) { + *pRelativeDir = pSpatializer->direction; + } + } else { + ma_vec3f v; + ma_vec3f axisX; + ma_vec3f axisY; + ma_vec3f axisZ; + float m[4][4]; + + /* + We need to calcualte the right vector from our forward and up vectors. This is done with + a cross product. + */ + axisZ = ma_vec3f_normalize(pListener->direction); /* Normalization required here because we can't trust the caller. */ + axisX = ma_vec3f_normalize(ma_vec3f_cross(axisZ, pListener->config.worldUp)); /* Normalization required here because the world up vector may not be perpendicular with the forward vector. */ + + /* + The calculation of axisX above can result in a zero-length vector if the listener is + looking straight up on the Y axis. We'll need to fall back to a +X in this case so that + the calculations below don't fall apart. This is where a quaternion based listener and + sound orientation would come in handy. + */ + if (ma_vec3f_len2(axisX) == 0) { + axisX = ma_vec3f_init_3f(1, 0, 0); + } + + axisY = ma_vec3f_cross(axisX, axisZ); /* No normalization is required here because axisX and axisZ are unit length and perpendicular. */ + + /* + We need to swap the X axis if we're left handed because otherwise the cross product above + will have resulted in it pointing in the wrong direction (right handed was assumed in the + cross products above). + */ + if (pListener->config.handedness == ma_handedness_left) { + axisX = ma_vec3f_neg(axisX); + } + + /* Lookat. */ + m[0][0] = axisX.x; m[1][0] = axisX.y; m[2][0] = axisX.z; m[3][0] = -ma_vec3f_dot(axisX, pListener->position); + m[0][1] = axisY.x; m[1][1] = axisY.y; m[2][1] = axisY.z; m[3][1] = -ma_vec3f_dot(axisY, pListener->position); + m[0][2] = -axisZ.x; m[1][2] = -axisZ.y; m[2][2] = -axisZ.z; m[3][2] = -ma_vec3f_dot(ma_vec3f_neg(axisZ), pListener->position); + m[0][3] = 0; m[1][3] = 0; m[2][3] = 0; m[3][3] = 1; + + /* + Multiply the lookat matrix by the spatializer position to transform it to listener + space. This allows calculations to work based on the sound being relative to the + origin which makes things simpler. + */ + if (pRelativePos != NULL) { + v = pSpatializer->position; + pRelativePos->x = m[0][0] * v.x + m[1][0] * v.y + m[2][0] * v.z + m[3][0] * 1; + pRelativePos->y = m[0][1] * v.x + m[1][1] * v.y + m[2][1] * v.z + m[3][1] * 1; + pRelativePos->z = m[0][2] * v.x + m[1][2] * v.y + m[2][2] * v.z + m[3][2] * 1; + } + + /* + The direction of the sound needs to also be transformed so that it's relative to the + rotation of the listener. + */ + if (pRelativeDir != NULL) { + v = pSpatializer->direction; + pRelativeDir->x = m[0][0] * v.x + m[1][0] * v.y + m[2][0] * v.z; + pRelativeDir->y = m[0][1] * v.x + m[1][1] * v.y + m[2][1] * v.z; + pRelativeDir->z = m[0][2] * v.x + m[1][2] * v.y + m[2][2] * v.z; + } + } +} + + + + /************************************************************************************************************************************************************** Resampling @@ -38994,6 +49032,16 @@ MA_API ma_linear_resampler_config ma_linear_resampler_config_init(ma_format form return config; } + +typedef struct +{ + size_t sizeInBytes; + size_t x0Offset; + size_t x1Offset; + size_t lpfOffset; +} ma_linear_resampler_heap_layout; + + static void ma_linear_resampler_adjust_timer_for_new_rate(ma_linear_resampler* pResampler, ma_uint32 oldSampleRateOut, ma_uint32 newSampleRateOut) { /* @@ -39012,7 +49060,7 @@ static void ma_linear_resampler_adjust_timer_for_new_rate(ma_linear_resampler* p pResampler->inTimeFrac = pResampler->inTimeFrac % pResampler->config.sampleRateOut; } -static ma_result ma_linear_resampler_set_rate_internal(ma_linear_resampler* pResampler, ma_uint32 sampleRateIn, ma_uint32 sampleRateOut, ma_bool32 isResamplerAlreadyInitialized) +static ma_result ma_linear_resampler_set_rate_internal(ma_linear_resampler* pResampler, void* pHeap, ma_linear_resampler_heap_layout* pHeapLayout, ma_uint32 sampleRateIn, ma_uint32 sampleRateOut, ma_bool32 isResamplerAlreadyInitialized) { ma_result result; ma_uint32 gcf; @@ -39056,7 +49104,7 @@ static ma_result ma_linear_resampler_set_rate_internal(ma_linear_resampler* pRes if (isResamplerAlreadyInitialized) { result = ma_lpf_reinit(&lpfConfig, &pResampler->lpf); } else { - result = ma_lpf_init(&lpfConfig, &pResampler->lpf); + result = ma_lpf_init_preallocated(&lpfConfig, ma_offset_ptr(pHeap, pHeapLayout->lpfOffset), &pResampler->lpf); } if (result != MA_SUCCESS) { @@ -39073,9 +49121,88 @@ static ma_result ma_linear_resampler_set_rate_internal(ma_linear_resampler* pRes return MA_SUCCESS; } -MA_API ma_result ma_linear_resampler_init(const ma_linear_resampler_config* pConfig, ma_linear_resampler* pResampler) +static ma_result ma_linear_resampler_get_heap_layout(const ma_linear_resampler_config* pConfig, ma_linear_resampler_heap_layout* pHeapLayout) +{ + MA_ASSERT(pHeapLayout != NULL); + + MA_ZERO_OBJECT(pHeapLayout); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->format != ma_format_f32 && pConfig->format != ma_format_s16) { + return MA_INVALID_ARGS; + } + + if (pConfig->channels == 0) { + return MA_INVALID_ARGS; + } + + pHeapLayout->sizeInBytes = 0; + + /* x0 */ + pHeapLayout->x0Offset = pHeapLayout->sizeInBytes; + if (pConfig->format == ma_format_f32) { + pHeapLayout->sizeInBytes += sizeof(float) * pConfig->channels; + } else { + pHeapLayout->sizeInBytes += sizeof(ma_int16) * pConfig->channels; + } + + /* x1 */ + pHeapLayout->x1Offset = pHeapLayout->sizeInBytes; + if (pConfig->format == ma_format_f32) { + pHeapLayout->sizeInBytes += sizeof(float) * pConfig->channels; + } else { + pHeapLayout->sizeInBytes += sizeof(ma_int16) * pConfig->channels; + } + + /* LPF */ + pHeapLayout->lpfOffset = pHeapLayout->sizeInBytes; + { + ma_result result; + size_t lpfHeapSizeInBytes; + ma_lpf_config lpfConfig = ma_lpf_config_init(pConfig->format, pConfig->channels, 1, 1, pConfig->lpfOrder); /* Sample rate and cutoff frequency do not matter. */ + + result = ma_lpf_get_heap_size(&lpfConfig, &lpfHeapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + pHeapLayout->sizeInBytes += lpfHeapSizeInBytes; + } + + /* Make sure allocation size is aligned. */ + pHeapLayout->sizeInBytes = ma_align_64(pHeapLayout->sizeInBytes); + + return MA_SUCCESS; +} + +MA_API ma_result ma_linear_resampler_get_heap_size(const ma_linear_resampler_config* pConfig, size_t* pHeapSizeInBytes) { ma_result result; + ma_linear_resampler_heap_layout heapLayout; + + if (pHeapSizeInBytes == NULL) { + return MA_INVALID_ARGS; + } + + *pHeapSizeInBytes = 0; + + result = ma_linear_resampler_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + *pHeapSizeInBytes = heapLayout.sizeInBytes; + + return MA_SUCCESS; +} + +MA_API ma_result ma_linear_resampler_init_preallocated(const ma_linear_resampler_config* pConfig, void* pHeap, ma_linear_resampler* pResampler) +{ + ma_result result; + ma_linear_resampler_heap_layout heapLayout; if (pResampler == NULL) { return MA_INVALID_ARGS; @@ -39083,18 +49210,26 @@ MA_API ma_result ma_linear_resampler_init(const ma_linear_resampler_config* pCon MA_ZERO_OBJECT(pResampler); - if (pConfig == NULL) { - return MA_INVALID_ARGS; - } - - if (pConfig->channels < MA_MIN_CHANNELS || pConfig->channels > MA_MAX_CHANNELS) { - return MA_INVALID_ARGS; + result = ma_linear_resampler_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; } pResampler->config = *pConfig; + pResampler->_pHeap = pHeap; + MA_ZERO_MEMORY(pHeap, heapLayout.sizeInBytes); + + if (pConfig->format == ma_format_f32) { + pResampler->x0.f32 = (float*)ma_offset_ptr(pHeap, heapLayout.x0Offset); + pResampler->x1.f32 = (float*)ma_offset_ptr(pHeap, heapLayout.x1Offset); + } else { + pResampler->x0.s16 = (ma_int16*)ma_offset_ptr(pHeap, heapLayout.x0Offset); + pResampler->x1.s16 = (ma_int16*)ma_offset_ptr(pHeap, heapLayout.x1Offset); + } + /* Setting the rate will set up the filter and time advances for us. */ - result = ma_linear_resampler_set_rate_internal(pResampler, pConfig->sampleRateIn, pConfig->sampleRateOut, /* isResamplerAlreadyInitialized = */ MA_FALSE); + result = ma_linear_resampler_set_rate_internal(pResampler, pHeap, &heapLayout, pConfig->sampleRateIn, pConfig->sampleRateOut, /* isResamplerAlreadyInitialized = */ MA_FALSE); if (result != MA_SUCCESS) { return result; } @@ -39105,11 +49240,47 @@ MA_API ma_result ma_linear_resampler_init(const ma_linear_resampler_config* pCon return MA_SUCCESS; } -MA_API void ma_linear_resampler_uninit(ma_linear_resampler* pResampler) +MA_API ma_result ma_linear_resampler_init(const ma_linear_resampler_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_linear_resampler* pResampler) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_linear_resampler_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_linear_resampler_init_preallocated(pConfig, pHeap, pResampler); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pResampler->_ownsHeap = MA_TRUE; + return MA_SUCCESS; +} + +MA_API void ma_linear_resampler_uninit(ma_linear_resampler* pResampler, const ma_allocation_callbacks* pAllocationCallbacks) { if (pResampler == NULL) { return; } + + ma_lpf_uninit(&pResampler->lpf, pAllocationCallbacks); + + if (pResampler->_ownsHeap) { + ma_free(pResampler->_pHeap, pAllocationCallbacks); + } } static MA_INLINE ma_int16 ma_linear_resampler_mix_s16(ma_int16 x, ma_int16 y, ma_int32 a, const ma_int32 shift) @@ -39139,7 +49310,7 @@ static void ma_linear_resampler_interpolate_frame_s16(ma_linear_resampler* pResa a = (pResampler->inTimeFrac << shift) / pResampler->config.sampleRateOut; - MA_ASSUME(channels >= MA_MIN_CHANNELS && channels <= MA_MAX_CHANNELS); + MA_ASSUME(channels > 0); for (c = 0; c < channels; c += 1) { ma_int16 s = ma_linear_resampler_mix_s16(pResampler->x0.s16[c], pResampler->x1.s16[c], a, shift); pFrameOut[c] = s; @@ -39158,7 +49329,7 @@ static void ma_linear_resampler_interpolate_frame_f32(ma_linear_resampler* pResa a = (float)pResampler->inTimeFrac / pResampler->config.sampleRateOut; - MA_ASSUME(channels >= MA_MIN_CHANNELS && channels <= MA_MAX_CHANNELS); + MA_ASSUME(channels > 0); for (c = 0; c < channels; c += 1) { float s = ma_mix_f32_fast(pResampler->x0.f32[c], pResampler->x1.f32[c], a); pFrameOut[c] = s; @@ -39505,7 +49676,7 @@ MA_API ma_result ma_linear_resampler_process_pcm_frames(ma_linear_resampler* pRe MA_API ma_result ma_linear_resampler_set_rate(ma_linear_resampler* pResampler, ma_uint32 sampleRateIn, ma_uint32 sampleRateOut) { - return ma_linear_resampler_set_rate_internal(pResampler, sampleRateIn, sampleRateOut, /* isResamplerAlreadyInitialized = */ MA_TRUE); + return ma_linear_resampler_set_rate_internal(pResampler, NULL, NULL, sampleRateIn, sampleRateOut, /* isResamplerAlreadyInitialized = */ MA_TRUE); } MA_API ma_result ma_linear_resampler_set_rate_ratio(ma_linear_resampler* pResampler, float ratioInOut) @@ -39513,6 +49684,14 @@ MA_API ma_result ma_linear_resampler_set_rate_ratio(ma_linear_resampler* pResamp ma_uint32 n; ma_uint32 d; + if (pResampler == NULL) { + return MA_INVALID_ARGS; + } + + if (ratioInOut <= 0) { + return MA_INVALID_ARGS; + } + d = 1000; n = (ma_uint32)(ratioInOut * d); @@ -39525,19 +49704,42 @@ MA_API ma_result ma_linear_resampler_set_rate_ratio(ma_linear_resampler* pResamp return ma_linear_resampler_set_rate(pResampler, n, d); } - -MA_API ma_uint64 ma_linear_resampler_get_required_input_frame_count(const ma_linear_resampler* pResampler, ma_uint64 outputFrameCount) +MA_API ma_uint64 ma_linear_resampler_get_input_latency(const ma_linear_resampler* pResampler) { - ma_uint64 inputFrameCount; - if (pResampler == NULL) { return 0; } - if (outputFrameCount == 0) { + return 1 + ma_lpf_get_latency(&pResampler->lpf); +} + +MA_API ma_uint64 ma_linear_resampler_get_output_latency(const ma_linear_resampler* pResampler) +{ + if (pResampler == NULL) { return 0; } + return ma_linear_resampler_get_input_latency(pResampler) * pResampler->config.sampleRateOut / pResampler->config.sampleRateIn; +} + +MA_API ma_result ma_linear_resampler_get_required_input_frame_count(const ma_linear_resampler* pResampler, ma_uint64 outputFrameCount, ma_uint64* pInputFrameCount) +{ + ma_uint64 inputFrameCount; + + if (pInputFrameCount == NULL) { + return MA_INVALID_ARGS; + } + + *pInputFrameCount = 0; + + if (pResampler == NULL) { + return MA_INVALID_ARGS; + } + + if (outputFrameCount == 0) { + return MA_SUCCESS; + } + /* Any whole input frames are consumed before the first output frame is generated. */ inputFrameCount = pResampler->inTimeInt; outputFrameCount -= 1; @@ -39546,17 +49748,25 @@ MA_API ma_uint64 ma_linear_resampler_get_required_input_frame_count(const ma_lin inputFrameCount += outputFrameCount * pResampler->inAdvanceInt; inputFrameCount += (pResampler->inTimeFrac + (outputFrameCount * pResampler->inAdvanceFrac)) / pResampler->config.sampleRateOut; - return inputFrameCount; + *pInputFrameCount = inputFrameCount; + + return MA_SUCCESS; } -MA_API ma_uint64 ma_linear_resampler_get_expected_output_frame_count(const ma_linear_resampler* pResampler, ma_uint64 inputFrameCount) +MA_API ma_result ma_linear_resampler_get_expected_output_frame_count(const ma_linear_resampler* pResampler, ma_uint64 inputFrameCount, ma_uint64* pOutputFrameCount) { ma_uint64 outputFrameCount; ma_uint64 preliminaryInputFrameCountFromFrac; ma_uint64 preliminaryInputFrameCount; + if (pOutputFrameCount == NULL) { + return MA_INVALID_ARGS; + } + + *pOutputFrameCount = 0; + if (pResampler == NULL) { - return 0; + return MA_INVALID_ARGS; } /* @@ -39583,45 +49793,157 @@ MA_API ma_uint64 ma_linear_resampler_get_expected_output_frame_count(const ma_li outputFrameCount += 1; } - return outputFrameCount; + *pOutputFrameCount = outputFrameCount; + + return MA_SUCCESS; } -MA_API ma_uint64 ma_linear_resampler_get_input_latency(const ma_linear_resampler* pResampler) +MA_API ma_result ma_linear_resampler_reset(ma_linear_resampler* pResampler) { + ma_uint32 iChannel; + if (pResampler == NULL) { - return 0; + return MA_INVALID_ARGS; } - return 1 + ma_lpf_get_latency(&pResampler->lpf); + /* Timers need to be cleared back to zero. */ + pResampler->inTimeInt = 1; /* Set this to one to force an input sample to always be loaded for the first output frame. */ + pResampler->inTimeFrac = 0; + + /* Cached samples need to be cleared. */ + if (pResampler->config.format == ma_format_f32) { + for (iChannel = 0; iChannel < pResampler->config.channels; iChannel += 1) { + pResampler->x0.f32[iChannel] = 0; + pResampler->x1.f32[iChannel] = 0; + } + } else { + for (iChannel = 0; iChannel < pResampler->config.channels; iChannel += 1) { + pResampler->x0.s16[iChannel] = 0; + pResampler->x1.s16[iChannel] = 0; + } + } + + /* The low pass filter needs to have it's cache reset. */ + ma_lpf_clear_cache(&pResampler->lpf); + + return MA_SUCCESS; } -MA_API ma_uint64 ma_linear_resampler_get_output_latency(const ma_linear_resampler* pResampler) + + +/* Linear resampler backend vtable. */ +static ma_linear_resampler_config ma_resampling_backend_get_config__linear(const ma_resampler_config* pConfig) { - if (pResampler == NULL) { - return 0; - } + ma_linear_resampler_config linearConfig; - return ma_linear_resampler_get_input_latency(pResampler) * pResampler->config.sampleRateOut / pResampler->config.sampleRateIn; + linearConfig = ma_linear_resampler_config_init(pConfig->format, pConfig->channels, pConfig->sampleRateIn, pConfig->sampleRateOut); + linearConfig.lpfOrder = pConfig->linear.lpfOrder; + + return linearConfig; } - -#if defined(ma_speex_resampler_h) -#define MA_HAS_SPEEX_RESAMPLER - -static ma_result ma_result_from_speex_err(int err) +static ma_result ma_resampling_backend_get_heap_size__linear(void* pUserData, const ma_resampler_config* pConfig, size_t* pHeapSizeInBytes) { - switch (err) - { - case RESAMPLER_ERR_SUCCESS: return MA_SUCCESS; - case RESAMPLER_ERR_ALLOC_FAILED: return MA_OUT_OF_MEMORY; - case RESAMPLER_ERR_BAD_STATE: return MA_ERROR; - case RESAMPLER_ERR_INVALID_ARG: return MA_INVALID_ARGS; - case RESAMPLER_ERR_PTR_OVERLAP: return MA_INVALID_ARGS; - case RESAMPLER_ERR_OVERFLOW: return MA_ERROR; - default: return MA_ERROR; - } + ma_linear_resampler_config linearConfig; + + (void)pUserData; + + linearConfig = ma_resampling_backend_get_config__linear(pConfig); + + return ma_linear_resampler_get_heap_size(&linearConfig, pHeapSizeInBytes); } -#endif /* ma_speex_resampler_h */ + +static ma_result ma_resampling_backend_init__linear(void* pUserData, const ma_resampler_config* pConfig, void* pHeap, ma_resampling_backend** ppBackend) +{ + ma_resampler* pResampler = (ma_resampler*)pUserData; + ma_result result; + ma_linear_resampler_config linearConfig; + + (void)pUserData; + + linearConfig = ma_resampling_backend_get_config__linear(pConfig); + + result = ma_linear_resampler_init_preallocated(&linearConfig, pHeap, &pResampler->state.linear); + if (result != MA_SUCCESS) { + return result; + } + + *ppBackend = &pResampler->state.linear; + + return MA_SUCCESS; +} + +static void ma_resampling_backend_uninit__linear(void* pUserData, ma_resampling_backend* pBackend, const ma_allocation_callbacks* pAllocationCallbacks) +{ + (void)pUserData; + + ma_linear_resampler_uninit((ma_linear_resampler*)pBackend, pAllocationCallbacks); +} + +static ma_result ma_resampling_backend_process__linear(void* pUserData, ma_resampling_backend* pBackend, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut) +{ + (void)pUserData; + + return ma_linear_resampler_process_pcm_frames((ma_linear_resampler*)pBackend, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); +} + +static ma_result ma_resampling_backend_set_rate__linear(void* pUserData, ma_resampling_backend* pBackend, ma_uint32 sampleRateIn, ma_uint32 sampleRateOut) +{ + (void)pUserData; + + return ma_linear_resampler_set_rate((ma_linear_resampler*)pBackend, sampleRateIn, sampleRateOut); +} + +static ma_uint64 ma_resampling_backend_get_input_latency__linear(void* pUserData, const ma_resampling_backend* pBackend) +{ + (void)pUserData; + + return ma_linear_resampler_get_input_latency((const ma_linear_resampler*)pBackend); +} + +static ma_uint64 ma_resampling_backend_get_output_latency__linear(void* pUserData, const ma_resampling_backend* pBackend) +{ + (void)pUserData; + + return ma_linear_resampler_get_output_latency((const ma_linear_resampler*)pBackend); +} + +static ma_result ma_resampling_backend_get_required_input_frame_count__linear(void* pUserData, const ma_resampling_backend* pBackend, ma_uint64 outputFrameCount, ma_uint64* pInputFrameCount) +{ + (void)pUserData; + + return ma_linear_resampler_get_required_input_frame_count((const ma_linear_resampler*)pBackend, outputFrameCount, pInputFrameCount); +} + +static ma_result ma_resampling_backend_get_expected_output_frame_count__linear(void* pUserData, const ma_resampling_backend* pBackend, ma_uint64 inputFrameCount, ma_uint64* pOutputFrameCount) +{ + (void)pUserData; + + return ma_linear_resampler_get_expected_output_frame_count((const ma_linear_resampler*)pBackend, inputFrameCount, pOutputFrameCount); +} + +static ma_result ma_resampling_backend_reset__linear(void* pUserData, ma_resampling_backend* pBackend) +{ + (void)pUserData; + + return ma_linear_resampler_reset((ma_linear_resampler*)pBackend); +} + +static ma_resampling_backend_vtable g_ma_linear_resampler_vtable = +{ + ma_resampling_backend_get_heap_size__linear, + ma_resampling_backend_init__linear, + ma_resampling_backend_uninit__linear, + ma_resampling_backend_process__linear, + ma_resampling_backend_set_rate__linear, + ma_resampling_backend_get_input_latency__linear, + ma_resampling_backend_get_output_latency__linear, + ma_resampling_backend_get_required_input_frame_count__linear, + ma_resampling_backend_get_expected_output_frame_count__linear, + ma_resampling_backend_reset__linear +}; + + MA_API ma_resampler_config ma_resampler_config_init(ma_format format, ma_uint32 channels, ma_uint32 sampleRateIn, ma_uint32 sampleRateOut, ma_resample_algorithm algorithm) { @@ -39636,15 +49958,74 @@ MA_API ma_resampler_config ma_resampler_config_init(ma_format format, ma_uint32 /* Linear. */ config.linear.lpfOrder = ma_min(MA_DEFAULT_RESAMPLER_LPF_ORDER, MA_MAX_FILTER_ORDER); - config.linear.lpfNyquistFactor = 1; - - /* Speex. */ - config.speex.quality = 3; /* Cannot leave this as 0 as that is actually a valid value for Speex resampling quality. */ return config; } -MA_API ma_result ma_resampler_init(const ma_resampler_config* pConfig, ma_resampler* pResampler) +static ma_result ma_resampler_get_vtable(const ma_resampler_config* pConfig, ma_resampler* pResampler, ma_resampling_backend_vtable** ppVTable, void** ppUserData) +{ + MA_ASSERT(pConfig != NULL); + MA_ASSERT(ppVTable != NULL); + MA_ASSERT(ppUserData != NULL); + + /* Safety. */ + *ppVTable = NULL; + *ppUserData = NULL; + + switch (pConfig->algorithm) + { + case ma_resample_algorithm_linear: + { + *ppVTable = &g_ma_linear_resampler_vtable; + *ppUserData = pResampler; + } break; + + case ma_resample_algorithm_custom: + { + *ppVTable = pConfig->pBackendVTable; + *ppUserData = pConfig->pBackendUserData; + } break; + + default: return MA_INVALID_ARGS; + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_resampler_get_heap_size(const ma_resampler_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_result result; + ma_resampling_backend_vtable* pVTable; + void* pVTableUserData; + + if (pHeapSizeInBytes == NULL) { + return MA_INVALID_ARGS; + } + + *pHeapSizeInBytes = 0; + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + result = ma_resampler_get_vtable(pConfig, NULL, &pVTable, &pVTableUserData); + if (result != MA_SUCCESS) { + return result; + } + + if (pVTable == NULL || pVTable->onGetHeapSize == NULL) { + return MA_NOT_IMPLEMENTED; + } + + result = pVTable->onGetHeapSize(pVTableUserData, pConfig, pHeapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_resampler_init_preallocated(const ma_resampler_config* pConfig, void* pHeap, ma_resampler* pResampler) { ma_result result; @@ -39658,291 +50039,75 @@ MA_API ma_result ma_resampler_init(const ma_resampler_config* pConfig, ma_resamp return MA_INVALID_ARGS; } - if (pConfig->format != ma_format_f32 && pConfig->format != ma_format_s16) { - return MA_INVALID_ARGS; + pResampler->_pHeap = pHeap; + pResampler->format = pConfig->format; + pResampler->channels = pConfig->channels; + pResampler->sampleRateIn = pConfig->sampleRateIn; + pResampler->sampleRateOut = pConfig->sampleRateOut; + + result = ma_resampler_get_vtable(pConfig, pResampler, &pResampler->pBackendVTable, &pResampler->pBackendUserData); + if (result != MA_SUCCESS) { + return result; } - pResampler->config = *pConfig; + if (pResampler->pBackendVTable == NULL || pResampler->pBackendVTable->onInit == NULL) { + return MA_NOT_IMPLEMENTED; /* onInit not implemented. */ + } - switch (pConfig->algorithm) - { - case ma_resample_algorithm_linear: - { - ma_linear_resampler_config linearConfig; - linearConfig = ma_linear_resampler_config_init(pConfig->format, pConfig->channels, pConfig->sampleRateIn, pConfig->sampleRateOut); - linearConfig.lpfOrder = pConfig->linear.lpfOrder; - linearConfig.lpfNyquistFactor = pConfig->linear.lpfNyquistFactor; - - result = ma_linear_resampler_init(&linearConfig, &pResampler->state.linear); - if (result != MA_SUCCESS) { - return result; - } - } break; - - case ma_resample_algorithm_speex: - { - #if defined(MA_HAS_SPEEX_RESAMPLER) - int speexErr; - pResampler->state.speex.pSpeexResamplerState = speex_resampler_init(pConfig->channels, pConfig->sampleRateIn, pConfig->sampleRateOut, pConfig->speex.quality, &speexErr); - if (pResampler->state.speex.pSpeexResamplerState == NULL) { - return ma_result_from_speex_err(speexErr); - } - #else - /* Speex resampler not available. */ - return MA_NO_BACKEND; - #endif - } break; - - default: return MA_INVALID_ARGS; + result = pResampler->pBackendVTable->onInit(pResampler->pBackendUserData, pConfig, pHeap, &pResampler->pBackend); + if (result != MA_SUCCESS) { + return result; } return MA_SUCCESS; } -MA_API void ma_resampler_uninit(ma_resampler* pResampler) +MA_API ma_result ma_resampler_init(const ma_resampler_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_resampler* pResampler) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_resampler_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_resampler_init_preallocated(pConfig, pHeap, pResampler); + if (result != MA_SUCCESS) { + return result; + } + + pResampler->_ownsHeap = MA_TRUE; + return MA_SUCCESS; +} + +MA_API void ma_resampler_uninit(ma_resampler* pResampler, const ma_allocation_callbacks* pAllocationCallbacks) { if (pResampler == NULL) { return; } - if (pResampler->config.algorithm == ma_resample_algorithm_linear) { - ma_linear_resampler_uninit(&pResampler->state.linear); + if (pResampler->pBackendVTable == NULL || pResampler->pBackendVTable->onUninit == NULL) { + return; } -#if defined(MA_HAS_SPEEX_RESAMPLER) - if (pResampler->config.algorithm == ma_resample_algorithm_speex) { - speex_resampler_destroy((SpeexResamplerState*)pResampler->state.speex.pSpeexResamplerState); + pResampler->pBackendVTable->onUninit(pResampler->pBackendUserData, pResampler->pBackend, pAllocationCallbacks); + + if (pResampler->_ownsHeap) { + ma_free(pResampler->_pHeap, pAllocationCallbacks); } -#endif } -static ma_result ma_resampler_process_pcm_frames__read__linear(ma_resampler* pResampler, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut) -{ - return ma_linear_resampler_process_pcm_frames(&pResampler->state.linear, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); -} - -#if defined(MA_HAS_SPEEX_RESAMPLER) -static ma_result ma_resampler_process_pcm_frames__read__speex(ma_resampler* pResampler, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut) -{ - int speexErr; - ma_uint64 frameCountOut; - ma_uint64 frameCountIn; - ma_uint64 framesProcessedOut; - ma_uint64 framesProcessedIn; - unsigned int framesPerIteration = UINT_MAX; - - MA_ASSERT(pResampler != NULL); - MA_ASSERT(pFramesOut != NULL); - MA_ASSERT(pFrameCountOut != NULL); - MA_ASSERT(pFrameCountIn != NULL); - - /* - Reading from the Speex resampler requires a bit of dancing around for a few reasons. The first thing is that it's frame counts - are in unsigned int's whereas ours is in ma_uint64. We therefore need to run the conversion in a loop. The other, more complicated - problem, is that we need to keep track of the input time, similar to what we do with the linear resampler. The reason we need to - do this is for ma_resampler_get_required_input_frame_count() and ma_resampler_get_expected_output_frame_count(). - */ - frameCountOut = *pFrameCountOut; - frameCountIn = *pFrameCountIn; - framesProcessedOut = 0; - framesProcessedIn = 0; - - while (framesProcessedOut < frameCountOut && framesProcessedIn < frameCountIn) { - unsigned int frameCountInThisIteration; - unsigned int frameCountOutThisIteration; - const void* pFramesInThisIteration; - void* pFramesOutThisIteration; - - frameCountInThisIteration = framesPerIteration; - if ((ma_uint64)frameCountInThisIteration > (frameCountIn - framesProcessedIn)) { - frameCountInThisIteration = (unsigned int)(frameCountIn - framesProcessedIn); - } - - frameCountOutThisIteration = framesPerIteration; - if ((ma_uint64)frameCountOutThisIteration > (frameCountOut - framesProcessedOut)) { - frameCountOutThisIteration = (unsigned int)(frameCountOut - framesProcessedOut); - } - - pFramesInThisIteration = ma_offset_ptr(pFramesIn, framesProcessedIn * ma_get_bytes_per_frame(pResampler->config.format, pResampler->config.channels)); - pFramesOutThisIteration = ma_offset_ptr(pFramesOut, framesProcessedOut * ma_get_bytes_per_frame(pResampler->config.format, pResampler->config.channels)); - - if (pResampler->config.format == ma_format_f32) { - speexErr = speex_resampler_process_interleaved_float((SpeexResamplerState*)pResampler->state.speex.pSpeexResamplerState, (const float*)pFramesInThisIteration, &frameCountInThisIteration, (float*)pFramesOutThisIteration, &frameCountOutThisIteration); - } else if (pResampler->config.format == ma_format_s16) { - speexErr = speex_resampler_process_interleaved_int((SpeexResamplerState*)pResampler->state.speex.pSpeexResamplerState, (const spx_int16_t*)pFramesInThisIteration, &frameCountInThisIteration, (spx_int16_t*)pFramesOutThisIteration, &frameCountOutThisIteration); - } else { - /* Format not supported. Should never get here. */ - MA_ASSERT(MA_FALSE); - return MA_INVALID_OPERATION; - } - - if (speexErr != RESAMPLER_ERR_SUCCESS) { - return ma_result_from_speex_err(speexErr); - } - - framesProcessedIn += frameCountInThisIteration; - framesProcessedOut += frameCountOutThisIteration; - } - - *pFrameCountOut = framesProcessedOut; - *pFrameCountIn = framesProcessedIn; - - return MA_SUCCESS; -} -#endif - -static ma_result ma_resampler_process_pcm_frames__read(ma_resampler* pResampler, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut) -{ - MA_ASSERT(pResampler != NULL); - MA_ASSERT(pFramesOut != NULL); - - /* pFramesOut is not NULL, which means we must have a capacity. */ - if (pFrameCountOut == NULL) { - return MA_INVALID_ARGS; - } - - /* It doesn't make sense to not have any input frames to process. */ - if (pFrameCountIn == NULL || pFramesIn == NULL) { - return MA_INVALID_ARGS; - } - - switch (pResampler->config.algorithm) - { - case ma_resample_algorithm_linear: - { - return ma_resampler_process_pcm_frames__read__linear(pResampler, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); - } - - case ma_resample_algorithm_speex: - { - #if defined(MA_HAS_SPEEX_RESAMPLER) - return ma_resampler_process_pcm_frames__read__speex(pResampler, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); - #else - break; - #endif - } - - default: break; - } - - /* Should never get here. */ - MA_ASSERT(MA_FALSE); - return MA_INVALID_ARGS; -} - - -static ma_result ma_resampler_process_pcm_frames__seek__linear(ma_resampler* pResampler, const void* pFramesIn, ma_uint64* pFrameCountIn, ma_uint64* pFrameCountOut) -{ - MA_ASSERT(pResampler != NULL); - - /* Seeking is supported natively by the linear resampler. */ - return ma_linear_resampler_process_pcm_frames(&pResampler->state.linear, pFramesIn, pFrameCountIn, NULL, pFrameCountOut); -} - -#if defined(MA_HAS_SPEEX_RESAMPLER) -static ma_result ma_resampler_process_pcm_frames__seek__speex(ma_resampler* pResampler, const void* pFramesIn, ma_uint64* pFrameCountIn, ma_uint64* pFrameCountOut) -{ - /* The generic seek method is implemented in on top of ma_resampler_process_pcm_frames__read() by just processing into a dummy buffer. */ - float devnull[4096]; - ma_uint64 totalOutputFramesToProcess; - ma_uint64 totalOutputFramesProcessed; - ma_uint64 totalInputFramesProcessed; - ma_uint32 bpf; - ma_result result; - - MA_ASSERT(pResampler != NULL); - - totalOutputFramesProcessed = 0; - totalInputFramesProcessed = 0; - bpf = ma_get_bytes_per_frame(pResampler->config.format, pResampler->config.channels); - - if (pFrameCountOut != NULL) { - /* Seek by output frames. */ - totalOutputFramesToProcess = *pFrameCountOut; - } else { - /* Seek by input frames. */ - MA_ASSERT(pFrameCountIn != NULL); - totalOutputFramesToProcess = ma_resampler_get_expected_output_frame_count(pResampler, *pFrameCountIn); - } - - if (pFramesIn != NULL) { - /* Process input data. */ - MA_ASSERT(pFrameCountIn != NULL); - while (totalOutputFramesProcessed < totalOutputFramesToProcess && totalInputFramesProcessed < *pFrameCountIn) { - ma_uint64 inputFramesToProcessThisIteration = (*pFrameCountIn - totalInputFramesProcessed); - ma_uint64 outputFramesToProcessThisIteration = (totalOutputFramesToProcess - totalOutputFramesProcessed); - if (outputFramesToProcessThisIteration > sizeof(devnull) / bpf) { - outputFramesToProcessThisIteration = sizeof(devnull) / bpf; - } - - result = ma_resampler_process_pcm_frames__read(pResampler, ma_offset_ptr(pFramesIn, totalInputFramesProcessed*bpf), &inputFramesToProcessThisIteration, ma_offset_ptr(devnull, totalOutputFramesProcessed*bpf), &outputFramesToProcessThisIteration); - if (result != MA_SUCCESS) { - return result; - } - - totalOutputFramesProcessed += outputFramesToProcessThisIteration; - totalInputFramesProcessed += inputFramesToProcessThisIteration; - } - } else { - /* Don't process input data - just update timing and filter state as if zeroes were passed in. */ - while (totalOutputFramesProcessed < totalOutputFramesToProcess) { - ma_uint64 inputFramesToProcessThisIteration = 16384; - ma_uint64 outputFramesToProcessThisIteration = (totalOutputFramesToProcess - totalOutputFramesProcessed); - if (outputFramesToProcessThisIteration > sizeof(devnull) / bpf) { - outputFramesToProcessThisIteration = sizeof(devnull) / bpf; - } - - result = ma_resampler_process_pcm_frames__read(pResampler, NULL, &inputFramesToProcessThisIteration, ma_offset_ptr(devnull, totalOutputFramesProcessed*bpf), &outputFramesToProcessThisIteration); - if (result != MA_SUCCESS) { - return result; - } - - totalOutputFramesProcessed += outputFramesToProcessThisIteration; - totalInputFramesProcessed += inputFramesToProcessThisIteration; - } - } - - - if (pFrameCountIn != NULL) { - *pFrameCountIn = totalInputFramesProcessed; - } - if (pFrameCountOut != NULL) { - *pFrameCountOut = totalOutputFramesProcessed; - } - - return MA_SUCCESS; -} -#endif - -static ma_result ma_resampler_process_pcm_frames__seek(ma_resampler* pResampler, const void* pFramesIn, ma_uint64* pFrameCountIn, ma_uint64* pFrameCountOut) -{ - MA_ASSERT(pResampler != NULL); - - switch (pResampler->config.algorithm) - { - case ma_resample_algorithm_linear: - { - return ma_resampler_process_pcm_frames__seek__linear(pResampler, pFramesIn, pFrameCountIn, pFrameCountOut); - } break; - - case ma_resample_algorithm_speex: - { - #if defined(MA_HAS_SPEEX_RESAMPLER) - return ma_resampler_process_pcm_frames__seek__speex(pResampler, pFramesIn, pFrameCountIn, pFrameCountOut); - #else - break; - #endif - }; - - default: break; - } - - /* Should never hit this. */ - MA_ASSERT(MA_FALSE); - return MA_INVALID_ARGS; -} - - MA_API ma_result ma_resampler_process_pcm_frames(ma_resampler* pResampler, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut) { if (pResampler == NULL) { @@ -39953,17 +50118,17 @@ MA_API ma_result ma_resampler_process_pcm_frames(ma_resampler* pResampler, const return MA_INVALID_ARGS; } - if (pFramesOut != NULL) { - /* Reading. */ - return ma_resampler_process_pcm_frames__read(pResampler, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); - } else { - /* Seeking. */ - return ma_resampler_process_pcm_frames__seek(pResampler, pFramesIn, pFrameCountIn, pFrameCountOut); + if (pResampler->pBackendVTable == NULL || pResampler->pBackendVTable->onProcess == NULL) { + return MA_NOT_IMPLEMENTED; } + + return pResampler->pBackendVTable->onProcess(pResampler->pBackendUserData, pResampler->pBackend, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); } MA_API ma_result ma_resampler_set_rate(ma_resampler* pResampler, ma_uint32 sampleRateIn, ma_uint32 sampleRateOut) { + ma_result result; + if (pResampler == NULL) { return MA_INVALID_ARGS; } @@ -39972,137 +50137,44 @@ MA_API ma_result ma_resampler_set_rate(ma_resampler* pResampler, ma_uint32 sampl return MA_INVALID_ARGS; } - pResampler->config.sampleRateIn = sampleRateIn; - pResampler->config.sampleRateOut = sampleRateOut; - - switch (pResampler->config.algorithm) - { - case ma_resample_algorithm_linear: - { - return ma_linear_resampler_set_rate(&pResampler->state.linear, sampleRateIn, sampleRateOut); - } break; - - case ma_resample_algorithm_speex: - { - #if defined(MA_HAS_SPEEX_RESAMPLER) - return ma_result_from_speex_err(speex_resampler_set_rate((SpeexResamplerState*)pResampler->state.speex.pSpeexResamplerState, sampleRateIn, sampleRateOut)); - #else - break; - #endif - }; - - default: break; + if (pResampler->pBackendVTable == NULL || pResampler->pBackendVTable->onSetRate == NULL) { + return MA_NOT_IMPLEMENTED; } - /* Should never get here. */ - MA_ASSERT(MA_FALSE); - return MA_INVALID_OPERATION; + result = pResampler->pBackendVTable->onSetRate(pResampler->pBackendUserData, pResampler->pBackend, sampleRateIn, sampleRateOut); + if (result != MA_SUCCESS) { + return result; + } + + pResampler->sampleRateIn = sampleRateIn; + pResampler->sampleRateOut = sampleRateOut; + + return MA_SUCCESS; } MA_API ma_result ma_resampler_set_rate_ratio(ma_resampler* pResampler, float ratio) { + ma_uint32 n; + ma_uint32 d; + if (pResampler == NULL) { return MA_INVALID_ARGS; } - if (pResampler->config.algorithm == ma_resample_algorithm_linear) { - return ma_linear_resampler_set_rate_ratio(&pResampler->state.linear, ratio); - } else { - /* Getting here means the backend does not have native support for setting the rate as a ratio so we just do it generically. */ - ma_uint32 n; - ma_uint32 d; - - d = 1000; - n = (ma_uint32)(ratio * d); - - if (n == 0) { - return MA_INVALID_ARGS; /* Ratio too small. */ - } - - MA_ASSERT(n != 0); - - return ma_resampler_set_rate(pResampler, n, d); - } -} - -MA_API ma_uint64 ma_resampler_get_required_input_frame_count(const ma_resampler* pResampler, ma_uint64 outputFrameCount) -{ - if (pResampler == NULL) { - return 0; + if (ratio <= 0) { + return MA_INVALID_ARGS; } - if (outputFrameCount == 0) { - return 0; + d = 1000; + n = (ma_uint32)(ratio * d); + + if (n == 0) { + return MA_INVALID_ARGS; /* Ratio too small. */ } - switch (pResampler->config.algorithm) - { - case ma_resample_algorithm_linear: - { - return ma_linear_resampler_get_required_input_frame_count(&pResampler->state.linear, outputFrameCount); - } + MA_ASSERT(n != 0); - case ma_resample_algorithm_speex: - { - #if defined(MA_HAS_SPEEX_RESAMPLER) - spx_uint64_t count; - int speexErr = ma_speex_resampler_get_required_input_frame_count((SpeexResamplerState*)pResampler->state.speex.pSpeexResamplerState, outputFrameCount, &count); - if (speexErr != RESAMPLER_ERR_SUCCESS) { - return 0; - } - - return (ma_uint64)count; - #else - break; - #endif - } - - default: break; - } - - /* Should never get here. */ - MA_ASSERT(MA_FALSE); - return 0; -} - -MA_API ma_uint64 ma_resampler_get_expected_output_frame_count(const ma_resampler* pResampler, ma_uint64 inputFrameCount) -{ - if (pResampler == NULL) { - return 0; /* Invalid args. */ - } - - if (inputFrameCount == 0) { - return 0; - } - - switch (pResampler->config.algorithm) - { - case ma_resample_algorithm_linear: - { - return ma_linear_resampler_get_expected_output_frame_count(&pResampler->state.linear, inputFrameCount); - } - - case ma_resample_algorithm_speex: - { - #if defined(MA_HAS_SPEEX_RESAMPLER) - spx_uint64_t count; - int speexErr = ma_speex_resampler_get_expected_output_frame_count((SpeexResamplerState*)pResampler->state.speex.pSpeexResamplerState, inputFrameCount, &count); - if (speexErr != RESAMPLER_ERR_SUCCESS) { - return 0; - } - - return (ma_uint64)count; - #else - break; - #endif - } - - default: break; - } - - /* Should never get here. */ - MA_ASSERT(MA_FALSE); - return 0; + return ma_resampler_set_rate(pResampler, n, d); } MA_API ma_uint64 ma_resampler_get_input_latency(const ma_resampler* pResampler) @@ -40111,28 +50183,11 @@ MA_API ma_uint64 ma_resampler_get_input_latency(const ma_resampler* pResampler) return 0; } - switch (pResampler->config.algorithm) - { - case ma_resample_algorithm_linear: - { - return ma_linear_resampler_get_input_latency(&pResampler->state.linear); - } - - case ma_resample_algorithm_speex: - { - #if defined(MA_HAS_SPEEX_RESAMPLER) - return (ma_uint64)ma_speex_resampler_get_input_latency((SpeexResamplerState*)pResampler->state.speex.pSpeexResamplerState); - #else - break; - #endif - } - - default: break; + if (pResampler->pBackendVTable == NULL || pResampler->pBackendVTable->onGetInputLatency == NULL) { + return 0; } - /* Should never get here. */ - MA_ASSERT(MA_FALSE); - return 0; + return pResampler->pBackendVTable->onGetInputLatency(pResampler->pBackendUserData, pResampler->pBackend); } MA_API ma_uint64 ma_resampler_get_output_latency(const ma_resampler* pResampler) @@ -40141,28 +50196,62 @@ MA_API ma_uint64 ma_resampler_get_output_latency(const ma_resampler* pResampler) return 0; } - switch (pResampler->config.algorithm) - { - case ma_resample_algorithm_linear: - { - return ma_linear_resampler_get_output_latency(&pResampler->state.linear); - } - - case ma_resample_algorithm_speex: - { - #if defined(MA_HAS_SPEEX_RESAMPLER) - return (ma_uint64)ma_speex_resampler_get_output_latency((SpeexResamplerState*)pResampler->state.speex.pSpeexResamplerState); - #else - break; - #endif - } - - default: break; + if (pResampler->pBackendVTable == NULL || pResampler->pBackendVTable->onGetOutputLatency == NULL) { + return 0; } - /* Should never get here. */ - MA_ASSERT(MA_FALSE); - return 0; + return pResampler->pBackendVTable->onGetOutputLatency(pResampler->pBackendUserData, pResampler->pBackend); +} + +MA_API ma_result ma_resampler_get_required_input_frame_count(const ma_resampler* pResampler, ma_uint64 outputFrameCount, ma_uint64* pInputFrameCount) +{ + if (pInputFrameCount == NULL) { + return MA_INVALID_ARGS; + } + + *pInputFrameCount = 0; + + if (pResampler == NULL) { + return MA_INVALID_ARGS; + } + + if (pResampler->pBackendVTable == NULL || pResampler->pBackendVTable->onGetRequiredInputFrameCount == NULL) { + return MA_NOT_IMPLEMENTED; + } + + return pResampler->pBackendVTable->onGetRequiredInputFrameCount(pResampler->pBackendUserData, pResampler->pBackend, outputFrameCount, pInputFrameCount); +} + +MA_API ma_result ma_resampler_get_expected_output_frame_count(const ma_resampler* pResampler, ma_uint64 inputFrameCount, ma_uint64* pOutputFrameCount) +{ + if (pOutputFrameCount == NULL) { + return MA_INVALID_ARGS; + } + + *pOutputFrameCount = 0; + + if (pResampler == NULL) { + return MA_INVALID_ARGS; + } + + if (pResampler->pBackendVTable == NULL || pResampler->pBackendVTable->onGetExpectedOutputFrameCount == NULL) { + return MA_NOT_IMPLEMENTED; + } + + return pResampler->pBackendVTable->onGetExpectedOutputFrameCount(pResampler->pBackendUserData, pResampler->pBackend, inputFrameCount, pOutputFrameCount); +} + +MA_API ma_result ma_resampler_reset(ma_resampler* pResampler) +{ + if (pResampler == NULL) { + return MA_INVALID_ARGS; + } + + if (pResampler->pBackendVTable == NULL || pResampler->pBackendVTable->onReset == NULL) { + return MA_NOT_IMPLEMENTED; + } + + return pResampler->pBackendVTable->onReset(pResampler->pBackendUserData, pResampler->pBackend); } /************************************************************************************************************************************************************** @@ -40283,17 +50372,13 @@ MA_API ma_channel_converter_config ma_channel_converter_config_init(ma_format fo { ma_channel_converter_config config; - /* Channel counts need to be clamped. */ - channelsIn = ma_min(channelsIn, ma_countof(config.channelMapIn)); - channelsOut = ma_min(channelsOut, ma_countof(config.channelMapOut)); - MA_ZERO_OBJECT(&config); - config.format = format; - config.channelsIn = channelsIn; - config.channelsOut = channelsOut; - ma_channel_map_copy_or_default(config.channelMapIn, pChannelMapIn, channelsIn); - ma_channel_map_copy_or_default(config.channelMapOut, pChannelMapOut, channelsOut); - config.mixingMode = mixingMode; + config.format = format; + config.channelsIn = channelsIn; + config.channelsOut = channelsOut; + config.pChannelMapIn = pChannelMapIn; + config.pChannelMapOut = pChannelMapOut; + config.mixingMode = mixingMode; return config; } @@ -40324,320 +50409,862 @@ static ma_bool32 ma_is_spatial_channel_position(ma_channel channelPosition) return MA_FALSE; } -MA_API ma_result ma_channel_converter_init(const ma_channel_converter_config* pConfig, ma_channel_converter* pConverter) + +static ma_bool32 ma_channel_map_is_passthrough(const ma_channel* pChannelMapIn, ma_uint32 channelsIn, const ma_channel* pChannelMapOut, ma_uint32 channelsOut) +{ + if (channelsOut == channelsIn) { + return ma_channel_map_is_equal(pChannelMapOut, pChannelMapIn, channelsOut); + } else { + return MA_FALSE; /* Channel counts differ, so cannot be a passthrough. */ + } +} + +static ma_channel_conversion_path ma_channel_map_get_conversion_path(const ma_channel* pChannelMapIn, ma_uint32 channelsIn, const ma_channel* pChannelMapOut, ma_uint32 channelsOut, ma_channel_mix_mode mode) +{ + if (ma_channel_map_is_passthrough(pChannelMapIn, channelsIn, pChannelMapOut, channelsOut)) { + return ma_channel_conversion_path_passthrough; + } + + if (channelsOut == 1 && (pChannelMapOut == NULL || pChannelMapOut[0] == MA_CHANNEL_MONO)) { + return ma_channel_conversion_path_mono_out; + } + + if (channelsIn == 1 && (pChannelMapIn == NULL || pChannelMapIn[0] == MA_CHANNEL_MONO)) { + return ma_channel_conversion_path_mono_in; + } + + if (mode == ma_channel_mix_mode_custom_weights) { + return ma_channel_conversion_path_weights; + } + + /* + We can use a simple shuffle if both channel maps have the same channel count and all channel + positions are present in both. + */ + if (channelsIn == channelsOut) { + ma_uint32 iChannelIn; + ma_bool32 areAllChannelPositionsPresent = MA_TRUE; + for (iChannelIn = 0; iChannelIn < channelsIn; ++iChannelIn) { + ma_bool32 isInputChannelPositionInOutput = MA_FALSE; + if (ma_channel_map_contains_channel_position(channelsOut, pChannelMapOut, ma_channel_map_get_channel(pChannelMapIn, channelsIn, iChannelIn))) { + isInputChannelPositionInOutput = MA_TRUE; + break; + } + + if (!isInputChannelPositionInOutput) { + areAllChannelPositionsPresent = MA_FALSE; + break; + } + } + + if (areAllChannelPositionsPresent) { + return ma_channel_conversion_path_shuffle; + } + } + + /* Getting here means we'll need to use weights. */ + return ma_channel_conversion_path_weights; +} + + +static ma_result ma_channel_map_build_shuffle_table(const ma_channel* pChannelMapIn, ma_uint32 channelCountIn, const ma_channel* pChannelMapOut, ma_uint32 channelCountOut, ma_uint8* pShuffleTable) { ma_uint32 iChannelIn; ma_uint32 iChannelOut; + if (pShuffleTable == NULL || channelCountIn == 0 || channelCountOut == 0) { + return MA_INVALID_ARGS; + } + + /* + When building the shuffle table we just do a 1:1 mapping based on the first occurance of a channel. If the + input channel has more than one occurance of a channel position, the second one will be ignored. + */ + for (iChannelOut = 0; iChannelOut < channelCountOut; iChannelOut += 1) { + ma_channel channelOut; + + /* Default to MA_CHANNEL_INDEX_NULL so that if a mapping is not found it'll be set appropriately. */ + pShuffleTable[iChannelOut] = MA_CHANNEL_INDEX_NULL; + + channelOut = ma_channel_map_get_channel(pChannelMapOut, channelCountOut, iChannelOut); + for (iChannelIn = 0; iChannelIn < channelCountIn; iChannelIn += 1) { + ma_channel channelIn; + + channelIn = ma_channel_map_get_channel(pChannelMapIn, channelCountIn, iChannelIn); + if (channelOut == channelIn) { + pShuffleTable[iChannelOut] = (ma_uint8)iChannelIn; + break; + } + + /* + Getting here means the channels don't exactly match, but we are going to support some + relaxed matching for practicality. If, for example, there are two stereo channel maps, + but one uses front left/right and the other uses side left/right, it makes logical + sense to just map these. The way we'll do it is we'll check if there is a logical + corresponding mapping, and if so, apply it, but we will *not* break from the loop, + thereby giving the loop a chance to find an exact match later which will take priority. + */ + switch (channelOut) + { + /* Left channels. */ + case MA_CHANNEL_FRONT_LEFT: + case MA_CHANNEL_SIDE_LEFT: + { + switch (channelIn) { + case MA_CHANNEL_FRONT_LEFT: + case MA_CHANNEL_SIDE_LEFT: + { + pShuffleTable[iChannelOut] = (ma_uint8)iChannelIn; + } break; + } + } break; + + /* Right channels. */ + case MA_CHANNEL_FRONT_RIGHT: + case MA_CHANNEL_SIDE_RIGHT: + { + switch (channelIn) { + case MA_CHANNEL_FRONT_RIGHT: + case MA_CHANNEL_SIDE_RIGHT: + { + pShuffleTable[iChannelOut] = (ma_uint8)iChannelIn; + } break; + } + } break; + + default: break; + } + } + } + + return MA_SUCCESS; +} + + +static void ma_channel_map_apply_shuffle_table_u8(ma_uint8* pFramesOut, ma_uint32 channelsOut, const ma_uint8* pFramesIn, ma_uint32 channelsIn, ma_uint64 frameCount, const ma_uint8* pShuffleTable) +{ + ma_uint64 iFrame; + ma_uint32 iChannelOut; + + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + for (iChannelOut = 0; iChannelOut < channelsOut; iChannelOut += 1) { + ma_uint8 iChannelIn = pShuffleTable[iChannelOut]; + if (iChannelIn < channelsIn) { /* For safety, and to deal with MA_CHANNEL_INDEX_NULL. */ + pFramesOut[iChannelOut] = pFramesIn[iChannelIn]; + } else { + pFramesOut[iChannelOut] = 0; + } + } + + pFramesOut += channelsOut; + pFramesIn += channelsIn; + } +} + +static void ma_channel_map_apply_shuffle_table_s16(ma_int16* pFramesOut, ma_uint32 channelsOut, const ma_int16* pFramesIn, ma_uint32 channelsIn, ma_uint64 frameCount, const ma_uint8* pShuffleTable) +{ + ma_uint64 iFrame; + ma_uint32 iChannelOut; + + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + for (iChannelOut = 0; iChannelOut < channelsOut; iChannelOut += 1) { + ma_uint8 iChannelIn = pShuffleTable[iChannelOut]; + if (iChannelIn < channelsIn) { /* For safety, and to deal with MA_CHANNEL_INDEX_NULL. */ + pFramesOut[iChannelOut] = pFramesIn[iChannelIn]; + } else { + pFramesOut[iChannelOut] = 0; + } + } + + pFramesOut += channelsOut; + pFramesIn += channelsIn; + } +} + +static void ma_channel_map_apply_shuffle_table_s24(ma_uint8* pFramesOut, ma_uint32 channelsOut, const ma_uint8* pFramesIn, ma_uint32 channelsIn, ma_uint64 frameCount, const ma_uint8* pShuffleTable) +{ + ma_uint64 iFrame; + ma_uint32 iChannelOut; + + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + for (iChannelOut = 0; iChannelOut < channelsOut; iChannelOut += 1) { + ma_uint8 iChannelIn = pShuffleTable[iChannelOut]; + if (iChannelIn < channelsIn) { /* For safety, and to deal with MA_CHANNEL_INDEX_NULL. */ + pFramesOut[iChannelOut*3 + 0] = pFramesIn[iChannelIn*3 + 0]; + pFramesOut[iChannelOut*3 + 1] = pFramesIn[iChannelIn*3 + 1]; + pFramesOut[iChannelOut*3 + 2] = pFramesIn[iChannelIn*3 + 2]; + } else { + pFramesOut[iChannelOut*3 + 0] = 0; + } pFramesOut[iChannelOut*3 + 1] = 0; + } pFramesOut[iChannelOut*3 + 2] = 0; + + pFramesOut += channelsOut*3; + pFramesIn += channelsIn*3; + } +} + +static void ma_channel_map_apply_shuffle_table_s32(ma_int32* pFramesOut, ma_uint32 channelsOut, const ma_int32* pFramesIn, ma_uint32 channelsIn, ma_uint64 frameCount, const ma_uint8* pShuffleTable) +{ + ma_uint64 iFrame; + ma_uint32 iChannelOut; + + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + for (iChannelOut = 0; iChannelOut < channelsOut; iChannelOut += 1) { + ma_uint8 iChannelIn = pShuffleTable[iChannelOut]; + if (iChannelIn < channelsIn) { /* For safety, and to deal with MA_CHANNEL_INDEX_NULL. */ + pFramesOut[iChannelOut] = pFramesIn[iChannelIn]; + } else { + pFramesOut[iChannelOut] = 0; + } + } + + pFramesOut += channelsOut; + pFramesIn += channelsIn; + } +} + +static void ma_channel_map_apply_shuffle_table_f32(float* pFramesOut, ma_uint32 channelsOut, const float* pFramesIn, ma_uint32 channelsIn, ma_uint64 frameCount, const ma_uint8* pShuffleTable) +{ + ma_uint64 iFrame; + ma_uint32 iChannelOut; + + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + for (iChannelOut = 0; iChannelOut < channelsOut; iChannelOut += 1) { + ma_uint8 iChannelIn = pShuffleTable[iChannelOut]; + if (iChannelIn < channelsIn) { /* For safety, and to deal with MA_CHANNEL_INDEX_NULL. */ + pFramesOut[iChannelOut] = pFramesIn[iChannelIn]; + } else { + pFramesOut[iChannelOut] = 0; + } + } + + pFramesOut += channelsOut; + pFramesIn += channelsIn; + } +} + +static ma_result ma_channel_map_apply_shuffle_table(void* pFramesOut, ma_uint32 channelsOut, const void* pFramesIn, ma_uint32 channelsIn, ma_uint64 frameCount, const ma_uint8* pShuffleTable, ma_format format) +{ + if (pFramesOut == NULL || pFramesIn == NULL || channelsOut == 0 || pShuffleTable == NULL) { + return MA_INVALID_ARGS; + } + + switch (format) + { + case ma_format_u8: + { + ma_channel_map_apply_shuffle_table_u8((ma_uint8*)pFramesOut, channelsOut, (const ma_uint8*)pFramesIn, channelsIn, frameCount, pShuffleTable); + } break; + + case ma_format_s16: + { + ma_channel_map_apply_shuffle_table_s16((ma_int16*)pFramesOut, channelsOut, (const ma_int16*)pFramesIn, channelsIn, frameCount, pShuffleTable); + } break; + + case ma_format_s24: + { + ma_channel_map_apply_shuffle_table_s24((ma_uint8*)pFramesOut, channelsOut, (const ma_uint8*)pFramesIn, channelsIn, frameCount, pShuffleTable); + } break; + + case ma_format_s32: + { + ma_channel_map_apply_shuffle_table_s32((ma_int32*)pFramesOut, channelsOut, (const ma_int32*)pFramesIn, channelsIn, frameCount, pShuffleTable); + } break; + + case ma_format_f32: + { + ma_channel_map_apply_shuffle_table_f32((float*)pFramesOut, channelsOut, (const float*)pFramesIn, channelsIn, frameCount, pShuffleTable); + } break; + + default: return MA_INVALID_ARGS; /* Unknown format. */ + } + + return MA_SUCCESS; +} + +static ma_result ma_channel_map_apply_mono_out_f32(float* pFramesOut, const float* pFramesIn, const ma_channel* pChannelMapIn, ma_uint32 channelsIn, ma_uint64 frameCount) +{ + ma_uint64 iFrame; + ma_uint32 iChannelIn; + ma_uint32 accumulationCount; + + if (pFramesOut == NULL || pFramesIn == NULL || channelsIn == 0) { + return MA_INVALID_ARGS; + } + + /* In this case the output stream needs to be the average of all channels, ignoring NONE. */ + + /* A quick pre-processing step to get the accumulation counter since we're ignoring NONE channels. */ + accumulationCount = 0; + for (iChannelIn = 0; iChannelIn < channelsIn; iChannelIn += 1) { + if (ma_channel_map_get_channel(pChannelMapIn, channelsIn, iChannelIn) != MA_CHANNEL_NONE) { + accumulationCount += 1; + } + } + + if (accumulationCount > 0) { /* <-- Prevent a division by zero. */ + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + float accumulation = 0; + + for (iChannelIn = 0; iChannelIn < channelsIn; iChannelIn += 1) { + ma_channel channelIn = ma_channel_map_get_channel(pChannelMapIn, channelsIn, iChannelIn); + if (channelIn != MA_CHANNEL_NONE) { + accumulation += pFramesIn[iChannelIn]; + } + } + + pFramesOut[0] = accumulation / accumulationCount; + pFramesOut += 1; + pFramesIn += channelsIn; + } + } else { + ma_silence_pcm_frames(pFramesOut, frameCount, ma_format_f32, 1); + } + + return MA_SUCCESS; +} + +static ma_result ma_channel_map_apply_mono_in_f32(float* pFramesOut, const ma_channel* pChannelMapOut, ma_uint32 channelsOut, const float* pFramesIn, ma_uint64 frameCount, ma_mono_expansion_mode monoExpansionMode) +{ + ma_uint64 iFrame; + ma_uint32 iChannelOut; + + if (pFramesOut == NULL || channelsOut == 0 || pFramesIn == NULL) { + return MA_INVALID_ARGS; + } + + /* Note that the MA_CHANNEL_NONE channel must be ignored in all cases. */ + switch (monoExpansionMode) + { + case ma_mono_expansion_mode_average: + { + float weight; + ma_uint32 validChannelCount = 0; + + for (iChannelOut = 0; iChannelOut < channelsOut; iChannelOut += 1) { + ma_channel channelOut = ma_channel_map_get_channel(pChannelMapOut, channelsOut, iChannelOut); + if (channelOut != MA_CHANNEL_NONE) { + validChannelCount += 1; + } + } + + weight = 1.0f / validChannelCount; + + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + for (iChannelOut = 0; iChannelOut < channelsOut; iChannelOut += 1) { + ma_channel channelOut = ma_channel_map_get_channel(pChannelMapOut, channelsOut, iChannelOut); + if (channelOut != MA_CHANNEL_NONE) { + pFramesOut[iChannelOut] = pFramesIn[0] * weight; + } + } + + pFramesOut += channelsOut; + pFramesIn += 1; + } + } break; + + case ma_mono_expansion_mode_stereo_only: + { + if (channelsOut >= 2) { + ma_uint32 iChannelLeft = (ma_uint32)-1; + ma_uint32 iChannelRight = (ma_uint32)-1; + + /* + We first need to find our stereo channels. We prefer front-left and front-right, but + if they're not available, we'll also try side-left and side-right. If neither are + available we'll fall through to the default case below. + */ + for (iChannelOut = 0; iChannelOut < channelsOut; iChannelOut += 1) { + ma_channel channelOut = ma_channel_map_get_channel(pChannelMapOut, channelsOut, iChannelOut); + if (channelOut == MA_CHANNEL_SIDE_LEFT) { + iChannelLeft = iChannelOut; + } + if (channelOut == MA_CHANNEL_SIDE_RIGHT) { + iChannelRight = iChannelOut; + } + } + + for (iChannelOut = 0; iChannelOut < channelsOut; iChannelOut += 1) { + ma_channel channelOut = ma_channel_map_get_channel(pChannelMapOut, channelsOut, iChannelOut); + if (channelOut == MA_CHANNEL_FRONT_LEFT) { + iChannelLeft = iChannelOut; + } + if (channelOut == MA_CHANNEL_FRONT_RIGHT) { + iChannelRight = iChannelOut; + } + } + + + if (iChannelLeft != (ma_uint32)-1 && iChannelRight != (ma_uint32)-1) { + /* We found our stereo channels so we can duplicate the signal across those channels. */ + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + for (iChannelOut = 0; iChannelOut < channelsOut; iChannelOut += 1) { + ma_channel channelOut = ma_channel_map_get_channel(pChannelMapOut, channelsOut, iChannelOut); + if (channelOut != MA_CHANNEL_NONE) { + if (iChannelOut == iChannelLeft || iChannelOut == iChannelRight) { + pFramesOut[iChannelOut] = pFramesIn[0]; + } else { + pFramesOut[iChannelOut] = 0.0f; + } + } + } + + pFramesOut += channelsOut; + pFramesIn += 1; + } + + break; /* Get out of the switch. */ + } else { + /* Fallthrough. Does not have left and right channels. */ + goto default_handler; + } + } else { + /* Fallthrough. Does not have stereo channels. */ + goto default_handler; + } + }; /* Fallthrough. See comments above. */ + + case ma_mono_expansion_mode_duplicate: + default: + { + default_handler: + { + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + for (iChannelOut = 0; iChannelOut < channelsOut; iChannelOut += 1) { + ma_channel channelOut = ma_channel_map_get_channel(pChannelMapOut, channelsOut, iChannelOut); + if (channelOut != MA_CHANNEL_NONE) { + pFramesOut[iChannelOut] = pFramesIn[0]; + } + } + + pFramesOut += channelsOut; + pFramesIn += 1; + } + } + } break; + } + + return MA_SUCCESS; +} + +static void ma_channel_map_apply_f32(float* pFramesOut, const ma_channel* pChannelMapOut, ma_uint32 channelsOut, const float* pFramesIn, const ma_channel* pChannelMapIn, ma_uint32 channelsIn, ma_uint64 frameCount, ma_channel_mix_mode mode, ma_mono_expansion_mode monoExpansionMode) +{ + ma_channel_conversion_path conversionPath = ma_channel_map_get_conversion_path(pChannelMapIn, channelsIn, pChannelMapOut, channelsOut, mode); + + /* Optimized Path: Passthrough */ + if (conversionPath == ma_channel_conversion_path_passthrough) { + ma_copy_pcm_frames(pFramesOut, pFramesIn, frameCount, ma_format_f32, channelsOut); + return; + } + + /* Special Path: Mono Output. */ + if (conversionPath == ma_channel_conversion_path_mono_out) { + ma_channel_map_apply_mono_out_f32(pFramesOut, pFramesIn, pChannelMapIn, channelsIn, frameCount); + return; + } + + /* Special Path: Mono Input. */ + if (conversionPath == ma_channel_conversion_path_mono_in) { + ma_channel_map_apply_mono_in_f32(pFramesOut, pChannelMapOut, channelsOut, pFramesIn, frameCount, monoExpansionMode); + return; + } + + /* Getting here means we aren't running on an optimized conversion path. */ + if (channelsOut <= MA_MAX_CHANNELS) { + ma_result result; + + if (mode == ma_channel_mix_mode_simple) { + ma_channel shuffleTable[MA_MAX_CHANNELS]; + + result = ma_channel_map_build_shuffle_table(pChannelMapIn, channelsIn, pChannelMapOut, channelsOut, shuffleTable); + if (result != MA_SUCCESS) { + return; + } + + result = ma_channel_map_apply_shuffle_table(pFramesOut, channelsOut, pFramesIn, channelsIn, frameCount, shuffleTable, ma_format_f32); + if (result != MA_SUCCESS) { + return; + } + } else { + ma_uint32 iFrame; + ma_uint32 iChannelOut; + ma_uint32 iChannelIn; + float weights[32][32]; /* Do not use MA_MAX_CHANNELS here! */ + + /* + If we have a small enough number of channels, pre-compute the weights. Otherwise we'll just need to + fall back to a slower path because otherwise we'll run out of stack space. + */ + if (channelsIn <= ma_countof(weights) && channelsOut <= ma_countof(weights)) { + /* Pre-compute weights. */ + for (iChannelOut = 0; iChannelOut < channelsOut; iChannelOut += 1) { + ma_channel channelOut = ma_channel_map_get_channel(pChannelMapOut, channelsOut, iChannelOut); + for (iChannelIn = 0; iChannelIn < channelsIn; iChannelIn += 1) { + ma_channel channelIn = ma_channel_map_get_channel(pChannelMapIn, channelsIn, iChannelIn); + weights[iChannelOut][iChannelIn] = ma_calculate_channel_position_rectangular_weight(channelOut, channelIn); + } + } + + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + for (iChannelOut = 0; iChannelOut < channelsOut; iChannelOut += 1) { + float accumulation = 0; + + for (iChannelIn = 0; iChannelIn < channelsIn; iChannelIn += 1) { + accumulation += pFramesIn[iChannelIn] * weights[iChannelOut][iChannelIn]; + } + + pFramesOut[iChannelOut] = accumulation; + } + + pFramesOut += channelsOut; + pFramesIn += channelsIn; + } + } else { + /* Cannot pre-compute weights because not enough room in stack-allocated buffer. */ + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + for (iChannelOut = 0; iChannelOut < channelsOut; iChannelOut += 1) { + float accumulation = 0; + ma_channel channelOut = ma_channel_map_get_channel(pChannelMapOut, channelsOut, iChannelOut); + + for (iChannelIn = 0; iChannelIn < channelsIn; iChannelIn += 1) { + ma_channel channelIn = ma_channel_map_get_channel(pChannelMapIn, channelsIn, iChannelIn); + accumulation += pFramesIn[iChannelIn] * ma_calculate_channel_position_rectangular_weight(channelOut, channelIn); + } + + pFramesOut[iChannelOut] = accumulation; + } + + pFramesOut += channelsOut; + pFramesIn += channelsIn; + } + } + } + } else { + /* Fall back to silence. If you hit this, what are you doing with so many channels?! */ + ma_silence_pcm_frames(pFramesOut, frameCount, ma_format_f32, channelsOut); + } +} + + +typedef struct +{ + size_t sizeInBytes; + size_t channelMapInOffset; + size_t channelMapOutOffset; + size_t shuffleTableOffset; + size_t weightsOffset; +} ma_channel_converter_heap_layout; + +static ma_channel_conversion_path ma_channel_converter_config_get_conversion_path(const ma_channel_converter_config* pConfig) +{ + return ma_channel_map_get_conversion_path(pConfig->pChannelMapIn, pConfig->channelsIn, pConfig->pChannelMapOut, pConfig->channelsOut, pConfig->mixingMode); +} + +static ma_result ma_channel_converter_get_heap_layout(const ma_channel_converter_config* pConfig, ma_channel_converter_heap_layout* pHeapLayout) +{ + ma_channel_conversion_path conversionPath; + + MA_ASSERT(pHeapLayout != NULL); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->channelsIn == 0 || pConfig->channelsOut == 0) { + return MA_INVALID_ARGS; + } + + if (!ma_channel_map_is_valid(pConfig->pChannelMapIn, pConfig->channelsIn)) { + return MA_INVALID_ARGS; + } + + if (!ma_channel_map_is_valid(pConfig->pChannelMapOut, pConfig->channelsOut)) { + return MA_INVALID_ARGS; + } + + pHeapLayout->sizeInBytes = 0; + + /* Input channel map. Only need to allocate this if we have an input channel map (otherwise default channel map is assumed). */ + pHeapLayout->channelMapInOffset = pHeapLayout->sizeInBytes; + if (pConfig->pChannelMapIn != NULL) { + pHeapLayout->sizeInBytes += sizeof(ma_channel) * pConfig->channelsIn; + } + + /* Output channel map. Only need to allocate this if we have an output channel map (otherwise default channel map is assumed). */ + pHeapLayout->channelMapOutOffset = pHeapLayout->sizeInBytes; + if (pConfig->pChannelMapOut != NULL) { + pHeapLayout->sizeInBytes += sizeof(ma_channel) * pConfig->channelsOut; + } + + /* Alignment for the next section. */ + pHeapLayout->sizeInBytes = ma_align_64(pHeapLayout->sizeInBytes); + + /* Whether or not we use weights of a shuffle table depends on the channel map themselves and the algorithm we've chosen. */ + conversionPath = ma_channel_converter_config_get_conversion_path(pConfig); + + /* Shuffle table */ + pHeapLayout->shuffleTableOffset = pHeapLayout->sizeInBytes; + if (conversionPath == ma_channel_conversion_path_shuffle) { + pHeapLayout->sizeInBytes += sizeof(ma_uint8) * pConfig->channelsOut; + } + + /* Weights */ + pHeapLayout->weightsOffset = pHeapLayout->sizeInBytes; + if (conversionPath == ma_channel_conversion_path_weights) { + pHeapLayout->sizeInBytes += sizeof(float*) * pConfig->channelsIn; + pHeapLayout->sizeInBytes += sizeof(float ) * pConfig->channelsIn * pConfig->channelsOut; + } + + /* Make sure allocation size is aligned. */ + pHeapLayout->sizeInBytes = ma_align_64(pHeapLayout->sizeInBytes); + + return MA_SUCCESS; +} + +MA_API ma_result ma_channel_converter_get_heap_size(const ma_channel_converter_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_result result; + ma_channel_converter_heap_layout heapLayout; + + if (pHeapSizeInBytes == NULL) { + return MA_INVALID_ARGS; + } + + *pHeapSizeInBytes = 0; + + result = ma_channel_converter_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + *pHeapSizeInBytes = heapLayout.sizeInBytes; + + return MA_SUCCESS; +} + +MA_API ma_result ma_channel_converter_init_preallocated(const ma_channel_converter_config* pConfig, void* pHeap, ma_channel_converter* pConverter) +{ + ma_result result; + ma_channel_converter_heap_layout heapLayout; + if (pConverter == NULL) { return MA_INVALID_ARGS; } MA_ZERO_OBJECT(pConverter); - if (pConfig == NULL) { - return MA_INVALID_ARGS; + result = ma_channel_converter_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; } - /* Basic validation for channel counts. */ - if (pConfig->channelsIn < MA_MIN_CHANNELS || pConfig->channelsIn > MA_MAX_CHANNELS || - pConfig->channelsOut < MA_MIN_CHANNELS || pConfig->channelsOut > MA_MAX_CHANNELS) { - return MA_INVALID_ARGS; - } - - if (!ma_channel_map_valid(pConfig->channelsIn, pConfig->channelMapIn)) { - return MA_INVALID_ARGS; /* Invalid input channel map. */ - } - if (!ma_channel_map_valid(pConfig->channelsOut, pConfig->channelMapOut)) { - return MA_INVALID_ARGS; /* Invalid output channel map. */ - } + pConverter->_pHeap = pHeap; + MA_ZERO_MEMORY(pConverter->_pHeap, heapLayout.sizeInBytes); pConverter->format = pConfig->format; pConverter->channelsIn = pConfig->channelsIn; pConverter->channelsOut = pConfig->channelsOut; - ma_channel_map_copy_or_default(pConverter->channelMapIn, pConfig->channelMapIn, pConfig->channelsIn); - ma_channel_map_copy_or_default(pConverter->channelMapOut, pConfig->channelMapOut, pConfig->channelsOut); pConverter->mixingMode = pConfig->mixingMode; - for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; iChannelIn += 1) { - for (iChannelOut = 0; iChannelOut < pConverter->channelsOut; ++iChannelOut) { - if (pConverter->format == ma_format_f32) { - pConverter->weights.f32[iChannelIn][iChannelOut] = pConfig->weights[iChannelIn][iChannelOut]; - } else { - pConverter->weights.s16[iChannelIn][iChannelOut] = ma_channel_converter_float_to_fixed(pConfig->weights[iChannelIn][iChannelOut]); + if (pConfig->pChannelMapIn != NULL) { + pConverter->pChannelMapIn = (ma_channel*)ma_offset_ptr(pHeap, heapLayout.channelMapInOffset); + ma_channel_map_copy_or_default(pConverter->pChannelMapIn, pConfig->channelsIn, pConfig->pChannelMapIn, pConfig->channelsIn); + } else { + pConverter->pChannelMapIn = NULL; /* Use default channel map. */ + } + + if (pConfig->pChannelMapOut != NULL) { + pConverter->pChannelMapOut = (ma_channel*)ma_offset_ptr(pHeap, heapLayout.channelMapOutOffset); + ma_channel_map_copy_or_default(pConverter->pChannelMapOut, pConfig->channelsOut, pConfig->pChannelMapOut, pConfig->channelsOut); + } else { + pConverter->pChannelMapOut = NULL; /* Use default channel map. */ + } + + pConverter->conversionPath = ma_channel_converter_config_get_conversion_path(pConfig); + + if (pConverter->conversionPath == ma_channel_conversion_path_shuffle) { + pConverter->pShuffleTable = (ma_uint8*)ma_offset_ptr(pHeap, heapLayout.shuffleTableOffset); + ma_channel_map_build_shuffle_table(pConverter->pChannelMapIn, pConverter->channelsIn, pConverter->pChannelMapOut, pConverter->channelsOut, pConverter->pShuffleTable); + } + + if (pConverter->conversionPath == ma_channel_conversion_path_weights) { + ma_uint32 iChannelIn; + ma_uint32 iChannelOut; + + if (pConverter->format == ma_format_f32) { + pConverter->weights.f32 = (float** )ma_offset_ptr(pHeap, heapLayout.weightsOffset); + for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; iChannelIn += 1) { + pConverter->weights.f32[iChannelIn] = (float*)ma_offset_ptr(pHeap, heapLayout.weightsOffset + ((sizeof(float*) * pConverter->channelsIn) + (sizeof(float) * pConverter->channelsOut * iChannelIn))); + } + } else { + pConverter->weights.s16 = (ma_int32**)ma_offset_ptr(pHeap, heapLayout.weightsOffset); + for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; iChannelIn += 1) { + pConverter->weights.s16[iChannelIn] = (ma_int32*)ma_offset_ptr(pHeap, heapLayout.weightsOffset + ((sizeof(ma_int32*) * pConverter->channelsIn) + (sizeof(ma_int32) * pConverter->channelsOut * iChannelIn))); } } - } - - - /* If the input and output channels and channel maps are the same we should use a passthrough. */ - if (pConverter->channelsIn == pConverter->channelsOut) { - if (ma_channel_map_equal(pConverter->channelsIn, pConverter->channelMapIn, pConverter->channelMapOut)) { - pConverter->isPassthrough = MA_TRUE; - } - if (ma_channel_map_blank(pConverter->channelsIn, pConverter->channelMapIn) || ma_channel_map_blank(pConverter->channelsOut, pConverter->channelMapOut)) { - pConverter->isPassthrough = MA_TRUE; - } - } - - - /* - We can use a simple case for expanding the mono channel. This will used when expanding a mono input into any output so long - as no LFE is present in the output. - */ - if (!pConverter->isPassthrough) { - if (pConverter->channelsIn == 1 && pConverter->channelMapIn[0] == MA_CHANNEL_MONO) { - /* Optimal case if no LFE is in the output channel map. */ - pConverter->isSimpleMonoExpansion = MA_TRUE; - if (ma_channel_map_contains_channel_position(pConverter->channelsOut, pConverter->channelMapOut, MA_CHANNEL_LFE)) { - pConverter->isSimpleMonoExpansion = MA_FALSE; - } - } - } - - /* Another optimized case is stereo to mono. */ - if (!pConverter->isPassthrough) { - if (pConverter->channelsOut == 1 && pConverter->channelMapOut[0] == MA_CHANNEL_MONO && pConverter->channelsIn == 2) { - /* Optimal case if no LFE is in the input channel map. */ - pConverter->isStereoToMono = MA_TRUE; - if (ma_channel_map_contains_channel_position(pConverter->channelsIn, pConverter->channelMapIn, MA_CHANNEL_LFE)) { - pConverter->isStereoToMono = MA_FALSE; - } - } - } - - - /* - Here is where we do a bit of pre-processing to know how each channel should be combined to make up the output. Rules: - - 1) If it's a passthrough, do nothing - it's just a simple memcpy(). - 2) If the channel counts are the same and every channel position in the input map is present in the output map, use a - simple shuffle. An example might be different 5.1 channel layouts. - 3) Otherwise channels are blended based on spatial locality. - */ - if (!pConverter->isPassthrough) { - if (pConverter->channelsIn == pConverter->channelsOut) { - ma_bool32 areAllChannelPositionsPresent = MA_TRUE; - for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; ++iChannelIn) { - ma_bool32 isInputChannelPositionInOutput = MA_FALSE; - for (iChannelOut = 0; iChannelOut < pConverter->channelsOut; ++iChannelOut) { - if (pConverter->channelMapIn[iChannelIn] == pConverter->channelMapOut[iChannelOut]) { - isInputChannelPositionInOutput = MA_TRUE; - break; - } - } - - if (!isInputChannelPositionInOutput) { - areAllChannelPositionsPresent = MA_FALSE; - break; + /* Silence our weights by default. */ + for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; iChannelIn += 1) { + for (iChannelOut = 0; iChannelOut < pConverter->channelsOut; iChannelOut += 1) { + if (pConverter->format == ma_format_f32) { + pConverter->weights.f32[iChannelIn][iChannelOut] = 0.0f; + } else { + pConverter->weights.s16[iChannelIn][iChannelOut] = 0; } } + } - if (areAllChannelPositionsPresent) { - pConverter->isSimpleShuffle = MA_TRUE; + /* + We now need to fill out our weights table. This is determined by the mixing mode. + */ + switch (pConverter->mixingMode) + { + case ma_channel_mix_mode_custom_weights: + { + if (pConfig->ppWeights == NULL) { + return MA_INVALID_ARGS; /* Config specified a custom weights mixing mode, but no custom weights have been specified. */ + } - /* - All the router will be doing is rearranging channels which means all we need to do is use a shuffling table which is just - a mapping between the index of the input channel to the index of the output channel. - */ - for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; ++iChannelIn) { - for (iChannelOut = 0; iChannelOut < pConverter->channelsOut; ++iChannelOut) { - if (pConverter->channelMapIn[iChannelIn] == pConverter->channelMapOut[iChannelOut]) { - pConverter->shuffleTable[iChannelIn] = (ma_uint8)iChannelOut; - break; + for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; iChannelIn += 1) { + for (iChannelOut = 0; iChannelOut < pConverter->channelsOut; iChannelOut += 1) { + float weight = pConfig->ppWeights[iChannelIn][iChannelOut]; + + if (pConverter->format == ma_format_f32) { + pConverter->weights.f32[iChannelIn][iChannelOut] = weight; + } else { + pConverter->weights.s16[iChannelIn][iChannelOut] = ma_channel_converter_float_to_fixed(weight); } } } - } - } - } + } break; - - /* - Here is where weights are calculated. Note that we calculate the weights at all times, even when using a passthrough and simple - shuffling. We use different algorithms for calculating weights depending on our mixing mode. - - In simple mode we don't do any blending (except for converting between mono, which is done in a later step). Instead we just - map 1:1 matching channels. In this mode, if no channels in the input channel map correspond to anything in the output channel - map, nothing will be heard! - */ - - /* In all cases we need to make sure all channels that are present in both channel maps have a 1:1 mapping. */ - for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; ++iChannelIn) { - ma_channel channelPosIn = pConverter->channelMapIn[iChannelIn]; - - for (iChannelOut = 0; iChannelOut < pConverter->channelsOut; ++iChannelOut) { - ma_channel channelPosOut = pConverter->channelMapOut[iChannelOut]; - - if (channelPosIn == channelPosOut) { - if (pConverter->format == ma_format_f32) { - pConverter->weights.f32[iChannelIn][iChannelOut] = 1; - } else { - pConverter->weights.s16[iChannelIn][iChannelOut] = (1 << MA_CHANNEL_CONVERTER_FIXED_POINT_SHIFT); - } - } - } - } - - /* - The mono channel is accumulated on all other channels, except LFE. Make sure in this loop we exclude output mono channels since - they were handled in the pass above. - */ - for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; ++iChannelIn) { - ma_channel channelPosIn = pConverter->channelMapIn[iChannelIn]; - - if (channelPosIn == MA_CHANNEL_MONO) { - for (iChannelOut = 0; iChannelOut < pConverter->channelsOut; ++iChannelOut) { - ma_channel channelPosOut = pConverter->channelMapOut[iChannelOut]; - - if (channelPosOut != MA_CHANNEL_NONE && channelPosOut != MA_CHANNEL_MONO && channelPosOut != MA_CHANNEL_LFE) { + case ma_channel_mix_mode_simple: + { + /* In simple mode, excess channels need to be silenced or dropped. */ + ma_uint32 iChannel; + for (iChannel = 0; iChannel < ma_min(pConverter->channelsIn, pConverter->channelsOut); iChannel += 1) { if (pConverter->format == ma_format_f32) { - pConverter->weights.f32[iChannelIn][iChannelOut] = 1; + if (pConverter->weights.f32[iChannel][iChannel] == 0) { + pConverter->weights.f32[iChannel][iChannel] = 1; + } } else { - pConverter->weights.s16[iChannelIn][iChannelOut] = (1 << MA_CHANNEL_CONVERTER_FIXED_POINT_SHIFT); - } - } - } - } - } - - /* The output mono channel is the average of all non-none, non-mono and non-lfe input channels. */ - { - ma_uint32 len = 0; - for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; ++iChannelIn) { - ma_channel channelPosIn = pConverter->channelMapIn[iChannelIn]; - - if (channelPosIn != MA_CHANNEL_NONE && channelPosIn != MA_CHANNEL_MONO && channelPosIn != MA_CHANNEL_LFE) { - len += 1; - } - } - - if (len > 0) { - float monoWeight = 1.0f / len; - - for (iChannelOut = 0; iChannelOut < pConverter->channelsOut; ++iChannelOut) { - ma_channel channelPosOut = pConverter->channelMapOut[iChannelOut]; - - if (channelPosOut == MA_CHANNEL_MONO) { - for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; ++iChannelIn) { - ma_channel channelPosIn = pConverter->channelMapIn[iChannelIn]; - - if (channelPosIn != MA_CHANNEL_NONE && channelPosIn != MA_CHANNEL_MONO && channelPosIn != MA_CHANNEL_LFE) { - if (pConverter->format == ma_format_f32) { - pConverter->weights.f32[iChannelIn][iChannelOut] = monoWeight; - } else { - pConverter->weights.s16[iChannelIn][iChannelOut] = ma_channel_converter_float_to_fixed(monoWeight); - } + if (pConverter->weights.s16[iChannel][iChannel] == 0) { + pConverter->weights.s16[iChannel][iChannel] = ma_channel_converter_float_to_fixed(1); } } } - } - } - } + } break; + case ma_channel_mix_mode_rectangular: + default: + { + /* Unmapped input channels. */ + for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; ++iChannelIn) { + ma_channel channelPosIn = pConverter->pChannelMapIn[iChannelIn]; - /* Input and output channels that are not present on the other side need to be blended in based on spatial locality. */ - switch (pConverter->mixingMode) - { - case ma_channel_mix_mode_rectangular: - { - /* Unmapped input channels. */ - for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; ++iChannelIn) { - ma_channel channelPosIn = pConverter->channelMapIn[iChannelIn]; + if (ma_is_spatial_channel_position(channelPosIn)) { + if (!ma_channel_map_contains_channel_position(pConverter->channelsOut, pConverter->pChannelMapOut, channelPosIn)) { + for (iChannelOut = 0; iChannelOut < pConverter->channelsOut; ++iChannelOut) { + ma_channel channelPosOut = pConverter->pChannelMapOut[iChannelOut]; - if (ma_is_spatial_channel_position(channelPosIn)) { - if (!ma_channel_map_contains_channel_position(pConverter->channelsOut, pConverter->channelMapOut, channelPosIn)) { - for (iChannelOut = 0; iChannelOut < pConverter->channelsOut; ++iChannelOut) { - ma_channel channelPosOut = pConverter->channelMapOut[iChannelOut]; - - if (ma_is_spatial_channel_position(channelPosOut)) { - float weight = 0; - if (pConverter->mixingMode == ma_channel_mix_mode_rectangular) { - weight = ma_calculate_channel_position_rectangular_weight(channelPosIn, channelPosOut); - } - - /* Only apply the weight if we haven't already got some contribution from the respective channels. */ - if (pConverter->format == ma_format_f32) { - if (pConverter->weights.f32[iChannelIn][iChannelOut] == 0) { - pConverter->weights.f32[iChannelIn][iChannelOut] = weight; + if (ma_is_spatial_channel_position(channelPosOut)) { + float weight = 0; + if (pConverter->mixingMode == ma_channel_mix_mode_rectangular) { + weight = ma_calculate_channel_position_rectangular_weight(channelPosIn, channelPosOut); } - } else { - if (pConverter->weights.s16[iChannelIn][iChannelOut] == 0) { - pConverter->weights.s16[iChannelIn][iChannelOut] = ma_channel_converter_float_to_fixed(weight); + + /* Only apply the weight if we haven't already got some contribution from the respective channels. */ + if (pConverter->format == ma_format_f32) { + if (pConverter->weights.f32[iChannelIn][iChannelOut] == 0) { + pConverter->weights.f32[iChannelIn][iChannelOut] = weight; + } + } else { + if (pConverter->weights.s16[iChannelIn][iChannelOut] == 0) { + pConverter->weights.s16[iChannelIn][iChannelOut] = ma_channel_converter_float_to_fixed(weight); + } } } } } } } - } - /* Unmapped output channels. */ - for (iChannelOut = 0; iChannelOut < pConverter->channelsOut; ++iChannelOut) { - ma_channel channelPosOut = pConverter->channelMapOut[iChannelOut]; + /* Unmapped output channels. */ + for (iChannelOut = 0; iChannelOut < pConverter->channelsOut; ++iChannelOut) { + ma_channel channelPosOut = pConverter->pChannelMapOut[iChannelOut]; - if (ma_is_spatial_channel_position(channelPosOut)) { - if (!ma_channel_map_contains_channel_position(pConverter->channelsIn, pConverter->channelMapIn, channelPosOut)) { - for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; ++iChannelIn) { - ma_channel channelPosIn = pConverter->channelMapIn[iChannelIn]; + if (ma_is_spatial_channel_position(channelPosOut)) { + if (!ma_channel_map_contains_channel_position(pConverter->channelsIn, pConverter->pChannelMapIn, channelPosOut)) { + for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; ++iChannelIn) { + ma_channel channelPosIn = pConverter->pChannelMapIn[iChannelIn]; - if (ma_is_spatial_channel_position(channelPosIn)) { - float weight = 0; - if (pConverter->mixingMode == ma_channel_mix_mode_rectangular) { - weight = ma_calculate_channel_position_rectangular_weight(channelPosIn, channelPosOut); - } - - /* Only apply the weight if we haven't already got some contribution from the respective channels. */ - if (pConverter->format == ma_format_f32) { - if (pConverter->weights.f32[iChannelIn][iChannelOut] == 0) { - pConverter->weights.f32[iChannelIn][iChannelOut] = weight; + if (ma_is_spatial_channel_position(channelPosIn)) { + float weight = 0; + if (pConverter->mixingMode == ma_channel_mix_mode_rectangular) { + weight = ma_calculate_channel_position_rectangular_weight(channelPosIn, channelPosOut); } - } else { - if (pConverter->weights.s16[iChannelIn][iChannelOut] == 0) { - pConverter->weights.s16[iChannelIn][iChannelOut] = ma_channel_converter_float_to_fixed(weight); + + /* Only apply the weight if we haven't already got some contribution from the respective channels. */ + if (pConverter->format == ma_format_f32) { + if (pConverter->weights.f32[iChannelIn][iChannelOut] == 0) { + pConverter->weights.f32[iChannelIn][iChannelOut] = weight; + } + } else { + if (pConverter->weights.s16[iChannelIn][iChannelOut] == 0) { + pConverter->weights.s16[iChannelIn][iChannelOut] = ma_channel_converter_float_to_fixed(weight); + } } } } } } } - } - } break; - - case ma_channel_mix_mode_simple: - { - /* In simple mode, excess channels need to be silenced or dropped. */ - ma_uint32 iChannel; - for (iChannel = 0; iChannel < ma_min(pConverter->channelsIn, pConverter->channelsOut); iChannel += 1) { - if (pConverter->format == ma_format_f32) { - if (pConverter->weights.f32[iChannel][iChannel] == 0) { - pConverter->weights.f32[iChannel][iChannel] = 1; - } - } else { - if (pConverter->weights.s16[iChannel][iChannel] == 0) { - pConverter->weights.s16[iChannel][iChannel] = ma_channel_converter_float_to_fixed(1); - } - } - } - } break; - - case ma_channel_mix_mode_custom_weights: - default: - { - /* Fallthrough. */ - } break; + } break; + } } - return MA_SUCCESS; } -MA_API void ma_channel_converter_uninit(ma_channel_converter* pConverter) +MA_API ma_result ma_channel_converter_init(const ma_channel_converter_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_channel_converter* pConverter) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_channel_converter_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_channel_converter_init_preallocated(pConfig, pHeap, pConverter); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pConverter->_ownsHeap = MA_TRUE; + return MA_SUCCESS; +} + +MA_API void ma_channel_converter_uninit(ma_channel_converter* pConverter, const ma_allocation_callbacks* pAllocationCallbacks) { if (pConverter == NULL) { return; } + + if (pConverter->_ownsHeap) { + ma_free(pConverter->_pHeap, pAllocationCallbacks); + } } static ma_result ma_channel_converter_process_pcm_frames__passthrough(ma_channel_converter* pConverter, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount) @@ -40650,103 +51277,17 @@ static ma_result ma_channel_converter_process_pcm_frames__passthrough(ma_channel return MA_SUCCESS; } -static ma_result ma_channel_converter_process_pcm_frames__simple_shuffle(ma_channel_converter* pConverter, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount) +static ma_result ma_channel_converter_process_pcm_frames__shuffle(ma_channel_converter* pConverter, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount) { - ma_uint32 iFrame; - ma_uint32 iChannelIn; - MA_ASSERT(pConverter != NULL); MA_ASSERT(pFramesOut != NULL); MA_ASSERT(pFramesIn != NULL); MA_ASSERT(pConverter->channelsIn == pConverter->channelsOut); - switch (pConverter->format) - { - case ma_format_u8: - { - /* */ ma_uint8* pFramesOutU8 = ( ma_uint8*)pFramesOut; - const ma_uint8* pFramesInU8 = (const ma_uint8*)pFramesIn; - - for (iFrame = 0; iFrame < frameCount; iFrame += 1) { - for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; ++iChannelIn) { - pFramesOutU8[pConverter->shuffleTable[iChannelIn]] = pFramesInU8[iChannelIn]; - } - - pFramesOutU8 += pConverter->channelsOut; - pFramesInU8 += pConverter->channelsIn; - } - } break; - - case ma_format_s16: - { - /* */ ma_int16* pFramesOutS16 = ( ma_int16*)pFramesOut; - const ma_int16* pFramesInS16 = (const ma_int16*)pFramesIn; - - for (iFrame = 0; iFrame < frameCount; iFrame += 1) { - for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; ++iChannelIn) { - pFramesOutS16[pConverter->shuffleTable[iChannelIn]] = pFramesInS16[iChannelIn]; - } - - pFramesOutS16 += pConverter->channelsOut; - pFramesInS16 += pConverter->channelsIn; - } - } break; - - case ma_format_s24: - { - /* */ ma_uint8* pFramesOutS24 = ( ma_uint8*)pFramesOut; - const ma_uint8* pFramesInS24 = (const ma_uint8*)pFramesIn; - - for (iFrame = 0; iFrame < frameCount; iFrame += 1) { - for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; ++iChannelIn) { - ma_uint32 iChannelOut = pConverter->shuffleTable[iChannelIn]; - pFramesOutS24[iChannelOut*3 + 0] = pFramesInS24[iChannelIn*3 + 0]; - pFramesOutS24[iChannelOut*3 + 1] = pFramesInS24[iChannelIn*3 + 1]; - pFramesOutS24[iChannelOut*3 + 2] = pFramesInS24[iChannelIn*3 + 2]; - } - - pFramesOutS24 += pConverter->channelsOut*3; - pFramesInS24 += pConverter->channelsIn*3; - } - } break; - - case ma_format_s32: - { - /* */ ma_int32* pFramesOutS32 = ( ma_int32*)pFramesOut; - const ma_int32* pFramesInS32 = (const ma_int32*)pFramesIn; - - for (iFrame = 0; iFrame < frameCount; iFrame += 1) { - for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; ++iChannelIn) { - pFramesOutS32[pConverter->shuffleTable[iChannelIn]] = pFramesInS32[iChannelIn]; - } - - pFramesOutS32 += pConverter->channelsOut; - pFramesInS32 += pConverter->channelsIn; - } - } break; - - case ma_format_f32: - { - /* */ float* pFramesOutF32 = ( float*)pFramesOut; - const float* pFramesInF32 = (const float*)pFramesIn; - - for (iFrame = 0; iFrame < frameCount; iFrame += 1) { - for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; ++iChannelIn) { - pFramesOutF32[pConverter->shuffleTable[iChannelIn]] = pFramesInF32[iChannelIn]; - } - - pFramesOutF32 += pConverter->channelsOut; - pFramesInF32 += pConverter->channelsIn; - } - } break; - - default: return MA_INVALID_OPERATION; /* Unknown format. */ - } - - return MA_SUCCESS; + return ma_channel_map_apply_shuffle_table(pFramesOut, pConverter->channelsOut, pFramesIn, pConverter->channelsIn, frameCount, pConverter->pShuffleTable, pConverter->format); } -static ma_result ma_channel_converter_process_pcm_frames__simple_mono_expansion(ma_channel_converter* pConverter, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount) +static ma_result ma_channel_converter_process_pcm_frames__mono_in(ma_channel_converter* pConverter, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount) { ma_uint64 iFrame; @@ -40846,14 +51387,14 @@ static ma_result ma_channel_converter_process_pcm_frames__simple_mono_expansion( return MA_SUCCESS; } -static ma_result ma_channel_converter_process_pcm_frames__stereo_to_mono(ma_channel_converter* pConverter, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount) +static ma_result ma_channel_converter_process_pcm_frames__mono_out(ma_channel_converter* pConverter, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount) { ma_uint64 iFrame; + ma_uint32 iChannel; MA_ASSERT(pConverter != NULL); MA_ASSERT(pFramesOut != NULL); MA_ASSERT(pFramesIn != NULL); - MA_ASSERT(pConverter->channelsIn == 2); MA_ASSERT(pConverter->channelsOut == 1); switch (pConverter->format) @@ -40864,7 +51405,12 @@ static ma_result ma_channel_converter_process_pcm_frames__stereo_to_mono(ma_chan const ma_uint8* pFramesInU8 = (const ma_uint8*)pFramesIn; for (iFrame = 0; iFrame < frameCount; ++iFrame) { - pFramesOutU8[iFrame] = ma_clip_u8((ma_int16)((ma_pcm_sample_u8_to_s16_no_scale(pFramesInU8[iFrame*2+0]) + ma_pcm_sample_u8_to_s16_no_scale(pFramesInU8[iFrame*2+1])) / 2)); + ma_int32 t = 0; + for (iChannel = 0; iChannel < pConverter->channelsIn; iChannel += 1) { + t += ma_pcm_sample_u8_to_s16_no_scale(pFramesInU8[iFrame*pConverter->channelsIn + iChannel]); + } + + pFramesOutU8[iFrame] = ma_clip_u8(t / pConverter->channelsOut); } } break; @@ -40874,7 +51420,12 @@ static ma_result ma_channel_converter_process_pcm_frames__stereo_to_mono(ma_chan const ma_int16* pFramesInS16 = (const ma_int16*)pFramesIn; for (iFrame = 0; iFrame < frameCount; ++iFrame) { - pFramesOutS16[iFrame] = (ma_int16)(((ma_int32)pFramesInS16[iFrame*2+0] + (ma_int32)pFramesInS16[iFrame*2+1]) / 2); + ma_int32 t = 0; + for (iChannel = 0; iChannel < pConverter->channelsIn; iChannel += 1) { + t += pFramesInS16[iFrame*pConverter->channelsIn + iChannel]; + } + + pFramesOutS16[iFrame] = (ma_int16)(t / pConverter->channelsIn); } } break; @@ -40884,9 +51435,12 @@ static ma_result ma_channel_converter_process_pcm_frames__stereo_to_mono(ma_chan const ma_uint8* pFramesInS24 = (const ma_uint8*)pFramesIn; for (iFrame = 0; iFrame < frameCount; ++iFrame) { - ma_int64 s24_0 = ma_pcm_sample_s24_to_s32_no_scale(&pFramesInS24[(iFrame*2+0)*3]); - ma_int64 s24_1 = ma_pcm_sample_s24_to_s32_no_scale(&pFramesInS24[(iFrame*2+1)*3]); - ma_pcm_sample_s32_to_s24_no_scale((s24_0 + s24_1) / 2, &pFramesOutS24[iFrame*3]); + ma_int64 t = 0; + for (iChannel = 0; iChannel < pConverter->channelsIn; iChannel += 1) { + t += ma_pcm_sample_s24_to_s32_no_scale(&pFramesInS24[(iFrame*pConverter->channelsIn + iChannel)*3]); + } + + ma_pcm_sample_s32_to_s24_no_scale(t / pConverter->channelsIn, &pFramesOutS24[iFrame*3]); } } break; @@ -40896,7 +51450,12 @@ static ma_result ma_channel_converter_process_pcm_frames__stereo_to_mono(ma_chan const ma_int32* pFramesInS32 = (const ma_int32*)pFramesIn; for (iFrame = 0; iFrame < frameCount; ++iFrame) { - pFramesOutS32[iFrame] = (ma_int16)(((ma_int32)pFramesInS32[iFrame*2+0] + (ma_int32)pFramesInS32[iFrame*2+1]) / 2); + ma_int64 t = 0; + for (iChannel = 0; iChannel < pConverter->channelsIn; iChannel += 1) { + t += pFramesInS32[iFrame*pConverter->channelsIn + iChannel]; + } + + pFramesOutS32[iFrame] = (ma_int32)(t / pConverter->channelsIn); } } break; @@ -40906,7 +51465,12 @@ static ma_result ma_channel_converter_process_pcm_frames__stereo_to_mono(ma_chan const float* pFramesInF32 = (const float*)pFramesIn; for (iFrame = 0; iFrame < frameCount; ++iFrame) { - pFramesOutF32[iFrame] = (pFramesInF32[iFrame*2+0] + pFramesInF32[iFrame*2+1]) * 0.5f; + float t = 0; + for (iChannel = 0; iChannel < pConverter->channelsIn; iChannel += 1) { + t += pFramesInF32[iFrame*pConverter->channelsIn + iChannel]; + } + + pFramesOutF32[iFrame] = t / pConverter->channelsIn; } } break; @@ -41037,19 +51601,42 @@ MA_API ma_result ma_channel_converter_process_pcm_frames(ma_channel_converter* p return MA_SUCCESS; } - if (pConverter->isPassthrough) { - return ma_channel_converter_process_pcm_frames__passthrough(pConverter, pFramesOut, pFramesIn, frameCount); - } else if (pConverter->isSimpleShuffle) { - return ma_channel_converter_process_pcm_frames__simple_shuffle(pConverter, pFramesOut, pFramesIn, frameCount); - } else if (pConverter->isSimpleMonoExpansion) { - return ma_channel_converter_process_pcm_frames__simple_mono_expansion(pConverter, pFramesOut, pFramesIn, frameCount); - } else if (pConverter->isStereoToMono) { - return ma_channel_converter_process_pcm_frames__stereo_to_mono(pConverter, pFramesOut, pFramesIn, frameCount); - } else { - return ma_channel_converter_process_pcm_frames__weights(pConverter, pFramesOut, pFramesIn, frameCount); + switch (pConverter->conversionPath) + { + case ma_channel_conversion_path_passthrough: return ma_channel_converter_process_pcm_frames__passthrough(pConverter, pFramesOut, pFramesIn, frameCount); + case ma_channel_conversion_path_mono_out: return ma_channel_converter_process_pcm_frames__mono_out(pConverter, pFramesOut, pFramesIn, frameCount); + case ma_channel_conversion_path_mono_in: return ma_channel_converter_process_pcm_frames__mono_in(pConverter, pFramesOut, pFramesIn, frameCount); + case ma_channel_conversion_path_shuffle: return ma_channel_converter_process_pcm_frames__shuffle(pConverter, pFramesOut, pFramesIn, frameCount); + case ma_channel_conversion_path_weights: + default: + { + return ma_channel_converter_process_pcm_frames__weights(pConverter, pFramesOut, pFramesIn, frameCount); + } } } +MA_API ma_result ma_channel_converter_get_input_channel_map(const ma_channel_converter* pConverter, ma_channel* pChannelMap, size_t channelMapCap) +{ + if (pConverter == NULL || pChannelMap == NULL) { + return MA_INVALID_ARGS; + } + + ma_channel_map_copy_or_default(pChannelMap, channelMapCap, pConverter->pChannelMapIn, pConverter->channelsIn); + + return MA_SUCCESS; +} + +MA_API ma_result ma_channel_converter_get_output_channel_map(const ma_channel_converter* pConverter, ma_channel* pChannelMap, size_t channelMapCap) +{ + if (pConverter == NULL || pChannelMap == NULL) { + return MA_INVALID_ARGS; + } + + ma_channel_map_copy_or_default(pChannelMap, channelMapCap, pConverter->pChannelMapOut, pConverter->channelsOut); + + return MA_SUCCESS; +} + /************************************************************************************************************************************************************** @@ -41063,14 +51650,10 @@ MA_API ma_data_converter_config ma_data_converter_config_init_default() config.ditherMode = ma_dither_mode_none; config.resampling.algorithm = ma_resample_algorithm_linear; - config.resampling.allowDynamicSampleRate = MA_FALSE; /* Disable dynamic sample rates by default because dynamic rate adjustments should be quite rare and it allows an optimization for cases when the in and out sample rates are the same. */ + config.allowDynamicSampleRate = MA_FALSE; /* Disable dynamic sample rates by default because dynamic rate adjustments should be quite rare and it allows an optimization for cases when the in and out sample rates are the same. */ /* Linear resampling defaults. */ config.resampling.linear.lpfOrder = 1; - config.resampling.linear.lpfNyquistFactor = 1; - - /* Speex resampling defaults. */ - config.resampling.speex.quality = 3; return config; } @@ -41080,18 +51663,168 @@ MA_API ma_data_converter_config ma_data_converter_config_init(ma_format formatIn ma_data_converter_config config = ma_data_converter_config_init_default(); config.formatIn = formatIn; config.formatOut = formatOut; - config.channelsIn = ma_min(channelsIn, MA_MAX_CHANNELS); - config.channelsOut = ma_min(channelsOut, MA_MAX_CHANNELS); + config.channelsIn = channelsIn; + config.channelsOut = channelsOut; config.sampleRateIn = sampleRateIn; config.sampleRateOut = sampleRateOut; return config; } -MA_API ma_result ma_data_converter_init(const ma_data_converter_config* pConfig, ma_data_converter* pConverter) + +typedef struct +{ + size_t sizeInBytes; + size_t channelConverterOffset; + size_t resamplerOffset; +} ma_data_converter_heap_layout; + +static ma_bool32 ma_data_converter_config_is_resampler_required(const ma_data_converter_config* pConfig) +{ + MA_ASSERT(pConfig != NULL); + + return pConfig->allowDynamicSampleRate || pConfig->sampleRateIn != pConfig->sampleRateOut; +} + +static ma_format ma_data_converter_config_get_mid_format(const ma_data_converter_config* pConfig) +{ + MA_ASSERT(pConfig != NULL); + + /* + We want to avoid as much data conversion as possible. The channel converter and linear + resampler both support s16 and f32 natively. We need to decide on the format to use for this + stage. We call this the mid format because it's used in the middle stage of the conversion + pipeline. If the output format is either s16 or f32 we use that one. If that is not the case it + will do the same thing for the input format. If it's neither we just use f32. If we are using a + custom resampling backend, we can only guarantee that f32 will be supported so we'll be forced + to use that if resampling is required. + */ + if (ma_data_converter_config_is_resampler_required(pConfig) && pConfig->resampling.algorithm != ma_resample_algorithm_linear) { + return ma_format_f32; /* <-- Force f32 since that is the only one we can guarantee will be supported by the resampler. */ + } else { + /* */ if (pConfig->formatOut == ma_format_s16 || pConfig->formatOut == ma_format_f32) { + return pConfig->formatOut; + } else if (pConfig->formatIn == ma_format_s16 || pConfig->formatIn == ma_format_f32) { + return pConfig->formatIn; + } else { + return ma_format_f32; + } + } +} + +static ma_channel_converter_config ma_channel_converter_config_init_from_data_converter_config(const ma_data_converter_config* pConfig) +{ + ma_channel_converter_config channelConverterConfig; + + MA_ASSERT(pConfig != NULL); + + channelConverterConfig = ma_channel_converter_config_init(ma_data_converter_config_get_mid_format(pConfig), pConfig->channelsIn, pConfig->pChannelMapIn, pConfig->channelsOut, pConfig->pChannelMapOut, pConfig->channelMixMode); + channelConverterConfig.ppWeights = pConfig->ppChannelWeights; + + return channelConverterConfig; +} + +static ma_resampler_config ma_resampler_config_init_from_data_converter_config(const ma_data_converter_config* pConfig) +{ + ma_resampler_config resamplerConfig; + ma_uint32 resamplerChannels; + + MA_ASSERT(pConfig != NULL); + + /* The resampler is the most expensive part of the conversion process, so we need to do it at the stage where the channel count is at it's lowest. */ + if (pConfig->channelsIn < pConfig->channelsOut) { + resamplerChannels = pConfig->channelsIn; + } else { + resamplerChannels = pConfig->channelsOut; + } + + resamplerConfig = ma_resampler_config_init(ma_data_converter_config_get_mid_format(pConfig), resamplerChannels, pConfig->sampleRateIn, pConfig->sampleRateOut, pConfig->resampling.algorithm); + resamplerConfig.linear = pConfig->resampling.linear; + resamplerConfig.pBackendVTable = pConfig->resampling.pBackendVTable; + resamplerConfig.pBackendUserData = pConfig->resampling.pBackendUserData; + + return resamplerConfig; +} + +static ma_result ma_data_converter_get_heap_layout(const ma_data_converter_config* pConfig, ma_data_converter_heap_layout* pHeapLayout) { ma_result result; + + MA_ASSERT(pHeapLayout != NULL); + + MA_ZERO_OBJECT(pHeapLayout); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->channelsIn == 0 || pConfig->channelsOut == 0) { + return MA_INVALID_ARGS; + } + + pHeapLayout->sizeInBytes = 0; + + /* Channel converter. */ + pHeapLayout->channelConverterOffset = pHeapLayout->sizeInBytes; + { + size_t heapSizeInBytes; + ma_channel_converter_config channelConverterConfig = ma_channel_converter_config_init_from_data_converter_config(pConfig); + + result = ma_channel_converter_get_heap_size(&channelConverterConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + pHeapLayout->sizeInBytes += heapSizeInBytes; + } + + /* Resampler. */ + pHeapLayout->resamplerOffset = pHeapLayout->sizeInBytes; + if (ma_data_converter_config_is_resampler_required(pConfig)) { + size_t heapSizeInBytes; + ma_resampler_config resamplerConfig = ma_resampler_config_init_from_data_converter_config(pConfig); + + result = ma_resampler_get_heap_size(&resamplerConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + pHeapLayout->sizeInBytes += heapSizeInBytes; + } + + /* Make sure allocation size is aligned. */ + pHeapLayout->sizeInBytes = ma_align_64(pHeapLayout->sizeInBytes); + + return MA_SUCCESS; +} + +MA_API ma_result ma_data_converter_get_heap_size(const ma_data_converter_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_result result; + ma_data_converter_heap_layout heapLayout; + + if (pHeapSizeInBytes == NULL) { + return MA_INVALID_ARGS; + } + + *pHeapSizeInBytes = 0; + + result = ma_data_converter_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + *pHeapSizeInBytes = heapLayout.sizeInBytes; + + return MA_SUCCESS; +} + +MA_API ma_result ma_data_converter_init_preallocated(const ma_data_converter_config* pConfig, void* pHeap, ma_data_converter* pConverter) +{ + ma_result result; + ma_data_converter_heap_layout heapLayout; ma_format midFormat; + ma_bool32 isResamplingRequired; if (pConverter == NULL) { return MA_INVALID_ARGS; @@ -41099,82 +51832,52 @@ MA_API ma_result ma_data_converter_init(const ma_data_converter_config* pConfig, MA_ZERO_OBJECT(pConverter); - if (pConfig == NULL) { - return MA_INVALID_ARGS; + result = ma_data_converter_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; } - pConverter->config = *pConfig; + pConverter->_pHeap = pHeap; + MA_ZERO_MEMORY(pHeap, heapLayout.sizeInBytes); - /* Basic validation. */ - if (pConfig->channelsIn < MA_MIN_CHANNELS || pConfig->channelsOut < MA_MIN_CHANNELS || - pConfig->channelsIn > MA_MAX_CHANNELS || pConfig->channelsOut > MA_MAX_CHANNELS) { - return MA_INVALID_ARGS; - } + pConverter->formatIn = pConfig->formatIn; + pConverter->formatOut = pConfig->formatOut; + pConverter->channelsIn = pConfig->channelsIn; + pConverter->channelsOut = pConfig->channelsOut; + pConverter->sampleRateIn = pConfig->sampleRateIn; + pConverter->sampleRateOut = pConfig->sampleRateOut; + pConverter->ditherMode = pConfig->ditherMode; /* - We want to avoid as much data conversion as possible. The channel converter and resampler both support s16 and f32 natively. We need to decide - on the format to use for this stage. We call this the mid format because it's used in the middle stage of the conversion pipeline. If the output - format is either s16 or f32 we use that one. If that is not the case it will do the same thing for the input format. If it's neither we just - use f32. + Determine if resampling is required. We need to do this so we can determine an appropriate + mid format to use. If resampling is required, the mid format must be ma_format_f32 since + that is the only one that is guaranteed to supported by custom resampling backends. */ - /* */ if (pConverter->config.formatOut == ma_format_s16 || pConverter->config.formatOut == ma_format_f32) { - midFormat = pConverter->config.formatOut; - } else if (pConverter->config.formatIn == ma_format_s16 || pConverter->config.formatIn == ma_format_f32) { - midFormat = pConverter->config.formatIn; - } else { - midFormat = ma_format_f32; - } + isResamplingRequired = ma_data_converter_config_is_resampler_required(pConfig); + midFormat = ma_data_converter_config_get_mid_format(pConfig); + /* Channel converter. We always initialize this, but we check if it configures itself as a passthrough to determine whether or not it's needed. */ { - ma_uint32 iChannelIn; - ma_uint32 iChannelOut; - ma_channel_converter_config channelConverterConfig; + ma_channel_converter_config channelConverterConfig = ma_channel_converter_config_init_from_data_converter_config(pConfig); - channelConverterConfig = ma_channel_converter_config_init(midFormat, pConverter->config.channelsIn, pConverter->config.channelMapIn, pConverter->config.channelsOut, pConverter->config.channelMapOut, pConverter->config.channelMixMode); - - /* Channel weights. */ - for (iChannelIn = 0; iChannelIn < pConverter->config.channelsIn; iChannelIn += 1) { - for (iChannelOut = 0; iChannelOut < pConverter->config.channelsOut; iChannelOut += 1) { - channelConverterConfig.weights[iChannelIn][iChannelOut] = pConverter->config.channelWeights[iChannelIn][iChannelOut]; - } - } - - result = ma_channel_converter_init(&channelConverterConfig, &pConverter->channelConverter); + result = ma_channel_converter_init_preallocated(&channelConverterConfig, ma_offset_ptr(pHeap, heapLayout.channelConverterOffset), &pConverter->channelConverter); if (result != MA_SUCCESS) { return result; } /* If the channel converter is not a passthrough we need to enable it. Otherwise we can skip it. */ - if (pConverter->channelConverter.isPassthrough == MA_FALSE) { + if (pConverter->channelConverter.conversionPath != ma_channel_conversion_path_passthrough) { pConverter->hasChannelConverter = MA_TRUE; } } - /* Always enable dynamic sample rates if the input sample rate is different because we're always going to need a resampler in this case anyway. */ - if (pConverter->config.resampling.allowDynamicSampleRate == MA_FALSE) { - pConverter->config.resampling.allowDynamicSampleRate = pConverter->config.sampleRateIn != pConverter->config.sampleRateOut; - } - /* Resampler. */ - if (pConverter->config.resampling.allowDynamicSampleRate) { - ma_resampler_config resamplerConfig; - ma_uint32 resamplerChannels; + if (isResamplingRequired) { + ma_resampler_config resamplerConfig = ma_resampler_config_init_from_data_converter_config(pConfig); - /* The resampler is the most expensive part of the conversion process, so we need to do it at the stage where the channel count is at it's lowest. */ - if (pConverter->config.channelsIn < pConverter->config.channelsOut) { - resamplerChannels = pConverter->config.channelsIn; - } else { - resamplerChannels = pConverter->config.channelsOut; - } - - resamplerConfig = ma_resampler_config_init(midFormat, resamplerChannels, pConverter->config.sampleRateIn, pConverter->config.sampleRateOut, pConverter->config.resampling.algorithm); - resamplerConfig.linear.lpfOrder = pConverter->config.resampling.linear.lpfOrder; - resamplerConfig.linear.lpfNyquistFactor = pConverter->config.resampling.linear.lpfNyquistFactor; - resamplerConfig.speex.quality = pConverter->config.resampling.speex.quality; - - result = ma_resampler_init(&resamplerConfig, &pConverter->resampler); + result = ma_resampler_init_preallocated(&resamplerConfig, ma_offset_ptr(pHeap, heapLayout.resamplerOffset), &pConverter->resampler); if (result != MA_SUCCESS) { return result; } @@ -41186,7 +51889,7 @@ MA_API ma_result ma_data_converter_init(const ma_data_converter_config* pConfig, /* We can simplify pre- and post-format conversion if we have neither channel conversion nor resampling. */ if (pConverter->hasChannelConverter == MA_FALSE && pConverter->hasResampler == MA_FALSE) { /* We have neither channel conversion nor resampling so we'll only need one of pre- or post-format conversion, or none if the input and output formats are the same. */ - if (pConverter->config.formatIn == pConverter->config.formatOut) { + if (pConverter->formatIn == pConverter->formatOut) { /* The formats are the same so we can just pass through. */ pConverter->hasPreFormatConversion = MA_FALSE; pConverter->hasPostFormatConversion = MA_FALSE; @@ -41197,10 +51900,10 @@ MA_API ma_result ma_data_converter_init(const ma_data_converter_config* pConfig, } } else { /* We have a channel converter and/or resampler so we'll need channel conversion based on the mid format. */ - if (pConverter->config.formatIn != midFormat) { - pConverter->hasPreFormatConversion = MA_TRUE; + if (pConverter->formatIn != midFormat) { + pConverter->hasPreFormatConversion = MA_TRUE; } - if (pConverter->config.formatOut != midFormat) { + if (pConverter->formatOut != midFormat) { pConverter->hasPostFormatConversion = MA_TRUE; } } @@ -41213,17 +51916,86 @@ MA_API ma_result ma_data_converter_init(const ma_data_converter_config* pConfig, pConverter->isPassthrough = MA_TRUE; } + + /* We now need to determine our execution path. */ + if (pConverter->isPassthrough) { + pConverter->executionPath = ma_data_converter_execution_path_passthrough; + } else { + if (pConverter->channelsIn < pConverter->channelsOut) { + /* Do resampling first, if necessary. */ + MA_ASSERT(pConverter->hasChannelConverter == MA_TRUE); + + if (pConverter->hasResampler) { + pConverter->executionPath = ma_data_converter_execution_path_resample_first; + } else { + pConverter->executionPath = ma_data_converter_execution_path_channels_only; + } + } else { + /* Do channel conversion first, if necessary. */ + if (pConverter->hasChannelConverter) { + if (pConverter->hasResampler) { + pConverter->executionPath = ma_data_converter_execution_path_channels_first; + } else { + pConverter->executionPath = ma_data_converter_execution_path_channels_only; + } + } else { + /* Channel routing not required. */ + if (pConverter->hasResampler) { + pConverter->executionPath = ma_data_converter_execution_path_resample_only; + } else { + pConverter->executionPath = ma_data_converter_execution_path_format_only; + } + } + } + } + return MA_SUCCESS; } -MA_API void ma_data_converter_uninit(ma_data_converter* pConverter) +MA_API ma_result ma_data_converter_init(const ma_data_converter_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_converter* pConverter) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_data_converter_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_data_converter_init_preallocated(pConfig, pHeap, pConverter); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pConverter->_ownsHeap = MA_TRUE; + return MA_SUCCESS; +} + +MA_API void ma_data_converter_uninit(ma_data_converter* pConverter, const ma_allocation_callbacks* pAllocationCallbacks) { if (pConverter == NULL) { return; } if (pConverter->hasResampler) { - ma_resampler_uninit(&pConverter->resampler); + ma_resampler_uninit(&pConverter->resampler, pAllocationCallbacks); + } + + ma_channel_converter_uninit(&pConverter->channelConverter, pAllocationCallbacks); + + if (pConverter->_ownsHeap) { + ma_free(pConverter->_pHeap, pAllocationCallbacks); } } @@ -41249,9 +52021,9 @@ static ma_result ma_data_converter_process_pcm_frames__passthrough(ma_data_conve if (pFramesOut != NULL) { if (pFramesIn != NULL) { - ma_copy_memory_64(pFramesOut, pFramesIn, frameCount * ma_get_bytes_per_frame(pConverter->config.formatOut, pConverter->config.channelsOut)); + ma_copy_memory_64(pFramesOut, pFramesIn, frameCount * ma_get_bytes_per_frame(pConverter->formatOut, pConverter->channelsOut)); } else { - ma_zero_memory_64(pFramesOut, frameCount * ma_get_bytes_per_frame(pConverter->config.formatOut, pConverter->config.channelsOut)); + ma_zero_memory_64(pFramesOut, frameCount * ma_get_bytes_per_frame(pConverter->formatOut, pConverter->channelsOut)); } } @@ -41287,9 +52059,9 @@ static ma_result ma_data_converter_process_pcm_frames__format_only(ma_data_conve if (pFramesOut != NULL) { if (pFramesIn != NULL) { - ma_convert_pcm_frames_format(pFramesOut, pConverter->config.formatOut, pFramesIn, pConverter->config.formatIn, frameCount, pConverter->config.channelsIn, pConverter->config.ditherMode); + ma_convert_pcm_frames_format(pFramesOut, pConverter->formatOut, pFramesIn, pConverter->formatIn, frameCount, pConverter->channelsIn, pConverter->ditherMode); } else { - ma_zero_memory_64(pFramesOut, frameCount * ma_get_bytes_per_frame(pConverter->config.formatOut, pConverter->config.channelsOut)); + ma_zero_memory_64(pFramesOut, frameCount * ma_get_bytes_per_frame(pConverter->formatOut, pConverter->channelsOut)); } } @@ -41329,20 +52101,20 @@ static ma_result ma_data_converter_process_pcm_frames__resample_with_format_conv while (framesProcessedOut < frameCountOut) { ma_uint8 pTempBufferOut[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - const ma_uint32 tempBufferOutCap = sizeof(pTempBufferOut) / ma_get_bytes_per_frame(pConverter->resampler.config.format, pConverter->resampler.config.channels); + const ma_uint32 tempBufferOutCap = sizeof(pTempBufferOut) / ma_get_bytes_per_frame(pConverter->resampler.format, pConverter->resampler.channels); const void* pFramesInThisIteration; /* */ void* pFramesOutThisIteration; ma_uint64 frameCountInThisIteration; ma_uint64 frameCountOutThisIteration; if (pFramesIn != NULL) { - pFramesInThisIteration = ma_offset_ptr(pFramesIn, framesProcessedIn * ma_get_bytes_per_frame(pConverter->config.formatIn, pConverter->config.channelsIn)); + pFramesInThisIteration = ma_offset_ptr(pFramesIn, framesProcessedIn * ma_get_bytes_per_frame(pConverter->formatIn, pConverter->channelsIn)); } else { pFramesInThisIteration = NULL; } if (pFramesOut != NULL) { - pFramesOutThisIteration = ma_offset_ptr(pFramesOut, framesProcessedOut * ma_get_bytes_per_frame(pConverter->config.formatOut, pConverter->config.channelsOut)); + pFramesOutThisIteration = ma_offset_ptr(pFramesOut, framesProcessedOut * ma_get_bytes_per_frame(pConverter->formatOut, pConverter->channelsOut)); } else { pFramesOutThisIteration = NULL; } @@ -41350,7 +52122,7 @@ static ma_result ma_data_converter_process_pcm_frames__resample_with_format_conv /* Do a pre format conversion if necessary. */ if (pConverter->hasPreFormatConversion) { ma_uint8 pTempBufferIn[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - const ma_uint32 tempBufferInCap = sizeof(pTempBufferIn) / ma_get_bytes_per_frame(pConverter->resampler.config.format, pConverter->resampler.config.channels); + const ma_uint32 tempBufferInCap = sizeof(pTempBufferIn) / ma_get_bytes_per_frame(pConverter->resampler.format, pConverter->resampler.channels); frameCountInThisIteration = (frameCountIn - framesProcessedIn); if (frameCountInThisIteration > tempBufferInCap) { @@ -41364,7 +52136,7 @@ static ma_result ma_data_converter_process_pcm_frames__resample_with_format_conv } if (pFramesInThisIteration != NULL) { - ma_convert_pcm_frames_format(pTempBufferIn, pConverter->resampler.config.format, pFramesInThisIteration, pConverter->config.formatIn, frameCountInThisIteration, pConverter->config.channelsIn, pConverter->config.ditherMode); + ma_convert_pcm_frames_format(pTempBufferIn, pConverter->resampler.format, pFramesInThisIteration, pConverter->formatIn, frameCountInThisIteration, pConverter->channelsIn, pConverter->ditherMode); } else { MA_ZERO_MEMORY(pTempBufferIn, sizeof(pTempBufferIn)); } @@ -41405,7 +52177,7 @@ static ma_result ma_data_converter_process_pcm_frames__resample_with_format_conv /* If we are doing a post format conversion we need to do that now. */ if (pConverter->hasPostFormatConversion) { if (pFramesOutThisIteration != NULL) { - ma_convert_pcm_frames_format(pFramesOutThisIteration, pConverter->config.formatOut, pTempBufferOut, pConverter->resampler.config.format, frameCountOutThisIteration, pConverter->resampler.config.channels, pConverter->config.ditherMode); + ma_convert_pcm_frames_format(pFramesOutThisIteration, pConverter->formatOut, pTempBufferOut, pConverter->resampler.format, frameCountOutThisIteration, pConverter->resampler.channels, pConverter->ditherMode); } } @@ -41482,13 +52254,13 @@ static ma_result ma_data_converter_process_pcm_frames__channels_only(ma_data_con ma_uint64 frameCountThisIteration; if (pFramesIn != NULL) { - pFramesInThisIteration = ma_offset_ptr(pFramesIn, framesProcessed * ma_get_bytes_per_frame(pConverter->config.formatIn, pConverter->config.channelsIn)); + pFramesInThisIteration = ma_offset_ptr(pFramesIn, framesProcessed * ma_get_bytes_per_frame(pConverter->formatIn, pConverter->channelsIn)); } else { pFramesInThisIteration = NULL; } if (pFramesOut != NULL) { - pFramesOutThisIteration = ma_offset_ptr(pFramesOut, framesProcessed * ma_get_bytes_per_frame(pConverter->config.formatOut, pConverter->config.channelsOut)); + pFramesOutThisIteration = ma_offset_ptr(pFramesOut, framesProcessed * ma_get_bytes_per_frame(pConverter->formatOut, pConverter->channelsOut)); } else { pFramesOutThisIteration = NULL; } @@ -41510,7 +52282,7 @@ static ma_result ma_data_converter_process_pcm_frames__channels_only(ma_data_con } if (pFramesInThisIteration != NULL) { - ma_convert_pcm_frames_format(pTempBufferIn, pConverter->channelConverter.format, pFramesInThisIteration, pConverter->config.formatIn, frameCountThisIteration, pConverter->config.channelsIn, pConverter->config.ditherMode); + ma_convert_pcm_frames_format(pTempBufferIn, pConverter->channelConverter.format, pFramesInThisIteration, pConverter->formatIn, frameCountThisIteration, pConverter->channelsIn, pConverter->ditherMode); } else { MA_ZERO_MEMORY(pTempBufferIn, sizeof(pTempBufferIn)); } @@ -41544,7 +52316,7 @@ static ma_result ma_data_converter_process_pcm_frames__channels_only(ma_data_con /* If we are doing a post format conversion we need to do that now. */ if (pConverter->hasPostFormatConversion) { if (pFramesOutThisIteration != NULL) { - ma_convert_pcm_frames_format(pFramesOutThisIteration, pConverter->config.formatOut, pTempBufferOut, pConverter->channelConverter.format, frameCountThisIteration, pConverter->channelConverter.channelsOut, pConverter->config.ditherMode); + ma_convert_pcm_frames_format(pFramesOutThisIteration, pConverter->formatOut, pTempBufferOut, pConverter->channelConverter.format, frameCountThisIteration, pConverter->channelConverter.channelsOut, pConverter->ditherMode); } } @@ -41562,7 +52334,7 @@ static ma_result ma_data_converter_process_pcm_frames__channels_only(ma_data_con return MA_SUCCESS; } -static ma_result ma_data_converter_process_pcm_frames__resampling_first(ma_data_converter* pConverter, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut) +static ma_result ma_data_converter_process_pcm_frames__resample_first(ma_data_converter* pConverter, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut) { ma_result result; ma_uint64 frameCountIn; @@ -41577,9 +52349,9 @@ static ma_result ma_data_converter_process_pcm_frames__resampling_first(ma_data_ ma_uint64 tempBufferOutCap; MA_ASSERT(pConverter != NULL); - MA_ASSERT(pConverter->resampler.config.format == pConverter->channelConverter.format); - MA_ASSERT(pConverter->resampler.config.channels == pConverter->channelConverter.channelsIn); - MA_ASSERT(pConverter->resampler.config.channels < pConverter->channelConverter.channelsOut); + MA_ASSERT(pConverter->resampler.format == pConverter->channelConverter.format); + MA_ASSERT(pConverter->resampler.channels == pConverter->channelConverter.channelsIn); + MA_ASSERT(pConverter->resampler.channels < pConverter->channelConverter.channelsOut); frameCountIn = 0; if (pFrameCountIn != NULL) { @@ -41594,8 +52366,8 @@ static ma_result ma_data_converter_process_pcm_frames__resampling_first(ma_data_ framesProcessedIn = 0; framesProcessedOut = 0; - tempBufferInCap = sizeof(pTempBufferIn) / ma_get_bytes_per_frame(pConverter->resampler.config.format, pConverter->resampler.config.channels); - tempBufferMidCap = sizeof(pTempBufferIn) / ma_get_bytes_per_frame(pConverter->resampler.config.format, pConverter->resampler.config.channels); + tempBufferInCap = sizeof(pTempBufferIn) / ma_get_bytes_per_frame(pConverter->resampler.format, pConverter->resampler.channels); + tempBufferMidCap = sizeof(pTempBufferIn) / ma_get_bytes_per_frame(pConverter->resampler.format, pConverter->resampler.channels); tempBufferOutCap = sizeof(pTempBufferOut) / ma_get_bytes_per_frame(pConverter->channelConverter.format, pConverter->channelConverter.channelsOut); while (framesProcessedOut < frameCountOut) { @@ -41607,10 +52379,10 @@ static ma_result ma_data_converter_process_pcm_frames__resampling_first(ma_data_ void* pChannelsBufferOut; if (pFramesIn != NULL) { - pRunningFramesIn = ma_offset_ptr(pFramesIn, framesProcessedIn * ma_get_bytes_per_frame(pConverter->config.formatIn, pConverter->config.channelsIn)); + pRunningFramesIn = ma_offset_ptr(pFramesIn, framesProcessedIn * ma_get_bytes_per_frame(pConverter->formatIn, pConverter->channelsIn)); } if (pFramesOut != NULL) { - pRunningFramesOut = ma_offset_ptr(pFramesOut, framesProcessedOut * ma_get_bytes_per_frame(pConverter->config.formatOut, pConverter->config.channelsOut)); + pRunningFramesOut = ma_offset_ptr(pFramesOut, framesProcessedOut * ma_get_bytes_per_frame(pConverter->formatOut, pConverter->channelsOut)); } /* Run input data through the resampler and output it to the temporary buffer. */ @@ -41635,16 +52407,31 @@ static ma_result ma_data_converter_process_pcm_frames__resampling_first(ma_data_ } /* We need to ensure we don't try to process too many input frames that we run out of room in the output buffer. If this happens we'll end up glitching. */ + + /* + We need to try to predict how many input frames will be required for the resampler. If the + resampler can tell us, we'll use that. Otherwise we'll need to make a best guess. The further + off we are from this, the more wasted format conversions we'll end up doing. + */ + #if 1 { - ma_uint64 requiredInputFrameCount = ma_resampler_get_required_input_frame_count(&pConverter->resampler, frameCountOutThisIteration); + ma_uint64 requiredInputFrameCount; + + result = ma_resampler_get_required_input_frame_count(&pConverter->resampler, frameCountOutThisIteration, &requiredInputFrameCount); + if (result != MA_SUCCESS) { + /* Fall back to a best guess. */ + requiredInputFrameCount = (frameCountOutThisIteration * pConverter->resampler.sampleRateIn) / pConverter->resampler.sampleRateOut; + } + if (frameCountInThisIteration > requiredInputFrameCount) { frameCountInThisIteration = requiredInputFrameCount; } } + #endif if (pConverter->hasPreFormatConversion) { if (pFramesIn != NULL) { - ma_convert_pcm_frames_format(pTempBufferIn, pConverter->resampler.config.format, pRunningFramesIn, pConverter->config.formatIn, frameCountInThisIteration, pConverter->config.channelsIn, pConverter->config.ditherMode); + ma_convert_pcm_frames_format(pTempBufferIn, pConverter->resampler.format, pRunningFramesIn, pConverter->formatIn, frameCountInThisIteration, pConverter->channelsIn, pConverter->ditherMode); pResampleBufferIn = pTempBufferIn; } else { pResampleBufferIn = NULL; @@ -41677,7 +52464,7 @@ static ma_result ma_data_converter_process_pcm_frames__resampling_first(ma_data_ /* Finally we do post format conversion. */ if (pConverter->hasPostFormatConversion) { - ma_convert_pcm_frames_format(pRunningFramesOut, pConverter->config.formatOut, pChannelsBufferOut, pConverter->channelConverter.format, frameCountOutThisIteration, pConverter->channelConverter.channelsOut, pConverter->config.ditherMode); + ma_convert_pcm_frames_format(pRunningFramesOut, pConverter->formatOut, pChannelsBufferOut, pConverter->channelConverter.format, frameCountOutThisIteration, pConverter->channelConverter.channelsOut, pConverter->ditherMode); } } @@ -41718,9 +52505,9 @@ static ma_result ma_data_converter_process_pcm_frames__channels_first(ma_data_co ma_uint64 tempBufferOutCap; MA_ASSERT(pConverter != NULL); - MA_ASSERT(pConverter->resampler.config.format == pConverter->channelConverter.format); - MA_ASSERT(pConverter->resampler.config.channels == pConverter->channelConverter.channelsOut); - MA_ASSERT(pConverter->resampler.config.channels < pConverter->channelConverter.channelsIn); + MA_ASSERT(pConverter->resampler.format == pConverter->channelConverter.format); + MA_ASSERT(pConverter->resampler.channels == pConverter->channelConverter.channelsOut); + MA_ASSERT(pConverter->resampler.channels <= pConverter->channelConverter.channelsIn); frameCountIn = 0; if (pFrameCountIn != NULL) { @@ -41737,7 +52524,7 @@ static ma_result ma_data_converter_process_pcm_frames__channels_first(ma_data_co tempBufferInCap = sizeof(pTempBufferIn) / ma_get_bytes_per_frame(pConverter->channelConverter.format, pConverter->channelConverter.channelsIn); tempBufferMidCap = sizeof(pTempBufferIn) / ma_get_bytes_per_frame(pConverter->channelConverter.format, pConverter->channelConverter.channelsOut); - tempBufferOutCap = sizeof(pTempBufferOut) / ma_get_bytes_per_frame(pConverter->resampler.config.format, pConverter->resampler.config.channels); + tempBufferOutCap = sizeof(pTempBufferOut) / ma_get_bytes_per_frame(pConverter->resampler.format, pConverter->resampler.channels); while (framesProcessedOut < frameCountOut) { ma_uint64 frameCountInThisIteration; @@ -41748,22 +52535,67 @@ static ma_result ma_data_converter_process_pcm_frames__channels_first(ma_data_co void* pResampleBufferOut; if (pFramesIn != NULL) { - pRunningFramesIn = ma_offset_ptr(pFramesIn, framesProcessedIn * ma_get_bytes_per_frame(pConverter->config.formatIn, pConverter->config.channelsIn)); + pRunningFramesIn = ma_offset_ptr(pFramesIn, framesProcessedIn * ma_get_bytes_per_frame(pConverter->formatIn, pConverter->channelsIn)); } if (pFramesOut != NULL) { - pRunningFramesOut = ma_offset_ptr(pFramesOut, framesProcessedOut * ma_get_bytes_per_frame(pConverter->config.formatOut, pConverter->config.channelsOut)); + pRunningFramesOut = ma_offset_ptr(pFramesOut, framesProcessedOut * ma_get_bytes_per_frame(pConverter->formatOut, pConverter->channelsOut)); } - /* Run input data through the channel converter and output it to the temporary buffer. */ - frameCountInThisIteration = (frameCountIn - framesProcessedIn); + /* + Before doing any processing we need to determine how many frames we should try processing + this iteration, for both input and output. The resampler requires us to perform format and + channel conversion before passing any data into it. If we get our input count wrong, we'll + end up peforming redundant pre-processing. This isn't the end of the world, but it does + result in some inefficiencies proportionate to how far our estimates are off. + If the resampler has a means to calculate exactly how much we'll need, we'll use that. + Otherwise we'll make a best guess. In order to do this, we'll need to calculate the output + frame count first. + */ + frameCountOutThisIteration = (frameCountOut - framesProcessedOut); + if (frameCountOutThisIteration > tempBufferMidCap) { + frameCountOutThisIteration = tempBufferMidCap; + } + + if (pConverter->hasPostFormatConversion) { + if (frameCountOutThisIteration > tempBufferOutCap) { + frameCountOutThisIteration = tempBufferOutCap; + } + } + + /* Now that we have the output frame count we can determine the input frame count. */ + frameCountInThisIteration = (frameCountIn - framesProcessedIn); if (pConverter->hasPreFormatConversion) { if (frameCountInThisIteration > tempBufferInCap) { frameCountInThisIteration = tempBufferInCap; } + } + if (frameCountInThisIteration > tempBufferMidCap) { + frameCountInThisIteration = tempBufferMidCap; + } + + #if 1 + { + ma_uint64 requiredInputFrameCount; + + result = ma_resampler_get_required_input_frame_count(&pConverter->resampler, frameCountOutThisIteration, &requiredInputFrameCount); + if (result != MA_SUCCESS) { + /* Fall back to a best guess. */ + requiredInputFrameCount = (frameCountOutThisIteration * pConverter->resampler.sampleRateIn) / pConverter->resampler.sampleRateOut; + } + + if (frameCountInThisIteration > requiredInputFrameCount) { + frameCountInThisIteration = requiredInputFrameCount; + } + } + #endif + + + /* Pre format conversion. */ + if (pConverter->hasPreFormatConversion) { if (pRunningFramesIn != NULL) { - ma_convert_pcm_frames_format(pTempBufferIn, pConverter->channelConverter.format, pRunningFramesIn, pConverter->config.formatIn, frameCountInThisIteration, pConverter->config.channelsIn, pConverter->config.ditherMode); + ma_convert_pcm_frames_format(pTempBufferIn, pConverter->channelConverter.format, pRunningFramesIn, pConverter->formatIn, frameCountInThisIteration, pConverter->channelsIn, pConverter->ditherMode); pChannelsBufferIn = pTempBufferIn; } else { pChannelsBufferIn = NULL; @@ -41772,43 +52604,15 @@ static ma_result ma_data_converter_process_pcm_frames__channels_first(ma_data_co pChannelsBufferIn = pRunningFramesIn; } - /* - We can't convert more frames than will fit in the output buffer. We shouldn't actually need to do this check because the channel count is always reduced - in this case which means we should always have capacity, but I'm leaving it here just for safety for future maintenance. - */ - if (frameCountInThisIteration > tempBufferMidCap) { - frameCountInThisIteration = tempBufferMidCap; - } - - /* - Make sure we don't read any more input frames than we need to fill the output frame count. If we do this we will end up in a situation where we lose some - input samples and will end up glitching. - */ - frameCountOutThisIteration = (frameCountOut - framesProcessedOut); - if (frameCountOutThisIteration > tempBufferMidCap) { - frameCountOutThisIteration = tempBufferMidCap; - } - - if (pConverter->hasPostFormatConversion) { - ma_uint64 requiredInputFrameCount; - - if (frameCountOutThisIteration > tempBufferOutCap) { - frameCountOutThisIteration = tempBufferOutCap; - } - - requiredInputFrameCount = ma_resampler_get_required_input_frame_count(&pConverter->resampler, frameCountOutThisIteration); - if (frameCountInThisIteration > requiredInputFrameCount) { - frameCountInThisIteration = requiredInputFrameCount; - } - } + /* Channel conversion. */ result = ma_channel_converter_process_pcm_frames(&pConverter->channelConverter, pTempBufferMid, pChannelsBufferIn, frameCountInThisIteration); if (result != MA_SUCCESS) { return result; } - /* At this point we have converted the channels to the output channel count which we now need to resample. */ + /* Resampling. */ if (pConverter->hasPostFormatConversion) { pResampleBufferOut = pTempBufferOut; } else { @@ -41820,13 +52624,15 @@ static ma_result ma_data_converter_process_pcm_frames__channels_first(ma_data_co return result; } - /* Finally we can do the post format conversion. */ + + /* Post format conversion. */ if (pConverter->hasPostFormatConversion) { if (pRunningFramesOut != NULL) { - ma_convert_pcm_frames_format(pRunningFramesOut, pConverter->config.formatOut, pResampleBufferOut, pConverter->resampler.config.format, frameCountOutThisIteration, pConverter->config.channelsOut, pConverter->config.ditherMode); + ma_convert_pcm_frames_format(pRunningFramesOut, pConverter->formatOut, pResampleBufferOut, pConverter->resampler.format, frameCountOutThisIteration, pConverter->channelsOut, pConverter->ditherMode); } } + framesProcessedIn += frameCountInThisIteration; framesProcessedOut += frameCountOutThisIteration; @@ -41854,46 +52660,15 @@ MA_API ma_result ma_data_converter_process_pcm_frames(ma_data_converter* pConver return MA_INVALID_ARGS; } - if (pConverter->isPassthrough) { - return ma_data_converter_process_pcm_frames__passthrough(pConverter, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); - } - - /* - Here is where the real work is done. Getting here means we're not using a passthrough and we need to move the data through each of the relevant stages. The order - of our stages depends on the input and output channel count. If the input channels is less than the output channels we want to do sample rate conversion first so - that it has less work (resampling is the most expensive part of format conversion). - */ - if (pConverter->config.channelsIn < pConverter->config.channelsOut) { - /* Do resampling first, if necessary. */ - MA_ASSERT(pConverter->hasChannelConverter == MA_TRUE); - - if (pConverter->hasResampler) { - /* Resampling first. */ - return ma_data_converter_process_pcm_frames__resampling_first(pConverter, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); - } else { - /* Resampling not required. */ - return ma_data_converter_process_pcm_frames__channels_only(pConverter, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); - } - } else { - /* Do channel conversion first, if necessary. */ - if (pConverter->hasChannelConverter) { - if (pConverter->hasResampler) { - /* Channel routing first. */ - return ma_data_converter_process_pcm_frames__channels_first(pConverter, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); - } else { - /* Resampling not required. */ - return ma_data_converter_process_pcm_frames__channels_only(pConverter, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); - } - } else { - /* Channel routing not required. */ - if (pConverter->hasResampler) { - /* Resampling only. */ - return ma_data_converter_process_pcm_frames__resample_only(pConverter, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); - } else { - /* No channel routing nor resampling required. Just format conversion. */ - return ma_data_converter_process_pcm_frames__format_only(pConverter, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); - } - } + switch (pConverter->executionPath) + { + case ma_data_converter_execution_path_passthrough: return ma_data_converter_process_pcm_frames__passthrough(pConverter, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); + case ma_data_converter_execution_path_format_only: return ma_data_converter_process_pcm_frames__format_only(pConverter, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); + case ma_data_converter_execution_path_channels_only: return ma_data_converter_process_pcm_frames__channels_only(pConverter, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); + case ma_data_converter_execution_path_resample_only: return ma_data_converter_process_pcm_frames__resample_only(pConverter, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); + case ma_data_converter_execution_path_resample_first: return ma_data_converter_process_pcm_frames__resample_first(pConverter, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); + case ma_data_converter_execution_path_channels_first: return ma_data_converter_process_pcm_frames__channels_first(pConverter, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); + default: return MA_INVALID_OPERATION; /* Should never hit this. */ } } @@ -41923,32 +52698,6 @@ MA_API ma_result ma_data_converter_set_rate_ratio(ma_data_converter* pConverter, return ma_resampler_set_rate_ratio(&pConverter->resampler, ratioInOut); } -MA_API ma_uint64 ma_data_converter_get_required_input_frame_count(const ma_data_converter* pConverter, ma_uint64 outputFrameCount) -{ - if (pConverter == NULL) { - return 0; - } - - if (pConverter->hasResampler) { - return ma_resampler_get_required_input_frame_count(&pConverter->resampler, outputFrameCount); - } else { - return outputFrameCount; /* 1:1 */ - } -} - -MA_API ma_uint64 ma_data_converter_get_expected_output_frame_count(const ma_data_converter* pConverter, ma_uint64 inputFrameCount) -{ - if (pConverter == NULL) { - return 0; - } - - if (pConverter->hasResampler) { - return ma_resampler_get_expected_output_frame_count(&pConverter->resampler, inputFrameCount); - } else { - return inputFrameCount; /* 1:1 */ - } -} - MA_API ma_uint64 ma_data_converter_get_input_latency(const ma_data_converter* pConverter) { if (pConverter == NULL) { @@ -41975,6 +52724,90 @@ MA_API ma_uint64 ma_data_converter_get_output_latency(const ma_data_converter* p return 0; /* No latency without a resampler. */ } +MA_API ma_result ma_data_converter_get_required_input_frame_count(const ma_data_converter* pConverter, ma_uint64 outputFrameCount, ma_uint64* pInputFrameCount) +{ + if (pInputFrameCount == NULL) { + return MA_INVALID_ARGS; + } + + *pInputFrameCount = 0; + + if (pConverter == NULL) { + return MA_INVALID_ARGS; + } + + if (pConverter->hasResampler) { + return ma_resampler_get_required_input_frame_count(&pConverter->resampler, outputFrameCount, pInputFrameCount); + } else { + *pInputFrameCount = outputFrameCount; /* 1:1 */ + return MA_SUCCESS; + } +} + +MA_API ma_result ma_data_converter_get_expected_output_frame_count(const ma_data_converter* pConverter, ma_uint64 inputFrameCount, ma_uint64* pOutputFrameCount) +{ + if (pOutputFrameCount == NULL) { + return MA_INVALID_ARGS; + } + + *pOutputFrameCount = 0; + + if (pConverter == NULL) { + return MA_INVALID_ARGS; + } + + if (pConverter->hasResampler) { + return ma_resampler_get_expected_output_frame_count(&pConverter->resampler, inputFrameCount, pOutputFrameCount); + } else { + *pOutputFrameCount = inputFrameCount; /* 1:1 */ + return MA_SUCCESS; + } +} + +MA_API ma_result ma_data_converter_get_input_channel_map(const ma_data_converter* pConverter, ma_channel* pChannelMap, size_t channelMapCap) +{ + if (pConverter == NULL || pChannelMap == NULL) { + return MA_INVALID_ARGS; + } + + if (pConverter->hasChannelConverter) { + ma_channel_converter_get_output_channel_map(&pConverter->channelConverter, pChannelMap, channelMapCap); + } else { + ma_channel_map_init_standard(ma_standard_channel_map_default, pChannelMap, channelMapCap, pConverter->channelsOut); + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_data_converter_get_output_channel_map(const ma_data_converter* pConverter, ma_channel* pChannelMap, size_t channelMapCap) +{ + if (pConverter == NULL || pChannelMap == NULL) { + return MA_INVALID_ARGS; + } + + if (pConverter->hasChannelConverter) { + ma_channel_converter_get_input_channel_map(&pConverter->channelConverter, pChannelMap, channelMapCap); + } else { + ma_channel_map_init_standard(ma_standard_channel_map_default, pChannelMap, channelMapCap, pConverter->channelsIn); + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_data_converter_reset(ma_data_converter* pConverter) +{ + if (pConverter == NULL) { + return MA_INVALID_ARGS; + } + + /* There's nothing to do if we're not resampling. */ + if (pConverter->hasResampler == MA_FALSE) { + return MA_SUCCESS; + } + + return ma_resampler_reset(&pConverter->resampler); +} + /************************************************************************************************************************************************************** @@ -41982,7 +52815,32 @@ MA_API ma_uint64 ma_data_converter_get_output_latency(const ma_data_converter* p Channel Maps **************************************************************************************************************************************************************/ -MA_API ma_channel ma_channel_map_get_default_channel(ma_uint32 channelCount, ma_uint32 channelIndex) +static ma_channel ma_channel_map_init_standard_channel(ma_standard_channel_map standardChannelMap, ma_uint32 channelCount, ma_uint32 channelIndex); + +MA_API ma_channel ma_channel_map_get_channel(const ma_channel* pChannelMap, ma_uint32 channelCount, ma_uint32 channelIndex) +{ + if (pChannelMap == NULL) { + return ma_channel_map_init_standard_channel(ma_standard_channel_map_default, channelCount, channelIndex); + } else { + if (channelIndex >= channelCount) { + return MA_CHANNEL_NONE; + } + + return pChannelMap[channelIndex]; + } +} + +MA_API void ma_channel_map_init_blank(ma_channel* pChannelMap, ma_uint32 channels) +{ + if (pChannelMap == NULL) { + return; + } + + MA_ZERO_MEMORY(pChannelMap, sizeof(*pChannelMap) * channels); +} + + +static ma_channel ma_channel_map_init_standard_channel_microsoft(ma_uint32 channelCount, ma_uint32 channelIndex) { if (channelCount == 0 || channelIndex >= channelCount) { return MA_CHANNEL_NONE; @@ -41991,7 +52849,7 @@ MA_API ma_channel ma_channel_map_get_default_channel(ma_uint32 channelCount, ma_ /* This is the Microsoft channel map. Based off the speaker configurations mentioned here: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/ksmedia/ns-ksmedia-ksaudio_channel_config */ switch (channelCount) { - case 0: return MA_CHANNEL_NONE; + case 0: return MA_CHANNEL_NONE; case 1: { @@ -42096,645 +52954,619 @@ MA_API ma_channel ma_channel_map_get_default_channel(ma_uint32 channelCount, ma_ return MA_CHANNEL_NONE; } -MA_API ma_channel ma_channel_map_get_channel(const ma_channel* pChannelMap, ma_uint32 channelCount, ma_uint32 channelIndex) +static ma_channel ma_channel_map_init_standard_channel_alsa(ma_uint32 channelCount, ma_uint32 channelIndex) { - if (pChannelMap == NULL) { - return ma_channel_map_get_default_channel(channelCount, channelIndex); - } else { - if (channelIndex >= channelCount) { - return MA_CHANNEL_NONE; - } - - return pChannelMap[channelIndex]; - } -} - - -MA_API void ma_channel_map_init_blank(ma_uint32 channels, ma_channel* pChannelMap) -{ - if (pChannelMap == NULL) { - return; - } - - MA_ZERO_MEMORY(pChannelMap, sizeof(*pChannelMap) * channels); -} - -static void ma_get_standard_channel_map_microsoft(ma_uint32 channels, ma_channel* pChannelMap) -{ - /* Based off the speaker configurations mentioned here: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/ksmedia/ns-ksmedia-ksaudio_channel_config */ - switch (channels) + switch (channelCount) { + case 0: return MA_CHANNEL_NONE; + case 1: { - pChannelMap[0] = MA_CHANNEL_MONO; + return MA_CHANNEL_MONO; } break; case 2: { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + } } break; - case 3: /* Not defined, but best guess. */ + case 3: { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_FRONT_CENTER; + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_FRONT_CENTER; + } } break; case 4: { -#ifndef MA_USE_QUAD_MICROSOFT_CHANNEL_MAP - /* Surround. Using the Surround profile has the advantage of the 3rd channel (MA_CHANNEL_FRONT_CENTER) mapping nicely with higher channel counts. */ - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[3] = MA_CHANNEL_BACK_CENTER; -#else - /* Quad. */ - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_BACK_LEFT; - pChannelMap[3] = MA_CHANNEL_BACK_RIGHT; -#endif + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_BACK_LEFT; + case 3: return MA_CHANNEL_BACK_RIGHT; + } + } break; + + case 5: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_BACK_LEFT; + case 3: return MA_CHANNEL_BACK_RIGHT; + case 4: return MA_CHANNEL_FRONT_CENTER; + } + } break; + + case 6: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_BACK_LEFT; + case 3: return MA_CHANNEL_BACK_RIGHT; + case 4: return MA_CHANNEL_FRONT_CENTER; + case 5: return MA_CHANNEL_LFE; + } + } break; + + case 7: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_BACK_LEFT; + case 3: return MA_CHANNEL_BACK_RIGHT; + case 4: return MA_CHANNEL_FRONT_CENTER; + case 5: return MA_CHANNEL_LFE; + case 6: return MA_CHANNEL_BACK_CENTER; + } + } break; + + case 8: + default: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_BACK_LEFT; + case 3: return MA_CHANNEL_BACK_RIGHT; + case 4: return MA_CHANNEL_FRONT_CENTER; + case 5: return MA_CHANNEL_LFE; + case 6: return MA_CHANNEL_SIDE_LEFT; + case 7: return MA_CHANNEL_SIDE_RIGHT; + } + } break; + } + + if (channelCount > 8) { + if (channelIndex < 32) { /* We have 32 AUX channels. */ + return (ma_channel)(MA_CHANNEL_AUX_0 + (channelIndex - 8)); + } + } + + /* Getting here means we don't know how to map the channel position so just return MA_CHANNEL_NONE. */ + return MA_CHANNEL_NONE; +} + +static ma_channel ma_channel_map_init_standard_channel_rfc3551(ma_uint32 channelCount, ma_uint32 channelIndex) +{ + switch (channelCount) + { + case 0: return MA_CHANNEL_NONE; + + case 1: + { + return MA_CHANNEL_MONO; + } break; + + case 2: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + } + } break; + + case 3: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_FRONT_CENTER; + } + } break; + + case 4: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 2: return MA_CHANNEL_FRONT_CENTER; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 3: return MA_CHANNEL_BACK_CENTER; + } + } break; + + case 5: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_FRONT_CENTER; + case 3: return MA_CHANNEL_BACK_LEFT; + case 4: return MA_CHANNEL_BACK_RIGHT; + } + } break; + + case 6: + default: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_SIDE_LEFT; + case 2: return MA_CHANNEL_FRONT_CENTER; + case 3: return MA_CHANNEL_FRONT_RIGHT; + case 4: return MA_CHANNEL_SIDE_RIGHT; + case 5: return MA_CHANNEL_BACK_CENTER; + } + } break; + } + + if (channelCount > 6) { + if (channelIndex < 32) { /* We have 32 AUX channels. */ + return (ma_channel)(MA_CHANNEL_AUX_0 + (channelIndex - 6)); + } + } + + /* Getting here means we don't know how to map the channel position so just return MA_CHANNEL_NONE. */ + return MA_CHANNEL_NONE; +} + +static ma_channel ma_channel_map_init_standard_channel_flac(ma_uint32 channelCount, ma_uint32 channelIndex) +{ + switch (channelCount) + { + case 0: return MA_CHANNEL_NONE; + + case 1: + { + return MA_CHANNEL_MONO; + } break; + + case 2: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + } + } break; + + case 3: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_FRONT_CENTER; + } + } break; + + case 4: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_BACK_LEFT; + case 3: return MA_CHANNEL_BACK_RIGHT; + } + } break; + + case 5: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_FRONT_CENTER; + case 3: return MA_CHANNEL_BACK_LEFT; + case 4: return MA_CHANNEL_BACK_RIGHT; + } + } break; + + case 6: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_FRONT_CENTER; + case 3: return MA_CHANNEL_LFE; + case 4: return MA_CHANNEL_BACK_LEFT; + case 5: return MA_CHANNEL_BACK_RIGHT; + } + } break; + + case 7: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_FRONT_CENTER; + case 3: return MA_CHANNEL_LFE; + case 4: return MA_CHANNEL_BACK_CENTER; + case 5: return MA_CHANNEL_SIDE_LEFT; + case 6: return MA_CHANNEL_SIDE_RIGHT; + } + } break; + + case 8: + default: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_FRONT_CENTER; + case 3: return MA_CHANNEL_LFE; + case 4: return MA_CHANNEL_BACK_LEFT; + case 5: return MA_CHANNEL_BACK_RIGHT; + case 6: return MA_CHANNEL_SIDE_LEFT; + case 7: return MA_CHANNEL_SIDE_RIGHT; + } + } break; + } + + if (channelCount > 8) { + if (channelIndex < 32) { /* We have 32 AUX channels. */ + return (ma_channel)(MA_CHANNEL_AUX_0 + (channelIndex - 8)); + } + } + + /* Getting here means we don't know how to map the channel position so just return MA_CHANNEL_NONE. */ + return MA_CHANNEL_NONE; +} + +static ma_channel ma_channel_map_init_standard_channel_vorbis(ma_uint32 channelCount, ma_uint32 channelIndex) +{ + switch (channelCount) + { + case 0: return MA_CHANNEL_NONE; + + case 1: + { + return MA_CHANNEL_MONO; + } break; + + case 2: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + } + } break; + + case 3: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_CENTER; + case 2: return MA_CHANNEL_FRONT_RIGHT; + } + } break; + + case 4: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_BACK_LEFT; + case 3: return MA_CHANNEL_BACK_RIGHT; + } + } break; + + case 5: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_CENTER; + case 2: return MA_CHANNEL_FRONT_RIGHT; + case 3: return MA_CHANNEL_BACK_LEFT; + case 4: return MA_CHANNEL_BACK_RIGHT; + } + } break; + + case 6: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_CENTER; + case 2: return MA_CHANNEL_FRONT_RIGHT; + case 3: return MA_CHANNEL_BACK_LEFT; + case 4: return MA_CHANNEL_BACK_RIGHT; + case 5: return MA_CHANNEL_LFE; + } + } break; + + case 7: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_CENTER; + case 2: return MA_CHANNEL_FRONT_RIGHT; + case 3: return MA_CHANNEL_SIDE_LEFT; + case 4: return MA_CHANNEL_SIDE_RIGHT; + case 5: return MA_CHANNEL_BACK_CENTER; + case 6: return MA_CHANNEL_LFE; + } + } break; + + case 8: + default: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_CENTER; + case 2: return MA_CHANNEL_FRONT_RIGHT; + case 3: return MA_CHANNEL_SIDE_LEFT; + case 4: return MA_CHANNEL_SIDE_RIGHT; + case 5: return MA_CHANNEL_BACK_LEFT; + case 6: return MA_CHANNEL_BACK_RIGHT; + case 7: return MA_CHANNEL_LFE; + } + } break; + } + + if (channelCount > 8) { + if (channelIndex < 32) { /* We have 32 AUX channels. */ + return (ma_channel)(MA_CHANNEL_AUX_0 + (channelIndex - 8)); + } + } + + /* Getting here means we don't know how to map the channel position so just return MA_CHANNEL_NONE. */ + return MA_CHANNEL_NONE; +} + +static ma_channel ma_channel_map_init_standard_channel_sound4(ma_uint32 channelCount, ma_uint32 channelIndex) +{ + switch (channelCount) + { + case 0: return MA_CHANNEL_NONE; + + case 1: + { + return MA_CHANNEL_MONO; + } break; + + case 2: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + } + } break; + + case 3: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_FRONT_CENTER; + } + } break; + + case 4: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_BACK_LEFT; + case 3: return MA_CHANNEL_BACK_RIGHT; + } + } break; + + case 5: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_FRONT_CENTER; + case 3: return MA_CHANNEL_BACK_LEFT; + case 4: return MA_CHANNEL_BACK_RIGHT; + } + } break; + + case 6: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_CENTER; + case 2: return MA_CHANNEL_FRONT_RIGHT; + case 3: return MA_CHANNEL_BACK_LEFT; + case 4: return MA_CHANNEL_BACK_RIGHT; + case 5: return MA_CHANNEL_LFE; + } + } break; + + case 7: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_CENTER; + case 2: return MA_CHANNEL_FRONT_RIGHT; + case 3: return MA_CHANNEL_SIDE_LEFT; + case 4: return MA_CHANNEL_SIDE_RIGHT; + case 5: return MA_CHANNEL_BACK_CENTER; + case 6: return MA_CHANNEL_LFE; + } + } break; + + case 8: + default: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_CENTER; + case 2: return MA_CHANNEL_FRONT_RIGHT; + case 3: return MA_CHANNEL_SIDE_LEFT; + case 4: return MA_CHANNEL_SIDE_RIGHT; + case 5: return MA_CHANNEL_BACK_LEFT; + case 6: return MA_CHANNEL_BACK_RIGHT; + case 7: return MA_CHANNEL_LFE; + } + } break; + } + + if (channelCount > 8) { + if (channelIndex < 32) { /* We have 32 AUX channels. */ + return (ma_channel)(MA_CHANNEL_AUX_0 + (channelIndex - 8)); + } + } + + /* Getting here means we don't know how to map the channel position so just return MA_CHANNEL_NONE. */ + return MA_CHANNEL_NONE; +} + +static ma_channel ma_channel_map_init_standard_channel_sndio(ma_uint32 channelCount, ma_uint32 channelIndex) +{ + switch (channelCount) + { + case 0: return MA_CHANNEL_NONE; + + case 1: + { + return MA_CHANNEL_MONO; + } break; + + case 2: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + } + } break; + + case 3: /* No defined, but best guess. */ + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_FRONT_CENTER; + } + } break; + + case 4: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_BACK_LEFT; + case 3: return MA_CHANNEL_BACK_RIGHT; + } } break; case 5: /* Not defined, but best guess. */ { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[3] = MA_CHANNEL_BACK_LEFT; - pChannelMap[4] = MA_CHANNEL_BACK_RIGHT; - } break; - - case 6: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[3] = MA_CHANNEL_LFE; - pChannelMap[4] = MA_CHANNEL_SIDE_LEFT; - pChannelMap[5] = MA_CHANNEL_SIDE_RIGHT; - } break; - - case 7: /* Not defined, but best guess. */ - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[3] = MA_CHANNEL_LFE; - pChannelMap[4] = MA_CHANNEL_BACK_CENTER; - pChannelMap[5] = MA_CHANNEL_SIDE_LEFT; - pChannelMap[6] = MA_CHANNEL_SIDE_RIGHT; - } break; - - case 8: - default: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[3] = MA_CHANNEL_LFE; - pChannelMap[4] = MA_CHANNEL_BACK_LEFT; - pChannelMap[5] = MA_CHANNEL_BACK_RIGHT; - pChannelMap[6] = MA_CHANNEL_SIDE_LEFT; - pChannelMap[7] = MA_CHANNEL_SIDE_RIGHT; - } break; - } - - /* Remainder. */ - if (channels > 8) { - ma_uint32 iChannel; - for (iChannel = 8; iChannel < channels; ++iChannel) { - if (iChannel < MA_MAX_CHANNELS) { - pChannelMap[iChannel] = (ma_channel)(MA_CHANNEL_AUX_0 + (iChannel-8)); - } else { - pChannelMap[iChannel] = MA_CHANNEL_NONE; + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_BACK_LEFT; + case 3: return MA_CHANNEL_BACK_RIGHT; + case 4: return MA_CHANNEL_FRONT_CENTER; } - } - } -} - -static void ma_get_standard_channel_map_alsa(ma_uint32 channels, ma_channel* pChannelMap) -{ - switch (channels) - { - case 1: - { - pChannelMap[0] = MA_CHANNEL_MONO; - } break; - - case 2: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - } break; - - case 3: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_FRONT_CENTER; - } break; - - case 4: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_BACK_LEFT; - pChannelMap[3] = MA_CHANNEL_BACK_RIGHT; - } break; - - case 5: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_BACK_LEFT; - pChannelMap[3] = MA_CHANNEL_BACK_RIGHT; - pChannelMap[4] = MA_CHANNEL_FRONT_CENTER; - } break; - - case 6: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_BACK_LEFT; - pChannelMap[3] = MA_CHANNEL_BACK_RIGHT; - pChannelMap[4] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[5] = MA_CHANNEL_LFE; - } break; - - case 7: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_BACK_LEFT; - pChannelMap[3] = MA_CHANNEL_BACK_RIGHT; - pChannelMap[4] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[5] = MA_CHANNEL_LFE; - pChannelMap[6] = MA_CHANNEL_BACK_CENTER; - } break; - - case 8: - default: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_BACK_LEFT; - pChannelMap[3] = MA_CHANNEL_BACK_RIGHT; - pChannelMap[4] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[5] = MA_CHANNEL_LFE; - pChannelMap[6] = MA_CHANNEL_SIDE_LEFT; - pChannelMap[7] = MA_CHANNEL_SIDE_RIGHT; - } break; - } - - /* Remainder. */ - if (channels > 8) { - ma_uint32 iChannel; - for (iChannel = 8; iChannel < channels; ++iChannel) { - if (iChannel < MA_MAX_CHANNELS) { - pChannelMap[iChannel] = (ma_channel)(MA_CHANNEL_AUX_0 + (iChannel-8)); - } else { - pChannelMap[iChannel] = MA_CHANNEL_NONE; - } - } - } -} - -static void ma_get_standard_channel_map_rfc3551(ma_uint32 channels, ma_channel* pChannelMap) -{ - switch (channels) - { - case 1: - { - pChannelMap[0] = MA_CHANNEL_MONO; - } break; - - case 2: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - } break; - - case 3: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_FRONT_CENTER; - } break; - - case 4: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[2] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[3] = MA_CHANNEL_BACK_CENTER; - } break; - - case 5: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[3] = MA_CHANNEL_BACK_LEFT; - pChannelMap[4] = MA_CHANNEL_BACK_RIGHT; - } break; - - case 6: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_SIDE_LEFT; - pChannelMap[2] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[3] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[4] = MA_CHANNEL_SIDE_RIGHT; - pChannelMap[5] = MA_CHANNEL_BACK_CENTER; - } break; - } - - /* Remainder. */ - if (channels > 8) { - ma_uint32 iChannel; - for (iChannel = 6; iChannel < channels; ++iChannel) { - if (iChannel < MA_MAX_CHANNELS) { - pChannelMap[iChannel] = (ma_channel)(MA_CHANNEL_AUX_0 + (iChannel-6)); - } else { - pChannelMap[iChannel] = MA_CHANNEL_NONE; - } - } - } -} - -static void ma_get_standard_channel_map_flac(ma_uint32 channels, ma_channel* pChannelMap) -{ - switch (channels) - { - case 1: - { - pChannelMap[0] = MA_CHANNEL_MONO; - } break; - - case 2: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - } break; - - case 3: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_FRONT_CENTER; - } break; - - case 4: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_BACK_LEFT; - pChannelMap[3] = MA_CHANNEL_BACK_RIGHT; - } break; - - case 5: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[3] = MA_CHANNEL_BACK_LEFT; - pChannelMap[4] = MA_CHANNEL_BACK_RIGHT; - } break; - - case 6: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[3] = MA_CHANNEL_LFE; - pChannelMap[4] = MA_CHANNEL_BACK_LEFT; - pChannelMap[5] = MA_CHANNEL_BACK_RIGHT; - } break; - - case 7: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[3] = MA_CHANNEL_LFE; - pChannelMap[4] = MA_CHANNEL_BACK_CENTER; - pChannelMap[5] = MA_CHANNEL_SIDE_LEFT; - pChannelMap[6] = MA_CHANNEL_SIDE_RIGHT; - } break; - - case 8: - default: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[3] = MA_CHANNEL_LFE; - pChannelMap[4] = MA_CHANNEL_BACK_LEFT; - pChannelMap[5] = MA_CHANNEL_BACK_RIGHT; - pChannelMap[6] = MA_CHANNEL_SIDE_LEFT; - pChannelMap[7] = MA_CHANNEL_SIDE_RIGHT; - } break; - } - - /* Remainder. */ - if (channels > 8) { - ma_uint32 iChannel; - for (iChannel = 8; iChannel < channels; ++iChannel) { - if (iChannel < MA_MAX_CHANNELS) { - pChannelMap[iChannel] = (ma_channel)(MA_CHANNEL_AUX_0 + (iChannel-8)); - } else { - pChannelMap[iChannel] = MA_CHANNEL_NONE; - } - } - } -} - -static void ma_get_standard_channel_map_vorbis(ma_uint32 channels, ma_channel* pChannelMap) -{ - /* In Vorbis' type 0 channel mapping, the first two channels are not always the standard left/right - it will have the center speaker where the right usually goes. Why?! */ - switch (channels) - { - case 1: - { - pChannelMap[0] = MA_CHANNEL_MONO; - } break; - - case 2: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - } break; - - case 3: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[2] = MA_CHANNEL_FRONT_RIGHT; - } break; - - case 4: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_BACK_LEFT; - pChannelMap[3] = MA_CHANNEL_BACK_RIGHT; - } break; - - case 5: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[2] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[3] = MA_CHANNEL_BACK_LEFT; - pChannelMap[4] = MA_CHANNEL_BACK_RIGHT; - } break; - - case 6: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[2] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[3] = MA_CHANNEL_BACK_LEFT; - pChannelMap[4] = MA_CHANNEL_BACK_RIGHT; - pChannelMap[5] = MA_CHANNEL_LFE; - } break; - - case 7: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[2] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[3] = MA_CHANNEL_SIDE_LEFT; - pChannelMap[4] = MA_CHANNEL_SIDE_RIGHT; - pChannelMap[5] = MA_CHANNEL_BACK_CENTER; - pChannelMap[6] = MA_CHANNEL_LFE; - } break; - - case 8: - default: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[2] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[3] = MA_CHANNEL_SIDE_LEFT; - pChannelMap[4] = MA_CHANNEL_SIDE_RIGHT; - pChannelMap[5] = MA_CHANNEL_BACK_LEFT; - pChannelMap[6] = MA_CHANNEL_BACK_RIGHT; - pChannelMap[7] = MA_CHANNEL_LFE; - } break; - } - - /* Remainder. */ - if (channels > 8) { - ma_uint32 iChannel; - for (iChannel = 8; iChannel < channels; ++iChannel) { - if (iChannel < MA_MAX_CHANNELS) { - pChannelMap[iChannel] = (ma_channel)(MA_CHANNEL_AUX_0 + (iChannel-8)); - } else { - pChannelMap[iChannel] = MA_CHANNEL_NONE; - } - } - } -} - -static void ma_get_standard_channel_map_sound4(ma_uint32 channels, ma_channel* pChannelMap) -{ - switch (channels) - { - case 1: - { - pChannelMap[0] = MA_CHANNEL_MONO; - } break; - - case 2: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - } break; - - case 3: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_BACK_CENTER; - } break; - - case 4: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_BACK_LEFT; - pChannelMap[3] = MA_CHANNEL_BACK_RIGHT; - } break; - - case 5: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_BACK_LEFT; - pChannelMap[3] = MA_CHANNEL_BACK_RIGHT; - pChannelMap[4] = MA_CHANNEL_FRONT_CENTER; - } break; - - case 6: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_BACK_LEFT; - pChannelMap[3] = MA_CHANNEL_BACK_RIGHT; - pChannelMap[4] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[5] = MA_CHANNEL_LFE; - } break; - - case 7: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_BACK_LEFT; - pChannelMap[3] = MA_CHANNEL_BACK_RIGHT; - pChannelMap[4] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[5] = MA_CHANNEL_BACK_CENTER; - pChannelMap[6] = MA_CHANNEL_LFE; - } break; - - case 8: - default: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_BACK_LEFT; - pChannelMap[3] = MA_CHANNEL_BACK_RIGHT; - pChannelMap[4] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[5] = MA_CHANNEL_LFE; - pChannelMap[6] = MA_CHANNEL_SIDE_LEFT; - pChannelMap[7] = MA_CHANNEL_SIDE_RIGHT; - } break; - } - - /* Remainder. */ - if (channels > 8) { - ma_uint32 iChannel; - for (iChannel = 8; iChannel < MA_MAX_CHANNELS; ++iChannel) { - if (iChannel < MA_MAX_CHANNELS) { - pChannelMap[iChannel] = (ma_channel)(MA_CHANNEL_AUX_0 + (iChannel-8)); - } else { - pChannelMap[iChannel] = MA_CHANNEL_NONE; - } - } - } -} - -static void ma_get_standard_channel_map_sndio(ma_uint32 channels, ma_channel* pChannelMap) -{ - switch (channels) - { - case 1: - { - pChannelMap[0] = MA_CHANNEL_MONO; - } break; - - case 2: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - } break; - - case 3: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_FRONT_CENTER; - } break; - - case 4: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_BACK_LEFT; - pChannelMap[3] = MA_CHANNEL_BACK_RIGHT; - } break; - - case 5: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_BACK_LEFT; - pChannelMap[3] = MA_CHANNEL_BACK_RIGHT; - pChannelMap[4] = MA_CHANNEL_FRONT_CENTER; } break; case 6: default: { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_BACK_LEFT; - pChannelMap[3] = MA_CHANNEL_BACK_RIGHT; - pChannelMap[4] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[5] = MA_CHANNEL_LFE; + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_BACK_LEFT; + case 3: return MA_CHANNEL_BACK_RIGHT; + case 4: return MA_CHANNEL_FRONT_CENTER; + case 5: return MA_CHANNEL_LFE; + } } break; } - /* Remainder. */ - if (channels > 6) { - ma_uint32 iChannel; - for (iChannel = 6; iChannel < channels && iChannel < MA_MAX_CHANNELS; ++iChannel) { - if (iChannel < MA_MAX_CHANNELS) { - pChannelMap[iChannel] = (ma_channel)(MA_CHANNEL_AUX_0 + (iChannel-6)); - } else { - pChannelMap[iChannel] = MA_CHANNEL_NONE; - } + if (channelCount > 6) { + if (channelIndex < 32) { /* We have 32 AUX channels. */ + return (ma_channel)(MA_CHANNEL_AUX_0 + (channelIndex - 6)); } } + + /* Getting here means we don't know how to map the channel position so just return MA_CHANNEL_NONE. */ + return MA_CHANNEL_NONE; } -MA_API void ma_get_standard_channel_map(ma_standard_channel_map standardChannelMap, ma_uint32 channels, ma_channel* pChannelMap) + +static ma_channel ma_channel_map_init_standard_channel(ma_standard_channel_map standardChannelMap, ma_uint32 channelCount, ma_uint32 channelIndex) { + if (channelCount == 0 || channelIndex >= channelCount) { + return MA_CHANNEL_NONE; + } + switch (standardChannelMap) { case ma_standard_channel_map_alsa: { - ma_get_standard_channel_map_alsa(channels, pChannelMap); + return ma_channel_map_init_standard_channel_alsa(channelCount, channelIndex); } break; case ma_standard_channel_map_rfc3551: { - ma_get_standard_channel_map_rfc3551(channels, pChannelMap); + return ma_channel_map_init_standard_channel_rfc3551(channelCount, channelIndex); } break; case ma_standard_channel_map_flac: { - ma_get_standard_channel_map_flac(channels, pChannelMap); + return ma_channel_map_init_standard_channel_flac(channelCount, channelIndex); } break; case ma_standard_channel_map_vorbis: { - ma_get_standard_channel_map_vorbis(channels, pChannelMap); + return ma_channel_map_init_standard_channel_vorbis(channelCount, channelIndex); } break; case ma_standard_channel_map_sound4: { - ma_get_standard_channel_map_sound4(channels, pChannelMap); + return ma_channel_map_init_standard_channel_sound4(channelCount, channelIndex); } break; case ma_standard_channel_map_sndio: { - ma_get_standard_channel_map_sndio(channels, pChannelMap); + return ma_channel_map_init_standard_channel_sndio(channelCount, channelIndex); } break; case ma_standard_channel_map_microsoft: /* Also default. */ /*case ma_standard_channel_map_default;*/ default: { - ma_get_standard_channel_map_microsoft(channels, pChannelMap); + return ma_channel_map_init_standard_channel_microsoft(channelCount, channelIndex); } break; } } +MA_API void ma_channel_map_init_standard(ma_standard_channel_map standardChannelMap, ma_channel* pChannelMap, size_t channelMapCap, ma_uint32 channels) +{ + ma_uint32 iChannel; + + if (pChannelMap == NULL || channelMapCap == 0 || channels == 0) { + return; + } + + for (iChannel = 0; iChannel < channels; iChannel += 1) { + if (channelMapCap == 0) { + break; /* Ran out of room. */ + } + + pChannelMap[0] = ma_channel_map_init_standard_channel(standardChannelMap, channels, iChannel); + pChannelMap += 1; + channelMapCap -= 1; + } +} + MA_API void ma_channel_map_copy(ma_channel* pOut, const ma_channel* pIn, ma_uint32 channels) { if (pOut != NULL && pIn != NULL && channels > 0) { @@ -42742,7 +53574,7 @@ MA_API void ma_channel_map_copy(ma_channel* pOut, const ma_channel* pIn, ma_uint } } -MA_API void ma_channel_map_copy_or_default(ma_channel* pOut, const ma_channel* pIn, ma_uint32 channels) +MA_API void ma_channel_map_copy_or_default(ma_channel* pOut, size_t channelMapCapOut, const ma_channel* pIn, ma_uint32 channels) { if (pOut == NULL || channels == 0) { return; @@ -42751,16 +53583,12 @@ MA_API void ma_channel_map_copy_or_default(ma_channel* pOut, const ma_channel* p if (pIn != NULL) { ma_channel_map_copy(pOut, pIn, channels); } else { - ma_get_standard_channel_map(ma_standard_channel_map_default, channels, pOut); + ma_channel_map_init_standard(ma_standard_channel_map_default, pOut, channelMapCapOut, channels); } } -MA_API ma_bool32 ma_channel_map_valid(ma_uint32 channels, const ma_channel* pChannelMap) +MA_API ma_bool32 ma_channel_map_is_valid(const ma_channel* pChannelMap, ma_uint32 channels) { - if (pChannelMap == NULL) { - return MA_FALSE; - } - /* A channel count of 0 is invalid. */ if (channels == 0) { return MA_FALSE; @@ -42770,7 +53598,7 @@ MA_API ma_bool32 ma_channel_map_valid(ma_uint32 channels, const ma_channel* pCha if (channels > 1) { ma_uint32 iChannel; for (iChannel = 0; iChannel < channels; ++iChannel) { - if (pChannelMap[iChannel] == MA_CHANNEL_MONO) { + if (ma_channel_map_get_channel(pChannelMap, channels, iChannel) == MA_CHANNEL_MONO) { return MA_FALSE; } } @@ -42779,7 +53607,7 @@ MA_API ma_bool32 ma_channel_map_valid(ma_uint32 channels, const ma_channel* pCha return MA_TRUE; } -MA_API ma_bool32 ma_channel_map_equal(ma_uint32 channels, const ma_channel* pChannelMapA, const ma_channel* pChannelMapB) +MA_API ma_bool32 ma_channel_map_is_equal(const ma_channel* pChannelMapA, const ma_channel* pChannelMapB, ma_uint32 channels) { ma_uint32 iChannel; @@ -42796,7 +53624,7 @@ MA_API ma_bool32 ma_channel_map_equal(ma_uint32 channels, const ma_channel* pCha return MA_TRUE; } -MA_API ma_bool32 ma_channel_map_blank(ma_uint32 channels, const ma_channel* pChannelMap) +MA_API ma_bool32 ma_channel_map_is_blank(const ma_channel* pChannelMap, ma_uint32 channels) { ma_uint32 iChannel; @@ -42839,8 +53667,6 @@ MA_API ma_uint64 ma_convert_frames(void* pOut, ma_uint64 frameCountOut, ma_forma ma_data_converter_config config; config = ma_data_converter_config_init(formatIn, formatOut, channelsIn, channelsOut, sampleRateIn, sampleRateOut); - ma_get_standard_channel_map(ma_standard_channel_map_default, channelsOut, config.channelMapOut); - ma_get_standard_channel_map(ma_standard_channel_map_default, channelsIn, config.channelMapIn); config.resampling.linear.lpfOrder = ma_min(MA_DEFAULT_RESAMPLER_LPF_ORDER, MA_MAX_FILTER_ORDER); return ma_convert_frames_ex(pOut, frameCountOut, pIn, frameCountIn, &config); @@ -42855,13 +53681,31 @@ MA_API ma_uint64 ma_convert_frames_ex(void* pOut, ma_uint64 frameCountOut, const return 0; } - result = ma_data_converter_init(pConfig, &converter); + result = ma_data_converter_init(pConfig, NULL, &converter); if (result != MA_SUCCESS) { return 0; /* Failed to initialize the data converter. */ } if (pOut == NULL) { - frameCountOut = ma_data_converter_get_expected_output_frame_count(&converter, frameCountIn); + result = ma_data_converter_get_expected_output_frame_count(&converter, frameCountIn, &frameCountOut); + if (result != MA_SUCCESS) { + if (result == MA_NOT_IMPLEMENTED) { + /* No way to calculate the number of frames, so we'll need to brute force it and loop. */ + frameCountOut = 0; + + while (frameCountIn > 0) { + ma_uint64 framesProcessedIn = frameCountIn; + ma_uint64 framesProcessedOut = 0xFFFFFFFF; + + result = ma_data_converter_process_pcm_frames(&converter, pIn, &framesProcessedIn, NULL, &framesProcessedOut); + if (result != MA_SUCCESS) { + break; + } + + frameCountIn -= framesProcessedIn; + } + } + } } else { result = ma_data_converter_process_pcm_frames(&converter, pIn, &frameCountIn, pOut, &frameCountOut); if (result != MA_SUCCESS) { @@ -42869,7 +53713,7 @@ MA_API ma_uint64 ma_convert_frames_ex(void* pOut, ma_uint64 frameCountOut, const } } - ma_data_converter_uninit(&converter); + ma_data_converter_uninit(&converter, NULL); return frameCountOut; } @@ -43038,7 +53882,7 @@ MA_API ma_result ma_rb_acquire_read(ma_rb* pRB, size_t* pSizeInBytes, void** ppB return MA_SUCCESS; } -MA_API ma_result ma_rb_commit_read(ma_rb* pRB, size_t sizeInBytes, void* pBufferOut) +MA_API ma_result ma_rb_commit_read(ma_rb* pRB, size_t sizeInBytes) { ma_uint32 readOffset; ma_uint32 readOffsetInBytes; @@ -43050,11 +53894,6 @@ MA_API ma_result ma_rb_commit_read(ma_rb* pRB, size_t sizeInBytes, void* pBuffer return MA_INVALID_ARGS; } - /* Validate the buffer. */ - if (pBufferOut != ma_rb__get_read_ptr(pRB)) { - return MA_INVALID_ARGS; - } - readOffset = c89atomic_load_32(&pRB->encodedReadOffset); ma_rb__deconstruct_offset(readOffset, &readOffsetInBytes, &readOffsetLoopFlag); @@ -43129,7 +53968,7 @@ MA_API ma_result ma_rb_acquire_write(ma_rb* pRB, size_t* pSizeInBytes, void** pp return MA_SUCCESS; } -MA_API ma_result ma_rb_commit_write(ma_rb* pRB, size_t sizeInBytes, void* pBufferOut) +MA_API ma_result ma_rb_commit_write(ma_rb* pRB, size_t sizeInBytes) { ma_uint32 writeOffset; ma_uint32 writeOffsetInBytes; @@ -43141,11 +53980,6 @@ MA_API ma_result ma_rb_commit_write(ma_rb* pRB, size_t sizeInBytes, void* pBuffe return MA_INVALID_ARGS; } - /* Validate the buffer. */ - if (pBufferOut != ma_rb__get_write_ptr(pRB)) { - return MA_INVALID_ARGS; - } - writeOffset = c89atomic_load_32(&pRB->encodedWriteOffset); ma_rb__deconstruct_offset(writeOffset, &writeOffsetInBytes, &writeOffsetLoopFlag); @@ -43429,13 +54263,13 @@ MA_API ma_result ma_pcm_rb_acquire_read(ma_pcm_rb* pRB, ma_uint32* pSizeInFrames return MA_SUCCESS; } -MA_API ma_result ma_pcm_rb_commit_read(ma_pcm_rb* pRB, ma_uint32 sizeInFrames, void* pBufferOut) +MA_API ma_result ma_pcm_rb_commit_read(ma_pcm_rb* pRB, ma_uint32 sizeInFrames) { if (pRB == NULL) { return MA_INVALID_ARGS; } - return ma_rb_commit_read(&pRB->rb, sizeInFrames * ma_pcm_rb_get_bpf(pRB), pBufferOut); + return ma_rb_commit_read(&pRB->rb, sizeInFrames * ma_pcm_rb_get_bpf(pRB)); } MA_API ma_result ma_pcm_rb_acquire_write(ma_pcm_rb* pRB, ma_uint32* pSizeInFrames, void** ppBufferOut) @@ -43458,13 +54292,13 @@ MA_API ma_result ma_pcm_rb_acquire_write(ma_pcm_rb* pRB, ma_uint32* pSizeInFrame return MA_SUCCESS; } -MA_API ma_result ma_pcm_rb_commit_write(ma_pcm_rb* pRB, ma_uint32 sizeInFrames, void* pBufferOut) +MA_API ma_result ma_pcm_rb_commit_write(ma_pcm_rb* pRB, ma_uint32 sizeInFrames) { if (pRB == NULL) { return MA_INVALID_ARGS; } - return ma_rb_commit_write(&pRB->rb, sizeInFrames * ma_pcm_rb_get_bpf(pRB), pBufferOut); + return ma_rb_commit_write(&pRB->rb, sizeInFrames * ma_pcm_rb_get_bpf(pRB)); } MA_API ma_result ma_pcm_rb_seek_read(ma_pcm_rb* pRB, ma_uint32 offsetInFrames) @@ -43665,19 +54499,33 @@ MA_API const char* ma_result_description(ma_result result) MA_API void* ma_malloc(size_t sz, const ma_allocation_callbacks* pAllocationCallbacks) { if (pAllocationCallbacks != NULL) { - return ma__malloc_from_callbacks(sz, pAllocationCallbacks); + if (pAllocationCallbacks->onMalloc != NULL) { + return pAllocationCallbacks->onMalloc(sz, pAllocationCallbacks->pUserData); + } else { + return NULL; /* Do not fall back to the default implementation. */ + } } else { return ma__malloc_default(sz, NULL); } } +MA_API void* ma_calloc(size_t sz, const ma_allocation_callbacks* pAllocationCallbacks) +{ + void* p = ma_malloc(sz, pAllocationCallbacks); + if (p != NULL) { + MA_ZERO_MEMORY(p, sz); + } + + return p; +} + MA_API void* ma_realloc(void* p, size_t sz, const ma_allocation_callbacks* pAllocationCallbacks) { if (pAllocationCallbacks != NULL) { if (pAllocationCallbacks->onRealloc != NULL) { return pAllocationCallbacks->onRealloc(p, sz, pAllocationCallbacks->pUserData); } else { - return NULL; /* This requires a native implementation of realloc(). */ + return NULL; /* Do not fall back to the default implementation. */ } } else { return ma__realloc_default(p, sz, NULL); @@ -43686,8 +54534,16 @@ MA_API void* ma_realloc(void* p, size_t sz, const ma_allocation_callbacks* pAllo MA_API void ma_free(void* p, const ma_allocation_callbacks* pAllocationCallbacks) { + if (p == NULL) { + return; + } + if (pAllocationCallbacks != NULL) { - ma__free_from_callbacks(p, pAllocationCallbacks); + if (pAllocationCallbacks->onFree != NULL) { + pAllocationCallbacks->onFree(p, pAllocationCallbacks->pUserData); + } else { + return; /* Do no fall back to the default implementation. */ + } } else { ma__free_default(p, NULL); } @@ -43792,11 +54648,6 @@ MA_API ma_result ma_data_source_init(const ma_data_source_config* pConfig, ma_da pDataSourceBase->pNext = NULL; pDataSourceBase->onGetNext = NULL; - /* Compatibility: Need to make a copy of the callbacks. This will be removed in version 0.11. */ - if (pConfig->vtable != NULL) { - pDataSourceBase->cb = *pConfig->vtable; - } - return MA_SUCCESS; } @@ -43812,7 +54663,6 @@ MA_API void ma_data_source_uninit(ma_data_source* pDataSource) */ } -#if defined(MA_EXPERIMENTAL__DATA_LOOPING_AND_CHAINING) static ma_result ma_data_source_resolve_current(ma_data_source* pDataSource, ma_data_source** ppCurrentDataSource) { ma_data_source_base* pCurrentDataSource = (ma_data_source_base*)pDataSource; @@ -43839,63 +54689,76 @@ static ma_result ma_data_source_resolve_current(ma_data_source* pDataSource, ma_ return MA_SUCCESS; } -static ma_result ma_data_source_read_pcm_frames_within_range(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead, ma_bool32 loop) +static ma_result ma_data_source_read_pcm_frames_within_range(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) { ma_data_source_base* pDataSourceBase = (ma_data_source_base*)pDataSource; - + ma_result result; + ma_uint64 framesRead = 0; + ma_bool32 loop = ma_data_source_is_looping(pDataSource); + if (pDataSourceBase == NULL) { return MA_AT_END; } - if (pDataSourceBase->rangeEndInFrames == ~((ma_uint64)0) && (pDataSourceBase->loopEndInFrames == ~((ma_uint64)0) || loop == MA_FALSE)) { - /* No range is set - just read like normal. The data source itself will tell us when the end is reached. */ - return pDataSourceBase->cb.onRead(pDataSourceBase, pFramesOut, frameCount, pFramesRead); + if (frameCount == 0) { + return MA_INVALID_ARGS; + } + + if ((pDataSourceBase->vtable->flags & MA_DATA_SOURCE_SELF_MANAGED_RANGE_AND_LOOP_POINT) != 0 || (pDataSourceBase->rangeEndInFrames == ~((ma_uint64)0) && (pDataSourceBase->loopEndInFrames == ~((ma_uint64)0) || loop == MA_FALSE))) { + /* Either the data source is self-managing the range, or no range is set - just read like normal. The data source itself will tell us when the end is reached. */ + result = pDataSourceBase->vtable->onRead(pDataSourceBase, pFramesOut, frameCount, &framesRead); } else { /* Need to clamp to within the range. */ - ma_result result; ma_uint64 cursor; - ma_uint64 framesRead = 0; - ma_uint64 rangeEnd; result = ma_data_source_get_cursor_in_pcm_frames(pDataSourceBase, &cursor); if (result != MA_SUCCESS) { /* Failed to retrieve the cursor. Cannot read within a range or loop points. Just read like normal - this may happen for things like noise data sources where it doesn't really matter. */ - return pDataSourceBase->cb.onRead(pDataSourceBase, pFramesOut, frameCount, pFramesRead); - } + result = pDataSourceBase->vtable->onRead(pDataSourceBase, pFramesOut, frameCount, &framesRead); + } else { + ma_uint64 rangeEnd; - /* We have the cursor. We need to make sure we don't read beyond our range. */ - rangeEnd = pDataSourceBase->rangeEndInFrames; + /* We have the cursor. We need to make sure we don't read beyond our range. */ + rangeEnd = pDataSourceBase->rangeEndInFrames; - /* If looping, make sure we're within range. */ - if (loop) { - if (pDataSourceBase->loopEndInFrames != ~((ma_uint64)0)) { - rangeEnd = ma_min(rangeEnd, pDataSourceBase->rangeBegInFrames + pDataSourceBase->loopEndInFrames); + /* If looping, make sure we're within range. */ + if (loop) { + if (pDataSourceBase->loopEndInFrames != ~((ma_uint64)0)) { + rangeEnd = ma_min(rangeEnd, pDataSourceBase->rangeBegInFrames + pDataSourceBase->loopEndInFrames); + } + } + + if (frameCount > (rangeEnd - cursor) && rangeEnd != ~((ma_uint64)0)) { + frameCount = (rangeEnd - cursor); + } + + /* + If the cursor is sitting on the end of the range the frame count will be set to 0 which can + result in MA_INVALID_ARGS. In this case, we don't want to try reading, but instead return + MA_AT_END so the higher level function can know about it. + */ + if (frameCount > 0) { + result = pDataSourceBase->vtable->onRead(pDataSourceBase, pFramesOut, frameCount, &framesRead); + } else { + result = MA_AT_END; /* The cursor is sitting on the end of the range which means we're at the end. */ } } - - if (frameCount > (rangeEnd - cursor) && rangeEnd != ~((ma_uint64)0)) { - frameCount = (rangeEnd - cursor); - } - - result = pDataSourceBase->cb.onRead(pDataSourceBase, pFramesOut, frameCount, &framesRead); - - if (pFramesRead != NULL) { - *pFramesRead = framesRead; - } - - /* We need to make sure MA_AT_END is returned if we hit the end of the range. */ - if (result != MA_AT_END && framesRead == 0) { - result = MA_AT_END; - } - - return result; } -} -#endif -MA_API ma_result ma_data_source_read_pcm_frames(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead, ma_bool32 loop) + if (pFramesRead != NULL) { + *pFramesRead = framesRead; + } + + /* We need to make sure MA_AT_END is returned if we hit the end of the range. */ + if (result == MA_SUCCESS && framesRead == 0) { + result = MA_AT_END; + } + + return result; +} + +MA_API ma_result ma_data_source_read_pcm_frames(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) { -#if defined(MA_EXPERIMENTAL__DATA_LOOPING_AND_CHAINING) ma_result result = MA_SUCCESS; ma_data_source_base* pDataSourceBase = (ma_data_source_base*)pDataSource; ma_data_source_base* pCurrentDataSource; @@ -43904,26 +54767,33 @@ MA_API ma_result ma_data_source_read_pcm_frames(ma_data_source* pDataSource, voi ma_format format; ma_uint32 channels; ma_uint32 emptyLoopCounter = 0; /* Keeps track of how many times 0 frames have been read. For infinite loop detection of sounds with no audio data. */ + ma_bool32 loop; if (pFramesRead != NULL) { *pFramesRead = 0; } + if (frameCount == 0) { + return MA_INVALID_ARGS; + } + if (pDataSourceBase == NULL) { return MA_INVALID_ARGS; } + loop = ma_data_source_is_looping(pDataSource); + /* We need to know the data format so we can advance the output buffer as we read frames. If this fails, chaining will not work and we'll just read as much as we can from the current source. */ - if (ma_data_source_get_data_format(pDataSource, &format, &channels, NULL) != MA_SUCCESS) { + if (ma_data_source_get_data_format(pDataSource, &format, &channels, NULL, NULL, 0) != MA_SUCCESS) { result = ma_data_source_resolve_current(pDataSource, (ma_data_source**)&pCurrentDataSource); if (result != MA_SUCCESS) { return result; } - return ma_data_source_read_pcm_frames_within_range(pCurrentDataSource, pFramesOut, frameCount, pFramesRead, loop); + return ma_data_source_read_pcm_frames_within_range(pCurrentDataSource, pFramesOut, frameCount, pFramesRead); } /* @@ -43946,7 +54816,7 @@ MA_API ma_result ma_data_source_read_pcm_frames(ma_data_source* pDataSource, voi break; } - result = ma_data_source_read_pcm_frames_within_range(pCurrentDataSource, pRunningFramesOut, framesRemaining, &framesProcessed, loop); + result = ma_data_source_read_pcm_frames_within_range(pCurrentDataSource, pRunningFramesOut, framesRemaining, &framesProcessed); totalFramesProcessed += framesProcessed; /* @@ -43958,10 +54828,18 @@ MA_API ma_result ma_data_source_read_pcm_frames(ma_data_source* pDataSource, voi } /* - We can determine if we've reached the end by checking the return value of the onRead() - callback. To loop back to the start, all we need to do is seek back to the first frame. + We can determine if we've reached the end by checking if ma_data_source_read_pcm_frames_within_range() returned + MA_AT_END. To loop back to the start, all we need to do is seek back to the first frame. */ if (result == MA_AT_END) { + /* + The result needs to be reset back to MA_SUCCESS (from MA_AT_END) so that we don't + accidentally return MA_AT_END when data has been read in prior loop iterations. at the + end of this function, the result will be checked for MA_SUCCESS, and if the total + number of frames processed is 0, will be explicitly set to MA_AT_END. + */ + result = MA_SUCCESS; + /* We reached the end. If we're looping, we just loop back to the start of the current data source. If we're not looping we need to check if we have another in the chain, and @@ -43977,7 +54855,8 @@ MA_API ma_result ma_data_source_read_pcm_frames(ma_data_source* pDataSource, voi emptyLoopCounter = 0; } - if (ma_data_source_seek_to_pcm_frame(pCurrentDataSource, pCurrentDataSource->loopBegInFrames) != MA_SUCCESS) { + result = ma_data_source_seek_to_pcm_frame(pCurrentDataSource, pCurrentDataSource->loopBegInFrames); + if (result != MA_SUCCESS) { break; /* Failed to loop. Abort. */ } @@ -43997,14 +54876,10 @@ MA_API ma_result ma_data_source_read_pcm_frames(ma_data_source* pDataSource, voi } /* The next data source needs to be rewound to ensure data is read in looping scenarios. */ - ma_data_source_seek_to_pcm_frame(pDataSourceBase->pCurrent, 0); - - /* - We need to make sure we clear the MA_AT_END result so we don't accidentally return - it in the event that we coincidentally ended reading at the exact transition point - of two data sources in a chain. - */ - result = MA_SUCCESS; + result = ma_data_source_seek_to_pcm_frame(pDataSourceBase->pCurrent, 0); + if (result != MA_SUCCESS) { + break; + } } } @@ -44017,93 +54892,29 @@ MA_API ma_result ma_data_source_read_pcm_frames(ma_data_source* pDataSource, voi *pFramesRead = totalFramesProcessed; } + MA_ASSERT(!(result == MA_AT_END && totalFramesProcessed > 0)); /* We should never be returning MA_AT_END if we read some data. */ + + if (result == MA_SUCCESS && totalFramesProcessed == 0) { + result = MA_AT_END; + } + return result; -#else - ma_data_source_callbacks* pCallbacks = (ma_data_source_callbacks*)pDataSource; - - /* Safety. */ - if (pFramesRead != NULL) { - *pFramesRead = 0; - } - - if (pCallbacks == NULL) { - return MA_INVALID_ARGS; - } - - if (pCallbacks->onRead == NULL) { - return MA_NOT_IMPLEMENTED; - } - - /* A very small optimization for the non looping case. */ - if (loop == MA_FALSE) { - return pCallbacks->onRead(pDataSource, pFramesOut, frameCount, pFramesRead); - } else { - ma_format format; - ma_uint32 channels; - ma_uint32 sampleRate; - if (ma_data_source_get_data_format(pDataSource, &format, &channels, &sampleRate) != MA_SUCCESS) { - return pCallbacks->onRead(pDataSource, pFramesOut, frameCount, pFramesRead); /* We don't have a way to retrieve the data format which means we don't know how to offset the output buffer. Just read as much as we can. */ - } else { - ma_result result = MA_SUCCESS; - ma_uint64 totalFramesProcessed; - void* pRunningFramesOut = pFramesOut; - - totalFramesProcessed = 0; - while (totalFramesProcessed < frameCount) { - ma_uint64 framesProcessed; - ma_uint64 framesRemaining = frameCount - totalFramesProcessed; - - result = pCallbacks->onRead(pDataSource, pRunningFramesOut, framesRemaining, &framesProcessed); - totalFramesProcessed += framesProcessed; - - /* - If we encounted an error from the read callback, make sure it's propagated to the caller. The caller may need to know whether or not MA_BUSY is returned which is - not necessarily considered an error. - */ - if (result != MA_SUCCESS && result != MA_AT_END) { - break; - } - - /* - We can determine if we've reached the end by checking the return value of the onRead() callback. If it's less than what we requested it means - we've reached the end. To loop back to the start, all we need to do is seek back to the first frame. - */ - if (framesProcessed < framesRemaining || result == MA_AT_END) { - if (ma_data_source_seek_to_pcm_frame(pDataSource, 0) != MA_SUCCESS) { - break; - } - } - - if (pRunningFramesOut != NULL) { - pRunningFramesOut = ma_offset_ptr(pRunningFramesOut, framesProcessed * ma_get_bytes_per_frame(format, channels)); - } - } - - if (pFramesRead != NULL) { - *pFramesRead = totalFramesProcessed; - } - - return result; - } - } -#endif } -MA_API ma_result ma_data_source_seek_pcm_frames(ma_data_source* pDataSource, ma_uint64 frameCount, ma_uint64* pFramesSeeked, ma_bool32 loop) +MA_API ma_result ma_data_source_seek_pcm_frames(ma_data_source* pDataSource, ma_uint64 frameCount, ma_uint64* pFramesSeeked) { - return ma_data_source_read_pcm_frames(pDataSource, NULL, frameCount, pFramesSeeked, loop); + return ma_data_source_read_pcm_frames(pDataSource, NULL, frameCount, pFramesSeeked); } MA_API ma_result ma_data_source_seek_to_pcm_frame(ma_data_source* pDataSource, ma_uint64 frameIndex) { -#if defined(MA_EXPERIMENTAL__DATA_LOOPING_AND_CHAINING) ma_data_source_base* pDataSourceBase = (ma_data_source_base*)pDataSource; if (pDataSourceBase == NULL) { return MA_SUCCESS; } - if (pDataSourceBase->cb.onSeek == NULL) { + if (pDataSourceBase->vtable->onSeek == NULL) { return MA_NOT_IMPLEMENTED; } @@ -44111,78 +54922,40 @@ MA_API ma_result ma_data_source_seek_to_pcm_frame(ma_data_source* pDataSource, m return MA_INVALID_OPERATION; /* Trying to seek to far forward. */ } - return pDataSourceBase->cb.onSeek(pDataSource, pDataSourceBase->rangeBegInFrames + frameIndex); -#else - ma_data_source_callbacks* pCallbacks = (ma_data_source_callbacks*)pDataSource; - if (pCallbacks == NULL) { - return MA_INVALID_ARGS; - } - - if (pCallbacks->onSeek == NULL) { - return MA_NOT_IMPLEMENTED; - } - - return pCallbacks->onSeek(pDataSource, frameIndex); -#endif + return pDataSourceBase->vtable->onSeek(pDataSource, pDataSourceBase->rangeBegInFrames + frameIndex); } -MA_API ma_result ma_data_source_map(ma_data_source* pDataSource, void** ppFramesOut, ma_uint64* pFrameCount) -{ - ma_data_source_callbacks* pCallbacks = (ma_data_source_callbacks*)pDataSource; - if (pCallbacks == NULL) { - return MA_INVALID_ARGS; - } - - if (pCallbacks->onMap == NULL) { - return MA_NOT_IMPLEMENTED; - } - - return pCallbacks->onMap(pDataSource, ppFramesOut, pFrameCount); -} - -MA_API ma_result ma_data_source_unmap(ma_data_source* pDataSource, ma_uint64 frameCount) -{ - ma_data_source_callbacks* pCallbacks = (ma_data_source_callbacks*)pDataSource; - if (pCallbacks == NULL) { - return MA_INVALID_ARGS; - } - - if (pCallbacks->onUnmap == NULL) { - return MA_NOT_IMPLEMENTED; - } - - return pCallbacks->onUnmap(pDataSource, frameCount); -} - -MA_API ma_result ma_data_source_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate) +MA_API ma_result ma_data_source_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) { + ma_data_source_base* pDataSourceBase = (ma_data_source_base*)pDataSource; ma_result result; ma_format format; ma_uint32 channels; ma_uint32 sampleRate; - ma_data_source_callbacks* pCallbacks = (ma_data_source_callbacks*)pDataSource; + /* Initialize to defaults for safety just in case the data source does not implement this callback. */ if (pFormat != NULL) { *pFormat = ma_format_unknown; } - if (pChannels != NULL) { *pChannels = 0; } - if (pSampleRate != NULL) { *pSampleRate = 0; } + if (pChannelMap != NULL) { + MA_ZERO_MEMORY(pChannelMap, sizeof(*pChannelMap) * channelMapCap); + } - if (pCallbacks == NULL) { + if (pDataSourceBase == NULL) { return MA_INVALID_ARGS; } - if (pCallbacks->onGetDataFormat == NULL) { + if (pDataSourceBase->vtable->onGetDataFormat == NULL) { return MA_NOT_IMPLEMENTED; } - result = pCallbacks->onGetDataFormat(pDataSource, &format, &channels, &sampleRate); + result = pDataSourceBase->vtable->onGetDataFormat(pDataSource, &format, &channels, &sampleRate, pChannelMap, channelMapCap); if (result != MA_SUCCESS) { return result; } @@ -44197,12 +54970,13 @@ MA_API ma_result ma_data_source_get_data_format(ma_data_source* pDataSource, ma_ *pSampleRate = sampleRate; } + /* Channel map was passed in directly to the callback. This is safe due to the channelMapCap parameter. */ + return MA_SUCCESS; } MA_API ma_result ma_data_source_get_cursor_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pCursor) { -#if defined(MA_EXPERIMENTAL__DATA_LOOPING_AND_CHAINING) ma_data_source_base* pDataSourceBase = (ma_data_source_base*)pDataSource; ma_result result; ma_uint64 cursor; @@ -44217,11 +54991,11 @@ MA_API ma_result ma_data_source_get_cursor_in_pcm_frames(ma_data_source* pDataSo return MA_SUCCESS; } - if (pDataSourceBase->cb.onGetCursor == NULL) { + if (pDataSourceBase->vtable->onGetCursor == NULL) { return MA_NOT_IMPLEMENTED; } - result = pDataSourceBase->cb.onGetCursor(pDataSourceBase, &cursor); + result = pDataSourceBase->vtable->onGetCursor(pDataSourceBase, &cursor); if (result != MA_SUCCESS) { return result; } @@ -44232,32 +55006,12 @@ MA_API ma_result ma_data_source_get_cursor_in_pcm_frames(ma_data_source* pDataSo } else { *pCursor = cursor - pDataSourceBase->rangeBegInFrames; } - + return MA_SUCCESS; -#else - ma_data_source_callbacks* pCallbacks = (ma_data_source_callbacks*)pDataSource; - - if (pCursor == NULL) { - return MA_INVALID_ARGS; - } - - *pCursor = 0; - - if (pCallbacks == NULL) { - return MA_INVALID_ARGS; - } - - if (pCallbacks->onGetCursor == NULL) { - return MA_NOT_IMPLEMENTED; - } - - return pCallbacks->onGetCursor(pDataSource, pCursor); -#endif } MA_API ma_result ma_data_source_get_length_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pLength) { -#if defined(MA_EXPERIMENTAL__DATA_LOOPING_AND_CHAINING) ma_data_source_base* pDataSourceBase = (ma_data_source_base*)pDataSource; if (pLength == NULL) { @@ -44284,13 +55038,45 @@ MA_API ma_result ma_data_source_get_length_in_pcm_frames(ma_data_source* pDataSo Getting here means a range is not defined so we'll need to get the data source itself to tell us the length. */ - if (pDataSourceBase->cb.onGetLength == NULL) { + if (pDataSourceBase->vtable->onGetLength == NULL) { return MA_NOT_IMPLEMENTED; } - return pDataSourceBase->cb.onGetLength(pDataSource, pLength); -#else - ma_data_source_callbacks* pCallbacks = (ma_data_source_callbacks*)pDataSource; + return pDataSourceBase->vtable->onGetLength(pDataSource, pLength); +} + +MA_API ma_result ma_data_source_get_cursor_in_seconds(ma_data_source* pDataSource, float* pCursor) +{ + ma_result result; + ma_uint64 cursorInPCMFrames; + ma_uint32 sampleRate; + + if (pCursor == NULL) { + return MA_INVALID_ARGS; + } + + *pCursor = 0; + + result = ma_data_source_get_cursor_in_pcm_frames(pDataSource, &cursorInPCMFrames); + if (result != MA_SUCCESS) { + return result; + } + + result = ma_data_source_get_data_format(pDataSource, NULL, NULL, &sampleRate, NULL, 0); + if (result != MA_SUCCESS) { + return result; + } + + *pCursor = cursorInPCMFrames / (float)sampleRate; + + return MA_SUCCESS; +} + +MA_API ma_result ma_data_source_get_length_in_seconds(ma_data_source* pDataSource, float* pLength) +{ + ma_result result; + ma_uint64 lengthInPCMFrames; + ma_uint32 sampleRate; if (pLength == NULL) { return MA_INVALID_ARGS; @@ -44298,20 +55084,50 @@ MA_API ma_result ma_data_source_get_length_in_pcm_frames(ma_data_source* pDataSo *pLength = 0; - if (pCallbacks == NULL) { + result = ma_data_source_get_length_in_pcm_frames(pDataSource, &lengthInPCMFrames); + if (result != MA_SUCCESS) { + return result; + } + + result = ma_data_source_get_data_format(pDataSource, NULL, NULL, &sampleRate, NULL, 0); + if (result != MA_SUCCESS) { + return result; + } + + *pLength = lengthInPCMFrames / (float)sampleRate; + + return MA_SUCCESS; +} + +MA_API ma_result ma_data_source_set_looping(ma_data_source* pDataSource, ma_bool32 isLooping) +{ + ma_data_source_base* pDataSourceBase = (ma_data_source_base*)pDataSource; + + if (pDataSource == NULL) { return MA_INVALID_ARGS; } - if (pCallbacks->onGetLength == NULL) { - return MA_NOT_IMPLEMENTED; + c89atomic_exchange_32(&pDataSourceBase->isLooping, isLooping); + + /* If there's no callback for this just treat it as a successful no-op. */ + if (pDataSourceBase->vtable->onSetLooping == NULL) { + return MA_SUCCESS; } - return pCallbacks->onGetLength(pDataSource, pLength); -#endif + return pDataSourceBase->vtable->onSetLooping(pDataSource, isLooping); } +MA_API ma_bool32 ma_data_source_is_looping(const ma_data_source* pDataSource) +{ + const ma_data_source_base* pDataSourceBase = (const ma_data_source_base*)pDataSource; + + if (pDataSource == NULL) { + return MA_FALSE; + } + + return c89atomic_load_32(&pDataSourceBase->isLooping); +} -#if defined(MA_EXPERIMENTAL__DATA_LOOPING_AND_CHAINING) MA_API ma_result ma_data_source_set_range_in_pcm_frames(ma_data_source* pDataSource, ma_uint64 rangeBegInFrames, ma_uint64 rangeEndInFrames) { ma_data_source_base* pDataSourceBase = (ma_data_source_base*)pDataSource; @@ -44356,12 +55172,12 @@ MA_API ma_result ma_data_source_set_range_in_pcm_frames(ma_data_source* pDataSou } else { pDataSourceBase->loopEndInFrames = 0; } - + if (pDataSourceBase->loopEndInFrames > pDataSourceBase->rangeEndInFrames && pDataSourceBase->loopEndInFrames) { pDataSourceBase->loopEndInFrames = pDataSourceBase->rangeEndInFrames; } } - + /* If the new range is past the current cursor position we need to seek to it. */ result = ma_data_source_get_cursor_in_pcm_frames(pDataSource, &cursor); @@ -44379,9 +55195,9 @@ MA_API ma_result ma_data_source_set_range_in_pcm_frames(ma_data_source* pDataSou return MA_SUCCESS; } -MA_API void ma_data_source_get_range_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pRangeBegInFrames, ma_uint64* pRangeEndInFrames) +MA_API void ma_data_source_get_range_in_pcm_frames(const ma_data_source* pDataSource, ma_uint64* pRangeBegInFrames, ma_uint64* pRangeEndInFrames) { - ma_data_source_base* pDataSourceBase = (ma_data_source_base*)pDataSource; + const ma_data_source_base* pDataSourceBase = (const ma_data_source_base*)pDataSource; if (pDataSource == NULL) { return; @@ -44423,9 +55239,9 @@ MA_API ma_result ma_data_source_set_loop_point_in_pcm_frames(ma_data_source* pDa return MA_SUCCESS; } -MA_API void ma_data_source_get_loop_point_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pLoopBegInFrames, ma_uint64* pLoopEndInFrames) +MA_API void ma_data_source_get_loop_point_in_pcm_frames(const ma_data_source* pDataSource, ma_uint64* pLoopBegInFrames, ma_uint64* pLoopEndInFrames) { - ma_data_source_base* pDataSourceBase = (ma_data_source_base*)pDataSource; + const ma_data_source_base* pDataSourceBase = (const ma_data_source_base*)pDataSource; if (pDataSource == NULL) { return; @@ -44453,9 +55269,9 @@ MA_API ma_result ma_data_source_set_current(ma_data_source* pDataSource, ma_data return MA_SUCCESS; } -MA_API ma_data_source* ma_data_source_get_current(ma_data_source* pDataSource) +MA_API ma_data_source* ma_data_source_get_current(const ma_data_source* pDataSource) { - ma_data_source_base* pDataSourceBase = (ma_data_source_base*)pDataSource; + const ma_data_source_base* pDataSourceBase = (const ma_data_source_base*)pDataSource; if (pDataSource == NULL) { return NULL; @@ -44477,9 +55293,9 @@ MA_API ma_result ma_data_source_set_next(ma_data_source* pDataSource, ma_data_so return MA_SUCCESS; } -MA_API ma_data_source* ma_data_source_get_next(ma_data_source* pDataSource) +MA_API ma_data_source* ma_data_source_get_next(const ma_data_source* pDataSource) { - ma_data_source_base* pDataSourceBase = (ma_data_source_base*)pDataSource; + const ma_data_source_base* pDataSourceBase = (const ma_data_source_base*)pDataSource; if (pDataSource == NULL) { return NULL; @@ -44501,9 +55317,9 @@ MA_API ma_result ma_data_source_set_next_callback(ma_data_source* pDataSource, m return MA_SUCCESS; } -MA_API ma_data_source_get_next_proc ma_data_source_get_next_callback(ma_data_source* pDataSource) +MA_API ma_data_source_get_next_proc ma_data_source_get_next_callback(const ma_data_source* pDataSource) { - ma_data_source_base* pDataSourceBase = (ma_data_source_base*)pDataSource; + const ma_data_source_base* pDataSourceBase = (const ma_data_source_base*)pDataSource; if (pDataSource == NULL) { return NULL; @@ -44511,7 +55327,6 @@ MA_API ma_data_source_get_next_proc ma_data_source_get_next_callback(ma_data_sou return pDataSourceBase->onGetNext; } -#endif static ma_result ma_audio_buffer_ref__data_source_on_read(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) @@ -44535,23 +55350,14 @@ static ma_result ma_audio_buffer_ref__data_source_on_seek(ma_data_source* pDataS return ma_audio_buffer_ref_seek_to_pcm_frame((ma_audio_buffer_ref*)pDataSource, frameIndex); } -static ma_result ma_audio_buffer_ref__data_source_on_map(ma_data_source* pDataSource, void** ppFramesOut, ma_uint64* pFrameCount) -{ - return ma_audio_buffer_ref_map((ma_audio_buffer_ref*)pDataSource, ppFramesOut, pFrameCount); -} - -static ma_result ma_audio_buffer_ref__data_source_on_unmap(ma_data_source* pDataSource, ma_uint64 frameCount) -{ - return ma_audio_buffer_ref_unmap((ma_audio_buffer_ref*)pDataSource, frameCount); -} - -static ma_result ma_audio_buffer_ref__data_source_on_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate) +static ma_result ma_audio_buffer_ref__data_source_on_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) { ma_audio_buffer_ref* pAudioBufferRef = (ma_audio_buffer_ref*)pDataSource; *pFormat = pAudioBufferRef->format; *pChannels = pAudioBufferRef->channels; - *pSampleRate = 0; /* There is no notion of a sample rate with audio buffers. */ + *pSampleRate = pAudioBufferRef->sampleRate; + ma_channel_map_init_standard(ma_standard_channel_map_default, pChannelMap, channelMapCap, pAudioBufferRef->channels); return MA_SUCCESS; } @@ -44578,11 +55384,11 @@ static ma_data_source_vtable g_ma_audio_buffer_ref_data_source_vtable = { ma_audio_buffer_ref__data_source_on_read, ma_audio_buffer_ref__data_source_on_seek, - ma_audio_buffer_ref__data_source_on_map, - ma_audio_buffer_ref__data_source_on_unmap, ma_audio_buffer_ref__data_source_on_get_data_format, ma_audio_buffer_ref__data_source_on_get_cursor, - ma_audio_buffer_ref__data_source_on_get_length + ma_audio_buffer_ref__data_source_on_get_length, + NULL, /* onSetLooping */ + 0 }; MA_API ma_result ma_audio_buffer_ref_init(ma_format format, ma_uint32 channels, const void* pData, ma_uint64 sizeInFrames, ma_audio_buffer_ref* pAudioBufferRef) @@ -44606,6 +55412,7 @@ MA_API ma_result ma_audio_buffer_ref_init(ma_format format, ma_uint32 channels, pAudioBufferRef->format = format; pAudioBufferRef->channels = channels; + pAudioBufferRef->sampleRate = 0; /* TODO: Version 0.12. Set this to sampleRate. */ pAudioBufferRef->cursor = 0; pAudioBufferRef->sizeInFrames = sizeInFrames; pAudioBufferRef->pData = pData; @@ -44658,7 +55465,7 @@ MA_API ma_uint64 ma_audio_buffer_ref_read_pcm_frames(ma_audio_buffer_ref* pAudio } if (pFramesOut != NULL) { - ma_copy_pcm_frames(pFramesOut, ma_offset_ptr(pAudioBufferRef->pData, pAudioBufferRef->cursor * ma_get_bytes_per_frame(pAudioBufferRef->format, pAudioBufferRef->channels)), framesToRead, pAudioBufferRef->format, pAudioBufferRef->channels); + ma_copy_pcm_frames(ma_offset_ptr(pFramesOut, totalFramesRead * ma_get_bytes_per_frame(pAudioBufferRef->format, pAudioBufferRef->channels)), ma_offset_ptr(pAudioBufferRef->pData, pAudioBufferRef->cursor * ma_get_bytes_per_frame(pAudioBufferRef->format, pAudioBufferRef->channels)), framesToRead, pAudioBufferRef->format, pAudioBufferRef->channels); } totalFramesRead += framesToRead; @@ -44816,10 +55623,11 @@ MA_API ma_audio_buffer_config ma_audio_buffer_config_init(ma_format format, ma_u ma_audio_buffer_config config; MA_ZERO_OBJECT(&config); - config.format = format; - config.channels = channels; + config.format = format; + config.channels = channels; + config.sampleRate = 0; /* TODO: Version 0.12. Set this to sampleRate. */ config.sizeInFrames = sizeInFrames; - config.pData = pData; + config.pData = pData; ma_allocation_callbacks_init_copy(&config.allocationCallbacks, pAllocationCallbacks); return config; @@ -44848,6 +55656,9 @@ static ma_result ma_audio_buffer_init_ex(const ma_audio_buffer_config* pConfig, return result; } + /* TODO: Version 0.12. Set this in ma_audio_buffer_ref_init() instead of here. */ + pAudioBuffer->ref.sampleRate = pConfig->sampleRate; + ma_allocation_callbacks_init_copy(&pAudioBuffer->allocationCallbacks, &pConfig->allocationCallbacks); if (doCopy) { @@ -44859,7 +55670,7 @@ static ma_result ma_audio_buffer_init_ex(const ma_audio_buffer_config* pConfig, return MA_OUT_OF_MEMORY; /* Too big. */ } - pData = ma__malloc_from_callbacks((size_t)allocationSizeInBytes, &pAudioBuffer->allocationCallbacks); /* Safe cast to size_t. */ + pData = ma_malloc((size_t)allocationSizeInBytes, &pAudioBuffer->allocationCallbacks); /* Safe cast to size_t. */ if (pData == NULL) { return MA_OUT_OF_MEMORY; } @@ -44887,11 +55698,11 @@ static void ma_audio_buffer_uninit_ex(ma_audio_buffer* pAudioBuffer, ma_bool32 d } if (pAudioBuffer->ownsData && pAudioBuffer->ref.pData != &pAudioBuffer->_pExtraData[0]) { - ma__free_from_callbacks((void*)pAudioBuffer->ref.pData, &pAudioBuffer->allocationCallbacks); /* Naugty const cast, but OK in this case since we've guarded it with the ownsData check. */ + ma_free((void*)pAudioBuffer->ref.pData, &pAudioBuffer->allocationCallbacks); /* Naugty const cast, but OK in this case since we've guarded it with the ownsData check. */ } if (doFree) { - ma__free_from_callbacks(pAudioBuffer, &pAudioBuffer->allocationCallbacks); + ma_free(pAudioBuffer, &pAudioBuffer->allocationCallbacks); } ma_audio_buffer_ref_uninit(&pAudioBuffer->ref); @@ -44932,7 +55743,7 @@ MA_API ma_result ma_audio_buffer_alloc_and_init(const ma_audio_buffer_config* pC return MA_OUT_OF_MEMORY; /* Too big. */ } - pAudioBuffer = (ma_audio_buffer*)ma__malloc_from_callbacks((size_t)allocationSizeInBytes, &innerConfig.allocationCallbacks); /* Safe cast to size_t. */ + pAudioBuffer = (ma_audio_buffer*)ma_malloc((size_t)allocationSizeInBytes, &innerConfig.allocationCallbacks); /* Safe cast to size_t. */ if (pAudioBuffer == NULL) { return MA_OUT_OF_MEMORY; } @@ -44947,7 +55758,7 @@ MA_API ma_result ma_audio_buffer_alloc_and_init(const ma_audio_buffer_config* pC result = ma_audio_buffer_init_ex(&innerConfig, MA_FALSE, pAudioBuffer); if (result != MA_SUCCESS) { - ma__free_from_callbacks(pAudioBuffer, &innerConfig.allocationCallbacks); + ma_free(pAudioBuffer, &innerConfig.allocationCallbacks); return result; } @@ -45054,6 +55865,392 @@ MA_API ma_result ma_audio_buffer_get_available_frames(const ma_audio_buffer* pAu + + +MA_API ma_result ma_paged_audio_buffer_data_init(ma_format format, ma_uint32 channels, ma_paged_audio_buffer_data* pData) +{ + if (pData == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pData); + + pData->format = format; + pData->channels = channels; + pData->pTail = &pData->head; + + return MA_SUCCESS; +} + +MA_API void ma_paged_audio_buffer_data_uninit(ma_paged_audio_buffer_data* pData, const ma_allocation_callbacks* pAllocationCallbacks) +{ + ma_paged_audio_buffer_page* pPage; + + if (pData == NULL) { + return; + } + + /* All pages need to be freed. */ + pPage = (ma_paged_audio_buffer_page*)c89atomic_load_ptr(&pData->head.pNext); + while (pPage != NULL) { + ma_paged_audio_buffer_page* pNext = (ma_paged_audio_buffer_page*)c89atomic_load_ptr(&pPage->pNext); + + ma_free(pPage, pAllocationCallbacks); + pPage = pNext; + } +} + +MA_API ma_paged_audio_buffer_page* ma_paged_audio_buffer_data_get_head(ma_paged_audio_buffer_data* pData) +{ + if (pData == NULL) { + return NULL; + } + + return &pData->head; +} + +MA_API ma_paged_audio_buffer_page* ma_paged_audio_buffer_data_get_tail(ma_paged_audio_buffer_data* pData) +{ + if (pData == NULL) { + return NULL; + } + + return pData->pTail; +} + +MA_API ma_result ma_paged_audio_buffer_data_get_length_in_pcm_frames(ma_paged_audio_buffer_data* pData, ma_uint64* pLength) +{ + ma_paged_audio_buffer_page* pPage; + + if (pLength == NULL) { + return MA_INVALID_ARGS; + } + + *pLength = 0; + + if (pData == NULL) { + return MA_INVALID_ARGS; + } + + /* Calculate the length from the linked list. */ + for (pPage = (ma_paged_audio_buffer_page*)c89atomic_load_ptr(&pData->head.pNext); pPage != NULL; pPage = (ma_paged_audio_buffer_page*)c89atomic_load_ptr(&pPage->pNext)) { + *pLength += pPage->sizeInFrames; + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_paged_audio_buffer_data_allocate_page(ma_paged_audio_buffer_data* pData, ma_uint64 pageSizeInFrames, const void* pInitialData, const ma_allocation_callbacks* pAllocationCallbacks, ma_paged_audio_buffer_page** ppPage) +{ + ma_paged_audio_buffer_page* pPage; + ma_uint64 allocationSize; + + if (ppPage == NULL) { + return MA_INVALID_ARGS; + } + + *ppPage = NULL; + + if (pData == NULL) { + return MA_INVALID_ARGS; + } + + allocationSize = sizeof(*pPage) + (pageSizeInFrames * ma_get_bytes_per_frame(pData->format, pData->channels)); + if (allocationSize > MA_SIZE_MAX) { + return MA_OUT_OF_MEMORY; /* Too big. */ + } + + pPage = (ma_paged_audio_buffer_page*)ma_malloc((size_t)allocationSize, pAllocationCallbacks); /* Safe cast to size_t. */ + if (pPage == NULL) { + return MA_OUT_OF_MEMORY; + } + + pPage->pNext = NULL; + pPage->sizeInFrames = pageSizeInFrames; + + if (pInitialData != NULL) { + ma_copy_pcm_frames(pPage->pAudioData, pInitialData, pageSizeInFrames, pData->format, pData->channels); + } + + *ppPage = pPage; + + return MA_SUCCESS; +} + +MA_API ma_result ma_paged_audio_buffer_data_free_page(ma_paged_audio_buffer_data* pData, ma_paged_audio_buffer_page* pPage, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pData == NULL || pPage == NULL) { + return MA_INVALID_ARGS; + } + + /* It's assumed the page is not attached to the list. */ + ma_free(pPage, pAllocationCallbacks); + + return MA_SUCCESS; +} + +MA_API ma_result ma_paged_audio_buffer_data_append_page(ma_paged_audio_buffer_data* pData, ma_paged_audio_buffer_page* pPage) +{ + if (pData == NULL || pPage == NULL) { + return MA_INVALID_ARGS; + } + + /* This function assumes the page has been filled with audio data by this point. As soon as we append, the page will be available for reading. */ + + /* First thing to do is update the tail. */ + for (;;) { + ma_paged_audio_buffer_page* pOldTail = (ma_paged_audio_buffer_page*)c89atomic_load_ptr(&pData->pTail); + ma_paged_audio_buffer_page* pNewTail = pPage; + + if (c89atomic_compare_exchange_weak_ptr((volatile void**)&pData->pTail, (void**)&pOldTail, pNewTail)) { + /* Here is where we append the page to the list. After this, the page is attached to the list and ready to be read from. */ + c89atomic_exchange_ptr(&pOldTail->pNext, pPage); + break; /* Done. */ + } + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_paged_audio_buffer_data_allocate_and_append_page(ma_paged_audio_buffer_data* pData, ma_uint32 pageSizeInFrames, const void* pInitialData, const ma_allocation_callbacks* pAllocationCallbacks) +{ + ma_result result; + ma_paged_audio_buffer_page* pPage; + + result = ma_paged_audio_buffer_data_allocate_page(pData, pageSizeInFrames, pInitialData, pAllocationCallbacks, &pPage); + if (result != MA_SUCCESS) { + return result; + } + + return ma_paged_audio_buffer_data_append_page(pData, pPage); /* <-- Should never fail. */ +} + + +MA_API ma_paged_audio_buffer_config ma_paged_audio_buffer_config_init(ma_paged_audio_buffer_data* pData) +{ + ma_paged_audio_buffer_config config; + + MA_ZERO_OBJECT(&config); + config.pData = pData; + + return config; +} + + +static ma_result ma_paged_audio_buffer__data_source_on_read(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) +{ + return ma_paged_audio_buffer_read_pcm_frames((ma_paged_audio_buffer*)pDataSource, pFramesOut, frameCount, pFramesRead); +} + +static ma_result ma_paged_audio_buffer__data_source_on_seek(ma_data_source* pDataSource, ma_uint64 frameIndex) +{ + return ma_paged_audio_buffer_seek_to_pcm_frame((ma_paged_audio_buffer*)pDataSource, frameIndex); +} + +static ma_result ma_paged_audio_buffer__data_source_on_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) +{ + ma_paged_audio_buffer* pPagedAudioBuffer = (ma_paged_audio_buffer*)pDataSource; + + *pFormat = pPagedAudioBuffer->pData->format; + *pChannels = pPagedAudioBuffer->pData->channels; + *pSampleRate = 0; /* There is no notion of a sample rate with audio buffers. */ + ma_channel_map_init_standard(ma_standard_channel_map_default, pChannelMap, channelMapCap, pPagedAudioBuffer->pData->channels); + + return MA_SUCCESS; +} + +static ma_result ma_paged_audio_buffer__data_source_on_get_cursor(ma_data_source* pDataSource, ma_uint64* pCursor) +{ + return ma_paged_audio_buffer_get_cursor_in_pcm_frames((ma_paged_audio_buffer*)pDataSource, pCursor); +} + +static ma_result ma_paged_audio_buffer__data_source_on_get_length(ma_data_source* pDataSource, ma_uint64* pLength) +{ + return ma_paged_audio_buffer_get_length_in_pcm_frames((ma_paged_audio_buffer*)pDataSource, pLength); +} + +static ma_data_source_vtable g_ma_paged_audio_buffer_data_source_vtable = +{ + ma_paged_audio_buffer__data_source_on_read, + ma_paged_audio_buffer__data_source_on_seek, + ma_paged_audio_buffer__data_source_on_get_data_format, + ma_paged_audio_buffer__data_source_on_get_cursor, + ma_paged_audio_buffer__data_source_on_get_length, + NULL, /* onSetLooping */ + 0 +}; + +MA_API ma_result ma_paged_audio_buffer_init(const ma_paged_audio_buffer_config* pConfig, ma_paged_audio_buffer* pPagedAudioBuffer) +{ + ma_result result; + ma_data_source_config dataSourceConfig; + + if (pPagedAudioBuffer == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pPagedAudioBuffer); + + /* A config is required for the format and channel count. */ + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->pData == NULL) { + return MA_INVALID_ARGS; /* No underlying data specified. */ + } + + dataSourceConfig = ma_data_source_config_init(); + dataSourceConfig.vtable = &g_ma_paged_audio_buffer_data_source_vtable; + + result = ma_data_source_init(&dataSourceConfig, &pPagedAudioBuffer->ds); + if (result != MA_SUCCESS) { + return result; + } + + pPagedAudioBuffer->pData = pConfig->pData; + pPagedAudioBuffer->pCurrent = ma_paged_audio_buffer_data_get_head(pConfig->pData); + pPagedAudioBuffer->relativeCursor = 0; + pPagedAudioBuffer->absoluteCursor = 0; + + return MA_SUCCESS; +} + +MA_API void ma_paged_audio_buffer_uninit(ma_paged_audio_buffer* pPagedAudioBuffer) +{ + if (pPagedAudioBuffer == NULL) { + return; + } + + /* Nothing to do. The data needs to be deleted separately. */ +} + +MA_API ma_result ma_paged_audio_buffer_read_pcm_frames(ma_paged_audio_buffer* pPagedAudioBuffer, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) +{ + ma_result result = MA_SUCCESS; + ma_uint64 totalFramesRead = 0; + ma_format format; + ma_uint32 channels; + + if (pPagedAudioBuffer == NULL) { + return MA_INVALID_ARGS; + } + + format = pPagedAudioBuffer->pData->format; + channels = pPagedAudioBuffer->pData->channels; + + while (totalFramesRead < frameCount) { + /* Read from the current page. The buffer should never be in a state where this is NULL. */ + ma_uint64 framesRemainingInCurrentPage; + ma_uint64 framesRemainingToRead = frameCount - totalFramesRead; + ma_uint64 framesToReadThisIteration; + + MA_ASSERT(pPagedAudioBuffer->pCurrent != NULL); + + framesRemainingInCurrentPage = pPagedAudioBuffer->pCurrent->sizeInFrames - pPagedAudioBuffer->relativeCursor; + + framesToReadThisIteration = ma_min(framesRemainingInCurrentPage, framesRemainingToRead); + ma_copy_pcm_frames(ma_offset_pcm_frames_ptr(pFramesOut, totalFramesRead, format, channels), ma_offset_pcm_frames_ptr(pPagedAudioBuffer->pCurrent->pAudioData, pPagedAudioBuffer->relativeCursor, format, channels), framesToReadThisIteration, format, channels); + totalFramesRead += framesToReadThisIteration; + + pPagedAudioBuffer->absoluteCursor += framesToReadThisIteration; + pPagedAudioBuffer->relativeCursor += framesToReadThisIteration; + + /* Move to the next page if necessary. If there's no more pages, we need to return MA_AT_END. */ + MA_ASSERT(pPagedAudioBuffer->relativeCursor <= pPagedAudioBuffer->pCurrent->sizeInFrames); + + if (pPagedAudioBuffer->relativeCursor == pPagedAudioBuffer->pCurrent->sizeInFrames) { + /* We reached the end of the page. Need to move to the next. If there's no more pages, we're done. */ + ma_paged_audio_buffer_page* pNext = (ma_paged_audio_buffer_page*)c89atomic_load_ptr(&pPagedAudioBuffer->pCurrent->pNext); + if (pNext == NULL) { + result = MA_AT_END; + break; /* We've reached the end. */ + } else { + pPagedAudioBuffer->pCurrent = pNext; + pPagedAudioBuffer->relativeCursor = 0; + } + } + } + + if (pFramesRead != NULL) { + *pFramesRead = totalFramesRead; + } + + return result; +} + +MA_API ma_result ma_paged_audio_buffer_seek_to_pcm_frame(ma_paged_audio_buffer* pPagedAudioBuffer, ma_uint64 frameIndex) +{ + if (pPagedAudioBuffer == NULL) { + return MA_INVALID_ARGS; + } + + if (frameIndex == pPagedAudioBuffer->absoluteCursor) { + return MA_SUCCESS; /* Nothing to do. */ + } + + if (frameIndex < pPagedAudioBuffer->absoluteCursor) { + /* Moving backwards. Need to move the cursor back to the start, and then move forward. */ + pPagedAudioBuffer->pCurrent = ma_paged_audio_buffer_data_get_head(pPagedAudioBuffer->pData); + pPagedAudioBuffer->absoluteCursor = 0; + pPagedAudioBuffer->relativeCursor = 0; + + /* Fall through to the forward seeking section below. */ + } + + if (frameIndex > pPagedAudioBuffer->absoluteCursor) { + /* Moving forward. */ + ma_paged_audio_buffer_page* pPage; + ma_uint64 runningCursor = 0; + + for (pPage = (ma_paged_audio_buffer_page*)c89atomic_load_ptr(&ma_paged_audio_buffer_data_get_head(pPagedAudioBuffer->pData)->pNext); pPage != NULL; pPage = (ma_paged_audio_buffer_page*)c89atomic_load_ptr(&pPage->pNext)) { + ma_uint64 pageRangeBeg = runningCursor; + ma_uint64 pageRangeEnd = pageRangeBeg + pPage->sizeInFrames; + + if (frameIndex >= pageRangeBeg) { + if (frameIndex < pageRangeEnd || (frameIndex == pageRangeEnd && pPage == (ma_paged_audio_buffer_page*)c89atomic_load_ptr(ma_paged_audio_buffer_data_get_tail(pPagedAudioBuffer->pData)))) { /* A small edge case - allow seeking to the very end of the buffer. */ + /* We found the page. */ + pPagedAudioBuffer->pCurrent = pPage; + pPagedAudioBuffer->absoluteCursor = frameIndex; + pPagedAudioBuffer->relativeCursor = frameIndex - pageRangeBeg; + return MA_SUCCESS; + } + } + + runningCursor = pageRangeEnd; + } + + /* Getting here means we tried seeking too far forward. Don't change any state. */ + return MA_BAD_SEEK; + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_paged_audio_buffer_get_cursor_in_pcm_frames(ma_paged_audio_buffer* pPagedAudioBuffer, ma_uint64* pCursor) +{ + if (pCursor == NULL) { + return MA_INVALID_ARGS; + } + + *pCursor = 0; /* Safety. */ + + if (pPagedAudioBuffer == NULL) { + return MA_INVALID_ARGS; + } + + *pCursor = pPagedAudioBuffer->absoluteCursor; + + return MA_SUCCESS; +} + +MA_API ma_result ma_paged_audio_buffer_get_length_in_pcm_frames(ma_paged_audio_buffer* pPagedAudioBuffer, ma_uint64* pLength) +{ + return ma_paged_audio_buffer_data_get_length_in_pcm_frames(pPagedAudioBuffer->pData, pLength); +} + + + /************************************************************************************************************************************************************** VFS @@ -45119,6 +56316,8 @@ MA_API ma_result ma_vfs_close(ma_vfs* pVFS, ma_vfs_file file) MA_API ma_result ma_vfs_read(ma_vfs* pVFS, ma_vfs_file file, void* pDst, size_t sizeInBytes, size_t* pBytesRead) { ma_vfs_callbacks* pCallbacks = (ma_vfs_callbacks*)pVFS; + ma_result result; + size_t bytesRead; if (pBytesRead != NULL) { *pBytesRead = 0; @@ -45132,7 +56331,17 @@ MA_API ma_result ma_vfs_read(ma_vfs* pVFS, ma_vfs_file file, void* pDst, size_t return MA_NOT_IMPLEMENTED; } - return pCallbacks->onRead(pVFS, file, pDst, sizeInBytes, pBytesRead); + result = pCallbacks->onRead(pVFS, file, pDst, sizeInBytes, &bytesRead); + + if (pBytesRead != NULL) { + *pBytesRead = bytesRead; + } + + if (result == MA_SUCCESS && bytesRead == 0 && sizeInBytes > 0) { + result = MA_AT_END; + } + + return result; } MA_API ma_result ma_vfs_write(ma_vfs* pVFS, ma_vfs_file file, const void* pSrc, size_t sizeInBytes, size_t* pBytesWritten) @@ -45212,7 +56421,7 @@ MA_API ma_result ma_vfs_info(ma_vfs* pVFS, ma_vfs_file file, ma_file_info* pInfo } -static ma_result ma_vfs_open_and_read_file_ex(ma_vfs* pVFS, const char* pFilePath, const wchar_t* pFilePathW, void** ppData, size_t* pSize, const ma_allocation_callbacks* pAllocationCallbacks, ma_uint32 allocationType) +static ma_result ma_vfs_open_and_read_file_ex(ma_vfs* pVFS, const char* pFilePath, const wchar_t* pFilePathW, void** ppData, size_t* pSize, const ma_allocation_callbacks* pAllocationCallbacks) { ma_result result; ma_vfs_file file; @@ -45220,8 +56429,6 @@ static ma_result ma_vfs_open_and_read_file_ex(ma_vfs* pVFS, const char* pFilePat void* pData; size_t bytesRead; - (void)allocationType; - if (ppData != NULL) { *ppData = NULL; } @@ -45253,7 +56460,7 @@ static ma_result ma_vfs_open_and_read_file_ex(ma_vfs* pVFS, const char* pFilePat return MA_TOO_BIG; } - pData = ma__malloc_from_callbacks((size_t)info.sizeInBytes, pAllocationCallbacks); /* Safe cast. */ + pData = ma_malloc((size_t)info.sizeInBytes, pAllocationCallbacks); /* Safe cast. */ if (pData == NULL) { ma_vfs_close(pVFS, file); return result; @@ -45263,7 +56470,7 @@ static ma_result ma_vfs_open_and_read_file_ex(ma_vfs* pVFS, const char* pFilePat ma_vfs_close(pVFS, file); if (result != MA_SUCCESS) { - ma__free_from_callbacks(pData, pAllocationCallbacks); + ma_free(pData, pAllocationCallbacks); return result; } @@ -45279,12 +56486,12 @@ static ma_result ma_vfs_open_and_read_file_ex(ma_vfs* pVFS, const char* pFilePat MA_API ma_result ma_vfs_open_and_read_file(ma_vfs* pVFS, const char* pFilePath, void** ppData, size_t* pSize, const ma_allocation_callbacks* pAllocationCallbacks) { - return ma_vfs_open_and_read_file_ex(pVFS, pFilePath, NULL, ppData, pSize, pAllocationCallbacks, 0 /*MA_ALLOCATION_TYPE_GENERAL*/); + return ma_vfs_open_and_read_file_ex(pVFS, pFilePath, NULL, ppData, pSize, pAllocationCallbacks); } MA_API ma_result ma_vfs_open_and_read_file_w(ma_vfs* pVFS, const wchar_t* pFilePath, void** ppData, size_t* pSize, const ma_allocation_callbacks* pAllocationCallbacks) { - return ma_vfs_open_and_read_file_ex(pVFS, NULL, pFilePath, ppData, pSize, pAllocationCallbacks, 0 /*MA_ALLOCATION_TYPE_GENERAL*/); + return ma_vfs_open_and_read_file_ex(pVFS, NULL, pFilePath, ppData, pSize, pAllocationCallbacks); } @@ -45992,7 +57199,7 @@ extern "C" { #define DRWAV_XSTRINGIFY(x) DRWAV_STRINGIFY(x) #define DRWAV_VERSION_MAJOR 0 #define DRWAV_VERSION_MINOR 13 -#define DRWAV_VERSION_REVISION 1 +#define DRWAV_VERSION_REVISION 6 #define DRWAV_VERSION_STRING DRWAV_XSTRINGIFY(DRWAV_VERSION_MAJOR) "." DRWAV_XSTRINGIFY(DRWAV_VERSION_MINOR) "." DRWAV_XSTRINGIFY(DRWAV_VERSION_REVISION) #include typedef signed char drwav_int8; @@ -46527,7 +57734,7 @@ extern "C" { #define DRFLAC_XSTRINGIFY(x) DRFLAC_STRINGIFY(x) #define DRFLAC_VERSION_MAJOR 0 #define DRFLAC_VERSION_MINOR 12 -#define DRFLAC_VERSION_REVISION 31 +#define DRFLAC_VERSION_REVISION 38 #define DRFLAC_VERSION_STRING DRFLAC_XSTRINGIFY(DRFLAC_VERSION_MAJOR) "." DRFLAC_XSTRINGIFY(DRFLAC_VERSION_MINOR) "." DRFLAC_XSTRINGIFY(DRFLAC_VERSION_REVISION) #include typedef signed char drflac_int8; @@ -46536,7 +57743,7 @@ typedef signed short drflac_int16; typedef unsigned short drflac_uint16; typedef signed int drflac_int32; typedef unsigned int drflac_uint32; -#if defined(_MSC_VER) +#if defined(_MSC_VER) && !defined(__clang__) typedef signed __int64 drflac_int64; typedef unsigned __int64 drflac_uint64; #else @@ -46553,7 +57760,7 @@ typedef unsigned int drflac_uint32; #pragma GCC diagnostic pop #endif #endif -#if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__)) || defined(_M_X64) || defined(__ia64) || defined (_M_IA64) || defined(__aarch64__) || defined(_M_ARM64) || defined(__powerpc64__) +#if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__)) || defined(_M_X64) || defined(__ia64) || defined(_M_IA64) || defined(__aarch64__) || defined(_M_ARM64) || defined(__powerpc64__) typedef drflac_uint64 drflac_uintptr; #else typedef drflac_uint32 drflac_uintptr; @@ -46888,7 +58095,7 @@ extern "C" { #define DRMP3_XSTRINGIFY(x) DRMP3_STRINGIFY(x) #define DRMP3_VERSION_MAJOR 0 #define DRMP3_VERSION_MINOR 6 -#define DRMP3_VERSION_REVISION 31 +#define DRMP3_VERSION_REVISION 33 #define DRMP3_VERSION_STRING DRMP3_XSTRINGIFY(DRMP3_VERSION_MAJOR) "." DRMP3_XSTRINGIFY(DRMP3_VERSION_MINOR) "." DRMP3_XSTRINGIFY(DRMP3_VERSION_REVISION) #include typedef signed char drmp3_int8; @@ -46897,7 +58104,7 @@ typedef signed short drmp3_int16; typedef unsigned short drmp3_uint16; typedef signed int drmp3_int32; typedef unsigned int drmp3_uint32; -#if defined(_MSC_VER) +#if defined(_MSC_VER) && !defined(__clang__) typedef signed __int64 drmp3_int64; typedef unsigned __int64 drmp3_uint64; #else @@ -47012,9 +58219,14 @@ typedef drmp3_int32 drmp3_result; #define DRMP3_INLINE __forceinline #elif defined(__GNUC__) #if defined(__STRICT_ANSI__) - #define DRMP3_INLINE __inline__ __attribute__((always_inline)) + #define DRMP3_GNUC_INLINE_HINT __inline__ #else - #define DRMP3_INLINE inline __attribute__((always_inline)) + #define DRMP3_GNUC_INLINE_HINT inline + #endif + #if (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 2)) || defined(__clang__) + #define DRMP3_INLINE DRMP3_GNUC_INLINE_HINT __attribute__((always_inline)) + #else + #define DRMP3_INLINE DRMP3_GNUC_INLINE_HINT #endif #elif defined(__WATCOMC__) #define DRMP3_INLINE __inline @@ -47135,43 +58347,22 @@ Decoding static ma_result ma_decoder_read_bytes(ma_decoder* pDecoder, void* pBufferOut, size_t bytesToRead, size_t* pBytesRead) { - size_t bytesRead; + MA_ASSERT(pDecoder != NULL); - MA_ASSERT(pDecoder != NULL); - MA_ASSERT(pBufferOut != NULL); - MA_ASSERT(bytesToRead > 0); /* It's an error to call this with a byte count of zero. */ - - bytesRead = pDecoder->onRead(pDecoder, pBufferOut, bytesToRead); - - if (pBytesRead != NULL) { - *pBytesRead = bytesRead; - } - - if (bytesRead == 0) { - return MA_AT_END; - } - - return MA_SUCCESS; + return pDecoder->onRead(pDecoder, pBufferOut, bytesToRead, pBytesRead); } static ma_result ma_decoder_seek_bytes(ma_decoder* pDecoder, ma_int64 byteOffset, ma_seek_origin origin) { - ma_bool32 wasSuccessful; - MA_ASSERT(pDecoder != NULL); - wasSuccessful = pDecoder->onSeek(pDecoder, byteOffset, origin); - if (wasSuccessful) { - return MA_SUCCESS; - } else { - return MA_ERROR; - } + return pDecoder->onSeek(pDecoder, byteOffset, origin); } static ma_result ma_decoder_tell_bytes(ma_decoder* pDecoder, ma_int64* pCursor) { MA_ASSERT(pDecoder != NULL); - + if (pDecoder->onTell == NULL) { return MA_NOT_IMPLEMENTED; } @@ -47180,12 +58371,13 @@ static ma_result ma_decoder_tell_bytes(ma_decoder* pDecoder, ma_int64* pCursor) } -MA_API ma_decoding_backend_config ma_decoding_backend_config_init(ma_format preferredFormat) +MA_API ma_decoding_backend_config ma_decoding_backend_config_init(ma_format preferredFormat, ma_uint32 seekPointCount) { ma_decoding_backend_config config; MA_ZERO_OBJECT(&config); config.preferredFormat = preferredFormat; + config.seekPointCount = seekPointCount; return config; } @@ -47195,12 +58387,10 @@ MA_API ma_decoder_config ma_decoder_config_init(ma_format outputFormat, ma_uint3 { ma_decoder_config config; MA_ZERO_OBJECT(&config); - config.format = outputFormat; - config.channels = ma_min(outputChannels, ma_countof(config.channelMap)); - config.sampleRate = outputSampleRate; - config.resampling.algorithm = ma_resample_algorithm_linear; - config.resampling.linear.lpfOrder = ma_min(MA_DEFAULT_RESAMPLER_LPF_ORDER, MA_MAX_FILTER_ORDER); - config.resampling.speex.quality = 3; + config.format = outputFormat; + config.channels = outputChannels; + config.sampleRate = outputSampleRate; + config.resampling = ma_resampler_config_init(ma_format_unknown, 0, 0, 0, ma_resample_algorithm_linear); /* Format/channels/rate doesn't matter here. */ config.encodingFormat = ma_encoding_format_unknown; /* Note that we are intentionally leaving the channel map empty here which will cause the default channel map to be used. */ @@ -47237,19 +58427,11 @@ static ma_result ma_decoder__init_data_converter(ma_decoder* pDecoder, const ma_ MA_ASSERT(pDecoder != NULL); MA_ASSERT(pConfig != NULL); - result = ma_data_source_get_data_format(pDecoder->pBackend, &internalFormat, &internalChannels, &internalSampleRate); + result = ma_data_source_get_data_format(pDecoder->pBackend, &internalFormat, &internalChannels, &internalSampleRate, internalChannelMap, ma_countof(internalChannelMap)); if (result != MA_SUCCESS) { return result; /* Failed to retrieve the internal data format. */ } - /* Channel map needs to be retrieved separately. */ - if (pDecoder->pBackendVTable != NULL && pDecoder->pBackendVTable->onGetChannelMap != NULL) { - pDecoder->pBackendVTable->onGetChannelMap(pDecoder->pBackendUserData, pDecoder->pBackend, internalChannelMap, ma_countof(internalChannelMap)); - } else { - ma_get_standard_channel_map(ma_standard_channel_map_default, ma_min(internalChannels, ma_countof(internalChannelMap)), internalChannelMap); - } - - /* Make sure we're not asking for too many channels. */ if (pConfig->channels > MA_MAX_CHANNELS) { @@ -47281,28 +58463,57 @@ static ma_result ma_decoder__init_data_converter(ma_decoder* pDecoder, const ma_ pDecoder->outputSampleRate = pConfig->sampleRate; } - if (ma_channel_map_blank(pDecoder->outputChannels, pConfig->channelMap)) { - ma_get_standard_channel_map(ma_standard_channel_map_default, pDecoder->outputChannels, pDecoder->outputChannelMap); - } else { - MA_COPY_MEMORY(pDecoder->outputChannelMap, pConfig->channelMap, sizeof(pConfig->channelMap)); - } - - converterConfig = ma_data_converter_config_init( internalFormat, pDecoder->outputFormat, internalChannels, pDecoder->outputChannels, internalSampleRate, pDecoder->outputSampleRate ); - ma_channel_map_copy(converterConfig.channelMapIn, internalChannelMap, internalChannels); - ma_channel_map_copy(converterConfig.channelMapOut, pDecoder->outputChannelMap, pDecoder->outputChannels); - converterConfig.channelMixMode = pConfig->channelMixMode; - converterConfig.ditherMode = pConfig->ditherMode; - converterConfig.resampling.allowDynamicSampleRate = MA_FALSE; /* Never allow dynamic sample rate conversion. Setting this to true will disable passthrough optimizations. */ - converterConfig.resampling.algorithm = pConfig->resampling.algorithm; - converterConfig.resampling.linear.lpfOrder = pConfig->resampling.linear.lpfOrder; - converterConfig.resampling.speex.quality = pConfig->resampling.speex.quality; + converterConfig.pChannelMapIn = internalChannelMap; + converterConfig.pChannelMapOut = pConfig->pChannelMap; + converterConfig.channelMixMode = pConfig->channelMixMode; + converterConfig.ditherMode = pConfig->ditherMode; + converterConfig.allowDynamicSampleRate = MA_FALSE; /* Never allow dynamic sample rate conversion. Setting this to true will disable passthrough optimizations. */ + converterConfig.resampling = pConfig->resampling; - return ma_data_converter_init(&converterConfig, &pDecoder->converter); + result = ma_data_converter_init(&converterConfig, &pDecoder->allocationCallbacks, &pDecoder->converter); + if (result != MA_SUCCESS) { + return result; + } + + /* + Now that we have the decoder we need to determine whether or not we need a heap-allocated cache. We'll + need this if the data converter does not support calculation of the required input frame count. To + determine support for this we'll just run a test. + */ + { + ma_uint64 unused; + + result = ma_data_converter_get_required_input_frame_count(&pDecoder->converter, 1, &unused); + if (result != MA_SUCCESS) { + /* + We were unable to calculate the required input frame count which means we'll need to use + a heap-allocated cache. + */ + ma_uint64 inputCacheCapSizeInBytes; + + pDecoder->inputCacheCap = MA_DATA_CONVERTER_STACK_BUFFER_SIZE / ma_get_bytes_per_frame(internalFormat, internalChannels); + + /* Not strictly necessary, but keeping here for safety in case we change the default value of pDecoder->inputCacheCap. */ + inputCacheCapSizeInBytes = pDecoder->inputCacheCap * ma_get_bytes_per_frame(internalFormat, internalChannels); + if (inputCacheCapSizeInBytes > MA_SIZE_MAX) { + ma_data_converter_uninit(&pDecoder->converter, &pDecoder->allocationCallbacks); + return MA_OUT_OF_MEMORY; + } + + pDecoder->pInputCache = ma_malloc((size_t)inputCacheCapSizeInBytes, &pDecoder->allocationCallbacks); /* Safe cast to size_t. */ + if (pDecoder->pInputCache == NULL) { + ma_data_converter_uninit(&pDecoder->converter, &pDecoder->allocationCallbacks); + return MA_OUT_OF_MEMORY; + } + } + } + + return MA_SUCCESS; } @@ -47346,7 +58557,7 @@ static ma_result ma_decoder_init_from_vtable(const ma_decoding_backend_vtable* p return MA_NOT_IMPLEMENTED; } - backendConfig = ma_decoding_backend_config_init(pConfig->format); + backendConfig = ma_decoding_backend_config_init(pConfig->format, pConfig->seekPointCount); result = pVTable->onInit(pVTableUserData, ma_decoder_internal_on_read__custom, ma_decoder_internal_on_seek__custom, ma_decoder_internal_on_tell__custom, pDecoder, &backendConfig, &pDecoder->allocationCallbacks, &pBackend); if (result != MA_SUCCESS) { @@ -47438,9 +58649,9 @@ static ma_result ma_wav_ds_seek(ma_data_source* pDataSource, ma_uint64 frameInde return ma_wav_seek_to_pcm_frame((ma_wav*)pDataSource, frameIndex); } -static ma_result ma_wav_ds_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate) +static ma_result ma_wav_ds_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) { - return ma_wav_get_data_format((ma_wav*)pDataSource, pFormat, pChannels, pSampleRate, NULL, 0); + return ma_wav_get_data_format((ma_wav*)pDataSource, pFormat, pChannels, pSampleRate, pChannelMap, channelMapCap); } static ma_result ma_wav_ds_get_cursor(ma_data_source* pDataSource, ma_uint64* pCursor) @@ -47457,11 +58668,11 @@ static ma_data_source_vtable g_ma_wav_ds_vtable = { ma_wav_ds_read, ma_wav_ds_seek, - NULL, /* onMap() */ - NULL, /* onUnmap() */ ma_wav_ds_get_data_format, ma_wav_ds_get_cursor, - ma_wav_ds_get_length + ma_wav_ds_get_length, + NULL, /* onSetLooping */ + 0 }; @@ -47531,7 +58742,7 @@ static ma_result ma_wav_init_internal(const ma_decoding_backend_config* pConfig, } MA_ZERO_OBJECT(pWav); - pWav->format = ma_format_f32; /* f32 by default. */ + pWav->format = ma_format_unknown; /* Use closest match to source file by default. */ if (pConfig != NULL && (pConfig->preferredFormat == ma_format_f32 || pConfig->preferredFormat == ma_format_s16 || pConfig->preferredFormat == ma_format_s32)) { pWav->format = pConfig->preferredFormat; @@ -47578,6 +58789,42 @@ MA_API ma_result ma_wav_init(ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_p return MA_INVALID_FILE; } + /* + If an explicit format was not specified, try picking the closest match based on the internal + format. The format needs to be supported by miniaudio. + */ + if (pWav->format == ma_format_unknown) { + switch (pWav->dr.translatedFormatTag) + { + case DR_WAVE_FORMAT_PCM: + { + if (pWav->dr.bitsPerSample == 8) { + pWav->format = ma_format_u8; + } else if (pWav->dr.bitsPerSample == 16) { + pWav->format = ma_format_s16; + } else if (pWav->dr.bitsPerSample == 24) { + pWav->format = ma_format_s24; + } else if (pWav->dr.bitsPerSample == 32) { + pWav->format = ma_format_s32; + } + } break; + + case DR_WAVE_FORMAT_IEEE_FLOAT: + { + if (pWav->dr.bitsPerSample == 32) { + pWav->format = ma_format_f32; + } + } break; + + default: break; + } + + /* Fall back to f32 if we couldn't find anything. */ + if (pWav->format == ma_format_unknown) { + pWav->format = ma_format_f32; + } + } + return MA_SUCCESS; } #else @@ -47707,6 +58954,14 @@ MA_API void ma_wav_uninit(ma_wav* pWav, const ma_allocation_callbacks* pAllocati MA_API ma_result ma_wav_read_pcm_frames(ma_wav* pWav, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) { + if (pFramesRead != NULL) { + *pFramesRead = 0; + } + + if (frameCount == 0) { + return MA_INVALID_ARGS; + } + if (pWav == NULL) { return MA_INVALID_ARGS; } @@ -47738,7 +58993,7 @@ MA_API ma_result ma_wav_read_pcm_frames(ma_wav* pWav, void* pFramesOut, ma_uint6 } break; /* Fallback to a raw read. */ - case ma_format_unknown: return MA_INVALID_OPERATION; /* <-- this should never be hit because initialization would just fall back to supported format. */ + case ma_format_unknown: return MA_INVALID_OPERATION; /* <-- this should never be hit because initialization would just fall back to a supported format. */ default: { totalFramesRead = drwav_read_pcm_frames(&pWav->dr, frameCount, pFramesOut); @@ -47754,6 +59009,10 @@ MA_API ma_result ma_wav_read_pcm_frames(ma_wav* pWav, void* pFramesOut, ma_uint6 *pFramesRead = totalFramesRead; } + if (result == MA_SUCCESS && totalFramesRead == 0) { + result = MA_AT_END; + } + return result; } #else @@ -47779,7 +59038,7 @@ MA_API ma_result ma_wav_seek_to_pcm_frame(ma_wav* pWav, ma_uint64 frameIndex) #if !defined(MA_NO_WAV) { drwav_bool32 wavResult; - + wavResult = drwav_seek_to_pcm_frame(&pWav->dr, frameIndex); if (wavResult != DRWAV_TRUE) { return MA_ERROR; @@ -47834,7 +59093,7 @@ MA_API ma_result ma_wav_get_data_format(ma_wav* pWav, ma_format* pFormat, ma_uin } if (pChannelMap != NULL) { - ma_get_standard_channel_map(ma_standard_channel_map_microsoft, (ma_uint32)ma_min(pWav->dr.channels, channelMapCap), pChannelMap); + ma_channel_map_init_standard(ma_standard_channel_map_microsoft, pChannelMap, channelMapCap, pWav->dr.channels); } return MA_SUCCESS; @@ -48015,23 +59274,13 @@ static void ma_decoding_backend_uninit__wav(void* pUserData, ma_data_source* pBa ma_free(pWav, pAllocationCallbacks); } -static ma_result ma_decoding_backend_get_channel_map__wav(void* pUserData, ma_data_source* pBackend, ma_channel* pChannelMap, size_t channelMapCap) -{ - ma_wav* pWav = (ma_wav*)pBackend; - - (void)pUserData; - - return ma_wav_get_data_format(pWav, NULL, NULL, NULL, pChannelMap, channelMapCap); -} - static ma_decoding_backend_vtable g_ma_decoding_backend_vtable_wav = { ma_decoding_backend_init__wav, ma_decoding_backend_init_file__wav, ma_decoding_backend_init_file_w__wav, ma_decoding_backend_init_memory__wav, - ma_decoding_backend_uninit__wav, - ma_decoding_backend_get_channel_map__wav + ma_decoding_backend_uninit__wav }; static ma_result ma_decoder_init_wav__internal(const ma_decoder_config* pConfig, ma_decoder* pDecoder) @@ -48079,9 +59328,9 @@ static ma_result ma_flac_ds_seek(ma_data_source* pDataSource, ma_uint64 frameInd return ma_flac_seek_to_pcm_frame((ma_flac*)pDataSource, frameIndex); } -static ma_result ma_flac_ds_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate) +static ma_result ma_flac_ds_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) { - return ma_flac_get_data_format((ma_flac*)pDataSource, pFormat, pChannels, pSampleRate, NULL, 0); + return ma_flac_get_data_format((ma_flac*)pDataSource, pFormat, pChannels, pSampleRate, pChannelMap, channelMapCap); } static ma_result ma_flac_ds_get_cursor(ma_data_source* pDataSource, ma_uint64* pCursor) @@ -48098,11 +59347,11 @@ static ma_data_source_vtable g_ma_flac_ds_vtable = { ma_flac_ds_read, ma_flac_ds_seek, - NULL, /* onMap() */ - NULL, /* onUnmap() */ ma_flac_ds_get_data_format, ma_flac_ds_get_cursor, - ma_flac_ds_get_length + ma_flac_ds_get_length, + NULL, /* onSetLooping */ + 0 }; @@ -48344,6 +59593,14 @@ MA_API void ma_flac_uninit(ma_flac* pFlac, const ma_allocation_callbacks* pAlloc MA_API ma_result ma_flac_read_pcm_frames(ma_flac* pFlac, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) { + if (pFramesRead != NULL) { + *pFramesRead = 0; + } + + if (frameCount == 0) { + return MA_INVALID_ARGS; + } + if (pFlac == NULL) { return MA_INVALID_ARGS; } @@ -48392,6 +59649,10 @@ MA_API ma_result ma_flac_read_pcm_frames(ma_flac* pFlac, void* pFramesOut, ma_ui *pFramesRead = totalFramesRead; } + if (result == MA_SUCCESS && totalFramesRead == 0) { + result = MA_AT_END; + } + return result; } #else @@ -48417,7 +59678,7 @@ MA_API ma_result ma_flac_seek_to_pcm_frame(ma_flac* pFlac, ma_uint64 frameIndex) #if !defined(MA_NO_FLAC) { drflac_bool32 flacResult; - + flacResult = drflac_seek_to_pcm_frame(pFlac->dr, frameIndex); if (flacResult != DRFLAC_TRUE) { return MA_ERROR; @@ -48472,7 +59733,7 @@ MA_API ma_result ma_flac_get_data_format(ma_flac* pFlac, ma_format* pFormat, ma_ } if (pChannelMap != NULL) { - ma_get_standard_channel_map(ma_standard_channel_map_microsoft, (ma_uint32)ma_min(pFlac->dr->channels, channelMapCap), pChannelMap); + ma_channel_map_init_standard(ma_standard_channel_map_microsoft, pChannelMap, channelMapCap, pFlac->dr->channels); } return MA_SUCCESS; @@ -48647,23 +59908,13 @@ static void ma_decoding_backend_uninit__flac(void* pUserData, ma_data_source* pB ma_free(pFlac, pAllocationCallbacks); } -static ma_result ma_decoding_backend_get_channel_map__flac(void* pUserData, ma_data_source* pBackend, ma_channel* pChannelMap, size_t channelMapCap) -{ - ma_flac* pFlac = (ma_flac*)pBackend; - - (void)pUserData; - - return ma_flac_get_data_format(pFlac, NULL, NULL, NULL, pChannelMap, channelMapCap); -} - static ma_decoding_backend_vtable g_ma_decoding_backend_vtable_flac = { ma_decoding_backend_init__flac, ma_decoding_backend_init_file__flac, ma_decoding_backend_init_file_w__flac, ma_decoding_backend_init_memory__flac, - ma_decoding_backend_uninit__flac, - ma_decoding_backend_get_channel_map__flac + ma_decoding_backend_uninit__flac }; static ma_result ma_decoder_init_flac__internal(const ma_decoder_config* pConfig, ma_decoder* pDecoder) @@ -48686,6 +59937,8 @@ typedef struct ma_format format; /* Can be f32 or s16. */ #if !defined(MA_NO_MP3) drmp3 dr; + drmp3_uint32 seekPointCount; + drmp3_seek_point* pSeekPoints; /* Only used if seek table generation is used. */ #endif } ma_mp3; @@ -48711,9 +59964,9 @@ static ma_result ma_mp3_ds_seek(ma_data_source* pDataSource, ma_uint64 frameInde return ma_mp3_seek_to_pcm_frame((ma_mp3*)pDataSource, frameIndex); } -static ma_result ma_mp3_ds_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate) +static ma_result ma_mp3_ds_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) { - return ma_mp3_get_data_format((ma_mp3*)pDataSource, pFormat, pChannels, pSampleRate, NULL, 0); + return ma_mp3_get_data_format((ma_mp3*)pDataSource, pFormat, pChannels, pSampleRate, pChannelMap, channelMapCap); } static ma_result ma_mp3_ds_get_cursor(ma_data_source* pDataSource, ma_uint64* pCursor) @@ -48730,11 +59983,11 @@ static ma_data_source_vtable g_ma_mp3_ds_vtable = { ma_mp3_ds_read, ma_mp3_ds_seek, - NULL, /* onMap() */ - NULL, /* onUnmap() */ ma_mp3_ds_get_data_format, ma_mp3_ds_get_cursor, - ma_mp3_ds_get_length + ma_mp3_ds_get_length, + NULL, /* onSetLooping */ + 0 }; @@ -48823,6 +60076,40 @@ static ma_result ma_mp3_init_internal(const ma_decoding_backend_config* pConfig, return MA_SUCCESS; } +static ma_result ma_mp3_generate_seek_table(ma_mp3* pMP3, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks) +{ + drmp3_bool32 mp3Result; + drmp3_uint32 seekPointCount = 0; + drmp3_seek_point* pSeekPoints = NULL; + + MA_ASSERT(pMP3 != NULL); + MA_ASSERT(pConfig != NULL); + + seekPointCount = pConfig->seekPointCount; + if (seekPointCount > 0) { + pSeekPoints = (drmp3_seek_point*)ma_malloc(sizeof(*pMP3->pSeekPoints) * seekPointCount, pAllocationCallbacks); + if (pSeekPoints == NULL) { + return MA_OUT_OF_MEMORY; + } + } + + mp3Result = drmp3_calculate_seek_points(&pMP3->dr, &seekPointCount, pSeekPoints); + if (mp3Result != MA_TRUE) { + return MA_ERROR; + } + + mp3Result = drmp3_bind_seek_table(&pMP3->dr, seekPointCount, pSeekPoints); + if (mp3Result != MA_TRUE) { + ma_free(pSeekPoints, pAllocationCallbacks); + return MA_ERROR; + } + + pMP3->seekPointCount = seekPointCount; + pMP3->pSeekPoints = pSeekPoints; + + return MA_SUCCESS; +} + MA_API ma_result ma_mp3_init(ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void* pReadSeekTellUserData, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_mp3* pMP3) { ma_result result; @@ -48851,6 +60138,8 @@ MA_API ma_result ma_mp3_init(ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_p return MA_INVALID_FILE; } + ma_mp3_generate_seek_table(pMP3, pConfig, pAllocationCallbacks); + return MA_SUCCESS; } #else @@ -48881,6 +60170,8 @@ MA_API ma_result ma_mp3_init_file(const char* pFilePath, const ma_decoding_backe return MA_INVALID_FILE; } + ma_mp3_generate_seek_table(pMP3, pConfig, pAllocationCallbacks); + return MA_SUCCESS; } #else @@ -48912,6 +60203,8 @@ MA_API ma_result ma_mp3_init_file_w(const wchar_t* pFilePath, const ma_decoding_ return MA_INVALID_FILE; } + ma_mp3_generate_seek_table(pMP3, pConfig, pAllocationCallbacks); + return MA_SUCCESS; } #else @@ -48943,6 +60236,8 @@ MA_API ma_result ma_mp3_init_memory(const void* pData, size_t dataSize, const ma return MA_INVALID_FILE; } + ma_mp3_generate_seek_table(pMP3, pConfig, pAllocationCallbacks); + return MA_SUCCESS; } #else @@ -48962,8 +60257,6 @@ MA_API void ma_mp3_uninit(ma_mp3* pMP3, const ma_allocation_callbacks* pAllocati return; } - (void)pAllocationCallbacks; - #if !defined(MA_NO_MP3) { drmp3_uninit(&pMP3->dr); @@ -48975,11 +60268,22 @@ MA_API void ma_mp3_uninit(ma_mp3* pMP3, const ma_allocation_callbacks* pAllocati } #endif + /* Seek points need to be freed after the MP3 decoder has been uninitialized to ensure they're no longer being referenced. */ + ma_free(pMP3->pSeekPoints, pAllocationCallbacks); + ma_data_source_uninit(&pMP3->ds); } MA_API ma_result ma_mp3_read_pcm_frames(ma_mp3* pMP3, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) { + if (pFramesRead != NULL) { + *pFramesRead = 0; + } + + if (frameCount == 0) { + return MA_INVALID_ARGS; + } + if (pMP3 == NULL) { return MA_INVALID_ARGS; } @@ -49049,7 +60353,7 @@ MA_API ma_result ma_mp3_seek_to_pcm_frame(ma_mp3* pMP3, ma_uint64 frameIndex) #if !defined(MA_NO_MP3) { drmp3_bool32 mp3Result; - + mp3Result = drmp3_seek_to_pcm_frame(&pMP3->dr, frameIndex); if (mp3Result != DRMP3_TRUE) { return MA_ERROR; @@ -49104,7 +60408,7 @@ MA_API ma_result ma_mp3_get_data_format(ma_mp3* pMP3, ma_format* pFormat, ma_uin } if (pChannelMap != NULL) { - ma_get_standard_channel_map(ma_standard_channel_map_default, (ma_uint32)ma_min(pMP3->dr.channels, channelMapCap), pChannelMap); + ma_channel_map_init_standard(ma_standard_channel_map_default, pChannelMap, channelMapCap, pMP3->dr.channels); } return MA_SUCCESS; @@ -49279,23 +60583,13 @@ static void ma_decoding_backend_uninit__mp3(void* pUserData, ma_data_source* pBa ma_free(pMP3, pAllocationCallbacks); } -static ma_result ma_decoding_backend_get_channel_map__mp3(void* pUserData, ma_data_source* pBackend, ma_channel* pChannelMap, size_t channelMapCap) -{ - ma_mp3* pMP3 = (ma_mp3*)pBackend; - - (void)pUserData; - - return ma_mp3_get_data_format(pMP3, NULL, NULL, NULL, pChannelMap, channelMapCap); -} - static ma_decoding_backend_vtable g_ma_decoding_backend_vtable_mp3 = { ma_decoding_backend_init__mp3, ma_decoding_backend_init_file__mp3, ma_decoding_backend_init_file_w__mp3, ma_decoding_backend_init_memory__mp3, - ma_decoding_backend_uninit__mp3, - ma_decoding_backend_get_channel_map__mp3 + ma_decoding_backend_uninit__mp3 }; static ma_result ma_decoder_init_mp3__internal(const ma_decoder_config* pConfig, ma_decoder* pDecoder) @@ -49359,9 +60653,9 @@ static ma_result ma_stbvorbis_ds_seek(ma_data_source* pDataSource, ma_uint64 fra return ma_stbvorbis_seek_to_pcm_frame((ma_stbvorbis*)pDataSource, frameIndex); } -static ma_result ma_stbvorbis_ds_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate) +static ma_result ma_stbvorbis_ds_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) { - return ma_stbvorbis_get_data_format((ma_stbvorbis*)pDataSource, pFormat, pChannels, pSampleRate, NULL, 0); + return ma_stbvorbis_get_data_format((ma_stbvorbis*)pDataSource, pFormat, pChannels, pSampleRate, pChannelMap, channelMapCap); } static ma_result ma_stbvorbis_ds_get_cursor(ma_data_source* pDataSource, ma_uint64* pCursor) @@ -49378,11 +60672,11 @@ static ma_data_source_vtable g_ma_stbvorbis_ds_vtable = { ma_stbvorbis_ds_read, ma_stbvorbis_ds_seek, - NULL, /* onMap() */ - NULL, /* onUnmap() */ ma_stbvorbis_ds_get_data_format, ma_stbvorbis_ds_get_cursor, - ma_stbvorbis_ds_get_length + ma_stbvorbis_ds_get_length, + NULL, /* onSetLooping */ + 0 }; @@ -49645,6 +60939,14 @@ MA_API void ma_stbvorbis_uninit(ma_stbvorbis* pVorbis, const ma_allocation_callb MA_API ma_result ma_stbvorbis_read_pcm_frames(ma_stbvorbis* pVorbis, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) { + if (pFramesRead != NULL) { + *pFramesRead = 0; + } + + if (frameCount == 0) { + return MA_INVALID_ARGS; + } + if (pVorbis == NULL) { return MA_INVALID_ARGS; } @@ -49753,7 +61055,7 @@ MA_API ma_result ma_stbvorbis_read_pcm_frames(ma_stbvorbis* pVorbis, void* pFram while (totalFramesRead < frameCount) { ma_uint64 framesRemaining = (frameCount - totalFramesRead); int framesRead; - + if (framesRemaining > INT_MAX) { framesRemaining = INT_MAX; } @@ -49761,7 +61063,7 @@ MA_API ma_result ma_stbvorbis_read_pcm_frames(ma_stbvorbis* pVorbis, void* pFram framesRead = stb_vorbis_get_samples_float_interleaved(pVorbis->stb, channels, (float*)ma_offset_pcm_frames_ptr(pFramesOut, totalFramesRead, format, channels), (int)framesRemaining * channels); /* Safe cast. */ totalFramesRead += framesRead; - if (framesRead < framesRemaining) { + if (framesRead < (int)framesRemaining) { break; /* Nothing left to read. Get out. */ } } @@ -49780,6 +61082,10 @@ MA_API ma_result ma_stbvorbis_read_pcm_frames(ma_stbvorbis* pVorbis, void* pFram *pFramesRead = totalFramesRead; } + if (result == MA_SUCCESS && totalFramesRead == 0) { + result = MA_AT_END; + } + return result; } #else @@ -49910,7 +61216,7 @@ MA_API ma_result ma_stbvorbis_get_data_format(ma_stbvorbis* pVorbis, ma_format* } if (pChannelMap != NULL) { - ma_get_standard_channel_map(ma_standard_channel_map_vorbis, (ma_uint32)ma_min(pVorbis->channels, channelMapCap), pChannelMap); + ma_channel_map_init_standard(ma_standard_channel_map_vorbis, pChannelMap, channelMapCap, pVorbis->channels); } return MA_SUCCESS; @@ -49970,7 +61276,7 @@ MA_API ma_result ma_stbvorbis_get_length_in_pcm_frames(ma_stbvorbis* pVorbis, ma } else { *pLength = stb_vorbis_stream_length_in_samples(pVorbis->stb); } - + return MA_SUCCESS; } #else @@ -50065,23 +61371,13 @@ static void ma_decoding_backend_uninit__stbvorbis(void* pUserData, ma_data_sourc ma_free(pVorbis, pAllocationCallbacks); } -static ma_result ma_decoding_backend_get_channel_map__stbvorbis(void* pUserData, ma_data_source* pBackend, ma_channel* pChannelMap, size_t channelMapCap) -{ - ma_stbvorbis* pVorbis = (ma_stbvorbis*)pBackend; - - (void)pUserData; - - return ma_stbvorbis_get_data_format(pVorbis, NULL, NULL, NULL, pChannelMap, channelMapCap); -} - static ma_decoding_backend_vtable g_ma_decoding_backend_vtable_stbvorbis = { ma_decoding_backend_init__stbvorbis, ma_decoding_backend_init_file__stbvorbis, NULL, /* onInitFileW() */ ma_decoding_backend_init_memory__stbvorbis, - ma_decoding_backend_uninit__stbvorbis, - ma_decoding_backend_get_channel_map__stbvorbis + ma_decoding_backend_uninit__stbvorbis }; static ma_result ma_decoder_init_vorbis__internal(const ma_decoder_config* pConfig, ma_decoder* pDecoder) @@ -50106,17 +61402,7 @@ static ma_result ma_decoder__init_allocation_callbacks(const ma_decoder_config* static ma_result ma_decoder__data_source_on_read(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) { - ma_uint64 framesRead = ma_decoder_read_pcm_frames((ma_decoder*)pDataSource, pFramesOut, frameCount); - - if (pFramesRead != NULL) { - *pFramesRead = framesRead; - } - - if (framesRead == 0) { - return MA_AT_END; - } - - return MA_SUCCESS; + return ma_decoder_read_pcm_frames((ma_decoder*)pDataSource, pFramesOut, frameCount, pFramesRead); } static ma_result ma_decoder__data_source_on_seek(ma_data_source* pDataSource, ma_uint64 frameIndex) @@ -50124,45 +61410,30 @@ static ma_result ma_decoder__data_source_on_seek(ma_data_source* pDataSource, ma return ma_decoder_seek_to_pcm_frame((ma_decoder*)pDataSource, frameIndex); } -static ma_result ma_decoder__data_source_on_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate) +static ma_result ma_decoder__data_source_on_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) { - ma_decoder* pDecoder = (ma_decoder*)pDataSource; - - *pFormat = pDecoder->outputFormat; - *pChannels = pDecoder->outputChannels; - *pSampleRate = pDecoder->outputSampleRate; - - return MA_SUCCESS; + return ma_decoder_get_data_format((ma_decoder*)pDataSource, pFormat, pChannels, pSampleRate, pChannelMap, channelMapCap); } static ma_result ma_decoder__data_source_on_get_cursor(ma_data_source* pDataSource, ma_uint64* pCursor) { - ma_decoder* pDecoder = (ma_decoder*)pDataSource; - - return ma_decoder_get_cursor_in_pcm_frames(pDecoder, pCursor); + return ma_decoder_get_cursor_in_pcm_frames((ma_decoder*)pDataSource, pCursor); } static ma_result ma_decoder__data_source_on_get_length(ma_data_source* pDataSource, ma_uint64* pLength) { - ma_decoder* pDecoder = (ma_decoder*)pDataSource; - - *pLength = ma_decoder_get_length_in_pcm_frames(pDecoder); - if (*pLength == 0) { - return MA_NOT_IMPLEMENTED; - } - - return MA_SUCCESS; + return ma_decoder_get_length_in_pcm_frames((ma_decoder*)pDataSource, pLength); } static ma_data_source_vtable g_ma_decoder_data_source_vtable = { ma_decoder__data_source_on_read, ma_decoder__data_source_on_seek, - NULL, /* onMap */ - NULL, /* onUnmap */ ma_decoder__data_source_on_get_data_format, ma_decoder__data_source_on_get_cursor, - ma_decoder__data_source_on_get_length + ma_decoder__data_source_on_get_length, + NULL, /* onSetLooping */ + 0 }; static ma_result ma_decoder__preinit(ma_decoder_read_proc onRead, ma_decoder_seek_proc onSeek, ma_decoder_tell_proc onTell, void* pUserData, const ma_decoder_config* pConfig, ma_decoder* pDecoder) @@ -50206,22 +61477,9 @@ static ma_result ma_decoder__preinit(ma_decoder_read_proc onRead, ma_decoder_see static ma_result ma_decoder__postinit(const ma_decoder_config* pConfig, ma_decoder* pDecoder) { - ma_result result = MA_SUCCESS; + ma_result result; - /* Basic validation in case the internal decoder supports different limits to miniaudio. */ - { - /* TODO: Remove this block once we remove MA_MIN_CHANNELS and MA_MAX_CHANNELS. */ - ma_uint32 internalChannels; - ma_data_source_get_data_format(pDecoder->pBackend, NULL, &internalChannels, NULL); - - if (internalChannels < MA_MIN_CHANNELS || internalChannels > MA_MAX_CHANNELS) { - result = MA_INVALID_DATA; - } - } - - if (result == MA_SUCCESS) { - result = ma_decoder__init_data_converter(pDecoder, pConfig); - } + result = ma_decoder__init_data_converter(pDecoder, pConfig); /* If we failed post initialization we need to uninitialize the decoder before returning to prevent a memory leak. */ if (result != MA_SUCCESS) { @@ -50232,83 +61490,6 @@ static ma_result ma_decoder__postinit(const ma_decoder_config* pConfig, ma_decod return result; } -MA_API ma_result ma_decoder_init_wav(ma_decoder_read_proc onRead, ma_decoder_seek_proc onSeek, void* pUserData, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ -#ifdef MA_HAS_WAV - ma_decoder_config config; - - config = ma_decoder_config_init_copy(pConfig); - config.encodingFormat = ma_encoding_format_wav; - - return ma_decoder_init(onRead, onSeek, pUserData, &config, pDecoder); -#else - (void)onRead; - (void)onSeek; - (void)pUserData; - (void)pConfig; - (void)pDecoder; - return MA_NO_BACKEND; -#endif -} - -MA_API ma_result ma_decoder_init_flac(ma_decoder_read_proc onRead, ma_decoder_seek_proc onSeek, void* pUserData, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ -#ifdef MA_HAS_FLAC - ma_decoder_config config; - - config = ma_decoder_config_init_copy(pConfig); - config.encodingFormat = ma_encoding_format_flac; - - return ma_decoder_init(onRead, onSeek, pUserData, &config, pDecoder); -#else - (void)onRead; - (void)onSeek; - (void)pUserData; - (void)pConfig; - (void)pDecoder; - return MA_NO_BACKEND; -#endif -} - -MA_API ma_result ma_decoder_init_mp3(ma_decoder_read_proc onRead, ma_decoder_seek_proc onSeek, void* pUserData, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ -#ifdef MA_HAS_MP3 - ma_decoder_config config; - - config = ma_decoder_config_init_copy(pConfig); - config.encodingFormat = ma_encoding_format_mp3; - - return ma_decoder_init(onRead, onSeek, pUserData, &config, pDecoder); -#else - (void)onRead; - (void)onSeek; - (void)pUserData; - (void)pConfig; - (void)pDecoder; - return MA_NO_BACKEND; -#endif -} - -MA_API ma_result ma_decoder_init_vorbis(ma_decoder_read_proc onRead, ma_decoder_seek_proc onSeek, void* pUserData, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ -#ifdef MA_HAS_VORBIS - ma_decoder_config config; - - config = ma_decoder_config_init_copy(pConfig); - config.encodingFormat = ma_encoding_format_vorbis; - - return ma_decoder_init(onRead, onSeek, pUserData, &config, pDecoder); -#else - (void)onRead; - (void)onSeek; - (void)pUserData; - (void)pConfig; - (void)pDecoder; - return MA_NO_BACKEND; -#endif -} - - static ma_result ma_decoder_init__internal(ma_decoder_read_proc onRead, ma_decoder_seek_proc onSeek, void* pUserData, const ma_decoder_config* pConfig, ma_decoder* pDecoder) { @@ -50431,29 +61612,41 @@ MA_API ma_result ma_decoder_init(ma_decoder_read_proc onRead, ma_decoder_seek_pr } -static size_t ma_decoder__on_read_memory(ma_decoder* pDecoder, void* pBufferOut, size_t bytesToRead) +static ma_result ma_decoder__on_read_memory(ma_decoder* pDecoder, void* pBufferOut, size_t bytesToRead, size_t* pBytesRead) { size_t bytesRemaining; MA_ASSERT(pDecoder->data.memory.dataSize >= pDecoder->data.memory.currentReadPos); + if (pBytesRead != NULL) { + *pBytesRead = 0; + } + bytesRemaining = pDecoder->data.memory.dataSize - pDecoder->data.memory.currentReadPos; if (bytesToRead > bytesRemaining) { bytesToRead = bytesRemaining; } + if (bytesRemaining == 0) { + return MA_AT_END; + } + if (bytesToRead > 0) { MA_COPY_MEMORY(pBufferOut, pDecoder->data.memory.pData + pDecoder->data.memory.currentReadPos, bytesToRead); pDecoder->data.memory.currentReadPos += bytesToRead; } - return bytesToRead; + if (pBytesRead != NULL) { + *pBytesRead = bytesToRead; + } + + return MA_SUCCESS; } -static ma_bool32 ma_decoder__on_seek_memory(ma_decoder* pDecoder, ma_int64 byteOffset, ma_seek_origin origin) +static ma_result ma_decoder__on_seek_memory(ma_decoder* pDecoder, ma_int64 byteOffset, ma_seek_origin origin) { if (byteOffset > 0 && (ma_uint64)byteOffset > MA_SIZE_MAX) { - return MA_FALSE; /* Too far. */ + return MA_BAD_SEEK; } if (origin == ma_seek_origin_current) { @@ -50490,7 +61683,7 @@ static ma_bool32 ma_decoder__on_seek_memory(ma_decoder* pDecoder, ma_int64 byteO } } - return MA_TRUE; + return MA_SUCCESS; } static ma_result ma_decoder__on_tell_memory(ma_decoder* pDecoder, ma_int64* pCursor) @@ -50537,79 +61730,6 @@ MA_API ma_result ma_decoder_init_memory(const void* pData, size_t dataSize, cons return ma_decoder_init__internal(ma_decoder__on_read_memory, ma_decoder__on_seek_memory, NULL, &config, pDecoder); } -MA_API ma_result ma_decoder_init_memory_wav(const void* pData, size_t dataSize, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ -#ifdef MA_HAS_WAV - ma_decoder_config config; - - config = ma_decoder_config_init_copy(pConfig); /* Make sure the config is not NULL. */ - config.encodingFormat = ma_encoding_format_wav; - - return ma_decoder_init_memory(pData, dataSize, &config, pDecoder); -#else - (void)pData; - (void)dataSize; - (void)pConfig; - (void)pDecoder; - return MA_NO_BACKEND; -#endif -} - -MA_API ma_result ma_decoder_init_memory_flac(const void* pData, size_t dataSize, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ -#ifdef MA_HAS_FLAC - ma_decoder_config config; - - config = ma_decoder_config_init_copy(pConfig); /* Make sure the config is not NULL. */ - config.encodingFormat = ma_encoding_format_flac; - - return ma_decoder_init_memory(pData, dataSize, &config, pDecoder); -#else - (void)pData; - (void)dataSize; - (void)pConfig; - (void)pDecoder; - return MA_NO_BACKEND; -#endif -} - -MA_API ma_result ma_decoder_init_memory_mp3(const void* pData, size_t dataSize, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ -#ifdef MA_HAS_MP3 - ma_decoder_config config; - - config = ma_decoder_config_init_copy(pConfig); /* Make sure the config is not NULL. */ - config.encodingFormat = ma_encoding_format_mp3; - - return ma_decoder_init_memory(pData, dataSize, &config, pDecoder); -#else - (void)pData; - (void)dataSize; - (void)pConfig; - (void)pDecoder; - return MA_NO_BACKEND; -#endif -} - -MA_API ma_result ma_decoder_init_memory_vorbis(const void* pData, size_t dataSize, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ -#ifdef MA_HAS_VORBIS - ma_decoder_config config; - - config = ma_decoder_config_init_copy(pConfig); /* Make sure the config is not NULL. */ - config.encodingFormat = ma_encoding_format_vorbis; - - return ma_decoder_init_memory(pData, dataSize, &config, pDecoder); -#else - (void)pData; - (void)dataSize; - (void)pConfig; - (void)pDecoder; - return MA_NO_BACKEND; -#endif -} - - #if defined(MA_HAS_WAV) || \ defined(MA_HAS_MP3) || \ @@ -50790,30 +61910,19 @@ static ma_bool32 ma_path_extension_equal_w(const wchar_t* path, const wchar_t* e -static size_t ma_decoder__on_read_vfs(ma_decoder* pDecoder, void* pBufferOut, size_t bytesToRead) +static ma_result ma_decoder__on_read_vfs(ma_decoder* pDecoder, void* pBufferOut, size_t bytesToRead, size_t* pBytesRead) { - size_t bytesRead; - MA_ASSERT(pDecoder != NULL); MA_ASSERT(pBufferOut != NULL); - ma_vfs_or_default_read(pDecoder->data.vfs.pVFS, pDecoder->data.vfs.file, pBufferOut, bytesToRead, &bytesRead); - - return bytesRead; + return ma_vfs_or_default_read(pDecoder->data.vfs.pVFS, pDecoder->data.vfs.file, pBufferOut, bytesToRead, pBytesRead); } -static ma_bool32 ma_decoder__on_seek_vfs(ma_decoder* pDecoder, ma_int64 offset, ma_seek_origin origin) +static ma_result ma_decoder__on_seek_vfs(ma_decoder* pDecoder, ma_int64 offset, ma_seek_origin origin) { - ma_result result; - MA_ASSERT(pDecoder != NULL); - result = ma_vfs_or_default_seek(pDecoder->data.vfs.pVFS, pDecoder->data.vfs.file, offset, origin); - if (result != MA_SUCCESS) { - return MA_FALSE; - } - - return MA_TRUE; + return ma_vfs_or_default_seek(pDecoder->data.vfs.pVFS, pDecoder->data.vfs.file, offset, origin); } static ma_result ma_decoder__on_tell_vfs(ma_decoder* pDecoder, ma_int64* pCursor) @@ -50955,79 +62064,6 @@ MA_API ma_result ma_decoder_init_vfs(ma_vfs* pVFS, const char* pFilePath, const return MA_SUCCESS; } -MA_API ma_result ma_decoder_init_vfs_wav(ma_vfs* pVFS, const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ -#ifdef MA_HAS_WAV - ma_decoder_config config; - - config = ma_decoder_config_init_copy(pConfig); - config.encodingFormat = ma_encoding_format_wav; - - return ma_decoder_init_vfs(pVFS, pFilePath, &config, pDecoder); -#else - (void)pVFS; - (void)pFilePath; - (void)pConfig; - (void)pDecoder; - return MA_NO_BACKEND; -#endif -} - -MA_API ma_result ma_decoder_init_vfs_flac(ma_vfs* pVFS, const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ -#ifdef MA_HAS_FLAC - ma_decoder_config config; - - config = ma_decoder_config_init_copy(pConfig); - config.encodingFormat = ma_encoding_format_flac; - - return ma_decoder_init_vfs(pVFS, pFilePath, &config, pDecoder); -#else - (void)pVFS; - (void)pFilePath; - (void)pConfig; - (void)pDecoder; - return MA_NO_BACKEND; -#endif -} - -MA_API ma_result ma_decoder_init_vfs_mp3(ma_vfs* pVFS, const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ -#ifdef MA_HAS_MP3 - ma_decoder_config config; - - config = ma_decoder_config_init_copy(pConfig); - config.encodingFormat = ma_encoding_format_mp3; - - return ma_decoder_init_vfs(pVFS, pFilePath, &config, pDecoder); -#else - (void)pVFS; - (void)pFilePath; - (void)pConfig; - (void)pDecoder; - return MA_NO_BACKEND; -#endif -} - -MA_API ma_result ma_decoder_init_vfs_vorbis(ma_vfs* pVFS, const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ -#ifdef MA_HAS_VORBIS - ma_decoder_config config; - - config = ma_decoder_config_init_copy(pConfig); - config.encodingFormat = ma_encoding_format_vorbis; - - return ma_decoder_init_vfs(pVFS, pFilePath, &config, pDecoder); -#else - (void)pVFS; - (void)pFilePath; - (void)pConfig; - (void)pDecoder; - return MA_NO_BACKEND; -#endif -} - - static ma_result ma_decoder__preinit_vfs_w(ma_vfs* pVFS, const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) { @@ -51158,132 +62194,16 @@ MA_API ma_result ma_decoder_init_vfs_w(ma_vfs* pVFS, const wchar_t* pFilePath, c return MA_SUCCESS; } -MA_API ma_result ma_decoder_init_vfs_wav_w(ma_vfs* pVFS, const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ -#ifdef MA_HAS_WAV - ma_decoder_config config; - - config = ma_decoder_config_init_copy(pConfig); - config.encodingFormat = ma_encoding_format_wav; - - return ma_decoder_init_vfs_w(pVFS, pFilePath, &config, pDecoder); -#else - (void)pVFS; - (void)pFilePath; - (void)pConfig; - (void)pDecoder; - return MA_NO_BACKEND; -#endif -} - -MA_API ma_result ma_decoder_init_vfs_flac_w(ma_vfs* pVFS, const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ -#ifdef MA_HAS_FLAC - ma_decoder_config config; - - config = ma_decoder_config_init_copy(pConfig); - config.encodingFormat = ma_encoding_format_flac; - - return ma_decoder_init_vfs_w(pVFS, pFilePath, &config, pDecoder); -#else - (void)pVFS; - (void)pFilePath; - (void)pConfig; - (void)pDecoder; - return MA_NO_BACKEND; -#endif -} - -MA_API ma_result ma_decoder_init_vfs_mp3_w(ma_vfs* pVFS, const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ -#ifdef MA_HAS_MP3 - ma_decoder_config config; - - config = ma_decoder_config_init_copy(pConfig); - config.encodingFormat = ma_encoding_format_mp3; - - return ma_decoder_init_vfs_w(pVFS, pFilePath, &config, pDecoder); -#else - (void)pVFS; - (void)pFilePath; - (void)pConfig; - (void)pDecoder; - return MA_NO_BACKEND; -#endif -} - -MA_API ma_result ma_decoder_init_vfs_vorbis_w(ma_vfs* pVFS, const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ -#ifdef MA_HAS_VORBIS - ma_decoder_config config; - - config = ma_decoder_config_init_copy(pConfig); - config.encodingFormat = ma_encoding_format_vorbis; - - return ma_decoder_init_vfs_w(pVFS, pFilePath, &config, pDecoder); -#else - (void)pVFS; - (void)pFilePath; - (void)pConfig; - (void)pDecoder; - return MA_NO_BACKEND; -#endif -} - - - MA_API ma_result ma_decoder_init_file(const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) { return ma_decoder_init_vfs(NULL, pFilePath, pConfig, pDecoder); } -MA_API ma_result ma_decoder_init_file_wav(const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ - return ma_decoder_init_vfs_wav(NULL, pFilePath, pConfig, pDecoder); -} - -MA_API ma_result ma_decoder_init_file_flac(const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ - return ma_decoder_init_vfs_flac(NULL, pFilePath, pConfig, pDecoder); -} - -MA_API ma_result ma_decoder_init_file_mp3(const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ - return ma_decoder_init_vfs_mp3(NULL, pFilePath, pConfig, pDecoder); -} - -MA_API ma_result ma_decoder_init_file_vorbis(const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ - return ma_decoder_init_vfs_vorbis(NULL, pFilePath, pConfig, pDecoder); -} - - - MA_API ma_result ma_decoder_init_file_w(const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) { return ma_decoder_init_vfs_w(NULL, pFilePath, pConfig, pDecoder); } -MA_API ma_result ma_decoder_init_file_wav_w(const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ - return ma_decoder_init_vfs_wav_w(NULL, pFilePath, pConfig, pDecoder); -} - -MA_API ma_result ma_decoder_init_file_flac_w(const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ - return ma_decoder_init_vfs_flac_w(NULL, pFilePath, pConfig, pDecoder); -} - -MA_API ma_result ma_decoder_init_file_mp3_w(const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ - return ma_decoder_init_vfs_mp3_w(NULL, pFilePath, pConfig, pDecoder); -} - -MA_API ma_result ma_decoder_init_file_vorbis_w(const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ - return ma_decoder_init_vfs_vorbis_w(NULL, pFilePath, pConfig, pDecoder); -} - MA_API ma_result ma_decoder_uninit(ma_decoder* pDecoder) { if (pDecoder == NULL) { @@ -51296,15 +62216,244 @@ MA_API ma_result ma_decoder_uninit(ma_decoder* pDecoder) } } - /* Legacy. */ if (pDecoder->onRead == ma_decoder__on_read_vfs) { ma_vfs_or_default_close(pDecoder->data.vfs.pVFS, pDecoder->data.vfs.file); pDecoder->data.vfs.file = NULL; } - ma_data_converter_uninit(&pDecoder->converter); + ma_data_converter_uninit(&pDecoder->converter, &pDecoder->allocationCallbacks); ma_data_source_uninit(&pDecoder->ds); + if (pDecoder->pInputCache != NULL) { + ma_free(pDecoder->pInputCache, &pDecoder->allocationCallbacks); + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_decoder_read_pcm_frames(ma_decoder* pDecoder, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) +{ + ma_result result = MA_SUCCESS; + ma_uint64 totalFramesReadOut; + void* pRunningFramesOut; + + if (pFramesRead != NULL) { + *pFramesRead = 0; /* Safety. */ + } + + if (frameCount == 0) { + return MA_INVALID_ARGS; + } + + if (pDecoder == NULL) { + return MA_INVALID_ARGS; + } + + if (pDecoder->pBackend == NULL) { + return MA_INVALID_OPERATION; + } + + /* Fast path. */ + if (pDecoder->converter.isPassthrough) { + result = ma_data_source_read_pcm_frames(pDecoder->pBackend, pFramesOut, frameCount, &totalFramesReadOut); + } else { + /* + Getting here means we need to do data conversion. If we're seeking forward and are _not_ doing resampling we can run this in a fast path. If we're doing resampling we + need to run through each sample because we need to ensure it's internal cache is updated. + */ + if (pFramesOut == NULL && pDecoder->converter.hasResampler == MA_FALSE) { + result = ma_data_source_read_pcm_frames(pDecoder->pBackend, NULL, frameCount, &totalFramesReadOut); + } else { + /* Slow path. Need to run everything through the data converter. */ + ma_format internalFormat; + ma_uint32 internalChannels; + + totalFramesReadOut = 0; + pRunningFramesOut = pFramesOut; + + result = ma_data_source_get_data_format(pDecoder->pBackend, &internalFormat, &internalChannels, NULL, NULL, 0); + if (result != MA_SUCCESS) { + return result; /* Failed to retrieve the internal format and channel count. */ + } + + /* + We run a different path depending on whether or not we are using a heap-allocated + intermediary buffer or not. If the data converter does not support the calculation of + the required number of input frames, we'll use the heap-allocated path. Otherwise we'll + use the stack-allocated path. + */ + if (pDecoder->pInputCache != NULL) { + /* We don't have a way of determining the required number of input frames, so need to persistently store input data in a cache. */ + while (totalFramesReadOut < frameCount) { + ma_uint64 framesToReadThisIterationIn; + ma_uint64 framesToReadThisIterationOut; + + /* If there's any data available in the cache, that needs to get processed first. */ + if (pDecoder->inputCacheRemaining > 0) { + framesToReadThisIterationOut = (frameCount - totalFramesReadOut); + framesToReadThisIterationIn = framesToReadThisIterationOut; + if (framesToReadThisIterationIn > pDecoder->inputCacheRemaining) { + framesToReadThisIterationIn = pDecoder->inputCacheRemaining; + } + + result = ma_data_converter_process_pcm_frames(&pDecoder->converter, ma_offset_pcm_frames_ptr(pDecoder->pInputCache, pDecoder->inputCacheConsumed, internalFormat, internalChannels), &framesToReadThisIterationIn, pRunningFramesOut, &framesToReadThisIterationOut); + if (result != MA_SUCCESS) { + break; + } + + pDecoder->inputCacheConsumed += framesToReadThisIterationIn; + pDecoder->inputCacheRemaining -= framesToReadThisIterationIn; + + totalFramesReadOut += framesToReadThisIterationOut; + + if (pRunningFramesOut != NULL) { + pRunningFramesOut = ma_offset_ptr(pRunningFramesOut, framesToReadThisIterationOut * ma_get_bytes_per_frame(pDecoder->outputFormat, pDecoder->outputChannels)); + } + + if (framesToReadThisIterationIn == 0 && framesToReadThisIterationOut == 0) { + break; /* We're done. */ + } + } + + /* Getting here means there's no data in the cache and we need to fill it up from the data source. */ + if (pDecoder->inputCacheRemaining == 0) { + pDecoder->inputCacheConsumed = 0; + + result = ma_data_source_read_pcm_frames(pDecoder->pBackend, pDecoder->pInputCache, pDecoder->inputCacheCap, &pDecoder->inputCacheRemaining); + if (result != MA_SUCCESS) { + break; + } + } + } + } else { + /* We have a way of determining the required number of input frames so just use the stack. */ + while (totalFramesReadOut < frameCount) { + ma_uint8 pIntermediaryBuffer[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; /* In internal format. */ + ma_uint64 intermediaryBufferCap = sizeof(pIntermediaryBuffer) / ma_get_bytes_per_frame(internalFormat, internalChannels); + ma_uint64 framesToReadThisIterationIn; + ma_uint64 framesReadThisIterationIn; + ma_uint64 framesToReadThisIterationOut; + ma_uint64 framesReadThisIterationOut; + ma_uint64 requiredInputFrameCount; + + framesToReadThisIterationOut = (frameCount - totalFramesReadOut); + framesToReadThisIterationIn = framesToReadThisIterationOut; + if (framesToReadThisIterationIn > intermediaryBufferCap) { + framesToReadThisIterationIn = intermediaryBufferCap; + } + + ma_data_converter_get_required_input_frame_count(&pDecoder->converter, framesToReadThisIterationOut, &requiredInputFrameCount); + if (framesToReadThisIterationIn > requiredInputFrameCount) { + framesToReadThisIterationIn = requiredInputFrameCount; + } + + if (requiredInputFrameCount > 0) { + result = ma_data_source_read_pcm_frames(pDecoder->pBackend, pIntermediaryBuffer, framesToReadThisIterationIn, &framesReadThisIterationIn); + } else { + framesReadThisIterationIn = 0; + } + + /* + At this point we have our decoded data in input format and now we need to convert to output format. Note that even if we didn't read any + input frames, we still want to try processing frames because there may some output frames generated from cached input data. + */ + framesReadThisIterationOut = framesToReadThisIterationOut; + result = ma_data_converter_process_pcm_frames(&pDecoder->converter, pIntermediaryBuffer, &framesReadThisIterationIn, pRunningFramesOut, &framesReadThisIterationOut); + if (result != MA_SUCCESS) { + break; + } + + totalFramesReadOut += framesReadThisIterationOut; + + if (pRunningFramesOut != NULL) { + pRunningFramesOut = ma_offset_ptr(pRunningFramesOut, framesReadThisIterationOut * ma_get_bytes_per_frame(pDecoder->outputFormat, pDecoder->outputChannels)); + } + + if (framesReadThisIterationIn == 0 && framesReadThisIterationOut == 0) { + break; /* We're done. */ + } + } + } + } + } + + pDecoder->readPointerInPCMFrames += totalFramesReadOut; + + if (pFramesRead != NULL) { + *pFramesRead = totalFramesReadOut; + } + + if (result == MA_SUCCESS && totalFramesReadOut == 0) { + result = MA_AT_END; + } + + return result; +} + +MA_API ma_result ma_decoder_seek_to_pcm_frame(ma_decoder* pDecoder, ma_uint64 frameIndex) +{ + if (pDecoder == NULL) { + return MA_INVALID_ARGS; + } + + if (pDecoder->pBackend != NULL) { + ma_result result; + ma_uint64 internalFrameIndex; + ma_uint32 internalSampleRate; + ma_uint64 currentFrameIndex; + + result = ma_data_source_get_data_format(pDecoder->pBackend, NULL, NULL, &internalSampleRate, NULL, 0); + if (result != MA_SUCCESS) { + return result; /* Failed to retrieve the internal sample rate. */ + } + + if (internalSampleRate == pDecoder->outputSampleRate) { + internalFrameIndex = frameIndex; + } else { + internalFrameIndex = ma_calculate_frame_count_after_resampling(internalSampleRate, pDecoder->outputSampleRate, frameIndex); + } + + /* Only seek if we're requesting a different frame to what we're currently sitting on. */ + ma_data_source_get_cursor_in_pcm_frames(pDecoder->pBackend, ¤tFrameIndex); + if (currentFrameIndex != internalFrameIndex) { + result = ma_data_source_seek_to_pcm_frame(pDecoder->pBackend, internalFrameIndex); + if (result == MA_SUCCESS) { + pDecoder->readPointerInPCMFrames = frameIndex; + } + + /* Reset the data converter so that any cached data in the resampler is cleared. */ + ma_data_converter_reset(&pDecoder->converter); + } + + return result; + } + + /* Should never get here, but if we do it means onSeekToPCMFrame was not set by the backend. */ + return MA_INVALID_ARGS; +} + +MA_API ma_result ma_decoder_get_data_format(ma_decoder* pDecoder, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) +{ + if (pDecoder == NULL) { + return MA_INVALID_ARGS; + } + + if (pFormat != NULL) { + *pFormat = pDecoder->outputFormat; + } + + if (pChannels != NULL) { + *pChannels = pDecoder->outputChannels; + } + + if (pSampleRate != NULL) { + *pSampleRate = pDecoder->outputSampleRate; + } + + if (pChannelMap != NULL) { + ma_data_converter_get_output_channel_map(&pDecoder->converter, pChannelMap, channelMapCap); + } + return MA_SUCCESS; } @@ -51325,164 +62474,48 @@ MA_API ma_result ma_decoder_get_cursor_in_pcm_frames(ma_decoder* pDecoder, ma_ui return MA_SUCCESS; } -MA_API ma_uint64 ma_decoder_get_length_in_pcm_frames(ma_decoder* pDecoder) +MA_API ma_result ma_decoder_get_length_in_pcm_frames(ma_decoder* pDecoder, ma_uint64* pLength) { - if (pDecoder == NULL) { - return 0; + if (pLength == NULL) { + return MA_INVALID_ARGS; } - if (pDecoder->pBackend != NULL) { - ma_result result; - ma_uint64 nativeLengthInPCMFrames; - ma_uint32 internalSampleRate; + *pLength = 0; - ma_data_source_get_length_in_pcm_frames(pDecoder->pBackend, &nativeLengthInPCMFrames); - - result = ma_data_source_get_data_format(pDecoder->pBackend, NULL, NULL, &internalSampleRate); - if (result != MA_SUCCESS) { - return 0; /* Failed to retrieve the internal sample rate. */ - } - - if (internalSampleRate == pDecoder->outputSampleRate) { - return nativeLengthInPCMFrames; - } else { - return ma_calculate_frame_count_after_resampling(pDecoder->outputSampleRate, internalSampleRate, nativeLengthInPCMFrames); - } - } - - return 0; -} - -MA_API ma_uint64 ma_decoder_read_pcm_frames(ma_decoder* pDecoder, void* pFramesOut, ma_uint64 frameCount) -{ - ma_result result; - ma_uint64 totalFramesReadOut; - ma_uint64 totalFramesReadIn; - void* pRunningFramesOut; - - if (pDecoder == NULL) { - return 0; - } - - if (pDecoder->pBackend == NULL) { - return 0; - } - - /* Fast path. */ - if (pDecoder->converter.isPassthrough) { - result = ma_data_source_read_pcm_frames(pDecoder->pBackend, pFramesOut, frameCount, &totalFramesReadOut, MA_FALSE); - } else { - /* - Getting here means we need to do data conversion. If we're seeking forward and are _not_ doing resampling we can run this in a fast path. If we're doing resampling we - need to run through each sample because we need to ensure it's internal cache is updated. - */ - if (pFramesOut == NULL && pDecoder->converter.hasResampler == MA_FALSE) { - result = ma_data_source_read_pcm_frames(pDecoder->pBackend, NULL, frameCount, &totalFramesReadOut, MA_FALSE); - } else { - /* Slow path. Need to run everything through the data converter. */ - ma_format internalFormat; - ma_uint32 internalChannels; - - totalFramesReadOut = 0; - totalFramesReadIn = 0; - pRunningFramesOut = pFramesOut; - - result = ma_data_source_get_data_format(pDecoder->pBackend, &internalFormat, &internalChannels, NULL); - if (result != MA_SUCCESS) { - return 0; /* Failed to retrieve the internal format and channel count. */ - } - - while (totalFramesReadOut < frameCount) { - ma_uint8 pIntermediaryBuffer[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; /* In internal format. */ - ma_uint64 intermediaryBufferCap = sizeof(pIntermediaryBuffer) / ma_get_bytes_per_frame(internalFormat, internalChannels); - ma_uint64 framesToReadThisIterationIn; - ma_uint64 framesReadThisIterationIn; - ma_uint64 framesToReadThisIterationOut; - ma_uint64 framesReadThisIterationOut; - ma_uint64 requiredInputFrameCount; - - framesToReadThisIterationOut = (frameCount - totalFramesReadOut); - framesToReadThisIterationIn = framesToReadThisIterationOut; - if (framesToReadThisIterationIn > intermediaryBufferCap) { - framesToReadThisIterationIn = intermediaryBufferCap; - } - - requiredInputFrameCount = ma_data_converter_get_required_input_frame_count(&pDecoder->converter, framesToReadThisIterationOut); - if (framesToReadThisIterationIn > requiredInputFrameCount) { - framesToReadThisIterationIn = requiredInputFrameCount; - } - - if (requiredInputFrameCount > 0) { - result = ma_data_source_read_pcm_frames(pDecoder->pBackend, pIntermediaryBuffer, framesToReadThisIterationIn, &framesReadThisIterationIn, MA_FALSE); - totalFramesReadIn += framesReadThisIterationIn; - } else { - framesReadThisIterationIn = 0; - } - - /* - At this point we have our decoded data in input format and now we need to convert to output format. Note that even if we didn't read any - input frames, we still want to try processing frames because there may some output frames generated from cached input data. - */ - framesReadThisIterationOut = framesToReadThisIterationOut; - result = ma_data_converter_process_pcm_frames(&pDecoder->converter, pIntermediaryBuffer, &framesReadThisIterationIn, pRunningFramesOut, &framesReadThisIterationOut); - if (result != MA_SUCCESS) { - break; - } - - totalFramesReadOut += framesReadThisIterationOut; - - if (pRunningFramesOut != NULL) { - pRunningFramesOut = ma_offset_ptr(pRunningFramesOut, framesReadThisIterationOut * ma_get_bytes_per_frame(pDecoder->outputFormat, pDecoder->outputChannels)); - } - - if (framesReadThisIterationIn == 0 && framesReadThisIterationOut == 0) { - break; /* We're done. */ - } - } - } - } - - pDecoder->readPointerInPCMFrames += totalFramesReadOut; - - return totalFramesReadOut; -} - -MA_API ma_result ma_decoder_seek_to_pcm_frame(ma_decoder* pDecoder, ma_uint64 frameIndex) -{ if (pDecoder == NULL) { return MA_INVALID_ARGS; } if (pDecoder->pBackend != NULL) { ma_result result; - ma_uint64 internalFrameIndex; + ma_uint64 internalLengthInPCMFrames; ma_uint32 internalSampleRate; - result = ma_data_source_get_data_format(pDecoder->pBackend, NULL, NULL, &internalSampleRate); + result = ma_data_source_get_length_in_pcm_frames(pDecoder->pBackend, &internalLengthInPCMFrames); if (result != MA_SUCCESS) { - return result; /* Failed to retrieve the internal sample rate. */ + return result; /* Failed to retrieve the internal length. */ + } + + result = ma_data_source_get_data_format(pDecoder->pBackend, NULL, NULL, &internalSampleRate, NULL, 0); + if (result != MA_SUCCESS) { + return result; /* Failed to retrieve the internal sample rate. */ } if (internalSampleRate == pDecoder->outputSampleRate) { - internalFrameIndex = frameIndex; + *pLength = internalLengthInPCMFrames; } else { - internalFrameIndex = ma_calculate_frame_count_after_resampling(internalSampleRate, pDecoder->outputSampleRate, frameIndex); + *pLength = ma_calculate_frame_count_after_resampling(pDecoder->outputSampleRate, internalSampleRate, internalLengthInPCMFrames); } - result = ma_data_source_seek_to_pcm_frame(pDecoder->pBackend, internalFrameIndex); - if (result == MA_SUCCESS) { - pDecoder->readPointerInPCMFrames = frameIndex; - } - - return result; + return MA_SUCCESS; + } else { + return MA_NO_BACKEND; } - - /* Should never get here, but if we do it means onSeekToPCMFrame was not set by the backend. */ - return MA_INVALID_ARGS; } MA_API ma_result ma_decoder_get_available_frames(ma_decoder* pDecoder, ma_uint64* pAvailableFrames) { + ma_result result; ma_uint64 totalFrameCount; if (pAvailableFrames == NULL) { @@ -51495,9 +62528,9 @@ MA_API ma_result ma_decoder_get_available_frames(ma_decoder* pDecoder, ma_uint64 return MA_INVALID_ARGS; } - totalFrameCount = ma_decoder_get_length_in_pcm_frames(pDecoder); - if (totalFrameCount == 0) { - return MA_NOT_IMPLEMENTED; + result = ma_decoder_get_length_in_pcm_frames(pDecoder, &totalFrameCount); + if (result != MA_SUCCESS) { + return result; } if (totalFrameCount <= pDecoder->readPointerInPCMFrames) { @@ -51506,12 +62539,13 @@ MA_API ma_result ma_decoder_get_available_frames(ma_decoder* pDecoder, ma_uint64 *pAvailableFrames = totalFrameCount - pDecoder->readPointerInPCMFrames; } - return MA_SUCCESS; /* No frames available. */ + return MA_SUCCESS; } static ma_result ma_decoder__full_decode_and_uninit(ma_decoder* pDecoder, ma_decoder_config* pConfigOut, ma_uint64* pFrameCountOut, void** ppPCMFramesOut) { + ma_result result; ma_uint64 totalFrameCount; ma_uint64 bpf; ma_uint64 dataCapInFrames; @@ -51532,21 +62566,19 @@ static ma_result ma_decoder__full_decode_and_uninit(ma_decoder* pDecoder, ma_dec /* Make room if there's not enough. */ if (totalFrameCount == dataCapInFrames) { void* pNewPCMFramesOut; - ma_uint64 oldDataCapInFrames = dataCapInFrames; ma_uint64 newDataCapInFrames = dataCapInFrames*2; if (newDataCapInFrames == 0) { newDataCapInFrames = 4096; } if ((newDataCapInFrames * bpf) > MA_SIZE_MAX) { - ma__free_from_callbacks(pPCMFramesOut, &pDecoder->allocationCallbacks); + ma_free(pPCMFramesOut, &pDecoder->allocationCallbacks); return MA_TOO_BIG; } - - pNewPCMFramesOut = (void*)ma__realloc_from_callbacks(pPCMFramesOut, (size_t)(newDataCapInFrames * bpf), (size_t)(oldDataCapInFrames * bpf), &pDecoder->allocationCallbacks); + pNewPCMFramesOut = (void*)ma_realloc(pPCMFramesOut, (size_t)(newDataCapInFrames * bpf), &pDecoder->allocationCallbacks); if (pNewPCMFramesOut == NULL) { - ma__free_from_callbacks(pPCMFramesOut, &pDecoder->allocationCallbacks); + ma_free(pPCMFramesOut, &pDecoder->allocationCallbacks); return MA_OUT_OF_MEMORY; } @@ -51557,9 +62589,13 @@ static ma_result ma_decoder__full_decode_and_uninit(ma_decoder* pDecoder, ma_dec frameCountToTryReading = dataCapInFrames - totalFrameCount; MA_ASSERT(frameCountToTryReading > 0); - framesJustRead = ma_decoder_read_pcm_frames(pDecoder, (ma_uint8*)pPCMFramesOut + (totalFrameCount * bpf), frameCountToTryReading); + result = ma_decoder_read_pcm_frames(pDecoder, (ma_uint8*)pPCMFramesOut + (totalFrameCount * bpf), frameCountToTryReading, &framesJustRead); totalFrameCount += framesJustRead; + if (result != MA_SUCCESS) { + break; + } + if (framesJustRead < frameCountToTryReading) { break; } @@ -51567,16 +62603,15 @@ static ma_result ma_decoder__full_decode_and_uninit(ma_decoder* pDecoder, ma_dec if (pConfigOut != NULL) { - pConfigOut->format = pDecoder->outputFormat; - pConfigOut->channels = pDecoder->outputChannels; + pConfigOut->format = pDecoder->outputFormat; + pConfigOut->channels = pDecoder->outputChannels; pConfigOut->sampleRate = pDecoder->outputSampleRate; - ma_channel_map_copy(pConfigOut->channelMap, pDecoder->outputChannelMap, pDecoder->outputChannels); } if (ppPCMFramesOut != NULL) { *ppPCMFramesOut = pPCMFramesOut; } else { - ma__free_from_callbacks(pPCMFramesOut, &pDecoder->allocationCallbacks); + ma_free(pPCMFramesOut, &pDecoder->allocationCallbacks); } if (pFrameCountOut != NULL) { @@ -51652,17 +62687,27 @@ MA_API ma_result ma_decode_memory(const void* pData, size_t dataSize, ma_decoder static size_t ma_encoder__internal_on_write_wav(void* pUserData, const void* pData, size_t bytesToWrite) { ma_encoder* pEncoder = (ma_encoder*)pUserData; + size_t bytesWritten = 0; + MA_ASSERT(pEncoder != NULL); - return pEncoder->onWrite(pEncoder, pData, bytesToWrite); + pEncoder->onWrite(pEncoder, pData, bytesToWrite, &bytesWritten); + return bytesWritten; } static drwav_bool32 ma_encoder__internal_on_seek_wav(void* pUserData, int offset, drwav_seek_origin origin) { ma_encoder* pEncoder = (ma_encoder*)pUserData; + ma_result result; + MA_ASSERT(pEncoder != NULL); - return pEncoder->onSeek(pEncoder, offset, (origin == drwav_seek_origin_start) ? ma_seek_origin_start : ma_seek_origin_current); + result = pEncoder->onSeek(pEncoder, offset, (origin == drwav_seek_origin_start) ? ma_seek_origin_start : ma_seek_origin_current); + if (result != MA_SUCCESS) { + return DRWAV_FALSE; + } else { + return DRWAV_TRUE; + } } static ma_result ma_encoder__on_init_wav(ma_encoder* pEncoder) @@ -51673,7 +62718,7 @@ static ma_result ma_encoder__on_init_wav(ma_encoder* pEncoder) MA_ASSERT(pEncoder != NULL); - pWav = (drwav*)ma__malloc_from_callbacks(sizeof(*pWav), &pEncoder->config.allocationCallbacks); + pWav = (drwav*)ma_malloc(sizeof(*pWav), &pEncoder->config.allocationCallbacks); if (pWav == NULL) { return MA_OUT_OF_MEMORY; } @@ -51712,28 +62757,35 @@ static void ma_encoder__on_uninit_wav(ma_encoder* pEncoder) MA_ASSERT(pWav != NULL); drwav_uninit(pWav); - ma__free_from_callbacks(pWav, &pEncoder->config.allocationCallbacks); + ma_free(pWav, &pEncoder->config.allocationCallbacks); } -static ma_uint64 ma_encoder__on_write_pcm_frames_wav(ma_encoder* pEncoder, const void* pFramesIn, ma_uint64 frameCount) +static ma_result ma_encoder__on_write_pcm_frames_wav(ma_encoder* pEncoder, const void* pFramesIn, ma_uint64 frameCount, ma_uint64* pFramesWritten) { drwav* pWav; + ma_uint64 framesWritten; MA_ASSERT(pEncoder != NULL); pWav = (drwav*)pEncoder->pInternalEncoder; MA_ASSERT(pWav != NULL); - return drwav_write_pcm_frames(pWav, frameCount, pFramesIn); + framesWritten = drwav_write_pcm_frames(pWav, frameCount, pFramesIn); + + if (pFramesWritten != NULL) { + *pFramesWritten = framesWritten; + } + + return MA_SUCCESS; } #endif -MA_API ma_encoder_config ma_encoder_config_init(ma_resource_format resourceFormat, ma_format format, ma_uint32 channels, ma_uint32 sampleRate) +MA_API ma_encoder_config ma_encoder_config_init(ma_encoding_format encodingFormat, ma_format format, ma_uint32 channels, ma_uint32 sampleRate) { ma_encoder_config config; MA_ZERO_OBJECT(&config); - config.resourceFormat = resourceFormat; + config.encodingFormat = encodingFormat; config.format = format; config.channels = channels; config.sampleRate = sampleRate; @@ -51784,9 +62836,9 @@ MA_API ma_result ma_encoder_init__internal(ma_encoder_write_proc onWrite, ma_enc pEncoder->onSeek = onSeek; pEncoder->pUserData = pUserData; - switch (pEncoder->config.resourceFormat) + switch (pEncoder->config.encodingFormat) { - case ma_resource_format_wav: + case ma_encoding_format_wav: { #if defined(MA_HAS_WAV) pEncoder->onInit = ma_encoder__on_init_wav; @@ -51806,64 +62858,85 @@ MA_API ma_result ma_encoder_init__internal(ma_encoder_write_proc onWrite, ma_enc /* Getting here means we should have our backend callbacks set up. */ if (result == MA_SUCCESS) { result = pEncoder->onInit(pEncoder); - if (result != MA_SUCCESS) { - return result; - } + } + + return result; +} + +static ma_result ma_encoder__on_write_vfs(ma_encoder* pEncoder, const void* pBufferIn, size_t bytesToWrite, size_t* pBytesWritten) +{ + return ma_vfs_or_default_write(pEncoder->data.vfs.pVFS, pEncoder->data.vfs.file, pBufferIn, bytesToWrite, pBytesWritten); +} + +static ma_result ma_encoder__on_seek_vfs(ma_encoder* pEncoder, ma_int64 offset, ma_seek_origin origin) +{ + return ma_vfs_or_default_seek(pEncoder->data.vfs.pVFS, pEncoder->data.vfs.file, offset, origin); +} + +MA_API ma_result ma_encoder_init_vfs(ma_vfs* pVFS, const char* pFilePath, const ma_encoder_config* pConfig, ma_encoder* pEncoder) +{ + ma_result result; + ma_vfs_file file; + + result = ma_encoder_preinit(pConfig, pEncoder); + if (result != MA_SUCCESS) { + return result; + } + + /* Now open the file. If this fails we don't need to uninitialize the encoder. */ + result = ma_vfs_or_default_open(pVFS, pFilePath, MA_OPEN_MODE_WRITE, &file); + if (result != MA_SUCCESS) { + return result; + } + + pEncoder->data.vfs.pVFS = pVFS; + pEncoder->data.vfs.file = file; + + result = ma_encoder_init__internal(ma_encoder__on_write_vfs, ma_encoder__on_seek_vfs, NULL, pEncoder); + if (result != MA_SUCCESS) { + ma_vfs_or_default_close(pVFS, file); + return result; } return MA_SUCCESS; } -MA_API size_t ma_encoder__on_write_stdio(ma_encoder* pEncoder, const void* pBufferIn, size_t bytesToWrite) +MA_API ma_result ma_encoder_init_vfs_w(ma_vfs* pVFS, const wchar_t* pFilePath, const ma_encoder_config* pConfig, ma_encoder* pEncoder) { - return fwrite(pBufferIn, 1, bytesToWrite, (FILE*)pEncoder->pFile); -} + ma_result result; + ma_vfs_file file; -MA_API ma_bool32 ma_encoder__on_seek_stdio(ma_encoder* pEncoder, int byteOffset, ma_seek_origin origin) -{ - return fseek((FILE*)pEncoder->pFile, byteOffset, (origin == ma_seek_origin_current) ? SEEK_CUR : SEEK_SET) == 0; + result = ma_encoder_preinit(pConfig, pEncoder); + if (result != MA_SUCCESS) { + return result; + } + + /* Now open the file. If this fails we don't need to uninitialize the encoder. */ + result = ma_vfs_or_default_open_w(pVFS, pFilePath, MA_OPEN_MODE_WRITE, &file); + if (result != MA_SUCCESS) { + return result; + } + + pEncoder->data.vfs.pVFS = pVFS; + pEncoder->data.vfs.file = file; + + result = ma_encoder_init__internal(ma_encoder__on_write_vfs, ma_encoder__on_seek_vfs, NULL, pEncoder); + if (result != MA_SUCCESS) { + ma_vfs_or_default_close(pVFS, file); + return result; + } + + return MA_SUCCESS; } MA_API ma_result ma_encoder_init_file(const char* pFilePath, const ma_encoder_config* pConfig, ma_encoder* pEncoder) { - ma_result result; - FILE* pFile; - - result = ma_encoder_preinit(pConfig, pEncoder); - if (result != MA_SUCCESS) { - return result; - } - - /* Now open the file. If this fails we don't need to uninitialize the encoder. */ - result = ma_fopen(&pFile, pFilePath, "wb"); - if (pFile == NULL) { - return result; - } - - pEncoder->pFile = pFile; - - return ma_encoder_init__internal(ma_encoder__on_write_stdio, ma_encoder__on_seek_stdio, NULL, pEncoder); + return ma_encoder_init_vfs(NULL, pFilePath, pConfig, pEncoder); } MA_API ma_result ma_encoder_init_file_w(const wchar_t* pFilePath, const ma_encoder_config* pConfig, ma_encoder* pEncoder) { - ma_result result; - FILE* pFile; - - result = ma_encoder_preinit(pConfig, pEncoder); - if (result != MA_SUCCESS) { - return result; - } - - /* Now open the file. If this fails we don't need to uninitialize the encoder. */ - result = ma_wfopen(&pFile, pFilePath, L"wb", &pEncoder->config.allocationCallbacks); - if (pFile == NULL) { - return result; - } - - pEncoder->pFile = pFile; - - return ma_encoder_init__internal(ma_encoder__on_write_stdio, ma_encoder__on_seek_stdio, NULL, pEncoder); + return ma_encoder_init_vfs_w(NULL, pFilePath, pConfig, pEncoder); } MA_API ma_result ma_encoder_init(ma_encoder_write_proc onWrite, ma_encoder_seek_proc onSeek, void* pUserData, const ma_encoder_config* pConfig, ma_encoder* pEncoder) @@ -51890,19 +62963,24 @@ MA_API void ma_encoder_uninit(ma_encoder* pEncoder) } /* If we have a file handle, close it. */ - if (pEncoder->onWrite == ma_encoder__on_write_stdio) { - fclose((FILE*)pEncoder->pFile); + if (pEncoder->onWrite == ma_encoder__on_write_vfs) { + ma_vfs_or_default_close(pEncoder->data.vfs.pVFS, pEncoder->data.vfs.file); + pEncoder->data.vfs.file = NULL; } } -MA_API ma_uint64 ma_encoder_write_pcm_frames(ma_encoder* pEncoder, const void* pFramesIn, ma_uint64 frameCount) +MA_API ma_result ma_encoder_write_pcm_frames(ma_encoder* pEncoder, const void* pFramesIn, ma_uint64 frameCount, ma_uint64* pFramesWritten) { - if (pEncoder == NULL || pFramesIn == NULL) { - return 0; + if (pFramesWritten != NULL) { + *pFramesWritten = 0; } - return pEncoder->onWritePCMFrames(pEncoder, pFramesIn, frameCount); + if (pEncoder == NULL || pFramesIn == NULL) { + return MA_INVALID_ARGS; + } + + return pEncoder->onWritePCMFrames(pEncoder, pFramesIn, frameCount, pFramesWritten); } #endif /* MA_NO_ENCODING */ @@ -51931,17 +63009,7 @@ MA_API ma_waveform_config ma_waveform_config_init(ma_format format, ma_uint32 ch static ma_result ma_waveform__data_source_on_read(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) { - ma_uint64 framesRead = ma_waveform_read_pcm_frames((ma_waveform*)pDataSource, pFramesOut, frameCount); - - if (pFramesRead != NULL) { - *pFramesRead = framesRead; - } - - if (framesRead == 0) { - return MA_AT_END; - } - - return MA_SUCCESS; + return ma_waveform_read_pcm_frames((ma_waveform*)pDataSource, pFramesOut, frameCount, pFramesRead); } static ma_result ma_waveform__data_source_on_seek(ma_data_source* pDataSource, ma_uint64 frameIndex) @@ -51949,13 +63017,14 @@ static ma_result ma_waveform__data_source_on_seek(ma_data_source* pDataSource, m return ma_waveform_seek_to_pcm_frame((ma_waveform*)pDataSource, frameIndex); } -static ma_result ma_waveform__data_source_on_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate) +static ma_result ma_waveform__data_source_on_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) { ma_waveform* pWaveform = (ma_waveform*)pDataSource; *pFormat = pWaveform->config.format; *pChannels = pWaveform->config.channels; *pSampleRate = pWaveform->config.sampleRate; + ma_channel_map_init_standard(ma_standard_channel_map_default, pChannelMap, channelMapCap, pWaveform->config.channels); return MA_SUCCESS; } @@ -51983,11 +63052,11 @@ static ma_data_source_vtable g_ma_waveform_data_source_vtable = { ma_waveform__data_source_on_read, ma_waveform__data_source_on_seek, - NULL, /* onMap */ - NULL, /* onUnmap */ ma_waveform__data_source_on_get_data_format, ma_waveform__data_source_on_get_cursor, - NULL /* onGetLength. There's no notion of a length in waveforms. */ + NULL, /* onGetLength. There's no notion of a length in waveforms. */ + NULL, /* onSetLooping */ + 0 }; MA_API ma_result ma_waveform_init(const ma_waveform_config* pConfig, ma_waveform* pWaveform) @@ -52296,10 +63365,18 @@ static void ma_waveform_read_pcm_frames__sawtooth(ma_waveform* pWaveform, void* } } -MA_API ma_uint64 ma_waveform_read_pcm_frames(ma_waveform* pWaveform, void* pFramesOut, ma_uint64 frameCount) +MA_API ma_result ma_waveform_read_pcm_frames(ma_waveform* pWaveform, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) { + if (pFramesRead != NULL) { + *pFramesRead = 0; + } + + if (frameCount == 0) { + return MA_INVALID_ARGS; + } + if (pWaveform == NULL) { - return 0; + return MA_INVALID_ARGS; } if (pFramesOut != NULL) { @@ -52325,13 +63402,17 @@ MA_API ma_uint64 ma_waveform_read_pcm_frames(ma_waveform* pWaveform, void* pFram ma_waveform_read_pcm_frames__sawtooth(pWaveform, pFramesOut, frameCount); } break; - default: return 0; + default: return MA_INVALID_OPERATION; /* Unknown waveform type. */ } } else { pWaveform->time += pWaveform->advance * (ma_int64)frameCount; /* Cast to int64 required for VC6. Won't affect anything in practice. */ } - return frameCount; + if (pFramesRead != NULL) { + *pFramesRead = frameCount; + } + + return MA_SUCCESS; } MA_API ma_result ma_waveform_seek_to_pcm_frame(ma_waveform* pWaveform, ma_uint64 frameIndex) @@ -52367,17 +63448,7 @@ MA_API ma_noise_config ma_noise_config_init(ma_format format, ma_uint32 channels static ma_result ma_noise__data_source_on_read(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) { - ma_uint64 framesRead = ma_noise_read_pcm_frames((ma_noise*)pDataSource, pFramesOut, frameCount); - - if (pFramesRead != NULL) { - *pFramesRead = framesRead; - } - - if (framesRead == 0) { - return MA_AT_END; - } - - return MA_SUCCESS; + return ma_noise_read_pcm_frames((ma_noise*)pDataSource, pFramesOut, frameCount, pFramesRead); } static ma_result ma_noise__data_source_on_seek(ma_data_source* pDataSource, ma_uint64 frameIndex) @@ -52388,13 +63459,14 @@ static ma_result ma_noise__data_source_on_seek(ma_data_source* pDataSource, ma_u return MA_SUCCESS; } -static ma_result ma_noise__data_source_on_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate) +static ma_result ma_noise__data_source_on_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) { ma_noise* pNoise = (ma_noise*)pDataSource; *pFormat = pNoise->config.format; *pChannels = pNoise->config.channels; *pSampleRate = 0; /* There is no notion of sample rate with noise generation. */ + ma_channel_map_init_standard(ma_standard_channel_map_default, pChannelMap, channelMapCap, pNoise->config.channels); return MA_SUCCESS; } @@ -52403,17 +63475,105 @@ static ma_data_source_vtable g_ma_noise_data_source_vtable = { ma_noise__data_source_on_read, ma_noise__data_source_on_seek, /* No-op for noise. */ - NULL, /* onMap */ - NULL, /* onUnmap */ ma_noise__data_source_on_get_data_format, NULL, /* onGetCursor. No notion of a cursor for noise. */ - NULL /* onGetLength. No notion of a length for noise. */ + NULL, /* onGetLength. No notion of a length for noise. */ + NULL, /* onSetLooping */ + 0 }; -MA_API ma_result ma_noise_init(const ma_noise_config* pConfig, ma_noise* pNoise) + +#ifndef MA_PINK_NOISE_BIN_SIZE +#define MA_PINK_NOISE_BIN_SIZE 16 +#endif + +typedef struct +{ + size_t sizeInBytes; + struct + { + size_t binOffset; + size_t accumulationOffset; + size_t counterOffset; + } pink; + struct + { + size_t accumulationOffset; + } brownian; +} ma_noise_heap_layout; + +static ma_result ma_noise_get_heap_layout(const ma_noise_config* pConfig, ma_noise_heap_layout* pHeapLayout) +{ + MA_ASSERT(pHeapLayout != NULL); + + MA_ZERO_OBJECT(pHeapLayout); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->channels == 0) { + return MA_INVALID_ARGS; + } + + pHeapLayout->sizeInBytes = 0; + + /* Pink. */ + if (pConfig->type == ma_noise_type_pink) { + /* bin */ + pHeapLayout->pink.binOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += sizeof(double*) * pConfig->channels; + pHeapLayout->sizeInBytes += sizeof(double ) * pConfig->channels * MA_PINK_NOISE_BIN_SIZE; + + /* accumulation */ + pHeapLayout->pink.accumulationOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += sizeof(double) * pConfig->channels; + + /* counter */ + pHeapLayout->pink.counterOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += sizeof(ma_uint32) * pConfig->channels; + } + + /* Brownian. */ + if (pConfig->type == ma_noise_type_brownian) { + /* accumulation */ + pHeapLayout->brownian.accumulationOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += sizeof(double) * pConfig->channels; + } + + /* Make sure allocation size is aligned. */ + pHeapLayout->sizeInBytes = ma_align_64(pHeapLayout->sizeInBytes); + + return MA_SUCCESS; +} + +MA_API ma_result ma_noise_get_heap_size(const ma_noise_config* pConfig, size_t* pHeapSizeInBytes) { ma_result result; + ma_noise_heap_layout heapLayout; + + if (pHeapSizeInBytes == NULL) { + return MA_INVALID_ARGS; + } + + *pHeapSizeInBytes = 0; + + result = ma_noise_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + *pHeapSizeInBytes = heapLayout.sizeInBytes; + + return MA_SUCCESS; +} + +MA_API ma_result ma_noise_init_preallocated(const ma_noise_config* pConfig, void* pHeap, ma_noise* pNoise) +{ + ma_result result; + ma_noise_heap_layout heapLayout; ma_data_source_config dataSourceConfig; + ma_uint32 iChannel; if (pNoise == NULL) { return MA_INVALID_ARGS; @@ -52421,13 +63581,13 @@ MA_API ma_result ma_noise_init(const ma_noise_config* pConfig, ma_noise* pNoise) MA_ZERO_OBJECT(pNoise); - if (pConfig == NULL) { - return MA_INVALID_ARGS; + result = ma_noise_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; } - if (pConfig->channels < MA_MIN_CHANNELS || pConfig->channels > MA_MAX_CHANNELS) { - return MA_INVALID_ARGS; - } + pNoise->_pHeap = pHeap; + MA_ZERO_MEMORY(pNoise->_pHeap, heapLayout.sizeInBytes); dataSourceConfig = ma_data_source_config_init(); dataSourceConfig.vtable = &g_ma_noise_data_source_vtable; @@ -52441,15 +63601,20 @@ MA_API ma_result ma_noise_init(const ma_noise_config* pConfig, ma_noise* pNoise) ma_lcg_seed(&pNoise->lcg, pConfig->seed); if (pNoise->config.type == ma_noise_type_pink) { - ma_uint32 iChannel; + pNoise->state.pink.bin = (double** )ma_offset_ptr(pHeap, heapLayout.pink.binOffset); + pNoise->state.pink.accumulation = (double* )ma_offset_ptr(pHeap, heapLayout.pink.accumulationOffset); + pNoise->state.pink.counter = (ma_uint32*)ma_offset_ptr(pHeap, heapLayout.pink.counterOffset); + for (iChannel = 0; iChannel < pConfig->channels; iChannel += 1) { + pNoise->state.pink.bin[iChannel] = (double*)ma_offset_ptr(pHeap, heapLayout.pink.binOffset + (sizeof(double*) * pConfig->channels) + (sizeof(double) * MA_PINK_NOISE_BIN_SIZE * iChannel)); pNoise->state.pink.accumulation[iChannel] = 0; pNoise->state.pink.counter[iChannel] = 1; } } if (pNoise->config.type == ma_noise_type_brownian) { - ma_uint32 iChannel; + pNoise->state.brownian.accumulation = (double*)ma_offset_ptr(pHeap, heapLayout.brownian.accumulationOffset); + for (iChannel = 0; iChannel < pConfig->channels; iChannel += 1) { pNoise->state.brownian.accumulation[iChannel] = 0; } @@ -52458,13 +63623,47 @@ MA_API ma_result ma_noise_init(const ma_noise_config* pConfig, ma_noise* pNoise) return MA_SUCCESS; } -MA_API void ma_noise_uninit(ma_noise* pNoise) +MA_API ma_result ma_noise_init(const ma_noise_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_noise* pNoise) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_noise_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_noise_init_preallocated(pConfig, pHeap, pNoise); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pNoise->_ownsHeap = MA_TRUE; + return MA_SUCCESS; +} + +MA_API void ma_noise_uninit(ma_noise* pNoise, const ma_allocation_callbacks* pAllocationCallbacks) { if (pNoise == NULL) { return; } ma_data_source_uninit(&pNoise->ds); + + if (pNoise->_ownsHeap) { + ma_free(pNoise->_pHeap, pAllocationCallbacks); + } } MA_API ma_result ma_noise_set_amplitude(ma_noise* pNoise, double amplitude) @@ -52513,7 +63712,7 @@ static MA_INLINE ma_uint64 ma_noise_read_pcm_frames__white(ma_noise* pNoise, voi ma_uint64 iFrame; ma_uint32 iChannel; const ma_uint32 channels = pNoise->config.channels; - MA_ASSUME(channels >= MA_MIN_CHANNELS && channels <= MA_MAX_CHANNELS); + MA_ASSUME(channels > 0); if (pNoise->config.format == ma_format_f32) { float* pFramesOutF32 = (float*)pFramesOut; @@ -52607,7 +63806,7 @@ static MA_INLINE float ma_noise_f32_pink(ma_noise* pNoise, ma_uint32 iChannel) double binNext; unsigned int ibin; - ibin = ma_tzcnt32(pNoise->state.pink.counter[iChannel]) & (ma_countof(pNoise->state.pink.bin[0]) - 1); + ibin = ma_tzcnt32(pNoise->state.pink.counter[iChannel]) & (MA_PINK_NOISE_BIN_SIZE - 1); binPrev = pNoise->state.pink.bin[iChannel][ibin]; binNext = ma_lcg_rand_f64(&pNoise->lcg); @@ -52632,7 +63831,7 @@ static MA_INLINE ma_uint64 ma_noise_read_pcm_frames__pink(ma_noise* pNoise, void ma_uint64 iFrame; ma_uint32 iChannel; const ma_uint32 channels = pNoise->config.channels; - MA_ASSUME(channels >= MA_MIN_CHANNELS && channels <= MA_MAX_CHANNELS); + MA_ASSUME(channels > 0); if (pNoise->config.format == ma_format_f32) { float* pFramesOutF32 = (float*)pFramesOut; @@ -52714,7 +63913,7 @@ static MA_INLINE ma_uint64 ma_noise_read_pcm_frames__brownian(ma_noise* pNoise, ma_uint64 iFrame; ma_uint32 iChannel; const ma_uint32 channels = pNoise->config.channels; - MA_ASSUME(channels >= MA_MIN_CHANNELS && channels <= MA_MAX_CHANNELS); + MA_ASSUME(channels > 0); if (pNoise->config.format == ma_format_f32) { float* pFramesOutF32 = (float*)pFramesOut; @@ -52772,37 +63971,9798 @@ static MA_INLINE ma_uint64 ma_noise_read_pcm_frames__brownian(ma_noise* pNoise, return frameCount; } -MA_API ma_uint64 ma_noise_read_pcm_frames(ma_noise* pNoise, void* pFramesOut, ma_uint64 frameCount) +MA_API ma_result ma_noise_read_pcm_frames(ma_noise* pNoise, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) { + ma_uint64 framesRead = 0; + + if (pFramesRead != NULL) { + *pFramesRead = 0; + } + + if (frameCount == 0) { + return MA_INVALID_ARGS; + } + if (pNoise == NULL) { - return 0; + return MA_INVALID_ARGS; } /* The output buffer is allowed to be NULL. Since we aren't tracking cursors or anything we can just do nothing and pretend to be successful. */ if (pFramesOut == NULL) { - return frameCount; + framesRead = frameCount; + } else { + switch (pNoise->config.type) { + case ma_noise_type_white: framesRead = ma_noise_read_pcm_frames__white (pNoise, pFramesOut, frameCount); break; + case ma_noise_type_pink: framesRead = ma_noise_read_pcm_frames__pink (pNoise, pFramesOut, frameCount); break; + case ma_noise_type_brownian: framesRead = ma_noise_read_pcm_frames__brownian(pNoise, pFramesOut, frameCount); break; + default: return MA_INVALID_OPERATION; /* Unknown noise type. */ + } } - if (pNoise->config.type == ma_noise_type_white) { - return ma_noise_read_pcm_frames__white(pNoise, pFramesOut, frameCount); + if (pFramesRead != NULL) { + *pFramesRead = framesRead; } - if (pNoise->config.type == ma_noise_type_pink) { - return ma_noise_read_pcm_frames__pink(pNoise, pFramesOut, frameCount); - } - - if (pNoise->config.type == ma_noise_type_brownian) { - return ma_noise_read_pcm_frames__brownian(pNoise, pFramesOut, frameCount); - } - - /* Should never get here. */ - MA_ASSERT(MA_FALSE); - return 0; + return MA_SUCCESS; } #endif /* MA_NO_GENERATION */ +#ifndef MA_NO_RESOURCE_MANAGER +#ifndef MA_RESOURCE_MANAGER_PAGE_SIZE_IN_MILLISECONDS +#define MA_RESOURCE_MANAGER_PAGE_SIZE_IN_MILLISECONDS 1000 +#endif + +#ifndef MA_JOB_TYPE_RESOURCE_MANAGER_QUEUE_CAPACITY +#define MA_JOB_TYPE_RESOURCE_MANAGER_QUEUE_CAPACITY 1024 +#endif + +MA_API ma_resource_manager_pipeline_notifications ma_resource_manager_pipeline_notifications_init(void) +{ + ma_resource_manager_pipeline_notifications notifications; + + MA_ZERO_OBJECT(¬ifications); + + return notifications; +} + +static void ma_resource_manager_pipeline_notifications_signal_all_notifications(const ma_resource_manager_pipeline_notifications* pPipelineNotifications) +{ + if (pPipelineNotifications == NULL) { + return; + } + + if (pPipelineNotifications->init.pNotification) { ma_async_notification_signal(pPipelineNotifications->init.pNotification); } + if (pPipelineNotifications->done.pNotification) { ma_async_notification_signal(pPipelineNotifications->done.pNotification); } +} + +static void ma_resource_manager_pipeline_notifications_acquire_all_fences(const ma_resource_manager_pipeline_notifications* pPipelineNotifications) +{ + if (pPipelineNotifications == NULL) { + return; + } + + if (pPipelineNotifications->init.pFence != NULL) { ma_fence_acquire(pPipelineNotifications->init.pFence); } + if (pPipelineNotifications->done.pFence != NULL) { ma_fence_acquire(pPipelineNotifications->done.pFence); } +} + +static void ma_resource_manager_pipeline_notifications_release_all_fences(const ma_resource_manager_pipeline_notifications* pPipelineNotifications) +{ + if (pPipelineNotifications == NULL) { + return; + } + + if (pPipelineNotifications->init.pFence != NULL) { ma_fence_release(pPipelineNotifications->init.pFence); } + if (pPipelineNotifications->done.pFence != NULL) { ma_fence_release(pPipelineNotifications->done.pFence); } +} + + + +#ifndef MA_DEFAULT_HASH_SEED +#define MA_DEFAULT_HASH_SEED 42 +#endif + +/* MurmurHash3. Based on code from https://github.com/PeterScott/murmur3/blob/master/murmur3.c (public domain). */ +#if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) + #pragma GCC diagnostic push + #if __GNUC__ >= 7 + #pragma GCC diagnostic ignored "-Wimplicit-fallthrough" + #endif +#endif + +static MA_INLINE ma_uint32 ma_rotl32(ma_uint32 x, ma_int8 r) +{ + return (x << r) | (x >> (32 - r)); +} + +static MA_INLINE ma_uint32 ma_hash_getblock(const ma_uint32* blocks, int i) +{ + if (ma_is_little_endian()) { + return blocks[i]; + } else { + return ma_swap_endian_uint32(blocks[i]); + } +} + +static MA_INLINE ma_uint32 ma_hash_fmix32(ma_uint32 h) +{ + h ^= h >> 16; + h *= 0x85ebca6b; + h ^= h >> 13; + h *= 0xc2b2ae35; + h ^= h >> 16; + + return h; +} + +static ma_uint32 ma_hash_32(const void* key, int len, ma_uint32 seed) +{ + const ma_uint8* data = (const ma_uint8*)key; + const ma_uint32* blocks; + const ma_uint8* tail; + const int nblocks = len / 4; + ma_uint32 h1 = seed; + ma_uint32 c1 = 0xcc9e2d51; + ma_uint32 c2 = 0x1b873593; + ma_uint32 k1; + int i; + + blocks = (const ma_uint32 *)(data + nblocks*4); + + for(i = -nblocks; i; i++) { + k1 = ma_hash_getblock(blocks,i); + + k1 *= c1; + k1 = ma_rotl32(k1, 15); + k1 *= c2; + + h1 ^= k1; + h1 = ma_rotl32(h1, 13); + h1 = h1*5 + 0xe6546b64; + } + + + tail = (const ma_uint8*)(data + nblocks*4); + + k1 = 0; + switch(len & 3) { + case 3: k1 ^= tail[2] << 16; + case 2: k1 ^= tail[1] << 8; + case 1: k1 ^= tail[0]; + k1 *= c1; k1 = ma_rotl32(k1, 15); k1 *= c2; h1 ^= k1; + }; + + + h1 ^= len; + h1 = ma_hash_fmix32(h1); + + return h1; +} + +#if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) + #pragma GCC diagnostic push +#endif +/* End MurmurHash3 */ + +static ma_uint32 ma_hash_string_32(const char* str) +{ + return ma_hash_32(str, (int)strlen(str), MA_DEFAULT_HASH_SEED); +} + +static ma_uint32 ma_hash_string_w_32(const wchar_t* str) +{ + return ma_hash_32(str, (int)wcslen(str) * sizeof(*str), MA_DEFAULT_HASH_SEED); +} + + + + +/* +Basic BST Functions +*/ +static ma_result ma_resource_manager_data_buffer_node_search(ma_resource_manager* pResourceManager, ma_uint32 hashedName32, ma_resource_manager_data_buffer_node** ppDataBufferNode) +{ + ma_resource_manager_data_buffer_node* pCurrentNode; + + MA_ASSERT(pResourceManager != NULL); + MA_ASSERT(ppDataBufferNode != NULL); + + pCurrentNode = pResourceManager->pRootDataBufferNode; + while (pCurrentNode != NULL) { + if (hashedName32 == pCurrentNode->hashedName32) { + break; /* Found. */ + } else if (hashedName32 < pCurrentNode->hashedName32) { + pCurrentNode = pCurrentNode->pChildLo; + } else { + pCurrentNode = pCurrentNode->pChildHi; + } + } + + *ppDataBufferNode = pCurrentNode; + + if (pCurrentNode == NULL) { + return MA_DOES_NOT_EXIST; + } else { + return MA_SUCCESS; + } +} + +static ma_result ma_resource_manager_data_buffer_node_insert_point(ma_resource_manager* pResourceManager, ma_uint32 hashedName32, ma_resource_manager_data_buffer_node** ppInsertPoint) +{ + ma_result result = MA_SUCCESS; + ma_resource_manager_data_buffer_node* pCurrentNode; + + MA_ASSERT(pResourceManager != NULL); + MA_ASSERT(ppInsertPoint != NULL); + + *ppInsertPoint = NULL; + + if (pResourceManager->pRootDataBufferNode == NULL) { + return MA_SUCCESS; /* No items. */ + } + + /* We need to find the node that will become the parent of the new node. If a node is found that already has the same hashed name we need to return MA_ALREADY_EXISTS. */ + pCurrentNode = pResourceManager->pRootDataBufferNode; + while (pCurrentNode != NULL) { + if (hashedName32 == pCurrentNode->hashedName32) { + result = MA_ALREADY_EXISTS; + break; + } else { + if (hashedName32 < pCurrentNode->hashedName32) { + if (pCurrentNode->pChildLo == NULL) { + result = MA_SUCCESS; + break; + } else { + pCurrentNode = pCurrentNode->pChildLo; + } + } else { + if (pCurrentNode->pChildHi == NULL) { + result = MA_SUCCESS; + break; + } else { + pCurrentNode = pCurrentNode->pChildHi; + } + } + } + } + + *ppInsertPoint = pCurrentNode; + return result; +} + +static ma_result ma_resource_manager_data_buffer_node_insert_at(ma_resource_manager* pResourceManager, ma_resource_manager_data_buffer_node* pDataBufferNode, ma_resource_manager_data_buffer_node* pInsertPoint) +{ + MA_ASSERT(pResourceManager != NULL); + MA_ASSERT(pDataBufferNode != NULL); + + /* The key must have been set before calling this function. */ + MA_ASSERT(pDataBufferNode->hashedName32 != 0); + + if (pInsertPoint == NULL) { + /* It's the first node. */ + pResourceManager->pRootDataBufferNode = pDataBufferNode; + } else { + /* It's not the first node. It needs to be inserted. */ + if (pDataBufferNode->hashedName32 < pInsertPoint->hashedName32) { + MA_ASSERT(pInsertPoint->pChildLo == NULL); + pInsertPoint->pChildLo = pDataBufferNode; + } else { + MA_ASSERT(pInsertPoint->pChildHi == NULL); + pInsertPoint->pChildHi = pDataBufferNode; + } + } + + pDataBufferNode->pParent = pInsertPoint; + + return MA_SUCCESS; +} + +#if 0 /* Unused for now. */ +static ma_result ma_resource_manager_data_buffer_node_insert(ma_resource_manager* pResourceManager, ma_resource_manager_data_buffer_node* pDataBufferNode) +{ + ma_result result; + ma_resource_manager_data_buffer_node* pInsertPoint; + + MA_ASSERT(pResourceManager != NULL); + MA_ASSERT(pDataBufferNode != NULL); + + result = ma_resource_manager_data_buffer_node_insert_point(pResourceManager, pDataBufferNode->hashedName32, &pInsertPoint); + if (result != MA_SUCCESS) { + return MA_INVALID_ARGS; + } + + return ma_resource_manager_data_buffer_node_insert_at(pResourceManager, pDataBufferNode, pInsertPoint); +} +#endif + +static MA_INLINE ma_resource_manager_data_buffer_node* ma_resource_manager_data_buffer_node_find_min(ma_resource_manager_data_buffer_node* pDataBufferNode) +{ + ma_resource_manager_data_buffer_node* pCurrentNode; + + MA_ASSERT(pDataBufferNode != NULL); + + pCurrentNode = pDataBufferNode; + while (pCurrentNode->pChildLo != NULL) { + pCurrentNode = pCurrentNode->pChildLo; + } + + return pCurrentNode; +} + +static MA_INLINE ma_resource_manager_data_buffer_node* ma_resource_manager_data_buffer_node_find_max(ma_resource_manager_data_buffer_node* pDataBufferNode) +{ + ma_resource_manager_data_buffer_node* pCurrentNode; + + MA_ASSERT(pDataBufferNode != NULL); + + pCurrentNode = pDataBufferNode; + while (pCurrentNode->pChildHi != NULL) { + pCurrentNode = pCurrentNode->pChildHi; + } + + return pCurrentNode; +} + +static MA_INLINE ma_resource_manager_data_buffer_node* ma_resource_manager_data_buffer_node_find_inorder_successor(ma_resource_manager_data_buffer_node* pDataBufferNode) +{ + MA_ASSERT(pDataBufferNode != NULL); + MA_ASSERT(pDataBufferNode->pChildHi != NULL); + + return ma_resource_manager_data_buffer_node_find_min(pDataBufferNode->pChildHi); +} + +static MA_INLINE ma_resource_manager_data_buffer_node* ma_resource_manager_data_buffer_node_find_inorder_predecessor(ma_resource_manager_data_buffer_node* pDataBufferNode) +{ + MA_ASSERT(pDataBufferNode != NULL); + MA_ASSERT(pDataBufferNode->pChildLo != NULL); + + return ma_resource_manager_data_buffer_node_find_max(pDataBufferNode->pChildLo); +} + +static ma_result ma_resource_manager_data_buffer_node_remove(ma_resource_manager* pResourceManager, ma_resource_manager_data_buffer_node* pDataBufferNode) +{ + MA_ASSERT(pResourceManager != NULL); + MA_ASSERT(pDataBufferNode != NULL); + + if (pDataBufferNode->pChildLo == NULL) { + if (pDataBufferNode->pChildHi == NULL) { + /* Simple case - deleting a buffer with no children. */ + if (pDataBufferNode->pParent == NULL) { + MA_ASSERT(pResourceManager->pRootDataBufferNode == pDataBufferNode); /* There is only a single buffer in the tree which should be equal to the root node. */ + pResourceManager->pRootDataBufferNode = NULL; + } else { + if (pDataBufferNode->pParent->pChildLo == pDataBufferNode) { + pDataBufferNode->pParent->pChildLo = NULL; + } else { + pDataBufferNode->pParent->pChildHi = NULL; + } + } + } else { + /* Node has one child - pChildHi != NULL. */ + pDataBufferNode->pChildHi->pParent = pDataBufferNode->pParent; + + if (pDataBufferNode->pParent == NULL) { + MA_ASSERT(pResourceManager->pRootDataBufferNode == pDataBufferNode); + pResourceManager->pRootDataBufferNode = pDataBufferNode->pChildHi; + } else { + if (pDataBufferNode->pParent->pChildLo == pDataBufferNode) { + pDataBufferNode->pParent->pChildLo = pDataBufferNode->pChildHi; + } else { + pDataBufferNode->pParent->pChildHi = pDataBufferNode->pChildHi; + } + } + } + } else { + if (pDataBufferNode->pChildHi == NULL) { + /* Node has one child - pChildLo != NULL. */ + pDataBufferNode->pChildLo->pParent = pDataBufferNode->pParent; + + if (pDataBufferNode->pParent == NULL) { + MA_ASSERT(pResourceManager->pRootDataBufferNode == pDataBufferNode); + pResourceManager->pRootDataBufferNode = pDataBufferNode->pChildLo; + } else { + if (pDataBufferNode->pParent->pChildLo == pDataBufferNode) { + pDataBufferNode->pParent->pChildLo = pDataBufferNode->pChildLo; + } else { + pDataBufferNode->pParent->pChildHi = pDataBufferNode->pChildLo; + } + } + } else { + /* Complex case - deleting a node with two children. */ + ma_resource_manager_data_buffer_node* pReplacementDataBufferNode; + + /* For now we are just going to use the in-order successor as the replacement, but we may want to try to keep this balanced by switching between the two. */ + pReplacementDataBufferNode = ma_resource_manager_data_buffer_node_find_inorder_successor(pDataBufferNode); + MA_ASSERT(pReplacementDataBufferNode != NULL); + + /* + Now that we have our replacement node we can make the change. The simple way to do this would be to just exchange the values, and then remove the replacement + node, however we track specific nodes via pointers which means we can't just swap out the values. We need to instead just change the pointers around. The + replacement node should have at most 1 child. Therefore, we can detach it in terms of our simpler cases above. What we're essentially doing is detaching the + replacement node and reinserting it into the same position as the deleted node. + */ + MA_ASSERT(pReplacementDataBufferNode->pParent != NULL); /* The replacement node should never be the root which means it should always have a parent. */ + MA_ASSERT(pReplacementDataBufferNode->pChildLo == NULL); /* Because we used in-order successor. This would be pChildHi == NULL if we used in-order predecessor. */ + + if (pReplacementDataBufferNode->pChildHi == NULL) { + if (pReplacementDataBufferNode->pParent->pChildLo == pReplacementDataBufferNode) { + pReplacementDataBufferNode->pParent->pChildLo = NULL; + } else { + pReplacementDataBufferNode->pParent->pChildHi = NULL; + } + } else { + pReplacementDataBufferNode->pChildHi->pParent = pReplacementDataBufferNode->pParent; + if (pReplacementDataBufferNode->pParent->pChildLo == pReplacementDataBufferNode) { + pReplacementDataBufferNode->pParent->pChildLo = pReplacementDataBufferNode->pChildHi; + } else { + pReplacementDataBufferNode->pParent->pChildHi = pReplacementDataBufferNode->pChildHi; + } + } + + + /* The replacement node has essentially been detached from the binary tree, so now we need to replace the old data buffer with it. The first thing to update is the parent */ + if (pDataBufferNode->pParent != NULL) { + if (pDataBufferNode->pParent->pChildLo == pDataBufferNode) { + pDataBufferNode->pParent->pChildLo = pReplacementDataBufferNode; + } else { + pDataBufferNode->pParent->pChildHi = pReplacementDataBufferNode; + } + } + + /* Now need to update the replacement node's pointers. */ + pReplacementDataBufferNode->pParent = pDataBufferNode->pParent; + pReplacementDataBufferNode->pChildLo = pDataBufferNode->pChildLo; + pReplacementDataBufferNode->pChildHi = pDataBufferNode->pChildHi; + + /* Now the children of the replacement node need to have their parent pointers updated. */ + if (pReplacementDataBufferNode->pChildLo != NULL) { + pReplacementDataBufferNode->pChildLo->pParent = pReplacementDataBufferNode; + } + if (pReplacementDataBufferNode->pChildHi != NULL) { + pReplacementDataBufferNode->pChildHi->pParent = pReplacementDataBufferNode; + } + + /* Now the root node needs to be updated. */ + if (pResourceManager->pRootDataBufferNode == pDataBufferNode) { + pResourceManager->pRootDataBufferNode = pReplacementDataBufferNode; + } + } + } + + return MA_SUCCESS; +} + +#if 0 /* Unused for now. */ +static ma_result ma_resource_manager_data_buffer_node_remove_by_key(ma_resource_manager* pResourceManager, ma_uint32 hashedName32) +{ + ma_result result; + ma_resource_manager_data_buffer_node* pDataBufferNode; + + result = ma_resource_manager_data_buffer_search(pResourceManager, hashedName32, &pDataBufferNode); + if (result != MA_SUCCESS) { + return result; /* Could not find the data buffer. */ + } + + return ma_resource_manager_data_buffer_remove(pResourceManager, pDataBufferNode); +} +#endif + +static ma_resource_manager_data_supply_type ma_resource_manager_data_buffer_node_get_data_supply_type(ma_resource_manager_data_buffer_node* pDataBufferNode) +{ + return (ma_resource_manager_data_supply_type)c89atomic_load_i32(&pDataBufferNode->data.type); +} + +static void ma_resource_manager_data_buffer_node_set_data_supply_type(ma_resource_manager_data_buffer_node* pDataBufferNode, ma_resource_manager_data_supply_type supplyType) +{ + c89atomic_exchange_i32(&pDataBufferNode->data.type, supplyType); +} + +static ma_result ma_resource_manager_data_buffer_node_increment_ref(ma_resource_manager* pResourceManager, ma_resource_manager_data_buffer_node* pDataBufferNode, ma_uint32* pNewRefCount) +{ + ma_uint32 refCount; + + MA_ASSERT(pResourceManager != NULL); + MA_ASSERT(pDataBufferNode != NULL); + + (void)pResourceManager; + + refCount = c89atomic_fetch_add_32(&pDataBufferNode->refCount, 1) + 1; + + if (pNewRefCount != NULL) { + *pNewRefCount = refCount; + } + + return MA_SUCCESS; +} + +static ma_result ma_resource_manager_data_buffer_node_decrement_ref(ma_resource_manager* pResourceManager, ma_resource_manager_data_buffer_node* pDataBufferNode, ma_uint32* pNewRefCount) +{ + ma_uint32 refCount; + + MA_ASSERT(pResourceManager != NULL); + MA_ASSERT(pDataBufferNode != NULL); + + (void)pResourceManager; + + refCount = c89atomic_fetch_sub_32(&pDataBufferNode->refCount, 1) - 1; + + if (pNewRefCount != NULL) { + *pNewRefCount = refCount; + } + + return MA_SUCCESS; +} + +static void ma_resource_manager_data_buffer_node_free(ma_resource_manager* pResourceManager, ma_resource_manager_data_buffer_node* pDataBufferNode) +{ + MA_ASSERT(pResourceManager != NULL); + MA_ASSERT(pDataBufferNode != NULL); + + if (pDataBufferNode->isDataOwnedByResourceManager) { + if (ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBufferNode) == ma_resource_manager_data_supply_type_encoded) { + ma_free((void*)pDataBufferNode->data.backend.encoded.pData, &pResourceManager->config.allocationCallbacks); + pDataBufferNode->data.backend.encoded.pData = NULL; + pDataBufferNode->data.backend.encoded.sizeInBytes = 0; + } else if (ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBufferNode) == ma_resource_manager_data_supply_type_decoded) { + ma_free((void*)pDataBufferNode->data.backend.decoded.pData, &pResourceManager->config.allocationCallbacks); + pDataBufferNode->data.backend.decoded.pData = NULL; + pDataBufferNode->data.backend.decoded.totalFrameCount = 0; + } else if (ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBufferNode) == ma_resource_manager_data_supply_type_decoded_paged) { + ma_paged_audio_buffer_data_uninit(&pDataBufferNode->data.backend.decodedPaged.data, &pResourceManager->config.allocationCallbacks); + } else { + /* Should never hit this if the node was successfully initialized. */ + MA_ASSERT(pDataBufferNode->result != MA_SUCCESS); + } + } + + /* The data buffer itself needs to be freed. */ + ma_free(pDataBufferNode, &pResourceManager->config.allocationCallbacks); +} + +static ma_result ma_resource_manager_data_buffer_node_result(const ma_resource_manager_data_buffer_node* pDataBufferNode) +{ + MA_ASSERT(pDataBufferNode != NULL); + + return (ma_result)c89atomic_load_i32((ma_result*)&pDataBufferNode->result); /* Need a naughty const-cast here. */ +} + + +static ma_bool32 ma_resource_manager_is_threading_enabled(const ma_resource_manager* pResourceManager) +{ + MA_ASSERT(pResourceManager != NULL); + + return (pResourceManager->config.flags & MA_RESOURCE_MANAGER_FLAG_NO_THREADING) == 0; +} + + +typedef struct +{ + union + { + ma_async_notification_event e; + ma_async_notification_poll p; + } backend; /* Must be the first member. */ + ma_resource_manager* pResourceManager; +} ma_resource_manager_inline_notification; + +static ma_result ma_resource_manager_inline_notification_init(ma_resource_manager* pResourceManager, ma_resource_manager_inline_notification* pNotification) +{ + MA_ASSERT(pResourceManager != NULL); + MA_ASSERT(pNotification != NULL); + + pNotification->pResourceManager = pResourceManager; + + if (ma_resource_manager_is_threading_enabled(pResourceManager)) { + return ma_async_notification_event_init(&pNotification->backend.e); + } else { + return ma_async_notification_poll_init(&pNotification->backend.p); + } +} + +static void ma_resource_manager_inline_notification_uninit(ma_resource_manager_inline_notification* pNotification) +{ + MA_ASSERT(pNotification != NULL); + + if (ma_resource_manager_is_threading_enabled(pNotification->pResourceManager)) { + ma_async_notification_event_uninit(&pNotification->backend.e); + } else { + /* No need to uninitialize a polling notification. */ + } +} + +static void ma_resource_manager_inline_notification_wait(ma_resource_manager_inline_notification* pNotification) +{ + MA_ASSERT(pNotification != NULL); + + if (ma_resource_manager_is_threading_enabled(pNotification->pResourceManager)) { + ma_async_notification_event_wait(&pNotification->backend.e); + } else { + while (ma_async_notification_poll_is_signalled(&pNotification->backend.p) == MA_FALSE) { + ma_result result = ma_resource_manager_process_next_job(pNotification->pResourceManager); + if (result == MA_NO_DATA_AVAILABLE || result == MA_CANCELLED) { + break; + } + } + } +} + +static void ma_resource_manager_inline_notification_wait_and_uninit(ma_resource_manager_inline_notification* pNotification) +{ + ma_resource_manager_inline_notification_wait(pNotification); + ma_resource_manager_inline_notification_uninit(pNotification); +} + + +static void ma_resource_manager_data_buffer_bst_lock(ma_resource_manager* pResourceManager) +{ + MA_ASSERT(pResourceManager != NULL); + + if (ma_resource_manager_is_threading_enabled(pResourceManager)) { + #ifndef MA_NO_THREADING + { + ma_mutex_lock(&pResourceManager->dataBufferBSTLock); + } + #else + { + MA_ASSERT(MA_FALSE); /* Should never hit this. */ + } + #endif + } else { + /* Threading not enabled. Do nothing. */ + } +} + +static void ma_resource_manager_data_buffer_bst_unlock(ma_resource_manager* pResourceManager) +{ + MA_ASSERT(pResourceManager != NULL); + + if (ma_resource_manager_is_threading_enabled(pResourceManager)) { + #ifndef MA_NO_THREADING + { + ma_mutex_unlock(&pResourceManager->dataBufferBSTLock); + } + #else + { + MA_ASSERT(MA_FALSE); /* Should never hit this. */ + } + #endif + } else { + /* Threading not enabled. Do nothing. */ + } +} + +#ifndef MA_NO_THREADING +static ma_thread_result MA_THREADCALL ma_resource_manager_job_thread(void* pUserData) +{ + ma_resource_manager* pResourceManager = (ma_resource_manager*)pUserData; + MA_ASSERT(pResourceManager != NULL); + + for (;;) { + ma_result result; + ma_job job; + + result = ma_resource_manager_next_job(pResourceManager, &job); + if (result != MA_SUCCESS) { + break; + } + + /* Terminate if we got a quit message. */ + if (job.toc.breakup.code == MA_JOB_TYPE_QUIT) { + break; + } + + ma_job_process(&job); + } + + return (ma_thread_result)0; +} +#endif + +MA_API ma_resource_manager_config ma_resource_manager_config_init(void) +{ + ma_resource_manager_config config; + + MA_ZERO_OBJECT(&config); + config.decodedFormat = ma_format_unknown; + config.decodedChannels = 0; + config.decodedSampleRate = 0; + config.jobThreadCount = 1; /* A single miniaudio-managed job thread by default. */ + config.jobQueueCapacity = MA_JOB_TYPE_RESOURCE_MANAGER_QUEUE_CAPACITY; + + /* Flags. */ + config.flags = 0; + #ifdef MA_NO_THREADING + { + /* Threading is disabled at compile time so disable threading at runtime as well by default. */ + config.flags |= MA_RESOURCE_MANAGER_FLAG_NO_THREADING; + config.jobThreadCount = 0; + } + #endif + + return config; +} + + +MA_API ma_result ma_resource_manager_init(const ma_resource_manager_config* pConfig, ma_resource_manager* pResourceManager) +{ + ma_result result; + ma_job_queue_config jobQueueConfig; + + if (pResourceManager == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pResourceManager); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + #ifndef MA_NO_THREADING + { + if (pConfig->jobThreadCount > ma_countof(pResourceManager->jobThreads)) { + return MA_INVALID_ARGS; /* Requesting too many job threads. */ + } + } + #endif + + pResourceManager->config = *pConfig; + ma_allocation_callbacks_init_copy(&pResourceManager->config.allocationCallbacks, &pConfig->allocationCallbacks); + + /* Get the log set up early so we can start using it as soon as possible. */ + if (pResourceManager->config.pLog == NULL) { + result = ma_log_init(&pResourceManager->config.allocationCallbacks, &pResourceManager->log); + if (result == MA_SUCCESS) { + pResourceManager->config.pLog = &pResourceManager->log; + } else { + pResourceManager->config.pLog = NULL; /* Logging is unavailable. */ + } + } + + if (pResourceManager->config.pVFS == NULL) { + result = ma_default_vfs_init(&pResourceManager->defaultVFS, &pResourceManager->config.allocationCallbacks); + if (result != MA_SUCCESS) { + return result; /* Failed to initialize the default file system. */ + } + + pResourceManager->config.pVFS = &pResourceManager->defaultVFS; + } + + /* If threading has been disabled at compile time, enfore it at run time as well. */ + #ifdef MA_NO_THREADING + { + pResourceManager->config.flags |= MA_RESOURCE_MANAGER_FLAG_NO_THREADING; + } + #endif + + /* We need to force MA_RESOURCE_MANAGER_FLAG_NON_BLOCKING if MA_RESOURCE_MANAGER_FLAG_NO_THREADING is set. */ + if ((pResourceManager->config.flags & MA_RESOURCE_MANAGER_FLAG_NO_THREADING) != 0) { + pResourceManager->config.flags |= MA_RESOURCE_MANAGER_FLAG_NON_BLOCKING; + + /* We cannot allow job threads when MA_RESOURCE_MANAGER_FLAG_NO_THREADING has been set. This is an invalid use case. */ + if (pResourceManager->config.jobThreadCount > 0) { + return MA_INVALID_ARGS; + } + } + + /* Job queue. */ + jobQueueConfig.capacity = pResourceManager->config.jobQueueCapacity; + jobQueueConfig.flags = 0; + if ((pResourceManager->config.flags & MA_RESOURCE_MANAGER_FLAG_NON_BLOCKING) != 0) { + if (pResourceManager->config.jobThreadCount > 0) { + return MA_INVALID_ARGS; /* Non-blocking mode is only valid for self-managed job threads. */ + } + + jobQueueConfig.flags |= MA_JOB_QUEUE_FLAG_NON_BLOCKING; + } + + result = ma_job_queue_init(&jobQueueConfig, &pResourceManager->config.allocationCallbacks, &pResourceManager->jobQueue); + if (result != MA_SUCCESS) { + return result; + } + + + /* Custom decoding backends. */ + if (pConfig->ppCustomDecodingBackendVTables != NULL && pConfig->customDecodingBackendCount > 0) { + size_t sizeInBytes = sizeof(*pResourceManager->config.ppCustomDecodingBackendVTables) * pConfig->customDecodingBackendCount; + + pResourceManager->config.ppCustomDecodingBackendVTables = (ma_decoding_backend_vtable**)ma_malloc(sizeInBytes, &pResourceManager->config.allocationCallbacks); + if (pResourceManager->config.ppCustomDecodingBackendVTables == NULL) { + ma_job_queue_uninit(&pResourceManager->jobQueue, &pResourceManager->config.allocationCallbacks); + return MA_OUT_OF_MEMORY; + } + + MA_COPY_MEMORY(pResourceManager->config.ppCustomDecodingBackendVTables, pConfig->ppCustomDecodingBackendVTables, sizeInBytes); + + pResourceManager->config.customDecodingBackendCount = pConfig->customDecodingBackendCount; + pResourceManager->config.pCustomDecodingBackendUserData = pConfig->pCustomDecodingBackendUserData; + } + + + + /* Here is where we initialize our threading stuff. We don't do this if we don't support threading. */ + if (ma_resource_manager_is_threading_enabled(pResourceManager)) { + #ifndef MA_NO_THREADING + { + ma_uint32 iJobThread; + + /* Data buffer lock. */ + result = ma_mutex_init(&pResourceManager->dataBufferBSTLock); + if (result != MA_SUCCESS) { + ma_job_queue_uninit(&pResourceManager->jobQueue, &pResourceManager->config.allocationCallbacks); + return result; + } + + /* Create the job threads last to ensure the threads has access to valid data. */ + for (iJobThread = 0; iJobThread < pResourceManager->config.jobThreadCount; iJobThread += 1) { + result = ma_thread_create(&pResourceManager->jobThreads[iJobThread], ma_thread_priority_normal, 0, ma_resource_manager_job_thread, pResourceManager, &pResourceManager->config.allocationCallbacks); + if (result != MA_SUCCESS) { + ma_mutex_uninit(&pResourceManager->dataBufferBSTLock); + ma_job_queue_uninit(&pResourceManager->jobQueue, &pResourceManager->config.allocationCallbacks); + return result; + } + } + } + #else + { + /* Threading is disabled at compile time. We should never get here because validation checks should have already been performed. */ + MA_ASSERT(MA_FALSE); + } + #endif + } + + return MA_SUCCESS; +} + + +static void ma_resource_manager_delete_all_data_buffer_nodes(ma_resource_manager* pResourceManager) +{ + MA_ASSERT(pResourceManager); + + /* If everything was done properly, there shouldn't be any active data buffers. */ + while (pResourceManager->pRootDataBufferNode != NULL) { + ma_resource_manager_data_buffer_node* pDataBufferNode = pResourceManager->pRootDataBufferNode; + ma_resource_manager_data_buffer_node_remove(pResourceManager, pDataBufferNode); + + /* The data buffer has been removed from the BST, so now we need to free it's data. */ + ma_resource_manager_data_buffer_node_free(pResourceManager, pDataBufferNode); + } +} + +MA_API void ma_resource_manager_uninit(ma_resource_manager* pResourceManager) +{ + if (pResourceManager == NULL) { + return; + } + + /* + Job threads need to be killed first. To do this we need to post a quit message to the message queue and then wait for the thread. The quit message will never be removed from the + queue which means it will never not be returned after being encounted for the first time which means all threads will eventually receive it. + */ + ma_resource_manager_post_job_quit(pResourceManager); + + /* Wait for every job to finish before continuing to ensure nothing is sill trying to access any of our objects below. */ + if (ma_resource_manager_is_threading_enabled(pResourceManager)) { + #ifndef MA_NO_THREADING + { + ma_uint32 iJobThread; + + for (iJobThread = 0; iJobThread < pResourceManager->config.jobThreadCount; iJobThread += 1) { + ma_thread_wait(&pResourceManager->jobThreads[iJobThread]); + } + } + #else + { + MA_ASSERT(MA_FALSE); /* Should never hit this. */ + } + #endif + } + + /* At this point the thread should have returned and no other thread should be accessing our data. We can now delete all data buffers. */ + ma_resource_manager_delete_all_data_buffer_nodes(pResourceManager); + + /* The job queue is no longer needed. */ + ma_job_queue_uninit(&pResourceManager->jobQueue, &pResourceManager->config.allocationCallbacks); + + /* We're no longer doing anything with data buffers so the lock can now be uninitialized. */ + if (ma_resource_manager_is_threading_enabled(pResourceManager)) { + #ifndef MA_NO_THREADING + { + ma_mutex_uninit(&pResourceManager->dataBufferBSTLock); + } + #else + { + MA_ASSERT(MA_FALSE); /* Should never hit this. */ + } + #endif + } + + ma_free(pResourceManager->config.ppCustomDecodingBackendVTables, &pResourceManager->config.allocationCallbacks); + + if (pResourceManager->config.pLog == &pResourceManager->log) { + ma_log_uninit(&pResourceManager->log); + } +} + +MA_API ma_log* ma_resource_manager_get_log(ma_resource_manager* pResourceManager) +{ + if (pResourceManager == NULL) { + return NULL; + } + + return pResourceManager->config.pLog; +} + + + +MA_API ma_resource_manager_data_source_config ma_resource_manager_data_source_config_init(void) +{ + ma_resource_manager_data_source_config config; + + MA_ZERO_OBJECT(&config); + config.rangeEndInPCMFrames = ~((ma_uint64)0); + config.loopPointEndInPCMFrames = ~((ma_uint64)0); + + return config; +} + + +static ma_decoder_config ma_resource_manager__init_decoder_config(ma_resource_manager* pResourceManager) +{ + ma_decoder_config config; + + config = ma_decoder_config_init(pResourceManager->config.decodedFormat, pResourceManager->config.decodedChannels, pResourceManager->config.decodedSampleRate); + config.allocationCallbacks = pResourceManager->config.allocationCallbacks; + config.ppCustomBackendVTables = pResourceManager->config.ppCustomDecodingBackendVTables; + config.customBackendCount = pResourceManager->config.customDecodingBackendCount; + config.pCustomBackendUserData = pResourceManager->config.pCustomDecodingBackendUserData; + + return config; +} + +static ma_result ma_resource_manager__init_decoder(ma_resource_manager* pResourceManager, const char* pFilePath, const wchar_t* pFilePathW, ma_decoder* pDecoder) +{ + ma_result result; + ma_decoder_config config; + + MA_ASSERT(pResourceManager != NULL); + MA_ASSERT(pFilePath != NULL || pFilePathW != NULL); + MA_ASSERT(pDecoder != NULL); + + config = ma_resource_manager__init_decoder_config(pResourceManager); + + if (pFilePath != NULL) { + result = ma_decoder_init_vfs(pResourceManager->config.pVFS, pFilePath, &config, pDecoder); + if (result != MA_SUCCESS) { + ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_WARNING, "Failed to load file \"%s\". %s.\n", pFilePath, ma_result_description(result)); + return result; + } + } else { + result = ma_decoder_init_vfs_w(pResourceManager->config.pVFS, pFilePathW, &config, pDecoder); + if (result != MA_SUCCESS) { + #if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(_MSC_VER) + ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_WARNING, "Failed to load file \"%ls\". %s.\n", pFilePathW, ma_result_description(result)); + #endif + return result; + } + } + + return MA_SUCCESS; +} + +static ma_data_source* ma_resource_manager_data_buffer_get_connector(ma_resource_manager_data_buffer* pDataBuffer) +{ + switch (pDataBuffer->pNode->data.type) + { + case ma_resource_manager_data_supply_type_encoded: return &pDataBuffer->connector.decoder; + case ma_resource_manager_data_supply_type_decoded: return &pDataBuffer->connector.buffer; + case ma_resource_manager_data_supply_type_decoded_paged: return &pDataBuffer->connector.pagedBuffer; + + case ma_resource_manager_data_supply_type_unknown: + default: + { + ma_log_postf(ma_resource_manager_get_log(pDataBuffer->pResourceManager), MA_LOG_LEVEL_ERROR, "Failed to retrieve data buffer connector. Unknown data supply type.\n"); + return NULL; + }; + }; +} + +static ma_result ma_resource_manager_data_buffer_init_connector(ma_resource_manager_data_buffer* pDataBuffer, const ma_resource_manager_data_source_config* pConfig, ma_async_notification* pInitNotification, ma_fence* pInitFence) +{ + ma_result result; + + MA_ASSERT(pDataBuffer != NULL); + MA_ASSERT(pConfig != NULL); + MA_ASSERT(pDataBuffer->isConnectorInitialized == MA_FALSE); + + /* The underlying data buffer must be initialized before we'll be able to know how to initialize the backend. */ + result = ma_resource_manager_data_buffer_node_result(pDataBuffer->pNode); + if (result != MA_SUCCESS && result != MA_BUSY) { + return result; /* The data buffer is in an erroneous state. */ + } + + /* + We need to initialize either a ma_decoder or an ma_audio_buffer depending on whether or not the backing data is encoded or decoded. These act as the + "instance" to the data and are used to form the connection between underlying data buffer and the data source. If the data buffer is decoded, we can use + an ma_audio_buffer. This enables us to use memory mapping when mixing which saves us a bit of data movement overhead. + */ + switch (ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBuffer->pNode)) + { + case ma_resource_manager_data_supply_type_encoded: /* Connector is a decoder. */ + { + ma_decoder_config config; + config = ma_resource_manager__init_decoder_config(pDataBuffer->pResourceManager); + result = ma_decoder_init_memory(pDataBuffer->pNode->data.backend.encoded.pData, pDataBuffer->pNode->data.backend.encoded.sizeInBytes, &config, &pDataBuffer->connector.decoder); + } break; + + case ma_resource_manager_data_supply_type_decoded: /* Connector is an audio buffer. */ + { + ma_audio_buffer_config config; + config = ma_audio_buffer_config_init(pDataBuffer->pNode->data.backend.decoded.format, pDataBuffer->pNode->data.backend.decoded.channels, pDataBuffer->pNode->data.backend.decoded.totalFrameCount, pDataBuffer->pNode->data.backend.decoded.pData, NULL); + result = ma_audio_buffer_init(&config, &pDataBuffer->connector.buffer); + } break; + + case ma_resource_manager_data_supply_type_decoded_paged: /* Connector is a paged audio buffer. */ + { + ma_paged_audio_buffer_config config; + config = ma_paged_audio_buffer_config_init(&pDataBuffer->pNode->data.backend.decodedPaged.data); + result = ma_paged_audio_buffer_init(&config, &pDataBuffer->connector.pagedBuffer); + } break; + + case ma_resource_manager_data_supply_type_unknown: + default: + { + /* Unknown data supply type. Should never happen. Need to post an error here. */ + return MA_INVALID_ARGS; + }; + } + + /* + Initialization of the connector is when we can fire the init notification. This will give the application access to + the format/channels/rate of the data source. + */ + if (result == MA_SUCCESS) { + /* + Make sure the looping state is set before returning in order to handle the case where the + loop state was set on the data buffer before the connector was initialized. + */ + ma_data_source_set_range_in_pcm_frames(pDataBuffer, pConfig->rangeBegInPCMFrames, pConfig->rangeEndInPCMFrames); + ma_data_source_set_loop_point_in_pcm_frames(pDataBuffer, pConfig->loopPointBegInPCMFrames, pConfig->loopPointEndInPCMFrames); + ma_data_source_set_looping(pDataBuffer, pConfig->isLooping); + + pDataBuffer->isConnectorInitialized = MA_TRUE; + + if (pInitNotification != NULL) { + ma_async_notification_signal(pInitNotification); + } + + if (pInitFence != NULL) { + ma_fence_release(pInitFence); + } + } + + /* At this point the backend should be initialized. We do *not* want to set pDataSource->result here - that needs to be done at a higher level to ensure it's done as the last step. */ + return result; +} + +static ma_result ma_resource_manager_data_buffer_uninit_connector(ma_resource_manager* pResourceManager, ma_resource_manager_data_buffer* pDataBuffer) +{ + MA_ASSERT(pResourceManager != NULL); + MA_ASSERT(pDataBuffer != NULL); + + switch (ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBuffer->pNode)) + { + case ma_resource_manager_data_supply_type_encoded: /* Connector is a decoder. */ + { + ma_decoder_uninit(&pDataBuffer->connector.decoder); + } break; + + case ma_resource_manager_data_supply_type_decoded: /* Connector is an audio buffer. */ + { + ma_audio_buffer_uninit(&pDataBuffer->connector.buffer); + } break; + + case ma_resource_manager_data_supply_type_decoded_paged: /* Connector is a paged audio buffer. */ + { + ma_paged_audio_buffer_uninit(&pDataBuffer->connector.pagedBuffer); + } break; + + case ma_resource_manager_data_supply_type_unknown: + default: + { + /* Unknown data supply type. Should never happen. Need to post an error here. */ + return MA_INVALID_ARGS; + }; + } + + return MA_SUCCESS; +} + +static ma_uint32 ma_resource_manager_data_buffer_node_next_execution_order(ma_resource_manager_data_buffer_node* pDataBufferNode) +{ + MA_ASSERT(pDataBufferNode != NULL); + return c89atomic_fetch_add_32(&pDataBufferNode->executionCounter, 1); +} + +static ma_result ma_resource_manager_data_buffer_node_init_supply_encoded(ma_resource_manager* pResourceManager, ma_resource_manager_data_buffer_node* pDataBufferNode, const char* pFilePath, const wchar_t* pFilePathW) +{ + ma_result result; + size_t dataSizeInBytes; + void* pData; + + MA_ASSERT(pResourceManager != NULL); + MA_ASSERT(pDataBufferNode != NULL); + MA_ASSERT(pFilePath != NULL || pFilePathW != NULL); + + result = ma_vfs_open_and_read_file_ex(pResourceManager->config.pVFS, pFilePath, pFilePathW, &pData, &dataSizeInBytes, &pResourceManager->config.allocationCallbacks); + if (result != MA_SUCCESS) { + if (pFilePath != NULL) { + ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_WARNING, "Failed to load file \"%s\". %s.\n", pFilePath, ma_result_description(result)); + } else { + #if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(_MSC_VER) + ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_WARNING, "Failed to load file \"%ls\". %s.\n", pFilePathW, ma_result_description(result)); + #endif + } + + return result; + } + + pDataBufferNode->data.backend.encoded.pData = pData; + pDataBufferNode->data.backend.encoded.sizeInBytes = dataSizeInBytes; + ma_resource_manager_data_buffer_node_set_data_supply_type(pDataBufferNode, ma_resource_manager_data_supply_type_encoded); /* <-- Must be set last. */ + + return MA_SUCCESS; +} + +static ma_result ma_resource_manager_data_buffer_node_init_supply_decoded(ma_resource_manager* pResourceManager, ma_resource_manager_data_buffer_node* pDataBufferNode, const char* pFilePath, const wchar_t* pFilePathW, ma_uint32 flags, ma_decoder** ppDecoder) +{ + ma_result result = MA_SUCCESS; + ma_decoder* pDecoder; + ma_uint64 totalFrameCount; + + MA_ASSERT(pResourceManager != NULL); + MA_ASSERT(pDataBufferNode != NULL); + MA_ASSERT(ppDecoder != NULL); + MA_ASSERT(pFilePath != NULL || pFilePathW != NULL); + + *ppDecoder = NULL; /* For safety. */ + + pDecoder = (ma_decoder*)ma_malloc(sizeof(*pDecoder), &pResourceManager->config.allocationCallbacks); + if (pDecoder == NULL) { + return MA_OUT_OF_MEMORY; + } + + result = ma_resource_manager__init_decoder(pResourceManager, pFilePath, pFilePathW, pDecoder); + if (result != MA_SUCCESS) { + ma_free(pDecoder, &pResourceManager->config.allocationCallbacks); + return result; + } + + /* + At this point we have the decoder and we now need to initialize the data supply. This will + be either a decoded buffer, or a decoded paged buffer. A regular buffer is just one big heap + allocated buffer, whereas a paged buffer is a linked list of paged-sized buffers. The latter + is used when the length of a sound is unknown until a full decode has been performed. + */ + if ((flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_UNKNOWN_LENGTH) == 0) { + result = ma_decoder_get_length_in_pcm_frames(pDecoder, &totalFrameCount); + if (result != MA_SUCCESS) { + return result; + } + } else { + totalFrameCount = 0; + } + + if (totalFrameCount > 0) { + /* It's a known length. The data supply is a regular decoded buffer. */ + ma_uint64 dataSizeInBytes; + void* pData; + + dataSizeInBytes = totalFrameCount * ma_get_bytes_per_frame(pDecoder->outputFormat, pDecoder->outputChannels); + if (dataSizeInBytes > MA_SIZE_MAX) { + ma_decoder_uninit(pDecoder); + ma_free(pDecoder, &pResourceManager->config.allocationCallbacks); + return MA_TOO_BIG; + } + + pData = ma_malloc((size_t)dataSizeInBytes, &pResourceManager->config.allocationCallbacks); + if (pData == NULL) { + ma_decoder_uninit(pDecoder); + ma_free(pDecoder, &pResourceManager->config.allocationCallbacks); + return MA_OUT_OF_MEMORY; + } + + /* The buffer needs to be initialized to silence in case the caller reads from it. */ + ma_silence_pcm_frames(pData, totalFrameCount, pDecoder->outputFormat, pDecoder->outputChannels); + + /* Data has been allocated and the data supply can now be initialized. */ + pDataBufferNode->data.backend.decoded.pData = pData; + pDataBufferNode->data.backend.decoded.totalFrameCount = totalFrameCount; + pDataBufferNode->data.backend.decoded.format = pDecoder->outputFormat; + pDataBufferNode->data.backend.decoded.channels = pDecoder->outputChannels; + pDataBufferNode->data.backend.decoded.sampleRate = pDecoder->outputSampleRate; + pDataBufferNode->data.backend.decoded.decodedFrameCount = 0; + ma_resource_manager_data_buffer_node_set_data_supply_type(pDataBufferNode, ma_resource_manager_data_supply_type_decoded); /* <-- Must be set last. */ + } else { + /* + It's an unknown length. The data supply is a paged decoded buffer. Setting this up is + actually easier than the non-paged decoded buffer because we just need to initialize + a ma_paged_audio_buffer object. + */ + result = ma_paged_audio_buffer_data_init(pDecoder->outputFormat, pDecoder->outputChannels, &pDataBufferNode->data.backend.decodedPaged.data); + if (result != MA_SUCCESS) { + ma_decoder_uninit(pDecoder); + ma_free(pDecoder, &pResourceManager->config.allocationCallbacks); + return result; + } + + pDataBufferNode->data.backend.decodedPaged.sampleRate = pDecoder->outputSampleRate; + pDataBufferNode->data.backend.decodedPaged.decodedFrameCount = 0; + ma_resource_manager_data_buffer_node_set_data_supply_type(pDataBufferNode, ma_resource_manager_data_supply_type_decoded_paged); /* <-- Must be set last. */ + } + + *ppDecoder = pDecoder; + + return MA_SUCCESS; +} + +static ma_result ma_resource_manager_data_buffer_node_decode_next_page(ma_resource_manager* pResourceManager, ma_resource_manager_data_buffer_node* pDataBufferNode, ma_decoder* pDecoder) +{ + ma_result result = MA_SUCCESS; + ma_uint64 pageSizeInFrames; + ma_uint64 framesToTryReading; + ma_uint64 framesRead; + + MA_ASSERT(pResourceManager != NULL); + MA_ASSERT(pDataBufferNode != NULL); + MA_ASSERT(pDecoder != NULL); + + /* We need to know the size of a page in frames to know how many frames to decode. */ + pageSizeInFrames = MA_RESOURCE_MANAGER_PAGE_SIZE_IN_MILLISECONDS * (pDecoder->outputSampleRate/1000); + framesToTryReading = pageSizeInFrames; + + /* + Here is where we do the decoding of the next page. We'll run a slightly different path depending + on whether or not we're using a flat or paged buffer because the allocation of the page differs + between the two. For a flat buffer it's an offset to an already-allocated buffer. For a paged + buffer, we need to allocate a new page and attach it to the linked list. + */ + switch (ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBufferNode)) + { + case ma_resource_manager_data_supply_type_decoded: + { + /* The destination buffer is an offset to the existing buffer. Don't read more than we originally retrieved when we first initialized the decoder. */ + void* pDst; + ma_uint64 framesRemaining = pDataBufferNode->data.backend.decoded.totalFrameCount - pDataBufferNode->data.backend.decoded.decodedFrameCount; + if (framesToTryReading > framesRemaining) { + framesToTryReading = framesRemaining; + } + + if (framesToTryReading > 0) { + pDst = ma_offset_ptr( + pDataBufferNode->data.backend.decoded.pData, + pDataBufferNode->data.backend.decoded.decodedFrameCount * ma_get_bytes_per_frame(pDataBufferNode->data.backend.decoded.format, pDataBufferNode->data.backend.decoded.channels) + ); + MA_ASSERT(pDst != NULL); + + result = ma_decoder_read_pcm_frames(pDecoder, pDst, framesToTryReading, &framesRead); + if (framesRead > 0) { + pDataBufferNode->data.backend.decoded.decodedFrameCount += framesRead; + } + } else { + framesRead = 0; + } + } break; + + case ma_resource_manager_data_supply_type_decoded_paged: + { + /* The destination buffer is a freshly allocated page. */ + ma_paged_audio_buffer_page* pPage; + + result = ma_paged_audio_buffer_data_allocate_page(&pDataBufferNode->data.backend.decodedPaged.data, framesToTryReading, NULL, &pResourceManager->config.allocationCallbacks, &pPage); + if (result != MA_SUCCESS) { + return result; + } + + result = ma_decoder_read_pcm_frames(pDecoder, pPage->pAudioData, framesToTryReading, &framesRead); + if (framesRead > 0) { + pPage->sizeInFrames = framesRead; + + result = ma_paged_audio_buffer_data_append_page(&pDataBufferNode->data.backend.decodedPaged.data, pPage); + if (result == MA_SUCCESS) { + pDataBufferNode->data.backend.decodedPaged.decodedFrameCount += framesRead; + } else { + /* Failed to append the page. Just abort and set the status to MA_AT_END. */ + ma_paged_audio_buffer_data_free_page(&pDataBufferNode->data.backend.decodedPaged.data, pPage, &pResourceManager->config.allocationCallbacks); + result = MA_AT_END; + } + } else { + /* No frames were read. Free the page and just set the status to MA_AT_END. */ + ma_paged_audio_buffer_data_free_page(&pDataBufferNode->data.backend.decodedPaged.data, pPage, &pResourceManager->config.allocationCallbacks); + result = MA_AT_END; + } + } break; + + case ma_resource_manager_data_supply_type_encoded: + case ma_resource_manager_data_supply_type_unknown: + default: + { + /* Unexpected data supply type. */ + ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_ERROR, "Unexpected data supply type (%d) when decoding page.", ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBufferNode)); + return MA_ERROR; + }; + } + + if (result == MA_SUCCESS && framesRead == 0) { + result = MA_AT_END; + } + + return result; +} + +static ma_result ma_resource_manager_data_buffer_node_acquire_critical_section(ma_resource_manager* pResourceManager, const char* pFilePath, const wchar_t* pFilePathW, ma_uint32 hashedName32, ma_uint32 flags, const ma_resource_manager_data_supply* pExistingData, ma_fence* pInitFence, ma_fence* pDoneFence, ma_resource_manager_inline_notification* pInitNotification, ma_resource_manager_data_buffer_node** ppDataBufferNode) +{ + ma_result result = MA_SUCCESS; + ma_resource_manager_data_buffer_node* pDataBufferNode = NULL; + ma_resource_manager_data_buffer_node* pInsertPoint; + + if (ppDataBufferNode != NULL) { + *ppDataBufferNode = NULL; + } + + result = ma_resource_manager_data_buffer_node_insert_point(pResourceManager, hashedName32, &pInsertPoint); + if (result == MA_ALREADY_EXISTS) { + /* The node already exists. We just need to increment the reference count. */ + pDataBufferNode = pInsertPoint; + + result = ma_resource_manager_data_buffer_node_increment_ref(pResourceManager, pDataBufferNode, NULL); + if (result != MA_SUCCESS) { + return result; /* Should never happen. Failed to increment the reference count. */ + } + + result = MA_ALREADY_EXISTS; + goto done; + } else { + /* + The node does not already exist. We need to post a LOAD_DATA_BUFFER_NODE job here. This + needs to be done inside the critical section to ensure an uninitialization of the node + does not occur before initialization on another thread. + */ + pDataBufferNode = (ma_resource_manager_data_buffer_node*)ma_malloc(sizeof(*pDataBufferNode), &pResourceManager->config.allocationCallbacks); + if (pDataBufferNode == NULL) { + return MA_OUT_OF_MEMORY; + } + + MA_ZERO_OBJECT(pDataBufferNode); + pDataBufferNode->hashedName32 = hashedName32; + pDataBufferNode->refCount = 1; /* Always set to 1 by default (this is our first reference). */ + + if (pExistingData == NULL) { + pDataBufferNode->data.type = ma_resource_manager_data_supply_type_unknown; /* <-- We won't know this until we start decoding. */ + pDataBufferNode->result = MA_BUSY; /* Must be set to MA_BUSY before we leave the critical section, so might as well do it now. */ + pDataBufferNode->isDataOwnedByResourceManager = MA_TRUE; + } else { + pDataBufferNode->data = *pExistingData; + pDataBufferNode->result = MA_SUCCESS; /* Not loading asynchronously, so just set the status */ + pDataBufferNode->isDataOwnedByResourceManager = MA_FALSE; + } + + result = ma_resource_manager_data_buffer_node_insert_at(pResourceManager, pDataBufferNode, pInsertPoint); + if (result != MA_SUCCESS) { + ma_free(pDataBufferNode, &pResourceManager->config.allocationCallbacks); + return result; /* Should never happen. Failed to insert the data buffer into the BST. */ + } + + /* + Here is where we'll post the job, but only if we're loading asynchronously. If we're + loading synchronously we'll defer loading to a later stage, outside of the critical + section. + */ + if (pDataBufferNode->isDataOwnedByResourceManager && (flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC) != 0) { + /* Loading asynchronously. Post the job. */ + ma_job job; + char* pFilePathCopy = NULL; + wchar_t* pFilePathWCopy = NULL; + + /* We need a copy of the file path. We should probably make this more efficient, but for now we'll do a transient memory allocation. */ + if (pFilePath != NULL) { + pFilePathCopy = ma_copy_string(pFilePath, &pResourceManager->config.allocationCallbacks); + } else { + pFilePathWCopy = ma_copy_string_w(pFilePathW, &pResourceManager->config.allocationCallbacks); + } + + if (pFilePathCopy == NULL && pFilePathWCopy == NULL) { + ma_resource_manager_data_buffer_node_remove(pResourceManager, pDataBufferNode); + ma_free(pDataBufferNode, &pResourceManager->config.allocationCallbacks); + return MA_OUT_OF_MEMORY; + } + + if ((flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT) != 0) { + ma_resource_manager_inline_notification_init(pResourceManager, pInitNotification); + } + + /* Acquire init and done fences before posting the job. These will be unacquired by the job thread. */ + if (pInitFence != NULL) { ma_fence_acquire(pInitFence); } + if (pDoneFence != NULL) { ma_fence_acquire(pDoneFence); } + + /* We now have everything we need to post the job to the job thread. */ + job = ma_job_init(MA_JOB_TYPE_RESOURCE_MANAGER_LOAD_DATA_BUFFER_NODE); + job.order = ma_resource_manager_data_buffer_node_next_execution_order(pDataBufferNode); + job.data.resourceManager.loadDataBufferNode.pResourceManager = pResourceManager; + job.data.resourceManager.loadDataBufferNode.pDataBufferNode = pDataBufferNode; + job.data.resourceManager.loadDataBufferNode.pFilePath = pFilePathCopy; + job.data.resourceManager.loadDataBufferNode.pFilePathW = pFilePathWCopy; + job.data.resourceManager.loadDataBufferNode.flags = flags; + job.data.resourceManager.loadDataBufferNode.pInitNotification = ((flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT) != 0) ? pInitNotification : NULL; + job.data.resourceManager.loadDataBufferNode.pDoneNotification = NULL; + job.data.resourceManager.loadDataBufferNode.pInitFence = pInitFence; + job.data.resourceManager.loadDataBufferNode.pDoneFence = pDoneFence; + + result = ma_resource_manager_post_job(pResourceManager, &job); + if (result != MA_SUCCESS) { + /* Failed to post job. Probably ran out of memory. */ + ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_ERROR, "Failed to post MA_JOB_TYPE_RESOURCE_MANAGER_LOAD_DATA_BUFFER_NODE job. %s.\n", ma_result_description(result)); + + /* + Fences were acquired before posting the job, but since the job was not able to + be posted, we need to make sure we release them so nothing gets stuck waiting. + */ + if (pInitFence != NULL) { ma_fence_release(pInitFence); } + if (pDoneFence != NULL) { ma_fence_release(pDoneFence); } + + if ((flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT) != 0) { + ma_resource_manager_inline_notification_init(pResourceManager, pInitNotification); + } + + ma_free(pFilePathCopy, &pResourceManager->config.allocationCallbacks); + ma_free(pFilePathWCopy, &pResourceManager->config.allocationCallbacks); + + ma_resource_manager_data_buffer_node_remove(pResourceManager, pDataBufferNode); + ma_free(pDataBufferNode, &pResourceManager->config.allocationCallbacks); + + return result; + } + } + } + +done: + if (ppDataBufferNode != NULL) { + *ppDataBufferNode = pDataBufferNode; + } + + return result; +} + +static ma_result ma_resource_manager_data_buffer_node_acquire(ma_resource_manager* pResourceManager, const char* pFilePath, const wchar_t* pFilePathW, ma_uint32 hashedName32, ma_uint32 flags, const ma_resource_manager_data_supply* pExistingData, ma_fence* pInitFence, ma_fence* pDoneFence, ma_resource_manager_data_buffer_node** ppDataBufferNode) +{ + ma_result result = MA_SUCCESS; + ma_bool32 nodeAlreadyExists = MA_FALSE; + ma_resource_manager_data_buffer_node* pDataBufferNode = NULL; + ma_resource_manager_inline_notification initNotification; /* Used when the WAIT_INIT flag is set. */ + + if (ppDataBufferNode != NULL) { + *ppDataBufferNode = NULL; /* Safety. */ + } + + if (pResourceManager == NULL || (pFilePath == NULL && pFilePathW == NULL && hashedName32 == 0)) { + return MA_INVALID_ARGS; + } + + /* If we're specifying existing data, it must be valid. */ + if (pExistingData != NULL && pExistingData->type == ma_resource_manager_data_supply_type_unknown) { + return MA_INVALID_ARGS; + } + + /* If we don't support threading, remove the ASYNC flag to make the rest of this a bit simpler. */ + if (ma_resource_manager_is_threading_enabled(pResourceManager) == MA_FALSE) { + flags &= ~MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC; + } + + if (hashedName32 == 0) { + if (pFilePath != NULL) { + hashedName32 = ma_hash_string_32(pFilePath); + } else { + hashedName32 = ma_hash_string_w_32(pFilePathW); + } + } + + /* + Here is where we either increment the node's reference count or allocate a new one and add it + to the BST. When allocating a new node, we need to make sure the LOAD_DATA_BUFFER_NODE job is + posted inside the critical section just in case the caller immediately uninitializes the node + as this will ensure the FREE_DATA_BUFFER_NODE job is given an execution order such that the + node is not uninitialized before initialization. + */ + ma_resource_manager_data_buffer_bst_lock(pResourceManager); + { + result = ma_resource_manager_data_buffer_node_acquire_critical_section(pResourceManager, pFilePath, pFilePathW, hashedName32, flags, pExistingData, pInitFence, pDoneFence, &initNotification, &pDataBufferNode); + } + ma_resource_manager_data_buffer_bst_unlock(pResourceManager); + + if (result == MA_ALREADY_EXISTS) { + nodeAlreadyExists = MA_TRUE; + result = MA_SUCCESS; + } else { + if (result != MA_SUCCESS) { + return result; + } + } + + /* + If we're loading synchronously, we'll need to load everything now. When loading asynchronously, + a job will have been posted inside the BST critical section so that an uninitialization can be + allocated an appropriate execution order thereby preventing it from being uninitialized before + the node is initialized by the decoding thread(s). + */ + if (nodeAlreadyExists == MA_FALSE) { /* Don't need to try loading anything if the node already exists. */ + if (pFilePath == NULL && pFilePathW == NULL) { + /* + If this path is hit, it means a buffer is being copied (i.e. initialized from only the + hashed name), but that node has been freed in the meantime, probably from some other + thread. This is an invalid operation. + */ + ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_WARNING, "Cloning data buffer node failed because the source node was released. The source node must remain valid until the cloning has completed.\n"); + result = MA_INVALID_OPERATION; + goto done; + } + + if (pDataBufferNode->isDataOwnedByResourceManager) { + if ((flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC) == 0) { + /* Loading synchronously. Load the sound in it's entirety here. */ + if ((flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE) == 0) { + /* No decoding. This is the simple case - just store the file contents in memory. */ + result = ma_resource_manager_data_buffer_node_init_supply_encoded(pResourceManager, pDataBufferNode, pFilePath, pFilePathW); + if (result != MA_SUCCESS) { + goto done; + } + } else { + /* Decoding. We do this the same way as we do when loading asynchronously. */ + ma_decoder* pDecoder; + result = ma_resource_manager_data_buffer_node_init_supply_decoded(pResourceManager, pDataBufferNode, pFilePath, pFilePathW, flags, &pDecoder); + if (result != MA_SUCCESS) { + goto done; + } + + /* We have the decoder, now decode page by page just like we do when loading asynchronously. */ + for (;;) { + /* Decode next page. */ + result = ma_resource_manager_data_buffer_node_decode_next_page(pResourceManager, pDataBufferNode, pDecoder); + if (result != MA_SUCCESS) { + break; /* Will return MA_AT_END when the last page has been decoded. */ + } + } + + /* Reaching the end needs to be considered successful. */ + if (result == MA_AT_END) { + result = MA_SUCCESS; + } + + /* + At this point the data buffer is either fully decoded or some error occurred. Either + way, the decoder is no longer necessary. + */ + ma_decoder_uninit(pDecoder); + ma_free(pDecoder, &pResourceManager->config.allocationCallbacks); + } + + /* Getting here means we were successful. Make sure the status of the node is updated accordingly. */ + c89atomic_exchange_i32(&pDataBufferNode->result, result); + } else { + /* Loading asynchronously. We may need to wait for initialization. */ + if ((flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT) != 0) { + ma_resource_manager_inline_notification_wait(&initNotification); + } + } + } else { + /* The data is not managed by the resource manager so there's nothing else to do. */ + MA_ASSERT(pExistingData != NULL); + } + } + +done: + /* If we failed to initialize the data buffer we need to free it. */ + if (result != MA_SUCCESS) { + if (nodeAlreadyExists == MA_FALSE) { + ma_resource_manager_data_buffer_node_remove(pResourceManager, pDataBufferNode); + ma_free(pDataBufferNode, &pResourceManager->config.allocationCallbacks); + } + } + + /* + The init notification needs to be uninitialized. This will be used if the node does not already + exist, and we've specified ASYNC | WAIT_INIT. + */ + if (nodeAlreadyExists == MA_FALSE && pDataBufferNode->isDataOwnedByResourceManager && (flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC) != 0) { + if ((flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT) != 0) { + ma_resource_manager_inline_notification_uninit(&initNotification); + } + } + + if (ppDataBufferNode != NULL) { + *ppDataBufferNode = pDataBufferNode; + } + + return result; +} + +static ma_result ma_resource_manager_data_buffer_node_unacquire(ma_resource_manager* pResourceManager, ma_resource_manager_data_buffer_node* pDataBufferNode, const char* pName, const wchar_t* pNameW) +{ + ma_result result = MA_SUCCESS; + ma_uint32 refCount = 0xFFFFFFFF; /* The new reference count of the node after decrementing. Initialize to non-0 to be safe we don't fall into the freeing path. */ + ma_uint32 hashedName32 = 0; + + if (pResourceManager == NULL) { + return MA_INVALID_ARGS; + } + + if (pDataBufferNode == NULL) { + if (pName == NULL && pNameW == NULL) { + return MA_INVALID_ARGS; + } + + if (pName != NULL) { + hashedName32 = ma_hash_string_32(pName); + } else { + hashedName32 = ma_hash_string_w_32(pNameW); + } + } + + /* + The first thing to do is decrement the reference counter of the node. Then, if the reference + count is zero, we need to free the node. If the node is still in the process of loading, we'll + need to post a job to the job queue to free the node. Otherwise we'll just do it here. + */ + ma_resource_manager_data_buffer_bst_lock(pResourceManager); + { + /* Might need to find the node. Must be done inside the critical section. */ + if (pDataBufferNode == NULL) { + result = ma_resource_manager_data_buffer_node_search(pResourceManager, hashedName32, &pDataBufferNode); + if (result != MA_SUCCESS) { + goto stage2; /* Couldn't find the node. */ + } + } + + result = ma_resource_manager_data_buffer_node_decrement_ref(pResourceManager, pDataBufferNode, &refCount); + if (result != MA_SUCCESS) { + goto stage2; /* Should never happen. */ + } + + if (refCount == 0) { + result = ma_resource_manager_data_buffer_node_remove(pResourceManager, pDataBufferNode); + if (result != MA_SUCCESS) { + goto stage2; /* An error occurred when trying to remove the data buffer. This should never happen. */ + } + } + } + ma_resource_manager_data_buffer_bst_unlock(pResourceManager); + +stage2: + if (result != MA_SUCCESS) { + return result; + } + + /* + Here is where we need to free the node. We don't want to do this inside the critical section + above because we want to keep that as small as possible for multi-threaded efficiency. + */ + if (refCount == 0) { + if (ma_resource_manager_data_buffer_node_result(pDataBufferNode) == MA_BUSY) { + /* The sound is still loading. We need to delay the freeing of the node to a safe time. */ + ma_job job; + + /* We need to mark the node as unavailable for the sake of the resource manager worker threads. */ + c89atomic_exchange_i32(&pDataBufferNode->result, MA_UNAVAILABLE); + + job = ma_job_init(MA_JOB_TYPE_RESOURCE_MANAGER_FREE_DATA_BUFFER_NODE); + job.order = ma_resource_manager_data_buffer_node_next_execution_order(pDataBufferNode); + job.data.resourceManager.freeDataBufferNode.pResourceManager = pResourceManager; + job.data.resourceManager.freeDataBufferNode.pDataBufferNode = pDataBufferNode; + + result = ma_resource_manager_post_job(pResourceManager, &job); + if (result != MA_SUCCESS) { + ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_ERROR, "Failed to post MA_JOB_TYPE_RESOURCE_MANAGER_FREE_DATA_BUFFER_NODE job. %s.\n", ma_result_description(result)); + return result; + } + + /* If we don't support threading, process the job queue here. */ + if (ma_resource_manager_is_threading_enabled(pResourceManager) == MA_FALSE) { + while (ma_resource_manager_data_buffer_node_result(pDataBufferNode) == MA_BUSY) { + result = ma_resource_manager_process_next_job(pResourceManager); + if (result == MA_NO_DATA_AVAILABLE || result == MA_CANCELLED) { + result = MA_SUCCESS; + break; + } + } + } else { + /* Threading is enabled. The job queue will deal with the rest of the cleanup from here. */ + } + } else { + /* The sound isn't loading so we can just free the node here. */ + ma_resource_manager_data_buffer_node_free(pResourceManager, pDataBufferNode); + } + } + + return result; +} + + + +static ma_uint32 ma_resource_manager_data_buffer_next_execution_order(ma_resource_manager_data_buffer* pDataBuffer) +{ + MA_ASSERT(pDataBuffer != NULL); + return c89atomic_fetch_add_32(&pDataBuffer->executionCounter, 1); +} + +static ma_result ma_resource_manager_data_buffer_cb__read_pcm_frames(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) +{ + return ma_resource_manager_data_buffer_read_pcm_frames((ma_resource_manager_data_buffer*)pDataSource, pFramesOut, frameCount, pFramesRead); +} + +static ma_result ma_resource_manager_data_buffer_cb__seek_to_pcm_frame(ma_data_source* pDataSource, ma_uint64 frameIndex) +{ + return ma_resource_manager_data_buffer_seek_to_pcm_frame((ma_resource_manager_data_buffer*)pDataSource, frameIndex); +} + +static ma_result ma_resource_manager_data_buffer_cb__get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) +{ + return ma_resource_manager_data_buffer_get_data_format((ma_resource_manager_data_buffer*)pDataSource, pFormat, pChannels, pSampleRate, pChannelMap, channelMapCap); +} + +static ma_result ma_resource_manager_data_buffer_cb__get_cursor_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pCursor) +{ + return ma_resource_manager_data_buffer_get_cursor_in_pcm_frames((ma_resource_manager_data_buffer*)pDataSource, pCursor); +} + +static ma_result ma_resource_manager_data_buffer_cb__get_length_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pLength) +{ + return ma_resource_manager_data_buffer_get_length_in_pcm_frames((ma_resource_manager_data_buffer*)pDataSource, pLength); +} + +static ma_result ma_resource_manager_data_buffer_cb__set_looping(ma_data_source* pDataSource, ma_bool32 isLooping) +{ + ma_resource_manager_data_buffer* pDataBuffer = (ma_resource_manager_data_buffer*)pDataSource; + MA_ASSERT(pDataBuffer != NULL); + + c89atomic_exchange_32(&pDataBuffer->isLooping, isLooping); + + /* The looping state needs to be set on the connector as well or else looping won't work when we read audio data. */ + ma_data_source_set_looping(ma_resource_manager_data_buffer_get_connector(pDataBuffer), isLooping); + + return MA_SUCCESS; +} + +static ma_data_source_vtable g_ma_resource_manager_data_buffer_vtable = +{ + ma_resource_manager_data_buffer_cb__read_pcm_frames, + ma_resource_manager_data_buffer_cb__seek_to_pcm_frame, + ma_resource_manager_data_buffer_cb__get_data_format, + ma_resource_manager_data_buffer_cb__get_cursor_in_pcm_frames, + ma_resource_manager_data_buffer_cb__get_length_in_pcm_frames, + ma_resource_manager_data_buffer_cb__set_looping, + 0 +}; + +static ma_result ma_resource_manager_data_buffer_init_ex_internal(ma_resource_manager* pResourceManager, const ma_resource_manager_data_source_config* pConfig, ma_uint32 hashedName32, ma_resource_manager_data_buffer* pDataBuffer) +{ + ma_result result = MA_SUCCESS; + ma_resource_manager_data_buffer_node* pDataBufferNode; + ma_data_source_config dataSourceConfig; + ma_bool32 async; + ma_uint32 flags; + ma_resource_manager_pipeline_notifications notifications; + + if (pDataBuffer == NULL) { + if (pConfig != NULL && pConfig->pNotifications != NULL) { + ma_resource_manager_pipeline_notifications_signal_all_notifications(pConfig->pNotifications); + } + + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pDataBuffer); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->pNotifications != NULL) { + notifications = *pConfig->pNotifications; /* From here on out we should be referencing `notifications` instead of `pNotifications`. Set this to NULL to catch errors at testing time. */ + } else { + MA_ZERO_OBJECT(¬ifications); + } + + /* For safety, always remove the ASYNC flag if threading is disabled on the resource manager. */ + flags = pConfig->flags; + if (ma_resource_manager_is_threading_enabled(pResourceManager) == MA_FALSE) { + flags &= ~MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC; + } + + async = (flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC) != 0; + + /* + Fences need to be acquired before doing anything. These must be aquired and released outside of + the node to ensure there's no holes where ma_fence_wait() could prematurely return before the + data buffer has completed initialization. + + When loading asynchronously, the node acquisition routine below will acquire the fences on this + thread and then release them on the async thread when the operation is complete. + + These fences are always released at the "done" tag at the end of this function. They'll be + acquired a second if loading asynchronously. This double acquisition system is just done to + simplify code maintanence. + */ + ma_resource_manager_pipeline_notifications_acquire_all_fences(¬ifications); + { + /* We first need to acquire a node. If ASYNC is not set, this will not return until the entire sound has been loaded. */ + result = ma_resource_manager_data_buffer_node_acquire(pResourceManager, pConfig->pFilePath, pConfig->pFilePathW, hashedName32, flags, NULL, notifications.init.pFence, notifications.done.pFence, &pDataBufferNode); + if (result != MA_SUCCESS) { + ma_resource_manager_pipeline_notifications_signal_all_notifications(¬ifications); + goto done; + } + + dataSourceConfig = ma_data_source_config_init(); + dataSourceConfig.vtable = &g_ma_resource_manager_data_buffer_vtable; + + result = ma_data_source_init(&dataSourceConfig, &pDataBuffer->ds); + if (result != MA_SUCCESS) { + ma_resource_manager_data_buffer_node_unacquire(pResourceManager, pDataBufferNode, NULL, NULL); + ma_resource_manager_pipeline_notifications_signal_all_notifications(¬ifications); + goto done; + } + + pDataBuffer->pResourceManager = pResourceManager; + pDataBuffer->pNode = pDataBufferNode; + pDataBuffer->flags = flags; + pDataBuffer->result = MA_BUSY; /* Always default to MA_BUSY for safety. It'll be overwritten when loading completes or an error occurs. */ + + /* If we're loading asynchronously we need to post a job to the job queue to initialize the connector. */ + if (async == MA_FALSE || ma_resource_manager_data_buffer_node_result(pDataBufferNode) == MA_SUCCESS) { + /* Loading synchronously or the data has already been fully loaded. We can just initialize the connector from here without a job. */ + result = ma_resource_manager_data_buffer_init_connector(pDataBuffer, pConfig, NULL, NULL); + c89atomic_exchange_i32(&pDataBuffer->result, result); + + ma_resource_manager_pipeline_notifications_signal_all_notifications(¬ifications); + goto done; + } else { + /* The node's data supply isn't initialized yet. The caller has requested that we load asynchronously so we need to post a job to do this. */ + ma_job job; + ma_resource_manager_inline_notification initNotification; /* Used when the WAIT_INIT flag is set. */ + + if ((flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT) != 0) { + ma_resource_manager_inline_notification_init(pResourceManager, &initNotification); + } + + /* + The status of the data buffer needs to be set to MA_BUSY before posting the job so that the + worker thread is aware of it's busy state. If the LOAD_DATA_BUFFER job sees a status other + than MA_BUSY, it'll assume an error and fall through to an early exit. + */ + c89atomic_exchange_i32(&pDataBuffer->result, MA_BUSY); + + /* Acquire fences a second time. These will be released by the async thread. */ + ma_resource_manager_pipeline_notifications_acquire_all_fences(¬ifications); + + job = ma_job_init(MA_JOB_TYPE_RESOURCE_MANAGER_LOAD_DATA_BUFFER); + job.order = ma_resource_manager_data_buffer_next_execution_order(pDataBuffer); + job.data.resourceManager.loadDataBuffer.pDataBuffer = pDataBuffer; + job.data.resourceManager.loadDataBuffer.pInitNotification = ((flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT) != 0) ? &initNotification : notifications.init.pNotification; + job.data.resourceManager.loadDataBuffer.pDoneNotification = notifications.done.pNotification; + job.data.resourceManager.loadDataBuffer.pInitFence = notifications.init.pFence; + job.data.resourceManager.loadDataBuffer.pDoneFence = notifications.done.pFence; + job.data.resourceManager.loadDataBuffer.rangeBegInPCMFrames = pConfig->rangeBegInPCMFrames; + job.data.resourceManager.loadDataBuffer.rangeEndInPCMFrames = pConfig->rangeEndInPCMFrames; + job.data.resourceManager.loadDataBuffer.loopPointBegInPCMFrames = pConfig->loopPointBegInPCMFrames; + job.data.resourceManager.loadDataBuffer.loopPointEndInPCMFrames = pConfig->loopPointEndInPCMFrames; + job.data.resourceManager.loadDataBuffer.isLooping = pConfig->isLooping; + + result = ma_resource_manager_post_job(pResourceManager, &job); + if (result != MA_SUCCESS) { + /* We failed to post the job. Most likely there isn't enough room in the queue's buffer. */ + ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_ERROR, "Failed to post MA_JOB_TYPE_RESOURCE_MANAGER_LOAD_DATA_BUFFER job. %s.\n", ma_result_description(result)); + c89atomic_exchange_i32(&pDataBuffer->result, result); + + /* Release the fences after the result has been set on the data buffer. */ + ma_resource_manager_pipeline_notifications_release_all_fences(¬ifications); + } else { + if ((flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT) != 0) { + ma_resource_manager_inline_notification_wait(&initNotification); + + if (notifications.init.pNotification != NULL) { + ma_async_notification_signal(notifications.init.pNotification); + } + + /* NOTE: Do not release the init fence here. It will have been done by the job. */ + + /* Make sure we return an error if initialization failed on the async thread. */ + result = ma_resource_manager_data_buffer_result(pDataBuffer); + if (result == MA_BUSY) { + result = MA_SUCCESS; + } + } + } + + if ((flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT) != 0) { + ma_resource_manager_inline_notification_uninit(&initNotification); + } + } + + if (result != MA_SUCCESS) { + ma_resource_manager_data_buffer_node_unacquire(pResourceManager, pDataBufferNode, NULL, NULL); + goto done; + } + } +done: + if (result == MA_SUCCESS) { + if (pConfig->initialSeekPointInPCMFrames > 0) { + ma_resource_manager_data_buffer_seek_to_pcm_frame(pDataBuffer, pConfig->initialSeekPointInPCMFrames); + } + } + + ma_resource_manager_pipeline_notifications_release_all_fences(¬ifications); + + return result; +} + +MA_API ma_result ma_resource_manager_data_buffer_init_ex(ma_resource_manager* pResourceManager, const ma_resource_manager_data_source_config* pConfig, ma_resource_manager_data_buffer* pDataBuffer) +{ + return ma_resource_manager_data_buffer_init_ex_internal(pResourceManager, pConfig, 0, pDataBuffer); +} + +MA_API ma_result ma_resource_manager_data_buffer_init(ma_resource_manager* pResourceManager, const char* pFilePath, ma_uint32 flags, const ma_resource_manager_pipeline_notifications* pNotifications, ma_resource_manager_data_buffer* pDataBuffer) +{ + ma_resource_manager_data_source_config config; + + config = ma_resource_manager_data_source_config_init(); + config.pFilePath = pFilePath; + config.flags = flags; + config.pNotifications = pNotifications; + + return ma_resource_manager_data_buffer_init_ex(pResourceManager, &config, pDataBuffer); +} + +MA_API ma_result ma_resource_manager_data_buffer_init_w(ma_resource_manager* pResourceManager, const wchar_t* pFilePath, ma_uint32 flags, const ma_resource_manager_pipeline_notifications* pNotifications, ma_resource_manager_data_buffer* pDataBuffer) +{ + ma_resource_manager_data_source_config config; + + config = ma_resource_manager_data_source_config_init(); + config.pFilePathW = pFilePath; + config.flags = flags; + config.pNotifications = pNotifications; + + return ma_resource_manager_data_buffer_init_ex(pResourceManager, &config, pDataBuffer); +} + +MA_API ma_result ma_resource_manager_data_buffer_init_copy(ma_resource_manager* pResourceManager, const ma_resource_manager_data_buffer* pExistingDataBuffer, ma_resource_manager_data_buffer* pDataBuffer) +{ + ma_resource_manager_data_source_config config; + + if (pExistingDataBuffer == NULL) { + return MA_INVALID_ARGS; + } + + MA_ASSERT(pExistingDataBuffer->pNode != NULL); /* <-- If you've triggered this, you've passed in an invalid existing data buffer. */ + + config = ma_resource_manager_data_source_config_init(); + config.flags = pExistingDataBuffer->flags; + + return ma_resource_manager_data_buffer_init_ex_internal(pResourceManager, &config, pExistingDataBuffer->pNode->hashedName32, pDataBuffer); +} + +static ma_result ma_resource_manager_data_buffer_uninit_internal(ma_resource_manager_data_buffer* pDataBuffer) +{ + MA_ASSERT(pDataBuffer != NULL); + + /* The connector should be uninitialized first. */ + ma_resource_manager_data_buffer_uninit_connector(pDataBuffer->pResourceManager, pDataBuffer); + + /* With the connector uninitialized we can unacquire the node. */ + ma_resource_manager_data_buffer_node_unacquire(pDataBuffer->pResourceManager, pDataBuffer->pNode, NULL, NULL); + + /* The base data source needs to be uninitialized as well. */ + ma_data_source_uninit(&pDataBuffer->ds); + + return MA_SUCCESS; +} + +MA_API ma_result ma_resource_manager_data_buffer_uninit(ma_resource_manager_data_buffer* pDataBuffer) +{ + ma_result result; + + if (pDataBuffer == NULL) { + return MA_INVALID_ARGS; + } + + if (ma_resource_manager_data_buffer_result(pDataBuffer) == MA_SUCCESS) { + /* The data buffer can be deleted synchronously. */ + return ma_resource_manager_data_buffer_uninit_internal(pDataBuffer); + } else { + /* + The data buffer needs to be deleted asynchronously because it's still loading. With the status set to MA_UNAVAILABLE, no more pages will + be loaded and the uninitialization should happen fairly quickly. Since the caller owns the data buffer, we need to wait for this event + to get processed before returning. + */ + ma_resource_manager_inline_notification notification; + ma_job job; + + /* + We need to mark the node as unavailable so we don't try reading from it anymore, but also to + let the loading thread know that it needs to abort it's loading procedure. + */ + c89atomic_exchange_i32(&pDataBuffer->result, MA_UNAVAILABLE); + + result = ma_resource_manager_inline_notification_init(pDataBuffer->pResourceManager, ¬ification); + if (result != MA_SUCCESS) { + return result; /* Failed to create the notification. This should rarely, if ever, happen. */ + } + + job = ma_job_init(MA_JOB_TYPE_RESOURCE_MANAGER_FREE_DATA_BUFFER); + job.order = ma_resource_manager_data_buffer_next_execution_order(pDataBuffer); + job.data.resourceManager.freeDataBuffer.pDataBuffer = pDataBuffer; + job.data.resourceManager.freeDataBuffer.pDoneNotification = ¬ification; + job.data.resourceManager.freeDataBuffer.pDoneFence = NULL; + + result = ma_resource_manager_post_job(pDataBuffer->pResourceManager, &job); + if (result != MA_SUCCESS) { + ma_resource_manager_inline_notification_uninit(¬ification); + return result; + } + + ma_resource_manager_inline_notification_wait_and_uninit(¬ification); + } + + return result; +} + +MA_API ma_result ma_resource_manager_data_buffer_read_pcm_frames(ma_resource_manager_data_buffer* pDataBuffer, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) +{ + ma_result result = MA_SUCCESS; + ma_uint64 framesRead = 0; + ma_bool32 isDecodedBufferBusy = MA_FALSE; + + /* Safety. */ + if (pFramesRead != NULL) { + *pFramesRead = 0; + } + + if (frameCount == 0) { + return MA_INVALID_ARGS; + } + + /* + We cannot be using the data buffer after it's been uninitialized. If you trigger this assert it means you're trying to read from the data buffer after + it's been uninitialized or is in the process of uninitializing. + */ + MA_ASSERT(ma_resource_manager_data_buffer_node_result(pDataBuffer->pNode) != MA_UNAVAILABLE); + + /* If the node is not initialized we need to abort with a busy code. */ + if (ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBuffer->pNode) == ma_resource_manager_data_supply_type_unknown) { + return MA_BUSY; /* Still loading. */ + } + + if (pDataBuffer->seekToCursorOnNextRead) { + pDataBuffer->seekToCursorOnNextRead = MA_FALSE; + + result = ma_data_source_seek_to_pcm_frame(ma_resource_manager_data_buffer_get_connector(pDataBuffer), pDataBuffer->seekTargetInPCMFrames); + if (result != MA_SUCCESS) { + return result; + } + } + + /* + For decoded buffers (not paged) we need to check beforehand how many frames we have available. We cannot + exceed this amount. We'll read as much as we can, and then return MA_BUSY. + */ + if (ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBuffer->pNode) == ma_resource_manager_data_supply_type_decoded) { + ma_uint64 availableFrames; + + isDecodedBufferBusy = (ma_resource_manager_data_buffer_node_result(pDataBuffer->pNode) == MA_BUSY); + + if (ma_resource_manager_data_buffer_get_available_frames(pDataBuffer, &availableFrames) == MA_SUCCESS) { + /* Don't try reading more than the available frame count. */ + if (frameCount > availableFrames) { + frameCount = availableFrames; + + /* + If there's no frames available we want to set the status to MA_AT_END. The logic below + will check if the node is busy, and if so, change it to MA_BUSY. The reason we do this + is because we don't want to call `ma_data_source_read_pcm_frames()` if the frame count + is 0 because that'll result in a situation where it's possible MA_AT_END won't get + returned. + */ + if (frameCount == 0) { + result = MA_AT_END; + } + } else { + isDecodedBufferBusy = MA_FALSE; /* We have enough frames available in the buffer to avoid a MA_BUSY status. */ + } + } + } + + /* Don't attempt to read anything if we've got no frames available. */ + if (frameCount > 0) { + result = ma_data_source_read_pcm_frames(ma_resource_manager_data_buffer_get_connector(pDataBuffer), pFramesOut, frameCount, &framesRead); + } + + /* + If we returned MA_AT_END, but the node is still loading, we don't want to return that code or else the caller will interpret the sound + as at the end and terminate decoding. + */ + if (result == MA_AT_END) { + if (ma_resource_manager_data_buffer_node_result(pDataBuffer->pNode) == MA_BUSY) { + result = MA_BUSY; + } + } + + if (isDecodedBufferBusy) { + result = MA_BUSY; + } + + if (pFramesRead != NULL) { + *pFramesRead = framesRead; + } + + if (result == MA_SUCCESS && framesRead == 0) { + result = MA_AT_END; + } + + return result; +} + +MA_API ma_result ma_resource_manager_data_buffer_seek_to_pcm_frame(ma_resource_manager_data_buffer* pDataBuffer, ma_uint64 frameIndex) +{ + ma_result result; + + /* We cannot be using the data source after it's been uninitialized. */ + MA_ASSERT(ma_resource_manager_data_buffer_node_result(pDataBuffer->pNode) != MA_UNAVAILABLE); + + /* If we haven't yet got a connector we need to abort. */ + if (ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBuffer->pNode) == ma_resource_manager_data_supply_type_unknown) { + pDataBuffer->seekTargetInPCMFrames = frameIndex; + pDataBuffer->seekToCursorOnNextRead = MA_TRUE; + return MA_BUSY; /* Still loading. */ + } + + result = ma_data_source_seek_to_pcm_frame(ma_resource_manager_data_buffer_get_connector(pDataBuffer), frameIndex); + if (result != MA_SUCCESS) { + return result; + } + + pDataBuffer->seekTargetInPCMFrames = ~(ma_uint64)0; /* <-- For identification purposes. */ + pDataBuffer->seekToCursorOnNextRead = MA_FALSE; + + return MA_SUCCESS; +} + +MA_API ma_result ma_resource_manager_data_buffer_get_data_format(ma_resource_manager_data_buffer* pDataBuffer, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) +{ + /* We cannot be using the data source after it's been uninitialized. */ + MA_ASSERT(ma_resource_manager_data_buffer_node_result(pDataBuffer->pNode) != MA_UNAVAILABLE); + + switch (ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBuffer->pNode)) + { + case ma_resource_manager_data_supply_type_encoded: + { + return ma_data_source_get_data_format(&pDataBuffer->connector.decoder, pFormat, pChannels, pSampleRate, pChannelMap, channelMapCap); + }; + + case ma_resource_manager_data_supply_type_decoded: + { + *pFormat = pDataBuffer->pNode->data.backend.decoded.format; + *pChannels = pDataBuffer->pNode->data.backend.decoded.channels; + *pSampleRate = pDataBuffer->pNode->data.backend.decoded.sampleRate; + ma_channel_map_init_standard(ma_standard_channel_map_default, pChannelMap, channelMapCap, pDataBuffer->pNode->data.backend.decoded.channels); + return MA_SUCCESS; + }; + + case ma_resource_manager_data_supply_type_decoded_paged: + { + *pFormat = pDataBuffer->pNode->data.backend.decodedPaged.data.format; + *pChannels = pDataBuffer->pNode->data.backend.decodedPaged.data.channels; + *pSampleRate = pDataBuffer->pNode->data.backend.decodedPaged.sampleRate; + ma_channel_map_init_standard(ma_standard_channel_map_default, pChannelMap, channelMapCap, pDataBuffer->pNode->data.backend.decoded.channels); + return MA_SUCCESS; + }; + + case ma_resource_manager_data_supply_type_unknown: + { + return MA_BUSY; /* Still loading. */ + }; + + default: + { + /* Unknown supply type. Should never hit this. */ + return MA_INVALID_ARGS; + } + } +} + +MA_API ma_result ma_resource_manager_data_buffer_get_cursor_in_pcm_frames(ma_resource_manager_data_buffer* pDataBuffer, ma_uint64* pCursor) +{ + /* We cannot be using the data source after it's been uninitialized. */ + MA_ASSERT(ma_resource_manager_data_buffer_node_result(pDataBuffer->pNode) != MA_UNAVAILABLE); + + if (pDataBuffer == NULL || pCursor == NULL) { + return MA_INVALID_ARGS; + } + + *pCursor = 0; + + switch (ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBuffer->pNode)) + { + case ma_resource_manager_data_supply_type_encoded: + { + return ma_decoder_get_cursor_in_pcm_frames(&pDataBuffer->connector.decoder, pCursor); + }; + + case ma_resource_manager_data_supply_type_decoded: + { + return ma_audio_buffer_get_cursor_in_pcm_frames(&pDataBuffer->connector.buffer, pCursor); + }; + + case ma_resource_manager_data_supply_type_decoded_paged: + { + return ma_paged_audio_buffer_get_cursor_in_pcm_frames(&pDataBuffer->connector.pagedBuffer, pCursor); + }; + + case ma_resource_manager_data_supply_type_unknown: + { + return MA_BUSY; + }; + + default: + { + return MA_INVALID_ARGS; + } + } +} + +MA_API ma_result ma_resource_manager_data_buffer_get_length_in_pcm_frames(ma_resource_manager_data_buffer* pDataBuffer, ma_uint64* pLength) +{ + /* We cannot be using the data source after it's been uninitialized. */ + MA_ASSERT(ma_resource_manager_data_buffer_node_result(pDataBuffer->pNode) != MA_UNAVAILABLE); + + if (pDataBuffer == NULL || pLength == NULL) { + return MA_INVALID_ARGS; + } + + if (ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBuffer->pNode) == ma_resource_manager_data_supply_type_unknown) { + return MA_BUSY; /* Still loading. */ + } + + return ma_data_source_get_length_in_pcm_frames(ma_resource_manager_data_buffer_get_connector(pDataBuffer), pLength); +} + +MA_API ma_result ma_resource_manager_data_buffer_result(const ma_resource_manager_data_buffer* pDataBuffer) +{ + if (pDataBuffer == NULL) { + return MA_INVALID_ARGS; + } + + return (ma_result)c89atomic_load_i32((ma_result*)&pDataBuffer->result); /* Need a naughty const-cast here. */ +} + +MA_API ma_result ma_resource_manager_data_buffer_set_looping(ma_resource_manager_data_buffer* pDataBuffer, ma_bool32 isLooping) +{ + return ma_data_source_set_looping(pDataBuffer, isLooping); +} + +MA_API ma_bool32 ma_resource_manager_data_buffer_is_looping(const ma_resource_manager_data_buffer* pDataBuffer) +{ + return ma_data_source_is_looping(pDataBuffer); +} + +MA_API ma_result ma_resource_manager_data_buffer_get_available_frames(ma_resource_manager_data_buffer* pDataBuffer, ma_uint64* pAvailableFrames) +{ + if (pAvailableFrames == NULL) { + return MA_INVALID_ARGS; + } + + *pAvailableFrames = 0; + + if (pDataBuffer == NULL) { + return MA_INVALID_ARGS; + } + + if (ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBuffer->pNode) == ma_resource_manager_data_supply_type_unknown) { + if (ma_resource_manager_data_buffer_node_result(pDataBuffer->pNode) == MA_BUSY) { + return MA_BUSY; + } else { + return MA_INVALID_OPERATION; /* No connector. */ + } + } + + switch (ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBuffer->pNode)) + { + case ma_resource_manager_data_supply_type_encoded: + { + return ma_decoder_get_available_frames(&pDataBuffer->connector.decoder, pAvailableFrames); + }; + + case ma_resource_manager_data_supply_type_decoded: + { + return ma_audio_buffer_get_available_frames(&pDataBuffer->connector.buffer, pAvailableFrames); + }; + + case ma_resource_manager_data_supply_type_decoded_paged: + { + ma_uint64 cursor; + ma_paged_audio_buffer_get_cursor_in_pcm_frames(&pDataBuffer->connector.pagedBuffer, &cursor); + + if (pDataBuffer->pNode->data.backend.decodedPaged.decodedFrameCount > cursor) { + *pAvailableFrames = pDataBuffer->pNode->data.backend.decodedPaged.decodedFrameCount - cursor; + } else { + *pAvailableFrames = 0; + } + + return MA_SUCCESS; + }; + + case ma_resource_manager_data_supply_type_unknown: + default: + { + /* Unknown supply type. Should never hit this. */ + return MA_INVALID_ARGS; + } + } +} + +MA_API ma_result ma_resource_manager_register_file(ma_resource_manager* pResourceManager, const char* pFilePath, ma_uint32 flags) +{ + return ma_resource_manager_data_buffer_node_acquire(pResourceManager, pFilePath, NULL, 0, flags, NULL, NULL, NULL, NULL); +} + +MA_API ma_result ma_resource_manager_register_file_w(ma_resource_manager* pResourceManager, const wchar_t* pFilePath, ma_uint32 flags) +{ + return ma_resource_manager_data_buffer_node_acquire(pResourceManager, NULL, pFilePath, 0, flags, NULL, NULL, NULL, NULL); +} + + +static ma_result ma_resource_manager_register_data(ma_resource_manager* pResourceManager, const char* pName, const wchar_t* pNameW, ma_resource_manager_data_supply* pExistingData) +{ + return ma_resource_manager_data_buffer_node_acquire(pResourceManager, pName, pNameW, 0, 0, pExistingData, NULL, NULL, NULL); +} + +static ma_result ma_resource_manager_register_decoded_data_internal(ma_resource_manager* pResourceManager, const char* pName, const wchar_t* pNameW, const void* pData, ma_uint64 frameCount, ma_format format, ma_uint32 channels, ma_uint32 sampleRate) +{ + ma_resource_manager_data_supply data; + data.type = ma_resource_manager_data_supply_type_decoded; + data.backend.decoded.pData = pData; + data.backend.decoded.totalFrameCount = frameCount; + data.backend.decoded.format = format; + data.backend.decoded.channels = channels; + data.backend.decoded.sampleRate = sampleRate; + + return ma_resource_manager_register_data(pResourceManager, pName, pNameW, &data); +} + +MA_API ma_result ma_resource_manager_register_decoded_data(ma_resource_manager* pResourceManager, const char* pName, const void* pData, ma_uint64 frameCount, ma_format format, ma_uint32 channels, ma_uint32 sampleRate) +{ + return ma_resource_manager_register_decoded_data_internal(pResourceManager, pName, NULL, pData, frameCount, format, channels, sampleRate); +} + +MA_API ma_result ma_resource_manager_register_decoded_data_w(ma_resource_manager* pResourceManager, const wchar_t* pName, const void* pData, ma_uint64 frameCount, ma_format format, ma_uint32 channels, ma_uint32 sampleRate) +{ + return ma_resource_manager_register_decoded_data_internal(pResourceManager, NULL, pName, pData, frameCount, format, channels, sampleRate); +} + + +static ma_result ma_resource_manager_register_encoded_data_internal(ma_resource_manager* pResourceManager, const char* pName, const wchar_t* pNameW, const void* pData, size_t sizeInBytes) +{ + ma_resource_manager_data_supply data; + data.type = ma_resource_manager_data_supply_type_encoded; + data.backend.encoded.pData = pData; + data.backend.encoded.sizeInBytes = sizeInBytes; + + return ma_resource_manager_register_data(pResourceManager, pName, pNameW, &data); +} + +MA_API ma_result ma_resource_manager_register_encoded_data(ma_resource_manager* pResourceManager, const char* pName, const void* pData, size_t sizeInBytes) +{ + return ma_resource_manager_register_encoded_data_internal(pResourceManager, pName, NULL, pData, sizeInBytes); +} + +MA_API ma_result ma_resource_manager_register_encoded_data_w(ma_resource_manager* pResourceManager, const wchar_t* pName, const void* pData, size_t sizeInBytes) +{ + return ma_resource_manager_register_encoded_data_internal(pResourceManager, NULL, pName, pData, sizeInBytes); +} + + +MA_API ma_result ma_resource_manager_unregister_file(ma_resource_manager* pResourceManager, const char* pFilePath) +{ + return ma_resource_manager_unregister_data(pResourceManager, pFilePath); +} + +MA_API ma_result ma_resource_manager_unregister_file_w(ma_resource_manager* pResourceManager, const wchar_t* pFilePath) +{ + return ma_resource_manager_unregister_data_w(pResourceManager, pFilePath); +} + +MA_API ma_result ma_resource_manager_unregister_data(ma_resource_manager* pResourceManager, const char* pName) +{ + return ma_resource_manager_data_buffer_node_unacquire(pResourceManager, NULL, pName, NULL); +} + +MA_API ma_result ma_resource_manager_unregister_data_w(ma_resource_manager* pResourceManager, const wchar_t* pName) +{ + return ma_resource_manager_data_buffer_node_unacquire(pResourceManager, NULL, NULL, pName); +} + + +static ma_uint32 ma_resource_manager_data_stream_next_execution_order(ma_resource_manager_data_stream* pDataStream) +{ + MA_ASSERT(pDataStream != NULL); + return c89atomic_fetch_add_32(&pDataStream->executionCounter, 1); +} + +static ma_bool32 ma_resource_manager_data_stream_is_decoder_at_end(const ma_resource_manager_data_stream* pDataStream) +{ + MA_ASSERT(pDataStream != NULL); + return c89atomic_load_32((ma_bool32*)&pDataStream->isDecoderAtEnd); +} + +static ma_uint32 ma_resource_manager_data_stream_seek_counter(const ma_resource_manager_data_stream* pDataStream) +{ + MA_ASSERT(pDataStream != NULL); + return c89atomic_load_32((ma_uint32*)&pDataStream->seekCounter); +} + + +static ma_result ma_resource_manager_data_stream_cb__read_pcm_frames(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) +{ + return ma_resource_manager_data_stream_read_pcm_frames((ma_resource_manager_data_stream*)pDataSource, pFramesOut, frameCount, pFramesRead); +} + +static ma_result ma_resource_manager_data_stream_cb__seek_to_pcm_frame(ma_data_source* pDataSource, ma_uint64 frameIndex) +{ + return ma_resource_manager_data_stream_seek_to_pcm_frame((ma_resource_manager_data_stream*)pDataSource, frameIndex); +} + +static ma_result ma_resource_manager_data_stream_cb__get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) +{ + return ma_resource_manager_data_stream_get_data_format((ma_resource_manager_data_stream*)pDataSource, pFormat, pChannels, pSampleRate, pChannelMap, channelMapCap); +} + +static ma_result ma_resource_manager_data_stream_cb__get_cursor_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pCursor) +{ + return ma_resource_manager_data_stream_get_cursor_in_pcm_frames((ma_resource_manager_data_stream*)pDataSource, pCursor); +} + +static ma_result ma_resource_manager_data_stream_cb__get_length_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pLength) +{ + return ma_resource_manager_data_stream_get_length_in_pcm_frames((ma_resource_manager_data_stream*)pDataSource, pLength); +} + +static ma_result ma_resource_manager_data_stream_cb__set_looping(ma_data_source* pDataSource, ma_bool32 isLooping) +{ + ma_resource_manager_data_stream* pDataStream = (ma_resource_manager_data_stream*)pDataSource; + MA_ASSERT(pDataStream != NULL); + + c89atomic_exchange_32(&pDataStream->isLooping, isLooping); + + return MA_SUCCESS; +} + +static ma_data_source_vtable g_ma_resource_manager_data_stream_vtable = +{ + ma_resource_manager_data_stream_cb__read_pcm_frames, + ma_resource_manager_data_stream_cb__seek_to_pcm_frame, + ma_resource_manager_data_stream_cb__get_data_format, + ma_resource_manager_data_stream_cb__get_cursor_in_pcm_frames, + ma_resource_manager_data_stream_cb__get_length_in_pcm_frames, + ma_resource_manager_data_stream_cb__set_looping, + MA_DATA_SOURCE_SELF_MANAGED_RANGE_AND_LOOP_POINT +}; + +static void ma_resource_manager_data_stream_set_absolute_cursor(ma_resource_manager_data_stream* pDataStream, ma_uint64 absoluteCursor) +{ + /* Loop if possible. */ + if (absoluteCursor > pDataStream->totalLengthInPCMFrames && pDataStream->totalLengthInPCMFrames > 0) { + absoluteCursor = absoluteCursor % pDataStream->totalLengthInPCMFrames; + } + + c89atomic_exchange_64(&pDataStream->absoluteCursor, absoluteCursor); +} + +MA_API ma_result ma_resource_manager_data_stream_init_ex(ma_resource_manager* pResourceManager, const ma_resource_manager_data_source_config* pConfig, ma_resource_manager_data_stream* pDataStream) +{ + ma_result result; + ma_data_source_config dataSourceConfig; + char* pFilePathCopy = NULL; + wchar_t* pFilePathWCopy = NULL; + ma_job job; + ma_bool32 waitBeforeReturning = MA_FALSE; + ma_resource_manager_inline_notification waitNotification; + ma_resource_manager_pipeline_notifications notifications; + + if (pDataStream == NULL) { + if (pConfig != NULL && pConfig->pNotifications != NULL) { + ma_resource_manager_pipeline_notifications_signal_all_notifications(pConfig->pNotifications); + } + + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pDataStream); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->pNotifications != NULL) { + notifications = *pConfig->pNotifications; /* From here on out, `notifications` should be used instead of `pNotifications`. Setting this to NULL to catch any errors at testing time. */ + } else { + MA_ZERO_OBJECT(¬ifications); + } + + dataSourceConfig = ma_data_source_config_init(); + dataSourceConfig.vtable = &g_ma_resource_manager_data_stream_vtable; + + result = ma_data_source_init(&dataSourceConfig, &pDataStream->ds); + if (result != MA_SUCCESS) { + ma_resource_manager_pipeline_notifications_signal_all_notifications(¬ifications); + return result; + } + + pDataStream->pResourceManager = pResourceManager; + pDataStream->flags = pConfig->flags; + pDataStream->result = MA_BUSY; + + ma_data_source_set_range_in_pcm_frames(pDataStream, pConfig->rangeBegInPCMFrames, pConfig->rangeEndInPCMFrames); + ma_data_source_set_loop_point_in_pcm_frames(pDataStream, pConfig->loopPointBegInPCMFrames, pConfig->loopPointEndInPCMFrames); + ma_data_source_set_looping(pDataStream, pConfig->isLooping); + + if (pResourceManager == NULL || (pConfig->pFilePath == NULL && pConfig->pFilePathW == NULL)) { + ma_resource_manager_pipeline_notifications_signal_all_notifications(¬ifications); + return MA_INVALID_ARGS; + } + + /* We want all access to the VFS and the internal decoder to happen on the job thread just to keep things easier to manage for the VFS. */ + + /* We need a copy of the file path. We should probably make this more efficient, but for now we'll do a transient memory allocation. */ + if (pConfig->pFilePath != NULL) { + pFilePathCopy = ma_copy_string(pConfig->pFilePath, &pResourceManager->config.allocationCallbacks); + } else { + pFilePathWCopy = ma_copy_string_w(pConfig->pFilePathW, &pResourceManager->config.allocationCallbacks); + } + + if (pFilePathCopy == NULL && pFilePathWCopy == NULL) { + ma_resource_manager_pipeline_notifications_signal_all_notifications(¬ifications); + return MA_OUT_OF_MEMORY; + } + + /* + We need to check for the presence of MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC. If it's not set, we need to wait before returning. Otherwise we + can return immediately. Likewise, we'll also check for MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT and do the same. + */ + if ((pConfig->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC) == 0 || (pConfig->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT) != 0) { + waitBeforeReturning = MA_TRUE; + ma_resource_manager_inline_notification_init(pResourceManager, &waitNotification); + } + + ma_resource_manager_pipeline_notifications_acquire_all_fences(¬ifications); + + /* Set the absolute cursor to our initial seek position so retrieval of the cursor returns a good value. */ + ma_resource_manager_data_stream_set_absolute_cursor(pDataStream, pConfig->initialSeekPointInPCMFrames); + + /* We now have everything we need to post the job. This is the last thing we need to do from here. The rest will be done by the job thread. */ + job = ma_job_init(MA_JOB_TYPE_RESOURCE_MANAGER_LOAD_DATA_STREAM); + job.order = ma_resource_manager_data_stream_next_execution_order(pDataStream); + job.data.resourceManager.loadDataStream.pDataStream = pDataStream; + job.data.resourceManager.loadDataStream.pFilePath = pFilePathCopy; + job.data.resourceManager.loadDataStream.pFilePathW = pFilePathWCopy; + job.data.resourceManager.loadDataStream.initialSeekPoint = pConfig->initialSeekPointInPCMFrames; + job.data.resourceManager.loadDataStream.pInitNotification = (waitBeforeReturning == MA_TRUE) ? &waitNotification : notifications.init.pNotification; + job.data.resourceManager.loadDataStream.pInitFence = notifications.init.pFence; + result = ma_resource_manager_post_job(pResourceManager, &job); + if (result != MA_SUCCESS) { + ma_resource_manager_pipeline_notifications_signal_all_notifications(¬ifications); + ma_resource_manager_pipeline_notifications_release_all_fences(¬ifications); + + if (waitBeforeReturning) { + ma_resource_manager_inline_notification_uninit(&waitNotification); + } + + ma_free(pFilePathCopy, &pResourceManager->config.allocationCallbacks); + ma_free(pFilePathWCopy, &pResourceManager->config.allocationCallbacks); + return result; + } + + /* Wait if needed. */ + if (waitBeforeReturning) { + ma_resource_manager_inline_notification_wait_and_uninit(&waitNotification); + + if (notifications.init.pNotification != NULL) { + ma_async_notification_signal(notifications.init.pNotification); + } + + /* NOTE: Do not release pInitFence here. That will be done by the job. */ + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_resource_manager_data_stream_init(ma_resource_manager* pResourceManager, const char* pFilePath, ma_uint32 flags, const ma_resource_manager_pipeline_notifications* pNotifications, ma_resource_manager_data_stream* pDataStream) +{ + ma_resource_manager_data_source_config config; + + config = ma_resource_manager_data_source_config_init(); + config.pFilePath = pFilePath; + config.flags = flags; + config.pNotifications = pNotifications; + + return ma_resource_manager_data_stream_init_ex(pResourceManager, &config, pDataStream); +} + +MA_API ma_result ma_resource_manager_data_stream_init_w(ma_resource_manager* pResourceManager, const wchar_t* pFilePath, ma_uint32 flags, const ma_resource_manager_pipeline_notifications* pNotifications, ma_resource_manager_data_stream* pDataStream) +{ + ma_resource_manager_data_source_config config; + + config = ma_resource_manager_data_source_config_init(); + config.pFilePathW = pFilePath; + config.flags = flags; + config.pNotifications = pNotifications; + + return ma_resource_manager_data_stream_init_ex(pResourceManager, &config, pDataStream); +} + +MA_API ma_result ma_resource_manager_data_stream_uninit(ma_resource_manager_data_stream* pDataStream) +{ + ma_resource_manager_inline_notification freeEvent; + ma_job job; + + if (pDataStream == NULL) { + return MA_INVALID_ARGS; + } + + /* The first thing to do is set the result to unavailable. This will prevent future page decoding. */ + c89atomic_exchange_i32(&pDataStream->result, MA_UNAVAILABLE); + + /* + We need to post a job to ensure we're not in the middle or decoding or anything. Because the object is owned by the caller, we'll need + to wait for it to complete before returning which means we need an event. + */ + ma_resource_manager_inline_notification_init(pDataStream->pResourceManager, &freeEvent); + + job = ma_job_init(MA_JOB_TYPE_RESOURCE_MANAGER_FREE_DATA_STREAM); + job.order = ma_resource_manager_data_stream_next_execution_order(pDataStream); + job.data.resourceManager.freeDataStream.pDataStream = pDataStream; + job.data.resourceManager.freeDataStream.pDoneNotification = &freeEvent; + job.data.resourceManager.freeDataStream.pDoneFence = NULL; + ma_resource_manager_post_job(pDataStream->pResourceManager, &job); + + /* We need to wait for the job to finish processing before we return. */ + ma_resource_manager_inline_notification_wait_and_uninit(&freeEvent); + + return MA_SUCCESS; +} + + +static ma_uint32 ma_resource_manager_data_stream_get_page_size_in_frames(ma_resource_manager_data_stream* pDataStream) +{ + MA_ASSERT(pDataStream != NULL); + MA_ASSERT(pDataStream->isDecoderInitialized == MA_TRUE); + + return MA_RESOURCE_MANAGER_PAGE_SIZE_IN_MILLISECONDS * (pDataStream->decoder.outputSampleRate/1000); +} + +static void* ma_resource_manager_data_stream_get_page_data_pointer(ma_resource_manager_data_stream* pDataStream, ma_uint32 pageIndex, ma_uint32 relativeCursor) +{ + MA_ASSERT(pDataStream != NULL); + MA_ASSERT(pDataStream->isDecoderInitialized == MA_TRUE); + MA_ASSERT(pageIndex == 0 || pageIndex == 1); + + return ma_offset_ptr(pDataStream->pPageData, ((ma_resource_manager_data_stream_get_page_size_in_frames(pDataStream) * pageIndex) + relativeCursor) * ma_get_bytes_per_frame(pDataStream->decoder.outputFormat, pDataStream->decoder.outputChannels)); +} + +static void ma_resource_manager_data_stream_fill_page(ma_resource_manager_data_stream* pDataStream, ma_uint32 pageIndex) +{ + ma_result result = MA_SUCCESS; + ma_uint64 pageSizeInFrames; + ma_uint64 totalFramesReadForThisPage = 0; + void* pPageData = ma_resource_manager_data_stream_get_page_data_pointer(pDataStream, pageIndex, 0); + + pageSizeInFrames = ma_resource_manager_data_stream_get_page_size_in_frames(pDataStream); + + /* The decoder needs to inherit the stream's looping and range state. */ + { + ma_uint64 rangeBeg; + ma_uint64 rangeEnd; + ma_uint64 loopPointBeg; + ma_uint64 loopPointEnd; + + ma_data_source_set_looping(&pDataStream->decoder, ma_resource_manager_data_stream_is_looping(pDataStream)); + + ma_data_source_get_range_in_pcm_frames(pDataStream, &rangeBeg, &rangeEnd); + ma_data_source_set_range_in_pcm_frames(&pDataStream->decoder, rangeBeg, rangeEnd); + + ma_data_source_get_loop_point_in_pcm_frames(pDataStream, &loopPointBeg, &loopPointEnd); + ma_data_source_set_loop_point_in_pcm_frames(&pDataStream->decoder, loopPointBeg, loopPointEnd); + } + + /* Just read straight from the decoder. It will deal with ranges and looping for us. */ + result = ma_data_source_read_pcm_frames(&pDataStream->decoder, pPageData, pageSizeInFrames, &totalFramesReadForThisPage); + if (result == MA_AT_END || totalFramesReadForThisPage < pageSizeInFrames) { + c89atomic_exchange_32(&pDataStream->isDecoderAtEnd, MA_TRUE); + } + + c89atomic_exchange_32(&pDataStream->pageFrameCount[pageIndex], (ma_uint32)totalFramesReadForThisPage); + c89atomic_exchange_32(&pDataStream->isPageValid[pageIndex], MA_TRUE); +} + +static void ma_resource_manager_data_stream_fill_pages(ma_resource_manager_data_stream* pDataStream) +{ + ma_uint32 iPage; + + MA_ASSERT(pDataStream != NULL); + + for (iPage = 0; iPage < 2; iPage += 1) { + ma_resource_manager_data_stream_fill_page(pDataStream, iPage); + } +} + + +static ma_result ma_resource_manager_data_stream_map(ma_resource_manager_data_stream* pDataStream, void** ppFramesOut, ma_uint64* pFrameCount) +{ + ma_uint64 framesAvailable; + ma_uint64 frameCount = 0; + + /* We cannot be using the data source after it's been uninitialized. */ + MA_ASSERT(ma_resource_manager_data_stream_result(pDataStream) != MA_UNAVAILABLE); + + if (pFrameCount != NULL) { + frameCount = *pFrameCount; + *pFrameCount = 0; + } + if (ppFramesOut != NULL) { + *ppFramesOut = NULL; + } + + if (pDataStream == NULL || ppFramesOut == NULL || pFrameCount == NULL) { + return MA_INVALID_ARGS; + } + + if (ma_resource_manager_data_stream_result(pDataStream) != MA_SUCCESS) { + return MA_INVALID_OPERATION; + } + + /* Don't attempt to read while we're in the middle of seeking. Tell the caller that we're busy. */ + if (ma_resource_manager_data_stream_seek_counter(pDataStream) > 0) { + return MA_BUSY; + } + + /* If the page we're on is invalid it means we've caught up to the job thread. */ + if (c89atomic_load_32(&pDataStream->isPageValid[pDataStream->currentPageIndex]) == MA_FALSE) { + framesAvailable = 0; + } else { + /* + The page we're on is valid so we must have some frames available. We need to make sure that we don't overflow into the next page, even if it's valid. The reason is + that the unmap process will only post an update for one page at a time. Keeping mapping tied to page boundaries makes this simpler. + */ + ma_uint32 currentPageFrameCount = c89atomic_load_32(&pDataStream->pageFrameCount[pDataStream->currentPageIndex]); + MA_ASSERT(currentPageFrameCount >= pDataStream->relativeCursor); + + framesAvailable = currentPageFrameCount - pDataStream->relativeCursor; + } + + /* If there's no frames available and the result is set to MA_AT_END we need to return MA_AT_END. */ + if (framesAvailable == 0) { + if (ma_resource_manager_data_stream_is_decoder_at_end(pDataStream)) { + return MA_AT_END; + } else { + return MA_BUSY; /* There are no frames available, but we're not marked as EOF so we might have caught up to the job thread. Need to return MA_BUSY and wait for more data. */ + } + } + + MA_ASSERT(framesAvailable > 0); + + if (frameCount > framesAvailable) { + frameCount = framesAvailable; + } + + *ppFramesOut = ma_resource_manager_data_stream_get_page_data_pointer(pDataStream, pDataStream->currentPageIndex, pDataStream->relativeCursor); + *pFrameCount = frameCount; + + return MA_SUCCESS; +} + +static ma_result ma_resource_manager_data_stream_unmap(ma_resource_manager_data_stream* pDataStream, ma_uint64 frameCount) +{ + ma_uint32 newRelativeCursor; + ma_uint32 pageSizeInFrames; + ma_job job; + + /* We cannot be using the data source after it's been uninitialized. */ + MA_ASSERT(ma_resource_manager_data_stream_result(pDataStream) != MA_UNAVAILABLE); + + if (pDataStream == NULL) { + return MA_INVALID_ARGS; + } + + if (ma_resource_manager_data_stream_result(pDataStream) != MA_SUCCESS) { + return MA_INVALID_OPERATION; + } + + /* The frame count should always fit inside a 32-bit integer. */ + if (frameCount > 0xFFFFFFFF) { + return MA_INVALID_ARGS; + } + + pageSizeInFrames = ma_resource_manager_data_stream_get_page_size_in_frames(pDataStream); + + /* The absolute cursor needs to be updated for ma_resource_manager_data_stream_get_cursor_in_pcm_frames(). */ + ma_resource_manager_data_stream_set_absolute_cursor(pDataStream, c89atomic_load_64(&pDataStream->absoluteCursor) + frameCount); + + /* Here is where we need to check if we need to load a new page, and if so, post a job to load it. */ + newRelativeCursor = pDataStream->relativeCursor + (ma_uint32)frameCount; + + /* If the new cursor has flowed over to the next page we need to mark the old one as invalid and post an event for it. */ + if (newRelativeCursor >= pageSizeInFrames) { + newRelativeCursor -= pageSizeInFrames; + + /* Here is where we post the job start decoding. */ + job = ma_job_init(MA_JOB_TYPE_RESOURCE_MANAGER_PAGE_DATA_STREAM); + job.order = ma_resource_manager_data_stream_next_execution_order(pDataStream); + job.data.resourceManager.pageDataStream.pDataStream = pDataStream; + job.data.resourceManager.pageDataStream.pageIndex = pDataStream->currentPageIndex; + + /* The page needs to be marked as invalid so that the public API doesn't try reading from it. */ + c89atomic_exchange_32(&pDataStream->isPageValid[pDataStream->currentPageIndex], MA_FALSE); + + /* Before posting the job we need to make sure we set some state. */ + pDataStream->relativeCursor = newRelativeCursor; + pDataStream->currentPageIndex = (pDataStream->currentPageIndex + 1) & 0x01; + return ma_resource_manager_post_job(pDataStream->pResourceManager, &job); + } else { + /* We haven't moved into a new page so we can just move the cursor forward. */ + pDataStream->relativeCursor = newRelativeCursor; + return MA_SUCCESS; + } +} + + +MA_API ma_result ma_resource_manager_data_stream_read_pcm_frames(ma_resource_manager_data_stream* pDataStream, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) +{ + ma_result result = MA_SUCCESS; + ma_uint64 totalFramesProcessed; + ma_format format; + ma_uint32 channels; + + /* Safety. */ + if (pFramesRead != NULL) { + *pFramesRead = 0; + } + + if (frameCount == 0) { + return MA_INVALID_ARGS; + } + + /* We cannot be using the data source after it's been uninitialized. */ + MA_ASSERT(ma_resource_manager_data_stream_result(pDataStream) != MA_UNAVAILABLE); + + if (pDataStream == NULL) { + return MA_INVALID_ARGS; + } + + if (ma_resource_manager_data_stream_result(pDataStream) != MA_SUCCESS) { + return MA_INVALID_OPERATION; + } + + /* Don't attempt to read while we're in the middle of seeking. Tell the caller that we're busy. */ + if (ma_resource_manager_data_stream_seek_counter(pDataStream) > 0) { + return MA_BUSY; + } + + ma_resource_manager_data_stream_get_data_format(pDataStream, &format, &channels, NULL, NULL, 0); + + /* Reading is implemented in terms of map/unmap. We need to run this in a loop because mapping is clamped against page boundaries. */ + totalFramesProcessed = 0; + while (totalFramesProcessed < frameCount) { + void* pMappedFrames; + ma_uint64 mappedFrameCount; + + mappedFrameCount = frameCount - totalFramesProcessed; + result = ma_resource_manager_data_stream_map(pDataStream, &pMappedFrames, &mappedFrameCount); + if (result != MA_SUCCESS) { + break; + } + + /* Copy the mapped data to the output buffer if we have one. It's allowed for pFramesOut to be NULL in which case a relative forward seek is performed. */ + if (pFramesOut != NULL) { + ma_copy_pcm_frames(ma_offset_pcm_frames_ptr(pFramesOut, totalFramesProcessed, format, channels), pMappedFrames, mappedFrameCount, format, channels); + } + + totalFramesProcessed += mappedFrameCount; + + result = ma_resource_manager_data_stream_unmap(pDataStream, mappedFrameCount); + if (result != MA_SUCCESS) { + break; /* This is really bad - will only get an error here if we failed to post a job to the queue for loading the next page. */ + } + } + + if (pFramesRead != NULL) { + *pFramesRead = totalFramesProcessed; + } + + if (result == MA_SUCCESS && totalFramesProcessed == 0) { + result = MA_AT_END; + } + + return result; +} + +MA_API ma_result ma_resource_manager_data_stream_seek_to_pcm_frame(ma_resource_manager_data_stream* pDataStream, ma_uint64 frameIndex) +{ + ma_job job; + ma_result streamResult; + + streamResult = ma_resource_manager_data_stream_result(pDataStream); + + /* We cannot be using the data source after it's been uninitialized. */ + MA_ASSERT(streamResult != MA_UNAVAILABLE); + + if (pDataStream == NULL) { + return MA_INVALID_ARGS; + } + + if (streamResult != MA_SUCCESS && streamResult != MA_BUSY) { + return MA_INVALID_OPERATION; + } + + /* If we're not already seeking and we're sitting on the same frame, just make this a no-op. */ + if (c89atomic_load_32(&pDataStream->seekCounter) == 0) { + if (c89atomic_load_64(&pDataStream->absoluteCursor) == frameIndex) { + return MA_SUCCESS; + } + } + + + /* Increment the seek counter first to indicate to read_paged_pcm_frames() and map_paged_pcm_frames() that we are in the middle of a seek and MA_BUSY should be returned. */ + c89atomic_fetch_add_32(&pDataStream->seekCounter, 1); + + /* Update the absolute cursor so that ma_resource_manager_data_stream_get_cursor_in_pcm_frames() returns the new position. */ + ma_resource_manager_data_stream_set_absolute_cursor(pDataStream, frameIndex); + + /* + We need to clear our currently loaded pages so that the stream starts playback from the new seek point as soon as possible. These are for the purpose of the public + API and will be ignored by the seek job. The seek job will operate on the assumption that both pages have been marked as invalid and the cursor is at the start of + the first page. + */ + pDataStream->relativeCursor = 0; + pDataStream->currentPageIndex = 0; + c89atomic_exchange_32(&pDataStream->isPageValid[0], MA_FALSE); + c89atomic_exchange_32(&pDataStream->isPageValid[1], MA_FALSE); + + /* Make sure the data stream is not marked as at the end or else if we seek in response to hitting the end, we won't be able to read any more data. */ + c89atomic_exchange_32(&pDataStream->isDecoderAtEnd, MA_FALSE); + + /* + The public API is not allowed to touch the internal decoder so we need to use a job to perform the seek. When seeking, the job thread will assume both pages + are invalid and any content contained within them will be discarded and replaced with newly decoded data. + */ + job = ma_job_init(MA_JOB_TYPE_RESOURCE_MANAGER_SEEK_DATA_STREAM); + job.order = ma_resource_manager_data_stream_next_execution_order(pDataStream); + job.data.resourceManager.seekDataStream.pDataStream = pDataStream; + job.data.resourceManager.seekDataStream.frameIndex = frameIndex; + return ma_resource_manager_post_job(pDataStream->pResourceManager, &job); +} + +MA_API ma_result ma_resource_manager_data_stream_get_data_format(ma_resource_manager_data_stream* pDataStream, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) +{ + /* We cannot be using the data source after it's been uninitialized. */ + MA_ASSERT(ma_resource_manager_data_stream_result(pDataStream) != MA_UNAVAILABLE); + + if (pFormat != NULL) { + *pFormat = ma_format_unknown; + } + + if (pChannels != NULL) { + *pChannels = 0; + } + + if (pSampleRate != NULL) { + *pSampleRate = 0; + } + + if (pChannelMap != NULL) { + MA_ZERO_MEMORY(pChannelMap, sizeof(*pChannelMap) * channelMapCap); + } + + if (pDataStream == NULL) { + return MA_INVALID_ARGS; + } + + if (ma_resource_manager_data_stream_result(pDataStream) != MA_SUCCESS) { + return MA_INVALID_OPERATION; + } + + /* + We're being a little bit naughty here and accessing the internal decoder from the public API. The output data format is constant, and we've defined this function + such that the application is responsible for ensuring it's not called while uninitializing so it should be safe. + */ + return ma_data_source_get_data_format(&pDataStream->decoder, pFormat, pChannels, pSampleRate, pChannelMap, channelMapCap); +} + +MA_API ma_result ma_resource_manager_data_stream_get_cursor_in_pcm_frames(ma_resource_manager_data_stream* pDataStream, ma_uint64* pCursor) +{ + ma_result result; + + if (pCursor == NULL) { + return MA_INVALID_ARGS; + } + + *pCursor = 0; + + /* We cannot be using the data source after it's been uninitialized. */ + MA_ASSERT(ma_resource_manager_data_stream_result(pDataStream) != MA_UNAVAILABLE); + + if (pDataStream == NULL) { + return MA_INVALID_ARGS; + } + + /* + If the stream is in an erroneous state we need to return an invalid operation. We can allow + this to be called when the data stream is in a busy state because the caller may have asked + for an initial seek position and it's convenient to return that as the cursor position. + */ + result = ma_resource_manager_data_stream_result(pDataStream); + if (result != MA_SUCCESS && result != MA_BUSY) { + return MA_INVALID_OPERATION; + } + + *pCursor = c89atomic_load_64(&pDataStream->absoluteCursor); + + return MA_SUCCESS; +} + +MA_API ma_result ma_resource_manager_data_stream_get_length_in_pcm_frames(ma_resource_manager_data_stream* pDataStream, ma_uint64* pLength) +{ + ma_result streamResult; + + if (pLength == NULL) { + return MA_INVALID_ARGS; + } + + *pLength = 0; + + streamResult = ma_resource_manager_data_stream_result(pDataStream); + + /* We cannot be using the data source after it's been uninitialized. */ + MA_ASSERT(streamResult != MA_UNAVAILABLE); + + if (pDataStream == NULL) { + return MA_INVALID_ARGS; + } + + if (streamResult != MA_SUCCESS) { + return streamResult; + } + + /* + We most definitely do not want to be calling ma_decoder_get_length_in_pcm_frames() directly. Instead we want to use a cached value that we + calculated when we initialized it on the job thread. + */ + *pLength = pDataStream->totalLengthInPCMFrames; + if (*pLength == 0) { + return MA_NOT_IMPLEMENTED; /* Some decoders may not have a known length. */ + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_resource_manager_data_stream_result(const ma_resource_manager_data_stream* pDataStream) +{ + if (pDataStream == NULL) { + return MA_INVALID_ARGS; + } + + return (ma_result)c89atomic_load_i32(&pDataStream->result); +} + +MA_API ma_result ma_resource_manager_data_stream_set_looping(ma_resource_manager_data_stream* pDataStream, ma_bool32 isLooping) +{ + return ma_data_source_set_looping(pDataStream, isLooping); +} + +MA_API ma_bool32 ma_resource_manager_data_stream_is_looping(const ma_resource_manager_data_stream* pDataStream) +{ + if (pDataStream == NULL) { + return MA_FALSE; + } + + return c89atomic_load_32((ma_bool32*)&pDataStream->isLooping); /* Naughty const-cast. Value won't change from here in practice (maybe from another thread). */ +} + +MA_API ma_result ma_resource_manager_data_stream_get_available_frames(ma_resource_manager_data_stream* pDataStream, ma_uint64* pAvailableFrames) +{ + ma_uint32 pageIndex0; + ma_uint32 pageIndex1; + ma_uint32 relativeCursor; + ma_uint64 availableFrames; + + if (pAvailableFrames == NULL) { + return MA_INVALID_ARGS; + } + + *pAvailableFrames = 0; + + if (pDataStream == NULL) { + return MA_INVALID_ARGS; + } + + pageIndex0 = pDataStream->currentPageIndex; + pageIndex1 = (pDataStream->currentPageIndex + 1) & 0x01; + relativeCursor = pDataStream->relativeCursor; + + availableFrames = 0; + if (c89atomic_load_32(&pDataStream->isPageValid[pageIndex0])) { + availableFrames += c89atomic_load_32(&pDataStream->pageFrameCount[pageIndex0]) - relativeCursor; + if (c89atomic_load_32(&pDataStream->isPageValid[pageIndex1])) { + availableFrames += c89atomic_load_32(&pDataStream->pageFrameCount[pageIndex1]); + } + } + + *pAvailableFrames = availableFrames; + return MA_SUCCESS; +} + + +static ma_result ma_resource_manager_data_source_preinit(ma_resource_manager* pResourceManager, const ma_resource_manager_data_source_config* pConfig, ma_resource_manager_data_source* pDataSource) +{ + if (pDataSource == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pDataSource); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pResourceManager == NULL) { + return MA_INVALID_ARGS; + } + + pDataSource->flags = pConfig->flags; + + return MA_SUCCESS; +} + +MA_API ma_result ma_resource_manager_data_source_init_ex(ma_resource_manager* pResourceManager, const ma_resource_manager_data_source_config* pConfig, ma_resource_manager_data_source* pDataSource) +{ + ma_result result; + + result = ma_resource_manager_data_source_preinit(pResourceManager, pConfig, pDataSource); + if (result != MA_SUCCESS) { + return result; + } + + /* The data source itself is just a data stream or a data buffer. */ + if ((pConfig->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM) != 0) { + return ma_resource_manager_data_stream_init_ex(pResourceManager, pConfig, &pDataSource->backend.stream); + } else { + return ma_resource_manager_data_buffer_init_ex(pResourceManager, pConfig, &pDataSource->backend.buffer); + } +} + +MA_API ma_result ma_resource_manager_data_source_init(ma_resource_manager* pResourceManager, const char* pName, ma_uint32 flags, const ma_resource_manager_pipeline_notifications* pNotifications, ma_resource_manager_data_source* pDataSource) +{ + ma_resource_manager_data_source_config config; + + config = ma_resource_manager_data_source_config_init(); + config.pFilePath = pName; + config.flags = flags; + config.pNotifications = pNotifications; + + return ma_resource_manager_data_source_init_ex(pResourceManager, &config, pDataSource); +} + +MA_API ma_result ma_resource_manager_data_source_init_w(ma_resource_manager* pResourceManager, const wchar_t* pName, ma_uint32 flags, const ma_resource_manager_pipeline_notifications* pNotifications, ma_resource_manager_data_source* pDataSource) +{ + ma_resource_manager_data_source_config config; + + config = ma_resource_manager_data_source_config_init(); + config.pFilePathW = pName; + config.flags = flags; + config.pNotifications = pNotifications; + + return ma_resource_manager_data_source_init_ex(pResourceManager, &config, pDataSource); +} + +MA_API ma_result ma_resource_manager_data_source_init_copy(ma_resource_manager* pResourceManager, const ma_resource_manager_data_source* pExistingDataSource, ma_resource_manager_data_source* pDataSource) +{ + ma_result result; + ma_resource_manager_data_source_config config; + + if (pExistingDataSource == NULL) { + return MA_INVALID_ARGS; + } + + config = ma_resource_manager_data_source_config_init(); + config.flags = pExistingDataSource->flags; + + result = ma_resource_manager_data_source_preinit(pResourceManager, &config, pDataSource); + if (result != MA_SUCCESS) { + return result; + } + + /* Copying can only be done from data buffers. Streams cannot be copied. */ + if ((pExistingDataSource->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM) != 0) { + return MA_INVALID_OPERATION; + } + + return ma_resource_manager_data_buffer_init_copy(pResourceManager, &pExistingDataSource->backend.buffer, &pDataSource->backend.buffer); +} + +MA_API ma_result ma_resource_manager_data_source_uninit(ma_resource_manager_data_source* pDataSource) +{ + if (pDataSource == NULL) { + return MA_INVALID_ARGS; + } + + /* All we need to is uninitialize the underlying data buffer or data stream. */ + if ((pDataSource->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM) != 0) { + return ma_resource_manager_data_stream_uninit(&pDataSource->backend.stream); + } else { + return ma_resource_manager_data_buffer_uninit(&pDataSource->backend.buffer); + } +} + +MA_API ma_result ma_resource_manager_data_source_read_pcm_frames(ma_resource_manager_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) +{ + /* Safety. */ + if (pFramesRead != NULL) { + *pFramesRead = 0; + } + + if (pDataSource == NULL) { + return MA_INVALID_ARGS; + } + + if ((pDataSource->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM) != 0) { + return ma_resource_manager_data_stream_read_pcm_frames(&pDataSource->backend.stream, pFramesOut, frameCount, pFramesRead); + } else { + return ma_resource_manager_data_buffer_read_pcm_frames(&pDataSource->backend.buffer, pFramesOut, frameCount, pFramesRead); + } +} + +MA_API ma_result ma_resource_manager_data_source_seek_to_pcm_frame(ma_resource_manager_data_source* pDataSource, ma_uint64 frameIndex) +{ + if (pDataSource == NULL) { + return MA_INVALID_ARGS; + } + + if ((pDataSource->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM) != 0) { + return ma_resource_manager_data_stream_seek_to_pcm_frame(&pDataSource->backend.stream, frameIndex); + } else { + return ma_resource_manager_data_buffer_seek_to_pcm_frame(&pDataSource->backend.buffer, frameIndex); + } +} + +MA_API ma_result ma_resource_manager_data_source_map(ma_resource_manager_data_source* pDataSource, void** ppFramesOut, ma_uint64* pFrameCount) +{ + if (pDataSource == NULL) { + return MA_INVALID_ARGS; + } + + if ((pDataSource->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM) != 0) { + return ma_resource_manager_data_stream_map(&pDataSource->backend.stream, ppFramesOut, pFrameCount); + } else { + return MA_NOT_IMPLEMENTED; /* Mapping not supported with data buffers. */ + } +} + +MA_API ma_result ma_resource_manager_data_source_unmap(ma_resource_manager_data_source* pDataSource, ma_uint64 frameCount) +{ + if (pDataSource == NULL) { + return MA_INVALID_ARGS; + } + + if ((pDataSource->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM) != 0) { + return ma_resource_manager_data_stream_unmap(&pDataSource->backend.stream, frameCount); + } else { + return MA_NOT_IMPLEMENTED; /* Mapping not supported with data buffers. */ + } +} + +MA_API ma_result ma_resource_manager_data_source_get_data_format(ma_resource_manager_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) +{ + if (pDataSource == NULL) { + return MA_INVALID_ARGS; + } + + if ((pDataSource->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM) != 0) { + return ma_resource_manager_data_stream_get_data_format(&pDataSource->backend.stream, pFormat, pChannels, pSampleRate, pChannelMap, channelMapCap); + } else { + return ma_resource_manager_data_buffer_get_data_format(&pDataSource->backend.buffer, pFormat, pChannels, pSampleRate, pChannelMap, channelMapCap); + } +} + +MA_API ma_result ma_resource_manager_data_source_get_cursor_in_pcm_frames(ma_resource_manager_data_source* pDataSource, ma_uint64* pCursor) +{ + if (pDataSource == NULL) { + return MA_INVALID_ARGS; + } + + if ((pDataSource->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM) != 0) { + return ma_resource_manager_data_stream_get_cursor_in_pcm_frames(&pDataSource->backend.stream, pCursor); + } else { + return ma_resource_manager_data_buffer_get_cursor_in_pcm_frames(&pDataSource->backend.buffer, pCursor); + } +} + +MA_API ma_result ma_resource_manager_data_source_get_length_in_pcm_frames(ma_resource_manager_data_source* pDataSource, ma_uint64* pLength) +{ + if (pDataSource == NULL) { + return MA_INVALID_ARGS; + } + + if ((pDataSource->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM) != 0) { + return ma_resource_manager_data_stream_get_length_in_pcm_frames(&pDataSource->backend.stream, pLength); + } else { + return ma_resource_manager_data_buffer_get_length_in_pcm_frames(&pDataSource->backend.buffer, pLength); + } +} + +MA_API ma_result ma_resource_manager_data_source_result(const ma_resource_manager_data_source* pDataSource) +{ + if (pDataSource == NULL) { + return MA_INVALID_ARGS; + } + + if ((pDataSource->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM) != 0) { + return ma_resource_manager_data_stream_result(&pDataSource->backend.stream); + } else { + return ma_resource_manager_data_buffer_result(&pDataSource->backend.buffer); + } +} + +MA_API ma_result ma_resource_manager_data_source_set_looping(ma_resource_manager_data_source* pDataSource, ma_bool32 isLooping) +{ + if (pDataSource == NULL) { + return MA_INVALID_ARGS; + } + + if ((pDataSource->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM) != 0) { + return ma_resource_manager_data_stream_set_looping(&pDataSource->backend.stream, isLooping); + } else { + return ma_resource_manager_data_buffer_set_looping(&pDataSource->backend.buffer, isLooping); + } +} + +MA_API ma_bool32 ma_resource_manager_data_source_is_looping(const ma_resource_manager_data_source* pDataSource) +{ + if (pDataSource == NULL) { + return MA_FALSE; + } + + if ((pDataSource->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM) != 0) { + return ma_resource_manager_data_stream_is_looping(&pDataSource->backend.stream); + } else { + return ma_resource_manager_data_buffer_is_looping(&pDataSource->backend.buffer); + } +} + +MA_API ma_result ma_resource_manager_data_source_get_available_frames(ma_resource_manager_data_source* pDataSource, ma_uint64* pAvailableFrames) +{ + if (pAvailableFrames == NULL) { + return MA_INVALID_ARGS; + } + + *pAvailableFrames = 0; + + if (pDataSource == NULL) { + return MA_INVALID_ARGS; + } + + if ((pDataSource->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM) != 0) { + return ma_resource_manager_data_stream_get_available_frames(&pDataSource->backend.stream, pAvailableFrames); + } else { + return ma_resource_manager_data_buffer_get_available_frames(&pDataSource->backend.buffer, pAvailableFrames); + } +} + + +MA_API ma_result ma_resource_manager_post_job(ma_resource_manager* pResourceManager, const ma_job* pJob) +{ + if (pResourceManager == NULL) { + return MA_INVALID_ARGS; + } + + return ma_job_queue_post(&pResourceManager->jobQueue, pJob); +} + +MA_API ma_result ma_resource_manager_post_job_quit(ma_resource_manager* pResourceManager) +{ + ma_job job = ma_job_init(MA_JOB_TYPE_QUIT); + return ma_resource_manager_post_job(pResourceManager, &job); +} + +MA_API ma_result ma_resource_manager_next_job(ma_resource_manager* pResourceManager, ma_job* pJob) +{ + if (pResourceManager == NULL) { + return MA_INVALID_ARGS; + } + + return ma_job_queue_next(&pResourceManager->jobQueue, pJob); +} + + +static ma_result ma_job_process__resource_manager__load_data_buffer_node(ma_job* pJob) +{ + ma_result result = MA_SUCCESS; + ma_resource_manager* pResourceManager; + ma_resource_manager_data_buffer_node* pDataBufferNode; + + MA_ASSERT(pJob != NULL); + + pResourceManager = (ma_resource_manager*)pJob->data.resourceManager.loadDataBufferNode.pResourceManager; + MA_ASSERT(pResourceManager != NULL); + + pDataBufferNode = (ma_resource_manager_data_buffer_node*)pJob->data.resourceManager.loadDataBufferNode.pDataBufferNode; + MA_ASSERT(pDataBufferNode != NULL); + MA_ASSERT(pDataBufferNode->isDataOwnedByResourceManager == MA_TRUE); /* The data should always be owned by the resource manager. */ + + /* The data buffer is not getting deleted, but we may be getting executed out of order. If so, we need to push the job back onto the queue and return. */ + if (pJob->order != c89atomic_load_32(&pDataBufferNode->executionPointer)) { + return ma_resource_manager_post_job(pResourceManager, pJob); /* Attempting to execute out of order. Probably interleaved with a MA_JOB_TYPE_RESOURCE_MANAGER_FREE_DATA_BUFFER job. */ + } + + /* First thing we need to do is check whether or not the data buffer is getting deleted. If so we just abort. */ + if (ma_resource_manager_data_buffer_node_result(pDataBufferNode) != MA_BUSY) { + result = ma_resource_manager_data_buffer_node_result(pDataBufferNode); /* The data buffer may be getting deleted before it's even been loaded. */ + goto done; + } + + /* + We're ready to start loading. Essentially what we're doing here is initializing the data supply + of the node. Once this is complete, data buffers can have their connectors initialized which + will allow then to have audio data read from them. + + Note that when the data supply type has been moved away from "unknown", that is when other threads + will determine that the node is available for data delivery and the data buffer connectors can be + initialized. Therefore, it's important that it is set after the data supply has been initialized. + */ + if ((pJob->data.resourceManager.loadDataBufferNode.flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE) != 0) { + /* + Decoding. This is the complex case because we're not going to be doing the entire decoding + process here. Instead it's going to be split of multiple jobs and loaded in pages. The + reason for this is to evenly distribute decoding time across multiple sounds, rather than + having one huge sound hog all the available processing resources. + + The first thing we do is initialize a decoder. This is allocated on the heap and is passed + around to the paging jobs. When the last paging job has completed it's processing, it'll + free the decoder for us. + + This job does not do any actual decoding. It instead just posts a PAGE_DATA_BUFFER_NODE job + which is where the actual decoding work will be done. However, once this job is complete, + the node will be in a state where data buffer connectors can be initialized. + */ + ma_decoder* pDecoder; /* <-- Free'd on the last page decode. */ + ma_job pageDataBufferNodeJob; + + /* Allocate the decoder by initializing a decoded data supply. */ + result = ma_resource_manager_data_buffer_node_init_supply_decoded(pResourceManager, pDataBufferNode, pJob->data.resourceManager.loadDataBufferNode.pFilePath, pJob->data.resourceManager.loadDataBufferNode.pFilePathW, pJob->data.resourceManager.loadDataBufferNode.flags, &pDecoder); + + /* + Don't ever propagate an MA_BUSY result code or else the resource manager will think the + node is just busy decoding rather than in an error state. This should never happen, but + including this logic for safety just in case. + */ + if (result == MA_BUSY) { + result = MA_ERROR; + } + + if (result != MA_SUCCESS) { + if (pJob->data.resourceManager.loadDataBufferNode.pFilePath != NULL) { + ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_WARNING, "Failed to initialize data supply for \"%s\". %s.\n", pJob->data.resourceManager.loadDataBufferNode.pFilePath, ma_result_description(result)); + } else { + #if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(_MSC_VER) + ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_WARNING, "Failed to initialize data supply for \"%ls\", %s.\n", pJob->data.resourceManager.loadDataBufferNode.pFilePathW, ma_result_description(result)); + #endif + } + + goto done; + } + + /* + At this point the node's data supply is initialized and other threads can start initializing + their data buffer connectors. However, no data will actually be available until we start to + actually decode it. To do this, we need to post a paging job which is where the decoding + work is done. + + Note that if an error occurred at an earlier point, this section will have been skipped. + */ + pageDataBufferNodeJob = ma_job_init(MA_JOB_TYPE_RESOURCE_MANAGER_PAGE_DATA_BUFFER_NODE); + pageDataBufferNodeJob.order = ma_resource_manager_data_buffer_node_next_execution_order(pDataBufferNode); + pageDataBufferNodeJob.data.resourceManager.pageDataBufferNode.pResourceManager = pResourceManager; + pageDataBufferNodeJob.data.resourceManager.pageDataBufferNode.pDataBufferNode = pDataBufferNode; + pageDataBufferNodeJob.data.resourceManager.pageDataBufferNode.pDecoder = pDecoder; + pageDataBufferNodeJob.data.resourceManager.pageDataBufferNode.pDoneNotification = pJob->data.resourceManager.loadDataBufferNode.pDoneNotification; + pageDataBufferNodeJob.data.resourceManager.pageDataBufferNode.pDoneFence = pJob->data.resourceManager.loadDataBufferNode.pDoneFence; + + /* The job has been set up so it can now be posted. */ + result = ma_resource_manager_post_job(pResourceManager, &pageDataBufferNodeJob); + + /* + When we get here, we want to make sure the result code is set to MA_BUSY. The reason for + this is that the result will be copied over to the node's internal result variable. In + this case, since the decoding is still in-progress, we need to make sure the result code + is set to MA_BUSY. + */ + if (result != MA_SUCCESS) { + ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_ERROR, "Failed to post MA_JOB_TYPE_RESOURCE_MANAGER_PAGE_DATA_BUFFER_NODE job. %s\n", ma_result_description(result)); + ma_decoder_uninit(pDecoder); + ma_free(pDecoder, &pResourceManager->config.allocationCallbacks); + } else { + result = MA_BUSY; + } + } else { + /* No decoding. This is the simple case. We need only read the file content into memory and we're done. */ + result = ma_resource_manager_data_buffer_node_init_supply_encoded(pResourceManager, pDataBufferNode, pJob->data.resourceManager.loadDataBufferNode.pFilePath, pJob->data.resourceManager.loadDataBufferNode.pFilePathW); + } + + +done: + /* File paths are no longer needed. */ + ma_free(pJob->data.resourceManager.loadDataBufferNode.pFilePath, &pResourceManager->config.allocationCallbacks); + ma_free(pJob->data.resourceManager.loadDataBufferNode.pFilePathW, &pResourceManager->config.allocationCallbacks); + + /* + We need to set the result to at the very end to ensure no other threads try reading the data before we've fully initialized the object. Other threads + are going to be inspecting this variable to determine whether or not they're ready to read data. We can only change the result if it's set to MA_BUSY + because otherwise we may be changing away from an error code which would be bad. An example is if the application creates a data buffer, but then + immediately deletes it before we've got to this point. In this case, pDataBuffer->result will be MA_UNAVAILABLE, and setting it to MA_SUCCESS or any + other error code would cause the buffer to look like it's in a state that it's not. + */ + c89atomic_compare_and_swap_i32(&pDataBufferNode->result, MA_BUSY, result); + + /* At this point initialization is complete and we can signal the notification if any. */ + if (pJob->data.resourceManager.loadDataBufferNode.pInitNotification != NULL) { + ma_async_notification_signal(pJob->data.resourceManager.loadDataBufferNode.pInitNotification); + } + if (pJob->data.resourceManager.loadDataBufferNode.pInitFence != NULL) { + ma_fence_release(pJob->data.resourceManager.loadDataBufferNode.pInitFence); + } + + /* If we have a success result it means we've fully loaded the buffer. This will happen in the non-decoding case. */ + if (result != MA_BUSY) { + if (pJob->data.resourceManager.loadDataBufferNode.pDoneNotification != NULL) { + ma_async_notification_signal(pJob->data.resourceManager.loadDataBufferNode.pDoneNotification); + } + if (pJob->data.resourceManager.loadDataBufferNode.pDoneFence != NULL) { + ma_fence_release(pJob->data.resourceManager.loadDataBufferNode.pDoneFence); + } + } + + /* Increment the node's execution pointer so that the next jobs can be processed. This is how we keep decoding of pages in-order. */ + c89atomic_fetch_add_32(&pDataBufferNode->executionPointer, 1); + return result; +} + +static ma_result ma_job_process__resource_manager__free_data_buffer_node(ma_job* pJob) +{ + ma_resource_manager* pResourceManager; + ma_resource_manager_data_buffer_node* pDataBufferNode; + + MA_ASSERT(pJob != NULL); + + pResourceManager = (ma_resource_manager*)pJob->data.resourceManager.freeDataBufferNode.pResourceManager; + MA_ASSERT(pResourceManager != NULL); + + pDataBufferNode = (ma_resource_manager_data_buffer_node*)pJob->data.resourceManager.freeDataBufferNode.pDataBufferNode; + MA_ASSERT(pDataBufferNode != NULL); + + if (pJob->order != c89atomic_load_32(&pDataBufferNode->executionPointer)) { + return ma_resource_manager_post_job(pResourceManager, pJob); /* Out of order. */ + } + + ma_resource_manager_data_buffer_node_free(pResourceManager, pDataBufferNode); + + /* The event needs to be signalled last. */ + if (pJob->data.resourceManager.freeDataBufferNode.pDoneNotification != NULL) { + ma_async_notification_signal(pJob->data.resourceManager.freeDataBufferNode.pDoneNotification); + } + + if (pJob->data.resourceManager.freeDataBufferNode.pDoneFence != NULL) { + ma_fence_release(pJob->data.resourceManager.freeDataBufferNode.pDoneFence); + } + + c89atomic_fetch_add_32(&pDataBufferNode->executionPointer, 1); + return MA_SUCCESS; +} + +static ma_result ma_job_process__resource_manager__page_data_buffer_node(ma_job* pJob) +{ + ma_result result = MA_SUCCESS; + ma_resource_manager* pResourceManager; + ma_resource_manager_data_buffer_node* pDataBufferNode; + + MA_ASSERT(pJob != NULL); + + pResourceManager = (ma_resource_manager*)pJob->data.resourceManager.pageDataBufferNode.pResourceManager; + MA_ASSERT(pResourceManager != NULL); + + pDataBufferNode = (ma_resource_manager_data_buffer_node*)pJob->data.resourceManager.pageDataBufferNode.pDataBufferNode; + MA_ASSERT(pDataBufferNode != NULL); + + if (pJob->order != c89atomic_load_32(&pDataBufferNode->executionPointer)) { + return ma_resource_manager_post_job(pResourceManager, pJob); /* Out of order. */ + } + + /* Don't do any more decoding if the data buffer has started the uninitialization process. */ + result = ma_resource_manager_data_buffer_node_result(pDataBufferNode); + if (result != MA_BUSY) { + goto done; + } + + /* We're ready to decode the next page. */ + result = ma_resource_manager_data_buffer_node_decode_next_page(pResourceManager, pDataBufferNode, (ma_decoder*)pJob->data.resourceManager.pageDataBufferNode.pDecoder); + + /* + If we have a success code by this point, we want to post another job. We're going to set the + result back to MA_BUSY to make it clear that there's still more to load. + */ + if (result == MA_SUCCESS) { + ma_job newJob; + newJob = *pJob; /* Everything is the same as the input job, except the execution order. */ + newJob.order = ma_resource_manager_data_buffer_node_next_execution_order(pDataBufferNode); /* We need a fresh execution order. */ + + result = ma_resource_manager_post_job(pResourceManager, &newJob); + + /* Since the sound isn't yet fully decoded we want the status to be set to busy. */ + if (result == MA_SUCCESS) { + result = MA_BUSY; + } + } + +done: + /* If there's still more to decode the result will be set to MA_BUSY. Otherwise we can free the decoder. */ + if (result != MA_BUSY) { + ma_decoder_uninit((ma_decoder*)pJob->data.resourceManager.pageDataBufferNode.pDecoder); + ma_free(pJob->data.resourceManager.pageDataBufferNode.pDecoder, &pResourceManager->config.allocationCallbacks); + } + + /* If we reached the end we need to treat it as successful. */ + if (result == MA_AT_END) { + result = MA_SUCCESS; + } + + /* Make sure we set the result of node in case some error occurred. */ + c89atomic_compare_and_swap_i32(&pDataBufferNode->result, MA_BUSY, result); + + /* Signal the notification after setting the result in case the notification callback wants to inspect the result code. */ + if (result != MA_BUSY) { + if (pJob->data.resourceManager.pageDataBufferNode.pDoneNotification != NULL) { + ma_async_notification_signal(pJob->data.resourceManager.pageDataBufferNode.pDoneNotification); + } + + if (pJob->data.resourceManager.pageDataBufferNode.pDoneFence != NULL) { + ma_fence_release(pJob->data.resourceManager.pageDataBufferNode.pDoneFence); + } + } + + c89atomic_fetch_add_32(&pDataBufferNode->executionPointer, 1); + return result; +} + + +static ma_result ma_job_process__resource_manager__load_data_buffer(ma_job* pJob) +{ + ma_result result = MA_SUCCESS; + ma_resource_manager* pResourceManager; + ma_resource_manager_data_buffer* pDataBuffer; + ma_resource_manager_data_supply_type dataSupplyType = ma_resource_manager_data_supply_type_unknown; + ma_bool32 isConnectorInitialized = MA_FALSE; + + /* + All we're doing here is checking if the node has finished loading. If not, we just re-post the job + and keep waiting. Otherwise we increment the execution counter and set the buffer's result code. + */ + MA_ASSERT(pJob != NULL); + + pDataBuffer = (ma_resource_manager_data_buffer*)pJob->data.resourceManager.loadDataBuffer.pDataBuffer; + MA_ASSERT(pDataBuffer != NULL); + + pResourceManager = pDataBuffer->pResourceManager; + + if (pJob->order != c89atomic_load_32(&pDataBuffer->executionPointer)) { + return ma_resource_manager_post_job(pResourceManager, pJob); /* Attempting to execute out of order. Probably interleaved with a MA_JOB_TYPE_RESOURCE_MANAGER_FREE_DATA_BUFFER job. */ + } + + /* + First thing we need to do is check whether or not the data buffer is getting deleted. If so we + just abort, but making sure we increment the execution pointer. + */ + result = ma_resource_manager_data_buffer_result(pDataBuffer); + if (result != MA_BUSY) { + goto done; /* <-- This will ensure the exucution pointer is incremented. */ + } else { + result = MA_SUCCESS; /* <-- Make sure this is reset. */ + } + + /* Try initializing the connector if we haven't already. */ + isConnectorInitialized = pDataBuffer->isConnectorInitialized; + if (isConnectorInitialized == MA_FALSE) { + dataSupplyType = ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBuffer->pNode); + + if (dataSupplyType != ma_resource_manager_data_supply_type_unknown) { + /* We can now initialize the connector. If this fails, we need to abort. It's very rare for this to fail. */ + ma_resource_manager_data_source_config dataSourceConfig; /* For setting initial looping state and range. */ + dataSourceConfig = ma_resource_manager_data_source_config_init(); + dataSourceConfig.rangeBegInPCMFrames = pJob->data.resourceManager.loadDataBuffer.rangeBegInPCMFrames; + dataSourceConfig.rangeEndInPCMFrames = pJob->data.resourceManager.loadDataBuffer.rangeEndInPCMFrames; + dataSourceConfig.loopPointBegInPCMFrames = pJob->data.resourceManager.loadDataBuffer.loopPointBegInPCMFrames; + dataSourceConfig.loopPointEndInPCMFrames = pJob->data.resourceManager.loadDataBuffer.loopPointEndInPCMFrames; + dataSourceConfig.isLooping = pJob->data.resourceManager.loadDataBuffer.isLooping; + + result = ma_resource_manager_data_buffer_init_connector(pDataBuffer, &dataSourceConfig, pJob->data.resourceManager.loadDataBuffer.pInitNotification, pJob->data.resourceManager.loadDataBuffer.pInitFence); + if (result != MA_SUCCESS) { + ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_ERROR, "Failed to initialize connector for data buffer. %s.\n", ma_result_description(result)); + goto done; + } + } else { + /* Don't have a known data supply type. Most likely the data buffer node is still loading, but it could be that an error occurred. */ + } + } else { + /* The connector is already initialized. Nothing to do here. */ + } + + /* + If the data node is still loading, we need to repost the job and *not* increment the execution + pointer (i.e. we need to not fall through to the "done" label). + + There is a hole between here and the where the data connector is initialized where the data + buffer node may have finished initializing. We need to check for this by checking the result of + the data buffer node and whether or not we had an unknown data supply type at the time of + trying to initialize the data connector. + */ + result = ma_resource_manager_data_buffer_node_result(pDataBuffer->pNode); + if (result == MA_BUSY || (result == MA_SUCCESS && isConnectorInitialized == MA_FALSE && dataSupplyType == ma_resource_manager_data_supply_type_unknown)) { + return ma_resource_manager_post_job(pResourceManager, pJob); + } + +done: + /* Only move away from a busy code so that we don't trash any existing error codes. */ + c89atomic_compare_and_swap_i32(&pDataBuffer->result, MA_BUSY, result); + + /* Only signal the other threads after the result has been set just for cleanliness sake. */ + if (pJob->data.resourceManager.loadDataBuffer.pDoneNotification != NULL) { + ma_async_notification_signal(pJob->data.resourceManager.loadDataBuffer.pDoneNotification); + } + if (pJob->data.resourceManager.loadDataBuffer.pDoneFence != NULL) { + ma_fence_release(pJob->data.resourceManager.loadDataBuffer.pDoneFence); + } + + /* + If at this point the data buffer has not had it's connector initialized, it means the + notification event was never signalled which means we need to signal it here. + */ + if (pDataBuffer->isConnectorInitialized == MA_FALSE && result != MA_SUCCESS) { + if (pJob->data.resourceManager.loadDataBuffer.pInitNotification != NULL) { + ma_async_notification_signal(pJob->data.resourceManager.loadDataBuffer.pInitNotification); + } + if (pJob->data.resourceManager.loadDataBuffer.pInitFence != NULL) { + ma_fence_release(pJob->data.resourceManager.loadDataBuffer.pInitFence); + } + } + + c89atomic_fetch_add_32(&pDataBuffer->executionPointer, 1); + return result; +} + +static ma_result ma_job_process__resource_manager__free_data_buffer(ma_job* pJob) +{ + ma_resource_manager* pResourceManager; + ma_resource_manager_data_buffer* pDataBuffer; + + MA_ASSERT(pJob != NULL); + + pDataBuffer = (ma_resource_manager_data_buffer*)pJob->data.resourceManager.freeDataBuffer.pDataBuffer; + MA_ASSERT(pDataBuffer != NULL); + + pResourceManager = pDataBuffer->pResourceManager; + + if (pJob->order != c89atomic_load_32(&pDataBuffer->executionPointer)) { + return ma_resource_manager_post_job(pResourceManager, pJob); /* Out of order. */ + } + + ma_resource_manager_data_buffer_uninit_internal(pDataBuffer); + + /* The event needs to be signalled last. */ + if (pJob->data.resourceManager.freeDataBuffer.pDoneNotification != NULL) { + ma_async_notification_signal(pJob->data.resourceManager.freeDataBuffer.pDoneNotification); + } + + if (pJob->data.resourceManager.freeDataBuffer.pDoneFence != NULL) { + ma_fence_release(pJob->data.resourceManager.freeDataBuffer.pDoneFence); + } + + c89atomic_fetch_add_32(&pDataBuffer->executionPointer, 1); + return MA_SUCCESS; +} + +static ma_result ma_job_process__resource_manager__load_data_stream(ma_job* pJob) +{ + ma_result result = MA_SUCCESS; + ma_decoder_config decoderConfig; + ma_uint32 pageBufferSizeInBytes; + ma_resource_manager* pResourceManager; + ma_resource_manager_data_stream* pDataStream; + + MA_ASSERT(pJob != NULL); + + pDataStream = (ma_resource_manager_data_stream*)pJob->data.resourceManager.loadDataStream.pDataStream; + MA_ASSERT(pDataStream != NULL); + + pResourceManager = pDataStream->pResourceManager; + + if (pJob->order != c89atomic_load_32(&pDataStream->executionPointer)) { + return ma_resource_manager_post_job(pResourceManager, pJob); /* Out of order. */ + } + + if (ma_resource_manager_data_stream_result(pDataStream) != MA_BUSY) { + result = MA_INVALID_OPERATION; /* Most likely the data stream is being uninitialized. */ + goto done; + } + + /* We need to initialize the decoder first so we can determine the size of the pages. */ + decoderConfig = ma_resource_manager__init_decoder_config(pResourceManager); + + if (pJob->data.resourceManager.loadDataStream.pFilePath != NULL) { + result = ma_decoder_init_vfs(pResourceManager->config.pVFS, pJob->data.resourceManager.loadDataStream.pFilePath, &decoderConfig, &pDataStream->decoder); + } else { + result = ma_decoder_init_vfs_w(pResourceManager->config.pVFS, pJob->data.resourceManager.loadDataStream.pFilePathW, &decoderConfig, &pDataStream->decoder); + } + if (result != MA_SUCCESS) { + goto done; + } + + /* Retrieve the total length of the file before marking the decoder are loaded. */ + if ((pDataStream->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_UNKNOWN_LENGTH) == 0) { + result = ma_decoder_get_length_in_pcm_frames(&pDataStream->decoder, &pDataStream->totalLengthInPCMFrames); + if (result != MA_SUCCESS) { + goto done; /* Failed to retrieve the length. */ + } + } else { + pDataStream->totalLengthInPCMFrames = 0; + } + + /* + Only mark the decoder as initialized when the length of the decoder has been retrieved because that can possibly require a scan over the whole file + and we don't want to have another thread trying to access the decoder while it's scanning. + */ + pDataStream->isDecoderInitialized = MA_TRUE; + + /* We have the decoder so we can now initialize our page buffer. */ + pageBufferSizeInBytes = ma_resource_manager_data_stream_get_page_size_in_frames(pDataStream) * 2 * ma_get_bytes_per_frame(pDataStream->decoder.outputFormat, pDataStream->decoder.outputChannels); + + pDataStream->pPageData = ma_malloc(pageBufferSizeInBytes, &pResourceManager->config.allocationCallbacks); + if (pDataStream->pPageData == NULL) { + ma_decoder_uninit(&pDataStream->decoder); + result = MA_OUT_OF_MEMORY; + goto done; + } + + /* Seek to our initial seek point before filling the initial pages. */ + ma_decoder_seek_to_pcm_frame(&pDataStream->decoder, pJob->data.resourceManager.loadDataStream.initialSeekPoint); + + /* We have our decoder and our page buffer, so now we need to fill our pages. */ + ma_resource_manager_data_stream_fill_pages(pDataStream); + + /* And now we're done. We want to make sure the result is MA_SUCCESS. */ + result = MA_SUCCESS; + +done: + ma_free(pJob->data.resourceManager.loadDataStream.pFilePath, &pResourceManager->config.allocationCallbacks); + ma_free(pJob->data.resourceManager.loadDataStream.pFilePathW, &pResourceManager->config.allocationCallbacks); + + /* We can only change the status away from MA_BUSY. If it's set to anything else it means an error has occurred somewhere or the uninitialization process has started (most likely). */ + c89atomic_compare_and_swap_i32(&pDataStream->result, MA_BUSY, result); + + /* Only signal the other threads after the result has been set just for cleanliness sake. */ + if (pJob->data.resourceManager.loadDataStream.pInitNotification != NULL) { + ma_async_notification_signal(pJob->data.resourceManager.loadDataStream.pInitNotification); + } + if (pJob->data.resourceManager.loadDataStream.pInitFence != NULL) { + ma_fence_release(pJob->data.resourceManager.loadDataStream.pInitFence); + } + + c89atomic_fetch_add_32(&pDataStream->executionPointer, 1); + return result; +} + +static ma_result ma_job_process__resource_manager__free_data_stream(ma_job* pJob) +{ + ma_resource_manager* pResourceManager; + ma_resource_manager_data_stream* pDataStream; + + MA_ASSERT(pJob != NULL); + + pDataStream = (ma_resource_manager_data_stream*)pJob->data.resourceManager.freeDataStream.pDataStream; + MA_ASSERT(pDataStream != NULL); + + pResourceManager = pDataStream->pResourceManager; + + if (pJob->order != c89atomic_load_32(&pDataStream->executionPointer)) { + return ma_resource_manager_post_job(pResourceManager, pJob); /* Out of order. */ + } + + /* If our status is not MA_UNAVAILABLE we have a bug somewhere. */ + MA_ASSERT(ma_resource_manager_data_stream_result(pDataStream) == MA_UNAVAILABLE); + + if (pDataStream->isDecoderInitialized) { + ma_decoder_uninit(&pDataStream->decoder); + } + + if (pDataStream->pPageData != NULL) { + ma_free(pDataStream->pPageData, &pResourceManager->config.allocationCallbacks); + pDataStream->pPageData = NULL; /* Just in case... */ + } + + ma_data_source_uninit(&pDataStream->ds); + + /* The event needs to be signalled last. */ + if (pJob->data.resourceManager.freeDataStream.pDoneNotification != NULL) { + ma_async_notification_signal(pJob->data.resourceManager.freeDataStream.pDoneNotification); + } + if (pJob->data.resourceManager.freeDataStream.pDoneFence != NULL) { + ma_fence_release(pJob->data.resourceManager.freeDataStream.pDoneFence); + } + + /*c89atomic_fetch_add_32(&pDataStream->executionPointer, 1);*/ + return MA_SUCCESS; +} + +static ma_result ma_job_process__resource_manager__page_data_stream(ma_job* pJob) +{ + ma_result result = MA_SUCCESS; + ma_resource_manager* pResourceManager; + ma_resource_manager_data_stream* pDataStream; + + MA_ASSERT(pJob != NULL); + + pDataStream = (ma_resource_manager_data_stream*)pJob->data.resourceManager.pageDataStream.pDataStream; + MA_ASSERT(pDataStream != NULL); + + pResourceManager = pDataStream->pResourceManager; + + if (pJob->order != c89atomic_load_32(&pDataStream->executionPointer)) { + return ma_resource_manager_post_job(pResourceManager, pJob); /* Out of order. */ + } + + /* For streams, the status should be MA_SUCCESS. */ + if (ma_resource_manager_data_stream_result(pDataStream) != MA_SUCCESS) { + result = MA_INVALID_OPERATION; + goto done; + } + + ma_resource_manager_data_stream_fill_page(pDataStream, pJob->data.resourceManager.pageDataStream.pageIndex); + +done: + c89atomic_fetch_add_32(&pDataStream->executionPointer, 1); + return result; +} + +static ma_result ma_job_process__resource_manager__seek_data_stream(ma_job* pJob) +{ + ma_result result = MA_SUCCESS; + ma_resource_manager* pResourceManager; + ma_resource_manager_data_stream* pDataStream; + + MA_ASSERT(pJob != NULL); + + pDataStream = (ma_resource_manager_data_stream*)pJob->data.resourceManager.seekDataStream.pDataStream; + MA_ASSERT(pDataStream != NULL); + + pResourceManager = pDataStream->pResourceManager; + + if (pJob->order != c89atomic_load_32(&pDataStream->executionPointer)) { + return ma_resource_manager_post_job(pResourceManager, pJob); /* Out of order. */ + } + + /* For streams the status should be MA_SUCCESS for this to do anything. */ + if (ma_resource_manager_data_stream_result(pDataStream) != MA_SUCCESS || pDataStream->isDecoderInitialized == MA_FALSE) { + result = MA_INVALID_OPERATION; + goto done; + } + + /* + With seeking we just assume both pages are invalid and the relative frame cursor at position 0. This is basically exactly the same as loading, except + instead of initializing the decoder, we seek to a frame. + */ + ma_decoder_seek_to_pcm_frame(&pDataStream->decoder, pJob->data.resourceManager.seekDataStream.frameIndex); + + /* After seeking we'll need to reload the pages. */ + ma_resource_manager_data_stream_fill_pages(pDataStream); + + /* We need to let the public API know that we're done seeking. */ + c89atomic_fetch_sub_32(&pDataStream->seekCounter, 1); + +done: + c89atomic_fetch_add_32(&pDataStream->executionPointer, 1); + return result; +} + +MA_API ma_result ma_resource_manager_process_job(ma_resource_manager* pResourceManager, ma_job* pJob) +{ + if (pResourceManager == NULL || pJob == NULL) { + return MA_INVALID_ARGS; + } + + return ma_job_process(pJob); +} + +MA_API ma_result ma_resource_manager_process_next_job(ma_resource_manager* pResourceManager) +{ + ma_result result; + ma_job job; + + if (pResourceManager == NULL) { + return MA_INVALID_ARGS; + } + + /* This will return MA_CANCELLED if the next job is a quit job. */ + result = ma_resource_manager_next_job(pResourceManager, &job); + if (result != MA_SUCCESS) { + return result; + } + + return ma_job_process(&job); +} +#else +/* We'll get here if the resource manager is being excluded from the build. We need to define the job processing callbacks as no-ops. */ +static ma_result ma_job_process__resource_manager__load_data_buffer_node(ma_job* pJob) { return ma_job_process__noop(pJob); } +static ma_result ma_job_process__resource_manager__free_data_buffer_node(ma_job* pJob) { return ma_job_process__noop(pJob); } +static ma_result ma_job_process__resource_manager__page_data_buffer_node(ma_job* pJob) { return ma_job_process__noop(pJob); } +static ma_result ma_job_process__resource_manager__load_data_buffer(ma_job* pJob) { return ma_job_process__noop(pJob); } +static ma_result ma_job_process__resource_manager__free_data_buffer(ma_job* pJob) { return ma_job_process__noop(pJob); } +static ma_result ma_job_process__resource_manager__load_data_stream(ma_job* pJob) { return ma_job_process__noop(pJob); } +static ma_result ma_job_process__resource_manager__free_data_stream(ma_job* pJob) { return ma_job_process__noop(pJob); } +static ma_result ma_job_process__resource_manager__page_data_stream(ma_job* pJob) { return ma_job_process__noop(pJob); } +static ma_result ma_job_process__resource_manager__seek_data_stream(ma_job* pJob) { return ma_job_process__noop(pJob); } +#endif /* MA_NO_RESOURCE_MANAGER */ + + +#ifndef MA_NO_NODE_GRAPH +/* 10ms @ 48K = 480. Must never exceed 65535. */ +#ifndef MA_DEFAULT_NODE_CACHE_CAP_IN_FRAMES_PER_BUS +#define MA_DEFAULT_NODE_CACHE_CAP_IN_FRAMES_PER_BUS 480 +#endif + + +static ma_result ma_node_read_pcm_frames(ma_node* pNode, ma_uint32 outputBusIndex, float* pFramesOut, ma_uint32 frameCount, ma_uint32* pFramesRead, ma_uint64 globalTime); + +MA_API void ma_debug_fill_pcm_frames_with_sine_wave(float* pFramesOut, ma_uint32 frameCount, ma_format format, ma_uint32 channels, ma_uint32 sampleRate) +{ + #ifndef MA_NO_GENERATION + { + ma_waveform_config waveformConfig; + ma_waveform waveform; + + waveformConfig = ma_waveform_config_init(format, channels, sampleRate, ma_waveform_type_sine, 1.0, 400); + ma_waveform_init(&waveformConfig, &waveform); + ma_waveform_read_pcm_frames(&waveform, pFramesOut, frameCount, NULL); + } + #else + { + (void)pFramesOut; + (void)frameCount; + (void)format; + (void)channels; + (void)sampleRate; + #if defined(MA_DEBUG_OUTPUT) + { + #if _MSC_VER + #pragma message ("ma_debug_fill_pcm_frames_with_sine_wave() will do nothing because MA_NO_GENERATION is enabled.") + #endif + } + #endif + } + #endif +} + + + +static ma_result ma_mix_pcm_frames_f32(float* pDst, const float* pSrc, ma_uint64 frameCount, ma_uint32 channels, float volume) +{ + ma_uint64 iSample; + ma_uint64 sampleCount; + + if (pDst == NULL || pSrc == NULL || channels == 0) { + return MA_INVALID_ARGS; + } + + if (volume == 0) { + return MA_SUCCESS; /* No changes if the volume is 0. */ + } + + sampleCount = frameCount * channels; + + if (volume == 1) { + for (iSample = 0; iSample < sampleCount; iSample += 1) { + pDst[iSample] += pSrc[iSample]; + } + } else { + for (iSample = 0; iSample < sampleCount; iSample += 1) { + pDst[iSample] += ma_apply_volume_unclipped_f32(pSrc[iSample], volume); + } + } + + return MA_SUCCESS; +} + + +MA_API ma_node_graph_config ma_node_graph_config_init(ma_uint32 channels) +{ + ma_node_graph_config config; + + MA_ZERO_OBJECT(&config); + config.channels = channels; + config.nodeCacheCapInFrames = MA_DEFAULT_NODE_CACHE_CAP_IN_FRAMES_PER_BUS; + + return config; +} + + +static void ma_node_graph_set_is_reading(ma_node_graph* pNodeGraph, ma_bool32 isReading) +{ + MA_ASSERT(pNodeGraph != NULL); + c89atomic_exchange_32(&pNodeGraph->isReading, isReading); +} + +#if 0 +static ma_bool32 ma_node_graph_is_reading(ma_node_graph* pNodeGraph) +{ + MA_ASSERT(pNodeGraph != NULL); + return c89atomic_load_32(&pNodeGraph->isReading); +} +#endif + + +static void ma_node_graph_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + ma_node_graph* pNodeGraph = (ma_node_graph*)pNode; + ma_uint64 framesRead; + + ma_node_graph_read_pcm_frames(pNodeGraph, ppFramesOut[0], *pFrameCountOut, &framesRead); + + *pFrameCountOut = (ma_uint32)framesRead; /* Safe cast. */ + + (void)ppFramesIn; + (void)pFrameCountIn; +} + +static ma_node_vtable g_node_graph_node_vtable = +{ + ma_node_graph_node_process_pcm_frames, + NULL, /* onGetRequiredInputFrameCount */ + 0, /* 0 input buses. */ + 1, /* 1 output bus. */ + 0 /* Flags. */ +}; + +static void ma_node_graph_endpoint_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + MA_ASSERT(pNode != NULL); + MA_ASSERT(ma_node_get_input_bus_count(pNode) == 1); + MA_ASSERT(ma_node_get_output_bus_count(pNode) == 1); + + /* Input channel count needs to be the same as the output channel count. */ + MA_ASSERT(ma_node_get_input_channels(pNode, 0) == ma_node_get_output_channels(pNode, 0)); + + /* We don't need to do anything here because it's a passthrough. */ + (void)pNode; + (void)ppFramesIn; + (void)pFrameCountIn; + (void)ppFramesOut; + (void)pFrameCountOut; + +#if 0 + /* The data has already been mixed. We just need to move it to the output buffer. */ + if (ppFramesIn != NULL) { + ma_copy_pcm_frames(ppFramesOut[0], ppFramesIn[0], *pFrameCountOut, ma_format_f32, ma_node_get_output_channels(pNode, 0)); + } +#endif +} + +static ma_node_vtable g_node_graph_endpoint_vtable = +{ + ma_node_graph_endpoint_process_pcm_frames, + NULL, /* onGetRequiredInputFrameCount */ + 1, /* 1 input bus. */ + 1, /* 1 output bus. */ + MA_NODE_FLAG_PASSTHROUGH /* Flags. The endpoint is a passthrough. */ +}; + +MA_API ma_result ma_node_graph_init(const ma_node_graph_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_node_graph* pNodeGraph) +{ + ma_result result; + ma_node_config baseConfig; + ma_node_config endpointConfig; + + if (pNodeGraph == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pNodeGraph); + pNodeGraph->nodeCacheCapInFrames = pConfig->nodeCacheCapInFrames; + if (pNodeGraph->nodeCacheCapInFrames == 0) { + pNodeGraph->nodeCacheCapInFrames = MA_DEFAULT_NODE_CACHE_CAP_IN_FRAMES_PER_BUS; + } + + + /* Base node so we can use the node graph as a node into another graph. */ + baseConfig = ma_node_config_init(); + baseConfig.vtable = &g_node_graph_node_vtable; + baseConfig.pOutputChannels = &pConfig->channels; + + result = ma_node_init(pNodeGraph, &baseConfig, pAllocationCallbacks, &pNodeGraph->base); + if (result != MA_SUCCESS) { + return result; + } + + + /* Endpoint. */ + endpointConfig = ma_node_config_init(); + endpointConfig.vtable = &g_node_graph_endpoint_vtable; + endpointConfig.pInputChannels = &pConfig->channels; + endpointConfig.pOutputChannels = &pConfig->channels; + + result = ma_node_init(pNodeGraph, &endpointConfig, pAllocationCallbacks, &pNodeGraph->endpoint); + if (result != MA_SUCCESS) { + ma_node_uninit(&pNodeGraph->base, pAllocationCallbacks); + return result; + } + + return MA_SUCCESS; +} + +MA_API void ma_node_graph_uninit(ma_node_graph* pNodeGraph, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pNodeGraph == NULL) { + return; + } + + ma_node_uninit(&pNodeGraph->endpoint, pAllocationCallbacks); +} + +MA_API ma_node* ma_node_graph_get_endpoint(ma_node_graph* pNodeGraph) +{ + if (pNodeGraph == NULL) { + return NULL; + } + + return &pNodeGraph->endpoint; +} + +MA_API ma_result ma_node_graph_read_pcm_frames(ma_node_graph* pNodeGraph, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) +{ + ma_result result = MA_SUCCESS; + ma_uint64 totalFramesRead; + ma_uint32 channels; + + if (pFramesRead != NULL) { + *pFramesRead = 0; /* Safety. */ + } + + if (pNodeGraph == NULL) { + return MA_INVALID_ARGS; + } + + channels = ma_node_get_output_channels(&pNodeGraph->endpoint, 0); + + + /* We'll be nice and try to do a full read of all frameCount frames. */ + totalFramesRead = 0; + while (totalFramesRead < frameCount) { + ma_uint32 framesJustRead; + ma_uint64 framesToRead = frameCount - totalFramesRead; + + if (framesToRead > 0xFFFFFFFF) { + framesToRead = 0xFFFFFFFF; + } + + ma_node_graph_set_is_reading(pNodeGraph, MA_TRUE); + { + result = ma_node_read_pcm_frames(&pNodeGraph->endpoint, 0, (float*)ma_offset_pcm_frames_ptr(pFramesOut, totalFramesRead, ma_format_f32, channels), (ma_uint32)framesToRead, &framesJustRead, ma_node_get_time(&pNodeGraph->endpoint)); + } + ma_node_graph_set_is_reading(pNodeGraph, MA_FALSE); + + totalFramesRead += framesJustRead; + + if (result != MA_SUCCESS) { + break; + } + + /* Abort if we weren't able to read any frames or else we risk getting stuck in a loop. */ + if (framesJustRead == 0) { + break; + } + } + + /* Let's go ahead and silence any leftover frames just for some added safety to ensure the caller doesn't try emitting garbage out of the speakers. */ + if (totalFramesRead < frameCount) { + ma_silence_pcm_frames(ma_offset_pcm_frames_ptr(pFramesOut, totalFramesRead, ma_format_f32, channels), (frameCount - totalFramesRead), ma_format_f32, channels); + } + + if (pFramesRead != NULL) { + *pFramesRead = totalFramesRead; + } + + return result; +} + +MA_API ma_uint32 ma_node_graph_get_channels(const ma_node_graph* pNodeGraph) +{ + if (pNodeGraph == NULL) { + return 0; + } + + return ma_node_get_output_channels(&pNodeGraph->endpoint, 0); +} + +MA_API ma_uint64 ma_node_graph_get_time(const ma_node_graph* pNodeGraph) +{ + if (pNodeGraph == NULL) { + return 0; + } + + return ma_node_get_time(&pNodeGraph->endpoint); /* Global time is just the local time of the endpoint. */ +} + +MA_API ma_result ma_node_graph_set_time(ma_node_graph* pNodeGraph, ma_uint64 globalTime) +{ + if (pNodeGraph == NULL) { + return MA_INVALID_ARGS; + } + + return ma_node_set_time(&pNodeGraph->endpoint, globalTime); /* Global time is just the local time of the endpoint. */ +} + + +#define MA_NODE_OUTPUT_BUS_FLAG_HAS_READ 0x01 /* Whether or not this bus ready to read more data. Only used on nodes with multiple output buses. */ + +static ma_result ma_node_output_bus_init(ma_node* pNode, ma_uint32 outputBusIndex, ma_uint32 channels, ma_node_output_bus* pOutputBus) +{ + MA_ASSERT(pOutputBus != NULL); + MA_ASSERT(outputBusIndex < MA_MAX_NODE_BUS_COUNT); + MA_ASSERT(outputBusIndex < ma_node_get_output_bus_count(pNode)); + MA_ASSERT(channels < 256); + + MA_ZERO_OBJECT(pOutputBus); + + if (channels == 0) { + return MA_INVALID_ARGS; + } + + pOutputBus->pNode = pNode; + pOutputBus->outputBusIndex = (ma_uint8)outputBusIndex; + pOutputBus->channels = (ma_uint8)channels; + pOutputBus->flags = MA_NODE_OUTPUT_BUS_FLAG_HAS_READ; /* <-- Important that this flag is set by default. */ + pOutputBus->volume = 1; + + return MA_SUCCESS; +} + +static void ma_node_output_bus_lock(ma_node_output_bus* pOutputBus) +{ + ma_spinlock_lock(&pOutputBus->lock); +} + +static void ma_node_output_bus_unlock(ma_node_output_bus* pOutputBus) +{ + ma_spinlock_unlock(&pOutputBus->lock); +} + + +static ma_uint32 ma_node_output_bus_get_channels(const ma_node_output_bus* pOutputBus) +{ + return pOutputBus->channels; +} + + +static void ma_node_output_bus_set_has_read(ma_node_output_bus* pOutputBus, ma_bool32 hasRead) +{ + if (hasRead) { + c89atomic_fetch_or_32(&pOutputBus->flags, MA_NODE_OUTPUT_BUS_FLAG_HAS_READ); + } else { + c89atomic_fetch_and_32(&pOutputBus->flags, (ma_uint32)~MA_NODE_OUTPUT_BUS_FLAG_HAS_READ); + } +} + +static ma_bool32 ma_node_output_bus_has_read(ma_node_output_bus* pOutputBus) +{ + return (c89atomic_load_32(&pOutputBus->flags) & MA_NODE_OUTPUT_BUS_FLAG_HAS_READ) != 0; +} + + +static void ma_node_output_bus_set_is_attached(ma_node_output_bus* pOutputBus, ma_bool32 isAttached) +{ + c89atomic_exchange_32(&pOutputBus->isAttached, isAttached); +} + +static ma_bool32 ma_node_output_bus_is_attached(ma_node_output_bus* pOutputBus) +{ + return c89atomic_load_32(&pOutputBus->isAttached); +} + + +static ma_result ma_node_output_bus_set_volume(ma_node_output_bus* pOutputBus, float volume) +{ + MA_ASSERT(pOutputBus != NULL); + + if (volume < 0.0f) { + volume = 0.0f; + } + + c89atomic_exchange_f32(&pOutputBus->volume, volume); + + return MA_SUCCESS; +} + +static float ma_node_output_bus_get_volume(const ma_node_output_bus* pOutputBus) +{ + return c89atomic_load_f32((float*)&pOutputBus->volume); +} + + +static ma_result ma_node_input_bus_init(ma_uint32 channels, ma_node_input_bus* pInputBus) +{ + MA_ASSERT(pInputBus != NULL); + MA_ASSERT(channels < 256); + + MA_ZERO_OBJECT(pInputBus); + + if (channels == 0) { + return MA_INVALID_ARGS; + } + + pInputBus->channels = (ma_uint8)channels; + + return MA_SUCCESS; +} + +static void ma_node_input_bus_lock(ma_node_input_bus* pInputBus) +{ + ma_spinlock_lock(&pInputBus->lock); +} + +static void ma_node_input_bus_unlock(ma_node_input_bus* pInputBus) +{ + ma_spinlock_unlock(&pInputBus->lock); +} + + +static void ma_node_input_bus_next_begin(ma_node_input_bus* pInputBus) +{ + c89atomic_fetch_add_32(&pInputBus->nextCounter, 1); +} + +static void ma_node_input_bus_next_end(ma_node_input_bus* pInputBus) +{ + c89atomic_fetch_sub_32(&pInputBus->nextCounter, 1); +} + +static ma_uint32 ma_node_input_bus_get_next_counter(ma_node_input_bus* pInputBus) +{ + return c89atomic_load_32(&pInputBus->nextCounter); +} + + +static ma_uint32 ma_node_input_bus_get_channels(const ma_node_input_bus* pInputBus) +{ + return pInputBus->channels; +} + + +static void ma_node_input_bus_detach__no_output_bus_lock(ma_node_input_bus* pInputBus, ma_node_output_bus* pOutputBus) +{ + MA_ASSERT(pInputBus != NULL); + MA_ASSERT(pOutputBus != NULL); + + /* + Mark the output bus as detached first. This will prevent future iterations on the audio thread + from iterating this output bus. + */ + ma_node_output_bus_set_is_attached(pOutputBus, MA_FALSE); + + /* + We cannot use the output bus lock here since it'll be getting used at a higher level, but we do + still need to use the input bus lock since we'll be updating pointers on two different output + buses. The same rules apply here as the attaching case. Although we're using a lock here, we're + *not* using a lock when iterating over the list in the audio thread. We therefore need to craft + this in a way such that the iteration on the audio thread doesn't break. + + The the first thing to do is swap out the "next" pointer of the previous output bus with the + new "next" output bus. This is the operation that matters for iteration on the audio thread. + After that, the previous pointer on the new "next" pointer needs to be updated, after which + point the linked list will be in a good state. + */ + ma_node_input_bus_lock(pInputBus); + { + ma_node_output_bus* pOldPrev = (ma_node_output_bus*)c89atomic_load_ptr(&pOutputBus->pPrev); + ma_node_output_bus* pOldNext = (ma_node_output_bus*)c89atomic_load_ptr(&pOutputBus->pNext); + + if (pOldPrev != NULL) { + c89atomic_exchange_ptr(&pOldPrev->pNext, pOldNext); /* <-- This is where the output bus is detached from the list. */ + } + if (pOldNext != NULL) { + c89atomic_exchange_ptr(&pOldNext->pPrev, pOldPrev); /* <-- This is required for detachment. */ + } + } + ma_node_input_bus_unlock(pInputBus); + + /* At this point the output bus is detached and the linked list is completely unaware of it. Reset some data for safety. */ + c89atomic_exchange_ptr(&pOutputBus->pNext, NULL); /* Using atomic exchanges here, mainly for the benefit of analysis tools which don't always recognize spinlocks. */ + c89atomic_exchange_ptr(&pOutputBus->pPrev, NULL); /* As above. */ + pOutputBus->pInputNode = NULL; + pOutputBus->inputNodeInputBusIndex = 0; + + + /* + For thread-safety reasons, we don't want to be returning from this straight away. We need to + wait for the audio thread to finish with the output bus. There's two things we need to wait + for. The first is the part that selects the next output bus in the list, and the other is the + part that reads from the output bus. Basically all we're doing is waiting for the input bus + to stop referencing the output bus. + + We're doing this part last because we want the section above to run while the audio thread + is finishing up with the output bus, just for efficiency reasons. We marked the output bus as + detached right at the top of this function which is going to prevent the audio thread from + iterating the output bus again. + */ + + /* Part 1: Wait for the current iteration to complete. */ + while (ma_node_input_bus_get_next_counter(pInputBus) > 0) { + ma_yield(); + } + + /* Part 2: Wait for any reads to complete. */ + while (c89atomic_load_32(&pOutputBus->refCount) > 0) { + ma_yield(); + } + + /* + At this point we're done detaching and we can be guaranteed that the audio thread is not going + to attempt to reference this output bus again (until attached again). + */ +} + +#if 0 /* Not used at the moment, but leaving here in case I need it later. */ +static void ma_node_input_bus_detach(ma_node_input_bus* pInputBus, ma_node_output_bus* pOutputBus) +{ + MA_ASSERT(pInputBus != NULL); + MA_ASSERT(pOutputBus != NULL); + + ma_node_output_bus_lock(pOutputBus); + { + ma_node_input_bus_detach__no_output_bus_lock(pInputBus, pOutputBus); + } + ma_node_output_bus_unlock(pOutputBus); +} +#endif + +static void ma_node_input_bus_attach(ma_node_input_bus* pInputBus, ma_node_output_bus* pOutputBus, ma_node* pNewInputNode, ma_uint32 inputNodeInputBusIndex) +{ + MA_ASSERT(pInputBus != NULL); + MA_ASSERT(pOutputBus != NULL); + + ma_node_output_bus_lock(pOutputBus); + { + ma_node_output_bus* pOldInputNode = (ma_node_output_bus*)c89atomic_load_ptr(&pOutputBus->pInputNode); + + /* Detach from any existing attachment first if necessary. */ + if (pOldInputNode != NULL) { + ma_node_input_bus_detach__no_output_bus_lock(pInputBus, pOutputBus); + } + + /* + At this point we can be sure the output bus is not attached to anything. The linked list in the + old input bus has been updated so that pOutputBus will not get iterated again. + */ + pOutputBus->pInputNode = pNewInputNode; /* No need for an atomic assignment here because modification of this variable always happens within a lock. */ + pOutputBus->inputNodeInputBusIndex = (ma_uint8)inputNodeInputBusIndex; /* As above. */ + + /* + Now we need to attach the output bus to the linked list. This involves updating two pointers on + two different output buses so I'm going to go ahead and keep this simple and just use a lock. + There are ways to do this without a lock, but it's just too hard to maintain for it's value. + + Although we're locking here, it's important to remember that we're *not* locking when iterating + and reading audio data since that'll be running on the audio thread. As a result we need to be + careful how we craft this so that we don't break iteration. What we're going to do is always + attach the new item so that it becomes the first item in the list. That way, as we're iterating + we won't break any links in the list and iteration will continue safely. The detaching case will + also be crafted in a way as to not break list iteration. It's important to remember to use + atomic exchanges here since no locking is happening on the audio thread during iteration. + */ + ma_node_input_bus_lock(pInputBus); + { + ma_node_output_bus* pNewPrev = &pInputBus->head; + ma_node_output_bus* pNewNext = (ma_node_output_bus*)c89atomic_load_ptr(&pInputBus->head.pNext); + + /* Update the local output bus. */ + c89atomic_exchange_ptr(&pOutputBus->pPrev, pNewPrev); + c89atomic_exchange_ptr(&pOutputBus->pNext, pNewNext); + + /* Update the other output buses to point back to the local output bus. */ + c89atomic_exchange_ptr(&pInputBus->head.pNext, pOutputBus); /* <-- This is where the output bus is actually attached to the input bus. */ + + /* Do the previous pointer last. This is only used for detachment. */ + if (pNewNext != NULL) { + c89atomic_exchange_ptr(&pNewNext->pPrev, pOutputBus); + } + } + ma_node_input_bus_unlock(pInputBus); + + /* + Mark the node as attached last. This is used to controlling whether or the output bus will be + iterated on the audio thread. Mainly required for detachment purposes. + */ + ma_node_output_bus_set_is_attached(pOutputBus, MA_TRUE); + } + ma_node_output_bus_unlock(pOutputBus); +} + +static ma_node_output_bus* ma_node_input_bus_next(ma_node_input_bus* pInputBus, ma_node_output_bus* pOutputBus) +{ + ma_node_output_bus* pNext; + + MA_ASSERT(pInputBus != NULL); + + if (pOutputBus == NULL) { + return NULL; + } + + ma_node_input_bus_next_begin(pInputBus); + { + pNext = pOutputBus; + for (;;) { + pNext = (ma_node_output_bus*)c89atomic_load_ptr(&pNext->pNext); + if (pNext == NULL) { + break; /* Reached the end. */ + } + + if (ma_node_output_bus_is_attached(pNext) == MA_FALSE) { + continue; /* The node is not attached. Keep checking. */ + } + + /* The next node has been selected. */ + break; + } + + /* We need to increment the reference count of the selected node. */ + if (pNext != NULL) { + c89atomic_fetch_add_32(&pNext->refCount, 1); + } + + /* The previous node is no longer being referenced. */ + c89atomic_fetch_sub_32(&pOutputBus->refCount, 1); + } + ma_node_input_bus_next_end(pInputBus); + + return pNext; +} + +static ma_node_output_bus* ma_node_input_bus_first(ma_node_input_bus* pInputBus) +{ + return ma_node_input_bus_next(pInputBus, &pInputBus->head); +} + + + +static ma_result ma_node_input_bus_read_pcm_frames(ma_node* pInputNode, ma_node_input_bus* pInputBus, float* pFramesOut, ma_uint32 frameCount, ma_uint32* pFramesRead, ma_uint64 globalTime) +{ + ma_result result = MA_SUCCESS; + ma_node_output_bus* pOutputBus; + ma_node_output_bus* pFirst; + ma_uint32 inputChannels; + ma_bool32 doesOutputBufferHaveContent = MA_FALSE; + + /* + This will be called from the audio thread which means we can't be doing any locking. Basically, + this function will not perfom any locking, whereas attaching and detaching will, but crafted in + such a way that we don't need to perform any locking here. The important thing to remember is + to always iterate in a forward direction. + + In order to process any data we need to first read from all input buses. That's where this + function comes in. This iterates over each of the attachments and accumulates/mixes them. We + also convert the channels to the nodes output channel count before mixing. We want to do this + channel conversion so that the caller of this function can invoke the processing callback + without having to do it themselves. + + When we iterate over each of the attachments on the input bus, we need to read as much data as + we can from each of them so that we don't end up with holes between each of the attachments. To + do this, we need to read from each attachment in a loop and read as many frames as we can, up + to `frameCount`. + */ + MA_ASSERT(pInputNode != NULL); + MA_ASSERT(pFramesRead != NULL); /* pFramesRead is critical and must always be specified. On input it's undefined and on output it'll be set to the number of frames actually read. */ + + *pFramesRead = 0; /* Safety. */ + + inputChannels = ma_node_input_bus_get_channels(pInputBus); + + /* + We need to be careful with how we call ma_node_input_bus_first() and ma_node_input_bus_next(). They + are both critical to our lock-free thread-safety system. We can only call ma_node_input_bus_first() + once per iteration, however we have an optimization to checks whether or not it's the first item in + the list. We therefore need to store a pointer to the first item rather than repeatedly calling + ma_node_input_bus_first(). It's safe to keep hold of this pointer, so long as we don't dereference it + after calling ma_node_input_bus_next(), which we won't be. + */ + pFirst = ma_node_input_bus_first(pInputBus); + if (pFirst == NULL) { + return MA_SUCCESS; /* No attachments. Read nothing. */ + } + + for (pOutputBus = pFirst; pOutputBus != NULL; pOutputBus = ma_node_input_bus_next(pInputBus, pOutputBus)) { + ma_uint32 framesProcessed = 0; + ma_bool32 isSilentOutput = MA_FALSE; + + MA_ASSERT(pOutputBus->pNode != NULL); + + isSilentOutput = (((ma_node_base*)pOutputBus->pNode)->vtable->flags & MA_NODE_FLAG_SILENT_OUTPUT) != 0; + + if (pFramesOut != NULL) { + /* Read. */ + float temp[MA_DATA_CONVERTER_STACK_BUFFER_SIZE / sizeof(float)]; + ma_uint32 tempCapInFrames = ma_countof(temp) / inputChannels; + + while (framesProcessed < frameCount) { + float* pRunningFramesOut; + ma_uint32 framesToRead; + ma_uint32 framesJustRead; + + framesToRead = frameCount - framesProcessed; + if (framesToRead > tempCapInFrames) { + framesToRead = tempCapInFrames; + } + + pRunningFramesOut = ma_offset_pcm_frames_ptr_f32(pFramesOut, framesProcessed, inputChannels); + + if (doesOutputBufferHaveContent == MA_FALSE) { + /* Fast path. First attachment. We just read straight into the output buffer (no mixing required). */ + result = ma_node_read_pcm_frames(pOutputBus->pNode, pOutputBus->outputBusIndex, pRunningFramesOut, framesToRead, &framesJustRead, globalTime + framesProcessed); + } else { + /* Slow path. Not the first attachment. Mixing required. */ + result = ma_node_read_pcm_frames(pOutputBus->pNode, pOutputBus->outputBusIndex, temp, framesToRead, &framesJustRead, globalTime + framesProcessed); + if (result == MA_SUCCESS || result == MA_AT_END) { + if (isSilentOutput == MA_FALSE) { /* Don't mix if the node outputs silence. */ + ma_mix_pcm_frames_f32(pRunningFramesOut, temp, framesJustRead, inputChannels, /*volume*/1); + } + } + } + + framesProcessed += framesJustRead; + + /* If we reached the end or otherwise failed to read any data we need to finish up with this output node. */ + if (result != MA_SUCCESS) { + break; + } + + /* If we didn't read anything, abort so we don't get stuck in a loop. */ + if (framesJustRead == 0) { + break; + } + } + + /* If it's the first attachment we didn't do any mixing. Any leftover samples need to be silenced. */ + if (pOutputBus == pFirst && framesProcessed < frameCount) { + ma_silence_pcm_frames(ma_offset_pcm_frames_ptr(pFramesOut, framesProcessed, ma_format_f32, inputChannels), (frameCount - framesProcessed), ma_format_f32, inputChannels); + } + + if (isSilentOutput == MA_FALSE) { + doesOutputBufferHaveContent = MA_TRUE; + } + } else { + /* Seek. */ + ma_node_read_pcm_frames(pOutputBus->pNode, pOutputBus->outputBusIndex, NULL, frameCount, &framesProcessed, globalTime); + } + } + + /* If we didn't output anything, output silence. */ + if (doesOutputBufferHaveContent == MA_FALSE && pFramesOut != NULL) { + ma_silence_pcm_frames(pFramesOut, frameCount, ma_format_f32, inputChannels); + } + + /* In this path we always "process" the entire amount. */ + *pFramesRead = frameCount; + + return result; +} + + +MA_API ma_node_config ma_node_config_init(void) +{ + ma_node_config config; + + MA_ZERO_OBJECT(&config); + config.initialState = ma_node_state_started; /* Nodes are started by default. */ + config.inputBusCount = MA_NODE_BUS_COUNT_UNKNOWN; + config.outputBusCount = MA_NODE_BUS_COUNT_UNKNOWN; + + return config; +} + + + +static ma_result ma_node_detach_full(ma_node* pNode); + +static float* ma_node_get_cached_input_ptr(ma_node* pNode, ma_uint32 inputBusIndex) +{ + ma_node_base* pNodeBase = (ma_node_base*)pNode; + ma_uint32 iInputBus; + float* pBasePtr; + + MA_ASSERT(pNodeBase != NULL); + + /* Input data is stored at the front of the buffer. */ + pBasePtr = pNodeBase->pCachedData; + for (iInputBus = 0; iInputBus < inputBusIndex; iInputBus += 1) { + pBasePtr += pNodeBase->cachedDataCapInFramesPerBus * ma_node_input_bus_get_channels(&pNodeBase->pInputBuses[iInputBus]); + } + + return pBasePtr; +} + +static float* ma_node_get_cached_output_ptr(ma_node* pNode, ma_uint32 outputBusIndex) +{ + ma_node_base* pNodeBase = (ma_node_base*)pNode; + ma_uint32 iInputBus; + ma_uint32 iOutputBus; + float* pBasePtr; + + MA_ASSERT(pNodeBase != NULL); + + /* Cached output data starts after the input data. */ + pBasePtr = pNodeBase->pCachedData; + for (iInputBus = 0; iInputBus < ma_node_get_input_bus_count(pNodeBase); iInputBus += 1) { + pBasePtr += pNodeBase->cachedDataCapInFramesPerBus * ma_node_input_bus_get_channels(&pNodeBase->pInputBuses[iInputBus]); + } + + for (iOutputBus = 0; iOutputBus < outputBusIndex; iOutputBus += 1) { + pBasePtr += pNodeBase->cachedDataCapInFramesPerBus * ma_node_output_bus_get_channels(&pNodeBase->pOutputBuses[iOutputBus]); + } + + return pBasePtr; +} + + +typedef struct +{ + size_t sizeInBytes; + size_t inputBusOffset; + size_t outputBusOffset; + size_t cachedDataOffset; + ma_uint32 inputBusCount; /* So it doesn't have to be calculated twice. */ + ma_uint32 outputBusCount; /* So it doesn't have to be calculated twice. */ +} ma_node_heap_layout; + +static ma_result ma_node_translate_bus_counts(const ma_node_config* pConfig, ma_uint32* pInputBusCount, ma_uint32* pOutputBusCount) +{ + ma_uint32 inputBusCount; + ma_uint32 outputBusCount; + + MA_ASSERT(pConfig != NULL); + MA_ASSERT(pInputBusCount != NULL); + MA_ASSERT(pOutputBusCount != NULL); + + /* Bus counts are determined by the vtable, unless they're set to `MA_NODE_BUS_COUNT_UNKNWON`, in which case they're taken from the config. */ + if (pConfig->vtable->inputBusCount == MA_NODE_BUS_COUNT_UNKNOWN) { + inputBusCount = pConfig->inputBusCount; + } else { + inputBusCount = pConfig->vtable->inputBusCount; + + if (pConfig->inputBusCount != MA_NODE_BUS_COUNT_UNKNOWN && pConfig->inputBusCount != pConfig->vtable->inputBusCount) { + return MA_INVALID_ARGS; /* Invalid configuration. You must not specify a conflicting bus count between the node's config and the vtable. */ + } + } + + if (pConfig->vtable->outputBusCount == MA_NODE_BUS_COUNT_UNKNOWN) { + outputBusCount = pConfig->outputBusCount; + } else { + outputBusCount = pConfig->vtable->outputBusCount; + + if (pConfig->outputBusCount != MA_NODE_BUS_COUNT_UNKNOWN && pConfig->outputBusCount != pConfig->vtable->outputBusCount) { + return MA_INVALID_ARGS; /* Invalid configuration. You must not specify a conflicting bus count between the node's config and the vtable. */ + } + } + + /* Bus counts must be within limits. */ + if (inputBusCount > MA_MAX_NODE_BUS_COUNT || outputBusCount > MA_MAX_NODE_BUS_COUNT) { + return MA_INVALID_ARGS; + } + + + /* We must have channel counts for each bus. */ + if ((inputBusCount > 0 && pConfig->pInputChannels == NULL) || (outputBusCount > 0 && pConfig->pOutputChannels == NULL)) { + return MA_INVALID_ARGS; /* You must specify channel counts for each input and output bus. */ + } + + + /* Some special rules for passthrough nodes. */ + if ((pConfig->vtable->flags & MA_NODE_FLAG_PASSTHROUGH) != 0) { + if (pConfig->vtable->inputBusCount != 1 || pConfig->vtable->outputBusCount != 1) { + return MA_INVALID_ARGS; /* Passthrough nodes must have exactly 1 input bus and 1 output bus. */ + } + + if (pConfig->pInputChannels[0] != pConfig->pOutputChannels[0]) { + return MA_INVALID_ARGS; /* Passthrough nodes must have the same number of channels between input and output nodes. */ + } + } + + + *pInputBusCount = inputBusCount; + *pOutputBusCount = outputBusCount; + + return MA_SUCCESS; +} + +static ma_result ma_node_get_heap_layout(ma_node_graph* pNodeGraph, const ma_node_config* pConfig, ma_node_heap_layout* pHeapLayout) +{ + ma_result result; + ma_uint32 inputBusCount; + ma_uint32 outputBusCount; + + MA_ASSERT(pHeapLayout != NULL); + + MA_ZERO_OBJECT(pHeapLayout); + + if (pConfig == NULL || pConfig->vtable == NULL || pConfig->vtable->onProcess == NULL) { + return MA_INVALID_ARGS; + } + + result = ma_node_translate_bus_counts(pConfig, &inputBusCount, &outputBusCount); + if (result != MA_SUCCESS) { + return result; + } + + pHeapLayout->sizeInBytes = 0; + + /* Input buses. */ + if (inputBusCount > MA_MAX_NODE_LOCAL_BUS_COUNT) { + pHeapLayout->inputBusOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += ma_align_64(sizeof(ma_node_input_bus) * inputBusCount); + } else { + pHeapLayout->inputBusOffset = MA_SIZE_MAX; /* MA_SIZE_MAX indicates that no heap allocation is required for the input bus. */ + } + + /* Output buses. */ + if (outputBusCount > MA_MAX_NODE_LOCAL_BUS_COUNT) { + pHeapLayout->outputBusOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += ma_align_64(sizeof(ma_node_output_bus) * outputBusCount); + } else { + pHeapLayout->outputBusOffset = MA_SIZE_MAX; + } + + /* + Cached audio data. + + We need to allocate memory for a caching both input and output data. We have an optimization + where no caching is necessary for specific conditions: + + - The node has 0 inputs and 1 output. + + When a node meets the above conditions, no cache is allocated. + + The size choice for this buffer is a little bit finicky. We don't want to be too wasteful by + allocating too much, but at the same time we want it be large enough so that enough frames can + be processed for each call to ma_node_read_pcm_frames() so that it keeps things efficient. For + now I'm going with 10ms @ 48K which is 480 frames per bus. This is configurable at compile + time. It might also be worth investigating whether or not this can be configured at run time. + */ + if (inputBusCount == 0 && outputBusCount == 1) { + /* Fast path. No cache needed. */ + pHeapLayout->cachedDataOffset = MA_SIZE_MAX; + } else { + /* Slow path. Cache needed. */ + size_t cachedDataSizeInBytes = 0; + ma_uint32 iBus; + + for (iBus = 0; iBus < inputBusCount; iBus += 1) { + cachedDataSizeInBytes += pNodeGraph->nodeCacheCapInFrames * ma_get_bytes_per_frame(ma_format_f32, pConfig->pInputChannels[iBus]); + } + + for (iBus = 0; iBus < outputBusCount; iBus += 1) { + cachedDataSizeInBytes += pNodeGraph->nodeCacheCapInFrames * ma_get_bytes_per_frame(ma_format_f32, pConfig->pOutputChannels[iBus]); + } + + pHeapLayout->cachedDataOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += ma_align_64(cachedDataSizeInBytes); + } + + + /* + Not technically part of the heap, but we can output the input and output bus counts so we can + avoid a redundant call to ma_node_translate_bus_counts(). + */ + pHeapLayout->inputBusCount = inputBusCount; + pHeapLayout->outputBusCount = outputBusCount; + + /* Make sure allocation size is aligned. */ + pHeapLayout->sizeInBytes = ma_align_64(pHeapLayout->sizeInBytes); + + return MA_SUCCESS; +} + +MA_API ma_result ma_node_get_heap_size(ma_node_graph* pNodeGraph, const ma_node_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_result result; + ma_node_heap_layout heapLayout; + + if (pHeapSizeInBytes == NULL) { + return MA_INVALID_ARGS; + } + + *pHeapSizeInBytes = 0; + + result = ma_node_get_heap_layout(pNodeGraph, pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + *pHeapSizeInBytes = heapLayout.sizeInBytes; + + return MA_SUCCESS; +} + +MA_API ma_result ma_node_init_preallocated(ma_node_graph* pNodeGraph, const ma_node_config* pConfig, void* pHeap, ma_node* pNode) +{ + ma_node_base* pNodeBase = (ma_node_base*)pNode; + ma_result result; + ma_node_heap_layout heapLayout; + ma_uint32 iInputBus; + ma_uint32 iOutputBus; + + if (pNodeBase == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pNodeBase); + + result = ma_node_get_heap_layout(pNodeGraph, pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + pNodeBase->_pHeap = pHeap; + MA_ZERO_MEMORY(pHeap, heapLayout.sizeInBytes); + + pNodeBase->pNodeGraph = pNodeGraph; + pNodeBase->vtable = pConfig->vtable; + pNodeBase->state = pConfig->initialState; + pNodeBase->stateTimes[ma_node_state_started] = 0; + pNodeBase->stateTimes[ma_node_state_stopped] = (ma_uint64)(ma_int64)-1; /* Weird casting for VC6 compatibility. */ + pNodeBase->inputBusCount = heapLayout.inputBusCount; + pNodeBase->outputBusCount = heapLayout.outputBusCount; + + if (heapLayout.inputBusOffset != MA_SIZE_MAX) { + pNodeBase->pInputBuses = (ma_node_input_bus*)ma_offset_ptr(pHeap, heapLayout.inputBusOffset); + } else { + pNodeBase->pInputBuses = pNodeBase->_inputBuses; + } + + if (heapLayout.outputBusOffset != MA_SIZE_MAX) { + pNodeBase->pOutputBuses = (ma_node_output_bus*)ma_offset_ptr(pHeap, heapLayout.inputBusOffset); + } else { + pNodeBase->pOutputBuses = pNodeBase->_outputBuses; + } + + if (heapLayout.cachedDataOffset != MA_SIZE_MAX) { + pNodeBase->pCachedData = (float*)ma_offset_ptr(pHeap, heapLayout.cachedDataOffset); + pNodeBase->cachedDataCapInFramesPerBus = pNodeGraph->nodeCacheCapInFrames; + } else { + pNodeBase->pCachedData = NULL; + } + + + + /* We need to run an initialization step for each input and output bus. */ + for (iInputBus = 0; iInputBus < ma_node_get_input_bus_count(pNodeBase); iInputBus += 1) { + result = ma_node_input_bus_init(pConfig->pInputChannels[iInputBus], &pNodeBase->pInputBuses[iInputBus]); + if (result != MA_SUCCESS) { + return result; + } + } + + for (iOutputBus = 0; iOutputBus < ma_node_get_output_bus_count(pNodeBase); iOutputBus += 1) { + result = ma_node_output_bus_init(pNodeBase, iOutputBus, pConfig->pOutputChannels[iOutputBus], &pNodeBase->pOutputBuses[iOutputBus]); + if (result != MA_SUCCESS) { + return result; + } + } + + + /* The cached data needs to be initialized to silence (or a sine wave tone if we're debugging). */ + if (pNodeBase->pCachedData != NULL) { + ma_uint32 iBus; + + #if 1 /* Toggle this between 0 and 1 to turn debugging on or off. 1 = fill with a sine wave for debugging; 0 = fill with silence. */ + /* For safety we'll go ahead and default the buffer to silence. */ + for (iBus = 0; iBus < ma_node_get_input_bus_count(pNodeBase); iBus += 1) { + ma_silence_pcm_frames(ma_node_get_cached_input_ptr(pNode, iBus), pNodeBase->cachedDataCapInFramesPerBus, ma_format_f32, ma_node_input_bus_get_channels(&pNodeBase->pInputBuses[iBus])); + } + for (iBus = 0; iBus < ma_node_get_output_bus_count(pNodeBase); iBus += 1) { + ma_silence_pcm_frames(ma_node_get_cached_output_ptr(pNode, iBus), pNodeBase->cachedDataCapInFramesPerBus, ma_format_f32, ma_node_output_bus_get_channels(&pNodeBase->pOutputBuses[iBus])); + } + #else + /* For debugging. Default to a sine wave. */ + for (iBus = 0; iBus < ma_node_get_input_bus_count(pNodeBase); iBus += 1) { + ma_debug_fill_pcm_frames_with_sine_wave(ma_node_get_cached_input_ptr(pNode, iBus), pNodeBase->cachedDataCapInFramesPerBus, ma_format_f32, ma_node_input_bus_get_channels(&pNodeBase->pInputBuses[iBus]), 48000); + } + for (iBus = 0; iBus < ma_node_get_output_bus_count(pNodeBase); iBus += 1) { + ma_debug_fill_pcm_frames_with_sine_wave(ma_node_get_cached_output_ptr(pNode, iBus), pNodeBase->cachedDataCapInFramesPerBus, ma_format_f32, ma_node_output_bus_get_channels(&pNodeBase->pOutputBuses[iBus]), 48000); + } + #endif + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_node_init(ma_node_graph* pNodeGraph, const ma_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_node* pNode) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_node_get_heap_size(pNodeGraph, pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_node_init_preallocated(pNodeGraph, pConfig, pHeap, pNode); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + ((ma_node_base*)pNode)->_ownsHeap = MA_TRUE; + return MA_SUCCESS; +} + +MA_API void ma_node_uninit(ma_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks) +{ + ma_node_base* pNodeBase = (ma_node_base*)pNode; + + if (pNodeBase == NULL) { + return; + } + + /* + The first thing we need to do is fully detach the node. This will detach all inputs and + outputs. We need to do this first because it will sever the connection with the node graph and + allow us to complete uninitialization without needing to worry about thread-safety with the + audio thread. The detachment process will wait for any local processing of the node to finish. + */ + ma_node_detach_full(pNode); + + /* + At this point the node should be completely unreferenced by the node graph and we can finish up + the uninitialization process without needing to worry about thread-safety. + */ + if (pNodeBase->_ownsHeap) { + ma_free(pNodeBase->_pHeap, pAllocationCallbacks); + } +} + +MA_API ma_node_graph* ma_node_get_node_graph(const ma_node* pNode) +{ + if (pNode == NULL) { + return NULL; + } + + return ((const ma_node_base*)pNode)->pNodeGraph; +} + +MA_API ma_uint32 ma_node_get_input_bus_count(const ma_node* pNode) +{ + if (pNode == NULL) { + return 0; + } + + return ((ma_node_base*)pNode)->inputBusCount; +} + +MA_API ma_uint32 ma_node_get_output_bus_count(const ma_node* pNode) +{ + if (pNode == NULL) { + return 0; + } + + return ((ma_node_base*)pNode)->outputBusCount; +} + + +MA_API ma_uint32 ma_node_get_input_channels(const ma_node* pNode, ma_uint32 inputBusIndex) +{ + const ma_node_base* pNodeBase = (const ma_node_base*)pNode; + + if (pNode == NULL) { + return 0; + } + + if (inputBusIndex >= ma_node_get_input_bus_count(pNode)) { + return 0; /* Invalid bus index. */ + } + + return ma_node_input_bus_get_channels(&pNodeBase->pInputBuses[inputBusIndex]); +} + +MA_API ma_uint32 ma_node_get_output_channels(const ma_node* pNode, ma_uint32 outputBusIndex) +{ + const ma_node_base* pNodeBase = (const ma_node_base*)pNode; + + if (pNode == NULL) { + return 0; + } + + if (outputBusIndex >= ma_node_get_output_bus_count(pNode)) { + return 0; /* Invalid bus index. */ + } + + return ma_node_output_bus_get_channels(&pNodeBase->pOutputBuses[outputBusIndex]); +} + + +static ma_result ma_node_detach_full(ma_node* pNode) +{ + ma_node_base* pNodeBase = (ma_node_base*)pNode; + ma_uint32 iInputBus; + + if (pNodeBase == NULL) { + return MA_INVALID_ARGS; + } + + /* + Make sure the node is completely detached first. This will not return until the output bus is + guaranteed to no longer be referenced by the audio thread. + */ + ma_node_detach_all_output_buses(pNode); + + /* + At this point all output buses will have been detached from the graph and we can be guaranteed + that none of it's input nodes will be getting processed by the graph. We can detach these + without needing to worry about the audio thread touching them. + */ + for (iInputBus = 0; iInputBus < ma_node_get_input_bus_count(pNode); iInputBus += 1) { + ma_node_input_bus* pInputBus; + ma_node_output_bus* pOutputBus; + + pInputBus = &pNodeBase->pInputBuses[iInputBus]; + + /* + This is important. We cannot be using ma_node_input_bus_first() or ma_node_input_bus_next(). Those + functions are specifically for the audio thread. We'll instead just manually iterate using standard + linked list logic. We don't need to worry about the audio thread referencing these because the step + above severed the connection to the graph. + */ + for (pOutputBus = (ma_node_output_bus*)c89atomic_load_ptr(&pInputBus->head.pNext); pOutputBus != NULL; pOutputBus = (ma_node_output_bus*)c89atomic_load_ptr(&pOutputBus->pNext)) { + ma_node_detach_output_bus(pOutputBus->pNode, pOutputBus->outputBusIndex); /* This won't do any waiting in practice and should be efficient. */ + } + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_node_detach_output_bus(ma_node* pNode, ma_uint32 outputBusIndex) +{ + ma_result result = MA_SUCCESS; + ma_node_base* pNodeBase = (ma_node_base*)pNode; + ma_node_base* pInputNodeBase; + + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + if (outputBusIndex >= ma_node_get_output_bus_count(pNode)) { + return MA_INVALID_ARGS; /* Invalid output bus index. */ + } + + /* We need to lock the output bus because we need to inspect the input node and grab it's input bus. */ + ma_node_output_bus_lock(&pNodeBase->pOutputBuses[outputBusIndex]); + { + pInputNodeBase = (ma_node_base*)pNodeBase->pOutputBuses[outputBusIndex].pInputNode; + if (pInputNodeBase != NULL) { + ma_node_input_bus_detach__no_output_bus_lock(&pInputNodeBase->pInputBuses[pNodeBase->pOutputBuses[outputBusIndex].inputNodeInputBusIndex], &pNodeBase->pOutputBuses[outputBusIndex]); + } + } + ma_node_output_bus_unlock(&pNodeBase->pOutputBuses[outputBusIndex]); + + return result; +} + +MA_API ma_result ma_node_detach_all_output_buses(ma_node* pNode) +{ + ma_uint32 iOutputBus; + + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + for (iOutputBus = 0; iOutputBus < ma_node_get_output_bus_count(pNode); iOutputBus += 1) { + ma_node_detach_output_bus(pNode, iOutputBus); + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_node_attach_output_bus(ma_node* pNode, ma_uint32 outputBusIndex, ma_node* pOtherNode, ma_uint32 otherNodeInputBusIndex) +{ + ma_node_base* pNodeBase = (ma_node_base*)pNode; + ma_node_base* pOtherNodeBase = (ma_node_base*)pOtherNode; + + if (pNodeBase == NULL || pOtherNodeBase == NULL) { + return MA_INVALID_ARGS; + } + + if (pNodeBase == pOtherNodeBase) { + return MA_INVALID_OPERATION; /* Cannot attach a node to itself. */ + } + + if (outputBusIndex >= ma_node_get_output_bus_count(pNode) || otherNodeInputBusIndex >= ma_node_get_input_bus_count(pOtherNode)) { + return MA_INVALID_OPERATION; /* Invalid bus index. */ + } + + /* The output channel count of the output node must be the same as the input channel count of the input node. */ + if (ma_node_get_output_channels(pNode, outputBusIndex) != ma_node_get_input_channels(pOtherNode, otherNodeInputBusIndex)) { + return MA_INVALID_OPERATION; /* Channel count is incompatible. */ + } + + /* This will deal with detaching if the output bus is already attached to something. */ + ma_node_input_bus_attach(&pOtherNodeBase->pInputBuses[otherNodeInputBusIndex], &pNodeBase->pOutputBuses[outputBusIndex], pOtherNode, otherNodeInputBusIndex); + + return MA_SUCCESS; +} + +MA_API ma_result ma_node_set_output_bus_volume(ma_node* pNode, ma_uint32 outputBusIndex, float volume) +{ + ma_node_base* pNodeBase = (ma_node_base*)pNode; + + if (pNodeBase == NULL) { + return MA_INVALID_ARGS; + } + + if (outputBusIndex >= ma_node_get_output_bus_count(pNode)) { + return MA_INVALID_ARGS; /* Invalid bus index. */ + } + + return ma_node_output_bus_set_volume(&pNodeBase->pOutputBuses[outputBusIndex], volume); +} + +MA_API float ma_node_get_output_bus_volume(const ma_node* pNode, ma_uint32 outputBusIndex) +{ + const ma_node_base* pNodeBase = (const ma_node_base*)pNode; + + if (pNodeBase == NULL) { + return 0; + } + + if (outputBusIndex >= ma_node_get_output_bus_count(pNode)) { + return 0; /* Invalid bus index. */ + } + + return ma_node_output_bus_get_volume(&pNodeBase->pOutputBuses[outputBusIndex]); +} + +MA_API ma_result ma_node_set_state(ma_node* pNode, ma_node_state state) +{ + ma_node_base* pNodeBase = (ma_node_base*)pNode; + + if (pNodeBase == NULL) { + return MA_INVALID_ARGS; + } + + c89atomic_exchange_i32(&pNodeBase->state, state); + + return MA_SUCCESS; +} + +MA_API ma_node_state ma_node_get_state(const ma_node* pNode) +{ + const ma_node_base* pNodeBase = (const ma_node_base*)pNode; + + if (pNodeBase == NULL) { + return ma_node_state_stopped; + } + + return (ma_node_state)c89atomic_load_i32(&pNodeBase->state); +} + +MA_API ma_result ma_node_set_state_time(ma_node* pNode, ma_node_state state, ma_uint64 globalTime) +{ + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + /* Validation check for safety since we'll be using this as an index into stateTimes[]. */ + if (state != ma_node_state_started && state != ma_node_state_stopped) { + return MA_INVALID_ARGS; + } + + c89atomic_exchange_64(&((ma_node_base*)pNode)->stateTimes[state], globalTime); + + return MA_SUCCESS; +} + +MA_API ma_uint64 ma_node_get_state_time(const ma_node* pNode, ma_node_state state) +{ + if (pNode == NULL) { + return 0; + } + + /* Validation check for safety since we'll be using this as an index into stateTimes[]. */ + if (state != ma_node_state_started && state != ma_node_state_stopped) { + return 0; + } + + return c89atomic_load_64(&((ma_node_base*)pNode)->stateTimes[state]); +} + +MA_API ma_node_state ma_node_get_state_by_time(const ma_node* pNode, ma_uint64 globalTime) +{ + if (pNode == NULL) { + return ma_node_state_stopped; + } + + return ma_node_get_state_by_time_range(pNode, globalTime, globalTime); +} + +MA_API ma_node_state ma_node_get_state_by_time_range(const ma_node* pNode, ma_uint64 globalTimeBeg, ma_uint64 globalTimeEnd) +{ + ma_node_state state; + + if (pNode == NULL) { + return ma_node_state_stopped; + } + + state = ma_node_get_state(pNode); + + /* An explicitly stopped node is always stopped. */ + if (state == ma_node_state_stopped) { + return ma_node_state_stopped; + } + + /* + Getting here means the node is marked as started, but it may still not be truly started due to + it's start time not having been reached yet. Also, the stop time may have also been reached in + which case it'll be considered stopped. + */ + if (ma_node_get_state_time(pNode, ma_node_state_started) > globalTimeBeg) { + return ma_node_state_stopped; /* Start time has not yet been reached. */ + } + + if (ma_node_get_state_time(pNode, ma_node_state_stopped) <= globalTimeEnd) { + return ma_node_state_stopped; /* Stop time has been reached. */ + } + + /* Getting here means the node is marked as started and is within it's start/stop times. */ + return ma_node_state_started; +} + +MA_API ma_uint64 ma_node_get_time(const ma_node* pNode) +{ + if (pNode == NULL) { + return 0; + } + + return c89atomic_load_64(&((ma_node_base*)pNode)->localTime); +} + +MA_API ma_result ma_node_set_time(ma_node* pNode, ma_uint64 localTime) +{ + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + c89atomic_exchange_64(&((ma_node_base*)pNode)->localTime, localTime); + + return MA_SUCCESS; +} + + + +static void ma_node_process_pcm_frames_internal(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + ma_node_base* pNodeBase = (ma_node_base*)pNode; + + MA_ASSERT(pNode != NULL); + + if (pNodeBase->vtable->onProcess) { + pNodeBase->vtable->onProcess(pNode, ppFramesIn, pFrameCountIn, ppFramesOut, pFrameCountOut); + } +} + +static ma_result ma_node_read_pcm_frames(ma_node* pNode, ma_uint32 outputBusIndex, float* pFramesOut, ma_uint32 frameCount, ma_uint32* pFramesRead, ma_uint64 globalTime) +{ + ma_node_base* pNodeBase = (ma_node_base*)pNode; + ma_result result = MA_SUCCESS; + ma_uint32 iInputBus; + ma_uint32 iOutputBus; + ma_uint32 inputBusCount; + ma_uint32 outputBusCount; + ma_uint32 totalFramesRead = 0; + float* ppFramesIn[MA_MAX_NODE_BUS_COUNT]; + float* ppFramesOut[MA_MAX_NODE_BUS_COUNT]; + ma_uint64 globalTimeBeg; + ma_uint64 globalTimeEnd; + ma_uint64 startTime; + ma_uint64 stopTime; + ma_uint32 timeOffsetBeg; + ma_uint32 timeOffsetEnd; + ma_uint32 frameCountIn; + ma_uint32 frameCountOut; + + /* + pFramesRead is mandatory. It must be used to determine how many frames were read. It's normal and + expected that the number of frames read may be different to that requested. Therefore, the caller + must look at this value to correctly determine how many frames were read. + */ + MA_ASSERT(pFramesRead != NULL); /* <-- If you've triggered this assert, you're using this function wrong. You *must* use this variable and inspect it after the call returns. */ + if (pFramesRead == NULL) { + return MA_INVALID_ARGS; + } + + *pFramesRead = 0; /* Safety. */ + + if (pNodeBase == NULL) { + return MA_INVALID_ARGS; + } + + if (outputBusIndex >= ma_node_get_output_bus_count(pNodeBase)) { + return MA_INVALID_ARGS; /* Invalid output bus index. */ + } + + /* Don't do anything if we're in a stopped state. */ + if (ma_node_get_state_by_time_range(pNode, globalTime, globalTime + frameCount) != ma_node_state_started) { + return MA_SUCCESS; /* We're in a stopped state. This is not an error - we just need to not read anything. */ + } + + + globalTimeBeg = globalTime; + globalTimeEnd = globalTime + frameCount; + startTime = ma_node_get_state_time(pNode, ma_node_state_started); + stopTime = ma_node_get_state_time(pNode, ma_node_state_stopped); + + /* + At this point we know that we are inside our start/stop times. However, we may need to adjust + our frame count and output pointer to accomodate since we could be straddling the time period + that this function is getting called for. + + It's possible (and likely) that the start time does not line up with the output buffer. We + therefore need to offset it by a number of frames to accomodate. The same thing applies for + the stop time. + */ + timeOffsetBeg = (globalTimeBeg < startTime) ? (ma_uint32)(globalTimeEnd - startTime) : 0; + timeOffsetEnd = (globalTimeEnd > stopTime) ? (ma_uint32)(globalTimeEnd - stopTime) : 0; + + /* Trim based on the start offset. We need to silence the start of the buffer. */ + if (timeOffsetBeg > 0) { + ma_silence_pcm_frames(pFramesOut, timeOffsetBeg, ma_format_f32, ma_node_get_output_channels(pNode, outputBusIndex)); + pFramesOut += timeOffsetBeg * ma_node_get_output_channels(pNode, outputBusIndex); + frameCount -= timeOffsetBeg; + } + + /* Trim based on the end offset. We don't need to silence the tail section because we'll just have a reduced value written to pFramesRead. */ + if (timeOffsetEnd > 0) { + frameCount -= timeOffsetEnd; + } + + + /* We run on different paths depending on the bus counts. */ + inputBusCount = ma_node_get_input_bus_count(pNode); + outputBusCount = ma_node_get_output_bus_count(pNode); + + /* + Run a simplified path when there are no inputs and one output. In this case there's nothing to + actually read and we can go straight to output. This is a very common scenario because the vast + majority of data source nodes will use this setup so this optimization I think is worthwhile. + */ + if (inputBusCount == 0 && outputBusCount == 1) { + /* Fast path. No need to read from input and no need for any caching. */ + frameCountIn = 0; + frameCountOut = frameCount; /* Just read as much as we can. The callback will return what was actually read. */ + + ppFramesOut[0] = pFramesOut; + ma_node_process_pcm_frames_internal(pNode, NULL, &frameCountIn, ppFramesOut, &frameCountOut); + totalFramesRead = frameCountOut; + } else { + /* Slow path. Need to read input data. */ + if ((pNodeBase->vtable->flags & MA_NODE_FLAG_PASSTHROUGH) != 0) { + /* + Fast path. We're running a passthrough. We need to read directly into the output buffer, but + still fire the callback so that event handling and trigger nodes can do their thing. Since + it's a passthrough there's no need for any kind of caching logic. + */ + MA_ASSERT(outputBusCount == inputBusCount); + MA_ASSERT(outputBusCount == 1); + MA_ASSERT(outputBusIndex == 0); + + /* We just read directly from input bus to output buffer, and then afterwards fire the callback. */ + ppFramesOut[0] = pFramesOut; + ppFramesIn[0] = ppFramesOut[0]; + + result = ma_node_input_bus_read_pcm_frames(pNodeBase, &pNodeBase->pInputBuses[0], ppFramesIn[0], frameCount, &totalFramesRead, globalTime); + if (result == MA_SUCCESS) { + /* Even though it's a passthrough, we still need to fire the callback. */ + frameCountIn = totalFramesRead; + frameCountOut = totalFramesRead; + + if (totalFramesRead > 0) { + ma_node_process_pcm_frames_internal(pNode, (const float**)ppFramesIn, &frameCountIn, ppFramesOut, &frameCountOut); /* From GCC: expected 'const float **' but argument is of type 'float **'. Shouldn't this be implicit? Excplicit cast to silence the warning. */ + } + + /* + A passthrough should never have modified the input and output frame counts. If you're + triggering these assers you need to fix your processing callback. + */ + MA_ASSERT(frameCountIn == totalFramesRead); + MA_ASSERT(frameCountOut == totalFramesRead); + } + } else { + /* Slow path. Need to do caching. */ + ma_uint32 framesToProcessIn; + ma_uint32 framesToProcessOut; + ma_bool32 consumeNullInput = MA_FALSE; + + /* + We use frameCount as a basis for the number of frames to read since that's what's being + requested, however we still need to clamp it to whatever can fit in the cache. + + This will also be used as the basis for determining how many input frames to read. This is + not ideal because it can result in too many input frames being read which introduces latency. + To solve this, nodes can implement an optional callback called onGetRequiredInputFrameCount + which is used as hint to miniaudio as to how many input frames it needs to read at a time. This + callback is completely optional, and if it's not set, miniaudio will assume `frameCount`. + + This function will be called multiple times for each period of time, once for each output node. + We cannot read from each input node each time this function is called. Instead we need to check + whether or not this is first output bus to be read from for this time period, and if so, read + from our input data. + + To determine whether or not we're ready to read data, we check a flag. There will be one flag + for each output. When the flag is set, it means data has been read previously and that we're + ready to advance time forward for our input nodes by reading fresh data. + */ + framesToProcessOut = frameCount; + if (framesToProcessOut > pNodeBase->cachedDataCapInFramesPerBus) { + framesToProcessOut = pNodeBase->cachedDataCapInFramesPerBus; + } + + framesToProcessIn = frameCount; + if (pNodeBase->vtable->onGetRequiredInputFrameCount) { + pNodeBase->vtable->onGetRequiredInputFrameCount(pNode, framesToProcessOut, &framesToProcessIn); /* <-- It does not matter if this fails. */ + } + if (framesToProcessIn > pNodeBase->cachedDataCapInFramesPerBus) { + framesToProcessIn = pNodeBase->cachedDataCapInFramesPerBus; + } + + + MA_ASSERT(framesToProcessIn <= 0xFFFF); + MA_ASSERT(framesToProcessOut <= 0xFFFF); + + if (ma_node_output_bus_has_read(&pNodeBase->pOutputBuses[outputBusIndex])) { + /* Getting here means we need to do another round of processing. */ + pNodeBase->cachedFrameCountOut = 0; + + for (;;) { + frameCountOut = 0; + + /* + We need to prepare our output frame pointers for processing. In the same iteration we need + to mark every output bus as unread so that future calls to this function for different buses + for the current time period don't pull in data when they should instead be reading from cache. + */ + for (iOutputBus = 0; iOutputBus < outputBusCount; iOutputBus += 1) { + ma_node_output_bus_set_has_read(&pNodeBase->pOutputBuses[iOutputBus], MA_FALSE); /* <-- This is what tells the next calls to this function for other output buses for this time period to read from cache instead of pulling in more data. */ + ppFramesOut[iOutputBus] = ma_node_get_cached_output_ptr(pNode, iOutputBus); + } + + /* We only need to read from input buses if there isn't already some data in the cache. */ + if (pNodeBase->cachedFrameCountIn == 0) { + ma_uint32 maxFramesReadIn = 0; + + /* Here is where we pull in data from the input buses. This is what will trigger an advance in time. */ + for (iInputBus = 0; iInputBus < inputBusCount; iInputBus += 1) { + ma_uint32 framesRead; + + /* The first thing to do is get the offset within our bulk allocation to store this input data. */ + ppFramesIn[iInputBus] = ma_node_get_cached_input_ptr(pNode, iInputBus); + + /* Once we've determined our destination pointer we can read. Note that we must inspect the number of frames read and fill any leftovers with silence for safety. */ + result = ma_node_input_bus_read_pcm_frames(pNodeBase, &pNodeBase->pInputBuses[iInputBus], ppFramesIn[iInputBus], framesToProcessIn, &framesRead, globalTime); + if (result != MA_SUCCESS) { + /* It doesn't really matter if we fail because we'll just fill with silence. */ + framesRead = 0; /* Just for safety, but I don't think it's really needed. */ + } + + /* TODO: Minor optimization opportunity here. If no frames were read and the buffer is already filled with silence, no need to re-silence it. */ + /* Any leftover frames need to silenced for safety. */ + if (framesRead < framesToProcessIn) { + ma_silence_pcm_frames(ppFramesIn[iInputBus] + (framesRead * ma_node_get_input_channels(pNodeBase, iInputBus)), (framesToProcessIn - framesRead), ma_format_f32, ma_node_get_input_channels(pNodeBase, iInputBus)); + } + + maxFramesReadIn = ma_max(maxFramesReadIn, framesRead); + } + + /* This was a fresh load of input data so reset our consumption counter. */ + pNodeBase->consumedFrameCountIn = 0; + + /* + We don't want to keep processing if there's nothing to process, so set the number of cached + input frames to the maximum number we read from each attachment (the lesser will be padded + with silence). If we didn't read anything, this will be set to 0 and the entire buffer will + have been assigned to silence. This being equal to 0 is an important property for us because + it allows us to detect when NULL can be passed into the processing callback for the input + buffer for the purpose of continuous processing. + */ + pNodeBase->cachedFrameCountIn = (ma_uint16)maxFramesReadIn; + } else { + /* We don't need to read anything, but we do need to prepare our input frame pointers. */ + for (iInputBus = 0; iInputBus < inputBusCount; iInputBus += 1) { + ppFramesIn[iInputBus] = ma_node_get_cached_input_ptr(pNode, iInputBus) + (pNodeBase->consumedFrameCountIn * ma_node_get_input_channels(pNodeBase, iInputBus)); + } + } + + /* + At this point we have our input data so now we need to do some processing. Sneaky little + optimization here - we can set the pointer to the output buffer for this output bus so + that the final copy into the output buffer is done directly by onProcess(). + */ + if (pFramesOut != NULL) { + ppFramesOut[outputBusIndex] = ma_offset_pcm_frames_ptr_f32(pFramesOut, pNodeBase->cachedFrameCountOut, ma_node_get_output_channels(pNode, outputBusIndex)); + } + + + /* Give the processing function the entire capacity of the output buffer. */ + frameCountOut = (framesToProcessOut - pNodeBase->cachedFrameCountOut); + + /* + We need to treat nodes with continuous processing a little differently. For these ones, + we always want to fire the callback with the requested number of frames, regardless of + pNodeBase->cachedFrameCountIn, which could be 0. Also, we want to check if we can pass + in NULL for the input buffer to the callback. + */ + if ((pNodeBase->vtable->flags & MA_NODE_FLAG_CONTINUOUS_PROCESSING) != 0) { + /* We're using continuous processing. Make sure we specify the whole frame count at all times. */ + frameCountIn = framesToProcessIn; /* Give the processing function as much input data as we've got in the buffer, including any silenced padding from short reads. */ + + if ((pNodeBase->vtable->flags & MA_NODE_FLAG_ALLOW_NULL_INPUT) != 0 && pNodeBase->consumedFrameCountIn == 0 && pNodeBase->cachedFrameCountIn == 0) { + consumeNullInput = MA_TRUE; + } else { + consumeNullInput = MA_FALSE; + } + + /* + Since we're using continuous processing we're always passing in a full frame count + regardless of how much input data was read. If this is greater than what we read as + input, we'll end up with an underflow. We instead need to make sure our cached frame + count is set to the number of frames we'll be passing to the data callback. Not + doing this will result in an underflow when we "consume" the cached data later on. + + Note that this check needs to be done after the "consumeNullInput" check above because + we use the property of cachedFrameCountIn being 0 to determine whether or not we + should be passing in a null pointer to the processing callback for when the node is + configured with MA_NODE_FLAG_ALLOW_NULL_INPUT. + */ + if (pNodeBase->cachedFrameCountIn < (ma_uint16)frameCountIn) { + pNodeBase->cachedFrameCountIn = (ma_uint16)frameCountIn; + } + } else { + frameCountIn = pNodeBase->cachedFrameCountIn; /* Give the processing function as much valid input data as we've got. */ + consumeNullInput = MA_FALSE; + } + + /* + Process data slightly differently depending on whether or not we're consuming NULL + input (checked just above). + */ + if (consumeNullInput) { + ma_node_process_pcm_frames_internal(pNode, NULL, &frameCountIn, ppFramesOut, &frameCountOut); + } else { + /* + We want to skip processing if there's no input data, but we can only do that safely if + we know that there is no chance of any output frames being produced. If continuous + processing is being used, this won't be a problem because the input frame count will + always be non-0. However, if continuous processing is *not* enabled and input and output + data is processed at different rates, we still need to process that last input frame + because there could be a few excess output frames needing to be produced from cached + data. The `MA_NODE_FLAG_DIFFERENT_PROCESSING_RATES` flag is used as the indicator for + determining whether or not we need to process the node even when there are no input + frames available right now. + */ + if (frameCountIn > 0 || (pNodeBase->vtable->flags & MA_NODE_FLAG_DIFFERENT_PROCESSING_RATES) != 0) { + ma_node_process_pcm_frames_internal(pNode, (const float**)ppFramesIn, &frameCountIn, ppFramesOut, &frameCountOut); /* From GCC: expected 'const float **' but argument is of type 'float **'. Shouldn't this be implicit? Excplicit cast to silence the warning. */ + } else { + frameCountOut = 0; /* No data was processed. */ + } + } + + /* + Thanks to our sneaky optimization above we don't need to do any data copying directly into + the output buffer - the onProcess() callback just did that for us. We do, however, need to + apply the number of input and output frames that were processed. Note that due to continuous + processing above, we need to do explicit checks here. If we just consumed a NULL input + buffer it means that no actual input data was processed from the internal buffers and we + don't want to be modifying any counters. + */ + if (consumeNullInput == MA_FALSE) { + pNodeBase->consumedFrameCountIn += (ma_uint16)frameCountIn; + pNodeBase->cachedFrameCountIn -= (ma_uint16)frameCountIn; + } + + /* The cached output frame count is always equal to what we just read. */ + pNodeBase->cachedFrameCountOut += (ma_uint16)frameCountOut; + + /* If we couldn't process any data, we're done. The loop needs to be terminated here or else we'll get stuck in a loop. */ + if (pNodeBase->cachedFrameCountOut == framesToProcessOut || (frameCountOut == 0 && frameCountIn == 0)) { + break; + } + } + } else { + /* + We're not needing to read anything from the input buffer so just read directly from our + already-processed data. + */ + if (pFramesOut != NULL) { + ma_copy_pcm_frames(pFramesOut, ma_node_get_cached_output_ptr(pNodeBase, outputBusIndex), pNodeBase->cachedFrameCountOut, ma_format_f32, ma_node_get_output_channels(pNodeBase, outputBusIndex)); + } + } + + /* The number of frames read is always equal to the number of cached output frames. */ + totalFramesRead = pNodeBase->cachedFrameCountOut; + + /* Now that we've read the data, make sure our read flag is set. */ + ma_node_output_bus_set_has_read(&pNodeBase->pOutputBuses[outputBusIndex], MA_TRUE); + } + } + + /* Apply volume, if necessary. */ + ma_apply_volume_factor_f32(pFramesOut, totalFramesRead * ma_node_get_output_channels(pNodeBase, outputBusIndex), ma_node_output_bus_get_volume(&pNodeBase->pOutputBuses[outputBusIndex])); + + /* Advance our local time forward. */ + c89atomic_fetch_add_64(&pNodeBase->localTime, (ma_uint64)totalFramesRead); + + *pFramesRead = totalFramesRead + timeOffsetBeg; /* Must include the silenced section at the start of the buffer. */ + return result; +} + + + + +/* Data source node. */ +MA_API ma_data_source_node_config ma_data_source_node_config_init(ma_data_source* pDataSource) +{ + ma_data_source_node_config config; + + MA_ZERO_OBJECT(&config); + config.nodeConfig = ma_node_config_init(); + config.pDataSource = pDataSource; + + return config; +} + + +static void ma_data_source_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + ma_data_source_node* pDataSourceNode = (ma_data_source_node*)pNode; + ma_format format; + ma_uint32 channels; + ma_uint32 frameCount; + ma_uint64 framesRead = 0; + + MA_ASSERT(pDataSourceNode != NULL); + MA_ASSERT(pDataSourceNode->pDataSource != NULL); + MA_ASSERT(ma_node_get_input_bus_count(pDataSourceNode) == 0); + MA_ASSERT(ma_node_get_output_bus_count(pDataSourceNode) == 1); + + /* We don't want to read from ppFramesIn at all. Instead we read from the data source. */ + (void)ppFramesIn; + (void)pFrameCountIn; + + frameCount = *pFrameCountOut; + + /* miniaudio should never be calling this with a frame count of zero. */ + MA_ASSERT(frameCount > 0); + + if (ma_data_source_get_data_format(pDataSourceNode->pDataSource, &format, &channels, NULL, NULL, 0) == MA_SUCCESS) { /* <-- Don't care about sample rate here. */ + /* The node graph system requires samples be in floating point format. This is checked in ma_data_source_node_init(). */ + MA_ASSERT(format == ma_format_f32); + (void)format; /* Just to silence some static analysis tools. */ + + ma_data_source_read_pcm_frames(pDataSourceNode->pDataSource, ppFramesOut[0], frameCount, &framesRead); + } + + *pFrameCountOut = (ma_uint32)framesRead; +} + +static ma_node_vtable g_ma_data_source_node_vtable = +{ + ma_data_source_node_process_pcm_frames, + NULL, /* onGetRequiredInputFrameCount */ + 0, /* 0 input buses. */ + 1, /* 1 output bus. */ + 0 +}; + +MA_API ma_result ma_data_source_node_init(ma_node_graph* pNodeGraph, const ma_data_source_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source_node* pDataSourceNode) +{ + ma_result result; + ma_format format; /* For validating the format, which must be ma_format_f32. */ + ma_uint32 channels; /* For specifying the channel count of the output bus. */ + ma_node_config baseConfig; + + if (pDataSourceNode == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pDataSourceNode); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + result = ma_data_source_get_data_format(pConfig->pDataSource, &format, &channels, NULL, NULL, 0); /* Don't care about sample rate. This will check pDataSource for NULL. */ + if (result != MA_SUCCESS) { + return result; + } + + MA_ASSERT(format == ma_format_f32); /* <-- If you've triggered this it means your data source is not outputting floating-point samples. You must configure your data source to use ma_format_f32. */ + if (format != ma_format_f32) { + return MA_INVALID_ARGS; /* Invalid format. */ + } + + /* The channel count is defined by the data source. If the caller has manually changed the channels we just ignore it. */ + baseConfig = pConfig->nodeConfig; + baseConfig.vtable = &g_ma_data_source_node_vtable; /* Explicitly set the vtable here to prevent callers from setting it incorrectly. */ + + /* + The channel count is defined by the data source. It is invalid for the caller to manually set + the channel counts in the config. `ma_data_source_node_config_init()` will have defaulted the + channel count pointer to NULL which is how it must remain. If you trigger any of these asserts + it means you're explicitly setting the channel count. Instead, configure the output channel + count of your data source to be the necessary channel count. + */ + if (baseConfig.pOutputChannels != NULL) { + return MA_INVALID_ARGS; + } + + baseConfig.pOutputChannels = &channels; + + result = ma_node_init(pNodeGraph, &baseConfig, pAllocationCallbacks, &pDataSourceNode->base); + if (result != MA_SUCCESS) { + return result; + } + + pDataSourceNode->pDataSource = pConfig->pDataSource; + + return MA_SUCCESS; +} + +MA_API void ma_data_source_node_uninit(ma_data_source_node* pDataSourceNode, const ma_allocation_callbacks* pAllocationCallbacks) +{ + ma_node_uninit(&pDataSourceNode->base, pAllocationCallbacks); +} + +MA_API ma_result ma_data_source_node_set_looping(ma_data_source_node* pDataSourceNode, ma_bool32 isLooping) +{ + if (pDataSourceNode == NULL) { + return MA_INVALID_ARGS; + } + + return ma_data_source_set_looping(pDataSourceNode->pDataSource, isLooping); +} + +MA_API ma_bool32 ma_data_source_node_is_looping(ma_data_source_node* pDataSourceNode) +{ + if (pDataSourceNode == NULL) { + return MA_FALSE; + } + + return ma_data_source_is_looping(pDataSourceNode->pDataSource); +} + + + +/* Splitter Node. */ +MA_API ma_splitter_node_config ma_splitter_node_config_init(ma_uint32 channels) +{ + ma_splitter_node_config config; + + MA_ZERO_OBJECT(&config); + config.nodeConfig = ma_node_config_init(); + config.channels = channels; + + return config; +} + + +static void ma_splitter_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + ma_node_base* pNodeBase = (ma_node_base*)pNode; + ma_uint32 iOutputBus; + ma_uint32 channels; + + MA_ASSERT(pNodeBase != NULL); + MA_ASSERT(ma_node_get_input_bus_count(pNodeBase) == 1); + MA_ASSERT(ma_node_get_output_bus_count(pNodeBase) >= 2); + + /* We don't need to consider the input frame count - it'll be the same as the output frame count and we process everything. */ + (void)pFrameCountIn; + + /* NOTE: This assumes the same number of channels for all inputs and outputs. This was checked in ma_splitter_node_init(). */ + channels = ma_node_get_input_channels(pNodeBase, 0); + + /* Splitting is just copying the first input bus and copying it over to each output bus. */ + for (iOutputBus = 0; iOutputBus < ma_node_get_output_bus_count(pNodeBase); iOutputBus += 1) { + ma_copy_pcm_frames(ppFramesOut[iOutputBus], ppFramesIn[0], *pFrameCountOut, ma_format_f32, channels); + } +} + +static ma_node_vtable g_ma_splitter_node_vtable = +{ + ma_splitter_node_process_pcm_frames, + NULL, /* onGetRequiredInputFrameCount */ + 1, /* 1 input bus. */ + 2, /* 2 output buses. */ + 0 +}; + +MA_API ma_result ma_splitter_node_init(ma_node_graph* pNodeGraph, const ma_splitter_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_splitter_node* pSplitterNode) +{ + ma_result result; + ma_node_config baseConfig; + ma_uint32 pInputChannels[1]; + ma_uint32 pOutputChannels[2]; + + if (pSplitterNode == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pSplitterNode); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + /* Splitters require the same number of channels between inputs and outputs. */ + pInputChannels[0] = pConfig->channels; + pOutputChannels[0] = pConfig->channels; + pOutputChannels[1] = pConfig->channels; + + baseConfig = pConfig->nodeConfig; + baseConfig.vtable = &g_ma_splitter_node_vtable; + baseConfig.pInputChannels = pInputChannels; + baseConfig.pOutputChannels = pOutputChannels; + + result = ma_node_init(pNodeGraph, &baseConfig, pAllocationCallbacks, &pSplitterNode->base); + if (result != MA_SUCCESS) { + return result; /* Failed to initialize the base node. */ + } + + return MA_SUCCESS; +} + +MA_API void ma_splitter_node_uninit(ma_splitter_node* pSplitterNode, const ma_allocation_callbacks* pAllocationCallbacks) +{ + ma_node_uninit(pSplitterNode, pAllocationCallbacks); +} + + +/* +Biquad Node +*/ +MA_API ma_biquad_node_config ma_biquad_node_config_init(ma_uint32 channels, float b0, float b1, float b2, float a0, float a1, float a2) +{ + ma_biquad_node_config config; + + config.nodeConfig = ma_node_config_init(); + config.biquad = ma_biquad_config_init(ma_format_f32, channels, b0, b1, b2, a0, a1, a2); + + return config; +} + +static void ma_biquad_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + ma_biquad_node* pLPFNode = (ma_biquad_node*)pNode; + + MA_ASSERT(pNode != NULL); + (void)pFrameCountIn; + + ma_biquad_process_pcm_frames(&pLPFNode->biquad, ppFramesOut[0], ppFramesIn[0], *pFrameCountOut); +} + +static ma_node_vtable g_ma_biquad_node_vtable = +{ + ma_biquad_node_process_pcm_frames, + NULL, /* onGetRequiredInputFrameCount */ + 1, /* One input. */ + 1, /* One output. */ + 0 /* Default flags. */ +}; + +MA_API ma_result ma_biquad_node_init(ma_node_graph* pNodeGraph, const ma_biquad_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_biquad_node* pNode) +{ + ma_result result; + ma_node_config baseNodeConfig; + + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pNode); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->biquad.format != ma_format_f32) { + return MA_INVALID_ARGS; /* The format must be f32. */ + } + + result = ma_biquad_init(&pConfig->biquad, pAllocationCallbacks, &pNode->biquad); + if (result != MA_SUCCESS) { + return result; + } + + baseNodeConfig = ma_node_config_init(); + baseNodeConfig.vtable = &g_ma_biquad_node_vtable; + baseNodeConfig.pInputChannels = &pConfig->biquad.channels; + baseNodeConfig.pOutputChannels = &pConfig->biquad.channels; + + result = ma_node_init(pNodeGraph, &baseNodeConfig, pAllocationCallbacks, pNode); + if (result != MA_SUCCESS) { + return result; + } + + return result; +} + +MA_API ma_result ma_biquad_node_reinit(const ma_biquad_config* pConfig, ma_biquad_node* pNode) +{ + ma_biquad_node* pLPFNode = (ma_biquad_node*)pNode; + + MA_ASSERT(pNode != NULL); + + return ma_biquad_reinit(pConfig, &pLPFNode->biquad); +} + +MA_API void ma_biquad_node_uninit(ma_biquad_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks) +{ + ma_biquad_node* pLPFNode = (ma_biquad_node*)pNode; + + if (pNode == NULL) { + return; + } + + ma_node_uninit(pNode, pAllocationCallbacks); + ma_biquad_uninit(&pLPFNode->biquad, pAllocationCallbacks); +} + + + +/* +Low Pass Filter Node +*/ +MA_API ma_lpf_node_config ma_lpf_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, double cutoffFrequency, ma_uint32 order) +{ + ma_lpf_node_config config; + + config.nodeConfig = ma_node_config_init(); + config.lpf = ma_lpf_config_init(ma_format_f32, channels, sampleRate, cutoffFrequency, order); + + return config; +} + +static void ma_lpf_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + ma_lpf_node* pLPFNode = (ma_lpf_node*)pNode; + + MA_ASSERT(pNode != NULL); + (void)pFrameCountIn; + + ma_lpf_process_pcm_frames(&pLPFNode->lpf, ppFramesOut[0], ppFramesIn[0], *pFrameCountOut); +} + +static ma_node_vtable g_ma_lpf_node_vtable = +{ + ma_lpf_node_process_pcm_frames, + NULL, /* onGetRequiredInputFrameCount */ + 1, /* One input. */ + 1, /* One output. */ + 0 /* Default flags. */ +}; + +MA_API ma_result ma_lpf_node_init(ma_node_graph* pNodeGraph, const ma_lpf_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_lpf_node* pNode) +{ + ma_result result; + ma_node_config baseNodeConfig; + + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pNode); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->lpf.format != ma_format_f32) { + return MA_INVALID_ARGS; /* The format must be f32. */ + } + + result = ma_lpf_init(&pConfig->lpf, pAllocationCallbacks, &pNode->lpf); + if (result != MA_SUCCESS) { + return result; + } + + baseNodeConfig = ma_node_config_init(); + baseNodeConfig.vtable = &g_ma_lpf_node_vtable; + baseNodeConfig.pInputChannels = &pConfig->lpf.channels; + baseNodeConfig.pOutputChannels = &pConfig->lpf.channels; + + result = ma_node_init(pNodeGraph, &baseNodeConfig, pAllocationCallbacks, pNode); + if (result != MA_SUCCESS) { + return result; + } + + return result; +} + +MA_API ma_result ma_lpf_node_reinit(const ma_lpf_config* pConfig, ma_lpf_node* pNode) +{ + ma_lpf_node* pLPFNode = (ma_lpf_node*)pNode; + + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + return ma_lpf_reinit(pConfig, &pLPFNode->lpf); +} + +MA_API void ma_lpf_node_uninit(ma_lpf_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks) +{ + ma_lpf_node* pLPFNode = (ma_lpf_node*)pNode; + + if (pNode == NULL) { + return; + } + + ma_node_uninit(pNode, pAllocationCallbacks); + ma_lpf_uninit(&pLPFNode->lpf, pAllocationCallbacks); +} + + + +/* +High Pass Filter Node +*/ +MA_API ma_hpf_node_config ma_hpf_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, double cutoffFrequency, ma_uint32 order) +{ + ma_hpf_node_config config; + + config.nodeConfig = ma_node_config_init(); + config.hpf = ma_hpf_config_init(ma_format_f32, channels, sampleRate, cutoffFrequency, order); + + return config; +} + +static void ma_hpf_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + ma_hpf_node* pHPFNode = (ma_hpf_node*)pNode; + + MA_ASSERT(pNode != NULL); + (void)pFrameCountIn; + + ma_hpf_process_pcm_frames(&pHPFNode->hpf, ppFramesOut[0], ppFramesIn[0], *pFrameCountOut); +} + +static ma_node_vtable g_ma_hpf_node_vtable = +{ + ma_hpf_node_process_pcm_frames, + NULL, /* onGetRequiredInputFrameCount */ + 1, /* One input. */ + 1, /* One output. */ + 0 /* Default flags. */ +}; + +MA_API ma_result ma_hpf_node_init(ma_node_graph* pNodeGraph, const ma_hpf_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_hpf_node* pNode) +{ + ma_result result; + ma_node_config baseNodeConfig; + + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pNode); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->hpf.format != ma_format_f32) { + return MA_INVALID_ARGS; /* The format must be f32. */ + } + + result = ma_hpf_init(&pConfig->hpf, pAllocationCallbacks, &pNode->hpf); + if (result != MA_SUCCESS) { + return result; + } + + baseNodeConfig = ma_node_config_init(); + baseNodeConfig.vtable = &g_ma_hpf_node_vtable; + baseNodeConfig.pInputChannels = &pConfig->hpf.channels; + baseNodeConfig.pOutputChannels = &pConfig->hpf.channels; + + result = ma_node_init(pNodeGraph, &baseNodeConfig, pAllocationCallbacks, pNode); + if (result != MA_SUCCESS) { + return result; + } + + return result; +} + +MA_API ma_result ma_hpf_node_reinit(const ma_hpf_config* pConfig, ma_hpf_node* pNode) +{ + ma_hpf_node* pHPFNode = (ma_hpf_node*)pNode; + + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + return ma_hpf_reinit(pConfig, &pHPFNode->hpf); +} + +MA_API void ma_hpf_node_uninit(ma_hpf_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks) +{ + ma_hpf_node* pHPFNode = (ma_hpf_node*)pNode; + + if (pNode == NULL) { + return; + } + + ma_node_uninit(pNode, pAllocationCallbacks); + ma_hpf_uninit(&pHPFNode->hpf, pAllocationCallbacks); +} + + + + +/* +Band Pass Filter Node +*/ +MA_API ma_bpf_node_config ma_bpf_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, double cutoffFrequency, ma_uint32 order) +{ + ma_bpf_node_config config; + + config.nodeConfig = ma_node_config_init(); + config.bpf = ma_bpf_config_init(ma_format_f32, channels, sampleRate, cutoffFrequency, order); + + return config; +} + +static void ma_bpf_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + ma_bpf_node* pBPFNode = (ma_bpf_node*)pNode; + + MA_ASSERT(pNode != NULL); + (void)pFrameCountIn; + + ma_bpf_process_pcm_frames(&pBPFNode->bpf, ppFramesOut[0], ppFramesIn[0], *pFrameCountOut); +} + +static ma_node_vtable g_ma_bpf_node_vtable = +{ + ma_bpf_node_process_pcm_frames, + NULL, /* onGetRequiredInputFrameCount */ + 1, /* One input. */ + 1, /* One output. */ + 0 /* Default flags. */ +}; + +MA_API ma_result ma_bpf_node_init(ma_node_graph* pNodeGraph, const ma_bpf_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_bpf_node* pNode) +{ + ma_result result; + ma_node_config baseNodeConfig; + + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pNode); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->bpf.format != ma_format_f32) { + return MA_INVALID_ARGS; /* The format must be f32. */ + } + + result = ma_bpf_init(&pConfig->bpf, pAllocationCallbacks, &pNode->bpf); + if (result != MA_SUCCESS) { + return result; + } + + baseNodeConfig = ma_node_config_init(); + baseNodeConfig.vtable = &g_ma_bpf_node_vtable; + baseNodeConfig.pInputChannels = &pConfig->bpf.channels; + baseNodeConfig.pOutputChannels = &pConfig->bpf.channels; + + result = ma_node_init(pNodeGraph, &baseNodeConfig, pAllocationCallbacks, pNode); + if (result != MA_SUCCESS) { + return result; + } + + return result; +} + +MA_API ma_result ma_bpf_node_reinit(const ma_bpf_config* pConfig, ma_bpf_node* pNode) +{ + ma_bpf_node* pBPFNode = (ma_bpf_node*)pNode; + + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + return ma_bpf_reinit(pConfig, &pBPFNode->bpf); +} + +MA_API void ma_bpf_node_uninit(ma_bpf_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks) +{ + ma_bpf_node* pBPFNode = (ma_bpf_node*)pNode; + + if (pNode == NULL) { + return; + } + + ma_node_uninit(pNode, pAllocationCallbacks); + ma_bpf_uninit(&pBPFNode->bpf, pAllocationCallbacks); +} + + + +/* +Notching Filter Node +*/ +MA_API ma_notch_node_config ma_notch_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, double q, double frequency) +{ + ma_notch_node_config config; + + config.nodeConfig = ma_node_config_init(); + config.notch = ma_notch2_config_init(ma_format_f32, channels, sampleRate, q, frequency); + + return config; +} + +static void ma_notch_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + ma_notch_node* pBPFNode = (ma_notch_node*)pNode; + + MA_ASSERT(pNode != NULL); + (void)pFrameCountIn; + + ma_notch2_process_pcm_frames(&pBPFNode->notch, ppFramesOut[0], ppFramesIn[0], *pFrameCountOut); +} + +static ma_node_vtable g_ma_notch_node_vtable = +{ + ma_notch_node_process_pcm_frames, + NULL, /* onGetRequiredInputFrameCount */ + 1, /* One input. */ + 1, /* One output. */ + 0 /* Default flags. */ +}; + +MA_API ma_result ma_notch_node_init(ma_node_graph* pNodeGraph, const ma_notch_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_notch_node* pNode) +{ + ma_result result; + ma_node_config baseNodeConfig; + + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pNode); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->notch.format != ma_format_f32) { + return MA_INVALID_ARGS; /* The format must be f32. */ + } + + result = ma_notch2_init(&pConfig->notch, pAllocationCallbacks, &pNode->notch); + if (result != MA_SUCCESS) { + return result; + } + + baseNodeConfig = ma_node_config_init(); + baseNodeConfig.vtable = &g_ma_notch_node_vtable; + baseNodeConfig.pInputChannels = &pConfig->notch.channels; + baseNodeConfig.pOutputChannels = &pConfig->notch.channels; + + result = ma_node_init(pNodeGraph, &baseNodeConfig, pAllocationCallbacks, pNode); + if (result != MA_SUCCESS) { + return result; + } + + return result; +} + +MA_API ma_result ma_notch_node_reinit(const ma_notch_config* pConfig, ma_notch_node* pNode) +{ + ma_notch_node* pNotchNode = (ma_notch_node*)pNode; + + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + return ma_notch2_reinit(pConfig, &pNotchNode->notch); +} + +MA_API void ma_notch_node_uninit(ma_notch_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks) +{ + ma_notch_node* pNotchNode = (ma_notch_node*)pNode; + + if (pNode == NULL) { + return; + } + + ma_node_uninit(pNode, pAllocationCallbacks); + ma_notch2_uninit(&pNotchNode->notch, pAllocationCallbacks); +} + + + +/* +Peaking Filter Node +*/ +MA_API ma_peak_node_config ma_peak_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, double gainDB, double q, double frequency) +{ + ma_peak_node_config config; + + config.nodeConfig = ma_node_config_init(); + config.peak = ma_peak2_config_init(ma_format_f32, channels, sampleRate, gainDB, q, frequency); + + return config; +} + +static void ma_peak_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + ma_peak_node* pBPFNode = (ma_peak_node*)pNode; + + MA_ASSERT(pNode != NULL); + (void)pFrameCountIn; + + ma_peak2_process_pcm_frames(&pBPFNode->peak, ppFramesOut[0], ppFramesIn[0], *pFrameCountOut); +} + +static ma_node_vtable g_ma_peak_node_vtable = +{ + ma_peak_node_process_pcm_frames, + NULL, /* onGetRequiredInputFrameCount */ + 1, /* One input. */ + 1, /* One output. */ + 0 /* Default flags. */ +}; + +MA_API ma_result ma_peak_node_init(ma_node_graph* pNodeGraph, const ma_peak_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_peak_node* pNode) +{ + ma_result result; + ma_node_config baseNodeConfig; + + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pNode); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->peak.format != ma_format_f32) { + return MA_INVALID_ARGS; /* The format must be f32. */ + } + + result = ma_peak2_init(&pConfig->peak, pAllocationCallbacks, &pNode->peak); + if (result != MA_SUCCESS) { + ma_node_uninit(pNode, pAllocationCallbacks); + return result; + } + + baseNodeConfig = ma_node_config_init(); + baseNodeConfig.vtable = &g_ma_peak_node_vtable; + baseNodeConfig.pInputChannels = &pConfig->peak.channels; + baseNodeConfig.pOutputChannels = &pConfig->peak.channels; + + result = ma_node_init(pNodeGraph, &baseNodeConfig, pAllocationCallbacks, pNode); + if (result != MA_SUCCESS) { + return result; + } + + return result; +} + +MA_API ma_result ma_peak_node_reinit(const ma_peak_config* pConfig, ma_peak_node* pNode) +{ + ma_peak_node* pPeakNode = (ma_peak_node*)pNode; + + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + return ma_peak2_reinit(pConfig, &pPeakNode->peak); +} + +MA_API void ma_peak_node_uninit(ma_peak_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks) +{ + ma_peak_node* pPeakNode = (ma_peak_node*)pNode; + + if (pNode == NULL) { + return; + } + + ma_node_uninit(pNode, pAllocationCallbacks); + ma_peak2_uninit(&pPeakNode->peak, pAllocationCallbacks); +} + + + +/* +Low Shelf Filter Node +*/ +MA_API ma_loshelf_node_config ma_loshelf_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, double gainDB, double q, double frequency) +{ + ma_loshelf_node_config config; + + config.nodeConfig = ma_node_config_init(); + config.loshelf = ma_loshelf2_config_init(ma_format_f32, channels, sampleRate, gainDB, q, frequency); + + return config; +} + +static void ma_loshelf_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + ma_loshelf_node* pBPFNode = (ma_loshelf_node*)pNode; + + MA_ASSERT(pNode != NULL); + (void)pFrameCountIn; + + ma_loshelf2_process_pcm_frames(&pBPFNode->loshelf, ppFramesOut[0], ppFramesIn[0], *pFrameCountOut); +} + +static ma_node_vtable g_ma_loshelf_node_vtable = +{ + ma_loshelf_node_process_pcm_frames, + NULL, /* onGetRequiredInputFrameCount */ + 1, /* One input. */ + 1, /* One output. */ + 0 /* Default flags. */ +}; + +MA_API ma_result ma_loshelf_node_init(ma_node_graph* pNodeGraph, const ma_loshelf_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_loshelf_node* pNode) +{ + ma_result result; + ma_node_config baseNodeConfig; + + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pNode); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->loshelf.format != ma_format_f32) { + return MA_INVALID_ARGS; /* The format must be f32. */ + } + + result = ma_loshelf2_init(&pConfig->loshelf, pAllocationCallbacks, &pNode->loshelf); + if (result != MA_SUCCESS) { + return result; + } + + baseNodeConfig = ma_node_config_init(); + baseNodeConfig.vtable = &g_ma_loshelf_node_vtable; + baseNodeConfig.pInputChannels = &pConfig->loshelf.channels; + baseNodeConfig.pOutputChannels = &pConfig->loshelf.channels; + + result = ma_node_init(pNodeGraph, &baseNodeConfig, pAllocationCallbacks, pNode); + if (result != MA_SUCCESS) { + return result; + } + + return result; +} + +MA_API ma_result ma_loshelf_node_reinit(const ma_loshelf_config* pConfig, ma_loshelf_node* pNode) +{ + ma_loshelf_node* pLoshelfNode = (ma_loshelf_node*)pNode; + + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + return ma_loshelf2_reinit(pConfig, &pLoshelfNode->loshelf); +} + +MA_API void ma_loshelf_node_uninit(ma_loshelf_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks) +{ + ma_loshelf_node* pLoshelfNode = (ma_loshelf_node*)pNode; + + if (pNode == NULL) { + return; + } + + ma_node_uninit(pNode, pAllocationCallbacks); + ma_loshelf2_uninit(&pLoshelfNode->loshelf, pAllocationCallbacks); +} + + + +/* +High Shelf Filter Node +*/ +MA_API ma_hishelf_node_config ma_hishelf_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, double gainDB, double q, double frequency) +{ + ma_hishelf_node_config config; + + config.nodeConfig = ma_node_config_init(); + config.hishelf = ma_hishelf2_config_init(ma_format_f32, channels, sampleRate, gainDB, q, frequency); + + return config; +} + +static void ma_hishelf_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + ma_hishelf_node* pBPFNode = (ma_hishelf_node*)pNode; + + MA_ASSERT(pNode != NULL); + (void)pFrameCountIn; + + ma_hishelf2_process_pcm_frames(&pBPFNode->hishelf, ppFramesOut[0], ppFramesIn[0], *pFrameCountOut); +} + +static ma_node_vtable g_ma_hishelf_node_vtable = +{ + ma_hishelf_node_process_pcm_frames, + NULL, /* onGetRequiredInputFrameCount */ + 1, /* One input. */ + 1, /* One output. */ + 0 /* Default flags. */ +}; + +MA_API ma_result ma_hishelf_node_init(ma_node_graph* pNodeGraph, const ma_hishelf_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_hishelf_node* pNode) +{ + ma_result result; + ma_node_config baseNodeConfig; + + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pNode); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->hishelf.format != ma_format_f32) { + return MA_INVALID_ARGS; /* The format must be f32. */ + } + + result = ma_hishelf2_init(&pConfig->hishelf, pAllocationCallbacks, &pNode->hishelf); + if (result != MA_SUCCESS) { + return result; + } + + baseNodeConfig = ma_node_config_init(); + baseNodeConfig.vtable = &g_ma_hishelf_node_vtable; + baseNodeConfig.pInputChannels = &pConfig->hishelf.channels; + baseNodeConfig.pOutputChannels = &pConfig->hishelf.channels; + + result = ma_node_init(pNodeGraph, &baseNodeConfig, pAllocationCallbacks, pNode); + if (result != MA_SUCCESS) { + return result; + } + + return result; +} + +MA_API ma_result ma_hishelf_node_reinit(const ma_hishelf_config* pConfig, ma_hishelf_node* pNode) +{ + ma_hishelf_node* pHishelfNode = (ma_hishelf_node*)pNode; + + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + return ma_hishelf2_reinit(pConfig, &pHishelfNode->hishelf); +} + +MA_API void ma_hishelf_node_uninit(ma_hishelf_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks) +{ + ma_hishelf_node* pHishelfNode = (ma_hishelf_node*)pNode; + + if (pNode == NULL) { + return; + } + + ma_node_uninit(pNode, pAllocationCallbacks); + ma_hishelf2_uninit(&pHishelfNode->hishelf, pAllocationCallbacks); +} + + + + +MA_API ma_delay_node_config ma_delay_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, ma_uint32 delayInFrames, float decay) +{ + ma_delay_node_config config; + + config.nodeConfig = ma_node_config_init(); + config.delay = ma_delay_config_init(channels, sampleRate, delayInFrames, decay); + + return config; +} + + +static void ma_delay_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + ma_delay_node* pDelayNode = (ma_delay_node*)pNode; + + (void)pFrameCountIn; + + ma_delay_process_pcm_frames(&pDelayNode->delay, ppFramesOut[0], ppFramesIn[0], *pFrameCountOut); +} + +static ma_node_vtable g_ma_delay_node_vtable = +{ + ma_delay_node_process_pcm_frames, + NULL, + 1, /* 1 input channels. */ + 1, /* 1 output channel. */ + MA_NODE_FLAG_CONTINUOUS_PROCESSING /* Delay requires continuous processing to ensure the tail get's processed. */ +}; + +MA_API ma_result ma_delay_node_init(ma_node_graph* pNodeGraph, const ma_delay_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_delay_node* pDelayNode) +{ + ma_result result; + ma_node_config baseConfig; + + if (pDelayNode == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pDelayNode); + + result = ma_delay_init(&pConfig->delay, pAllocationCallbacks, &pDelayNode->delay); + if (result != MA_SUCCESS) { + return result; + } + + baseConfig = pConfig->nodeConfig; + baseConfig.vtable = &g_ma_delay_node_vtable; + baseConfig.pInputChannels = &pConfig->delay.channels; + baseConfig.pOutputChannels = &pConfig->delay.channels; + + result = ma_node_init(pNodeGraph, &baseConfig, pAllocationCallbacks, &pDelayNode->baseNode); + if (result != MA_SUCCESS) { + ma_delay_uninit(&pDelayNode->delay, pAllocationCallbacks); + return result; + } + + return result; +} + +MA_API void ma_delay_node_uninit(ma_delay_node* pDelayNode, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pDelayNode == NULL) { + return; + } + + /* The base node is always uninitialized first. */ + ma_node_uninit(pDelayNode, pAllocationCallbacks); + ma_delay_uninit(&pDelayNode->delay, pAllocationCallbacks); +} + +MA_API void ma_delay_node_set_wet(ma_delay_node* pDelayNode, float value) +{ + if (pDelayNode == NULL) { + return; + } + + ma_delay_set_wet(&pDelayNode->delay, value); +} + +MA_API float ma_delay_node_get_wet(const ma_delay_node* pDelayNode) +{ + if (pDelayNode == NULL) { + return 0; + } + + return ma_delay_get_wet(&pDelayNode->delay); +} + +MA_API void ma_delay_node_set_dry(ma_delay_node* pDelayNode, float value) +{ + if (pDelayNode == NULL) { + return; + } + + ma_delay_set_dry(&pDelayNode->delay, value); +} + +MA_API float ma_delay_node_get_dry(const ma_delay_node* pDelayNode) +{ + if (pDelayNode == NULL) { + return 0; + } + + return ma_delay_get_dry(&pDelayNode->delay); +} + +MA_API void ma_delay_node_set_decay(ma_delay_node* pDelayNode, float value) +{ + if (pDelayNode == NULL) { + return; + } + + ma_delay_set_decay(&pDelayNode->delay, value); +} + +MA_API float ma_delay_node_get_decay(const ma_delay_node* pDelayNode) +{ + if (pDelayNode == NULL) { + return 0; + } + + return ma_delay_get_decay(&pDelayNode->delay); +} +#endif /* MA_NO_NODE_GRAPH */ + + +#if !defined(MA_NO_ENGINE) && !defined(MA_NO_NODE_GRAPH) +/************************************************************************************************************************************************************** + +Engine + +**************************************************************************************************************************************************************/ +#define MA_SEEK_TARGET_NONE (~(ma_uint64)0) + +MA_API ma_engine_node_config ma_engine_node_config_init(ma_engine* pEngine, ma_engine_node_type type, ma_uint32 flags) +{ + ma_engine_node_config config; + + MA_ZERO_OBJECT(&config); + config.pEngine = pEngine; + config.type = type; + config.isPitchDisabled = (flags & MA_SOUND_FLAG_NO_PITCH) != 0; + config.isSpatializationDisabled = (flags & MA_SOUND_FLAG_NO_SPATIALIZATION) != 0; + + return config; +} + + +static void ma_engine_node_update_pitch_if_required(ma_engine_node* pEngineNode) +{ + ma_bool32 isUpdateRequired = MA_FALSE; + float newPitch; + + MA_ASSERT(pEngineNode != NULL); + + newPitch = c89atomic_load_explicit_f32(&pEngineNode->pitch, c89atomic_memory_order_acquire); + + if (pEngineNode->oldPitch != newPitch) { + pEngineNode->oldPitch = newPitch; + isUpdateRequired = MA_TRUE; + } + + if (pEngineNode->oldDopplerPitch != pEngineNode->spatializer.dopplerPitch) { + pEngineNode->oldDopplerPitch = pEngineNode->spatializer.dopplerPitch; + isUpdateRequired = MA_TRUE; + } + + if (isUpdateRequired) { + float basePitch = (float)pEngineNode->sampleRate / ma_engine_get_sample_rate(pEngineNode->pEngine); + ma_linear_resampler_set_rate_ratio(&pEngineNode->resampler, basePitch * pEngineNode->oldPitch * pEngineNode->oldDopplerPitch); + } +} + +static ma_bool32 ma_engine_node_is_pitching_enabled(const ma_engine_node* pEngineNode) +{ + MA_ASSERT(pEngineNode != NULL); + + /* Don't try to be clever by skiping resampling in the pitch=1 case or else you'll glitch when moving away from 1. */ + return !c89atomic_load_explicit_32(&pEngineNode->isPitchDisabled, c89atomic_memory_order_acquire); +} + +static ma_bool32 ma_engine_node_is_spatialization_enabled(const ma_engine_node* pEngineNode) +{ + MA_ASSERT(pEngineNode != NULL); + + return !c89atomic_load_explicit_32(&pEngineNode->isSpatializationDisabled, c89atomic_memory_order_acquire); +} + +static ma_uint64 ma_engine_node_get_required_input_frame_count(const ma_engine_node* pEngineNode, ma_uint64 outputFrameCount) +{ + ma_uint64 inputFrameCount = 0; + + if (ma_engine_node_is_pitching_enabled(pEngineNode)) { + ma_result result = ma_linear_resampler_get_required_input_frame_count(&pEngineNode->resampler, outputFrameCount, &inputFrameCount); + if (result != MA_SUCCESS) { + inputFrameCount = 0; + } + } else { + inputFrameCount = outputFrameCount; /* No resampling, so 1:1. */ + } + + return inputFrameCount; +} + +static void ma_engine_node_process_pcm_frames__general(ma_engine_node* pEngineNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + ma_uint32 frameCountIn; + ma_uint32 frameCountOut; + ma_uint32 totalFramesProcessedIn; + ma_uint32 totalFramesProcessedOut; + ma_uint32 channelsIn; + ma_uint32 channelsOut; + ma_bool32 isPitchingEnabled; + ma_bool32 isFadingEnabled; + ma_bool32 isSpatializationEnabled; + ma_bool32 isPanningEnabled; + + frameCountIn = *pFrameCountIn; + frameCountOut = *pFrameCountOut; + + channelsIn = ma_spatializer_get_input_channels(&pEngineNode->spatializer); + channelsOut = ma_spatializer_get_output_channels(&pEngineNode->spatializer); + + totalFramesProcessedIn = 0; + totalFramesProcessedOut = 0; + + isPitchingEnabled = ma_engine_node_is_pitching_enabled(pEngineNode); + isFadingEnabled = pEngineNode->fader.volumeBeg != 1 || pEngineNode->fader.volumeEnd != 1; + isSpatializationEnabled = ma_engine_node_is_spatialization_enabled(pEngineNode); + isPanningEnabled = pEngineNode->panner.pan != 0 && channelsOut != 1; + + /* Keep going while we've still got data available for processing. */ + while (totalFramesProcessedOut < frameCountOut) { + /* + We need to process in a specific order. We always do resampling first because it's likely + we're going to be increasing the channel count after spatialization. Also, I want to do + fading based on the output sample rate. + + We'll first read into a buffer from the resampler. Then we'll do all processing that + operates on the on the input channel count. We'll then get the spatializer to output to + the output buffer and then do all effects from that point directly in the output buffer + in-place. + + Note that we're always running the resampler. If we try to be clever and skip resampling + when the pitch is 1, we'll get a glitch when we move away from 1, back to 1, and then + away from 1 again. We'll want to implement any pitch=1 optimizations in the resampler + itself. + + There's a small optimization here that we'll utilize since it might be a fairly common + case. When the input and output channel counts are the same, we'll read straight into the + output buffer from the resampler and do everything in-place. + */ + const float* pRunningFramesIn; + float* pRunningFramesOut; + float* pWorkingBuffer; /* This is the buffer that we'll be processing frames in. This is in input channels. */ + float temp[MA_DATA_CONVERTER_STACK_BUFFER_SIZE / sizeof(float)]; + ma_uint32 tempCapInFrames = ma_countof(temp) / channelsIn; + ma_uint32 framesAvailableIn; + ma_uint32 framesAvailableOut; + ma_uint32 framesJustProcessedIn; + ma_uint32 framesJustProcessedOut; + ma_bool32 isWorkingBufferValid = MA_FALSE; + + framesAvailableIn = frameCountIn - totalFramesProcessedIn; + framesAvailableOut = frameCountOut - totalFramesProcessedOut; + + pRunningFramesIn = ma_offset_pcm_frames_const_ptr_f32(ppFramesIn[0], totalFramesProcessedIn, channelsIn); + pRunningFramesOut = ma_offset_pcm_frames_ptr_f32(ppFramesOut[0], totalFramesProcessedOut, channelsOut); + + if (channelsIn == channelsOut) { + /* Fast path. Channel counts are the same. No need for an intermediary input buffer. */ + pWorkingBuffer = pRunningFramesOut; + } else { + /* Slow path. Channel counts are different. Need to use an intermediary input buffer. */ + pWorkingBuffer = temp; + if (framesAvailableOut > tempCapInFrames) { + framesAvailableOut = tempCapInFrames; + } + } + + /* First is resampler. */ + if (isPitchingEnabled) { + ma_uint64 resampleFrameCountIn = framesAvailableIn; + ma_uint64 resampleFrameCountOut = framesAvailableOut; + + ma_linear_resampler_process_pcm_frames(&pEngineNode->resampler, pRunningFramesIn, &resampleFrameCountIn, pWorkingBuffer, &resampleFrameCountOut); + isWorkingBufferValid = MA_TRUE; + + framesJustProcessedIn = (ma_uint32)resampleFrameCountIn; + framesJustProcessedOut = (ma_uint32)resampleFrameCountOut; + } else { + framesJustProcessedIn = ma_min(framesAvailableIn, framesAvailableOut); + framesJustProcessedOut = framesJustProcessedIn; /* When no resampling is being performed, the number of output frames is the same as input frames. */ + } + + /* Fading. */ + if (isFadingEnabled) { + if (isWorkingBufferValid) { + ma_fader_process_pcm_frames(&pEngineNode->fader, pWorkingBuffer, pWorkingBuffer, framesJustProcessedOut); /* In-place processing. */ + } else { + ma_fader_process_pcm_frames(&pEngineNode->fader, pWorkingBuffer, pRunningFramesIn, framesJustProcessedOut); + isWorkingBufferValid = MA_TRUE; + } + } + + /* + If at this point we still haven't actually done anything with the working buffer we need + to just read straight from the input buffer. + */ + if (isWorkingBufferValid == MA_FALSE) { + pWorkingBuffer = (float*)pRunningFramesIn; /* Naughty const cast, but it's safe at this point because we won't ever be writing to it from this point out. */ + } + + /* Spatialization. */ + if (isSpatializationEnabled) { + ma_uint32 iListener; + + /* + When determining the listener to use, we first check to see if the sound is pinned to a + specific listener. If so, we use that. Otherwise we just use the closest listener. + */ + if (pEngineNode->pinnedListenerIndex != MA_LISTENER_INDEX_CLOSEST && pEngineNode->pinnedListenerIndex < ma_engine_get_listener_count(pEngineNode->pEngine)) { + iListener = pEngineNode->pinnedListenerIndex; + } else { + iListener = ma_engine_find_closest_listener(pEngineNode->pEngine, pEngineNode->spatializer.position.x, pEngineNode->spatializer.position.y, pEngineNode->spatializer.position.z); + } + + ma_spatializer_process_pcm_frames(&pEngineNode->spatializer, &pEngineNode->pEngine->listeners[iListener], pRunningFramesOut, pWorkingBuffer, framesJustProcessedOut); + } else { + /* No spatialization, but we still need to do channel conversion. */ + if (channelsIn == channelsOut) { + /* No channel conversion required. Just copy straight to the output buffer. */ + ma_copy_pcm_frames(pRunningFramesOut, pWorkingBuffer, framesJustProcessedOut, ma_format_f32, channelsOut); + } else { + /* Channel conversion required. TODO: Add support for channel maps here. */ + ma_channel_map_apply_f32(pRunningFramesOut, NULL, channelsOut, pWorkingBuffer, NULL, channelsIn, framesJustProcessedOut, ma_channel_mix_mode_simple, pEngineNode->pEngine->monoExpansionMode); + } + } + + /* At this point we can guarantee that the output buffer contains valid data. We can process everything in place now. */ + + /* Panning. */ + if (isPanningEnabled) { + ma_panner_process_pcm_frames(&pEngineNode->panner, pRunningFramesOut, pRunningFramesOut, framesJustProcessedOut); /* In-place processing. */ + } + + /* We're done for this chunk. */ + totalFramesProcessedIn += framesJustProcessedIn; + totalFramesProcessedOut += framesJustProcessedOut; + + /* If we didn't process any output frames this iteration it means we've either run out of input data, or run out of room in the output buffer. */ + if (framesJustProcessedOut == 0) { + break; + } + } + + /* At this point we're done processing. */ + *pFrameCountIn = totalFramesProcessedIn; + *pFrameCountOut = totalFramesProcessedOut; +} + +static void ma_engine_node_process_pcm_frames__sound(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + /* For sounds, we need to first read from the data source. Then we need to apply the engine effects (pan, pitch, fades, etc.). */ + ma_result result = MA_SUCCESS; + ma_sound* pSound = (ma_sound*)pNode; + ma_uint32 frameCount = *pFrameCountOut; + ma_uint32 totalFramesRead = 0; + ma_format dataSourceFormat; + ma_uint32 dataSourceChannels; + ma_uint8 temp[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; + ma_uint32 tempCapInFrames; + ma_uint64 seekTarget; + + /* This is a data source node which means no input buses. */ + (void)ppFramesIn; + (void)pFrameCountIn; + + /* If we're marked at the end we need to stop the sound and do nothing. */ + if (ma_sound_at_end(pSound)) { + ma_sound_stop(pSound); + *pFrameCountOut = 0; + return; + } + + /* If we're seeking, do so now before reading. */ + seekTarget = c89atomic_load_64(&pSound->seekTarget); + if (seekTarget != MA_SEEK_TARGET_NONE) { + ma_data_source_seek_to_pcm_frame(pSound->pDataSource, seekTarget); + + /* Any time-dependant effects need to have their times updated. */ + ma_node_set_time(pSound, seekTarget); + + c89atomic_exchange_64(&pSound->seekTarget, MA_SEEK_TARGET_NONE); + } + + /* + We want to update the pitch once. For sounds, this can be either at the start or at the end. If + we don't force this to only ever be updating once, we could end up in a situation where + retrieving the required input frame count ends up being different to what we actually retrieve. + What could happen is that the required input frame count is calculated, the pitch is update, + and then this processing function is called resulting in a different number of input frames + being processed. Do not call this in ma_engine_node_process_pcm_frames__general() or else + you'll hit the aforementioned bug. + */ + ma_engine_node_update_pitch_if_required(&pSound->engineNode); + + /* + For the convenience of the caller, we're doing to allow data sources to use non-floating-point formats and channel counts that differ + from the main engine. + */ + result = ma_data_source_get_data_format(pSound->pDataSource, &dataSourceFormat, &dataSourceChannels, NULL, NULL, 0); + if (result == MA_SUCCESS) { + tempCapInFrames = sizeof(temp) / ma_get_bytes_per_frame(dataSourceFormat, dataSourceChannels); + + /* Keep reading until we've read as much as was requested or we reach the end of the data source. */ + while (totalFramesRead < frameCount) { + ma_uint32 framesRemaining = frameCount - totalFramesRead; + ma_uint32 framesToRead; + ma_uint64 framesJustRead; + ma_uint32 frameCountIn; + ma_uint32 frameCountOut; + const float* pRunningFramesIn; + float* pRunningFramesOut; + + /* + The first thing we need to do is read into the temporary buffer. We can calculate exactly + how many input frames we'll need after resampling. + */ + framesToRead = (ma_uint32)ma_engine_node_get_required_input_frame_count(&pSound->engineNode, framesRemaining); + if (framesToRead > tempCapInFrames) { + framesToRead = tempCapInFrames; + } + + result = ma_data_source_read_pcm_frames(pSound->pDataSource, temp, framesToRead, &framesJustRead); + + /* If we reached the end of the sound we'll want to mark it as at the end and stop it. This should never be returned for looping sounds. */ + if (result == MA_AT_END) { + c89atomic_exchange_32(&pSound->atEnd, MA_TRUE); /* This will be set to false in ma_sound_start(). */ + } + + pRunningFramesOut = ma_offset_pcm_frames_ptr_f32(ppFramesOut[0], totalFramesRead, ma_engine_get_channels(ma_sound_get_engine(pSound))); + + frameCountIn = (ma_uint32)framesJustRead; + frameCountOut = framesRemaining; + + /* Convert if necessary. */ + if (dataSourceFormat == ma_format_f32) { + /* Fast path. No data conversion necessary. */ + pRunningFramesIn = (float*)temp; + ma_engine_node_process_pcm_frames__general(&pSound->engineNode, &pRunningFramesIn, &frameCountIn, &pRunningFramesOut, &frameCountOut); + } else { + /* Slow path. Need to do sample format conversion to f32. If we give the f32 buffer the same count as the first temp buffer, we're guaranteed it'll be large enough. */ + float tempf32[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; /* Do not do `MA_DATA_CONVERTER_STACK_BUFFER_SIZE/sizeof(float)` here like we've done in other places. */ + ma_convert_pcm_frames_format(tempf32, ma_format_f32, temp, dataSourceFormat, framesJustRead, dataSourceChannels, ma_dither_mode_none); + + /* Now that we have our samples in f32 format we can process like normal. */ + pRunningFramesIn = tempf32; + ma_engine_node_process_pcm_frames__general(&pSound->engineNode, &pRunningFramesIn, &frameCountIn, &pRunningFramesOut, &frameCountOut); + } + + /* We should have processed all of our input frames since we calculated the required number of input frames at the top. */ + MA_ASSERT(frameCountIn == framesJustRead); + totalFramesRead += (ma_uint32)frameCountOut; /* Safe cast. */ + + if (result != MA_SUCCESS || ma_sound_at_end(pSound)) { + break; /* Might have reached the end. */ + } + } + } + + *pFrameCountOut = totalFramesRead; +} + +static void ma_engine_node_process_pcm_frames__group(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + /* + Make sure the pitch is updated before trying to read anything. It's important that this is done + only once and not in ma_engine_node_process_pcm_frames__general(). The reason for this is that + ma_engine_node_process_pcm_frames__general() will call ma_engine_node_get_required_input_frame_count(), + and if another thread modifies the pitch just after that call it can result in a glitch due to + the input rate changing. + */ + ma_engine_node_update_pitch_if_required((ma_engine_node*)pNode); + + /* For groups, the input data has already been read and we just need to apply the effect. */ + ma_engine_node_process_pcm_frames__general((ma_engine_node*)pNode, ppFramesIn, pFrameCountIn, ppFramesOut, pFrameCountOut); +} + +static ma_result ma_engine_node_get_required_input_frame_count__group(ma_node* pNode, ma_uint32 outputFrameCount, ma_uint32* pInputFrameCount) +{ + ma_uint64 inputFrameCount; + + MA_ASSERT(pInputFrameCount != NULL); + + /* Our pitch will affect this calculation. We need to update it. */ + ma_engine_node_update_pitch_if_required((ma_engine_node*)pNode); + + inputFrameCount = ma_engine_node_get_required_input_frame_count((ma_engine_node*)pNode, outputFrameCount); + if (inputFrameCount > 0xFFFFFFFF) { + inputFrameCount = 0xFFFFFFFF; /* Will never happen because miniaudio will only ever process in relatively small chunks. */ + } + + *pInputFrameCount = (ma_uint32)inputFrameCount; + + return MA_SUCCESS; +} + + +static ma_node_vtable g_ma_engine_node_vtable__sound = +{ + ma_engine_node_process_pcm_frames__sound, + NULL, /* onGetRequiredInputFrameCount */ + 0, /* Sounds are data source nodes which means they have zero inputs (their input is drawn from the data source itself). */ + 1, /* Sounds have one output bus. */ + 0 /* Default flags. */ +}; + +static ma_node_vtable g_ma_engine_node_vtable__group = +{ + ma_engine_node_process_pcm_frames__group, + ma_engine_node_get_required_input_frame_count__group, + 1, /* Groups have one input bus. */ + 1, /* Groups have one output bus. */ + MA_NODE_FLAG_DIFFERENT_PROCESSING_RATES /* The engine node does resampling so should let miniaudio know about it. */ +}; + + + +static ma_node_config ma_engine_node_base_node_config_init(const ma_engine_node_config* pConfig) +{ + ma_node_config baseNodeConfig; + + if (pConfig->type == ma_engine_node_type_sound) { + /* Sound. */ + baseNodeConfig = ma_node_config_init(); + baseNodeConfig.vtable = &g_ma_engine_node_vtable__sound; + baseNodeConfig.initialState = ma_node_state_stopped; /* Sounds are stopped by default. */ + } else { + /* Group. */ + baseNodeConfig = ma_node_config_init(); + baseNodeConfig.vtable = &g_ma_engine_node_vtable__group; + baseNodeConfig.initialState = ma_node_state_started; /* Groups are started by default. */ + } + + return baseNodeConfig; +} + +static ma_spatializer_config ma_engine_node_spatializer_config_init(const ma_node_config* pBaseNodeConfig) +{ + return ma_spatializer_config_init(pBaseNodeConfig->pInputChannels[0], pBaseNodeConfig->pOutputChannels[0]); +} + +typedef struct +{ + size_t sizeInBytes; + size_t baseNodeOffset; + size_t resamplerOffset; + size_t spatializerOffset; +} ma_engine_node_heap_layout; + +static ma_result ma_engine_node_get_heap_layout(const ma_engine_node_config* pConfig, ma_engine_node_heap_layout* pHeapLayout) +{ + ma_result result; + size_t tempHeapSize; + ma_node_config baseNodeConfig; + ma_linear_resampler_config resamplerConfig; + ma_spatializer_config spatializerConfig; + ma_uint32 channelsIn; + ma_uint32 channelsOut; + + MA_ASSERT(pHeapLayout); + + MA_ZERO_OBJECT(pHeapLayout); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->pEngine == NULL) { + return MA_INVALID_ARGS; /* An engine must be specified. */ + } + + pHeapLayout->sizeInBytes = 0; + + channelsIn = (pConfig->channelsIn != 0) ? pConfig->channelsIn : ma_engine_get_channels(pConfig->pEngine); + channelsOut = (pConfig->channelsOut != 0) ? pConfig->channelsOut : ma_engine_get_channels(pConfig->pEngine); + + + /* Base node. */ + baseNodeConfig = ma_engine_node_base_node_config_init(pConfig); + baseNodeConfig.pInputChannels = &channelsIn; + baseNodeConfig.pOutputChannels = &channelsOut; + + result = ma_node_get_heap_size(ma_engine_get_node_graph(pConfig->pEngine), &baseNodeConfig, &tempHeapSize); + if (result != MA_SUCCESS) { + return result; /* Failed to retrieve the size of the heap for the base node. */ + } + + pHeapLayout->baseNodeOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += ma_align_64(tempHeapSize); + + + /* Resmapler. */ + resamplerConfig = ma_linear_resampler_config_init(ma_format_f32, channelsIn, 1, 1); /* Input and output sample rates don't affect the calculation of the heap size. */ + resamplerConfig.lpfOrder = 0; + + result = ma_linear_resampler_get_heap_size(&resamplerConfig, &tempHeapSize); + if (result != MA_SUCCESS) { + return result; /* Failed to retrieve the size of the heap for the resampler. */ + } + + pHeapLayout->resamplerOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += ma_align_64(tempHeapSize); + + + /* Spatializer. */ + spatializerConfig = ma_engine_node_spatializer_config_init(&baseNodeConfig); + + result = ma_spatializer_get_heap_size(&spatializerConfig, &tempHeapSize); + if (result != MA_SUCCESS) { + return result; /* Failed to retrieve the size of the heap for the spatializer. */ + } + + pHeapLayout->spatializerOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += ma_align_64(tempHeapSize); + + + return MA_SUCCESS; +} + +MA_API ma_result ma_engine_node_get_heap_size(const ma_engine_node_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_result result; + ma_engine_node_heap_layout heapLayout; + + if (pHeapSizeInBytes == NULL) { + return MA_INVALID_ARGS; + } + + *pHeapSizeInBytes = 0; + + result = ma_engine_node_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + *pHeapSizeInBytes = heapLayout.sizeInBytes; + + return MA_SUCCESS; +} + +MA_API ma_result ma_engine_node_init_preallocated(const ma_engine_node_config* pConfig, void* pHeap, ma_engine_node* pEngineNode) +{ + ma_result result; + ma_engine_node_heap_layout heapLayout; + ma_node_config baseNodeConfig; + ma_linear_resampler_config resamplerConfig; + ma_fader_config faderConfig; + ma_spatializer_config spatializerConfig; + ma_panner_config pannerConfig; + ma_uint32 channelsIn; + ma_uint32 channelsOut; + + if (pEngineNode == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pEngineNode); + + result = ma_engine_node_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + if (pConfig->pinnedListenerIndex != MA_LISTENER_INDEX_CLOSEST && pConfig->pinnedListenerIndex >= ma_engine_get_listener_count(pConfig->pEngine)) { + return MA_INVALID_ARGS; /* Invalid listener. */ + } + + pEngineNode->_pHeap = pHeap; + MA_ZERO_MEMORY(pHeap, heapLayout.sizeInBytes); + + pEngineNode->pEngine = pConfig->pEngine; + pEngineNode->sampleRate = (pConfig->sampleRate > 0) ? pConfig->sampleRate : ma_engine_get_sample_rate(pEngineNode->pEngine); + pEngineNode->pitch = 1; + pEngineNode->oldPitch = 1; + pEngineNode->oldDopplerPitch = 1; + pEngineNode->isPitchDisabled = pConfig->isPitchDisabled; + pEngineNode->isSpatializationDisabled = pConfig->isSpatializationDisabled; + pEngineNode->pinnedListenerIndex = pConfig->pinnedListenerIndex; + + + channelsIn = (pConfig->channelsIn != 0) ? pConfig->channelsIn : ma_engine_get_channels(pConfig->pEngine); + channelsOut = (pConfig->channelsOut != 0) ? pConfig->channelsOut : ma_engine_get_channels(pConfig->pEngine); + + + /* Base node. */ + baseNodeConfig = ma_engine_node_base_node_config_init(pConfig); + baseNodeConfig.pInputChannels = &channelsIn; + baseNodeConfig.pOutputChannels = &channelsOut; + + result = ma_node_init_preallocated(&pConfig->pEngine->nodeGraph, &baseNodeConfig, ma_offset_ptr(pHeap, heapLayout.baseNodeOffset), &pEngineNode->baseNode); + if (result != MA_SUCCESS) { + goto error0; + } + + + /* + We can now initialize the effects we need in order to implement the engine node. There's a + defined order of operations here, mainly centered around when we convert our channels from the + data source's native channel count to the engine's channel count. As a rule, we want to do as + much computation as possible before spatialization because there's a chance that will increase + the channel count, thereby increasing the amount of work needing to be done to process. + */ + + /* We'll always do resampling first. */ + resamplerConfig = ma_linear_resampler_config_init(ma_format_f32, baseNodeConfig.pInputChannels[0], pEngineNode->sampleRate, ma_engine_get_sample_rate(pEngineNode->pEngine)); + resamplerConfig.lpfOrder = 0; /* <-- Need to disable low-pass filtering for pitch shifting for now because there's cases where the biquads are becoming unstable. Need to figure out a better fix for this. */ + + result = ma_linear_resampler_init_preallocated(&resamplerConfig, ma_offset_ptr(pHeap, heapLayout.resamplerOffset), &pEngineNode->resampler); + if (result != MA_SUCCESS) { + goto error1; + } + + + /* After resampling will come the fader. */ + faderConfig = ma_fader_config_init(ma_format_f32, baseNodeConfig.pInputChannels[0], ma_engine_get_sample_rate(pEngineNode->pEngine)); + + result = ma_fader_init(&faderConfig, &pEngineNode->fader); + if (result != MA_SUCCESS) { + goto error2; + } + + + /* + Spatialization comes next. We spatialize based ont he node's output channel count. It's up the caller to + ensure channels counts link up correctly in the node graph. + */ + spatializerConfig = ma_engine_node_spatializer_config_init(&baseNodeConfig); + spatializerConfig.gainSmoothTimeInFrames = pEngineNode->pEngine->gainSmoothTimeInFrames; + + result = ma_spatializer_init_preallocated(&spatializerConfig, ma_offset_ptr(pHeap, heapLayout.spatializerOffset), &pEngineNode->spatializer); + if (result != MA_SUCCESS) { + goto error2; + } + + + /* + After spatialization comes panning. We need to do this after spatialization because otherwise we wouldn't + be able to pan mono sounds. + */ + pannerConfig = ma_panner_config_init(ma_format_f32, baseNodeConfig.pOutputChannels[0]); + + result = ma_panner_init(&pannerConfig, &pEngineNode->panner); + if (result != MA_SUCCESS) { + goto error3; + } + + return MA_SUCCESS; + + /* No need for allocation callbacks here because we use a preallocated heap. */ +error3: ma_spatializer_uninit(&pEngineNode->spatializer, NULL); +error2: ma_linear_resampler_uninit(&pEngineNode->resampler, NULL); +error1: ma_node_uninit(&pEngineNode->baseNode, NULL); +error0: return result; +} + +MA_API ma_result ma_engine_node_init(const ma_engine_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_engine_node* pEngineNode) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_engine_node_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_engine_node_init_preallocated(pConfig, pHeap, pEngineNode); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pEngineNode->_ownsHeap = MA_TRUE; + return MA_SUCCESS; +} + +MA_API void ma_engine_node_uninit(ma_engine_node* pEngineNode, const ma_allocation_callbacks* pAllocationCallbacks) +{ + /* + The base node always needs to be uninitialized first to ensure it's detached from the graph completely before we + destroy anything that might be in the middle of being used by the processing function. + */ + ma_node_uninit(&pEngineNode->baseNode, pAllocationCallbacks); + + /* Now that the node has been uninitialized we can safely uninitialize the rest. */ + ma_spatializer_uninit(&pEngineNode->spatializer, pAllocationCallbacks); + ma_linear_resampler_uninit(&pEngineNode->resampler, pAllocationCallbacks); + + /* Free the heap last. */ + if (pEngineNode->_ownsHeap) { + ma_free(pEngineNode->_pHeap, pAllocationCallbacks); + } +} + + +MA_API ma_sound_config ma_sound_config_init(void) +{ + ma_sound_config config; + + MA_ZERO_OBJECT(&config); + config.rangeEndInPCMFrames = ~((ma_uint64)0); + config.loopPointEndInPCMFrames = ~((ma_uint64)0); + + return config; +} + +MA_API ma_sound_group_config ma_sound_group_config_init(void) +{ + ma_sound_group_config config; + + MA_ZERO_OBJECT(&config); + + return config; +} + + +MA_API ma_engine_config ma_engine_config_init(void) +{ + ma_engine_config config; + + MA_ZERO_OBJECT(&config); + config.listenerCount = 1; /* Always want at least one listener. */ + config.monoExpansionMode = ma_mono_expansion_mode_default; + + return config; +} + + +#if !defined(MA_NO_DEVICE_IO) +static void ma_engine_data_callback_internal(ma_device* pDevice, void* pFramesOut, const void* pFramesIn, ma_uint32 frameCount) +{ + ma_engine* pEngine = (ma_engine*)pDevice->pUserData; + + (void)pFramesIn; + + /* + Experiment: Try processing a resource manager job if we're on the Emscripten build. + + This serves two purposes: + + 1) It ensures jobs are actually processed at some point since we cannot guarantee that the + caller is doing the right thing and calling ma_resource_manager_process_next_job(); and + + 2) It's an attempt at working around an issue where processing jobs on the Emscripten main + loop doesn't work as well as it should. When trying to load sounds without the `DECODE` + flag or with the `ASYNC` flag, the sound data is just not able to be loaded in time + before the callback is processed. I think it's got something to do with the single- + threaded nature of Web, but I'm not entirely sure. + */ + #if !defined(MA_NO_RESOURCE_MANAGER) && defined(MA_EMSCRIPTEN) + { + if (pEngine->pResourceManager != NULL) { + if ((pEngine->pResourceManager->config.flags & MA_RESOURCE_MANAGER_FLAG_NO_THREADING) != 0) { + ma_resource_manager_process_next_job(pEngine->pResourceManager); + } + } + } + #endif + + ma_engine_read_pcm_frames(pEngine, pFramesOut, frameCount, NULL); +} +#endif + +MA_API ma_result ma_engine_init(const ma_engine_config* pConfig, ma_engine* pEngine) +{ + ma_result result; + ma_node_graph_config nodeGraphConfig; + ma_engine_config engineConfig; + ma_spatializer_listener_config listenerConfig; + ma_uint32 iListener; + + if (pEngine == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pEngine); + + /* The config is allowed to be NULL in which case we use defaults for everything. */ + if (pConfig != NULL) { + engineConfig = *pConfig; + } else { + engineConfig = ma_engine_config_init(); + } + + pEngine->monoExpansionMode = engineConfig.monoExpansionMode; + ma_allocation_callbacks_init_copy(&pEngine->allocationCallbacks, &engineConfig.allocationCallbacks); + + #if !defined(MA_NO_RESOURCE_MANAGER) + { + pEngine->pResourceManager = engineConfig.pResourceManager; + } + #endif + + #if !defined(MA_NO_DEVICE_IO) + { + pEngine->pDevice = engineConfig.pDevice; + + /* If we don't have a device, we need one. */ + if (pEngine->pDevice == NULL && engineConfig.noDevice == MA_FALSE) { + ma_device_config deviceConfig; + + pEngine->pDevice = (ma_device*)ma_malloc(sizeof(*pEngine->pDevice), &pEngine->allocationCallbacks); + if (pEngine->pDevice == NULL) { + return MA_OUT_OF_MEMORY; + } + + deviceConfig = ma_device_config_init(ma_device_type_playback); + deviceConfig.playback.pDeviceID = engineConfig.pPlaybackDeviceID; + deviceConfig.playback.format = ma_format_f32; + deviceConfig.playback.channels = engineConfig.channels; + deviceConfig.sampleRate = engineConfig.sampleRate; + deviceConfig.dataCallback = ma_engine_data_callback_internal; + deviceConfig.pUserData = pEngine; + deviceConfig.periodSizeInFrames = engineConfig.periodSizeInFrames; + deviceConfig.periodSizeInMilliseconds = engineConfig.periodSizeInMilliseconds; + deviceConfig.noPreSilencedOutputBuffer = MA_TRUE; /* We'll always be outputting to every frame in the callback so there's no need for a pre-silenced buffer. */ + deviceConfig.noClip = MA_TRUE; /* The engine will do clipping itself. */ + + if (engineConfig.pContext == NULL) { + ma_context_config contextConfig = ma_context_config_init(); + contextConfig.allocationCallbacks = pEngine->allocationCallbacks; + contextConfig.pLog = engineConfig.pLog; + + /* If the engine config does not specify a log, use the resource manager's if we have one. */ + #ifndef MA_NO_RESOURCE_MANAGER + { + if (contextConfig.pLog == NULL && engineConfig.pResourceManager != NULL) { + contextConfig.pLog = ma_resource_manager_get_log(engineConfig.pResourceManager); + } + } + #endif + + result = ma_device_init_ex(NULL, 0, &contextConfig, &deviceConfig, pEngine->pDevice); + } else { + result = ma_device_init(engineConfig.pContext, &deviceConfig, pEngine->pDevice); + } + + if (result != MA_SUCCESS) { + ma_free(pEngine->pDevice, &pEngine->allocationCallbacks); + pEngine->pDevice = NULL; + return result; + } + + pEngine->ownsDevice = MA_TRUE; + } + + /* Update the channel count and sample rate of the engine config so we can reference it below. */ + if (pEngine->pDevice != NULL) { + engineConfig.channels = pEngine->pDevice->playback.channels; + engineConfig.sampleRate = pEngine->pDevice->sampleRate; + } + } + #endif + + if (engineConfig.channels == 0 || engineConfig.sampleRate == 0) { + return MA_INVALID_ARGS; + } + + pEngine->sampleRate = engineConfig.sampleRate; + + /* The engine always uses either the log that was passed into the config, or the context's log is available. */ + if (engineConfig.pLog != NULL) { + pEngine->pLog = engineConfig.pLog; + } else { + #if !defined(MA_NO_DEVICE_IO) + { + pEngine->pLog = ma_device_get_log(pEngine->pDevice); + } + #else + { + pEngine->pLog = NULL; + } + #endif + } + + + /* The engine is a node graph. This needs to be initialized after we have the device so we can can determine the channel count. */ + nodeGraphConfig = ma_node_graph_config_init(engineConfig.channels); + nodeGraphConfig.nodeCacheCapInFrames = (engineConfig.periodSizeInFrames > 0xFFFF) ? 0xFFFF : (ma_uint16)engineConfig.periodSizeInFrames; + + result = ma_node_graph_init(&nodeGraphConfig, &pEngine->allocationCallbacks, &pEngine->nodeGraph); + if (result != MA_SUCCESS) { + goto on_error_1; + } + + + /* We need at least one listener. */ + if (engineConfig.listenerCount == 0) { + engineConfig.listenerCount = 1; + } + + if (engineConfig.listenerCount > MA_ENGINE_MAX_LISTENERS) { + result = MA_INVALID_ARGS; /* Too many listeners. */ + goto on_error_1; + } + + for (iListener = 0; iListener < engineConfig.listenerCount; iListener += 1) { + listenerConfig = ma_spatializer_listener_config_init(ma_node_graph_get_channels(&pEngine->nodeGraph)); + + /* + If we're using a device, use the device's channel map for the listener. Otherwise just use + miniaudio's default channel map. + */ + #if !defined(MA_NO_DEVICE_IO) + { + if (pEngine->pDevice != NULL) { + /* + Temporarily disabled. There is a subtle bug here where front-left and front-right + will be used by the device's channel map, but this is not what we want to use for + spatialization. Instead we want to use side-left and side-right. I need to figure + out a better solution for this. For now, disabling the user of device channel maps. + */ + /*listenerConfig.pChannelMapOut = pEngine->pDevice->playback.channelMap;*/ + } + } + #endif + + result = ma_spatializer_listener_init(&listenerConfig, &pEngine->allocationCallbacks, &pEngine->listeners[iListener]); /* TODO: Change this to a pre-allocated heap. */ + if (result != MA_SUCCESS) { + goto on_error_2; + } + + pEngine->listenerCount += 1; + } + + + /* Gain smoothing for spatialized sounds. */ + pEngine->gainSmoothTimeInFrames = engineConfig.gainSmoothTimeInFrames; + if (pEngine->gainSmoothTimeInFrames == 0) { + ma_uint32 gainSmoothTimeInMilliseconds = engineConfig.gainSmoothTimeInMilliseconds; + if (gainSmoothTimeInMilliseconds == 0) { + gainSmoothTimeInMilliseconds = 8; + } + + pEngine->gainSmoothTimeInFrames = (gainSmoothTimeInMilliseconds * ma_engine_get_sample_rate(pEngine)) / 1000; /* 8ms by default. */ + } + + + /* We need a resource manager. */ + #ifndef MA_NO_RESOURCE_MANAGER + { + if (pEngine->pResourceManager == NULL) { + ma_resource_manager_config resourceManagerConfig; + + pEngine->pResourceManager = (ma_resource_manager*)ma_malloc(sizeof(*pEngine->pResourceManager), &pEngine->allocationCallbacks); + if (pEngine->pResourceManager == NULL) { + result = MA_OUT_OF_MEMORY; + goto on_error_2; + } + + resourceManagerConfig = ma_resource_manager_config_init(); + resourceManagerConfig.pLog = pEngine->pLog; /* Always use the engine's log for internally-managed resource managers. */ + resourceManagerConfig.decodedFormat = ma_format_f32; + resourceManagerConfig.decodedChannels = 0; /* Leave the decoded channel count as 0 so we can get good spatialization. */ + resourceManagerConfig.decodedSampleRate = ma_engine_get_sample_rate(pEngine); + ma_allocation_callbacks_init_copy(&resourceManagerConfig.allocationCallbacks, &pEngine->allocationCallbacks); + resourceManagerConfig.pVFS = engineConfig.pResourceManagerVFS; + + /* The Emscripten build cannot use threads. */ + #if defined(MA_EMSCRIPTEN) + { + resourceManagerConfig.jobThreadCount = 0; + resourceManagerConfig.flags |= MA_RESOURCE_MANAGER_FLAG_NO_THREADING; + } + #endif + + result = ma_resource_manager_init(&resourceManagerConfig, pEngine->pResourceManager); + if (result != MA_SUCCESS) { + goto on_error_3; + } + + pEngine->ownsResourceManager = MA_TRUE; + } + } + #endif + + /* Setup some stuff for inlined sounds. That is sounds played with ma_engine_play_sound(). */ + pEngine->inlinedSoundLock = 0; + pEngine->pInlinedSoundHead = NULL; + + /* Start the engine if required. This should always be the last step. */ + #if !defined(MA_NO_DEVICE_IO) + { + if (engineConfig.noAutoStart == MA_FALSE && pEngine->pDevice != NULL) { + result = ma_engine_start(pEngine); + if (result != MA_SUCCESS) { + goto on_error_4; /* Failed to start the engine. */ + } + } + } + #endif + + return MA_SUCCESS; + +#if !defined(MA_NO_DEVICE_IO) +on_error_4: +#endif +#if !defined(MA_NO_RESOURCE_MANAGER) +on_error_3: + if (pEngine->ownsResourceManager) { + ma_free(pEngine->pResourceManager, &pEngine->allocationCallbacks); + } +#endif /* MA_NO_RESOURCE_MANAGER */ +on_error_2: + for (iListener = 0; iListener < pEngine->listenerCount; iListener += 1) { + ma_spatializer_listener_uninit(&pEngine->listeners[iListener], &pEngine->allocationCallbacks); + } + + ma_node_graph_uninit(&pEngine->nodeGraph, &pEngine->allocationCallbacks); +on_error_1: + #if !defined(MA_NO_DEVICE_IO) + { + if (pEngine->ownsDevice) { + ma_device_uninit(pEngine->pDevice); + ma_free(pEngine->pDevice, &pEngine->allocationCallbacks); + } + } + #endif + + return result; +} + +MA_API void ma_engine_uninit(ma_engine* pEngine) +{ + ma_uint32 iListener; + + if (pEngine == NULL) { + return; + } + + /* The device must be uninitialized before the node graph to ensure the audio thread doesn't try accessing it. */ + #if !defined(MA_NO_DEVICE_IO) + { + if (pEngine->ownsDevice) { + ma_device_uninit(pEngine->pDevice); + ma_free(pEngine->pDevice, &pEngine->allocationCallbacks); + } else { + if (pEngine->pDevice != NULL) { + ma_device_stop(pEngine->pDevice); + } + } + } + #endif + + /* + All inlined sounds need to be deleted. I'm going to use a lock here just to future proof in case + I want to do some kind of garbage collection later on. + */ + ma_spinlock_lock(&pEngine->inlinedSoundLock); + { + for (;;) { + ma_sound_inlined* pSoundToDelete = pEngine->pInlinedSoundHead; + if (pSoundToDelete == NULL) { + break; /* Done. */ + } + + pEngine->pInlinedSoundHead = pSoundToDelete->pNext; + + ma_sound_uninit(&pSoundToDelete->sound); + ma_free(pSoundToDelete, &pEngine->allocationCallbacks); + } + } + ma_spinlock_unlock(&pEngine->inlinedSoundLock); + + for (iListener = 0; iListener < pEngine->listenerCount; iListener += 1) { + ma_spatializer_listener_uninit(&pEngine->listeners[iListener], &pEngine->allocationCallbacks); + } + + /* Make sure the node graph is uninitialized after the audio thread has been shutdown to prevent accessing of the node graph after being uninitialized. */ + ma_node_graph_uninit(&pEngine->nodeGraph, &pEngine->allocationCallbacks); + + /* Uninitialize the resource manager last to ensure we don't have a thread still trying to access it. */ +#ifndef MA_NO_RESOURCE_MANAGER + if (pEngine->ownsResourceManager) { + ma_resource_manager_uninit(pEngine->pResourceManager); + ma_free(pEngine->pResourceManager, &pEngine->allocationCallbacks); + } +#endif +} + +MA_API ma_result ma_engine_read_pcm_frames(ma_engine* pEngine, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) +{ + return ma_node_graph_read_pcm_frames(&pEngine->nodeGraph, pFramesOut, frameCount, pFramesRead); +} + +MA_API ma_node_graph* ma_engine_get_node_graph(ma_engine* pEngine) +{ + if (pEngine == NULL) { + return NULL; + } + + return &pEngine->nodeGraph; +} + +#if !defined(MA_NO_RESOURCE_MANAGER) +MA_API ma_resource_manager* ma_engine_get_resource_manager(ma_engine* pEngine) +{ + if (pEngine == NULL) { + return NULL; + } + + #if !defined(MA_NO_RESOURCE_MANAGER) + { + return pEngine->pResourceManager; + } + #else + { + return NULL; + } + #endif +} +#endif + +MA_API ma_device* ma_engine_get_device(ma_engine* pEngine) +{ + if (pEngine == NULL) { + return NULL; + } + + #if !defined(MA_NO_DEVICE_IO) + { + return pEngine->pDevice; + } + #else + { + return NULL; + } + #endif +} + +MA_API ma_log* ma_engine_get_log(ma_engine* pEngine) +{ + if (pEngine == NULL) { + return NULL; + } + + if (pEngine->pLog != NULL) { + return pEngine->pLog; + } else { + #if !defined(MA_NO_DEVICE_IO) + { + return ma_device_get_log(ma_engine_get_device(pEngine)); + } + #else + { + return NULL; + } + #endif + } +} + +MA_API ma_node* ma_engine_get_endpoint(ma_engine* pEngine) +{ + return ma_node_graph_get_endpoint(&pEngine->nodeGraph); +} + +MA_API ma_uint64 ma_engine_get_time(const ma_engine* pEngine) +{ + return ma_node_graph_get_time(&pEngine->nodeGraph); +} + +MA_API ma_result ma_engine_set_time(ma_engine* pEngine, ma_uint64 globalTime) +{ + return ma_node_graph_set_time(&pEngine->nodeGraph, globalTime); +} + +MA_API ma_uint32 ma_engine_get_channels(const ma_engine* pEngine) +{ + return ma_node_graph_get_channels(&pEngine->nodeGraph); +} + +MA_API ma_uint32 ma_engine_get_sample_rate(const ma_engine* pEngine) +{ + if (pEngine == NULL) { + return 0; + } + + return pEngine->sampleRate; +} + + +MA_API ma_result ma_engine_start(ma_engine* pEngine) +{ + ma_result result; + + if (pEngine == NULL) { + return MA_INVALID_ARGS; + } + + #if !defined(MA_NO_DEVICE_IO) + { + if (pEngine->pDevice != NULL) { + result = ma_device_start(pEngine->pDevice); + } else { + result = MA_INVALID_OPERATION; /* The engine is running without a device which means there's no real notion of "starting" the engine. */ + } + } + #else + { + result = MA_INVALID_OPERATION; /* Device IO is disabled, so there's no real notion of "starting" the engine. */ + } + #endif + + if (result != MA_SUCCESS) { + return result; + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_engine_stop(ma_engine* pEngine) +{ + ma_result result; + + if (pEngine == NULL) { + return MA_INVALID_ARGS; + } + + #if !defined(MA_NO_DEVICE_IO) + { + if (pEngine->pDevice != NULL) { + result = ma_device_stop(pEngine->pDevice); + } else { + result = MA_INVALID_OPERATION; /* The engine is running without a device which means there's no real notion of "stopping" the engine. */ + } + } + #else + { + result = MA_INVALID_OPERATION; /* Device IO is disabled, so there's no real notion of "stopping" the engine. */ + } + #endif + + if (result != MA_SUCCESS) { + return result; + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_engine_set_volume(ma_engine* pEngine, float volume) +{ + if (pEngine == NULL) { + return MA_INVALID_ARGS; + } + + return ma_node_set_output_bus_volume(ma_node_graph_get_endpoint(&pEngine->nodeGraph), 0, volume); +} + +MA_API ma_result ma_engine_set_gain_db(ma_engine* pEngine, float gainDB) +{ + if (pEngine == NULL) { + return MA_INVALID_ARGS; + } + + return ma_node_set_output_bus_volume(ma_node_graph_get_endpoint(&pEngine->nodeGraph), 0, ma_volume_db_to_linear(gainDB)); +} + + +MA_API ma_uint32 ma_engine_get_listener_count(const ma_engine* pEngine) +{ + if (pEngine == NULL) { + return 0; + } + + return pEngine->listenerCount; +} + +MA_API ma_uint32 ma_engine_find_closest_listener(const ma_engine* pEngine, float absolutePosX, float absolutePosY, float absolutePosZ) +{ + ma_uint32 iListener; + ma_uint32 iListenerClosest; + float closestLen2 = MA_FLT_MAX; + + if (pEngine == NULL || pEngine->listenerCount == 1) { + return 0; + } + + iListenerClosest = 0; + for (iListener = 0; iListener < pEngine->listenerCount; iListener += 1) { + if (ma_engine_listener_is_enabled(pEngine, iListener)) { + float len2 = ma_vec3f_len2(ma_vec3f_sub(pEngine->listeners[iListener].position, ma_vec3f_init_3f(absolutePosX, absolutePosY, absolutePosZ))); + if (closestLen2 > len2) { + closestLen2 = len2; + iListenerClosest = iListener; + } + } + } + + MA_ASSERT(iListenerClosest < 255); + return iListenerClosest; +} + +MA_API void ma_engine_listener_set_position(ma_engine* pEngine, ma_uint32 listenerIndex, float x, float y, float z) +{ + if (pEngine == NULL || listenerIndex >= pEngine->listenerCount) { + return; + } + + ma_spatializer_listener_set_position(&pEngine->listeners[listenerIndex], x, y, z); +} + +MA_API ma_vec3f ma_engine_listener_get_position(const ma_engine* pEngine, ma_uint32 listenerIndex) +{ + if (pEngine == NULL || listenerIndex >= pEngine->listenerCount) { + return ma_vec3f_init_3f(0, 0, 0); + } + + return ma_spatializer_listener_get_position(&pEngine->listeners[listenerIndex]); +} + +MA_API void ma_engine_listener_set_direction(ma_engine* pEngine, ma_uint32 listenerIndex, float x, float y, float z) +{ + if (pEngine == NULL || listenerIndex >= pEngine->listenerCount) { + return; + } + + ma_spatializer_listener_set_direction(&pEngine->listeners[listenerIndex], x, y, z); +} + +MA_API ma_vec3f ma_engine_listener_get_direction(const ma_engine* pEngine, ma_uint32 listenerIndex) +{ + if (pEngine == NULL || listenerIndex >= pEngine->listenerCount) { + return ma_vec3f_init_3f(0, 0, -1); + } + + return ma_spatializer_listener_get_direction(&pEngine->listeners[listenerIndex]); +} + +MA_API void ma_engine_listener_set_velocity(ma_engine* pEngine, ma_uint32 listenerIndex, float x, float y, float z) +{ + if (pEngine == NULL || listenerIndex >= pEngine->listenerCount) { + return; + } + + ma_spatializer_listener_set_velocity(&pEngine->listeners[listenerIndex], x, y, z); +} + +MA_API ma_vec3f ma_engine_listener_get_velocity(const ma_engine* pEngine, ma_uint32 listenerIndex) +{ + if (pEngine == NULL || listenerIndex >= pEngine->listenerCount) { + return ma_vec3f_init_3f(0, 0, 0); + } + + return ma_spatializer_listener_get_velocity(&pEngine->listeners[listenerIndex]); +} + +MA_API void ma_engine_listener_set_cone(ma_engine* pEngine, ma_uint32 listenerIndex, float innerAngleInRadians, float outerAngleInRadians, float outerGain) +{ + if (pEngine == NULL || listenerIndex >= pEngine->listenerCount) { + return; + } + + ma_spatializer_listener_set_cone(&pEngine->listeners[listenerIndex], innerAngleInRadians, outerAngleInRadians, outerGain); +} + +MA_API void ma_engine_listener_get_cone(const ma_engine* pEngine, ma_uint32 listenerIndex, float* pInnerAngleInRadians, float* pOuterAngleInRadians, float* pOuterGain) +{ + if (pInnerAngleInRadians != NULL) { + *pInnerAngleInRadians = 0; + } + + if (pOuterAngleInRadians != NULL) { + *pOuterAngleInRadians = 0; + } + + if (pOuterGain != NULL) { + *pOuterGain = 0; + } + + ma_spatializer_listener_get_cone(&pEngine->listeners[listenerIndex], pInnerAngleInRadians, pOuterAngleInRadians, pOuterGain); +} + +MA_API void ma_engine_listener_set_world_up(ma_engine* pEngine, ma_uint32 listenerIndex, float x, float y, float z) +{ + if (pEngine == NULL || listenerIndex >= pEngine->listenerCount) { + return; + } + + ma_spatializer_listener_set_world_up(&pEngine->listeners[listenerIndex], x, y, z); +} + +MA_API ma_vec3f ma_engine_listener_get_world_up(const ma_engine* pEngine, ma_uint32 listenerIndex) +{ + if (pEngine == NULL || listenerIndex >= pEngine->listenerCount) { + return ma_vec3f_init_3f(0, 1, 0); + } + + return ma_spatializer_listener_get_world_up(&pEngine->listeners[listenerIndex]); +} + +MA_API void ma_engine_listener_set_enabled(ma_engine* pEngine, ma_uint32 listenerIndex, ma_bool32 isEnabled) +{ + if (pEngine == NULL || listenerIndex >= pEngine->listenerCount) { + return; + } + + ma_spatializer_listener_set_enabled(&pEngine->listeners[listenerIndex], isEnabled); +} + +MA_API ma_bool32 ma_engine_listener_is_enabled(const ma_engine* pEngine, ma_uint32 listenerIndex) +{ + if (pEngine == NULL || listenerIndex >= pEngine->listenerCount) { + return MA_FALSE; + } + + return ma_spatializer_listener_is_enabled(&pEngine->listeners[listenerIndex]); +} + + +#ifndef MA_NO_RESOURCE_MANAGER +MA_API ma_result ma_engine_play_sound_ex(ma_engine* pEngine, const char* pFilePath, ma_node* pNode, ma_uint32 nodeInputBusIndex) +{ + ma_result result = MA_SUCCESS; + ma_sound_inlined* pSound = NULL; + ma_sound_inlined* pNextSound = NULL; + + if (pEngine == NULL || pFilePath == NULL) { + return MA_INVALID_ARGS; + } + + /* Attach to the endpoint node if nothing is specicied. */ + if (pNode == NULL) { + pNode = ma_node_graph_get_endpoint(&pEngine->nodeGraph); + nodeInputBusIndex = 0; + } + + /* + We want to check if we can recycle an already-allocated inlined sound. Since this is just a + helper I'm not *too* concerned about performance here and I'm happy to use a lock to keep + the implementation simple. Maybe this can be optimized later if there's enough demand, but + if this function is being used it probably means the caller doesn't really care too much. + + What we do is check the atEnd flag. When this is true, we can recycle the sound. Otherwise + we just keep iterating. If we reach the end without finding a sound to recycle we just + allocate a new one. This doesn't scale well for a massive number of sounds being played + simultaneously as we don't ever actually free the sound objects. Some kind of garbage + collection routine might be valuable for this which I'll think about. + */ + ma_spinlock_lock(&pEngine->inlinedSoundLock); + { + ma_uint32 soundFlags = 0; + + for (pNextSound = pEngine->pInlinedSoundHead; pNextSound != NULL; pNextSound = pNextSound->pNext) { + if (ma_sound_at_end(&pNextSound->sound)) { + /* + The sound is at the end which means it's available for recycling. All we need to do + is uninitialize it and reinitialize it. All we're doing is recycling memory. + */ + pSound = pNextSound; + c89atomic_fetch_sub_32(&pEngine->inlinedSoundCount, 1); + break; + } + } + + if (pSound != NULL) { + /* + We actually want to detach the sound from the list here. The reason is because we want the sound + to be in a consistent state at the non-recycled case to simplify the logic below. + */ + if (pEngine->pInlinedSoundHead == pSound) { + pEngine->pInlinedSoundHead = pSound->pNext; + } + + if (pSound->pPrev != NULL) { + pSound->pPrev->pNext = pSound->pNext; + } + if (pSound->pNext != NULL) { + pSound->pNext->pPrev = pSound->pPrev; + } + + /* Now the previous sound needs to be uninitialized. */ + ma_sound_uninit(&pNextSound->sound); + } else { + /* No sound available for recycling. Allocate one now. */ + pSound = (ma_sound_inlined*)ma_malloc(sizeof(*pSound), &pEngine->allocationCallbacks); + } + + if (pSound != NULL) { /* Safety check for the allocation above. */ + /* + At this point we should have memory allocated for the inlined sound. We just need + to initialize it like a normal sound now. + */ + soundFlags |= MA_SOUND_FLAG_ASYNC; /* For inlined sounds we don't want to be sitting around waiting for stuff to load so force an async load. */ + soundFlags |= MA_SOUND_FLAG_NO_DEFAULT_ATTACHMENT; /* We want specific control over where the sound is attached in the graph. We'll attach it manually just before playing the sound. */ + soundFlags |= MA_SOUND_FLAG_NO_PITCH; /* Pitching isn't usable with inlined sounds, so disable it to save on speed. */ + soundFlags |= MA_SOUND_FLAG_NO_SPATIALIZATION; /* Not currently doing spatialization with inlined sounds, but this might actually change later. For now disable spatialization. Will be removed if we ever add support for spatialization here. */ + + result = ma_sound_init_from_file(pEngine, pFilePath, soundFlags, NULL, NULL, &pSound->sound); + if (result == MA_SUCCESS) { + /* Now attach the sound to the graph. */ + result = ma_node_attach_output_bus(pSound, 0, pNode, nodeInputBusIndex); + if (result == MA_SUCCESS) { + /* At this point the sound should be loaded and we can go ahead and add it to the list. The new item becomes the new head. */ + pSound->pNext = pEngine->pInlinedSoundHead; + pSound->pPrev = NULL; + + pEngine->pInlinedSoundHead = pSound; /* <-- This is what attaches the sound to the list. */ + if (pSound->pNext != NULL) { + pSound->pNext->pPrev = pSound; + } + } else { + ma_free(pSound, &pEngine->allocationCallbacks); + } + } else { + ma_free(pSound, &pEngine->allocationCallbacks); + } + } else { + result = MA_OUT_OF_MEMORY; + } + } + ma_spinlock_unlock(&pEngine->inlinedSoundLock); + + if (result != MA_SUCCESS) { + return result; + } + + /* Finally we can start playing the sound. */ + result = ma_sound_start(&pSound->sound); + if (result != MA_SUCCESS) { + /* Failed to start the sound. We need to mark it for recycling and return an error. */ + c89atomic_exchange_32(&pSound->sound.atEnd, MA_TRUE); + return result; + } + + c89atomic_fetch_add_32(&pEngine->inlinedSoundCount, 1); + return result; +} + +MA_API ma_result ma_engine_play_sound(ma_engine* pEngine, const char* pFilePath, ma_sound_group* pGroup) +{ + return ma_engine_play_sound_ex(pEngine, pFilePath, pGroup, 0); +} +#endif + + +static ma_result ma_sound_preinit(ma_engine* pEngine, ma_sound* pSound) +{ + if (pSound == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pSound); + pSound->seekTarget = MA_SEEK_TARGET_NONE; + + if (pEngine == NULL) { + return MA_INVALID_ARGS; + } + + return MA_SUCCESS; +} + +static ma_result ma_sound_init_from_data_source_internal(ma_engine* pEngine, const ma_sound_config* pConfig, ma_sound* pSound) +{ + ma_result result; + ma_engine_node_config engineNodeConfig; + ma_engine_node_type type; /* Will be set to ma_engine_node_type_group if no data source is specified. */ + + /* Do not clear pSound to zero here - that's done at a higher level with ma_sound_preinit(). */ + MA_ASSERT(pEngine != NULL); + MA_ASSERT(pSound != NULL); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + pSound->pDataSource = pConfig->pDataSource; + + if (pConfig->pDataSource != NULL) { + type = ma_engine_node_type_sound; + } else { + type = ma_engine_node_type_group; + } + + /* + Sounds are engine nodes. Before we can initialize this we need to determine the channel count. + If we can't do this we need to abort. It's up to the caller to ensure they're using a data + source that provides this information upfront. + */ + engineNodeConfig = ma_engine_node_config_init(pEngine, type, pConfig->flags); + engineNodeConfig.channelsIn = pConfig->channelsIn; + engineNodeConfig.channelsOut = pConfig->channelsOut; + + /* If we're loading from a data source the input channel count needs to be the data source's native channel count. */ + if (pConfig->pDataSource != NULL) { + result = ma_data_source_get_data_format(pConfig->pDataSource, NULL, &engineNodeConfig.channelsIn, &engineNodeConfig.sampleRate, NULL, 0); + if (result != MA_SUCCESS) { + return result; /* Failed to retrieve the channel count. */ + } + + if (engineNodeConfig.channelsIn == 0) { + return MA_INVALID_OPERATION; /* Invalid channel count. */ + } + + if (engineNodeConfig.channelsOut == MA_SOUND_SOURCE_CHANNEL_COUNT) { + engineNodeConfig.channelsOut = engineNodeConfig.channelsIn; + } + } + + + /* Getting here means we should have a valid channel count and we can initialize the engine node. */ + result = ma_engine_node_init(&engineNodeConfig, &pEngine->allocationCallbacks, &pSound->engineNode); + if (result != MA_SUCCESS) { + return result; + } + + /* If no attachment is specified, attach the sound straight to the endpoint. */ + if (pConfig->pInitialAttachment == NULL) { + /* No group. Attach straight to the endpoint by default, unless the caller has requested that do not. */ + if ((pConfig->flags & MA_SOUND_FLAG_NO_DEFAULT_ATTACHMENT) == 0) { + result = ma_node_attach_output_bus(pSound, 0, ma_node_graph_get_endpoint(&pEngine->nodeGraph), 0); + } + } else { + /* An attachment is specified. Attach to it by default. The sound has only a single output bus, and the config will specify which input bus to attach to. */ + result = ma_node_attach_output_bus(pSound, 0, pConfig->pInitialAttachment, pConfig->initialAttachmentInputBusIndex); + } + + if (result != MA_SUCCESS) { + ma_engine_node_uninit(&pSound->engineNode, &pEngine->allocationCallbacks); + return result; + } + + + /* Apply initial range and looping state to the data source if applicable. */ + if (pConfig->rangeBegInPCMFrames != 0 || pConfig->rangeEndInPCMFrames != ~((ma_uint64)0)) { + ma_data_source_set_range_in_pcm_frames(ma_sound_get_data_source(pSound), pConfig->rangeBegInPCMFrames, pConfig->rangeEndInPCMFrames); + } + + if (pConfig->loopPointBegInPCMFrames != 0 || pConfig->loopPointEndInPCMFrames != ~((ma_uint64)0)) { + ma_data_source_set_range_in_pcm_frames(ma_sound_get_data_source(pSound), pConfig->loopPointBegInPCMFrames, pConfig->loopPointEndInPCMFrames); + } + + ma_sound_set_looping(pSound, pConfig->isLooping); + + return MA_SUCCESS; +} + +#ifndef MA_NO_RESOURCE_MANAGER +MA_API ma_result ma_sound_init_from_file_internal(ma_engine* pEngine, const ma_sound_config* pConfig, ma_sound* pSound) +{ + ma_result result = MA_SUCCESS; + ma_uint32 flags; + ma_sound_config config; + ma_resource_manager_pipeline_notifications notifications; + + /* + The engine requires knowledge of the channel count of the underlying data source before it can + initialize the sound. Therefore, we need to make the resource manager wait until initialization + of the underlying data source to be initialized so we can get access to the channel count. To + do this, the MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT is forced. + + Because we're initializing the data source before the sound, there's a chance the notification + will get triggered before this function returns. This is OK, so long as the caller is aware of + it and can avoid accessing the sound from within the notification. + */ + flags = pConfig->flags | MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT; + + pSound->pResourceManagerDataSource = (ma_resource_manager_data_source*)ma_malloc(sizeof(*pSound->pResourceManagerDataSource), &pEngine->allocationCallbacks); + if (pSound->pResourceManagerDataSource == NULL) { + return MA_OUT_OF_MEMORY; + } + + notifications = ma_resource_manager_pipeline_notifications_init(); + notifications.done.pFence = pConfig->pDoneFence; + + /* + We must wrap everything around the fence if one was specified. This ensures ma_fence_wait() does + not return prematurely before the sound has finished initializing. + */ + if (notifications.done.pFence) { ma_fence_acquire(notifications.done.pFence); } + { + ma_resource_manager_data_source_config resourceManagerDataSourceConfig = ma_resource_manager_data_source_config_init(); + resourceManagerDataSourceConfig.pFilePath = pConfig->pFilePath; + resourceManagerDataSourceConfig.pFilePathW = pConfig->pFilePathW; + resourceManagerDataSourceConfig.flags = flags; + resourceManagerDataSourceConfig.pNotifications = ¬ifications; + resourceManagerDataSourceConfig.initialSeekPointInPCMFrames = pConfig->initialSeekPointInPCMFrames; + resourceManagerDataSourceConfig.rangeBegInPCMFrames = pConfig->rangeBegInPCMFrames; + resourceManagerDataSourceConfig.rangeEndInPCMFrames = pConfig->rangeEndInPCMFrames; + resourceManagerDataSourceConfig.loopPointBegInPCMFrames = pConfig->loopPointBegInPCMFrames; + resourceManagerDataSourceConfig.loopPointEndInPCMFrames = pConfig->loopPointEndInPCMFrames; + resourceManagerDataSourceConfig.isLooping = pConfig->isLooping; + + result = ma_resource_manager_data_source_init_ex(pEngine->pResourceManager, &resourceManagerDataSourceConfig, pSound->pResourceManagerDataSource); + if (result != MA_SUCCESS) { + goto done; + } + + pSound->ownsDataSource = MA_TRUE; /* <-- Important. Not setting this will result in the resource manager data source never getting uninitialized. */ + + /* We need to use a slightly customized version of the config so we'll need to make a copy. */ + config = *pConfig; + config.pFilePath = NULL; + config.pFilePathW = NULL; + config.pDataSource = pSound->pResourceManagerDataSource; + + result = ma_sound_init_from_data_source_internal(pEngine, &config, pSound); + if (result != MA_SUCCESS) { + ma_resource_manager_data_source_uninit(pSound->pResourceManagerDataSource); + ma_free(pSound->pResourceManagerDataSource, &pEngine->allocationCallbacks); + MA_ZERO_OBJECT(pSound); + goto done; + } + } +done: + if (notifications.done.pFence) { ma_fence_release(notifications.done.pFence); } + return result; +} + +MA_API ma_result ma_sound_init_from_file(ma_engine* pEngine, const char* pFilePath, ma_uint32 flags, ma_sound_group* pGroup, ma_fence* pDoneFence, ma_sound* pSound) +{ + ma_sound_config config = ma_sound_config_init(); + config.pFilePath = pFilePath; + config.flags = flags; + config.pInitialAttachment = pGroup; + config.pDoneFence = pDoneFence; + return ma_sound_init_ex(pEngine, &config, pSound); +} + +MA_API ma_result ma_sound_init_from_file_w(ma_engine* pEngine, const wchar_t* pFilePath, ma_uint32 flags, ma_sound_group* pGroup, ma_fence* pDoneFence, ma_sound* pSound) +{ + ma_sound_config config = ma_sound_config_init(); + config.pFilePathW = pFilePath; + config.flags = flags; + config.pInitialAttachment = pGroup; + config.pDoneFence = pDoneFence; + return ma_sound_init_ex(pEngine, &config, pSound); +} + +MA_API ma_result ma_sound_init_copy(ma_engine* pEngine, const ma_sound* pExistingSound, ma_uint32 flags, ma_sound_group* pGroup, ma_sound* pSound) +{ + ma_result result; + ma_sound_config config; + + result = ma_sound_preinit(pEngine, pSound); + if (result != MA_SUCCESS) { + return result; + } + + if (pExistingSound == NULL) { + return MA_INVALID_ARGS; + } + + /* Cloning only works for data buffers (not streams) that are loaded from the resource manager. */ + if (pExistingSound->pResourceManagerDataSource == NULL) { + return MA_INVALID_OPERATION; + } + + /* + We need to make a clone of the data source. If the data source is not a data buffer (i.e. a stream) + the this will fail. + */ + pSound->pResourceManagerDataSource = (ma_resource_manager_data_source*)ma_malloc(sizeof(*pSound->pResourceManagerDataSource), &pEngine->allocationCallbacks); + if (pSound->pResourceManagerDataSource == NULL) { + return MA_OUT_OF_MEMORY; + } + + result = ma_resource_manager_data_source_init_copy(pEngine->pResourceManager, pExistingSound->pResourceManagerDataSource, pSound->pResourceManagerDataSource); + if (result != MA_SUCCESS) { + ma_free(pSound->pResourceManagerDataSource, &pEngine->allocationCallbacks); + return result; + } + + config = ma_sound_config_init(); + config.pDataSource = pSound->pResourceManagerDataSource; + config.flags = flags; + config.pInitialAttachment = pGroup; + + result = ma_sound_init_from_data_source_internal(pEngine, &config, pSound); + if (result != MA_SUCCESS) { + ma_resource_manager_data_source_uninit(pSound->pResourceManagerDataSource); + ma_free(pSound->pResourceManagerDataSource, &pEngine->allocationCallbacks); + MA_ZERO_OBJECT(pSound); + return result; + } + + return MA_SUCCESS; +} +#endif + +MA_API ma_result ma_sound_init_from_data_source(ma_engine* pEngine, ma_data_source* pDataSource, ma_uint32 flags, ma_sound_group* pGroup, ma_sound* pSound) +{ + ma_sound_config config = ma_sound_config_init(); + config.pDataSource = pDataSource; + config.flags = flags; + config.pInitialAttachment = pGroup; + return ma_sound_init_ex(pEngine, &config, pSound); +} + +MA_API ma_result ma_sound_init_ex(ma_engine* pEngine, const ma_sound_config* pConfig, ma_sound* pSound) +{ + ma_result result; + + result = ma_sound_preinit(pEngine, pSound); + if (result != MA_SUCCESS) { + return result; + } + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + /* We need to load the sound differently depending on whether or not we're loading from a file. */ +#ifndef MA_NO_RESOURCE_MANAGER + if (pConfig->pFilePath != NULL || pConfig->pFilePathW != NULL) { + return ma_sound_init_from_file_internal(pEngine, pConfig, pSound); + } else +#endif + { + /* + Getting here means we're not loading from a file. We may be loading from an already-initialized + data source, or none at all. If we aren't specifying any data source, we'll be initializing the + the equivalent to a group. ma_data_source_init_from_data_source_internal() will deal with this + for us, so no special treatment required here. + */ + return ma_sound_init_from_data_source_internal(pEngine, pConfig, pSound); + } +} + +MA_API void ma_sound_uninit(ma_sound* pSound) +{ + if (pSound == NULL) { + return; + } + + /* + Always uninitialize the node first. This ensures it's detached from the graph and does not return until it has done + so which makes thread safety beyond this point trivial. + */ + ma_engine_node_uninit(&pSound->engineNode, &pSound->engineNode.pEngine->allocationCallbacks); + + /* Once the sound is detached from the group we can guarantee that it won't be referenced by the mixer thread which means it's safe for us to destroy the data source. */ +#ifndef MA_NO_RESOURCE_MANAGER + if (pSound->ownsDataSource) { + ma_resource_manager_data_source_uninit(pSound->pResourceManagerDataSource); + ma_free(pSound->pResourceManagerDataSource, &pSound->engineNode.pEngine->allocationCallbacks); + pSound->pDataSource = NULL; + } +#else + MA_ASSERT(pSound->ownsDataSource == MA_FALSE); +#endif +} + +MA_API ma_engine* ma_sound_get_engine(const ma_sound* pSound) +{ + if (pSound == NULL) { + return NULL; + } + + return pSound->engineNode.pEngine; +} + +MA_API ma_data_source* ma_sound_get_data_source(const ma_sound* pSound) +{ + if (pSound == NULL) { + return NULL; + } + + return pSound->pDataSource; +} + +MA_API ma_result ma_sound_start(ma_sound* pSound) +{ + if (pSound == NULL) { + return MA_INVALID_ARGS; + } + + /* If the sound is already playing, do nothing. */ + if (ma_sound_is_playing(pSound)) { + return MA_SUCCESS; + } + + /* If the sound is at the end it means we want to start from the start again. */ + if (ma_sound_at_end(pSound)) { + ma_result result = ma_data_source_seek_to_pcm_frame(pSound->pDataSource, 0); + if (result != MA_SUCCESS && result != MA_NOT_IMPLEMENTED) { + return result; /* Failed to seek back to the start. */ + } + + /* Make sure we clear the end indicator. */ + c89atomic_exchange_32(&pSound->atEnd, MA_FALSE); + } + + /* Make sure the sound is started. If there's a start delay, the sound won't actually start until the start time is reached. */ + ma_node_set_state(pSound, ma_node_state_started); + + return MA_SUCCESS; +} + +MA_API ma_result ma_sound_stop(ma_sound* pSound) +{ + if (pSound == NULL) { + return MA_INVALID_ARGS; + } + + /* This will stop the sound immediately. Use ma_sound_set_stop_time() to stop the sound at a specific time. */ + ma_node_set_state(pSound, ma_node_state_stopped); + + return MA_SUCCESS; +} + +MA_API void ma_sound_set_volume(ma_sound* pSound, float volume) +{ + if (pSound == NULL) { + return; + } + + /* The volume is controlled via the output bus. */ + ma_node_set_output_bus_volume(pSound, 0, volume); +} + +MA_API float ma_sound_get_volume(const ma_sound* pSound) +{ + if (pSound == NULL) { + return 0; + } + + return ma_node_get_output_bus_volume(pSound, 0); +} + +MA_API void ma_sound_set_pan(ma_sound* pSound, float pan) +{ + if (pSound == NULL) { + return; + } + + ma_panner_set_pan(&pSound->engineNode.panner, pan); +} + +MA_API float ma_sound_get_pan(const ma_sound* pSound) +{ + if (pSound == NULL) { + return 0; + } + + return ma_panner_get_pan(&pSound->engineNode.panner); +} + +MA_API void ma_sound_set_pan_mode(ma_sound* pSound, ma_pan_mode panMode) +{ + if (pSound == NULL) { + return; + } + + ma_panner_set_mode(&pSound->engineNode.panner, panMode); +} + +MA_API ma_pan_mode ma_sound_get_pan_mode(const ma_sound* pSound) +{ + if (pSound == NULL) { + return ma_pan_mode_balance; + } + + return ma_panner_get_mode(&pSound->engineNode.panner); +} + +MA_API void ma_sound_set_pitch(ma_sound* pSound, float pitch) +{ + if (pSound == NULL) { + return; + } + + if (pitch <= 0) { + return; + } + + c89atomic_exchange_explicit_f32(&pSound->engineNode.pitch, pitch, c89atomic_memory_order_release); +} + +MA_API float ma_sound_get_pitch(const ma_sound* pSound) +{ + if (pSound == NULL) { + return 0; + } + + return c89atomic_load_f32(&pSound->engineNode.pitch); /* Naughty const-cast for this. */ +} + +MA_API void ma_sound_set_spatialization_enabled(ma_sound* pSound, ma_bool32 enabled) +{ + if (pSound == NULL) { + return; + } + + c89atomic_exchange_explicit_32(&pSound->engineNode.isSpatializationDisabled, !enabled, c89atomic_memory_order_release); +} + +MA_API ma_bool32 ma_sound_is_spatialization_enabled(const ma_sound* pSound) +{ + if (pSound == NULL) { + return MA_FALSE; + } + + return ma_engine_node_is_spatialization_enabled(&pSound->engineNode); +} + +MA_API void ma_sound_set_pinned_listener_index(ma_sound* pSound, ma_uint32 listenerIndex) +{ + if (pSound == NULL || listenerIndex >= ma_engine_get_listener_count(ma_sound_get_engine(pSound))) { + return; + } + + c89atomic_exchange_explicit_32(&pSound->engineNode.pinnedListenerIndex, listenerIndex, c89atomic_memory_order_release); +} + +MA_API ma_uint32 ma_sound_get_pinned_listener_index(const ma_sound* pSound) +{ + if (pSound == NULL) { + return MA_LISTENER_INDEX_CLOSEST; + } + + return c89atomic_load_explicit_32(&pSound->engineNode.pinnedListenerIndex, c89atomic_memory_order_acquire); +} + +MA_API ma_uint32 ma_sound_get_listener_index(const ma_sound* pSound) +{ + ma_uint32 listenerIndex; + + if (pSound == NULL) { + return 0; + } + + listenerIndex = ma_sound_get_pinned_listener_index(pSound); + if (listenerIndex == MA_LISTENER_INDEX_CLOSEST) { + ma_vec3f position = ma_sound_get_position(pSound); + return ma_engine_find_closest_listener(ma_sound_get_engine(pSound), position.x, position.y, position.z); + } + + return listenerIndex; +} + +MA_API ma_vec3f ma_sound_get_direction_to_listener(const ma_sound* pSound) +{ + ma_vec3f relativePos; + ma_engine* pEngine; + + if (pSound == NULL) { + return ma_vec3f_init_3f(0, 0, -1); + } + + pEngine = ma_sound_get_engine(pSound); + if (pEngine == NULL) { + return ma_vec3f_init_3f(0, 0, -1); + } + + ma_spatializer_get_relative_position_and_direction(&pSound->engineNode.spatializer, &pEngine->listeners[ma_sound_get_listener_index(pSound)], &relativePos, NULL); + + return ma_vec3f_normalize(ma_vec3f_neg(relativePos)); +} + +MA_API void ma_sound_set_position(ma_sound* pSound, float x, float y, float z) +{ + if (pSound == NULL) { + return; + } + + ma_spatializer_set_position(&pSound->engineNode.spatializer, x, y, z); +} + +MA_API ma_vec3f ma_sound_get_position(const ma_sound* pSound) +{ + if (pSound == NULL) { + return ma_vec3f_init_3f(0, 0, 0); + } + + return ma_spatializer_get_position(&pSound->engineNode.spatializer); +} + +MA_API void ma_sound_set_direction(ma_sound* pSound, float x, float y, float z) +{ + if (pSound == NULL) { + return; + } + + ma_spatializer_set_direction(&pSound->engineNode.spatializer, x, y, z); +} + +MA_API ma_vec3f ma_sound_get_direction(const ma_sound* pSound) +{ + if (pSound == NULL) { + return ma_vec3f_init_3f(0, 0, 0); + } + + return ma_spatializer_get_direction(&pSound->engineNode.spatializer); +} + +MA_API void ma_sound_set_velocity(ma_sound* pSound, float x, float y, float z) +{ + if (pSound == NULL) { + return; + } + + ma_spatializer_set_velocity(&pSound->engineNode.spatializer, x, y, z); +} + +MA_API ma_vec3f ma_sound_get_velocity(const ma_sound* pSound) +{ + if (pSound == NULL) { + return ma_vec3f_init_3f(0, 0, 0); + } + + return ma_spatializer_get_velocity(&pSound->engineNode.spatializer); +} + +MA_API void ma_sound_set_attenuation_model(ma_sound* pSound, ma_attenuation_model attenuationModel) +{ + if (pSound == NULL) { + return; + } + + ma_spatializer_set_attenuation_model(&pSound->engineNode.spatializer, attenuationModel); +} + +MA_API ma_attenuation_model ma_sound_get_attenuation_model(const ma_sound* pSound) +{ + if (pSound == NULL) { + return ma_attenuation_model_none; + } + + return ma_spatializer_get_attenuation_model(&pSound->engineNode.spatializer); +} + +MA_API void ma_sound_set_positioning(ma_sound* pSound, ma_positioning positioning) +{ + if (pSound == NULL) { + return; + } + + ma_spatializer_set_positioning(&pSound->engineNode.spatializer, positioning); +} + +MA_API ma_positioning ma_sound_get_positioning(const ma_sound* pSound) +{ + if (pSound == NULL) { + return ma_positioning_absolute; + } + + return ma_spatializer_get_positioning(&pSound->engineNode.spatializer); +} + +MA_API void ma_sound_set_rolloff(ma_sound* pSound, float rolloff) +{ + if (pSound == NULL) { + return; + } + + ma_spatializer_set_rolloff(&pSound->engineNode.spatializer, rolloff); +} + +MA_API float ma_sound_get_rolloff(const ma_sound* pSound) +{ + if (pSound == NULL) { + return 0; + } + + return ma_spatializer_get_rolloff(&pSound->engineNode.spatializer); +} + +MA_API void ma_sound_set_min_gain(ma_sound* pSound, float minGain) +{ + if (pSound == NULL) { + return; + } + + ma_spatializer_set_min_gain(&pSound->engineNode.spatializer, minGain); +} + +MA_API float ma_sound_get_min_gain(const ma_sound* pSound) +{ + if (pSound == NULL) { + return 0; + } + + return ma_spatializer_get_min_gain(&pSound->engineNode.spatializer); +} + +MA_API void ma_sound_set_max_gain(ma_sound* pSound, float maxGain) +{ + if (pSound == NULL) { + return; + } + + ma_spatializer_set_max_gain(&pSound->engineNode.spatializer, maxGain); +} + +MA_API float ma_sound_get_max_gain(const ma_sound* pSound) +{ + if (pSound == NULL) { + return 0; + } + + return ma_spatializer_get_max_gain(&pSound->engineNode.spatializer); +} + +MA_API void ma_sound_set_min_distance(ma_sound* pSound, float minDistance) +{ + if (pSound == NULL) { + return; + } + + ma_spatializer_set_min_distance(&pSound->engineNode.spatializer, minDistance); +} + +MA_API float ma_sound_get_min_distance(const ma_sound* pSound) +{ + if (pSound == NULL) { + return 0; + } + + return ma_spatializer_get_min_distance(&pSound->engineNode.spatializer); +} + +MA_API void ma_sound_set_max_distance(ma_sound* pSound, float maxDistance) +{ + if (pSound == NULL) { + return; + } + + ma_spatializer_set_max_distance(&pSound->engineNode.spatializer, maxDistance); +} + +MA_API float ma_sound_get_max_distance(const ma_sound* pSound) +{ + if (pSound == NULL) { + return 0; + } + + return ma_spatializer_get_max_distance(&pSound->engineNode.spatializer); +} + +MA_API void ma_sound_set_cone(ma_sound* pSound, float innerAngleInRadians, float outerAngleInRadians, float outerGain) +{ + if (pSound == NULL) { + return; + } + + ma_spatializer_set_cone(&pSound->engineNode.spatializer, innerAngleInRadians, outerAngleInRadians, outerGain); +} + +MA_API void ma_sound_get_cone(const ma_sound* pSound, float* pInnerAngleInRadians, float* pOuterAngleInRadians, float* pOuterGain) +{ + if (pInnerAngleInRadians != NULL) { + *pInnerAngleInRadians = 0; + } + + if (pOuterAngleInRadians != NULL) { + *pOuterAngleInRadians = 0; + } + + if (pOuterGain != NULL) { + *pOuterGain = 0; + } + + ma_spatializer_get_cone(&pSound->engineNode.spatializer, pInnerAngleInRadians, pOuterAngleInRadians, pOuterGain); +} + +MA_API void ma_sound_set_doppler_factor(ma_sound* pSound, float dopplerFactor) +{ + if (pSound == NULL) { + return; + } + + ma_spatializer_set_doppler_factor(&pSound->engineNode.spatializer, dopplerFactor); +} + +MA_API float ma_sound_get_doppler_factor(const ma_sound* pSound) +{ + if (pSound == NULL) { + return 0; + } + + return ma_spatializer_get_doppler_factor(&pSound->engineNode.spatializer); +} + +MA_API void ma_sound_set_directional_attenuation_factor(ma_sound* pSound, float directionalAttenuationFactor) +{ + if (pSound == NULL) { + return; + } + + ma_spatializer_set_directional_attenuation_factor(&pSound->engineNode.spatializer, directionalAttenuationFactor); +} + +MA_API float ma_sound_get_directional_attenuation_factor(const ma_sound* pSound) +{ + if (pSound == NULL) { + return 1; + } + + return ma_spatializer_get_directional_attenuation_factor(&pSound->engineNode.spatializer); +} + + +MA_API void ma_sound_set_fade_in_pcm_frames(ma_sound* pSound, float volumeBeg, float volumeEnd, ma_uint64 fadeLengthInFrames) +{ + if (pSound == NULL) { + return; + } + + ma_fader_set_fade(&pSound->engineNode.fader, volumeBeg, volumeEnd, fadeLengthInFrames); +} + +MA_API void ma_sound_set_fade_in_milliseconds(ma_sound* pSound, float volumeBeg, float volumeEnd, ma_uint64 fadeLengthInMilliseconds) +{ + if (pSound == NULL) { + return; + } + + ma_sound_set_fade_in_pcm_frames(pSound, volumeBeg, volumeEnd, (fadeLengthInMilliseconds * pSound->engineNode.fader.config.sampleRate) / 1000); +} + +MA_API float ma_sound_get_current_fade_volume(ma_sound* pSound) +{ + if (pSound == NULL) { + return MA_INVALID_ARGS; + } + + return ma_fader_get_current_volume(&pSound->engineNode.fader); +} + +MA_API void ma_sound_set_start_time_in_pcm_frames(ma_sound* pSound, ma_uint64 absoluteGlobalTimeInFrames) +{ + if (pSound == NULL) { + return; + } + + ma_node_set_state_time(pSound, ma_node_state_started, absoluteGlobalTimeInFrames); +} + +MA_API void ma_sound_set_start_time_in_milliseconds(ma_sound* pSound, ma_uint64 absoluteGlobalTimeInMilliseconds) +{ + if (pSound == NULL) { + return; + } + + ma_sound_set_start_time_in_pcm_frames(pSound, absoluteGlobalTimeInMilliseconds * ma_engine_get_sample_rate(ma_sound_get_engine(pSound)) / 1000); +} + +MA_API void ma_sound_set_stop_time_in_pcm_frames(ma_sound* pSound, ma_uint64 absoluteGlobalTimeInFrames) +{ + if (pSound == NULL) { + return; + } + + ma_node_set_state_time(pSound, ma_node_state_stopped, absoluteGlobalTimeInFrames); +} + +MA_API void ma_sound_set_stop_time_in_milliseconds(ma_sound* pSound, ma_uint64 absoluteGlobalTimeInMilliseconds) +{ + if (pSound == NULL) { + return; + } + + ma_sound_set_stop_time_in_pcm_frames(pSound, absoluteGlobalTimeInMilliseconds * ma_engine_get_sample_rate(ma_sound_get_engine(pSound)) / 1000); +} + +MA_API ma_bool32 ma_sound_is_playing(const ma_sound* pSound) +{ + if (pSound == NULL) { + return MA_FALSE; + } + + return ma_node_get_state_by_time(pSound, ma_engine_get_time(ma_sound_get_engine(pSound))) == ma_node_state_started; +} + +MA_API ma_uint64 ma_sound_get_time_in_pcm_frames(const ma_sound* pSound) +{ + if (pSound == NULL) { + return 0; + } + + return ma_node_get_time(pSound); +} + +MA_API void ma_sound_set_looping(ma_sound* pSound, ma_bool32 isLooping) +{ + if (pSound == NULL) { + return; + } + + /* Looping is only a valid concept if the sound is backed by a data source. */ + if (pSound->pDataSource == NULL) { + return; + } + + /* The looping state needs to be applied to the data source in order for any looping to actually happen. */ + ma_data_source_set_looping(pSound->pDataSource, isLooping); +} + +MA_API ma_bool32 ma_sound_is_looping(const ma_sound* pSound) +{ + if (pSound == NULL) { + return MA_FALSE; + } + + /* There is no notion of looping for sounds that are not backed by a data source. */ + if (pSound->pDataSource == NULL) { + return MA_FALSE; + } + + return ma_data_source_is_looping(pSound->pDataSource); +} + +MA_API ma_bool32 ma_sound_at_end(const ma_sound* pSound) +{ + if (pSound == NULL) { + return MA_FALSE; + } + + /* There is no notion of an end of a sound if it's not backed by a data source. */ + if (pSound->pDataSource == NULL) { + return MA_FALSE; + } + + return c89atomic_load_32(&pSound->atEnd); +} + +MA_API ma_result ma_sound_seek_to_pcm_frame(ma_sound* pSound, ma_uint64 frameIndex) +{ + if (pSound == NULL) { + return MA_INVALID_ARGS; + } + + /* Seeking is only valid for sounds that are backed by a data source. */ + if (pSound->pDataSource == NULL) { + return MA_INVALID_OPERATION; + } + + /* We can't be seeking while reading at the same time. We just set the seek target and get the mixing thread to do the actual seek. */ + c89atomic_exchange_64(&pSound->seekTarget, frameIndex); + + return MA_SUCCESS; +} + +MA_API ma_result ma_sound_get_data_format(ma_sound* pSound, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) +{ + if (pSound == NULL) { + return MA_INVALID_ARGS; + } + + /* The data format is retrieved directly from the data source if the sound is backed by one. Otherwise we pull it from the node. */ + if (pSound->pDataSource == NULL) { + ma_uint32 channels; + + if (pFormat != NULL) { + *pFormat = ma_format_f32; + } + + channels = ma_node_get_input_channels(&pSound->engineNode, 0); + if (pChannels != NULL) { + *pChannels = channels; + } + + if (pSampleRate != NULL) { + *pSampleRate = pSound->engineNode.resampler.config.sampleRateIn; + } + + if (pChannelMap != NULL) { + ma_channel_map_init_standard(ma_standard_channel_map_default, pChannelMap, channelMapCap, channels); + } + + return MA_SUCCESS; + } else { + return ma_data_source_get_data_format(pSound->pDataSource, pFormat, pChannels, pSampleRate, pChannelMap, channelMapCap); + } +} + +MA_API ma_result ma_sound_get_cursor_in_pcm_frames(ma_sound* pSound, ma_uint64* pCursor) +{ + if (pSound == NULL) { + return MA_INVALID_ARGS; + } + + /* The notion of a cursor is only valid for sounds that are backed by a data source. */ + if (pSound->pDataSource == NULL) { + return MA_INVALID_OPERATION; + } + + return ma_data_source_get_cursor_in_pcm_frames(pSound->pDataSource, pCursor); +} + +MA_API ma_result ma_sound_get_length_in_pcm_frames(ma_sound* pSound, ma_uint64* pLength) +{ + if (pSound == NULL) { + return MA_INVALID_ARGS; + } + + /* The notion of a sound length is only valid for sounds that are backed by a data source. */ + if (pSound->pDataSource == NULL) { + return MA_INVALID_OPERATION; + } + + return ma_data_source_get_length_in_pcm_frames(pSound->pDataSource, pLength); +} + +MA_API ma_result ma_sound_get_cursor_in_seconds(ma_sound* pSound, float* pCursor) +{ + if (pSound == NULL) { + return MA_INVALID_ARGS; + } + + /* The notion of a cursor is only valid for sounds that are backed by a data source. */ + if (pSound->pDataSource == NULL) { + return MA_INVALID_OPERATION; + } + + return ma_data_source_get_cursor_in_seconds(pSound->pDataSource, pCursor); +} + +MA_API ma_result ma_sound_get_length_in_seconds(ma_sound* pSound, float* pLength) +{ + if (pSound == NULL) { + return MA_INVALID_ARGS; + } + + /* The notion of a sound length is only valid for sounds that are backed by a data source. */ + if (pSound->pDataSource == NULL) { + return MA_INVALID_OPERATION; + } + + return ma_data_source_get_length_in_seconds(pSound->pDataSource, pLength); +} + + +MA_API ma_result ma_sound_group_init(ma_engine* pEngine, ma_uint32 flags, ma_sound_group* pParentGroup, ma_sound_group* pGroup) +{ + ma_sound_group_config config = ma_sound_group_config_init(); + config.flags = flags; + config.pInitialAttachment = pParentGroup; + return ma_sound_group_init_ex(pEngine, &config, pGroup); +} + +MA_API ma_result ma_sound_group_init_ex(ma_engine* pEngine, const ma_sound_group_config* pConfig, ma_sound_group* pGroup) +{ + ma_sound_config soundConfig; + + if (pGroup == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pGroup); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + /* A sound group is just a sound without a data source. */ + soundConfig = *pConfig; + soundConfig.pFilePath = NULL; + soundConfig.pFilePathW = NULL; + soundConfig.pDataSource = NULL; + + /* + Groups need to have spatialization disabled by default because I think it'll be pretty rare + that programs will want to spatialize groups (but not unheard of). Certainly it feels like + disabling this by default feels like the right option. Spatialization can be enabled with a + call to ma_sound_group_set_spatialization_enabled(). + */ + soundConfig.flags |= MA_SOUND_FLAG_NO_SPATIALIZATION; + + return ma_sound_init_ex(pEngine, &soundConfig, pGroup); +} + +MA_API void ma_sound_group_uninit(ma_sound_group* pGroup) +{ + ma_sound_uninit(pGroup); +} + +MA_API ma_engine* ma_sound_group_get_engine(const ma_sound_group* pGroup) +{ + return ma_sound_get_engine(pGroup); +} + +MA_API ma_result ma_sound_group_start(ma_sound_group* pGroup) +{ + return ma_sound_start(pGroup); +} + +MA_API ma_result ma_sound_group_stop(ma_sound_group* pGroup) +{ + return ma_sound_stop(pGroup); +} + +MA_API void ma_sound_group_set_volume(ma_sound_group* pGroup, float volume) +{ + ma_sound_set_volume(pGroup, volume); +} + +MA_API float ma_sound_group_get_volume(const ma_sound_group* pGroup) +{ + return ma_sound_get_volume(pGroup); +} + +MA_API void ma_sound_group_set_pan(ma_sound_group* pGroup, float pan) +{ + ma_sound_set_pan(pGroup, pan); +} + +MA_API float ma_sound_group_get_pan(const ma_sound_group* pGroup) +{ + return ma_sound_get_pan(pGroup); +} + +MA_API void ma_sound_group_set_pan_mode(ma_sound_group* pGroup, ma_pan_mode panMode) +{ + ma_sound_set_pan_mode(pGroup, panMode); +} + +MA_API ma_pan_mode ma_sound_group_get_pan_mode(const ma_sound_group* pGroup) +{ + return ma_sound_get_pan_mode(pGroup); +} + +MA_API void ma_sound_group_set_pitch(ma_sound_group* pGroup, float pitch) +{ + ma_sound_set_pitch(pGroup, pitch); +} + +MA_API float ma_sound_group_get_pitch(const ma_sound_group* pGroup) +{ + return ma_sound_get_pitch(pGroup); +} + +MA_API void ma_sound_group_set_spatialization_enabled(ma_sound_group* pGroup, ma_bool32 enabled) +{ + ma_sound_set_spatialization_enabled(pGroup, enabled); +} + +MA_API ma_bool32 ma_sound_group_is_spatialization_enabled(const ma_sound_group* pGroup) +{ + return ma_sound_is_spatialization_enabled(pGroup); +} + +MA_API void ma_sound_group_set_pinned_listener_index(ma_sound_group* pGroup, ma_uint32 listenerIndex) +{ + ma_sound_set_pinned_listener_index(pGroup, listenerIndex); +} + +MA_API ma_uint32 ma_sound_group_get_pinned_listener_index(const ma_sound_group* pGroup) +{ + return ma_sound_get_pinned_listener_index(pGroup); +} + +MA_API ma_uint32 ma_sound_group_get_listener_index(const ma_sound_group* pGroup) +{ + return ma_sound_get_listener_index(pGroup); +} + +MA_API ma_vec3f ma_sound_group_get_direction_to_listener(const ma_sound_group* pGroup) +{ + return ma_sound_get_direction_to_listener(pGroup); +} + +MA_API void ma_sound_group_set_position(ma_sound_group* pGroup, float x, float y, float z) +{ + ma_sound_set_position(pGroup, x, y, z); +} + +MA_API ma_vec3f ma_sound_group_get_position(const ma_sound_group* pGroup) +{ + return ma_sound_get_position(pGroup); +} + +MA_API void ma_sound_group_set_direction(ma_sound_group* pGroup, float x, float y, float z) +{ + ma_sound_set_direction(pGroup, x, y, z); +} + +MA_API ma_vec3f ma_sound_group_get_direction(const ma_sound_group* pGroup) +{ + return ma_sound_get_direction(pGroup); +} + +MA_API void ma_sound_group_set_velocity(ma_sound_group* pGroup, float x, float y, float z) +{ + ma_sound_set_velocity(pGroup, x, y, z); +} + +MA_API ma_vec3f ma_sound_group_get_velocity(const ma_sound_group* pGroup) +{ + return ma_sound_get_velocity(pGroup); +} + +MA_API void ma_sound_group_set_attenuation_model(ma_sound_group* pGroup, ma_attenuation_model attenuationModel) +{ + ma_sound_set_attenuation_model(pGroup, attenuationModel); +} + +MA_API ma_attenuation_model ma_sound_group_get_attenuation_model(const ma_sound_group* pGroup) +{ + return ma_sound_get_attenuation_model(pGroup); +} + +MA_API void ma_sound_group_set_positioning(ma_sound_group* pGroup, ma_positioning positioning) +{ + ma_sound_set_positioning(pGroup, positioning); +} + +MA_API ma_positioning ma_sound_group_get_positioning(const ma_sound_group* pGroup) +{ + return ma_sound_get_positioning(pGroup); +} + +MA_API void ma_sound_group_set_rolloff(ma_sound_group* pGroup, float rolloff) +{ + ma_sound_set_rolloff(pGroup, rolloff); +} + +MA_API float ma_sound_group_get_rolloff(const ma_sound_group* pGroup) +{ + return ma_sound_get_rolloff(pGroup); +} + +MA_API void ma_sound_group_set_min_gain(ma_sound_group* pGroup, float minGain) +{ + ma_sound_set_min_gain(pGroup, minGain); +} + +MA_API float ma_sound_group_get_min_gain(const ma_sound_group* pGroup) +{ + return ma_sound_get_min_gain(pGroup); +} + +MA_API void ma_sound_group_set_max_gain(ma_sound_group* pGroup, float maxGain) +{ + ma_sound_set_max_gain(pGroup, maxGain); +} + +MA_API float ma_sound_group_get_max_gain(const ma_sound_group* pGroup) +{ + return ma_sound_get_max_gain(pGroup); +} + +MA_API void ma_sound_group_set_min_distance(ma_sound_group* pGroup, float minDistance) +{ + ma_sound_set_min_distance(pGroup, minDistance); +} + +MA_API float ma_sound_group_get_min_distance(const ma_sound_group* pGroup) +{ + return ma_sound_get_min_distance(pGroup); +} + +MA_API void ma_sound_group_set_max_distance(ma_sound_group* pGroup, float maxDistance) +{ + ma_sound_set_max_distance(pGroup, maxDistance); +} + +MA_API float ma_sound_group_get_max_distance(const ma_sound_group* pGroup) +{ + return ma_sound_get_max_distance(pGroup); +} + +MA_API void ma_sound_group_set_cone(ma_sound_group* pGroup, float innerAngleInRadians, float outerAngleInRadians, float outerGain) +{ + ma_sound_set_cone(pGroup, innerAngleInRadians, outerAngleInRadians, outerGain); +} + +MA_API void ma_sound_group_get_cone(const ma_sound_group* pGroup, float* pInnerAngleInRadians, float* pOuterAngleInRadians, float* pOuterGain) +{ + ma_sound_get_cone(pGroup, pInnerAngleInRadians, pOuterAngleInRadians, pOuterGain); +} + +MA_API void ma_sound_group_set_doppler_factor(ma_sound_group* pGroup, float dopplerFactor) +{ + ma_sound_set_doppler_factor(pGroup, dopplerFactor); +} + +MA_API float ma_sound_group_get_doppler_factor(const ma_sound_group* pGroup) +{ + return ma_sound_get_doppler_factor(pGroup); +} + +MA_API void ma_sound_group_set_directional_attenuation_factor(ma_sound_group* pGroup, float directionalAttenuationFactor) +{ + ma_sound_set_directional_attenuation_factor(pGroup, directionalAttenuationFactor); +} + +MA_API float ma_sound_group_get_directional_attenuation_factor(const ma_sound_group* pGroup) +{ + return ma_sound_get_directional_attenuation_factor(pGroup); +} + +MA_API void ma_sound_group_set_fade_in_pcm_frames(ma_sound_group* pGroup, float volumeBeg, float volumeEnd, ma_uint64 fadeLengthInFrames) +{ + ma_sound_set_fade_in_pcm_frames(pGroup, volumeBeg, volumeEnd, fadeLengthInFrames); +} + +MA_API void ma_sound_group_set_fade_in_milliseconds(ma_sound_group* pGroup, float volumeBeg, float volumeEnd, ma_uint64 fadeLengthInMilliseconds) +{ + ma_sound_set_fade_in_milliseconds(pGroup, volumeBeg, volumeEnd, fadeLengthInMilliseconds); +} + +MA_API float ma_sound_group_get_current_fade_volume(ma_sound_group* pGroup) +{ + return ma_sound_get_current_fade_volume(pGroup); +} + +MA_API void ma_sound_group_set_start_time_in_pcm_frames(ma_sound_group* pGroup, ma_uint64 absoluteGlobalTimeInFrames) +{ + ma_sound_set_start_time_in_pcm_frames(pGroup, absoluteGlobalTimeInFrames); +} + +MA_API void ma_sound_group_set_start_time_in_milliseconds(ma_sound_group* pGroup, ma_uint64 absoluteGlobalTimeInMilliseconds) +{ + ma_sound_set_start_time_in_milliseconds(pGroup, absoluteGlobalTimeInMilliseconds); +} + +MA_API void ma_sound_group_set_stop_time_in_pcm_frames(ma_sound_group* pGroup, ma_uint64 absoluteGlobalTimeInFrames) +{ + ma_sound_set_stop_time_in_pcm_frames(pGroup, absoluteGlobalTimeInFrames); +} + +MA_API void ma_sound_group_set_stop_time_in_milliseconds(ma_sound_group* pGroup, ma_uint64 absoluteGlobalTimeInMilliseconds) +{ + ma_sound_set_stop_time_in_milliseconds(pGroup, absoluteGlobalTimeInMilliseconds); +} + +MA_API ma_bool32 ma_sound_group_is_playing(const ma_sound_group* pGroup) +{ + return ma_sound_is_playing(pGroup); +} + +MA_API ma_uint64 ma_sound_group_get_time_in_pcm_frames(const ma_sound_group* pGroup) +{ + return ma_sound_get_time_in_pcm_frames(pGroup); +} +#endif /* MA_NO_ENGINE */ + + + /************************************************************************************************************************************************************** *************************************************************************************************************************************************************** @@ -52852,6 +73812,7 @@ code below please report the bug to the respective repository for the relevant p #define drwav_min(a, b) (((a) < (b)) ? (a) : (b)) #define drwav_max(a, b) (((a) > (b)) ? (a) : (b)) #define drwav_clamp(x, lo, hi) (drwav_max((lo), drwav_min((hi), (x)))) +#define drwav_offset_ptr(p, offset) (((drwav_uint8*)(p)) + (offset)) #define DRWAV_MAX_SIMD_VECTOR_SIZE 64 #if defined(__x86_64__) || defined(_M_X64) #define DRWAV_X64 @@ -52864,9 +73825,14 @@ code below please report the bug to the respective repository for the relevant p #define DRWAV_INLINE __forceinline #elif defined(__GNUC__) #if defined(__STRICT_ANSI__) - #define DRWAV_INLINE __inline__ __attribute__((always_inline)) + #define DRWAV_GNUC_INLINE_HINT __inline__ #else - #define DRWAV_INLINE inline __attribute__((always_inline)) + #define DRWAV_GNUC_INLINE_HINT inline + #endif + #if (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 2)) || defined(__clang__) + #define DRWAV_INLINE DRWAV_GNUC_INLINE_HINT __attribute__((always_inline)) + #else + #define DRWAV_INLINE DRWAV_GNUC_INLINE_HINT #endif #elif defined(__WATCOMC__) #define DRWAV_INLINE __inline @@ -53095,6 +74061,9 @@ static DRWAV_INLINE void drwav__bswap_samples_pcm(void* pSamples, drwav_uint64 s { switch (bytesPerSample) { + case 1: + { + } break; case 2: { drwav__bswap_samples_s16((drwav_int16*)pSamples, sampleCount); @@ -53353,7 +74322,7 @@ DRWAV_PRIVATE drwav_bool32 drwav__read_fmt(drwav_read_proc onRead, drwav_seek_pr fmtOut->extendedSize = 0; fmtOut->validBitsPerSample = 0; fmtOut->channelMask = 0; - memset(fmtOut->subFormat, 0, sizeof(fmtOut->subFormat)); + DRWAV_ZERO_MEMORY(fmtOut->subFormat, sizeof(fmtOut->subFormat)); if (header.sizeInBytes > 16) { drwav_uint8 fmt_cbSize[2]; int bytesReadSoFar = 0; @@ -53486,7 +74455,7 @@ DRWAV_PRIVATE void drwav__metadata_request_extra_memory_for_stage_2(drwav__metad DRWAV_PRIVATE drwav_result drwav__metadata_alloc(drwav__metadata_parser* pParser, drwav_allocation_callbacks* pAllocationCallbacks) { if (pParser->extraCapacity != 0 || pParser->metadataCount != 0) { - free(pParser->pData); + pAllocationCallbacks->onFree(pParser->pData, pAllocationCallbacks->pUserData); pParser->pData = (drwav_uint8*)pAllocationCallbacks->onMalloc(drwav__metadata_memory_capacity(pParser), pAllocationCallbacks->pUserData); pParser->pDataCursor = pParser->pData; if (pParser->pData == NULL) { @@ -53505,12 +74474,13 @@ DRWAV_PRIVATE size_t drwav__metadata_parser_read(drwav__metadata_parser* pParser return pParser->onRead(pParser->pReadSeekUserData, pBufferOut, bytesToRead); } } -DRWAV_PRIVATE drwav_uint64 drwav__read_smpl_to_metadata_obj(drwav__metadata_parser* pParser, drwav_metadata* pMetadata) +DRWAV_PRIVATE drwav_uint64 drwav__read_smpl_to_metadata_obj(drwav__metadata_parser* pParser, const drwav_chunk_header* pChunkHeader, drwav_metadata* pMetadata) { drwav_uint8 smplHeaderData[DRWAV_SMPL_BYTES]; drwav_uint64 totalBytesRead = 0; size_t bytesJustRead = drwav__metadata_parser_read(pParser, smplHeaderData, sizeof(smplHeaderData), &totalBytesRead); DRWAV_ASSERT(pParser->stage == drwav__metadata_parser_stage_read); + DRWAV_ASSERT(pChunkHeader != NULL); if (bytesJustRead == sizeof(smplHeaderData)) { drwav_uint32 iSampleLoop; pMetadata->type = drwav_metadata_type_smpl; @@ -53523,30 +74493,32 @@ DRWAV_PRIVATE drwav_uint64 drwav__read_smpl_to_metadata_obj(drwav__metadata_pars pMetadata->data.smpl.smpteOffset = drwav_bytes_to_u32(smplHeaderData + 24); pMetadata->data.smpl.sampleLoopCount = drwav_bytes_to_u32(smplHeaderData + 28); pMetadata->data.smpl.samplerSpecificDataSizeInBytes = drwav_bytes_to_u32(smplHeaderData + 32); - pMetadata->data.smpl.pLoops = (drwav_smpl_loop*)drwav__metadata_get_memory(pParser, sizeof(drwav_smpl_loop) * pMetadata->data.smpl.sampleLoopCount, DRWAV_METADATA_ALIGNMENT); - for (iSampleLoop = 0; iSampleLoop < pMetadata->data.smpl.sampleLoopCount; ++iSampleLoop) { - drwav_uint8 smplLoopData[DRWAV_SMPL_LOOP_BYTES]; - bytesJustRead = drwav__metadata_parser_read(pParser, smplLoopData, sizeof(smplLoopData), &totalBytesRead); - if (bytesJustRead == sizeof(smplLoopData)) { - pMetadata->data.smpl.pLoops[iSampleLoop].cuePointId = drwav_bytes_to_u32(smplLoopData + 0); - pMetadata->data.smpl.pLoops[iSampleLoop].type = drwav_bytes_to_u32(smplLoopData + 4); - pMetadata->data.smpl.pLoops[iSampleLoop].firstSampleByteOffset = drwav_bytes_to_u32(smplLoopData + 8); - pMetadata->data.smpl.pLoops[iSampleLoop].lastSampleByteOffset = drwav_bytes_to_u32(smplLoopData + 12); - pMetadata->data.smpl.pLoops[iSampleLoop].sampleFraction = drwav_bytes_to_u32(smplLoopData + 16); - pMetadata->data.smpl.pLoops[iSampleLoop].playCount = drwav_bytes_to_u32(smplLoopData + 20); - } else { - break; + if (pMetadata->data.smpl.sampleLoopCount == (pChunkHeader->sizeInBytes - DRWAV_SMPL_BYTES) / DRWAV_SMPL_LOOP_BYTES) { + pMetadata->data.smpl.pLoops = (drwav_smpl_loop*)drwav__metadata_get_memory(pParser, sizeof(drwav_smpl_loop) * pMetadata->data.smpl.sampleLoopCount, DRWAV_METADATA_ALIGNMENT); + for (iSampleLoop = 0; iSampleLoop < pMetadata->data.smpl.sampleLoopCount; ++iSampleLoop) { + drwav_uint8 smplLoopData[DRWAV_SMPL_LOOP_BYTES]; + bytesJustRead = drwav__metadata_parser_read(pParser, smplLoopData, sizeof(smplLoopData), &totalBytesRead); + if (bytesJustRead == sizeof(smplLoopData)) { + pMetadata->data.smpl.pLoops[iSampleLoop].cuePointId = drwav_bytes_to_u32(smplLoopData + 0); + pMetadata->data.smpl.pLoops[iSampleLoop].type = drwav_bytes_to_u32(smplLoopData + 4); + pMetadata->data.smpl.pLoops[iSampleLoop].firstSampleByteOffset = drwav_bytes_to_u32(smplLoopData + 8); + pMetadata->data.smpl.pLoops[iSampleLoop].lastSampleByteOffset = drwav_bytes_to_u32(smplLoopData + 12); + pMetadata->data.smpl.pLoops[iSampleLoop].sampleFraction = drwav_bytes_to_u32(smplLoopData + 16); + pMetadata->data.smpl.pLoops[iSampleLoop].playCount = drwav_bytes_to_u32(smplLoopData + 20); + } else { + break; + } + } + if (pMetadata->data.smpl.samplerSpecificDataSizeInBytes > 0) { + pMetadata->data.smpl.pSamplerSpecificData = drwav__metadata_get_memory(pParser, pMetadata->data.smpl.samplerSpecificDataSizeInBytes, 1); + DRWAV_ASSERT(pMetadata->data.smpl.pSamplerSpecificData != NULL); + drwav__metadata_parser_read(pParser, pMetadata->data.smpl.pSamplerSpecificData, pMetadata->data.smpl.samplerSpecificDataSizeInBytes, &totalBytesRead); } - } - if (pMetadata->data.smpl.samplerSpecificDataSizeInBytes > 0) { - pMetadata->data.smpl.pSamplerSpecificData = drwav__metadata_get_memory(pParser, pMetadata->data.smpl.samplerSpecificDataSizeInBytes, 1); - DRWAV_ASSERT(pMetadata->data.smpl.pSamplerSpecificData != NULL); - bytesJustRead = drwav__metadata_parser_read(pParser, pMetadata->data.smpl.pSamplerSpecificData, pMetadata->data.smpl.samplerSpecificDataSizeInBytes, &totalBytesRead); } } return totalBytesRead; } -DRWAV_PRIVATE drwav_uint64 drwav__read_cue_to_metadata_obj(drwav__metadata_parser* pParser, drwav_metadata* pMetadata) +DRWAV_PRIVATE drwav_uint64 drwav__read_cue_to_metadata_obj(drwav__metadata_parser* pParser, const drwav_chunk_header* pChunkHeader, drwav_metadata* pMetadata) { drwav_uint8 cueHeaderSectionData[DRWAV_CUE_BYTES]; drwav_uint64 totalBytesRead = 0; @@ -53555,25 +74527,27 @@ DRWAV_PRIVATE drwav_uint64 drwav__read_cue_to_metadata_obj(drwav__metadata_parse if (bytesJustRead == sizeof(cueHeaderSectionData)) { pMetadata->type = drwav_metadata_type_cue; pMetadata->data.cue.cuePointCount = drwav_bytes_to_u32(cueHeaderSectionData); - pMetadata->data.cue.pCuePoints = (drwav_cue_point*)drwav__metadata_get_memory(pParser, sizeof(drwav_cue_point) * pMetadata->data.cue.cuePointCount, DRWAV_METADATA_ALIGNMENT); - DRWAV_ASSERT(pMetadata->data.cue.pCuePoints != NULL); - if (pMetadata->data.cue.cuePointCount > 0) { - drwav_uint32 iCuePoint; - for (iCuePoint = 0; iCuePoint < pMetadata->data.cue.cuePointCount; ++iCuePoint) { - drwav_uint8 cuePointData[DRWAV_CUE_POINT_BYTES]; - bytesJustRead = drwav__metadata_parser_read(pParser, cuePointData, sizeof(cuePointData), &totalBytesRead); - if (bytesJustRead == sizeof(cuePointData)) { - pMetadata->data.cue.pCuePoints[iCuePoint].id = drwav_bytes_to_u32(cuePointData + 0); - pMetadata->data.cue.pCuePoints[iCuePoint].playOrderPosition = drwav_bytes_to_u32(cuePointData + 4); - pMetadata->data.cue.pCuePoints[iCuePoint].dataChunkId[0] = cuePointData[8]; - pMetadata->data.cue.pCuePoints[iCuePoint].dataChunkId[1] = cuePointData[9]; - pMetadata->data.cue.pCuePoints[iCuePoint].dataChunkId[2] = cuePointData[10]; - pMetadata->data.cue.pCuePoints[iCuePoint].dataChunkId[3] = cuePointData[11]; - pMetadata->data.cue.pCuePoints[iCuePoint].chunkStart = drwav_bytes_to_u32(cuePointData + 12); - pMetadata->data.cue.pCuePoints[iCuePoint].blockStart = drwav_bytes_to_u32(cuePointData + 16); - pMetadata->data.cue.pCuePoints[iCuePoint].sampleByteOffset = drwav_bytes_to_u32(cuePointData + 20); - } else { - break; + if (pMetadata->data.cue.cuePointCount == (pChunkHeader->sizeInBytes - DRWAV_CUE_BYTES) / DRWAV_CUE_POINT_BYTES) { + pMetadata->data.cue.pCuePoints = (drwav_cue_point*)drwav__metadata_get_memory(pParser, sizeof(drwav_cue_point) * pMetadata->data.cue.cuePointCount, DRWAV_METADATA_ALIGNMENT); + DRWAV_ASSERT(pMetadata->data.cue.pCuePoints != NULL); + if (pMetadata->data.cue.cuePointCount > 0) { + drwav_uint32 iCuePoint; + for (iCuePoint = 0; iCuePoint < pMetadata->data.cue.cuePointCount; ++iCuePoint) { + drwav_uint8 cuePointData[DRWAV_CUE_POINT_BYTES]; + bytesJustRead = drwav__metadata_parser_read(pParser, cuePointData, sizeof(cuePointData), &totalBytesRead); + if (bytesJustRead == sizeof(cuePointData)) { + pMetadata->data.cue.pCuePoints[iCuePoint].id = drwav_bytes_to_u32(cuePointData + 0); + pMetadata->data.cue.pCuePoints[iCuePoint].playOrderPosition = drwav_bytes_to_u32(cuePointData + 4); + pMetadata->data.cue.pCuePoints[iCuePoint].dataChunkId[0] = cuePointData[8]; + pMetadata->data.cue.pCuePoints[iCuePoint].dataChunkId[1] = cuePointData[9]; + pMetadata->data.cue.pCuePoints[iCuePoint].dataChunkId[2] = cuePointData[10]; + pMetadata->data.cue.pCuePoints[iCuePoint].dataChunkId[3] = cuePointData[11]; + pMetadata->data.cue.pCuePoints[iCuePoint].chunkStart = drwav_bytes_to_u32(cuePointData + 12); + pMetadata->data.cue.pCuePoints[iCuePoint].blockStart = drwav_bytes_to_u32(cuePointData + 16); + pMetadata->data.cue.pCuePoints[iCuePoint].sampleByteOffset = drwav_bytes_to_u32(cuePointData + 20); + } else { + break; + } } } } @@ -53615,7 +74589,15 @@ DRWAV_PRIVATE drwav_uint64 drwav__read_acid_to_metadata_obj(drwav__metadata_pars } return bytesRead; } -DRWAV_PRIVATE size_t drwav__strlen_clamped(char* str, size_t maxToRead) +DRWAV_PRIVATE size_t drwav__strlen(const char* str) +{ + size_t result = 0; + while (*str++) { + result += 1; + } + return result; +} +DRWAV_PRIVATE size_t drwav__strlen_clamped(const char* str, size_t maxToRead) { size_t result = 0; while (*str++ && result < maxToRead) { @@ -53623,71 +74605,147 @@ DRWAV_PRIVATE size_t drwav__strlen_clamped(char* str, size_t maxToRead) } return result; } -DRWAV_PRIVATE char* drwav__metadata_copy_string(drwav__metadata_parser* pParser, char* str, size_t maxToRead) +DRWAV_PRIVATE char* drwav__metadata_copy_string(drwav__metadata_parser* pParser, const char* str, size_t maxToRead) { size_t len = drwav__strlen_clamped(str, maxToRead); if (len) { char* result = (char*)drwav__metadata_get_memory(pParser, len + 1, 1); DRWAV_ASSERT(result != NULL); - memcpy(result, str, len); + DRWAV_COPY_MEMORY(result, str, len); result[len] = '\0'; return result; } else { return NULL; } } +typedef struct +{ + const void* pBuffer; + size_t sizeInBytes; + size_t cursor; +} drwav_buffer_reader; +DRWAV_PRIVATE drwav_result drwav_buffer_reader_init(const void* pBuffer, size_t sizeInBytes, drwav_buffer_reader* pReader) +{ + DRWAV_ASSERT(pBuffer != NULL); + DRWAV_ASSERT(pReader != NULL); + DRWAV_ZERO_OBJECT(pReader); + pReader->pBuffer = pBuffer; + pReader->sizeInBytes = sizeInBytes; + pReader->cursor = 0; + return DRWAV_SUCCESS; +} +DRWAV_PRIVATE const void* drwav_buffer_reader_ptr(const drwav_buffer_reader* pReader) +{ + DRWAV_ASSERT(pReader != NULL); + return drwav_offset_ptr(pReader->pBuffer, pReader->cursor); +} +DRWAV_PRIVATE drwav_result drwav_buffer_reader_seek(drwav_buffer_reader* pReader, size_t bytesToSeek) +{ + DRWAV_ASSERT(pReader != NULL); + if (pReader->cursor + bytesToSeek > pReader->sizeInBytes) { + return DRWAV_BAD_SEEK; + } + pReader->cursor += bytesToSeek; + return DRWAV_SUCCESS; +} +DRWAV_PRIVATE drwav_result drwav_buffer_reader_read(drwav_buffer_reader* pReader, void* pDst, size_t bytesToRead, size_t* pBytesRead) +{ + drwav_result result = DRWAV_SUCCESS; + size_t bytesRemaining; + DRWAV_ASSERT(pReader != NULL); + if (pBytesRead != NULL) { + *pBytesRead = 0; + } + bytesRemaining = (pReader->sizeInBytes - pReader->cursor); + if (bytesToRead > bytesRemaining) { + bytesToRead = bytesRemaining; + } + if (pDst == NULL) { + result = drwav_buffer_reader_seek(pReader, bytesToRead); + } else { + DRWAV_COPY_MEMORY(pDst, drwav_buffer_reader_ptr(pReader), bytesToRead); + pReader->cursor += bytesToRead; + } + DRWAV_ASSERT(pReader->cursor <= pReader->sizeInBytes); + if (result == DRWAV_SUCCESS) { + if (pBytesRead != NULL) { + *pBytesRead = bytesToRead; + } + } + return DRWAV_SUCCESS; +} +DRWAV_PRIVATE drwav_result drwav_buffer_reader_read_u16(drwav_buffer_reader* pReader, drwav_uint16* pDst) +{ + drwav_result result; + size_t bytesRead; + drwav_uint8 data[2]; + DRWAV_ASSERT(pReader != NULL); + DRWAV_ASSERT(pDst != NULL); + *pDst = 0; + result = drwav_buffer_reader_read(pReader, data, sizeof(*pDst), &bytesRead); + if (result != DRWAV_SUCCESS || bytesRead != sizeof(*pDst)) { + return result; + } + *pDst = drwav_bytes_to_u16(data); + return DRWAV_SUCCESS; +} +DRWAV_PRIVATE drwav_result drwav_buffer_reader_read_u32(drwav_buffer_reader* pReader, drwav_uint32* pDst) +{ + drwav_result result; + size_t bytesRead; + drwav_uint8 data[4]; + DRWAV_ASSERT(pReader != NULL); + DRWAV_ASSERT(pDst != NULL); + *pDst = 0; + result = drwav_buffer_reader_read(pReader, data, sizeof(*pDst), &bytesRead); + if (result != DRWAV_SUCCESS || bytesRead != sizeof(*pDst)) { + return result; + } + *pDst = drwav_bytes_to_u32(data); + return DRWAV_SUCCESS; +} DRWAV_PRIVATE drwav_uint64 drwav__read_bext_to_metadata_obj(drwav__metadata_parser* pParser, drwav_metadata* pMetadata, drwav_uint64 chunkSize) { drwav_uint8 bextData[DRWAV_BEXT_BYTES]; - drwav_uint64 bytesRead = drwav__metadata_parser_read(pParser, bextData, sizeof(bextData), NULL); + size_t bytesRead = drwav__metadata_parser_read(pParser, bextData, sizeof(bextData), NULL); DRWAV_ASSERT(pParser->stage == drwav__metadata_parser_stage_read); if (bytesRead == sizeof(bextData)) { - drwav_uint8* pReadPointer; + drwav_buffer_reader reader; drwav_uint32 timeReferenceLow; drwav_uint32 timeReferenceHigh; size_t extraBytes; pMetadata->type = drwav_metadata_type_bext; - pReadPointer = bextData; - pMetadata->data.bext.pDescription = drwav__metadata_copy_string(pParser, (char*)(pReadPointer), DRWAV_BEXT_DESCRIPTION_BYTES); - pReadPointer += DRWAV_BEXT_DESCRIPTION_BYTES; - pMetadata->data.bext.pOriginatorName = drwav__metadata_copy_string(pParser, (char*)(pReadPointer), DRWAV_BEXT_ORIGINATOR_NAME_BYTES); - pReadPointer += DRWAV_BEXT_ORIGINATOR_NAME_BYTES; - pMetadata->data.bext.pOriginatorReference = drwav__metadata_copy_string(pParser, (char*)(pReadPointer), DRWAV_BEXT_ORIGINATOR_REF_BYTES); - pReadPointer += DRWAV_BEXT_ORIGINATOR_REF_BYTES; - memcpy(pReadPointer, pMetadata->data.bext.pOriginationDate, sizeof(pMetadata->data.bext.pOriginationDate)); - pReadPointer += sizeof(pMetadata->data.bext.pOriginationDate); - memcpy(pReadPointer, pMetadata->data.bext.pOriginationTime, sizeof(pMetadata->data.bext.pOriginationTime)); - pReadPointer += sizeof(pMetadata->data.bext.pOriginationTime); - timeReferenceLow = drwav_bytes_to_u32(pReadPointer); - pReadPointer += sizeof(drwav_uint32); - timeReferenceHigh = drwav_bytes_to_u32(pReadPointer); - pReadPointer += sizeof(drwav_uint32); - pMetadata->data.bext.timeReference = ((drwav_uint64)timeReferenceHigh << 32) + timeReferenceLow; - pMetadata->data.bext.version = drwav_bytes_to_u16(pReadPointer); - pReadPointer += sizeof(drwav_uint16); - pMetadata->data.bext.pUMID = drwav__metadata_get_memory(pParser, DRWAV_BEXT_UMID_BYTES, 1); - memcpy(pMetadata->data.bext.pUMID, pReadPointer, DRWAV_BEXT_UMID_BYTES); - pReadPointer += DRWAV_BEXT_UMID_BYTES; - pMetadata->data.bext.loudnessValue = drwav_bytes_to_u16(pReadPointer); - pReadPointer += sizeof(drwav_uint16); - pMetadata->data.bext.loudnessRange = drwav_bytes_to_u16(pReadPointer); - pReadPointer += sizeof(drwav_uint16); - pMetadata->data.bext.maxTruePeakLevel = drwav_bytes_to_u16(pReadPointer); - pReadPointer += sizeof(drwav_uint16); - pMetadata->data.bext.maxMomentaryLoudness = drwav_bytes_to_u16(pReadPointer); - pReadPointer += sizeof(drwav_uint16); - pMetadata->data.bext.maxShortTermLoudness = drwav_bytes_to_u16(pReadPointer); - pReadPointer += sizeof(drwav_uint16); - DRWAV_ASSERT((pReadPointer + DRWAV_BEXT_RESERVED_BYTES) == (bextData + DRWAV_BEXT_BYTES)); - extraBytes = (size_t)(chunkSize - DRWAV_BEXT_BYTES); - if (extraBytes > 0) { - pMetadata->data.bext.pCodingHistory = (char*)drwav__metadata_get_memory(pParser, extraBytes + 1, 1); - DRWAV_ASSERT(pMetadata->data.bext.pCodingHistory != NULL); - bytesRead += drwav__metadata_parser_read(pParser, pMetadata->data.bext.pCodingHistory, extraBytes, NULL); - pMetadata->data.bext.codingHistorySize = (drwav_uint32)strlen(pMetadata->data.bext.pCodingHistory); - } else { - pMetadata->data.bext.pCodingHistory = NULL; - pMetadata->data.bext.codingHistorySize = 0; + if (drwav_buffer_reader_init(bextData, bytesRead, &reader) == DRWAV_SUCCESS) { + pMetadata->data.bext.pDescription = drwav__metadata_copy_string(pParser, (const char*)drwav_buffer_reader_ptr(&reader), DRWAV_BEXT_DESCRIPTION_BYTES); + drwav_buffer_reader_seek(&reader, DRWAV_BEXT_DESCRIPTION_BYTES); + pMetadata->data.bext.pOriginatorName = drwav__metadata_copy_string(pParser, (const char*)drwav_buffer_reader_ptr(&reader), DRWAV_BEXT_ORIGINATOR_NAME_BYTES); + drwav_buffer_reader_seek(&reader, DRWAV_BEXT_ORIGINATOR_NAME_BYTES); + pMetadata->data.bext.pOriginatorReference = drwav__metadata_copy_string(pParser, (const char*)drwav_buffer_reader_ptr(&reader), DRWAV_BEXT_ORIGINATOR_REF_BYTES); + drwav_buffer_reader_seek(&reader, DRWAV_BEXT_ORIGINATOR_REF_BYTES); + drwav_buffer_reader_read(&reader, pMetadata->data.bext.pOriginationDate, sizeof(pMetadata->data.bext.pOriginationDate), NULL); + drwav_buffer_reader_read(&reader, pMetadata->data.bext.pOriginationTime, sizeof(pMetadata->data.bext.pOriginationTime), NULL); + drwav_buffer_reader_read_u32(&reader, &timeReferenceLow); + drwav_buffer_reader_read_u32(&reader, &timeReferenceHigh); + pMetadata->data.bext.timeReference = ((drwav_uint64)timeReferenceHigh << 32) + timeReferenceLow; + drwav_buffer_reader_read_u16(&reader, &pMetadata->data.bext.version); + pMetadata->data.bext.pUMID = drwav__metadata_get_memory(pParser, DRWAV_BEXT_UMID_BYTES, 1); + drwav_buffer_reader_read(&reader, pMetadata->data.bext.pUMID, DRWAV_BEXT_UMID_BYTES, NULL); + drwav_buffer_reader_read_u16(&reader, &pMetadata->data.bext.loudnessValue); + drwav_buffer_reader_read_u16(&reader, &pMetadata->data.bext.loudnessRange); + drwav_buffer_reader_read_u16(&reader, &pMetadata->data.bext.maxTruePeakLevel); + drwav_buffer_reader_read_u16(&reader, &pMetadata->data.bext.maxMomentaryLoudness); + drwav_buffer_reader_read_u16(&reader, &pMetadata->data.bext.maxShortTermLoudness); + DRWAV_ASSERT((drwav_offset_ptr(drwav_buffer_reader_ptr(&reader), DRWAV_BEXT_RESERVED_BYTES)) == (bextData + DRWAV_BEXT_BYTES)); + extraBytes = (size_t)(chunkSize - DRWAV_BEXT_BYTES); + if (extraBytes > 0) { + pMetadata->data.bext.pCodingHistory = (char*)drwav__metadata_get_memory(pParser, extraBytes + 1, 1); + DRWAV_ASSERT(pMetadata->data.bext.pCodingHistory != NULL); + bytesRead += drwav__metadata_parser_read(pParser, pMetadata->data.bext.pCodingHistory, extraBytes, NULL); + pMetadata->data.bext.codingHistorySize = (drwav_uint32)drwav__strlen(pMetadata->data.bext.pCodingHistory); + } else { + pMetadata->data.bext.pCodingHistory = NULL; + pMetadata->data.bext.codingHistorySize = 0; + } } } return bytesRead; @@ -53707,7 +74765,7 @@ DRWAV_PRIVATE drwav_uint64 drwav__read_list_label_or_note_to_metadata_obj(drwav_ pMetadata->data.labelOrNote.stringLength = sizeIncludingNullTerminator - 1; pMetadata->data.labelOrNote.pString = (char*)drwav__metadata_get_memory(pParser, sizeIncludingNullTerminator, 1); DRWAV_ASSERT(pMetadata->data.labelOrNote.pString != NULL); - bytesJustRead = drwav__metadata_parser_read(pParser, pMetadata->data.labelOrNote.pString, sizeIncludingNullTerminator, &totalBytesRead); + drwav__metadata_parser_read(pParser, pMetadata->data.labelOrNote.pString, sizeIncludingNullTerminator, &totalBytesRead); } else { pMetadata->data.labelOrNote.stringLength = 0; pMetadata->data.labelOrNote.pString = NULL; @@ -53739,7 +74797,7 @@ DRWAV_PRIVATE drwav_uint64 drwav__read_list_labelled_cue_region_to_metadata_obj( pMetadata->data.labelledCueRegion.stringLength = sizeIncludingNullTerminator - 1; pMetadata->data.labelledCueRegion.pString = (char*)drwav__metadata_get_memory(pParser, sizeIncludingNullTerminator, 1); DRWAV_ASSERT(pMetadata->data.labelledCueRegion.pString != NULL); - bytesJustRead = drwav__metadata_parser_read(pParser, pMetadata->data.labelledCueRegion.pString, sizeIncludingNullTerminator, &totalBytesRead); + drwav__metadata_parser_read(pParser, pMetadata->data.labelledCueRegion.pString, sizeIncludingNullTerminator, &totalBytesRead); } else { pMetadata->data.labelledCueRegion.stringLength = 0; pMetadata->data.labelledCueRegion.pString = NULL; @@ -53805,11 +74863,11 @@ DRWAV_PRIVATE drwav_uint64 drwav__metadata_process_unknown_chunk(drwav__metadata } return bytesRead; } -DRWAV_PRIVATE drwav_bool32 drwav__chunk_matches(drwav_uint64 allowedMetadataTypes, const drwav_uint8* pChunkID, drwav_metadata_type type, const char* pID) +DRWAV_PRIVATE drwav_bool32 drwav__chunk_matches(drwav_metadata_type allowedMetadataTypes, const drwav_uint8* pChunkID, drwav_metadata_type type, const char* pID) { return (allowedMetadataTypes & type) && drwav_fourcc_equal(pChunkID, pID); } -DRWAV_PRIVATE drwav_uint64 drwav__metadata_process_chunk(drwav__metadata_parser* pParser, const drwav_chunk_header* pChunkHeader, drwav_uint64 allowedMetadataTypes) +DRWAV_PRIVATE drwav_uint64 drwav__metadata_process_chunk(drwav__metadata_parser* pParser, const drwav_chunk_header* pChunkHeader, drwav_metadata_type allowedMetadataTypes) { const drwav_uint8 *pChunkID = pChunkHeader->id.fourcc; drwav_uint64 bytesRead = 0; @@ -53825,16 +74883,21 @@ DRWAV_PRIVATE drwav_uint64 drwav__metadata_process_chunk(drwav__metadata_parser* bytesJustRead = drwav__metadata_parser_read(pParser, buffer, sizeof(buffer), &bytesRead); if (bytesJustRead == sizeof(buffer)) { drwav_uint32 loopCount = drwav_bytes_to_u32(buffer); - bytesJustRead = drwav__metadata_parser_read(pParser, buffer, sizeof(buffer), &bytesRead); - if (bytesJustRead == sizeof(buffer)) { - drwav_uint32 samplerSpecificDataSizeInBytes = drwav_bytes_to_u32(buffer); - pParser->metadataCount += 1; - drwav__metadata_request_extra_memory_for_stage_2(pParser, sizeof(drwav_smpl_loop) * loopCount, DRWAV_METADATA_ALIGNMENT); - drwav__metadata_request_extra_memory_for_stage_2(pParser, samplerSpecificDataSizeInBytes, 1); + drwav_uint64 calculatedLoopCount; + calculatedLoopCount = (pChunkHeader->sizeInBytes - DRWAV_SMPL_BYTES) / DRWAV_SMPL_LOOP_BYTES; + if (calculatedLoopCount == loopCount) { + bytesJustRead = drwav__metadata_parser_read(pParser, buffer, sizeof(buffer), &bytesRead); + if (bytesJustRead == sizeof(buffer)) { + drwav_uint32 samplerSpecificDataSizeInBytes = drwav_bytes_to_u32(buffer); + pParser->metadataCount += 1; + drwav__metadata_request_extra_memory_for_stage_2(pParser, sizeof(drwav_smpl_loop) * loopCount, DRWAV_METADATA_ALIGNMENT); + drwav__metadata_request_extra_memory_for_stage_2(pParser, samplerSpecificDataSizeInBytes, 1); + } + } else { } } } else { - bytesRead = drwav__read_smpl_to_metadata_obj(pParser, &pParser->pMetadata[pParser->metadataCursor]); + bytesRead = drwav__read_smpl_to_metadata_obj(pParser, pChunkHeader, &pParser->pMetadata[pParser->metadataCursor]); if (bytesRead == pChunkHeader->sizeInBytes) { pParser->metadataCursor += 1; } else { @@ -53876,7 +74939,7 @@ DRWAV_PRIVATE drwav_uint64 drwav__metadata_process_chunk(drwav__metadata_parser* cueCount = (size_t)(pChunkHeader->sizeInBytes - DRWAV_CUE_BYTES) / DRWAV_CUE_POINT_BYTES; drwav__metadata_request_extra_memory_for_stage_2(pParser, sizeof(drwav_cue_point) * cueCount, DRWAV_METADATA_ALIGNMENT); } else { - bytesRead = drwav__read_cue_to_metadata_obj(pParser, &pParser->pMetadata[pParser->metadataCursor]); + bytesRead = drwav__read_cue_to_metadata_obj(pParser, pChunkHeader, &pParser->pMetadata[pParser->metadataCursor]); if (bytesRead == pChunkHeader->sizeInBytes) { pParser->metadataCursor += 1; } else { @@ -53895,19 +74958,19 @@ DRWAV_PRIVATE drwav_uint64 drwav__metadata_process_chunk(drwav__metadata_parser* if (bytesJustRead != DRWAV_BEXT_DESCRIPTION_BYTES) { return bytesRead; } - allocSizeNeeded += strlen(buffer) + 1; + allocSizeNeeded += drwav__strlen(buffer) + 1; buffer[DRWAV_BEXT_ORIGINATOR_NAME_BYTES] = '\0'; bytesJustRead = drwav__metadata_parser_read(pParser, buffer, DRWAV_BEXT_ORIGINATOR_NAME_BYTES, &bytesRead); if (bytesJustRead != DRWAV_BEXT_ORIGINATOR_NAME_BYTES) { return bytesRead; } - allocSizeNeeded += strlen(buffer) + 1; + allocSizeNeeded += drwav__strlen(buffer) + 1; buffer[DRWAV_BEXT_ORIGINATOR_REF_BYTES] = '\0'; bytesJustRead = drwav__metadata_parser_read(pParser, buffer, DRWAV_BEXT_ORIGINATOR_REF_BYTES, &bytesRead); if (bytesJustRead != DRWAV_BEXT_ORIGINATOR_REF_BYTES) { return bytesRead; } - allocSizeNeeded += strlen(buffer) + 1; + allocSizeNeeded += drwav__strlen(buffer) + 1; allocSizeNeeded += (size_t)pChunkHeader->sizeInBytes - DRWAV_BEXT_BYTES; drwav__metadata_request_extra_memory_for_stage_2(pParser, allocSizeNeeded, 1); pParser->metadataCount += 1; @@ -53991,7 +75054,7 @@ DRWAV_PRIVATE drwav_uint64 drwav__metadata_process_chunk(drwav__metadata_parser* subchunkBytesRead = drwav__metadata_process_info_text_chunk(pParser, subchunkDataSize, drwav_metadata_type_list_info_album); } else if (drwav__chunk_matches(allowedMetadataTypes, subchunkId, drwav_metadata_type_list_info_tracknumber, "ITRK")) { subchunkBytesRead = drwav__metadata_process_info_text_chunk(pParser, subchunkDataSize, drwav_metadata_type_list_info_tracknumber); - } else if (allowedMetadataTypes & drwav_metadata_type_unknown) { + } else if ((allowedMetadataTypes & drwav_metadata_type_unknown) != 0) { subchunkBytesRead = drwav__metadata_process_unknown_chunk(pParser, subchunkId, subchunkDataSize, listType); } bytesRead += subchunkBytesRead; @@ -54010,18 +75073,25 @@ DRWAV_PRIVATE drwav_uint64 drwav__metadata_process_chunk(drwav__metadata_parser* bytesRead += 1; } } - } else if (allowedMetadataTypes & drwav_metadata_type_unknown) { + } else if ((allowedMetadataTypes & drwav_metadata_type_unknown) != 0) { bytesRead = drwav__metadata_process_unknown_chunk(pParser, pChunkID, pChunkHeader->sizeInBytes, drwav_metadata_location_top_level); } return bytesRead; } DRWAV_PRIVATE drwav_uint32 drwav_get_bytes_per_pcm_frame(drwav* pWav) { + drwav_uint32 bytesPerFrame; if ((pWav->bitsPerSample & 0x7) == 0) { - return (pWav->bitsPerSample * pWav->fmt.channels) >> 3; + bytesPerFrame = (pWav->bitsPerSample * pWav->fmt.channels) >> 3; } else { - return pWav->fmt.blockAlign; + bytesPerFrame = pWav->fmt.blockAlign; } + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ALAW || pWav->translatedFormatTag == DR_WAVE_FORMAT_MULAW) { + if (bytesPerFrame != pWav->fmt.channels) { + return 0; + } + } + return bytesPerFrame; } DRWAV_API drwav_uint16 drwav_fmt_get_format(const drwav_fmt* pFMT) { @@ -54167,7 +75237,7 @@ DRWAV_PRIVATE drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc on if (translatedFormatTag == DR_WAVE_FORMAT_EXTENSIBLE) { translatedFormatTag = drwav_bytes_to_u16(fmt.subFormat + 0); } - memset(&metadataParser, 0, sizeof(metadataParser)); + DRWAV_ZERO_MEMORY(&metadataParser, sizeof(metadataParser)); if (!sequential && pWav->allowedMetadataTypes != drwav_metadata_type_none && (pWav->container == drwav_container_riff || pWav->container == drwav_container_rf64)) { drwav_uint64 cursorForMetadata = cursor; metadataParser.onRead = pWav->onRead; @@ -54302,7 +75372,11 @@ DRWAV_PRIVATE drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc on if (sampleCountFromFactChunk != 0) { pWav->totalPCMFrameCount = sampleCountFromFactChunk; } else { - pWav->totalPCMFrameCount = dataChunkSize / drwav_get_bytes_per_pcm_frame(pWav); + drwav_uint32 bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return DRWAV_FALSE; + } + pWav->totalPCMFrameCount = dataChunkSize / bytesPerFrame; if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { drwav_uint64 totalBlockHeaderSizeInBytes; drwav_uint64 blockCount = dataChunkSize / fmt.blockAlign; @@ -54328,6 +75402,9 @@ DRWAV_PRIVATE drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc on return DRWAV_FALSE; } } + if (drwav_get_bytes_per_pcm_frame(pWav) == 0) { + return DRWAV_FALSE; + } #ifdef DR_WAV_LIBSNDFILE_COMPAT if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { drwav_uint64 blockCount = dataChunkSize / fmt.blockAlign; @@ -54592,7 +75669,7 @@ DRWAV_PRIVATE size_t drwav__write_or_count_metadata(drwav* pWav, drwav_metadata* bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.bext.maxTruePeakLevel); bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.bext.maxMomentaryLoudness); bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.bext.maxShortTermLoudness); - memset(reservedBuf, 0, sizeof(reservedBuf)); + DRWAV_ZERO_MEMORY(reservedBuf, sizeof(reservedBuf)); bytesWritten += drwav__write_or_count(pWav, reservedBuf, sizeof(reservedBuf)); if (pMetadata->data.bext.codingHistorySize > 0) { bytesWritten += drwav__write_or_count(pWav, pMetadata->data.bext.pCodingHistory, pMetadata->data.bext.codingHistorySize); @@ -55829,6 +76906,7 @@ DRWAV_API drwav_result drwav_uninit(drwav* pWav) DRWAV_API size_t drwav_read_raw(drwav* pWav, size_t bytesToRead, void* pBufferOut) { size_t bytesRead; + drwav_uint32 bytesPerFrame; if (pWav == NULL || bytesToRead == 0) { return 0; } @@ -55838,6 +76916,10 @@ DRWAV_API size_t drwav_read_raw(drwav* pWav, size_t bytesToRead, void* pBufferOu if (bytesToRead == 0) { return 0; } + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return 0; + } if (pBufferOut != NULL) { bytesRead = pWav->onRead(pWav->pUserData, pBufferOut, bytesToRead); } else { @@ -55866,7 +76948,7 @@ DRWAV_API size_t drwav_read_raw(drwav* pWav, size_t bytesToRead, void* pBufferOu } } } - pWav->readCursorInPCMFrames += bytesRead / drwav_get_bytes_per_pcm_frame(pWav); + pWav->readCursorInPCMFrames += bytesRead / bytesPerFrame; pWav->bytesRemaining -= bytesRead; return bytesRead; } @@ -55897,7 +76979,11 @@ DRWAV_API drwav_uint64 drwav_read_pcm_frames_be(drwav* pWav, drwav_uint64 frames { drwav_uint64 framesRead = drwav_read_pcm_frames_le(pWav, framesToRead, pBufferOut); if (pBufferOut != NULL) { - drwav__bswap_samples(pBufferOut, framesRead*pWav->channels, drwav_get_bytes_per_pcm_frame(pWav)/pWav->channels, pWav->translatedFormatTag); + drwav_uint32 bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return 0; + } + drwav__bswap_samples(pBufferOut, framesRead*pWav->channels, bytesPerFrame/pWav->channels, pWav->translatedFormatTag); } return framesRead; } @@ -55941,8 +77027,8 @@ DRWAV_API drwav_bool32 drwav_seek_to_pcm_frame(drwav* pWav, drwav_uint64 targetF if (pWav->totalPCMFrameCount == 0) { return DRWAV_TRUE; } - if (targetFrameIndex >= pWav->totalPCMFrameCount) { - targetFrameIndex = pWav->totalPCMFrameCount - 1; + if (targetFrameIndex > pWav->totalPCMFrameCount) { + targetFrameIndex = pWav->totalPCMFrameCount; } if (drwav__is_compressed_format_tag(pWav->translatedFormatTag)) { if (targetFrameIndex < pWav->readCursorInPCMFrames) { @@ -55977,10 +77063,15 @@ DRWAV_API drwav_bool32 drwav_seek_to_pcm_frame(drwav* pWav, drwav_uint64 targetF drwav_uint64 currentBytePos; drwav_uint64 targetBytePos; drwav_uint64 offset; - totalSizeInBytes = pWav->totalPCMFrameCount * drwav_get_bytes_per_pcm_frame(pWav); + drwav_uint32 bytesPerFrame; + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return DRWAV_FALSE; + } + totalSizeInBytes = pWav->totalPCMFrameCount * bytesPerFrame; DRWAV_ASSERT(totalSizeInBytes >= pWav->bytesRemaining); currentBytePos = totalSizeInBytes - pWav->bytesRemaining; - targetBytePos = targetFrameIndex * drwav_get_bytes_per_pcm_frame(pWav); + targetBytePos = targetFrameIndex * bytesPerFrame; if (currentBytePos < targetBytePos) { offset = (targetBytePos - currentBytePos); } else { @@ -55994,7 +77085,7 @@ DRWAV_API drwav_bool32 drwav_seek_to_pcm_frame(drwav* pWav, drwav_uint64 targetF if (!pWav->onSeek(pWav->pUserData, offset32, drwav_seek_origin_current)) { return DRWAV_FALSE; } - pWav->readCursorInPCMFrames += offset32 / drwav_get_bytes_per_pcm_frame(pWav); + pWav->readCursorInPCMFrames += offset32 / bytesPerFrame; pWav->bytesRemaining -= offset32; offset -= offset32; } @@ -56080,6 +77171,9 @@ DRWAV_API drwav_uint64 drwav_write_pcm_frames_be(drwav* pWav, drwav_uint64 frame bytesWritten = 0; pRunningData = (const drwav_uint8*)pData; bytesPerSample = drwav_get_bytes_per_pcm_frame(pWav) / pWav->channels; + if (bytesPerSample == 0) { + return 0; + } while (bytesToWrite > 0) { drwav_uint8 temp[4096]; drwav_uint32 sampleCount; @@ -56278,7 +77372,7 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__ima(drwav* pWav, drwav_uin return totalFramesRead; } pWav->ima.predictor[0] = drwav_bytes_to_s16(header + 0); - pWav->ima.stepIndex[0] = header[2]; + pWav->ima.stepIndex[0] = drwav_clamp(header[2], 0, (drwav_int32)drwav_countof(stepTable)-1); pWav->ima.cachedFrames[drwav_countof(pWav->ima.cachedFrames) - 1] = pWav->ima.predictor[0]; pWav->ima.cachedFrameCount = 1; } else { @@ -56293,9 +77387,9 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__ima(drwav* pWav, drwav_uin return totalFramesRead; } pWav->ima.predictor[0] = drwav_bytes_to_s16(header + 0); - pWav->ima.stepIndex[0] = header[2]; + pWav->ima.stepIndex[0] = drwav_clamp(header[2], 0, (drwav_int32)drwav_countof(stepTable)-1); pWav->ima.predictor[1] = drwav_bytes_to_s16(header + 4); - pWav->ima.stepIndex[1] = header[6]; + pWav->ima.stepIndex[1] = drwav_clamp(header[6], 0, (drwav_int32)drwav_countof(stepTable)-1); pWav->ima.cachedFrames[drwav_countof(pWav->ima.cachedFrames) - 2] = pWav->ima.predictor[0]; pWav->ima.cachedFrames[drwav_countof(pWav->ima.cachedFrames) - 1] = pWav->ima.predictor[1]; pWav->ima.cachedFrameCount = 1; @@ -56409,7 +77503,7 @@ static DRWAV_INLINE drwav_int16 drwav__mulaw_to_s16(drwav_uint8 sampleIn) } DRWAV_PRIVATE void drwav__pcm_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t totalSampleCount, unsigned int bytesPerSample) { - unsigned int i; + size_t i; if (bytesPerSample == 1) { drwav_u8_to_s16(pOut, pIn, totalSampleCount); return; @@ -56461,8 +77555,10 @@ DRWAV_PRIVATE void drwav__ieee_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__pcm(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) { drwav_uint64 totalFramesRead; - drwav_uint8 sampleData[4096]; + drwav_uint8 sampleData[4096] = {0}; drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; if ((pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM && pWav->bitsPerSample == 16) || pBufferOut == NULL) { return drwav_read_pcm_frames(pWav, framesToRead, pBufferOut); } @@ -56470,14 +77566,25 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__pcm(drwav* pWav, drwav_uin if (bytesPerFrame == 0) { return 0; } + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; + } totalFramesRead = 0; while (framesToRead > 0) { - drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame), sampleData); + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); if (framesRead == 0) { break; } - drwav__pcm_to_s16(pBufferOut, sampleData, (size_t)(framesRead*pWav->channels), bytesPerFrame/pWav->channels); - pBufferOut += framesRead*pWav->channels; + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); + break; + } + drwav__pcm_to_s16(pBufferOut, sampleData, (size_t)samplesRead, bytesPerSample); + pBufferOut += samplesRead; framesToRead -= framesRead; totalFramesRead += framesRead; } @@ -56486,8 +77593,10 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__pcm(drwav* pWav, drwav_uin DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__ieee(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) { drwav_uint64 totalFramesRead; - drwav_uint8 sampleData[4096]; + drwav_uint8 sampleData[4096] = {0}; drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; if (pBufferOut == NULL) { return drwav_read_pcm_frames(pWav, framesToRead, NULL); } @@ -56495,14 +77604,25 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__ieee(drwav* pWav, drwav_ui if (bytesPerFrame == 0) { return 0; } + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; + } totalFramesRead = 0; while (framesToRead > 0) { - drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame), sampleData); + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); if (framesRead == 0) { break; } - drwav__ieee_to_s16(pBufferOut, sampleData, (size_t)(framesRead*pWav->channels), bytesPerFrame/pWav->channels); - pBufferOut += framesRead*pWav->channels; + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); + break; + } + drwav__ieee_to_s16(pBufferOut, sampleData, (size_t)samplesRead, bytesPerSample); + pBufferOut += samplesRead; framesToRead -= framesRead; totalFramesRead += framesRead; } @@ -56511,8 +77631,10 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__ieee(drwav* pWav, drwav_ui DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__alaw(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) { drwav_uint64 totalFramesRead; - drwav_uint8 sampleData[4096]; + drwav_uint8 sampleData[4096] = {0}; drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; if (pBufferOut == NULL) { return drwav_read_pcm_frames(pWav, framesToRead, NULL); } @@ -56520,14 +77642,25 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__alaw(drwav* pWav, drwav_ui if (bytesPerFrame == 0) { return 0; } + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; + } totalFramesRead = 0; while (framesToRead > 0) { - drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame), sampleData); + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); if (framesRead == 0) { break; } - drwav_alaw_to_s16(pBufferOut, sampleData, (size_t)(framesRead*pWav->channels)); - pBufferOut += framesRead*pWav->channels; + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); + break; + } + drwav_alaw_to_s16(pBufferOut, sampleData, (size_t)samplesRead); + pBufferOut += samplesRead; framesToRead -= framesRead; totalFramesRead += framesRead; } @@ -56536,8 +77669,10 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__alaw(drwav* pWav, drwav_ui DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__mulaw(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) { drwav_uint64 totalFramesRead; - drwav_uint8 sampleData[4096]; + drwav_uint8 sampleData[4096] = {0}; drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; if (pBufferOut == NULL) { return drwav_read_pcm_frames(pWav, framesToRead, NULL); } @@ -56545,14 +77680,25 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__mulaw(drwav* pWav, drwav_u if (bytesPerFrame == 0) { return 0; } + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; + } totalFramesRead = 0; while (framesToRead > 0) { - drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame), sampleData); + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); if (framesRead == 0) { break; } - drwav_mulaw_to_s16(pBufferOut, sampleData, (size_t)(framesRead*pWav->channels)); - pBufferOut += framesRead*pWav->channels; + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); + break; + } + drwav_mulaw_to_s16(pBufferOut, sampleData, (size_t)samplesRead); + pBufferOut += samplesRead; framesToRead -= framesRead; totalFramesRead += framesRead; } @@ -56733,49 +77879,50 @@ DRWAV_PRIVATE void drwav__ieee_to_f32(float* pOut, const drwav_uint8* pIn, size_ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_f32__pcm(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) { drwav_uint64 totalFramesRead; - drwav_uint8 sampleData[4096]; - drwav_uint32 bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + drwav_uint8 sampleData[4096] = {0}; + drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); if (bytesPerFrame == 0) { return 0; } + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; + } totalFramesRead = 0; while (framesToRead > 0) { - drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame), sampleData); + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); if (framesRead == 0) { break; } - drwav__pcm_to_f32(pBufferOut, sampleData, (size_t)framesRead*pWav->channels, bytesPerFrame/pWav->channels); - pBufferOut += framesRead*pWav->channels; + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); + break; + } + drwav__pcm_to_f32(pBufferOut, sampleData, (size_t)samplesRead, bytesPerSample); + pBufferOut += samplesRead; framesToRead -= framesRead; totalFramesRead += framesRead; } return totalFramesRead; } -DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_f32__msadpcm(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_f32__msadpcm_ima(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) { - drwav_uint64 totalFramesRead = 0; + drwav_uint64 totalFramesRead; drwav_int16 samples16[2048]; + totalFramesRead = 0; while (framesToRead > 0) { - drwav_uint64 framesRead = drwav_read_pcm_frames_s16(pWav, drwav_min(framesToRead, drwav_countof(samples16)/pWav->channels), samples16); - if (framesRead == 0) { - break; - } - drwav_s16_to_f32(pBufferOut, samples16, (size_t)(framesRead*pWav->channels)); - pBufferOut += framesRead*pWav->channels; - framesToRead -= framesRead; - totalFramesRead += framesRead; - } - return totalFramesRead; -} -DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_f32__ima(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) -{ - drwav_uint64 totalFramesRead = 0; - drwav_int16 samples16[2048]; - while (framesToRead > 0) { - drwav_uint64 framesRead = drwav_read_pcm_frames_s16(pWav, drwav_min(framesToRead, drwav_countof(samples16)/pWav->channels), samples16); + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, drwav_countof(samples16)/pWav->channels); + drwav_uint64 framesRead = drwav_read_pcm_frames_s16(pWav, framesToReadThisIteration, samples16); if (framesRead == 0) { break; } + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); drwav_s16_to_f32(pBufferOut, samples16, (size_t)(framesRead*pWav->channels)); pBufferOut += framesRead*pWav->channels; framesToRead -= framesRead; @@ -56786,8 +77933,10 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_f32__ima(drwav* pWav, drwav_uin DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_f32__ieee(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) { drwav_uint64 totalFramesRead; - drwav_uint8 sampleData[4096]; + drwav_uint8 sampleData[4096] = {0}; drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; if (pWav->translatedFormatTag == DR_WAVE_FORMAT_IEEE_FLOAT && pWav->bitsPerSample == 32) { return drwav_read_pcm_frames(pWav, framesToRead, pBufferOut); } @@ -56795,14 +77944,25 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_f32__ieee(drwav* pWav, drwav_ui if (bytesPerFrame == 0) { return 0; } + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; + } totalFramesRead = 0; while (framesToRead > 0) { - drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame), sampleData); + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); if (framesRead == 0) { break; } - drwav__ieee_to_f32(pBufferOut, sampleData, (size_t)(framesRead*pWav->channels), bytesPerFrame/pWav->channels); - pBufferOut += framesRead*pWav->channels; + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); + break; + } + drwav__ieee_to_f32(pBufferOut, sampleData, (size_t)samplesRead, bytesPerSample); + pBufferOut += samplesRead; framesToRead -= framesRead; totalFramesRead += framesRead; } @@ -56811,19 +77971,33 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_f32__ieee(drwav* pWav, drwav_ui DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_f32__alaw(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) { drwav_uint64 totalFramesRead; - drwav_uint8 sampleData[4096]; - drwav_uint32 bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + drwav_uint8 sampleData[4096] = {0}; + drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); if (bytesPerFrame == 0) { return 0; } + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; + } totalFramesRead = 0; while (framesToRead > 0) { - drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame), sampleData); + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); if (framesRead == 0) { break; } - drwav_alaw_to_f32(pBufferOut, sampleData, (size_t)(framesRead*pWav->channels)); - pBufferOut += framesRead*pWav->channels; + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); + break; + } + drwav_alaw_to_f32(pBufferOut, sampleData, (size_t)samplesRead); + pBufferOut += samplesRead; framesToRead -= framesRead; totalFramesRead += framesRead; } @@ -56832,19 +78006,33 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_f32__alaw(drwav* pWav, drwav_ui DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_f32__mulaw(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) { drwav_uint64 totalFramesRead; - drwav_uint8 sampleData[4096]; - drwav_uint32 bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + drwav_uint8 sampleData[4096] = {0}; + drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); if (bytesPerFrame == 0) { return 0; } + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; + } totalFramesRead = 0; while (framesToRead > 0) { - drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame), sampleData); + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); if (framesRead == 0) { break; } - drwav_mulaw_to_f32(pBufferOut, sampleData, (size_t)(framesRead*pWav->channels)); - pBufferOut += framesRead*pWav->channels; + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); + break; + } + drwav_mulaw_to_f32(pBufferOut, sampleData, (size_t)samplesRead); + pBufferOut += samplesRead; framesToRead -= framesRead; totalFramesRead += framesRead; } @@ -56864,8 +78052,8 @@ DRWAV_API drwav_uint64 drwav_read_pcm_frames_f32(drwav* pWav, drwav_uint64 frame if (pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM) { return drwav_read_pcm_frames_f32__pcm(pWav, framesToRead, pBufferOut); } - if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { - return drwav_read_pcm_frames_f32__msadpcm(pWav, framesToRead, pBufferOut); + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM || pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { + return drwav_read_pcm_frames_f32__msadpcm_ima(pWav, framesToRead, pBufferOut); } if (pWav->translatedFormatTag == DR_WAVE_FORMAT_IEEE_FLOAT) { return drwav_read_pcm_frames_f32__ieee(pWav, framesToRead, pBufferOut); @@ -56876,9 +78064,6 @@ DRWAV_API drwav_uint64 drwav_read_pcm_frames_f32(drwav* pWav, drwav_uint64 frame if (pWav->translatedFormatTag == DR_WAVE_FORMAT_MULAW) { return drwav_read_pcm_frames_f32__mulaw(pWav, framesToRead, pBufferOut); } - if (pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { - return drwav_read_pcm_frames_f32__ima(pWav, framesToRead, pBufferOut); - } return 0; } DRWAV_API drwav_uint64 drwav_read_pcm_frames_f32le(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) @@ -57035,8 +78220,10 @@ DRWAV_PRIVATE void drwav__ieee_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s32__pcm(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) { drwav_uint64 totalFramesRead; - drwav_uint8 sampleData[4096]; + drwav_uint8 sampleData[4096] = {0}; drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; if (pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM && pWav->bitsPerSample == 32) { return drwav_read_pcm_frames(pWav, framesToRead, pBufferOut); } @@ -57044,44 +78231,41 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s32__pcm(drwav* pWav, drwav_uin if (bytesPerFrame == 0) { return 0; } + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; + } totalFramesRead = 0; while (framesToRead > 0) { - drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame), sampleData); + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); if (framesRead == 0) { break; } - drwav__pcm_to_s32(pBufferOut, sampleData, (size_t)(framesRead*pWav->channels), bytesPerFrame/pWav->channels); - pBufferOut += framesRead*pWav->channels; + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); + break; + } + drwav__pcm_to_s32(pBufferOut, sampleData, (size_t)samplesRead, bytesPerSample); + pBufferOut += samplesRead; framesToRead -= framesRead; totalFramesRead += framesRead; } return totalFramesRead; } -DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s32__msadpcm(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s32__msadpcm_ima(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) { drwav_uint64 totalFramesRead = 0; drwav_int16 samples16[2048]; while (framesToRead > 0) { - drwav_uint64 framesRead = drwav_read_pcm_frames_s16(pWav, drwav_min(framesToRead, drwav_countof(samples16)/pWav->channels), samples16); - if (framesRead == 0) { - break; - } - drwav_s16_to_s32(pBufferOut, samples16, (size_t)(framesRead*pWav->channels)); - pBufferOut += framesRead*pWav->channels; - framesToRead -= framesRead; - totalFramesRead += framesRead; - } - return totalFramesRead; -} -DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s32__ima(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) -{ - drwav_uint64 totalFramesRead = 0; - drwav_int16 samples16[2048]; - while (framesToRead > 0) { - drwav_uint64 framesRead = drwav_read_pcm_frames_s16(pWav, drwav_min(framesToRead, drwav_countof(samples16)/pWav->channels), samples16); + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, drwav_countof(samples16)/pWav->channels); + drwav_uint64 framesRead = drwav_read_pcm_frames_s16(pWav, framesToReadThisIteration, samples16); if (framesRead == 0) { break; } + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); drwav_s16_to_s32(pBufferOut, samples16, (size_t)(framesRead*pWav->channels)); pBufferOut += framesRead*pWav->channels; framesToRead -= framesRead; @@ -57092,19 +78276,33 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s32__ima(drwav* pWav, drwav_uin DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s32__ieee(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) { drwav_uint64 totalFramesRead; - drwav_uint8 sampleData[4096]; - drwav_uint32 bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + drwav_uint8 sampleData[4096] = {0}; + drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); if (bytesPerFrame == 0) { return 0; } + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; + } totalFramesRead = 0; while (framesToRead > 0) { - drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame), sampleData); + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); if (framesRead == 0) { break; } - drwav__ieee_to_s32(pBufferOut, sampleData, (size_t)(framesRead*pWav->channels), bytesPerFrame/pWav->channels); - pBufferOut += framesRead*pWav->channels; + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); + break; + } + drwav__ieee_to_s32(pBufferOut, sampleData, (size_t)samplesRead, bytesPerSample); + pBufferOut += samplesRead; framesToRead -= framesRead; totalFramesRead += framesRead; } @@ -57113,19 +78311,33 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s32__ieee(drwav* pWav, drwav_ui DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s32__alaw(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) { drwav_uint64 totalFramesRead; - drwav_uint8 sampleData[4096]; - drwav_uint32 bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + drwav_uint8 sampleData[4096] = {0}; + drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); if (bytesPerFrame == 0) { return 0; } + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; + } totalFramesRead = 0; while (framesToRead > 0) { - drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame), sampleData); + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); if (framesRead == 0) { break; } - drwav_alaw_to_s32(pBufferOut, sampleData, (size_t)(framesRead*pWav->channels)); - pBufferOut += framesRead*pWav->channels; + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); + break; + } + drwav_alaw_to_s32(pBufferOut, sampleData, (size_t)samplesRead); + pBufferOut += samplesRead; framesToRead -= framesRead; totalFramesRead += framesRead; } @@ -57134,19 +78346,33 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s32__alaw(drwav* pWav, drwav_ui DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s32__mulaw(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) { drwav_uint64 totalFramesRead; - drwav_uint8 sampleData[4096]; - drwav_uint32 bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + drwav_uint8 sampleData[4096] = {0}; + drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); if (bytesPerFrame == 0) { return 0; } + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; + } totalFramesRead = 0; while (framesToRead > 0) { - drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame), sampleData); + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); if (framesRead == 0) { break; } - drwav_mulaw_to_s32(pBufferOut, sampleData, (size_t)(framesRead*pWav->channels)); - pBufferOut += framesRead*pWav->channels; + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); + break; + } + drwav_mulaw_to_s32(pBufferOut, sampleData, (size_t)samplesRead); + pBufferOut += samplesRead; framesToRead -= framesRead; totalFramesRead += framesRead; } @@ -57166,8 +78392,8 @@ DRWAV_API drwav_uint64 drwav_read_pcm_frames_s32(drwav* pWav, drwav_uint64 frame if (pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM) { return drwav_read_pcm_frames_s32__pcm(pWav, framesToRead, pBufferOut); } - if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { - return drwav_read_pcm_frames_s32__msadpcm(pWav, framesToRead, pBufferOut); + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM || pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { + return drwav_read_pcm_frames_s32__msadpcm_ima(pWav, framesToRead, pBufferOut); } if (pWav->translatedFormatTag == DR_WAVE_FORMAT_IEEE_FLOAT) { return drwav_read_pcm_frames_s32__ieee(pWav, framesToRead, pBufferOut); @@ -57178,9 +78404,6 @@ DRWAV_API drwav_uint64 drwav_read_pcm_frames_s32(drwav* pWav, drwav_uint64 frame if (pWav->translatedFormatTag == DR_WAVE_FORMAT_MULAW) { return drwav_read_pcm_frames_s32__mulaw(pWav, framesToRead, pBufferOut); } - if (pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { - return drwav_read_pcm_frames_s32__ima(pWav, framesToRead, pBufferOut); - } return 0; } DRWAV_API drwav_uint64 drwav_read_pcm_frames_s32le(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) @@ -57677,9 +78900,14 @@ DRWAV_API drwav_bool32 drwav_fourcc_equal(const drwav_uint8* a, const char* b) #define DRFLAC_INLINE __forceinline #elif defined(__GNUC__) #if defined(__STRICT_ANSI__) - #define DRFLAC_INLINE __inline__ __attribute__((always_inline)) + #define DRFLAC_GNUC_INLINE_HINT __inline__ #else - #define DRFLAC_INLINE inline __attribute__((always_inline)) + #define DRFLAC_GNUC_INLINE_HINT inline + #endif + #if (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 2)) || defined(__clang__) + #define DRFLAC_INLINE DRFLAC_GNUC_INLINE_HINT __attribute__((always_inline)) + #else + #define DRFLAC_INLINE DRFLAC_GNUC_INLINE_HINT #endif #elif defined(__WATCOMC__) #define DRFLAC_INLINE __inline @@ -57690,7 +78918,7 @@ DRWAV_API drwav_bool32 drwav_fourcc_equal(const drwav_uint8* a, const char* b) #define DRFLAC_X64 #elif defined(__i386) || defined(_M_IX86) #define DRFLAC_X86 -#elif defined(__arm__) || defined(_M_ARM) || defined(_M_ARM64) +#elif defined(__arm__) || defined(_M_ARM) || defined(__arm64) || defined(__arm64__) || defined(__aarch64__) || defined(_M_ARM64) #define DRFLAC_ARM #endif #if !defined(DR_FLAC_NO_SIMD) @@ -57727,13 +78955,6 @@ DRWAV_API drwav_bool32 drwav_fourcc_equal(const drwav_uint8* a, const char* b) #if defined(DRFLAC_ARM) #if !defined(DRFLAC_NO_NEON) && (defined(__ARM_NEON) || defined(__aarch64__) || defined(_M_ARM64)) #define DRFLAC_SUPPORT_NEON - #endif - #if !defined(__GNUC__) && !defined(__clang__) && defined(__has_include) - #if !defined(DRFLAC_SUPPORT_NEON) && !defined(DRFLAC_NO_NEON) && __has_include() - #define DRFLAC_SUPPORT_NEON - #endif - #endif - #if defined(DRFLAC_SUPPORT_NEON) #include #endif #endif @@ -58136,6 +79357,11 @@ static DRFLAC_INLINE drflac_uint32 drflac__be2host_32(drflac_uint32 n) } return n; } +static DRFLAC_INLINE drflac_uint32 drflac__be2host_32_ptr_unaligned(const void* pData) +{ + const drflac_uint8* pNum = (drflac_uint8*)pData; + return *(pNum) << 24 | *(pNum+1) << 16 | *(pNum+2) << 8 | *(pNum+3); +} static DRFLAC_INLINE drflac_uint64 drflac__be2host_64(drflac_uint64 n) { if (drflac__is_little_endian()) { @@ -58150,6 +79376,11 @@ static DRFLAC_INLINE drflac_uint32 drflac__le2host_32(drflac_uint32 n) } return n; } +static DRFLAC_INLINE drflac_uint32 drflac__le2host_32_ptr_unaligned(const void* pData) +{ + const drflac_uint8* pNum = (drflac_uint8*)pData; + return *pNum | *(pNum+1) << 8 | *(pNum+2) << 16 | *(pNum+3) << 24; +} static DRFLAC_INLINE drflac_uint32 drflac__unsynchsafe_32(drflac_uint32 n) { drflac_uint32 result = 0; @@ -58535,6 +79766,9 @@ static DRFLAC_INLINE drflac_bool32 drflac__read_uint32(drflac_bs* bs, unsigned i if (!drflac__reload_cache(bs)) { return DRFLAC_FALSE; } + if (bitCountLo > DRFLAC_CACHE_L1_BITS_REMAINING(bs)) { + return DRFLAC_FALSE; + } *pResultOut = (resultHi << bitCountLo) | (drflac_uint32)DRFLAC_CACHE_L1_SELECT_AND_SHIFT(bs, bitCountLo); bs->consumedBits += bitCountLo; bs->cache <<= bitCountLo; @@ -58876,8 +80110,18 @@ static DRFLAC_INLINE drflac_bool32 drflac__seek_past_next_set_bit(drflac_bs* bs, return DRFLAC_FALSE; } } + if (bs->cache == 1) { + *pOffsetOut = zeroCounter + (drflac_uint32)DRFLAC_CACHE_L1_BITS_REMAINING(bs) - 1; + if (!drflac__reload_cache(bs)) { + return DRFLAC_FALSE; + } + return DRFLAC_TRUE; + } setBitOffsetPlus1 = drflac__clz(bs->cache); setBitOffsetPlus1 += 1; + if (setBitOffsetPlus1 > DRFLAC_CACHE_L1_BITS_REMAINING(bs)) { + return DRFLAC_FALSE; + } bs->consumedBits += setBitOffsetPlus1; bs->cache <<= setBitOffsetPlus1; *pOffsetOut = zeroCounter + setBitOffsetPlus1 - 1; @@ -58963,6 +80207,24 @@ static drflac_result drflac__read_utf8_coded_number(drflac_bs* bs, drflac_uint64 *pCRCOut = crc; return DRFLAC_SUCCESS; } +static DRFLAC_INLINE drflac_uint32 drflac__ilog2_u32(drflac_uint32 x) +{ +#if 1 + drflac_uint32 result = 0; + while (x > 0) { + result += 1; + x >>= 1; + } + return result; +#endif +} +static DRFLAC_INLINE drflac_bool32 drflac__use_64_bit_prediction(drflac_uint32 bitsPerSample, drflac_uint32 order, drflac_uint32 precision) +{ + return bitsPerSample + precision + drflac__ilog2_u32(order) > 32; +} +#if defined(__clang__) +__attribute__((no_sanitize("signed-integer-overflow"))) +#endif static DRFLAC_INLINE drflac_int32 drflac__calculate_prediction_32(drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pDecodedSamples) { drflac_int32 prediction = 0; @@ -59173,7 +80435,7 @@ static DRFLAC_INLINE drflac_int32 drflac__calculate_prediction_64(drflac_uint32 return (drflac_int32)(prediction >> shift); } #if 0 -static drflac_bool32 drflac__decode_samples_with_residual__rice__reference(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut) +static drflac_bool32 drflac__decode_samples_with_residual__rice__reference(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 lpcOrder, drflac_int32 lpcShift, drflac_uint32 lpcPrecision, const drflac_int32* coefficients, drflac_int32* pSamplesOut) { drflac_uint32 i; DRFLAC_ASSERT(bs != NULL); @@ -59205,10 +80467,10 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__reference(drfla } else { decodedRice = (decodedRice >> 1); } - if (bitsPerSample+shift >= 32) { - pSamplesOut[i] = decodedRice + drflac__calculate_prediction_64(order, shift, coefficients, pSamplesOut + i); + if (drflac__use_64_bit_prediction(bitsPerSample, lpcOrder, lpcPrecision)) { + pSamplesOut[i] = decodedRice + drflac__calculate_prediction_64(lpcOrder, lpcShift, coefficients, pSamplesOut + i); } else { - pSamplesOut[i] = decodedRice + drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + i); + pSamplesOut[i] = decodedRice + drflac__calculate_prediction_32(lpcOrder, lpcShift, coefficients, pSamplesOut + i); } } return DRFLAC_TRUE; @@ -59287,6 +80549,9 @@ static DRFLAC_INLINE drflac_bool32 drflac__read_rice_parts(drflac_bs* bs, drflac if (!drflac__reload_cache(bs)) { return DRFLAC_FALSE; } + if (bitCountLo > DRFLAC_CACHE_L1_BITS_REMAINING(bs)) { + return DRFLAC_FALSE; + } } riceParamPart = (drflac_uint32)(resultHi | DRFLAC_CACHE_L1_SELECT_AND_SHIFT_SAFE(bs, bitCountLo)); bs->consumedBits += bitCountLo; @@ -59334,6 +80599,9 @@ static DRFLAC_INLINE drflac_bool32 drflac__read_rice_parts_x1(drflac_bs* bs, drf if (!drflac__reload_cache(bs)) { return DRFLAC_FALSE; } + if (riceParamPartLoBitCount > DRFLAC_CACHE_L1_BITS_REMAINING(bs)) { + return DRFLAC_FALSE; + } bs_cache = bs->cache; bs_consumedBits = bs->consumedBits + riceParamPartLoBitCount; } @@ -59403,6 +80671,9 @@ static DRFLAC_INLINE drflac_bool32 drflac__seek_rice_parts(drflac_bs* bs, drflac if (!drflac__reload_cache(bs)) { return DRFLAC_FALSE; } + if (riceParamPartLoBitCount > DRFLAC_CACHE_L1_BITS_REMAINING(bs)) { + return DRFLAC_FALSE; + } bs_cache = bs->cache; bs_consumedBits = bs->consumedBits + riceParamPartLoBitCount; } @@ -59464,7 +80735,7 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__scalar_zeroorde } return DRFLAC_TRUE; } -static drflac_bool32 drflac__decode_samples_with_residual__rice__scalar(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut) +static drflac_bool32 drflac__decode_samples_with_residual__rice__scalar(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 lpcOrder, drflac_int32 lpcShift, drflac_uint32 lpcPrecision, const drflac_int32* coefficients, drflac_int32* pSamplesOut) { drflac_uint32 t[2] = {0x00000000, 0xFFFFFFFF}; drflac_uint32 zeroCountPart0 = 0; @@ -59480,12 +80751,12 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__scalar(drflac_b drflac_uint32 i; DRFLAC_ASSERT(bs != NULL); DRFLAC_ASSERT(pSamplesOut != NULL); - if (order == 0) { - return drflac__decode_samples_with_residual__rice__scalar_zeroorder(bs, bitsPerSample, count, riceParam, order, shift, coefficients, pSamplesOut); + if (lpcOrder == 0) { + return drflac__decode_samples_with_residual__rice__scalar_zeroorder(bs, bitsPerSample, count, riceParam, lpcOrder, lpcShift, coefficients, pSamplesOut); } riceParamMask = (drflac_uint32)~((~0UL) << riceParam); pSamplesOutEnd = pSamplesOut + (count & ~3); - if (bitsPerSample+shift > 32) { + if (drflac__use_64_bit_prediction(bitsPerSample, lpcOrder, lpcPrecision)) { while (pSamplesOut < pSamplesOutEnd) { if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountPart0, &riceParamPart0) || !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountPart1, &riceParamPart1) || @@ -59505,10 +80776,10 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__scalar(drflac_b riceParamPart1 = (riceParamPart1 >> 1) ^ t[riceParamPart1 & 0x01]; riceParamPart2 = (riceParamPart2 >> 1) ^ t[riceParamPart2 & 0x01]; riceParamPart3 = (riceParamPart3 >> 1) ^ t[riceParamPart3 & 0x01]; - pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_64(order, shift, coefficients, pSamplesOut + 0); - pSamplesOut[1] = riceParamPart1 + drflac__calculate_prediction_64(order, shift, coefficients, pSamplesOut + 1); - pSamplesOut[2] = riceParamPart2 + drflac__calculate_prediction_64(order, shift, coefficients, pSamplesOut + 2); - pSamplesOut[3] = riceParamPart3 + drflac__calculate_prediction_64(order, shift, coefficients, pSamplesOut + 3); + pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_64(lpcOrder, lpcShift, coefficients, pSamplesOut + 0); + pSamplesOut[1] = riceParamPart1 + drflac__calculate_prediction_64(lpcOrder, lpcShift, coefficients, pSamplesOut + 1); + pSamplesOut[2] = riceParamPart2 + drflac__calculate_prediction_64(lpcOrder, lpcShift, coefficients, pSamplesOut + 2); + pSamplesOut[3] = riceParamPart3 + drflac__calculate_prediction_64(lpcOrder, lpcShift, coefficients, pSamplesOut + 3); pSamplesOut += 4; } } else { @@ -59531,10 +80802,10 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__scalar(drflac_b riceParamPart1 = (riceParamPart1 >> 1) ^ t[riceParamPart1 & 0x01]; riceParamPart2 = (riceParamPart2 >> 1) ^ t[riceParamPart2 & 0x01]; riceParamPart3 = (riceParamPart3 >> 1) ^ t[riceParamPart3 & 0x01]; - pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + 0); - pSamplesOut[1] = riceParamPart1 + drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + 1); - pSamplesOut[2] = riceParamPart2 + drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + 2); - pSamplesOut[3] = riceParamPart3 + drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + 3); + pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_32(lpcOrder, lpcShift, coefficients, pSamplesOut + 0); + pSamplesOut[1] = riceParamPart1 + drflac__calculate_prediction_32(lpcOrder, lpcShift, coefficients, pSamplesOut + 1); + pSamplesOut[2] = riceParamPart2 + drflac__calculate_prediction_32(lpcOrder, lpcShift, coefficients, pSamplesOut + 2); + pSamplesOut[3] = riceParamPart3 + drflac__calculate_prediction_32(lpcOrder, lpcShift, coefficients, pSamplesOut + 3); pSamplesOut += 4; } } @@ -59546,10 +80817,10 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__scalar(drflac_b riceParamPart0 &= riceParamMask; riceParamPart0 |= (zeroCountPart0 << riceParam); riceParamPart0 = (riceParamPart0 >> 1) ^ t[riceParamPart0 & 0x01]; - if (bitsPerSample+shift > 32) { - pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_64(order, shift, coefficients, pSamplesOut + 0); + if (drflac__use_64_bit_prediction(bitsPerSample, lpcOrder, lpcPrecision)) { + pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_64(lpcOrder, lpcShift, coefficients, pSamplesOut + 0); } else { - pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + 0); + pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_32(lpcOrder, lpcShift, coefficients, pSamplesOut + 0); } i += 1; pSamplesOut += 1; @@ -59894,18 +81165,18 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__sse41_64(drflac } return DRFLAC_TRUE; } -static drflac_bool32 drflac__decode_samples_with_residual__rice__sse41(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut) +static drflac_bool32 drflac__decode_samples_with_residual__rice__sse41(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 lpcOrder, drflac_int32 lpcShift, drflac_uint32 lpcPrecision, const drflac_int32* coefficients, drflac_int32* pSamplesOut) { DRFLAC_ASSERT(bs != NULL); DRFLAC_ASSERT(pSamplesOut != NULL); - if (order > 0 && order <= 12) { - if (bitsPerSample+shift > 32) { - return drflac__decode_samples_with_residual__rice__sse41_64(bs, count, riceParam, order, shift, coefficients, pSamplesOut); + if (lpcOrder > 0 && lpcOrder <= 12) { + if (drflac__use_64_bit_prediction(bitsPerSample, lpcOrder, lpcPrecision)) { + return drflac__decode_samples_with_residual__rice__sse41_64(bs, count, riceParam, lpcOrder, lpcShift, coefficients, pSamplesOut); } else { - return drflac__decode_samples_with_residual__rice__sse41_32(bs, count, riceParam, order, shift, coefficients, pSamplesOut); + return drflac__decode_samples_with_residual__rice__sse41_32(bs, count, riceParam, lpcOrder, lpcShift, coefficients, pSamplesOut); } } else { - return drflac__decode_samples_with_residual__rice__scalar(bs, bitsPerSample, count, riceParam, order, shift, coefficients, pSamplesOut); + return drflac__decode_samples_with_residual__rice__scalar(bs, bitsPerSample, count, riceParam, lpcOrder, lpcShift, lpcPrecision, coefficients, pSamplesOut); } } #endif @@ -60244,37 +81515,37 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__neon_64(drflac_ } return DRFLAC_TRUE; } -static drflac_bool32 drflac__decode_samples_with_residual__rice__neon(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut) +static drflac_bool32 drflac__decode_samples_with_residual__rice__neon(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 lpcOrder, drflac_int32 lpcShift, drflac_uint32 lpcPrecision, const drflac_int32* coefficients, drflac_int32* pSamplesOut) { DRFLAC_ASSERT(bs != NULL); DRFLAC_ASSERT(pSamplesOut != NULL); - if (order > 0 && order <= 12) { - if (bitsPerSample+shift > 32) { - return drflac__decode_samples_with_residual__rice__neon_64(bs, count, riceParam, order, shift, coefficients, pSamplesOut); + if (lpcOrder > 0 && lpcOrder <= 12) { + if (drflac__use_64_bit_prediction(bitsPerSample, lpcOrder, lpcPrecision)) { + return drflac__decode_samples_with_residual__rice__neon_64(bs, count, riceParam, lpcOrder, lpcShift, coefficients, pSamplesOut); } else { - return drflac__decode_samples_with_residual__rice__neon_32(bs, count, riceParam, order, shift, coefficients, pSamplesOut); + return drflac__decode_samples_with_residual__rice__neon_32(bs, count, riceParam, lpcOrder, lpcShift, coefficients, pSamplesOut); } } else { - return drflac__decode_samples_with_residual__rice__scalar(bs, bitsPerSample, count, riceParam, order, shift, coefficients, pSamplesOut); + return drflac__decode_samples_with_residual__rice__scalar(bs, bitsPerSample, count, riceParam, lpcOrder, lpcShift, lpcPrecision, coefficients, pSamplesOut); } } #endif -static drflac_bool32 drflac__decode_samples_with_residual__rice(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut) +static drflac_bool32 drflac__decode_samples_with_residual__rice(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 lpcOrder, drflac_int32 lpcShift, drflac_uint32 lpcPrecision, const drflac_int32* coefficients, drflac_int32* pSamplesOut) { #if defined(DRFLAC_SUPPORT_SSE41) if (drflac__gIsSSE41Supported) { - return drflac__decode_samples_with_residual__rice__sse41(bs, bitsPerSample, count, riceParam, order, shift, coefficients, pSamplesOut); + return drflac__decode_samples_with_residual__rice__sse41(bs, bitsPerSample, count, riceParam, lpcOrder, lpcShift, lpcPrecision, coefficients, pSamplesOut); } else #elif defined(DRFLAC_SUPPORT_NEON) if (drflac__gIsNEONSupported) { - return drflac__decode_samples_with_residual__rice__neon(bs, bitsPerSample, count, riceParam, order, shift, coefficients, pSamplesOut); + return drflac__decode_samples_with_residual__rice__neon(bs, bitsPerSample, count, riceParam, lpcOrder, lpcShift, lpcPrecision, coefficients, pSamplesOut); } else #endif { #if 0 - return drflac__decode_samples_with_residual__rice__reference(bs, bitsPerSample, count, riceParam, order, shift, coefficients, pSamplesOut); + return drflac__decode_samples_with_residual__rice__reference(bs, bitsPerSample, count, riceParam, lpcOrder, lpcShift, lpcPrecision, coefficients, pSamplesOut); #else - return drflac__decode_samples_with_residual__rice__scalar(bs, bitsPerSample, count, riceParam, order, shift, coefficients, pSamplesOut); + return drflac__decode_samples_with_residual__rice__scalar(bs, bitsPerSample, count, riceParam, lpcOrder, lpcShift, lpcPrecision, coefficients, pSamplesOut); #endif } } @@ -60289,7 +81560,10 @@ static drflac_bool32 drflac__read_and_seek_residual__rice(drflac_bs* bs, drflac_ } return DRFLAC_TRUE; } -static drflac_bool32 drflac__decode_samples_with_residual__unencoded(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 unencodedBitsPerSample, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut) +#if defined(__clang__) +__attribute__((no_sanitize("signed-integer-overflow"))) +#endif +static drflac_bool32 drflac__decode_samples_with_residual__unencoded(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 unencodedBitsPerSample, drflac_uint32 lpcOrder, drflac_int32 lpcShift, drflac_uint32 lpcPrecision, const drflac_int32* coefficients, drflac_int32* pSamplesOut) { drflac_uint32 i; DRFLAC_ASSERT(bs != NULL); @@ -60303,15 +81577,15 @@ static drflac_bool32 drflac__decode_samples_with_residual__unencoded(drflac_bs* } else { pSamplesOut[i] = 0; } - if (bitsPerSample >= 24) { - pSamplesOut[i] += drflac__calculate_prediction_64(order, shift, coefficients, pSamplesOut + i); + if (drflac__use_64_bit_prediction(bitsPerSample, lpcOrder, lpcPrecision)) { + pSamplesOut[i] += drflac__calculate_prediction_64(lpcOrder, lpcShift, coefficients, pSamplesOut + i); } else { - pSamplesOut[i] += drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + i); + pSamplesOut[i] += drflac__calculate_prediction_32(lpcOrder, lpcShift, coefficients, pSamplesOut + i); } } return DRFLAC_TRUE; } -static drflac_bool32 drflac__decode_samples_with_residual(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 blockSize, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pDecodedSamples) +static drflac_bool32 drflac__decode_samples_with_residual(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 blockSize, drflac_uint32 lpcOrder, drflac_int32 lpcShift, drflac_uint32 lpcPrecision, const drflac_int32* coefficients, drflac_int32* pDecodedSamples) { drflac_uint8 residualMethod; drflac_uint8 partitionOrder; @@ -60326,17 +81600,17 @@ static drflac_bool32 drflac__decode_samples_with_residual(drflac_bs* bs, drflac_ if (residualMethod != DRFLAC_RESIDUAL_CODING_METHOD_PARTITIONED_RICE && residualMethod != DRFLAC_RESIDUAL_CODING_METHOD_PARTITIONED_RICE2) { return DRFLAC_FALSE; } - pDecodedSamples += order; + pDecodedSamples += lpcOrder; if (!drflac__read_uint8(bs, 4, &partitionOrder)) { return DRFLAC_FALSE; } if (partitionOrder > 8) { return DRFLAC_FALSE; } - if ((blockSize / (1 << partitionOrder)) < order) { + if ((blockSize / (1 << partitionOrder)) < lpcOrder) { return DRFLAC_FALSE; } - samplesInPartition = (blockSize / (1 << partitionOrder)) - order; + samplesInPartition = (blockSize / (1 << partitionOrder)) - lpcOrder; partitionsRemaining = (1 << partitionOrder); for (;;) { drflac_uint8 riceParam = 0; @@ -60356,7 +81630,7 @@ static drflac_bool32 drflac__decode_samples_with_residual(drflac_bs* bs, drflac_ } } if (riceParam != 0xFF) { - if (!drflac__decode_samples_with_residual__rice(bs, bitsPerSample, samplesInPartition, riceParam, order, shift, coefficients, pDecodedSamples)) { + if (!drflac__decode_samples_with_residual__rice(bs, bitsPerSample, samplesInPartition, riceParam, lpcOrder, lpcShift, lpcPrecision, coefficients, pDecodedSamples)) { return DRFLAC_FALSE; } } else { @@ -60364,7 +81638,7 @@ static drflac_bool32 drflac__decode_samples_with_residual(drflac_bs* bs, drflac_ if (!drflac__read_uint8(bs, 5, &unencodedBitsPerSample)) { return DRFLAC_FALSE; } - if (!drflac__decode_samples_with_residual__unencoded(bs, bitsPerSample, samplesInPartition, unencodedBitsPerSample, order, shift, coefficients, pDecodedSamples)) { + if (!drflac__decode_samples_with_residual__unencoded(bs, bitsPerSample, samplesInPartition, unencodedBitsPerSample, lpcOrder, lpcShift, lpcPrecision, coefficients, pDecodedSamples)) { return DRFLAC_FALSE; } } @@ -60484,7 +81758,7 @@ static drflac_bool32 drflac__decode_samples__fixed(drflac_bs* bs, drflac_uint32 } pDecodedSamples[i] = sample; } - if (!drflac__decode_samples_with_residual(bs, subframeBitsPerSample, blockSize, lpcOrder, 0, lpcCoefficientsTable[lpcOrder], pDecodedSamples)) { + if (!drflac__decode_samples_with_residual(bs, subframeBitsPerSample, blockSize, lpcOrder, 0, 4, lpcCoefficientsTable[lpcOrder], pDecodedSamples)) { return DRFLAC_FALSE; } return DRFLAC_TRUE; @@ -60521,7 +81795,7 @@ static drflac_bool32 drflac__decode_samples__lpc(drflac_bs* bs, drflac_uint32 bl return DRFLAC_FALSE; } } - if (!drflac__decode_samples_with_residual(bs, bitsPerSample, blockSize, lpcOrder, lpcShift, coefficients, pDecodedSamples)) { + if (!drflac__decode_samples_with_residual(bs, bitsPerSample, blockSize, lpcOrder, lpcShift, lpcPrecision, coefficients, pDecodedSamples)) { return DRFLAC_FALSE; } return DRFLAC_TRUE; @@ -60630,6 +81904,9 @@ static drflac_bool32 drflac__read_next_flac_frame_header(drflac_bs* bs, drflac_u return DRFLAC_FALSE; } crc8 = drflac_crc8(crc8, header->blockSizeInPCMFrames, 16); + if (header->blockSizeInPCMFrames == 0xFFFF) { + return DRFLAC_FALSE; + } header->blockSizeInPCMFrames += 1; } else { DRFLAC_ASSERT(blockSize >= 8); @@ -60662,6 +81939,9 @@ static drflac_bool32 drflac__read_next_flac_frame_header(drflac_bs* bs, drflac_u if (header->bitsPerSample == 0) { header->bitsPerSample = streaminfoBitsPerSample; } + if (header->bitsPerSample != streaminfoBitsPerSample) { + return DRFLAC_FALSE; + } if (!drflac__read_uint8(bs, 8, &header->crc8)) { return DRFLAC_FALSE; } @@ -60732,6 +82012,9 @@ static drflac_bool32 drflac__decode_subframe(drflac_bs* bs, drflac_frame* frame, } else if (frame->header.channelAssignment == DRFLAC_CHANNEL_ASSIGNMENT_RIGHT_SIDE && subframeIndex == 0) { subframeBitsPerSample += 1; } + if (subframeBitsPerSample > 32) { + return DRFLAC_FALSE; + } if (pSubframe->wastedBitsPerSample >= subframeBitsPerSample) { return DRFLAC_FALSE; } @@ -61215,6 +82498,9 @@ static drflac_bool32 drflac__seek_to_pcm_frame__seek_table(drflac* pFlac, drflac if (pFlac->pSeekpoints == NULL || pFlac->seekpointCount == 0) { return DRFLAC_FALSE; } + if (pFlac->pSeekpoints[0].firstPCMFrame > pcmFrameIndex) { + return DRFLAC_FALSE; + } for (iSeekpoint = 0; iSeekpoint < pFlac->seekpointCount; ++iSeekpoint) { if (pFlac->pSeekpoints[iSeekpoint].firstPCMFrame >= pcmFrameIndex) { break; @@ -61558,13 +82844,13 @@ static drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, d metadata.rawDataSize = blockSize; pRunningData = (const char*)pRawData; pRunningDataEnd = (const char*)pRawData + blockSize; - metadata.data.vorbis_comment.vendorLength = drflac__le2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; + metadata.data.vorbis_comment.vendorLength = drflac__le2host_32_ptr_unaligned(pRunningData); pRunningData += 4; if ((pRunningDataEnd - pRunningData) - 4 < (drflac_int64)metadata.data.vorbis_comment.vendorLength) { drflac__free_from_callbacks(pRawData, pAllocationCallbacks); return DRFLAC_FALSE; } metadata.data.vorbis_comment.vendor = pRunningData; pRunningData += metadata.data.vorbis_comment.vendorLength; - metadata.data.vorbis_comment.commentCount = drflac__le2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; + metadata.data.vorbis_comment.commentCount = drflac__le2host_32_ptr_unaligned(pRunningData); pRunningData += 4; if ((pRunningDataEnd - pRunningData) / sizeof(drflac_uint32) < metadata.data.vorbis_comment.commentCount) { drflac__free_from_callbacks(pRawData, pAllocationCallbacks); return DRFLAC_FALSE; @@ -61576,7 +82862,7 @@ static drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, d drflac__free_from_callbacks(pRawData, pAllocationCallbacks); return DRFLAC_FALSE; } - commentLength = drflac__le2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; + commentLength = drflac__le2host_32_ptr_unaligned(pRunningData); pRunningData += 4; if (pRunningDataEnd - pRunningData < (drflac_int64)commentLength) { drflac__free_from_callbacks(pRawData, pAllocationCallbacks); return DRFLAC_FALSE; @@ -61660,24 +82946,24 @@ static drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, d metadata.rawDataSize = blockSize; pRunningData = (const char*)pRawData; pRunningDataEnd = (const char*)pRawData + blockSize; - metadata.data.picture.type = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; - metadata.data.picture.mimeLength = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; + metadata.data.picture.type = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4; + metadata.data.picture.mimeLength = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4; if ((pRunningDataEnd - pRunningData) - 24 < (drflac_int64)metadata.data.picture.mimeLength) { drflac__free_from_callbacks(pRawData, pAllocationCallbacks); return DRFLAC_FALSE; } metadata.data.picture.mime = pRunningData; pRunningData += metadata.data.picture.mimeLength; - metadata.data.picture.descriptionLength = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; + metadata.data.picture.descriptionLength = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4; if ((pRunningDataEnd - pRunningData) - 20 < (drflac_int64)metadata.data.picture.descriptionLength) { drflac__free_from_callbacks(pRawData, pAllocationCallbacks); return DRFLAC_FALSE; } metadata.data.picture.description = pRunningData; pRunningData += metadata.data.picture.descriptionLength; - metadata.data.picture.width = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; - metadata.data.picture.height = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; - metadata.data.picture.colorDepth = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; - metadata.data.picture.indexColorCount = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; - metadata.data.picture.pictureDataSize = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; + metadata.data.picture.width = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4; + metadata.data.picture.height = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4; + metadata.data.picture.colorDepth = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4; + metadata.data.picture.indexColorCount = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4; + metadata.data.picture.pictureDataSize = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4; metadata.data.picture.pPictureData = (const drflac_uint8*)pRunningData; if (pRunningDataEnd - pRunningData < (drflac_int64)metadata.data.picture.pictureDataSize) { drflac__free_from_callbacks(pRawData, pAllocationCallbacks); @@ -62563,7 +83849,7 @@ static drflac* drflac_open_with_metadata_private(drflac_read_proc onRead, drflac #ifndef DR_FLAC_NO_OGG if (init.container == drflac_container_ogg) { drflac_oggbs* pInternalOggbs = (drflac_oggbs*)((drflac_uint8*)pFlac->pDecodedSamples + decodedSamplesAllocationSize + seektableSize); - *pInternalOggbs = oggbs; + DRFLAC_COPY_MEMORY(pInternalOggbs, &oggbs, sizeof(oggbs)); pFlac->bs.onRead = drflac__on_read_ogg; pFlac->bs.onSeek = drflac__on_seek_ogg; pFlac->bs.pUserData = (void*)pInternalOggbs; @@ -65798,7 +87084,7 @@ DRFLAC_API const char* drflac_next_vorbis_comment(drflac_vorbis_comment_iterator if (pIter == NULL || pIter->countRemaining == 0 || pIter->pRunningData == NULL) { return NULL; } - length = drflac__le2host_32(*(const drflac_uint32*)pIter->pRunningData); + length = drflac__le2host_32_ptr_unaligned(pIter->pRunningData); pIter->pRunningData += 4; pComment = pIter->pRunningData; pIter->pRunningData += length; @@ -67301,7 +88587,11 @@ static void drmp3d_synth(float *xl, drmp3d_sample_t *dstl, int nch, float *lins) vst1_lane_s16(dstl + (49 + i)*nch, pcmb, 2); #endif #else + #if DRMP3_HAVE_SSE static const drmp3_f4 g_scale = { 1.0f/32768.0f, 1.0f/32768.0f, 1.0f/32768.0f, 1.0f/32768.0f }; + #else + const drmp3_f4 g_scale = vdupq_n_f32(1.0f/32768.0f); + #endif a = DRMP3_VMUL(a, g_scale); b = DRMP3_VMUL(b, g_scale); #if DRMP3_HAVE_SSE @@ -67575,7 +88865,6 @@ DRMP3_API void drmp3dec_f32_to_s16(const float *in, drmp3_int16 *out, size_t num } } } -#include #if defined(SIZE_MAX) #define DRMP3_SIZE_MAX SIZE_MAX #else @@ -67621,18 +88910,6 @@ static DRMP3_INLINE drmp3_uint32 drmp3_gcf_u32(drmp3_uint32 a, drmp3_uint32 b) } return a; } -static DRMP3_INLINE double drmp3_sin(double x) -{ - return sin(x); -} -static DRMP3_INLINE double drmp3_exp(double x) -{ - return exp(x); -} -static DRMP3_INLINE double drmp3_cos(double x) -{ - return drmp3_sin((DRMP3_PI_D*0.5) - x); -} static void* drmp3__malloc_default(size_t sz, void* pUserData) { (void)pUserData; @@ -69134,1093 +90411,6 @@ DRMP3_API void drmp3_free(void* p, const drmp3_allocation_callbacks* pAllocation #endif /* miniaudio_c */ #endif /* MINIAUDIO_IMPLEMENTATION */ -/* -RELEASE NOTES - VERSION 0.10.x -============================== -Version 0.10 includes major API changes and refactoring, mostly concerned with the data conversion system. Data conversion is performed internally to convert -audio data between the format requested when initializing the `ma_device` object and the format of the internal device used by the backend. The same applies -to the `ma_decoder` object. The previous design has several design flaws and missing features which necessitated a complete redesign. - - -Changes to Data Conversion --------------------------- -The previous data conversion system used callbacks to deliver input data for conversion. This design works well in some specific situations, but in other -situations it has some major readability and maintenance issues. The decision was made to replace this with a more iterative approach where you just pass in a -pointer to the input data directly rather than dealing with a callback. - -The following are the data conversion APIs that have been removed and their replacements: - - - ma_format_converter -> ma_convert_pcm_frames_format() - - ma_channel_router -> ma_channel_converter - - ma_src -> ma_resampler - - ma_pcm_converter -> ma_data_converter - -The previous conversion APIs accepted a callback in their configs. There are no longer any callbacks to deal with. Instead you just pass the data into the -`*_process_pcm_frames()` function as a pointer to a buffer. - -The simplest aspect of data conversion is sample format conversion. To convert between two formats, just call `ma_convert_pcm_frames_format()`. Channel -conversion is also simple which you can do with `ma_channel_converter` via `ma_channel_converter_process_pcm_frames()`. - -Resampling is more complicated because the number of output frames that are processed is different to the number of input frames that are consumed. When you -call `ma_resampler_process_pcm_frames()` you need to pass in the number of input frames available for processing and the number of output frames you want to -output. Upon returning they will receive the number of input frames that were consumed and the number of output frames that were generated. - -The `ma_data_converter` API is a wrapper around format, channel and sample rate conversion and handles all of the data conversion you'll need which probably -makes it the best option if you need to do data conversion. - -In addition to changes to the API design, a few other changes have been made to the data conversion pipeline: - - - The sinc resampler has been removed. This was completely broken and never actually worked properly. - - The linear resampler now uses low-pass filtering to remove aliasing. The quality of the low-pass filter can be controlled via the resampler config with the - `lpfOrder` option, which has a maximum value of MA_MAX_FILTER_ORDER. - - Data conversion now supports s16 natively which runs through a fixed point pipeline. Previously everything needed to be converted to floating point before - processing, whereas now both s16 and f32 are natively supported. Other formats still require conversion to either s16 or f32 prior to processing, however - `ma_data_converter` will handle this for you. - - -Custom Memory Allocators ------------------------- -miniaudio has always supported macro level customization for memory allocation via MA_MALLOC, MA_REALLOC and MA_FREE, however some scenarios require more -flexibility by allowing a user data pointer to be passed to the custom allocation routines. Support for this has been added to version 0.10 via the -`ma_allocation_callbacks` structure. Anything making use of heap allocations has been updated to accept this new structure. - -The `ma_context_config` structure has been updated with a new member called `allocationCallbacks`. Leaving this set to it's defaults returned by -`ma_context_config_init()` will cause it to use MA_MALLOC, MA_REALLOC and MA_FREE. Likewise, The `ma_decoder_config` structure has been updated in the same -way, and leaving everything as-is after `ma_decoder_config_init()` will cause it to use the same defaults. - -The following APIs have been updated to take a pointer to a `ma_allocation_callbacks` object. Setting this parameter to NULL will cause it to use defaults. -Otherwise they will use the relevant callback in the structure. - - - ma_malloc() - - ma_realloc() - - ma_free() - - ma_aligned_malloc() - - ma_aligned_free() - - ma_rb_init() / ma_rb_init_ex() - - ma_pcm_rb_init() / ma_pcm_rb_init_ex() - -Note that you can continue to use MA_MALLOC, MA_REALLOC and MA_FREE as per normal. These will continue to be used by default if you do not specify custom -allocation callbacks. - - -Buffer and Period Configuration Changes ---------------------------------------- -The way in which the size of the internal buffer and periods are specified in the device configuration have changed. In previous versions, the config variables -`bufferSizeInFrames` and `bufferSizeInMilliseconds` defined the size of the entire buffer, with the size of a period being the size of this variable divided by -the period count. This became confusing because people would expect the value of `bufferSizeInFrames` or `bufferSizeInMilliseconds` to independantly determine -latency, when in fact it was that value divided by the period count that determined it. These variables have been removed and replaced with new ones called -`periodSizeInFrames` and `periodSizeInMilliseconds`. - -These new configuration variables work in the same way as their predecessors in that if one is set to 0, the other will be used, but the main difference is -that you now set these to you desired latency rather than the size of the entire buffer. The benefit of this is that it's much easier and less confusing to -configure latency. - -The following unused APIs have been removed: - - ma_get_default_buffer_size_in_milliseconds() - ma_get_default_buffer_size_in_frames() - -The following macros have been removed: - - MA_BASE_BUFFER_SIZE_IN_MILLISECONDS_LOW_LATENCY - MA_BASE_BUFFER_SIZE_IN_MILLISECONDS_CONSERVATIVE - - -Other API Changes ------------------ -Other less major API changes have also been made in version 0.10. - -`ma_device_set_stop_callback()` has been removed. If you require a stop callback, you must now set it via the device config just like the data callback. - -The `ma_sine_wave` API has been replaced with a more general API called `ma_waveform`. This supports generation of different types of waveforms, including -sine, square, triangle and sawtooth. Use `ma_waveform_init()` in place of `ma_sine_wave_init()` to initialize the waveform object. This takes a configuration -object called `ma_waveform_config` which defines the properties of the waveform. Use `ma_waveform_config_init()` to initialize a `ma_waveform_config` object. -Use `ma_waveform_read_pcm_frames()` in place of `ma_sine_wave_read_f32()` and `ma_sine_wave_read_f32_ex()`. - -`ma_convert_frames()` and `ma_convert_frames_ex()` have been changed. Both of these functions now take a new parameter called `frameCountOut` which specifies -the size of the output buffer in PCM frames. This has been added for safety. In addition to this, the parameters for `ma_convert_frames_ex()` have changed to -take a pointer to a `ma_data_converter_config` object to specify the input and output formats to convert between. This was done to make it more flexible, to -prevent the parameter list getting too long, and to prevent API breakage whenever a new conversion property is added. - -`ma_calculate_frame_count_after_src()` has been renamed to `ma_calculate_frame_count_after_resampling()` for consistency with the new `ma_resampler` API. - - -Filters -------- -The following filters have been added: - - |-------------|-------------------------------------------------------------------| - | API | Description | - |-------------|-------------------------------------------------------------------| - | ma_biquad | Biquad filter (transposed direct form 2) | - | ma_lpf1 | First order low-pass filter | - | ma_lpf2 | Second order low-pass filter | - | ma_lpf | High order low-pass filter (Butterworth) | - | ma_hpf1 | First order high-pass filter | - | ma_hpf2 | Second order high-pass filter | - | ma_hpf | High order high-pass filter (Butterworth) | - | ma_bpf2 | Second order band-pass filter | - | ma_bpf | High order band-pass filter | - | ma_peak2 | Second order peaking filter | - | ma_notch2 | Second order notching filter | - | ma_loshelf2 | Second order low shelf filter | - | ma_hishelf2 | Second order high shelf filter | - |-------------|-------------------------------------------------------------------| - -These filters all support 32-bit floating point and 16-bit signed integer formats natively. Other formats need to be converted beforehand. - - -Sine, Square, Triangle and Sawtooth Waveforms ---------------------------------------------- -Previously miniaudio supported only sine wave generation. This has now been generalized to support sine, square, triangle and sawtooth waveforms. The old -`ma_sine_wave` API has been removed and replaced with the `ma_waveform` API. Use `ma_waveform_config_init()` to initialize a config object, and then pass it -into `ma_waveform_init()`. Then use `ma_waveform_read_pcm_frames()` to read PCM data. - - -Noise Generation ----------------- -A noise generation API has been added. This is used via the `ma_noise` API. Currently white, pink and Brownian noise is supported. The `ma_noise` API is -similar to the waveform API. Use `ma_noise_config_init()` to initialize a config object, and then pass it into `ma_noise_init()` to initialize a `ma_noise` -object. Then use `ma_noise_read_pcm_frames()` to read PCM data. - - -Miscellaneous Changes ---------------------- -The MA_NO_STDIO option has been removed. This would disable file I/O APIs, however this has proven to be too hard to maintain for it's perceived value and was -therefore removed. - -Internal functions have all been made static where possible. If you get warnings about unused functions, please submit a bug report. - -The `ma_device` structure is no longer defined as being aligned to MA_SIMD_ALIGNMENT. This resulted in a possible crash when allocating a `ma_device` object on -the heap, but not aligning it to MA_SIMD_ALIGNMENT. This crash would happen due to the compiler seeing the alignment specified on the structure and assuming it -was always aligned as such and thinking it was safe to emit alignment-dependant SIMD instructions. Since miniaudio's philosophy is for things to just work, -this has been removed from all structures. - -Results codes have been overhauled. Unnecessary result codes have been removed, and some have been renumbered for organisation purposes. If you are are binding -maintainer you will need to update your result codes. Support has also been added for retrieving a human readable description of a given result code via the -`ma_result_description()` API. - -ALSA: The automatic format conversion, channel conversion and resampling performed by ALSA is now disabled by default as they were causing some compatibility -issues with certain devices and configurations. These can be individually enabled via the device config: - - ```c - deviceConfig.alsa.noAutoFormat = MA_TRUE; - deviceConfig.alsa.noAutoChannels = MA_TRUE; - deviceConfig.alsa.noAutoResample = MA_TRUE; - ``` -*/ - -/* -RELEASE NOTES - VERSION 0.9.x -============================= -Version 0.9 includes major API changes, centered mostly around full-duplex and the rebrand to "miniaudio". Before I go into detail about the major changes I -would like to apologize. I know it's annoying dealing with breaking API changes, but I think it's best to get these changes out of the way now while the -library is still relatively young and unknown. - -There's been a lot of refactoring with this release so there's a good chance a few bugs have been introduced. I apologize in advance for this. You may want to -hold off on upgrading for the short term if you're worried. If mini_al v0.8.14 works for you, and you don't need full-duplex support, you can avoid upgrading -(though you won't be getting future bug fixes). - - -Rebranding to "miniaudio" -------------------------- -The decision was made to rename mini_al to miniaudio. Don't worry, it's the same project. The reason for this is simple: - -1) Having the word "audio" in the title makes it immediately clear that the library is related to audio; and -2) I don't like the look of the underscore. - -This rebrand has necessitated a change in namespace from "mal" to "ma". I know this is annoying, and I apologize, but it's better to get this out of the road -now rather than later. Also, since there are necessary API changes for full-duplex support I think it's better to just get the namespace change over and done -with at the same time as the full-duplex changes. I'm hoping this will be the last of the major API changes. Fingers crossed! - -The implementation define is now "#define MINIAUDIO_IMPLEMENTATION". You can also use "#define MA_IMPLEMENTATION" if that's your preference. - - -Full-Duplex Support -------------------- -The major feature added to version 0.9 is full-duplex. This has necessitated a few API changes. - -1) The data callback has now changed. Previously there was one type of callback for playback and another for capture. I wanted to avoid a third callback just - for full-duplex so the decision was made to break this API and unify the callbacks. Now, there is just one callback which is the same for all three modes - (playback, capture, duplex). The new callback looks like the following: - - void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount); - - This callback allows you to move data straight out of the input buffer and into the output buffer in full-duplex mode. In playback-only mode, pInput will be - null. Likewise, pOutput will be null in capture-only mode. The sample count is no longer returned from the callback since it's not necessary for miniaudio - anymore. - -2) The device config needed to change in order to support full-duplex. Full-duplex requires the ability to allow the client to choose a different PCM format - for the playback and capture sides. The old ma_device_config object simply did not allow this and needed to change. With these changes you now specify the - device ID, format, channels, channel map and share mode on a per-playback and per-capture basis (see example below). The sample rate must be the same for - playback and capture. - - Since the device config API has changed I have also decided to take the opportunity to simplify device initialization. Now, the device ID, device type and - callback user data are set in the config. ma_device_init() is now simplified down to taking just the context, device config and a pointer to the device - object being initialized. The rationale for this change is that it just makes more sense to me that these are set as part of the config like everything - else. - - Example device initialization: - - ma_device_config config = ma_device_config_init(ma_device_type_duplex); // Or ma_device_type_playback or ma_device_type_capture. - config.playback.pDeviceID = &myPlaybackDeviceID; // Or NULL for the default playback device. - config.playback.format = ma_format_f32; - config.playback.channels = 2; - config.capture.pDeviceID = &myCaptureDeviceID; // Or NULL for the default capture device. - config.capture.format = ma_format_s16; - config.capture.channels = 1; - config.sampleRate = 44100; - config.dataCallback = data_callback; - config.pUserData = &myUserData; - - result = ma_device_init(&myContext, &config, &device); - if (result != MA_SUCCESS) { - ... handle error ... - } - - Note that the "onDataCallback" member of ma_device_config has been renamed to "dataCallback". Also, "onStopCallback" has been renamed to "stopCallback". - -This is the first pass for full-duplex and there is a known bug. You will hear crackling on the following backends when sample rate conversion is required for -the playback device: - - Core Audio - - JACK - - AAudio - - OpenSL - - WebAudio - -In addition to the above, not all platforms have been absolutely thoroughly tested simply because I lack the hardware for such thorough testing. If you -experience a bug, an issue report on GitHub or an email would be greatly appreciated (and a sample program that reproduces the issue if possible). - - -Other API Changes ------------------ -In addition to the above, the following API changes have been made: - -- The log callback is no longer passed to ma_context_config_init(). Instead you need to set it manually after initialization. -- The onLogCallback member of ma_context_config has been renamed to "logCallback". -- The log callback now takes a logLevel parameter. The new callback looks like: void log_callback(ma_context* pContext, ma_device* pDevice, ma_uint32 logLevel, const char* message) - - You can use ma_log_level_to_string() to convert the logLevel to human readable text if you want to log it. -- Some APIs have been renamed: - - mal_decoder_read() -> ma_decoder_read_pcm_frames() - - mal_decoder_seek_to_frame() -> ma_decoder_seek_to_pcm_frame() - - mal_sine_wave_read() -> ma_sine_wave_read_f32() - - mal_sine_wave_read_ex() -> ma_sine_wave_read_f32_ex() -- Some APIs have been removed: - - mal_device_get_buffer_size_in_bytes() - - mal_device_set_recv_callback() - - mal_device_set_send_callback() - - mal_src_set_input_sample_rate() - - mal_src_set_output_sample_rate() -- Error codes have been rearranged. If you're a binding maintainer you will need to update. -- The ma_backend enums have been rearranged to priority order. The rationale for this is to simplify automatic backend selection and to make it easier to see - the priority. If you're a binding maintainer you will need to update. -- ma_dsp has been renamed to ma_pcm_converter. The rationale for this change is that I'm expecting "ma_dsp" to conflict with some future planned high-level - APIs. -- For functions that take a pointer/count combo, such as ma_decoder_read_pcm_frames(), the parameter order has changed so that the pointer comes before the - count. The rationale for this is to keep it consistent with things like memcpy(). - - -Miscellaneous Changes ---------------------- -The following miscellaneous changes have also been made. - -- The AAudio backend has been added for Android 8 and above. This is Android's new "High-Performance Audio" API. (For the record, this is one of the nicest - audio APIs out there, just behind the BSD audio APIs). -- The WebAudio backend has been added. This is based on ScriptProcessorNode. This removes the need for SDL. -- The SDL and OpenAL backends have been removed. These were originally implemented to add support for platforms for which miniaudio was not explicitly - supported. These are no longer needed and have therefore been removed. -- Device initialization now fails if the requested share mode is not supported. If you ask for exclusive mode, you either get an exclusive mode device, or an - error. The rationale for this change is to give the client more control over how to handle cases when the desired shared mode is unavailable. -- A lock-free ring buffer API has been added. There are two varients of this. "ma_rb" operates on bytes, whereas "ma_pcm_rb" operates on PCM frames. -- The library is now licensed as a choice of Public Domain (Unlicense) _or_ MIT-0 (No Attribution) which is the same as MIT, but removes the attribution - requirement. The rationale for this is to support countries that don't recognize public domain. -*/ - -/* -REVISION HISTORY -================ -v0.10.42 - 2021-08-22 - - Fix a possible deadlock when stopping devices. - -v0.10.41 - 2021-08-15 - - Core Audio: Fix some deadlock errors. - -v0.10.40 - 2021-07-23 - - Fix a bug when converting from stereo to mono. - - PulseAudio: Fix a glitch when pausing and resuming a device. - -v0.10.39 - 2021-07-20 - - Core Audio: Fix a deadlock when the default device is changed. - - Core Audio: Fix compilation errors on macOS and iOS. - - PulseAudio: Fix a bug where the stop callback is not fired when a device is unplugged. - - PulseAudio: Fix a null pointer dereference. - -v0.10.38 - 2021-07-14 - - Fix a linking error when MA_DEBUG_OUTPUT is not enabled. - - Fix an error where ma_log_postv() does not return a value. - - OpenSL: Fix a bug with setting of stream types and recording presets. - -0.10.37 - 2021-07-06 - - Fix a bug with log message formatting. - - Fix build when compiling with MA_NO_THREADING. - - Minor updates to channel mapping. - -0.10.36 - 2021-07-03 - - Add support for custom decoding backends. - - Fix some bugs with the Vorbis decoder. - - PulseAudio: Fix a bug with channel mapping. - - PulseAudio: Fix a bug where miniaudio does not fall back to a supported format when PulseAudio - defaults to a format not known to miniaudio. - - OpenSL: Fix a crash when initializing a capture device when a recording preset other than the - default is specified. - - Silence some warnings when compiling with MA_DEBUG_OUTPUT - - Improvements to logging. See the `ma_log` API for details. The logCallback variable used by - ma_context has been deprecated and will be replaced with the new system in version 0.11. - - Initialize an `ma_log` object with `ma_log_init()`. - - Register a callback with `ma_log_register_callback()`. - - In the context config, set `pLog` to your `ma_log` object and stop using `logCallback`. - - Prep work for some upcoming changes to data sources. These changes are still compatible with - existing code, however code will need to be updated in preparation for version 0.11 which will - be breaking. You should make these changes now for any custom data sources: - - Change your base data source object from `ma_data_source_callbacks` to `ma_data_source_base`. - - Call `ma_data_source_init()` for your base object in your custom data source's initialization - routine. This takes a config object which includes a pointer to a vtable which is now where - your custom callbacks are defined. - - Call `ma_data_source_uninit()` in your custom data source's uninitialization routine. This - doesn't currently do anything, but it placeholder in case some future uninitialization code - is required to be added at a later date. - -v0.10.35 - 2021-04-27 - - Fix the C++ build. - -v0.10.34 - 2021-04-26 - - WASAPI: Fix a bug where a result code is not getting checked at initialization time. - - WASAPI: Bug fixes for loopback mode. - - ALSA: Fix a possible deadlock when stopping devices. - - Mark devices as default on the null backend. - -v0.10.33 - 2021-04-04 - - Core Audio: Fix a memory leak. - - Core Audio: Fix a bug where the performance profile is not being used by playback devices. - - JACK: Fix loading of 64-bit JACK on Windows. - - Fix a calculation error and add a safety check to the following APIs to prevent a division by zero: - - ma_calculate_buffer_size_in_milliseconds_from_frames() - - ma_calculate_buffer_size_in_frames_from_milliseconds() - - Fix compilation errors relating to c89atomic. - - Update FLAC decoder. - -v0.10.32 - 2021-02-23 - - WASAPI: Fix a deadlock in exclusive mode. - - WASAPI: No longer return an error from ma_context_get_device_info() when an exclusive mode format - cannot be retrieved. - - WASAPI: Attempt to fix some bugs with device uninitialization. - - PulseAudio: Yet another refactor, this time to remove the dependency on `pa_threaded_mainloop`. - - Web Audio: Fix a bug on Chrome and any other browser using the same engine. - - Web Audio: Automatically start the device on some user input if the device has been started. This - is to work around Google's policy of not starting audio if no user input has yet been performed. - - Fix a bug where thread handles are not being freed. - - Fix some static analysis warnings in FLAC, WAV and MP3 decoders. - - Fix a warning due to referencing _MSC_VER when it is undefined. - - Update to latest version of c89atomic. - - Internal refactoring to migrate over to the new backend callback system for the following backends: - - PulseAudio - - ALSA - - Core Audio - - AAudio - - OpenSL|ES - - OSS - - audio(4) - - sndio - -v0.10.31 - 2021-01-17 - - Make some functions const correct. - - Update ma_data_source_read_pcm_frames() to initialize pFramesRead to 0 for safety. - - Add the MA_ATOMIC annotation for use with variables that should be used atomically and remove unnecessary volatile qualifiers. - - Add support for enabling only specific backends at compile time. This is the reverse of the pre-existing system. With the new - system, all backends are first disabled with `MA_ENABLE_ONLY_SPECIFIC_BACKENDS`, which is then followed with `MA_ENABLE_*`. The - old system where you disable backends with `MA_NO_*` still exists and is still the default. - -v0.10.30 - 2021-01-10 - - Fix a crash in ma_audio_buffer_read_pcm_frames(). - - Update spinlock APIs to take a volatile parameter as input. - - Silence some unused parameter warnings. - - Fix a warning on GCC when compiling as C++. - -v0.10.29 - 2020-12-26 - - Fix some subtle multi-threading bugs on non-x86 platforms. - - Fix a bug resulting in superfluous memory allocations when enumerating devices. - - Core Audio: Fix a compilation error when compiling for iOS. - -v0.10.28 - 2020-12-16 - - Fix a crash when initializing a POSIX thread. - - OpenSL|ES: Respect the MA_NO_RUNTIME_LINKING option. - -v0.10.27 - 2020-12-04 - - Add support for dynamically configuring some properties of `ma_noise` objects post-initialization. - - Add support for configuring the channel mixing mode in the device config. - - Fix a bug with simple channel mixing mode (drop or silence excess channels). - - Fix some bugs with trying to access uninitialized variables. - - Fix some errors with stopping devices for synchronous backends where the backend's stop callback would get fired twice. - - Fix a bug in the decoder due to using an uninitialized variable. - - Fix some data race errors. - -v0.10.26 - 2020-11-24 - - WASAPI: Fix a bug where the exclusive mode format may not be retrieved correctly due to accessing freed memory. - - Fix a bug with ma_waveform where glitching occurs after changing frequency. - - Fix compilation with OpenWatcom. - - Fix compilation with TCC. - - Fix compilation with Digital Mars. - - Fix compilation warnings. - - Remove bitfields from public structures to aid in binding maintenance. - -v0.10.25 - 2020-11-15 - - PulseAudio: Fix a bug where the stop callback isn't fired. - - WebAudio: Fix an error that occurs when Emscripten increases the size of it's heap. - - Custom Backends: Change the onContextInit and onDeviceInit callbacks to take a parameter which is a pointer to the config that was - passed into ma_context_init() and ma_device_init(). This replaces the deviceType parameter of onDeviceInit. - - Fix compilation warnings on older versions of GCC. - -v0.10.24 - 2020-11-10 - - Fix a bug where initialization of a backend can fail due to some bad state being set from a prior failed attempt at initializing a - lower priority backend. - -v0.10.23 - 2020-11-09 - - AAudio: Add support for configuring a playback stream's usage. - - Fix a compilation error when all built-in asynchronous backends are disabled at compile time. - - Fix compilation errors when compiling as C++. - -v0.10.22 - 2020-11-08 - - Add support for custom backends. - - Add support for detecting default devices during device enumeration and with `ma_context_get_device_info()`. - - Refactor to the PulseAudio backend. This simplifies the implementation and fixes a capture bug. - - ALSA: Fix a bug in `ma_context_get_device_info()` where the PCM handle is left open in the event of an error. - - Core Audio: Further improvements to sample rate selection. - - Core Audio: Fix some bugs with capture mode. - - OpenSL: Add support for configuring stream types and recording presets. - - AAudio: Add support for configuring content types and input presets. - - Fix bugs in `ma_decoder_init_file*()` where the file handle is not closed after a decoding error. - - Fix some compilation warnings on GCC and Clang relating to the Speex resampler. - - Fix a compilation error for the Linux build when the ALSA and JACK backends are both disabled. - - Fix a compilation error for the BSD build. - - Fix some compilation errors on older versions of GCC. - - Add documentation for `MA_NO_RUNTIME_LINKING`. - -v0.10.21 - 2020-10-30 - - Add ma_is_backend_enabled() and ma_get_enabled_backends() for retrieving enabled backends at run-time. - - WASAPI: Fix a copy and paste bug relating to loopback mode. - - Core Audio: Fix a bug when using multiple contexts. - - Core Audio: Fix a compilation warning. - - Core Audio: Improvements to sample rate selection. - - Core Audio: Improvements to format/channels/rate selection when requesting defaults. - - Core Audio: Add notes regarding the Apple notarization process. - - Fix some bugs due to null pointer dereferences. - -v0.10.20 - 2020-10-06 - - Fix build errors with UWP. - - Minor documentation updates. - -v0.10.19 - 2020-09-22 - - WASAPI: Return an error when exclusive mode is requested, but the native format is not supported by miniaudio. - - Fix a bug where ma_decoder_seek_to_pcm_frames() never returns MA_SUCCESS even though it was successful. - - Store the sample rate in the `ma_lpf` and `ma_hpf` structures. - -v0.10.18 - 2020-08-30 - - Fix build errors with VC6. - - Fix a bug in channel converter for s32 format. - - Change channel converter configs to use the default channel map instead of a blank channel map when no channel map is specified when initializing the - config. This fixes an issue where the optimized mono expansion path would never get used. - - Use a more appropriate default format for FLAC decoders. This will now use ma_format_s16 when the FLAC is encoded as 16-bit. - - Update FLAC decoder. - - Update links to point to the new repository location (https://github.com/mackron/miniaudio). - -v0.10.17 - 2020-08-28 - - Fix an error where the WAV codec is incorrectly excluded from the build depending on which compile time options are set. - - Fix a bug in ma_audio_buffer_read_pcm_frames() where it isn't returning the correct number of frames processed. - - Fix compilation error on Android. - - Core Audio: Fix a bug with full-duplex mode. - - Add ma_decoder_get_cursor_in_pcm_frames(). - - Update WAV codec. - -v0.10.16 - 2020-08-14 - - WASAPI: Fix a potential crash due to using an uninitialized variable. - - OpenSL: Enable runtime linking. - - OpenSL: Fix a multithreading bug when initializing and uninitializing multiple contexts at the same time. - - iOS: Improvements to device enumeration. - - Fix a crash in ma_data_source_read_pcm_frames() when the output frame count parameter is NULL. - - Fix a bug in ma_data_source_read_pcm_frames() where looping doesn't work. - - Fix some compilation warnings on Windows when both DirectSound and WinMM are disabled. - - Fix some compilation warnings when no decoders are enabled. - - Add ma_audio_buffer_get_available_frames(). - - Add ma_decoder_get_available_frames(). - - Add sample rate to ma_data_source_get_data_format(). - - Change volume APIs to take 64-bit frame counts. - - Updates to documentation. - -v0.10.15 - 2020-07-15 - - Fix a bug when converting bit-masked channel maps to miniaudio channel maps. This affects the WASAPI and OpenSL backends. - -v0.10.14 - 2020-07-14 - - Fix compilation errors on Android. - - Fix compilation errors with -march=armv6. - - Updates to the documentation. - -v0.10.13 - 2020-07-11 - - Fix some potential buffer overflow errors with channel maps when channel counts are greater than MA_MAX_CHANNELS. - - Fix compilation error on Emscripten. - - Silence some unused function warnings. - - Increase the default buffer size on the Web Audio backend. This fixes glitching issues on some browsers. - - Bring FLAC decoder up-to-date with dr_flac. - - Bring MP3 decoder up-to-date with dr_mp3. - -v0.10.12 - 2020-07-04 - - Fix compilation errors on the iOS build. - -v0.10.11 - 2020-06-28 - - Fix some bugs with device tracking on Core Audio. - - Updates to documentation. - -v0.10.10 - 2020-06-26 - - Add include guard for the implementation section. - - Mark ma_device_sink_info_callback() as static. - - Fix compilation errors with MA_NO_DECODING and MA_NO_ENCODING. - - Fix compilation errors with MA_NO_DEVICE_IO - -v0.10.9 - 2020-06-24 - - Amalgamation of dr_wav, dr_flac and dr_mp3. With this change, including the header section of these libraries before the implementation of miniaudio is no - longer required. Decoding of WAV, FLAC and MP3 should be supported seamlessly without any additional libraries. Decoders can be excluded from the build - with the following options: - - MA_NO_WAV - - MA_NO_FLAC - - MA_NO_MP3 - If you get errors about multiple definitions you need to either enable the options above, move the implementation of dr_wav, dr_flac and/or dr_mp3 to before - the implementation of miniaudio, or update dr_wav, dr_flac and/or dr_mp3. - - Changes to the internal atomics library. This has been replaced with c89atomic.h which is embedded within this file. - - Fix a bug when a decoding backend reports configurations outside the limits of miniaudio's decoder abstraction. - - Fix the UWP build. - - Fix the Core Audio build. - - Fix the -std=c89 build on GCC. - -v0.10.8 - 2020-06-22 - - Remove dependency on ma_context from mutexes. - - Change ma_data_source_read_pcm_frames() to return a result code and output the frames read as an output parameter. - - Change ma_data_source_seek_pcm_frames() to return a result code and output the frames seeked as an output parameter. - - Change ma_audio_buffer_unmap() to return MA_AT_END when the end has been reached. This should be considered successful. - - Change playback.pDeviceID and capture.pDeviceID to constant pointers in ma_device_config. - - Add support for initializing decoders from a virtual file system object. This is achieved via the ma_vfs API and allows the application to customize file - IO for the loading and reading of raw audio data. Passing in NULL for the VFS will use defaults. New APIs: - - ma_decoder_init_vfs() - - ma_decoder_init_vfs_wav() - - ma_decoder_init_vfs_flac() - - ma_decoder_init_vfs_mp3() - - ma_decoder_init_vfs_vorbis() - - ma_decoder_init_vfs_w() - - ma_decoder_init_vfs_wav_w() - - ma_decoder_init_vfs_flac_w() - - ma_decoder_init_vfs_mp3_w() - - ma_decoder_init_vfs_vorbis_w() - - Add support for memory mapping to ma_data_source. - - ma_data_source_map() - - ma_data_source_unmap() - - Add ma_offset_pcm_frames_ptr() and ma_offset_pcm_frames_const_ptr() which can be used for offsetting a pointer by a specified number of PCM frames. - - Add initial implementation of ma_yield() which is useful for spin locks which will be used in some upcoming work. - - Add documentation for log levels. - - The ma_event API has been made public in preparation for some uncoming work. - - Fix a bug in ma_decoder_seek_to_pcm_frame() where the internal sample rate is not being taken into account for determining the seek location. - - Fix some bugs with the linear resampler when dynamically changing the sample rate. - - Fix compilation errors with MA_NO_DEVICE_IO. - - Fix some warnings with GCC and -std=c89. - - Fix some formatting warnings with GCC and -Wall and -Wpedantic. - - Fix some warnings with VC6. - - Minor optimization to ma_copy_pcm_frames(). This is now a no-op when the input and output buffers are the same. - -v0.10.7 - 2020-05-25 - - Fix a compilation error in the C++ build. - - Silence a warning. - -v0.10.6 - 2020-05-24 - - Change ma_clip_samples_f32() and ma_clip_pcm_frames_f32() to take a 64-bit sample/frame count. - - Change ma_zero_pcm_frames() to clear to 128 for ma_format_u8. - - Add ma_silence_pcm_frames() which replaces ma_zero_pcm_frames(). ma_zero_pcm_frames() will be removed in version 0.11. - - Add support for u8, s24 and s32 formats to ma_channel_converter. - - Add compile-time and run-time version querying. - - MA_VERSION_MINOR - - MA_VERSION_MAJOR - - MA_VERSION_REVISION - - MA_VERSION_STRING - - ma_version() - - ma_version_string() - - Add ma_audio_buffer for reading raw audio data directly from memory. - - Fix a bug in shuffle mode in ma_channel_converter. - - Fix compilation errors in certain configurations for ALSA and PulseAudio. - - The data callback now initializes the output buffer to 128 when the playback sample format is ma_format_u8. - -v0.10.5 - 2020-05-05 - - Change ma_zero_pcm_frames() to take a 64-bit frame count. - - Add ma_copy_pcm_frames(). - - Add MA_NO_GENERATION build option to exclude the `ma_waveform` and `ma_noise` APIs from the build. - - Add support for formatted logging to the VC6 build. - - Fix a crash in the linear resampler when LPF order is 0. - - Fix compilation errors and warnings with older versions of Visual Studio. - - Minor documentation updates. - -v0.10.4 - 2020-04-12 - - Fix a data conversion bug when converting from the client format to the native device format. - -v0.10.3 - 2020-04-07 - - Bring up to date with breaking changes to dr_mp3. - - Remove MA_NO_STDIO. This was causing compilation errors and the maintenance cost versus practical benefit is no longer worthwhile. - - Fix a bug with data conversion where it was unnecessarily converting to s16 or f32 and then straight back to the original format. - - Fix compilation errors and warnings with Visual Studio 2005. - - ALSA: Disable ALSA's automatic data conversion by default and add configuration options to the device config: - - alsa.noAutoFormat - - alsa.noAutoChannels - - alsa.noAutoResample - - WASAPI: Add some overrun recovery for ma_device_type_capture devices. - -v0.10.2 - 2020-03-22 - - Decorate some APIs with MA_API which were missed in the previous version. - - Fix a bug in ma_linear_resampler_set_rate() and ma_linear_resampler_set_rate_ratio(). - -v0.10.1 - 2020-03-17 - - Add MA_API decoration. This can be customized by defining it before including miniaudio.h. - - Fix a bug where opening a file would return a success code when in fact it failed. - - Fix compilation errors with Visual Studio 6 and 2003. - - Fix warnings on macOS. - -v0.10.0 - 2020-03-07 - - API CHANGE: Refactor data conversion APIs - - ma_format_converter has been removed. Use ma_convert_pcm_frames_format() instead. - - ma_channel_router has been replaced with ma_channel_converter. - - ma_src has been replaced with ma_resampler - - ma_pcm_converter has been replaced with ma_data_converter - - API CHANGE: Add support for custom memory allocation callbacks. The following APIs have been updated to take an extra parameter for the allocation - callbacks: - - ma_malloc() - - ma_realloc() - - ma_free() - - ma_aligned_malloc() - - ma_aligned_free() - - ma_rb_init() / ma_rb_init_ex() - - ma_pcm_rb_init() / ma_pcm_rb_init_ex() - - API CHANGE: Simplify latency specification in device configurations. The bufferSizeInFrames and bufferSizeInMilliseconds parameters have been replaced with - periodSizeInFrames and periodSizeInMilliseconds respectively. The previous variables defined the size of the entire buffer, whereas the new ones define the - size of a period. The following APIs have been removed since they are no longer relevant: - - ma_get_default_buffer_size_in_milliseconds() - - ma_get_default_buffer_size_in_frames() - - API CHANGE: ma_device_set_stop_callback() has been removed. If you require a stop callback, you must now set it via the device config just like the data - callback. - - API CHANGE: The ma_sine_wave API has been replaced with ma_waveform. The following APIs have been removed: - - ma_sine_wave_init() - - ma_sine_wave_read_f32() - - ma_sine_wave_read_f32_ex() - - API CHANGE: ma_convert_frames() has been updated to take an extra parameter which is the size of the output buffer in PCM frames. Parameters have also been - reordered. - - API CHANGE: ma_convert_frames_ex() has been changed to take a pointer to a ma_data_converter_config object to specify the input and output formats to - convert between. - - API CHANGE: ma_calculate_frame_count_after_src() has been renamed to ma_calculate_frame_count_after_resampling(). - - Add support for the following filters: - - Biquad (ma_biquad) - - First order low-pass (ma_lpf1) - - Second order low-pass (ma_lpf2) - - Low-pass with configurable order (ma_lpf) - - First order high-pass (ma_hpf1) - - Second order high-pass (ma_hpf2) - - High-pass with configurable order (ma_hpf) - - Second order band-pass (ma_bpf2) - - Band-pass with configurable order (ma_bpf) - - Second order peaking EQ (ma_peak2) - - Second order notching (ma_notch2) - - Second order low shelf (ma_loshelf2) - - Second order high shelf (ma_hishelf2) - - Add waveform generation API (ma_waveform) with support for the following: - - Sine - - Square - - Triangle - - Sawtooth - - Add noise generation API (ma_noise) with support for the following: - - White - - Pink - - Brownian - - Add encoding API (ma_encoder). This only supports outputting to WAV files via dr_wav. - - Add ma_result_description() which is used to retrieve a human readable description of a given result code. - - Result codes have been changed. Binding maintainers will need to update their result code constants. - - More meaningful result codes are now returned when a file fails to open. - - Internal functions have all been made static where possible. - - Fix potential crash when ma_device object's are not aligned to MA_SIMD_ALIGNMENT. - - Fix a bug in ma_decoder_get_length_in_pcm_frames() where it was returning the length based on the internal sample rate rather than the output sample rate. - - Fix bugs in some backends where the device is not drained properly in ma_device_stop(). - - Improvements to documentation. - -v0.9.10 - 2020-01-15 - - Fix compilation errors due to #if/#endif mismatches. - - WASAPI: Fix a bug where automatic stream routing is being performed for devices that are initialized with an explicit device ID. - - iOS: Fix a crash on device uninitialization. - -v0.9.9 - 2020-01-09 - - Fix compilation errors with MinGW. - - Fix compilation errors when compiling on Apple platforms. - - WASAPI: Add support for disabling hardware offloading. - - WASAPI: Add support for disabling automatic stream routing. - - Core Audio: Fix bugs in the case where the internal device uses deinterleaved buffers. - - Core Audio: Add support for controlling the session category (AVAudioSessionCategory) and options (AVAudioSessionCategoryOptions). - - JACK: Fix bug where incorrect ports are connected. - -v0.9.8 - 2019-10-07 - - WASAPI: Fix a potential deadlock when starting a full-duplex device. - - WASAPI: Enable automatic resampling by default. Disable with config.wasapi.noAutoConvertSRC. - - Core Audio: Fix bugs with automatic stream routing. - - Add support for controlling whether or not the content of the output buffer passed in to the data callback is pre-initialized - to zero. By default it will be initialized to zero, but this can be changed by setting noPreZeroedOutputBuffer in the device - config. Setting noPreZeroedOutputBuffer to true will leave the contents undefined. - - Add support for clipping samples after the data callback has returned. This only applies when the playback sample format is - configured as ma_format_f32. If you are doing clipping yourself, you can disable this overhead by setting noClip to true in - the device config. - - Add support for master volume control for devices. - - Use ma_device_set_master_volume() to set the volume to a factor between 0 and 1, where 0 is silence and 1 is full volume. - - Use ma_device_set_master_gain_db() to set the volume in decibels where 0 is full volume and < 0 reduces the volume. - - Fix warnings emitted by GCC when `__inline__` is undefined or defined as nothing. - -v0.9.7 - 2019-08-28 - - Add support for loopback mode (WASAPI only). - - To use this, set the device type to ma_device_type_loopback, and then fill out the capture section of the device config. - - If you need to capture from a specific output device, set the capture device ID to that of a playback device. - - Fix a crash when an error is posted in ma_device_init(). - - Fix a compilation error when compiling for ARM architectures. - - Fix a bug with the audio(4) backend where the device is incorrectly being opened in non-blocking mode. - - Fix memory leaks in the Core Audio backend. - - Minor refactoring to the WinMM, ALSA, PulseAudio, OSS, audio(4), sndio and null backends. - -v0.9.6 - 2019-08-04 - - Add support for loading decoders using a wchar_t string for file paths. - - Don't trigger an assert when ma_device_start() is called on a device that is already started. This will now log a warning - and return MA_INVALID_OPERATION. The same applies for ma_device_stop(). - - Try fixing an issue with PulseAudio taking a long time to start playback. - - Fix a bug in ma_convert_frames() and ma_convert_frames_ex(). - - Fix memory leaks in the WASAPI backend. - - Fix a compilation error with Visual Studio 2010. - -v0.9.5 - 2019-05-21 - - Add logging to ma_dlopen() and ma_dlsym(). - - Add ma_decoder_get_length_in_pcm_frames(). - - Fix a bug with capture on the OpenSL|ES backend. - - Fix a bug with the ALSA backend where a device would not restart after being stopped. - -v0.9.4 - 2019-05-06 - - Add support for C89. With this change, miniaudio should compile clean with GCC/Clang with "-std=c89 -ansi -pedantic" and - Microsoft compilers back to VC6. Other compilers should also work, but have not been tested. - -v0.9.3 - 2019-04-19 - - Fix compiler errors on GCC when compiling with -std=c99. - -v0.9.2 - 2019-04-08 - - Add support for per-context user data. - - Fix a potential bug with context configs. - - Fix some bugs with PulseAudio. - -v0.9.1 - 2019-03-17 - - Fix a bug where the output buffer is not getting zeroed out before calling the data callback. This happens when - the device is running in passthrough mode (not doing any data conversion). - - Fix an issue where the data callback is getting called too frequently on the WASAPI and DirectSound backends. - - Fix error on the UWP build. - - Fix a build error on Apple platforms. - -v0.9 - 2019-03-06 - - Rebranded to "miniaudio". All namespaces have been renamed from "mal" to "ma". - - API CHANGE: ma_device_init() and ma_device_config_init() have changed significantly: - - The device type, device ID and user data pointer have moved from ma_device_init() to the config. - - All variations of ma_device_config_init_*() have been removed in favor of just ma_device_config_init(). - - ma_device_config_init() now takes only one parameter which is the device type. All other properties need - to be set on the returned object directly. - - The onDataCallback and onStopCallback members of ma_device_config have been renamed to "dataCallback" - and "stopCallback". - - The ID of the physical device is now split into two: one for the playback device and the other for the - capture device. This is required for full-duplex. These are named "pPlaybackDeviceID" and "pCaptureDeviceID". - - API CHANGE: The data callback has changed. It now uses a unified callback for all device types rather than - being separate for each. It now takes two pointers - one containing input data and the other output data. This - design in required for full-duplex. The return value is now void instead of the number of frames written. The - new callback looks like the following: - void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount); - - API CHANGE: Remove the log callback parameter from ma_context_config_init(). With this change, - ma_context_config_init() now takes no parameters and the log callback is set via the structure directly. The - new policy for config initialization is that only mandatory settings are passed in to *_config_init(). The - "onLog" member of ma_context_config has been renamed to "logCallback". - - API CHANGE: Remove ma_device_get_buffer_size_in_bytes(). - - API CHANGE: Rename decoding APIs to "pcm_frames" convention. - - mal_decoder_read() -> ma_decoder_read_pcm_frames() - - mal_decoder_seek_to_frame() -> ma_decoder_seek_to_pcm_frame() - - API CHANGE: Rename sine wave reading APIs to f32 convention. - - mal_sine_wave_read() -> ma_sine_wave_read_f32() - - mal_sine_wave_read_ex() -> ma_sine_wave_read_f32_ex() - - API CHANGE: Remove some deprecated APIs - - mal_device_set_recv_callback() - - mal_device_set_send_callback() - - mal_src_set_input_sample_rate() - - mal_src_set_output_sample_rate() - - API CHANGE: Add log level to the log callback. New signature: - - void on_log(ma_context* pContext, ma_device* pDevice, ma_uint32 logLevel, const char* message) - - API CHANGE: Changes to result codes. Constants have changed and unused codes have been removed. If you're - a binding mainainer you will need to update your result code constants. - - API CHANGE: Change the order of the ma_backend enums to priority order. If you are a binding maintainer, you - will need to update. - - API CHANGE: Rename mal_dsp to ma_pcm_converter. All functions have been renamed from mal_dsp_*() to - ma_pcm_converter_*(). All structures have been renamed from mal_dsp* to ma_pcm_converter*. - - API CHANGE: Reorder parameters of ma_decoder_read_pcm_frames() to be consistent with the new parameter order scheme. - - The resampling algorithm has been changed from sinc to linear. The rationale for this is that the sinc implementation - is too inefficient right now. This will hopefully be improved at a later date. - - Device initialization will no longer fall back to shared mode if exclusive mode is requested but is unusable. - With this change, if you request an device in exclusive mode, but exclusive mode is not supported, it will not - automatically fall back to shared mode. The client will need to reinitialize the device in shared mode if that's - what they want. - - Add ring buffer API. This is ma_rb and ma_pcm_rb, the difference being that ma_rb operates on bytes and - ma_pcm_rb operates on PCM frames. - - Add Web Audio backend. This is used when compiling with Emscripten. The SDL backend, which was previously - used for web support, will be removed in a future version. - - Add AAudio backend (Android Audio). This is the new priority backend for Android. Support for AAudio starts - with Android 8. OpenSL|ES is used as a fallback for older versions of Android. - - Remove OpenAL and SDL backends. - - Fix a possible deadlock when rapidly stopping the device after it has started. - - Update documentation. - - Change licensing to a choice of public domain _or_ MIT-0 (No Attribution). - -v0.8.14 - 2018-12-16 - - Core Audio: Fix a bug where the device state is not set correctly after stopping. - - Add support for custom weights to the channel router. - - Update decoders to use updated APIs in dr_flac, dr_mp3 and dr_wav. - -v0.8.13 - 2018-12-04 - - Core Audio: Fix a bug with channel mapping. - - Fix a bug with channel routing where the back/left and back/right channels have the wrong weight. - -v0.8.12 - 2018-11-27 - - Drop support for SDL 1.2. The Emscripten build now requires "-s USE_SDL=2". - - Fix a linking error with ALSA. - - Fix a bug on iOS where the device name is not set correctly. - -v0.8.11 - 2018-11-21 - - iOS bug fixes. - - Minor tweaks to PulseAudio. - -v0.8.10 - 2018-10-21 - - Core Audio: Fix a hang when uninitializing a device. - - Fix a bug where an incorrect value is returned from mal_device_stop(). - -v0.8.9 - 2018-09-28 - - Fix a bug with the SDL backend where device initialization fails. - -v0.8.8 - 2018-09-14 - - Fix Linux build with the ALSA backend. - - Minor documentation fix. - -v0.8.7 - 2018-09-12 - - Fix a bug with UWP detection. - -v0.8.6 - 2018-08-26 - - Automatically switch the internal device when the default device is unplugged. Note that this is still in the - early stages and not all backends handle this the same way. As of this version, this will not detect a default - device switch when changed from the operating system's audio preferences (unless the backend itself handles - this automatically). This is not supported in exclusive mode. - - WASAPI and Core Audio: Add support for stream routing. When the application is using a default device and the - user switches the default device via the operating system's audio preferences, miniaudio will automatically switch - the internal device to the new default. This is not supported in exclusive mode. - - WASAPI: Add support for hardware offloading via IAudioClient2. Only supported on Windows 8 and newer. - - WASAPI: Add support for low-latency shared mode via IAudioClient3. Only supported on Windows 10 and newer. - - Add support for compiling the UWP build as C. - - mal_device_set_recv_callback() and mal_device_set_send_callback() have been deprecated. You must now set this - when the device is initialized with mal_device_init*(). These will be removed in version 0.9.0. - -v0.8.5 - 2018-08-12 - - Add support for specifying the size of a device's buffer in milliseconds. You can still set the buffer size in - frames if that suits you. When bufferSizeInFrames is 0, bufferSizeInMilliseconds will be used. If both are non-0 - then bufferSizeInFrames will take priority. If both are set to 0 the default buffer size is used. - - Add support for the audio(4) backend to OpenBSD. - - Fix a bug with the ALSA backend that was causing problems on Raspberry Pi. This significantly improves the - Raspberry Pi experience. - - Fix a bug where an incorrect number of samples is returned from sinc resampling. - - Add support for setting the value to be passed to internal calls to CoInitializeEx(). - - WASAPI and WinMM: Stop the device when it is unplugged. - -v0.8.4 - 2018-08-06 - - Add sndio backend for OpenBSD. - - Add audio(4) backend for NetBSD. - - Drop support for the OSS backend on everything except FreeBSD and DragonFly BSD. - - Formats are now native-endian (were previously little-endian). - - Mark some APIs as deprecated: - - mal_src_set_input_sample_rate() and mal_src_set_output_sample_rate() are replaced with mal_src_set_sample_rate(). - - mal_dsp_set_input_sample_rate() and mal_dsp_set_output_sample_rate() are replaced with mal_dsp_set_sample_rate(). - - Fix a bug when capturing using the WASAPI backend. - - Fix some aliasing issues with resampling, specifically when increasing the sample rate. - - Fix warnings. - -v0.8.3 - 2018-07-15 - - Fix a crackling bug when resampling in capture mode. - - Core Audio: Fix a bug where capture does not work. - - ALSA: Fix a bug where the worker thread can get stuck in an infinite loop. - - PulseAudio: Fix a bug where mal_context_init() succeeds when PulseAudio is unusable. - - JACK: Fix a bug where mal_context_init() succeeds when JACK is unusable. - -v0.8.2 - 2018-07-07 - - Fix a bug on macOS with Core Audio where the internal callback is not called. - -v0.8.1 - 2018-07-06 - - Fix compilation errors and warnings. - -v0.8 - 2018-07-05 - - Changed MAL_IMPLEMENTATION to MINI_AL_IMPLEMENTATION for consistency with other libraries. The old - way is still supported for now, but you should update as it may be removed in the future. - - API CHANGE: Replace device enumeration APIs. mal_enumerate_devices() has been replaced with - mal_context_get_devices(). An additional low-level device enumration API has been introduced called - mal_context_enumerate_devices() which uses a callback to report devices. - - API CHANGE: Rename mal_get_sample_size_in_bytes() to mal_get_bytes_per_sample() and add - mal_get_bytes_per_frame(). - - API CHANGE: Replace mal_device_config.preferExclusiveMode with mal_device_config.shareMode. - - This new config can be set to mal_share_mode_shared (default) or mal_share_mode_exclusive. - - API CHANGE: Remove excludeNullDevice from mal_context_config.alsa. - - API CHANGE: Rename MAL_MAX_SAMPLE_SIZE_IN_BYTES to MAL_MAX_PCM_SAMPLE_SIZE_IN_BYTES. - - API CHANGE: Change the default channel mapping to the standard Microsoft mapping. - - API CHANGE: Remove backend-specific result codes. - - API CHANGE: Changes to the format conversion APIs (mal_pcm_f32_to_s16(), etc.) - - Add support for Core Audio (Apple). - - Add support for PulseAudio. - - This is the highest priority backend on Linux (higher priority than ALSA) since it is commonly - installed by default on many of the popular distros and offer's more seamless integration on - platforms where PulseAudio is used. In addition, if PulseAudio is installed and running (which - is extremely common), it's better to just use PulseAudio directly rather than going through the - "pulse" ALSA plugin (which is what the "default" ALSA device is likely set to). - - Add support for JACK. - - Remove dependency on asound.h for the ALSA backend. This means the ALSA development packages are no - longer required to build miniaudio. - - Remove dependency on dsound.h for the DirectSound backend. This fixes build issues with some - distributions of MinGW. - - Remove dependency on audioclient.h for the WASAPI backend. This fixes build issues with some - distributions of MinGW. - - Add support for dithering to format conversion. - - Add support for configuring the priority of the worker thread. - - Add a sine wave generator. - - Improve efficiency of sample rate conversion. - - Introduce the notion of standard channel maps. Use mal_get_standard_channel_map(). - - Introduce the notion of default device configurations. A default config uses the same configuration - as the backend's internal device, and as such results in a pass-through data transmission pipeline. - - Add support for passing in NULL for the device config in mal_device_init(), which uses a default - config. This requires manually calling mal_device_set_send/recv_callback(). - - Add support for decoding from raw PCM data (mal_decoder_init_raw(), etc.) - - Make mal_device_init_ex() more robust. - - Make some APIs more const-correct. - - Fix errors with SDL detection on Apple platforms. - - Fix errors with OpenAL detection. - - Fix some memory leaks. - - Fix a bug with opening decoders from memory. - - Early work on SSE2, AVX2 and NEON optimizations. - - Miscellaneous bug fixes. - - Documentation updates. - -v0.7 - 2018-02-25 - - API CHANGE: Change mal_src_read_frames() and mal_dsp_read_frames() to use 64-bit sample counts. - - Add decoder APIs for loading WAV, FLAC, Vorbis and MP3 files. - - Allow opening of devices without a context. - - In this case the context is created and managed internally by the device. - - Change the default channel mapping to the same as that used by FLAC. - - Fix build errors with macOS. - -v0.6c - 2018-02-12 - - Fix build errors with BSD/OSS. - -v0.6b - 2018-02-03 - - Fix some warnings when compiling with Visual C++. - -v0.6a - 2018-01-26 - - Fix errors with channel mixing when increasing the channel count. - - Improvements to the build system for the OpenAL backend. - - Documentation fixes. - -v0.6 - 2017-12-08 - - API CHANGE: Expose and improve mutex APIs. If you were using the mutex APIs before this version you'll - need to update. - - API CHANGE: SRC and DSP callbacks now take a pointer to a mal_src and mal_dsp object respectively. - - API CHANGE: Improvements to event and thread APIs. These changes make these APIs more consistent. - - Add support for SDL and Emscripten. - - Simplify the build system further for when development packages for various backends are not installed. - With this change, when the compiler supports __has_include, backends without the relevant development - packages installed will be ignored. This fixes the build for old versions of MinGW. - - Fixes to the Android build. - - Add mal_convert_frames(). This is a high-level helper API for performing a one-time, bulk conversion of - audio data to a different format. - - Improvements to f32 -> u8/s16/s24/s32 conversion routines. - - Fix a bug where the wrong value is returned from mal_device_start() for the OpenSL backend. - - Fixes and improvements for Raspberry Pi. - - Warning fixes. - -v0.5 - 2017-11-11 - - API CHANGE: The mal_context_init() function now takes a pointer to a mal_context_config object for - configuring the context. The works in the same kind of way as the device config. The rationale for this - change is to give applications better control over context-level properties, add support for backend- - specific configurations, and support extensibility without breaking the API. - - API CHANGE: The alsa.preferPlugHW device config variable has been removed since it's not really useful for - anything anymore. - - ALSA: By default, device enumeration will now only enumerate over unique card/device pairs. Applications - can enable verbose device enumeration by setting the alsa.useVerboseDeviceEnumeration context config - variable. - - ALSA: When opening a device in shared mode (the default), the dmix/dsnoop plugin will be prioritized. If - this fails it will fall back to the hw plugin. With this change the preferExclusiveMode config is now - honored. Note that this does not happen when alsa.useVerboseDeviceEnumeration is set to true (see above) - which is by design. - - ALSA: Add support for excluding the "null" device using the alsa.excludeNullDevice context config variable. - - ALSA: Fix a bug with channel mapping which causes an assertion to fail. - - Fix errors with enumeration when pInfo is set to NULL. - - OSS: Fix a bug when starting a device when the client sends 0 samples for the initial buffer fill. - -v0.4 - 2017-11-05 - - API CHANGE: The log callback is now per-context rather than per-device and as is thus now passed to - mal_context_init(). The rationale for this change is that it allows applications to capture diagnostic - messages at the context level. Previously this was only available at the device level. - - API CHANGE: The device config passed to mal_device_init() is now const. - - Added support for OSS which enables support on BSD platforms. - - Added support for WinMM (waveOut/waveIn). - - Added support for UWP (Universal Windows Platform) applications. Currently C++ only. - - Added support for exclusive mode for selected backends. Currently supported on WASAPI. - - POSIX builds no longer require explicit linking to libpthread (-lpthread). - - ALSA: Explicit linking to libasound (-lasound) is no longer required. - - ALSA: Latency improvements. - - ALSA: Use MMAP mode where available. This can be disabled with the alsa.noMMap config. - - ALSA: Use "hw" devices instead of "plughw" devices by default. This can be disabled with the - alsa.preferPlugHW config. - - WASAPI is now the highest priority backend on Windows platforms. - - Fixed an error with sample rate conversion which was causing crackling when capturing. - - Improved error handling. - - Improved compiler support. - - Miscellaneous bug fixes. - -v0.3 - 2017-06-19 - - API CHANGE: Introduced the notion of a context. The context is the highest level object and is required for - enumerating and creating devices. Now, applications must first create a context, and then use that to - enumerate and create devices. The reason for this change is to ensure device enumeration and creation is - tied to the same backend. In addition, some backends are better suited to this design. - - API CHANGE: Removed the rewinding APIs because they're too inconsistent across the different backends, hard - to test and maintain, and just generally unreliable. - - Added helper APIs for initializing mal_device_config objects. - - Null Backend: Fixed a crash when recording. - - Fixed build for UWP. - - Added support for f32 formats to the OpenSL|ES backend. - - Added initial implementation of the WASAPI backend. - - Added initial implementation of the OpenAL backend. - - Added support for low quality linear sample rate conversion. - - Added early support for basic channel mapping. - -v0.2 - 2016-10-28 - - API CHANGE: Add user data pointer as the last parameter to mal_device_init(). The rationale for this - change is to ensure the logging callback has access to the user data during initialization. - - API CHANGE: Have device configuration properties be passed to mal_device_init() via a structure. Rationale: - 1) The number of parameters is just getting too much. - 2) It makes it a bit easier to add new configuration properties in the future. In particular, there's a - chance there will be support added for backend-specific properties. - - Dropped support for f64, A-law and Mu-law formats since they just aren't common enough to justify the - added maintenance cost. - - DirectSound: Increased the default buffer size for capture devices. - - Added initial implementation of the OpenSL|ES backend. - -v0.1 - 2016-10-21 - - Initial versioned release. -*/ - /* This software is available as a choice of the following licenses. Choose diff --git a/vendor/miniaudio/synchronization.odin b/vendor/miniaudio/synchronization.odin new file mode 100644 index 000000000..7615e8f45 --- /dev/null +++ b/vendor/miniaudio/synchronization.odin @@ -0,0 +1,152 @@ +package miniaudio + +when ODIN_OS == .Windows { + foreign import lib "lib/miniaudio.lib" +} else when ODIN_OS == .Linux { + foreign import lib "lib/miniaudio.a" +} else { + foreign import lib "system:miniaudio" +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + /* + Locks a spinlock. + */ + spinlock_lock :: proc(/*volatile*/ pSpinlock: ^spinlock) -> result --- + + /* + Locks a spinlock, but does not yield() when looping. + */ + spinlock_lock_noyield :: proc(/*volatile*/ pSpinlock: ^spinlock) -> result --- + + /* + Unlocks a spinlock. + */ + spinlock_unlock :: proc(/*volatile*/ pSpinlock: ^spinlock) -> result --- + +when NO_THREADING { + /* + Creates a mutex. + + A mutex must be created from a valid context. A mutex is initially unlocked. + */ + mutex_init :: proc(pMutex: ^mutex) -> result --- + + /* + Deletes a mutex. + */ + mutex_uninit :: proc(pMutex: ^mutex) --- + + /* + Locks a mutex with an infinite timeout. + */ + mutex_lock :: proc(pMutex: ^mutex) --- + + /* + Unlocks a mutex. + */ + mutex_unlock :: proc(pMutex: ^mutex) --- + + + /* + Initializes an auto-reset event. + */ + event_init :: proc(pEvent: ^event) -> result --- + + /* + Uninitializes an auto-reset event. + */ + event_uninit :: proc(pEvent: ^event) --- + + /* + Waits for the specified auto-reset event to become signalled. + */ + event_wait :: proc(pEvent: ^event) -> result --- + + /* + Signals the specified auto-reset event. + */ + event_signal :: proc(pEvent: ^event) -> result --- +} /* NO_THREADING */ + +} + +/* +Fence +===== +This locks while the counter is larger than 0. Counter can be incremented and decremented by any +thread, but care needs to be taken when waiting. It is possible for one thread to acquire the +fence just as another thread returns from ma_fence_wait(). + +The idea behind a fence is to allow you to wait for a group of operations to complete. When an +operation starts, the counter is incremented which locks the fence. When the operation completes, +the fence will be released which decrements the counter. ma_fence_wait() will block until the +counter hits zero. + +If threading is disabled, ma_fence_wait() will spin on the counter. +*/ +fence :: struct { + e: (struct {} when NO_THREADING else event), + counter: (u32 when NO_THREADING else struct {}), +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + fence_init :: proc(pFence: ^fence) -> result --- + fence_uninit :: proc(pFence: ^fence) --- + fence_acquire :: proc(pFence: ^fence) -> result --- /* Increment counter. */ + fence_release :: proc(pFence: ^fence) -> result --- /* Decrement counter. */ + fence_wait :: proc(pFence: ^fence) -> result --- /* Wait for counter to reach 0. */ +} + + +/* +Notification callback for asynchronous operations. +*/ +async_notification :: struct {} + +async_notification_callbacks :: struct { + onSignal: proc "c" (pNotification: ^async_notification), +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + async_notification_signal :: proc(pNotification: ^async_notification) -> result --- +} + + +/* +Simple polling notification. + +This just sets a variable when the notification has been signalled which is then polled with ma_async_notification_poll_is_signalled() +*/ +async_notification_poll :: struct { + cb: async_notification_callbacks, + signalled: b32, +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + async_notification_poll_init :: proc(pNotificationPoll: ^async_notification_poll) -> result --- + async_notification_poll_is_signalled :: proc(pNotificationPoll: ^async_notification_poll) -> b32 --- +} + + +/* +Event Notification + +This uses an ma_event. If threading is disabled (MA_NO_THREADING), initialization will fail. +*/ +async_notification_event :: struct { + cb: async_notification_callbacks, + e: (struct {} when NO_THREADING else event), +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + async_notification_event_init :: proc(pNotificationEvent: ^async_notification_event) -> result --- + async_notification_event_uninit :: proc(pNotificationEvent: ^async_notification_event) -> result --- + async_notification_event_wait :: proc(pNotificationEvent: ^async_notification_event) -> result --- + async_notification_event_signal :: proc(pNotificationEvent: ^async_notification_event) -> result --- +} diff --git a/vendor/miniaudio/utilities.odin b/vendor/miniaudio/utilities.odin index 9ced019f5..791482cbf 100644 --- a/vendor/miniaudio/utilities.odin +++ b/vendor/miniaudio/utilities.odin @@ -1,5 +1,7 @@ package miniaudio +import c "core:c/libc" + when ODIN_OS == .Windows { foreign import lib "lib/miniaudio.lib" } else when ODIN_OS == .Linux { @@ -10,13 +12,6 @@ when ODIN_OS == .Windows { @(default_calling_convention="c", link_prefix="ma_") foreign lib { - /* - Adjust buffer size based on a scaling factor. - - This just multiplies the base size by the scaling factor, making sure it's a size of at least 1. - */ - scale_buffer_size :: proc(baseBufferSize: u32, scale: f32) -> u32 --- - /* Calculates a buffer size in milliseconds from the specified number of frames and sample rate. */ @@ -51,9 +46,14 @@ foreign lib { /* - Clips f32 samples. + Clips samples. */ - clip_samples_f32 :: proc(p: [^]f32, sampleCount: u64) --- + clip_samples_u8 :: proc(pDst: [^]u8, pSrc: [^]i16, count: u64) --- + clip_samples_s16 :: proc(pDst: [^]i16, pSrc: [^]i32, count: u64) --- + clip_samples_s24 :: proc(pDst: [^]u8, pSrc: [^]i64, count: u64) --- + clip_samples_s32 :: proc(pDst: [^]i32, pSrc: [^]i64, count: u64) --- + clip_samples_f32 :: proc(pDst, pSrc: [^]f32, count: u64) --- + clip_pcm_frames :: proc(pDst, pSrc: rawptr, frameCount: u64, format: format, channels: u32) --- /* Helper for applying a volume factor to samples. @@ -86,20 +86,26 @@ foreign lib { apply_volume_factor_pcm_frames_f32 :: proc(pFrames: [^]f32, frameCount: u64, channels: u32, factor: f32) --- apply_volume_factor_pcm_frames :: proc(pFrames: rawptr, frameCount: u64, format: format, channels: u32, factor: f32) --- + copy_and_apply_volume_factor_per_channel_f32 :: proc(pFramesOut, pFramesIn: [^]f32, frameCount: u64, channels: u32, pChannelGains: [^]f32) --- + + + ma_copy_and_apply_volume_and_clip_samples_u8 :: proc(pDst: [^]u8, pSrc: [^]i16, count: u64, volume: f32) --- + ma_copy_and_apply_volume_and_clip_samples_s16 :: proc(pDst: [^]i16, pSrc: [^]i32, count: u64, volume: f32) --- + ma_copy_and_apply_volume_and_clip_samples_s24 :: proc(pDst: [^]u8, pSrc: [^]i64, count: u64, volume: f32) --- + ma_copy_and_apply_volume_and_clip_samples_s32 :: proc(pDst: [^]i32, pSrc: [^]i64, count: u64, volume: f32) --- + ma_copy_and_apply_volume_and_clip_samples_f32 :: proc(pDst, pSrc: [^]f32, count: u64, volume: f32) --- + ma_copy_and_apply_volume_and_clip_pcm_frames :: proc(pDst, pSrc: rawptr, frameCount: u64, format: format, channels: u32, volume: f32) --- + /* Helper for converting a linear factor to gain in decibels. */ - factor_to_gain_db :: proc(factor: f32) -> f32 --- + volume_linear_to_db :: proc(factor: f32) -> f32 --- /* Helper for converting gain in decibels to a linear factor. */ - gain_db_to_factor :: proc(gain: f32) -> f32 --- -} - -zero_pcm_frames :: #force_inline proc "c" (p: rawptr, frameCount: u64, format: format, channels: u32) { - silence_pcm_frames(p, frameCount, format, channels) + volume_db_to_linear :: proc(gain: f32) -> f32 --- } offset_pcm_frames_ptr_f32 :: #force_inline proc "c" (p: [^]f32, offsetInFrames: u64, channels: u32) -> [^]f32 { @@ -109,23 +115,20 @@ offset_pcm_frames_const_ptr_f32 :: #force_inline proc "c" (p: [^]f32, offsetInFr return cast([^]f32)offset_pcm_frames_ptr(p, offsetInFrames, .f32, channels) } -clip_pcm_frames_f32 :: #force_inline proc "c" (p: [^]f32, frameCount: u64, channels: u32) { - clip_samples_f32(p, frameCount*u64(channels)) -} - data_source :: struct {} +DATA_SOURCE_SELF_MANAGED_RANGE_AND_LOOP_POINT :: 0x00000001 + data_source_vtable :: struct { onRead: proc "c" (pDataSource: ^data_source, pFramesOut: rawptr, frameCount: u64, pFramesRead: ^u64) -> result, onSeek: proc "c" (pDataSource: ^data_source, frameIndex: u64) -> result, - onMap: proc "c" (pDataSource: ^data_source, ppFramesOut: ^rawptr, pFrameCount: ^u64) -> result, /* Returns MA_AT_END if the end has been reached. This should be considered successful. */ - onUnmap: proc "c" (pDataSource: ^data_source, frameCount: u64) -> result, - onGetDataFormat: proc "c" (pDataSource: ^data_source, pFormat: ^format, pChannels: ^u32, pSampleRate: ^u32) -> result, + onGetDataFormat: proc "c" (pDataSource: ^data_source, pFormat: ^format, pChannels: ^u32, pSampleRate: ^u32, pChannelMap: [^]channel, channelMapCap: c.size_t) -> result, onGetCursor: proc "c" (pDataSource: ^data_source, pCursor: ^u64) -> result, onGetLength: proc "c" (pDataSource: ^data_source, pLength: ^u64) -> result, + onSetLooping: proc "c" (pDataSource: ^data_source, isLooping: b32) -> result, + flags: u32, } -data_source_callbacks :: data_source_vtable /* TODO: Remove ma_data_source_callbacks in version 0.11. */ data_source_get_next_proc :: proc "c" (pDataSource: ^data_source) -> ^data_source @@ -134,45 +137,43 @@ data_source_config :: struct { } data_source_base :: struct { - cb: data_source_callbacks, /* TODO: Remove this. */ - - /* Variables below are placeholder and not yet used. */ vtable: ^data_source_vtable, rangeBegInFrames: u64, - rangeEndInFrames: u64, /* Set to -1 for unranged (default). */ - loopBegInFrames: u64, /* Relative to rangeBegInFrames. */ - loopEndInFrames: u64, /* Relative to rangeBegInFrames. Set to -1 for the end of the range. */ - pCurrent: ^data_source, /* When non-NULL, the data source being initialized will act as a proxy and will route all operations to pCurrent. Used in conjunction with pNext/onGetNext for seamless chaining. */ - pNext: ^data_source, /* When set to NULL, onGetNext will be used. */ - onGetNext: ^data_source_get_next_proc, /* Will be used when pNext is NULL. If both are NULL, no next will be used. */ + rangeEndInFrames: u64, /* Set to -1 for unranged (default). */ + loopBegInFrames: u64, /* Relative to rangeBegInFrames. */ + loopEndInFrames: u64, /* Relative to rangeBegInFrames. Set to -1 for the end of the range. */ + pCurrent: ^data_source, /* When non-NULL, the data source being initialized will act as a proxy and will route all operations to pCurrent. Used in conjunction with pNext/onGetNext for seamless chaining. */ + pNext: ^data_source, /* When set to NULL, onGetNext will be used. */ + onGetNext: data_source_get_next_proc, /* Will be used when pNext is NULL. If both are NULL, no next will be used. */ + isLooping: b32, /*atomic*/ } @(default_calling_convention="c", link_prefix="ma_") foreign lib { - ma_data_source_config_init :: proc() -> data_source_config --- + data_source_config_init :: proc() -> data_source_config --- - data_source_init :: proc(pConfig: ^data_source_config, pDataSource: ^data_source) -> result --- - data_source_uninit :: proc(pDataSource: ^data_source) --- - data_source_read_pcm_frames :: proc(pDataSource: ^data_source, pFramesOut: rawptr, frameCount: u64, pFramesRead: ^u64, loop: b32) -> result --- /* Must support pFramesOut = NULL in which case a forward seek should be performed. */ - data_source_seek_pcm_frames :: proc(pDataSource: ^data_source, frameCount: u64, pFramesSeeked: ^u64, loop: b32) -> result --- /* Can only seek forward. Equivalent to ma_data_source_read_pcm_frames(pDataSource, NULL, frameCount); */ - data_source_seek_to_pcm_frame :: proc(pDataSource: ^data_source, frameIndex: u64) -> result --- - data_source_map :: proc(pDataSource: ^data_source, ppFramesOut: ^rawptr, pFrameCount: ^u64) -> result --- /* Returns MA_NOT_IMPLEMENTED if mapping is not supported. */ - data_source_unmap :: proc(pDataSource: ^data_source, frameCount: u64) -> result --- /* Returns MA_AT_END if the end has been reached. */ - data_source_get_data_format :: proc(pDataSource: ^data_source, pFormat: ^format, pChannels: ^u32, pSampleRate: ^u32) -> result --- - data_source_get_cursor_in_pcm_frames :: proc(pDataSource: ^data_source, pCursor: ^u64) -> result --- - data_source_get_length_in_pcm_frames :: proc(pDataSource: ^data_source, pLength: ^u64) -> result --- /* Returns MA_NOT_IMPLEMENTED if the length is unknown or cannot be determined. Decoders can return this. */ - // #if defined(MA_EXPERIMENTAL__DATA_LOOPING_AND_CHAINING) - // MA_API ma_result ma_data_source_set_range_in_pcm_frames(ma_data_source* pDataSource, ma_uint64 rangeBegInFrames, ma_uint64 rangeEndInFrames); - // MA_API void ma_data_source_get_range_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pRangeBegInFrames, ma_uint64* pRangeEndInFrames); - // MA_API ma_result ma_data_source_set_loop_point_in_pcm_frames(ma_data_source* pDataSource, ma_uint64 loopBegInFrames, ma_uint64 loopEndInFrames); - // MA_API void ma_data_source_get_loop_point_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pLoopBegInFrames, ma_uint64* pLoopEndInFrames); - // MA_API ma_result ma_data_source_set_current(ma_data_source* pDataSource, ma_data_source* pCurrentDataSource); - // MA_API ma_data_source* ma_data_source_get_current(ma_data_source* pDataSource); - // MA_API ma_result ma_data_source_set_next(ma_data_source* pDataSource, ma_data_source* pNextDataSource); - // MA_API ma_data_source* ma_data_source_get_next(ma_data_source* pDataSource); - // MA_API ma_result ma_data_source_set_next_callback(ma_data_source* pDataSource, ma_data_source_get_next_proc onGetNext); - // MA_API ma_data_source_get_next_proc ma_data_source_get_next_callback(ma_data_source* pDataSource); - // #endif + data_source_init :: proc(pConfig: ^data_source_config, pDataSource: ^data_source) -> result --- + data_source_uninit :: proc(pDataSource: ^data_source) --- + data_source_read_pcm_frames :: proc(pDataSource: ^data_source, pFramesOut: rawptr, frameCount: u64, pFramesRead: ^u64) -> result --- /* Must support pFramesOut = NULL in which case a forward seek should be performed. */ + data_source_seek_pcm_frames :: proc(pDataSource: ^data_source, frameCount: u64, pFramesSeeked: ^u64) -> result --- /* Can only seek forward. Equivalent to ma_data_source_read_pcm_frames(pDataSource, NULL, frameCount); */ + data_source_seek_to_pcm_frame :: proc(pDataSource: ^data_source, frameIndex: u64) -> result --- + data_source_get_data_format :: proc(pDataSource: ^data_source, pFormat: ^format, pChannels: ^u32, pSampleRate: ^u32, pChannelMap: [^]channel, channelMapCap: c.size_t) -> result --- + data_source_get_cursor_in_pcm_frames :: proc(pDataSource: ^data_source, pCursor: ^u64) -> result --- + data_source_get_length_in_pcm_frames :: proc(pDataSource: ^data_source, pLength: ^u64) -> result --- /* Returns MA_NOT_IMPLEMENTED if the length is unknown or cannot be determined. Decoders can return this. */ + data_source_get_cursor_in_seconds :: proc(pDataSource: ^data_source, pCursor: ^f32) -> result --- + data_source_get_length_in_seconds :: proc(pDataSource: ^data_source, pLength: ^f32) -> result --- + data_source_set_looping :: proc(pDataSource: ^data_source, isLooping: b32) -> result --- + data_source_is_looping :: proc(pDataSource: ^data_source) -> b32 --- + data_source_set_range_in_pcm_frames :: proc(pDataSource: ^data_source, rangeBegInFrames: u64, rangeEndInFrames: u64) -> result --- + data_source_get_range_in_pcm_frames :: proc(pDataSource: ^data_source, pRangeBegInFrames: ^u64, pRangeEndInFrames: ^u64) --- + data_source_set_loop_point_in_pcm_frames :: proc(pDataSource: ^data_source, loopBegInFrames: u64, loopEndInFrames: u64) -> result --- + data_source_get_loop_point_in_pcm_frames :: proc(pDataSource: ^data_source, pLoopBegInFrames: ^u64, pLoopEndInFrames: ^u64) --- + data_source_set_current :: proc(pDataSource: ^data_source, pCurrentDataSource: ^data_source) -> result --- + data_source_get_current :: proc(pDataSource: ^data_source) -> ^data_source --- + data_source_set_next :: proc(pDataSource: ^data_source, pNextDataSource: ^data_source) -> result --- + data_source_get_next :: proc(pDataSource: ^data_source) -> ^data_source --- + data_source_set_next_callback :: proc(pDataSource: ^data_source, onGetNext: ^data_source_get_next_proc) -> result --- + data_source_get_next_callback :: proc(pDataSource: ^data_source) -> ^data_source_get_next_proc --- } @@ -180,6 +181,7 @@ audio_buffer_ref :: struct { ds: data_source_base, format: format, channels: u32, + sampleRate: u32, cursor: u64, sizeInFrames: u64, pData: rawptr, @@ -204,6 +206,7 @@ foreign lib { audio_buffer_config :: struct { format: format, channels: u32, + sampleRate: u32, sizeInFrames: u64, pData: rawptr, /* If set to NULL, will allocate a block of memory for you. */ allocationCallbacks: allocation_callbacks, @@ -234,3 +237,65 @@ foreign lib { audio_buffer_get_length_in_pcm_frames :: proc(pAudioBuffer: ^audio_buffer, pLength: ^u64) -> result --- audio_buffer_get_available_frames :: proc(pAudioBuffer: ^audio_buffer, pAvailableFrames: ^u64) -> result --- } + +/* +Paged Audio Buffer +================== +A paged audio buffer is made up of a linked list of pages. It's expandable, but not shrinkable. It +can be used for cases where audio data is streamed in asynchronously while allowing data to be read +at the same time. + +This is lock-free, but not 100% thread safe. You can append a page and read from the buffer across +simultaneously across different threads, however only one thread at a time can append, and only one +thread at a time can read and seek. +*/ +paged_audio_buffer_page :: struct { + pNext: ^paged_audio_buffer_page, /*atomic*/ + sizeInFrames: u64, + pAudioData: [1]u8, +}; + +paged_audio_buffer_data :: struct { + format: format, + channels: u32, + head: paged_audio_buffer_page, /* Dummy head for the lock-free algorithm. Always has a size of 0. */ + pTail: ^paged_audio_buffer_page, /*atomic*/ /* Never null. Initially set to &head. */ +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + paged_audio_buffer_data_init :: proc(format: format, channels: u32, pData: ^paged_audio_buffer_data) -> result --- + paged_audio_buffer_data_uninit :: proc(pData: ^paged_audio_buffer_data, pAllocationCallbacks: ^allocation_callbacks) --- + paged_audio_buffer_data_get_head :: proc(pData: ^paged_audio_buffer_data) -> ^paged_audio_buffer_page --- + paged_audio_buffer_data_get_tail :: proc(pData: ^paged_audio_buffer_data) -> ^paged_audio_buffer_page --- + paged_audio_buffer_data_get_length_in_pcm_frames :: proc(pData: ^paged_audio_buffer_data, pLength: ^u64) -> result --- + paged_audio_buffer_data_allocate_page :: proc(pData: ^paged_audio_buffer_data, pageSizeInFrames: u64, pInitialData: rawptr, pAllocationCallbacks: ^allocation_callbacks, ppPage: ^^paged_audio_buffer_page) -> result --- + paged_audio_buffer_data_free_page :: proc(pData: ^paged_audio_buffer_data, pPage: ^paged_audio_buffer_page, pAllocationCallbacks: ^allocation_callbacks) -> result --- + paged_audio_buffer_data_append_page :: proc(pData: ^paged_audio_buffer_data, pPage: ^paged_audio_buffer_page) -> result --- + paged_audio_buffer_data_allocate_and_append_page :: proc(pData: ^paged_audio_buffer_data, pageSizeInFrames: u32, pInitialData: rawptr, pAllocationCallbacks: ^allocation_callbacks) -> result --- +} + + +paged_audio_buffer_config :: struct { + pData: ^paged_audio_buffer_data, /* Must not be null. */ +} + +paged_audio_buffer :: struct { + ds: data_source_base, + pData: ^paged_audio_buffer_data, /* Audio data is read from here. Cannot be null. */ + pCurrent: ^paged_audio_buffer_page, + relativeCursor: u64, /* Relative to the current page. */ + absoluteCursor: u64, +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + paged_audio_buffer_config_init :: proc(pData: ^paged_audio_buffer_data) -> paged_audio_buffer_config --- + + paged_audio_buffer_init :: proc(pConfig: ^paged_audio_buffer_config, pPagedAudioBuffer: ^paged_audio_buffer) -> result --- + paged_audio_buffer_uninit :: proc(pPagedAudioBuffer: ^paged_audio_buffer) --- + paged_audio_buffer_read_pcm_frames :: proc(pPagedAudioBuffer: ^paged_audio_buffer, pFramesOut: rawptr, frameCount: u64, pFramesRead: ^u64) -> result --- /* Returns MA_AT_END if no more pages available. */ + paged_audio_buffer_seek_to_pcm_frame :: proc(pPagedAudioBuffer: ^paged_audio_buffer, frameIndex: u64) -> result --- + paged_audio_buffer_get_cursor_in_pcm_frames :: proc(pPagedAudioBuffer: ^paged_audio_buffer, pCursor: ^u64) -> result --- + paged_audio_buffer_get_length_in_pcm_frames :: proc(pPagedAudioBuffer: ^paged_audio_buffer, pLength: ^u64) -> result --- +} diff --git a/vendor/miniaudio/vfs.odin b/vendor/miniaudio/vfs.odin index 85571341e..9731c713f 100644 --- a/vendor/miniaudio/vfs.odin +++ b/vendor/miniaudio/vfs.odin @@ -22,8 +22,10 @@ appropriate for a given situation. vfs :: struct {} vfs_file :: distinct handle -OPEN_MODE_READ :: 0x00000001 -OPEN_MODE_WRITE :: 0x00000002 +open_mode_flags :: enum c.int { + READ = 0x00000001, + WRITE = 0x00000002, +} seek_origin :: enum c.int { start, @@ -71,10 +73,6 @@ foreign lib { default_vfs_init :: proc(pVFS: ^default_vfs, pAllocationCallbacks: ^allocation_callbacks) -> result --- } -resource_format :: enum c.int { - wav, -} - encoding_format :: enum c.int { unknown = 0, wav, From 4911df9f99d8c0de4531c794c8fc7e7fccf009f0 Mon Sep 17 00:00:00 2001 From: bkrypt <4868093+bkrypt@users.noreply.github.com> Date: Fri, 29 Apr 2022 21:39:10 +0200 Subject: [PATCH 2/7] Remove unneeded semicolons --- vendor/miniaudio/node_graph.odin | 4 ++-- vendor/miniaudio/resource_manager.odin | 2 +- vendor/miniaudio/utilities.odin | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/vendor/miniaudio/node_graph.odin b/vendor/miniaudio/node_graph.odin index ac47d43d8..c0df39c0f 100644 --- a/vendor/miniaudio/node_graph.odin +++ b/vendor/miniaudio/node_graph.odin @@ -156,7 +156,7 @@ node_base :: struct { _outputBuses: [MAX_NODE_LOCAL_BUS_COUNT]node_output_bus, _pHeap: rawptr, /* A heap allocation for internal use only. pInputBuses and/or pOutputBuses will point to this if the bus count exceeds MA_MAX_NODE_LOCAL_BUS_COUNT. */ _ownsHeap: b32, /* If set to true, the node owns the heap allocation and _pHeap will be freed in ma_node_uninit(). */ -}; +} @(default_calling_convention="c", link_prefix="ma_") foreign lib { @@ -199,7 +199,7 @@ node_graph :: struct { /* Read and written by multiple threads. */ isReading: b32, /*atomic*/ -}; +} @(default_calling_convention="c", link_prefix="ma_") foreign lib { diff --git a/vendor/miniaudio/resource_manager.odin b/vendor/miniaudio/resource_manager.odin index c4d722342..e67d4a475 100644 --- a/vendor/miniaudio/resource_manager.odin +++ b/vendor/miniaudio/resource_manager.odin @@ -218,7 +218,7 @@ foreign lib { /* Init. */ resource_manager_init :: proc(pConfig: ^resource_manager_config, pResourceManager: ^resource_manager) -> result --- resource_manager_uninit :: proc(pResourceManager: ^resource_manager) --- - resource_manager_get_log :: proc(pResourceManager: ^resource_manager) -> ^log ---; + resource_manager_get_log :: proc(pResourceManager: ^resource_manager) -> ^log --- /* Registration. */ resource_manager_register_file :: proc(pResourceManager: ^resource_manager, pFilePath: cstring, flags: u32) -> result --- diff --git a/vendor/miniaudio/utilities.odin b/vendor/miniaudio/utilities.odin index 791482cbf..708cc820e 100644 --- a/vendor/miniaudio/utilities.odin +++ b/vendor/miniaudio/utilities.odin @@ -253,7 +253,7 @@ paged_audio_buffer_page :: struct { pNext: ^paged_audio_buffer_page, /*atomic*/ sizeInFrames: u64, pAudioData: [1]u8, -}; +} paged_audio_buffer_data :: struct { format: format, From 9e694523277b75a44e0dc58dafec910c2fa0e8f6 Mon Sep 17 00:00:00 2001 From: bkrypt <4868093+bkrypt@users.noreply.github.com> Date: Sat, 30 Apr 2022 20:42:42 +0200 Subject: [PATCH 3/7] Remove unnecessary value (`count`) from enum --- vendor/miniaudio/common.odin | 1 - 1 file changed, 1 deletion(-) diff --git a/vendor/miniaudio/common.odin b/vendor/miniaudio/common.odin index d7b901714..1d64dc182 100644 --- a/vendor/miniaudio/common.odin +++ b/vendor/miniaudio/common.odin @@ -193,7 +193,6 @@ format :: enum c.int { s24 = 3, /* Tightly packed. 3 bytes per sample. */ s32 = 4, f32 = 5, - count, } standard_sample_rate :: enum u32 { From be9b935953c50b7a9e8ee73442717335155ccb03 Mon Sep 17 00:00:00 2001 From: bkrypt <4868093+bkrypt@users.noreply.github.com> Date: Sat, 30 Apr 2022 20:43:22 +0200 Subject: [PATCH 4/7] Fix indentation --- vendor/miniaudio/device_io_types.odin | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/vendor/miniaudio/device_io_types.odin b/vendor/miniaudio/device_io_types.odin index b3ecb2301..5a2c4bc73 100644 --- a/vendor/miniaudio/device_io_types.odin +++ b/vendor/miniaudio/device_io_types.odin @@ -19,11 +19,11 @@ SUPPORT_CUSTOM :: true SUPPORT_NULL :: true // ODIN_OS != .Emscripten device_state :: enum c.int { - uninitialized = 0, - stopped = 1, /* The device's default state after initialization. */ - started = 2, /* The device is started and is requesting and/or delivering audio data. */ - starting = 3, /* Transitioning from a stopped state to started. */ - stopping = 4, /* Transitioning from a started state to stopped. */ + uninitialized = 0, + stopped = 1, /* The device's default state after initialization. */ + started = 2, /* The device is started and is requesting and/or delivering audio data. */ + starting = 3, /* Transitioning from a stopped state to started. */ + stopping = 4, /* Transitioning from a started state to stopped. */ } @@ -985,7 +985,7 @@ device :: struct { noPreSilencedOutputBuffer: b8, noClip: b8, noDisableDenormals: b8, - noFixedSizedCallback: b8, + noFixedSizedCallback: b8, masterVolumeFactor: f32, /*atomic*/ /* Linear 0..1. Can be read and written simultaneously by different threads. Must be used atomically. */ duplexRB: duplex_rb, /* Intermediary buffer for duplex device on asynchronous backends. */ resampling: struct { From db8d119cadbf1778516d1e6178bd566cc1609c61 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 20 May 2022 19:15:13 +0200 Subject: [PATCH 5/7] Fix Windows os.make_directory. --- core/os/file_windows.odin | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/os/file_windows.odin b/core/os/file_windows.odin index daabe60f0..ca9beff5d 100644 --- a/core/os/file_windows.odin +++ b/core/os/file_windows.odin @@ -389,7 +389,8 @@ change_directory :: proc(path: string) -> Errno { return Errno(win32.SetCurrentDirectoryW(wpath)) } -make_directory :: proc(path: string, mode: u32) -> Errno { +make_directory :: proc(path: string, mode: u32 = 0) -> Errno { + // Mode is unused on Windows, but is needed on *nix wpath := win32.utf8_to_wstring(path, context.temp_allocator) return Errno(win32.CreateDirectoryW(wpath, nil)) } From e85f1dd9fb79a127792e30abdf0a1e73980cadcd Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 20 May 2022 20:00:27 +0200 Subject: [PATCH 6/7] Fix is* proc in libc. --- core/c/libc/math.odin | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/core/c/libc/math.odin b/core/c/libc/math.odin index 97f77236f..6a7b81850 100644 --- a/core/c/libc/math.odin +++ b/core/c/libc/math.odin @@ -211,19 +211,19 @@ _signbitf :: #force_inline proc(x: float) -> int { return int(transmute(uint32_t)x >> 31) } -isfinite :: #force_inline proc(x: $T) where intrinsics.type_is_float(T) { +isfinite :: #force_inline proc(x: $T) -> bool where intrinsics.type_is_float(T) { return fpclassify(x) == FP_INFINITE } -isinf :: #force_inline proc(x: $T) where intrinsics.type_is_float(T) { +isinf :: #force_inline proc(x: $T) -> bool where intrinsics.type_is_float(T) { return fpclassify(x) > FP_INFINITE } -isnan :: #force_inline proc(x: $T) where intrinsics.type_is_float(T) { +isnan :: #force_inline proc(x: $T) -> bool where intrinsics.type_is_float(T) { return fpclassify(x) == FP_NAN } -isnormal :: #force_inline proc(x: $T) where intrinsics.type_is_float(T) { +isnormal :: #force_inline proc(x: $T) -> bool where intrinsics.type_is_float(T) { return fpclassify(x) == FP_NORMAL } @@ -231,27 +231,27 @@ isnormal :: #force_inline proc(x: $T) where intrinsics.type_is_float(T) { // implemented as the relational comparisons, as that would produce an invalid // "sticky" state that propagates and affects maths results. These need // to be implemented natively in Odin assuming isunordered to prevent that. -isgreater :: #force_inline proc(x, y: $T) where intrinsics.type_is_float(T) { +isgreater :: #force_inline proc(x, y: $T) -> bool where intrinsics.type_is_float(T) { return !isunordered(x, y) && x > y } -isgreaterequal :: #force_inline proc(x, y: $T) where intrinsics.type_is_float(T) { +isgreaterequal :: #force_inline proc(x, y: $T) -> bool where intrinsics.type_is_float(T) { return !isunordered(x, y) && x >= y } -isless :: #force_inline proc(x, y: $T) where intrinsics.type_is_float(T) { +isless :: #force_inline proc(x, y: $T) -> bool where intrinsics.type_is_float(T) { return !isunordered(x, y) && x < y } -islessequal :: #force_inline proc(x, y: $T) where intrinsics.type_is_float(T) { +islessequal :: #force_inline proc(x, y: $T) -> bool where intrinsics.type_is_float(T) { return !isunordered(x, y) && x <= y } -islessgreater :: #force_inline proc(x, y: $T) where intrinsics.type_is_float(T) { +islessgreater :: #force_inline proc(x, y: $T) -> bool where intrinsics.type_is_float(T) { return !isunordered(x, y) && x <= y } -isunordered :: #force_inline proc(x, y: $T) where intrinsics.type_is_float(T) { +isunordered :: #force_inline proc(x, y: $T) -> bool where intrinsics.type_is_float(T) { if isnan(x) { // Force evaluation of y to propagate exceptions for ordering semantics. // To ensure correct semantics of IEEE 754 this cannot be compiled away. From 06884da42b8a6a25f3d12f8ab7cfd08c21f4fa36 Mon Sep 17 00:00:00 2001 From: Tetralux Date: Sat, 21 May 2022 04:45:04 +0000 Subject: [PATCH 7/7] [path/filepath] Change join() to take a []string instead of varargs This makes passing an allocator easier, as you no longer have to resort to named arguments: Before: `join(a, b, c)` became `join(elems={a, b, c}, allocator=ally)` After: `join({a, b, c})` becomes `join({a, b, c}, ally)` --- core/c/frontend/preprocessor/preprocess.odin | 2 +- core/path/filepath/match.odin | 2 +- core/path/filepath/path_unix.odin | 2 +- core/path/filepath/path_windows.odin | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/c/frontend/preprocessor/preprocess.odin b/core/c/frontend/preprocessor/preprocess.odin index 9651cc81c..1db3fafa3 100644 --- a/core/c/frontend/preprocessor/preprocess.odin +++ b/core/c/frontend/preprocessor/preprocess.odin @@ -1276,7 +1276,7 @@ preprocess_internal :: proc(cpp: ^Preprocessor, tok: ^Token) -> ^Token { if start.file != nil { dir = filepath.dir(start.file.name) } - path := filepath.join(dir, filename) + path := filepath.join({dir, filename}) if os.exists(path) { tok = include_file(cpp, tok, path, start.next.next) continue diff --git a/core/path/filepath/match.odin b/core/path/filepath/match.odin index 252912710..00a9c9fb0 100644 --- a/core/path/filepath/match.odin +++ b/core/path/filepath/match.odin @@ -305,7 +305,7 @@ _glob :: proc(dir, pattern: string, matches: ^[dynamic]string, allocator := cont n := fi.name matched := match(pattern, n) or_return if matched { - append(&m, join(dir, n)) + append(&m, join({dir, n})) } } return diff --git a/core/path/filepath/path_unix.odin b/core/path/filepath/path_unix.odin index d0eaa3635..8faf6097c 100644 --- a/core/path/filepath/path_unix.odin +++ b/core/path/filepath/path_unix.odin @@ -38,7 +38,7 @@ abs :: proc(path: string, allocator := context.allocator) -> (string, bool) { return path_str, true } -join :: proc(elems: ..string, allocator := context.allocator) -> string { +join :: proc(elems: []string, allocator := context.allocator) -> string { for e, i in elems { if e != "" { p := strings.join(elems[i:], SEPARATOR_STRING, context.temp_allocator) diff --git a/core/path/filepath/path_windows.odin b/core/path/filepath/path_windows.odin index 28238dd6e..cdfe3ddbb 100644 --- a/core/path/filepath/path_windows.odin +++ b/core/path/filepath/path_windows.odin @@ -88,7 +88,7 @@ abs :: proc(path: string, allocator := context.allocator) -> (string, bool) { } -join :: proc(elems: ..string, allocator := context.allocator) -> string { +join :: proc(elems: []string, allocator := context.allocator) -> string { for e, i in elems { if e != "" { return join_non_empty(elems[i:], allocator)