From e6208611d061bf7cb2404760b6bf473ca481498a Mon Sep 17 00:00:00 2001 From: sirox00 Date: Mon, 27 Jul 2026 03:01:39 +0300 Subject: [PATCH 1/2] properly namespace openal backend --- .../{NativeAudioSource.hx => NativeOpenALAudioSource.hx} | 2 +- src/lime/media/AudioSource.hx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename src/lime/_internal/backend/native/{NativeAudioSource.hx => NativeOpenALAudioSource.hx} (99%) diff --git a/src/lime/_internal/backend/native/NativeAudioSource.hx b/src/lime/_internal/backend/native/NativeOpenALAudioSource.hx similarity index 99% rename from src/lime/_internal/backend/native/NativeAudioSource.hx rename to src/lime/_internal/backend/native/NativeOpenALAudioSource.hx index 99ad4d8bd4..97b86b1c65 100644 --- a/src/lime/_internal/backend/native/NativeAudioSource.hx +++ b/src/lime/_internal/backend/native/NativeOpenALAudioSource.hx @@ -11,7 +11,7 @@ import lime.media.AudioSource; import lime.utils.UInt8Array; @:access(lime.media.AudioBuffer) -class NativeAudioSource +class NativeOpenALAudioSource { private static var hasDirectChannelsExt:Null; private static var hasALSoftLatencyExt:Null; diff --git a/src/lime/media/AudioSource.hx b/src/lime/media/AudioSource.hx index e2f5cefe80..5afdff53f0 100644 --- a/src/lime/media/AudioSource.hx +++ b/src/lime/media/AudioSource.hx @@ -201,6 +201,6 @@ class AudioSource #if (js && html5) @:noCompletion private typedef AudioSourceBackend = lime._internal.backend.html5.HTML5AudioSource; -#else -@:noCompletion private typedef AudioSourceBackend = lime._internal.backend.native.NativeAudioSource; +#elseif lime_openal +@:noCompletion private typedef AudioSourceBackend = lime._internal.backend.native.NativeOpenALAudioSource; #end From 285ff1fc4101c2d3beca32623ceed7cd0750b906 Mon Sep 17 00:00:00 2001 From: sirox00 Date: Mon, 3 Aug 2026 21:13:10 +0300 Subject: [PATCH 2/2] da miniaudio backend --- .gitmodules | 3 + include.xml | 1 + project/Build.xml | 14 + .../media/miniaudio/MiniaudioBackend.h | 41 ++ project/include/utils/File.h | 1 + project/src/ExternalInterface.cpp | 223 ++++++++ .../src/media/miniaudio/MiniaudioBackend.cpp | 533 ++++++++++++++++++ project/src/utils/File.cpp | 7 + run.n | Bin 140717 -> 141217 bytes .../_internal/backend/native/NativeCFFI.hx | 130 ++++- .../native/NativeMiniaudioAudioSource.hx | 264 +++++++++ src/lime/media/AudioContext.hx | 16 + src/lime/media/AudioContextType.hx | 1 + src/lime/media/AudioManager.hx | 146 +++-- src/lime/media/AudioSource.hx | 75 +++ src/lime/media/MiniaudioFormat.hx | 26 + tools/tools.n | Bin 1595232 -> 1600853 bytes 17 files changed, 1421 insertions(+), 60 deletions(-) create mode 100644 project/include/media/miniaudio/MiniaudioBackend.h create mode 100644 project/src/media/miniaudio/MiniaudioBackend.cpp create mode 100644 src/lime/_internal/backend/native/NativeMiniaudioAudioSource.hx create mode 100644 src/lime/media/MiniaudioFormat.hx diff --git a/.gitmodules b/.gitmodules index 60f4123add..8bea074133 100644 --- a/.gitmodules +++ b/.gitmodules @@ -70,3 +70,6 @@ path = project/lib/dr_libs url = https://github.com/mackron/dr_libs.git shallow = true +[submodule "project/lib/miniaudio"] + path = project/lib/miniaudio + url = https://github.com/mackron/miniaudio diff --git a/include.xml b/include.xml index 07a07c0b30..fdac388071 100644 --- a/include.xml +++ b/include.xml @@ -16,6 +16,7 @@ + diff --git a/project/Build.xml b/project/Build.xml index 1244ae6726..2f394d2669 100644 --- a/project/Build.xml +++ b/project/Build.xml @@ -24,6 +24,7 @@ + @@ -157,6 +158,19 @@ +
+ + + + + + + + + + +
+
diff --git a/project/include/media/miniaudio/MiniaudioBackend.h b/project/include/media/miniaudio/MiniaudioBackend.h new file mode 100644 index 0000000000..5c15105ba3 --- /dev/null +++ b/project/include/media/miniaudio/MiniaudioBackend.h @@ -0,0 +1,41 @@ +#pragma once + + +#include + +#include + +#include + +#include + +namespace lime { + + void miniaudio_backend_init(); + void miniaudio_backend_uninit(); + int32_t miniaudio_backend_engine_init(uint32_t sampleRate, uint32_t channels, uint32_t periodSizeInFrames, uint32_t gainSmoothTimeInFrames); + void miniaudio_backend_engine_uninit(uint32_t engineIndex); + void miniaudio_backend_engine_start(uint32_t engineIndex); + void miniaudio_backend_engine_stop(uint32_t engineIndex); + int32_t miniaudio_backend_sound_init_from_file(uint32_t engineIndex, float offset, const char* path); + int32_t miniaudio_backend_sound_init_from_bytes(uint32_t engineIndex, float offset, bool stream, value bytes); + void miniaudio_backend_sound_uninit(uint32_t soundIndex); + void miniaudio_backend_sound_start(uint32_t soundIndex); + void miniaudio_backend_sound_stop(uint32_t soundIndex); + void miniaudio_backend_sound_reset(uint32_t soundIndex, float offset); + float miniaudio_backend_sound_get_time(uint32_t soundIndex, float offset); + void miniaudio_backend_sound_set_time(uint32_t soundIndex, float offset, float t); + float miniaudio_backend_sound_get_volume(uint32_t soundIndex); + void miniaudio_backend_sound_set_volume(uint32_t soundIndex, float v); + float miniaudio_backend_sound_get_pitch(uint32_t soundIndex); + void miniaudio_backend_sound_set_pitch(uint32_t soundIndex, float p); + ma_vec3f miniaudio_backend_sound_get_position(uint32_t soundIndex); + void miniaudio_backend_sound_set_position(uint32_t soundIndex, float x, float y, float z); + float miniaudio_backend_sound_get_length(uint32_t soundIndex, float offset); + void miniaudio_backend_sound_set_length(uint32_t soundIndex, float offset, float length); + int32_t miniaudio_backend_sound_get_loops(uint32_t soundIndex); + void miniaudio_backend_sound_set_loops(uint32_t soundIndex, int32_t loops); + bool miniaudio_backend_sound_is_playing(uint32_t soundIndex); + value miniaudio_backend_sound_readback_pcm(uint32_t soundIndex); + +} diff --git a/project/include/utils/File.h b/project/include/utils/File.h index 77f62fb974..42e3124626 100644 --- a/project/include/utils/File.h +++ b/project/include/utils/File.h @@ -15,6 +15,7 @@ namespace lime { File (const char* path, const char* mode); File (Bytes* bytes); + File (void* handle); bool Close (); bool Flush (); diff --git a/project/src/ExternalInterface.cpp b/project/src/ExternalInterface.cpp index f1eaece3b9..2bde952d6a 100644 --- a/project/src/ExternalInterface.cpp +++ b/project/src/ExternalInterface.cpp @@ -28,6 +28,9 @@ #include #include #include +#ifdef LIME_MINIAUDIO +#include +#endif #ifdef LIME_DR_LIBS #include #include @@ -58,6 +61,10 @@ #include #include +#ifdef LIME_MINIAUDIO +#include +#endif + #ifdef HX_WINDOWS #include #include @@ -66,8 +73,10 @@ #include #include +#include DEFINE_KIND (k_finalizer); +DEFINE_KIND (k_miniaudio_backend_engine) namespace lime { @@ -4259,6 +4268,194 @@ namespace lime { } + void lime_miniaudio_backend_init () { + + miniaudio_backend_init (); + + } + + + void lime_miniaudio_backend_uninit () { + + miniaudio_backend_uninit (); + + } + + + int lime_miniaudio_backend_engine_init (int sampleRate, int channels, int periodSizeInFrames, int gainSmoothTimeInFrames) { + + return miniaudio_backend_engine_init (sampleRate, channels, periodSizeInFrames, gainSmoothTimeInFrames); + + } + + + void lime_miniaudio_backend_engine_uninit (int engineIndex) { + + miniaudio_backend_engine_uninit (engineIndex); + + } + + + void lime_miniaudio_backend_engine_start (int engineIndex) { + + miniaudio_backend_engine_start (engineIndex); + + } + + + void lime_miniaudio_backend_engine_stop (int engineIndex) { + + miniaudio_backend_engine_stop (engineIndex); + + } + + + int lime_miniaudio_backend_sound_init_from_file (int engineIndex, float offset, const char* path) { + + return miniaudio_backend_sound_init_from_file(engineIndex, offset, path); + + } + + + int lime_miniaudio_backend_sound_init_from_bytes (int engineIndex, float offset, bool stream, value bytes) { + + return miniaudio_backend_sound_init_from_bytes (engineIndex, offset, stream, bytes); + + } + + void lime_miniaudio_backend_sound_uninit (int soundIndex) { + + miniaudio_backend_sound_uninit (soundIndex); + + } + + + void lime_miniaudio_backend_sound_start (int soundIndex) { + + miniaudio_backend_sound_start (soundIndex); + + } + + + void lime_miniaudio_backend_sound_stop(int soundIndex) { + + miniaudio_backend_sound_stop(soundIndex); + + } + + + void lime_miniaudio_backend_sound_reset(int soundIndex, float offset) { + + miniaudio_backend_sound_reset(soundIndex, offset); + + } + + + float lime_miniaudio_backend_sound_get_time(int soundIndex, float offset) { + + return miniaudio_backend_sound_get_time(soundIndex, offset); + + } + + + void lime_miniaudio_backend_sound_set_time(int soundIndex, float offset, float t) { + + miniaudio_backend_sound_set_time(soundIndex, offset, t); + + } + + + float lime_miniaudio_backend_sound_get_volume(int soundIndex) { + + return miniaudio_backend_sound_get_volume(soundIndex); + + } + + + void lime_miniaudio_backend_sound_set_volume(int soundIndex, float v) { + + miniaudio_backend_sound_set_volume(soundIndex, v); + + } + + + float lime_miniaudio_backend_sound_get_pitch(int soundIndex) { + + return miniaudio_backend_sound_get_pitch(soundIndex); + + } + + + void lime_miniaudio_backend_sound_set_pitch(int soundIndex, float p) { + + miniaudio_backend_sound_set_pitch(soundIndex, p); + + } + + + value lime_miniaudio_backend_sound_get_position(int soundIndex) { + + ma_vec3f pos = miniaudio_backend_sound_get_position(soundIndex); + + value arr = alloc_array(3); + + val_array_set_i(arr, 0, alloc_float(pos.x)); + val_array_set_i(arr, 1, alloc_float(pos.y)); + val_array_set_i(arr, 2, alloc_float(pos.z)); + + return arr; + + } + + + void lime_miniaudio_backend_sound_set_position(int soundIndex, float x, float y, float z) { + + miniaudio_backend_sound_set_position(soundIndex, x, y, z); + + } + + + float lime_miniaudio_backend_sound_get_length(int soundIndex, float offset) { + + return miniaudio_backend_sound_get_length(soundIndex, offset); + + } + + + void lime_miniaudio_backend_sound_set_length(int soundIndex, float offset, float length) { + + miniaudio_backend_sound_set_length(soundIndex, offset, length); + + } + + + int lime_miniaudio_backend_sound_get_loops(int soundIndex) { + + return miniaudio_backend_sound_get_loops(soundIndex); + + } + + + void lime_miniaudio_backend_sound_set_loops(int soundIndex, int loops) { + + miniaudio_backend_sound_set_loops(soundIndex, loops); + } + + + bool lime_miniaudio_backend_sound_is_playing(int soundIndex) { + + return miniaudio_backend_sound_is_playing(soundIndex); + + } + + + value lime_miniaudio_backend_sound_readback_pcm(int soundIndex) { + + return miniaudio_backend_sound_readback_pcm(soundIndex); + + } + + value lime_audio_decoder_open_file (value data, int codec) { AudioDecoder* decoder; @@ -4854,6 +5051,32 @@ namespace lime { DEFINE_PRIME3v (lime_window_warp_mouse); DEFINE_PRIME1 (lime_window_get_opacity); DEFINE_PRIME2v (lime_window_set_opacity); + DEFINE_PRIME0v (lime_miniaudio_backend_init); + DEFINE_PRIME0v (lime_miniaudio_backend_uninit); + DEFINE_PRIME4 (lime_miniaudio_backend_engine_init); + DEFINE_PRIME1v (lime_miniaudio_backend_engine_uninit); + DEFINE_PRIME1v (lime_miniaudio_backend_engine_start); + DEFINE_PRIME1v (lime_miniaudio_backend_engine_stop); + DEFINE_PRIME3 (lime_miniaudio_backend_sound_init_from_file); + DEFINE_PRIME4 (lime_miniaudio_backend_sound_init_from_bytes); + DEFINE_PRIME1v (lime_miniaudio_backend_sound_uninit); + DEFINE_PRIME1v (lime_miniaudio_backend_sound_start); + DEFINE_PRIME1v (lime_miniaudio_backend_sound_stop); + DEFINE_PRIME2v (lime_miniaudio_backend_sound_reset); + DEFINE_PRIME2 (lime_miniaudio_backend_sound_get_time); + DEFINE_PRIME3v (lime_miniaudio_backend_sound_set_time); + DEFINE_PRIME1 (lime_miniaudio_backend_sound_get_volume); + DEFINE_PRIME2v (lime_miniaudio_backend_sound_set_volume); + DEFINE_PRIME1 (lime_miniaudio_backend_sound_get_pitch); + DEFINE_PRIME2v (lime_miniaudio_backend_sound_set_pitch); + DEFINE_PRIME1 (lime_miniaudio_backend_sound_get_position); + DEFINE_PRIME4v (lime_miniaudio_backend_sound_set_position); + DEFINE_PRIME2 (lime_miniaudio_backend_sound_get_length) + DEFINE_PRIME3v (lime_miniaudio_backend_sound_set_length) + DEFINE_PRIME1 (lime_miniaudio_backend_sound_get_loops) + DEFINE_PRIME2v (lime_miniaudio_backend_sound_set_loops) + DEFINE_PRIME1 (lime_miniaudio_backend_sound_is_playing); + DEFINE_PRIME1 (lime_miniaudio_backend_sound_readback_pcm); DEFINE_PRIME2 (lime_audio_decoder_open_file); DEFINE_PRIME2 (lime_audio_decoder_open_bytes); DEFINE_PRIME1 (lime_audio_decoder_info); diff --git a/project/src/media/miniaudio/MiniaudioBackend.cpp b/project/src/media/miniaudio/MiniaudioBackend.cpp new file mode 100644 index 0000000000..eaa77ff882 --- /dev/null +++ b/project/src/media/miniaudio/MiniaudioBackend.cpp @@ -0,0 +1,533 @@ +#define MINIAUDIO_IMPLEMENTATION +#include +#include +#include + +#include +#include + +#include + +#include +#include +#include +#include + +namespace lime { + + #define ENGINE_VECTOR_STARTING_CAPACITY 4 + #define SOUND_VECTOR_STARTING_CAPACITY 32 + + typedef enum { + MA_BACKEND_SOUND_DATA_SOURCE_TYPE_INTERNAL = 0, // the data source is managed internally by miniaudio (this happens when we init from a file) + MA_BACKEND_SOUND_DATA_SOURCE_TYPE_DECODER = 1, + MA_BACKEND_SOUND_DATA_SOURCE_TYPE_AUDIO_BUFFER = 2 + } MABackendSoundDataSourceType; + + typedef struct { + uint32_t soundIndex; + } MABackendEndCallbackData; + + typedef struct { + ma_resource_manager resourceManager; + ma_engine engine; + bool locked; + } MABackendEngine; + + typedef struct { + float stopTime; + int32_t loops; + MABackendEndCallbackData endCallbackData; + ma_sound sound; + MABackendSoundDataSourceType dataSourceType; + union { + ma_decoder decoder; + ma_audio_buffer audioBuffer; + } dataSource; + bool locked; + } MABackendSound; + + typedef struct { + uint32_t capacity; + + void* ptr; + } Vector; + + Vector engineVector = {0, NULL}; + Vector soundVector = {0, NULL}; + + static inline void vec_expand(Vector* vec, uint32_t elementSize) { + uint32_t initialCapacity = vec->capacity; + vec->capacity *= 2; + + vec->ptr = realloc(vec->ptr, elementSize * vec->capacity); + + memset(((uint8_t*)vec->ptr) + initialCapacity * elementSize, 0, (vec->capacity - initialCapacity) * elementSize); + } + + #define vec_access(vec, type, idx) ((type*)vec.ptr)[idx] + + #define vec_get_unlocked(vec, type, out) { \ + out = ~0U; \ + for (uint32_t i = 0; i < vec.capacity; i++) { \ + if (!vec_access(vec, type, i).locked) { \ + out = i; \ + break; \ + } \ + } \ + if (out == ~0U) { \ + out = vec.capacity; \ + vec_expand(&vec, sizeof(type)); \ + } \ + } + + ma_result ma_File_onOpen(ma_vfs* pVFS, const char* pFilePath, ma_uint32 openMode, ma_vfs_file* pFile) { + if (openMode & MA_OPEN_MODE_READ) { + if (openMode & MA_OPEN_MODE_WRITE) + *pFile = File(pFilePath, "rb+").handle; + else *pFile = File(pFilePath, "rb").handle; + } else if (openMode & MA_OPEN_MODE_WRITE) + *pFile = File(pFilePath, "wb").handle; + + if (!(*pFile)) return MA_IO_ERROR; + + return MA_SUCCESS; + } + + ma_result ma_File_onClose(ma_vfs* pVFS, ma_vfs_file file) { + File f(file); + f.Close(); + + return MA_SUCCESS; + } + + ma_result ma_File_onRead(ma_vfs* pVFS, ma_vfs_file file, void* pDst, size_t sizeInBytes, size_t* pBytesRead) { + File f(file); + *pBytesRead = f.Read(pDst, sizeInBytes); + + if (*pBytesRead < sizeInBytes) return MA_AT_END; + + return MA_SUCCESS; + } + + ma_result ma_File_onWrite(ma_vfs* pVFS, ma_vfs_file file, const void* pSrc, size_t sizeInBytes, size_t* pBytesWritten) { + File f(file); + *pBytesWritten = f.Write(pSrc, sizeInBytes); + + if (*pBytesWritten < sizeInBytes) return MA_IO_ERROR; + + return MA_SUCCESS; + } + + ma_result ma_File_onSeek(ma_vfs* pVFS, ma_vfs_file file, ma_int64 offset, ma_seek_origin origin) { + File f(file); + + int corigin; + + switch (origin) { + case ma_seek_origin_start: + corigin = SEEK_SET; + break; + case ma_seek_origin_end: + corigin = SEEK_END; + break; + case ma_seek_origin_current: + corigin = SEEK_CUR; + break; + } + + if (f.Seek(offset, corigin) == -1) return MA_IO_ERROR; + + return MA_SUCCESS; + } + + ma_result ma_File_onTell(ma_vfs* pVFS, ma_vfs_file file, ma_int64* pCursor) { + File f(file); + + *pCursor = f.Tell(); + + if (*pCursor == -1) return MA_IO_ERROR; + + return MA_SUCCESS; + } + + ma_result ma_File_onInfo(ma_vfs* pVFS, ma_vfs_file file, ma_file_info* pInfo) { + File f(file); + + uint64_t current = f.Tell(); + + if (f.Seek(0, SEEK_END) == -1) return MA_IO_ERROR; + + pInfo->sizeInBytes = f.Tell(); + + if (pInfo->sizeInBytes == -1) return MA_IO_ERROR; + + if (f.Seek(current == -1 ? 0 : current, SEEK_SET) == -1) return MA_IO_ERROR; + + return MA_SUCCESS; + } + + ma_vfs_callbacks vfscb; + + ma_decoding_backend_vtable* pCustomDecodingBackends[] = { + ma_decoding_backend_libvorbis, + ma_decoding_backend_libopus + }; + + void miniaudio_backend_sound_end_callback(void* pUserData, ma_sound* pSound) { + MABackendEndCallbackData* endCallbackData = (MABackendEndCallbackData*)pUserData; + + if (vec_access(soundVector, MABackendSound, endCallbackData->soundIndex).loops < 1) { + ma_sound_stop(pSound); + } else { + vec_access(soundVector, MABackendSound, endCallbackData->soundIndex).loops--; + ma_sound_start(pSound); + } + } + + void miniaudio_backend_init() { + engineVector.capacity = ENGINE_VECTOR_STARTING_CAPACITY; + engineVector.ptr = malloc(sizeof(MABackendEngine) * ENGINE_VECTOR_STARTING_CAPACITY); + soundVector.capacity = SOUND_VECTOR_STARTING_CAPACITY; + soundVector.ptr = malloc(sizeof(MABackendSound) * SOUND_VECTOR_STARTING_CAPACITY); + + memset(((uint8_t*)engineVector.ptr), 0, sizeof(MABackendEngine) * ENGINE_VECTOR_STARTING_CAPACITY); + memset(((uint8_t*)soundVector.ptr), 0, sizeof(MABackendSound) * SOUND_VECTOR_STARTING_CAPACITY); + + vfscb.onOpen = ma_File_onOpen; + vfscb.onClose = ma_File_onClose; + vfscb.onRead = ma_File_onRead; + vfscb.onWrite = ma_File_onWrite; + vfscb.onSeek = ma_File_onSeek; + vfscb.onTell = ma_File_onTell; + vfscb.onInfo = ma_File_onInfo; + } + + void miniaudio_backend_uninit() { + free(soundVector.ptr); + free(engineVector.ptr); + } + + int32_t miniaudio_backend_engine_init(uint32_t sampleRate, uint32_t channels, uint32_t periodSizeInFrames, uint32_t gainSmoothTimeInFrames) { + uint32_t unlockedIndex; + vec_get_unlocked(engineVector, MABackendEngine, unlockedIndex); + + ma_resource_manager_config rmConfig = ma_resource_manager_config_init(); + rmConfig.ppCustomDecodingBackendVTables = pCustomDecodingBackends; + rmConfig.customDecodingBackendCount = 2; + rmConfig.pVFS = &vfscb; + + if (ma_resource_manager_init(&rmConfig, &vec_access(engineVector, MABackendEngine, unlockedIndex).resourceManager) != MA_SUCCESS) { + fprintf(stderr, "[lime miniaudio backend]: ERROR: failed to initialize an ma_resource_manager\n"); + return -1; + } + + ma_engine_config engineConfig = ma_engine_config_init(); + engineConfig.sampleRate = sampleRate; + engineConfig.channels = channels; + engineConfig.periodSizeInFrames = periodSizeInFrames; + engineConfig.gainSmoothTimeInFrames = gainSmoothTimeInFrames; + engineConfig.pResourceManager = &vec_access(engineVector, MABackendEngine, unlockedIndex).resourceManager; + + if (ma_engine_init(&engineConfig, &vec_access(engineVector, MABackendEngine, unlockedIndex).engine) != MA_SUCCESS) { + fprintf(stderr, "[lime miniaudio backend]: ERROR: failed to initialize an ma_engine\n"); + return -1; + } + + vec_access(engineVector, MABackendEngine, unlockedIndex).locked = true; + + return unlockedIndex; + } + + void miniaudio_backend_engine_uninit(uint32_t engineIndex) { + ma_engine_uninit(&vec_access(engineVector, MABackendEngine, engineIndex).engine); + ma_resource_manager_uninit(&vec_access(engineVector, MABackendEngine, engineIndex).resourceManager); + vec_access(engineVector, MABackendEngine, engineIndex).locked = false; + } + + void miniaudio_backend_engine_start(uint32_t engineIndex) { + if (ma_engine_start(&vec_access(engineVector, MABackendEngine, engineIndex).engine) != MA_SUCCESS) { + fprintf(stderr, "[lime miniaudio backend]: WARNING: failed start an ma_engine with index %d\n", engineIndex); + } + } + + void miniaudio_backend_engine_stop(uint32_t engineIndex) { + if (ma_engine_stop(&vec_access(engineVector, MABackendEngine, engineIndex).engine) != MA_SUCCESS) { + fprintf(stderr, "[lime miniaudio backend]: WARNING: failed stop an ma_engine with index %d\n", engineIndex); + } + } + + int32_t miniaudio_backend_sound_init_from_file(uint32_t engineIndex, float offset, const char* path) { + uint32_t unlockedIndex; + vec_get_unlocked(soundVector, MABackendSound, unlockedIndex); + + if (ma_sound_init_from_file(&vec_access(engineVector, MABackendEngine, engineIndex).engine, path, MA_SOUND_FLAG_STREAM, NULL, NULL, &vec_access(soundVector, MABackendSound, unlockedIndex).sound) != MA_SUCCESS) { + fprintf(stderr, "[lime miniaudio backend]: ERROR: failed to initialize an ma_sound at path %s\n", path); + return -1; + } + + uint32_t sampleRate; + if (ma_sound_get_data_format(&vec_access(soundVector, MABackendSound, unlockedIndex).sound, NULL, NULL, &sampleRate, NULL, 0) != MA_SUCCESS) { + fprintf(stderr, "[lime miniaudio backend]: WARNING: failed to get data format of an ma_sound with index %d\n", unlockedIndex); + sampleRate = 0; + } + + uint64_t frameOffset = offset * sampleRate / 1000; + + if (ma_sound_seek_to_pcm_frame(&vec_access(soundVector, MABackendSound, unlockedIndex).sound, frameOffset) != MA_SUCCESS) { + fprintf(stderr, "[lime miniaudio backend]: WARNING: failed to seek an ma_sound with index %d to pcm frame offset %I64d\n", unlockedIndex, frameOffset); + } + + vec_access(soundVector, MABackendSound, unlockedIndex).dataSourceType = MA_BACKEND_SOUND_DATA_SOURCE_TYPE_INTERNAL; + vec_access(soundVector, MABackendSound, unlockedIndex).stopTime = 0; + vec_access(soundVector, MABackendSound, unlockedIndex).loops = 0; + vec_access(soundVector, MABackendSound, unlockedIndex).endCallbackData = {unlockedIndex}; + vec_access(soundVector, MABackendSound, unlockedIndex).locked = true; + + if (ma_sound_set_end_callback(&vec_access(soundVector, MABackendSound, unlockedIndex).sound, miniaudio_backend_sound_end_callback, &vec_access(soundVector, MABackendSound, unlockedIndex).endCallbackData) != MA_SUCCESS) { + fprintf(stderr, "[lime miniaudio backend]: WARNING: failed to the end callback for an ma_sound with index %d\n", unlockedIndex); + } + + return unlockedIndex; + } + + int32_t miniaudio_backend_sound_init_from_bytes(uint32_t engineIndex, float offset, bool stream, value bytes) { + uint32_t unlockedIndex; + vec_get_unlocked(soundVector, MABackendSound, unlockedIndex); + + ma_data_source* pDataSource = NULL; + + if (stream) { + ma_decoder_config decoderConfig = ma_decoder_config_init(ma_format_unknown, 0, 0); + + decoderConfig.ppCustomBackendVTables = pCustomDecodingBackends; + decoderConfig.customBackendCount = 2; + + if (ma_decoder_init_memory(buffer_data(val_to_buffer(bytes)), val_array_size(bytes), &decoderConfig, &vec_access(soundVector, MABackendSound, unlockedIndex).dataSource.decoder) != MA_SUCCESS) { + fprintf(stderr, "[lime miniaudio backend]: ERROR: failed to create an ma_decoder for bytes\n"); + return -1; + } + + pDataSource = &vec_access(soundVector, MABackendSound, unlockedIndex).dataSource.decoder; + vec_access(soundVector, MABackendSound, unlockedIndex).dataSourceType = MA_BACKEND_SOUND_DATA_SOURCE_TYPE_DECODER; + } else { + ma_decoder_config decoderConfig = ma_decoder_config_init(ma_format_unknown, 0, 0); + + decoderConfig.ppCustomBackendVTables = pCustomDecodingBackends; + decoderConfig.customBackendCount = 2; + + uint64_t pcmFrameCount; + void* pPcmFrames; + + // we cant avoid allocating memory here, creating a lot of non-streamed sounds is not intended + if (ma_decode_memory(buffer_data(val_to_buffer(bytes)), val_array_size(bytes), &decoderConfig, &pcmFrameCount, &pPcmFrames) != MA_SUCCESS) { + fprintf(stderr, "[lime miniaudio backend]: ERROR: failed to decode bytes when creating a non-streamed ma_sound\n"); + return -1; + } + + ma_audio_buffer_config audioBufferConfig = ma_audio_buffer_config_init(decoderConfig.format, decoderConfig.channels, pcmFrameCount, pPcmFrames, NULL); + audioBufferConfig.sampleRate = decoderConfig.sampleRate; // miniaudio dev, why is this not an argument of ma_audio_buffer_config_init? + + if (ma_audio_buffer_init(&audioBufferConfig, &vec_access(soundVector, MABackendSound, unlockedIndex).dataSource.audioBuffer) != MA_SUCCESS) { + fprintf(stderr, "[lime miniaudio backend]: ERROR: failed to an ma_audio_buffer for decoded bytes\n"); + } + + vec_access(soundVector, MABackendSound, unlockedIndex).dataSource.audioBuffer.ownsData = MA_TRUE; // hijack this so that the audio buffer will free the memory allocated in ma_decode_memory + + pDataSource = &vec_access(soundVector, MABackendSound, unlockedIndex).dataSource.audioBuffer; + vec_access(soundVector, MABackendSound, unlockedIndex).dataSourceType = MA_BACKEND_SOUND_DATA_SOURCE_TYPE_AUDIO_BUFFER; + } + + // note that MA_SOUND_FLAG_STREAM is useless here, as it is a resource manager flag, ma_decoder does streaming automatically + if (ma_sound_init_from_data_source(&vec_access(engineVector, MABackendEngine, engineIndex).engine, pDataSource, 0, NULL, &vec_access(soundVector, MABackendSound, unlockedIndex).sound) != MA_SUCCESS) { + ma_decoder_uninit(&vec_access(soundVector, MABackendSound, unlockedIndex).dataSource.decoder); + fprintf(stderr, "[lime miniaudio backend]: ERROR: failed to initialize an ma_sound from bytes\n"); + return -1; + } + + uint32_t sampleRate; + if (ma_sound_get_data_format(&vec_access(soundVector, MABackendSound, unlockedIndex).sound, NULL, NULL, &sampleRate, NULL, 0) != MA_SUCCESS) { + fprintf(stderr, "[lime miniaudio backend]: WARNING: failed to get data format of an ma_sound with index %d\n", unlockedIndex); + sampleRate = 0; + } + + uint64_t frameOffset = offset * sampleRate / 1000; + + if (ma_sound_seek_to_pcm_frame(&vec_access(soundVector, MABackendSound, unlockedIndex).sound, frameOffset) != MA_SUCCESS) { + fprintf(stderr, "[lime miniaudio backend]: WARNING: failed to seek an ma_sound with index %d to pcm frame offset %I64d\n", unlockedIndex, frameOffset); + } + + vec_access(soundVector, MABackendSound, unlockedIndex).stopTime = 0; + vec_access(soundVector, MABackendSound, unlockedIndex).loops = 0; + vec_access(soundVector, MABackendSound, unlockedIndex).endCallbackData = {unlockedIndex}; + vec_access(soundVector, MABackendSound, unlockedIndex).locked = false; + + if (ma_sound_set_end_callback(&vec_access(soundVector, MABackendSound, unlockedIndex).sound, miniaudio_backend_sound_end_callback, &vec_access(soundVector, MABackendSound, unlockedIndex).endCallbackData) != MA_SUCCESS) { + fprintf(stderr, "[lime miniaudio backend]: WARNING: failed to the end callback for an ma_sound with index %d\n", unlockedIndex); + } + + return unlockedIndex; + } + + void miniaudio_backend_sound_uninit(uint32_t soundIndex) { + ma_sound_uninit(&vec_access(soundVector, MABackendSound, soundIndex).sound); + + switch (vec_access(soundVector, MABackendSound, soundIndex).dataSourceType) { + case MA_BACKEND_SOUND_DATA_SOURCE_TYPE_DECODER: + ma_decoder_uninit(&vec_access(soundVector, MABackendSound, soundIndex).dataSource.decoder); + break; + case MA_BACKEND_SOUND_DATA_SOURCE_TYPE_AUDIO_BUFFER: + ma_audio_buffer_uninit(&vec_access(soundVector, MABackendSound, soundIndex).dataSource.audioBuffer); + break; + case MA_BACKEND_SOUND_DATA_SOURCE_TYPE_INTERNAL: break; + } + + vec_access(soundVector, MABackendSound, soundIndex).locked = false; + } + + void miniaudio_backend_sound_start(uint32_t soundIndex) { + if (ma_sound_start(&vec_access(soundVector, MABackendSound, soundIndex).sound) != MA_SUCCESS) { + fprintf(stderr, "[lime miniaudio backend]: WARNING: failed to start a sound with index %d\n", soundIndex); + } + } + + void miniaudio_backend_sound_stop(uint32_t soundIndex) { + if (ma_sound_stop(&vec_access(soundVector, MABackendSound, soundIndex).sound) != MA_SUCCESS) { + fprintf(stderr, "[lime miniaudio backend]: WARNING: failed to stop a sound with index %d\n", soundIndex); + } + } + + void miniaudio_backend_sound_reset(uint32_t soundIndex, float offset) { + miniaudio_backend_sound_stop(soundIndex); + + uint32_t sampleRate; + if (ma_sound_get_data_format(&vec_access(soundVector, MABackendSound, soundIndex).sound, NULL, NULL, &sampleRate, NULL, 0) != MA_SUCCESS) { + fprintf(stderr, "[lime miniaudio backend]: WARNING: failed to get data format of an ma_sound with index %d\n", soundIndex); + return; + } + + uint64_t frameOffset = offset * sampleRate / 1000; + + if (ma_sound_seek_to_pcm_frame(&vec_access(soundVector, MABackendSound, soundIndex).sound, frameOffset) != MA_SUCCESS) { + fprintf(stderr, "[lime miniaudio backend]: WARNING: failed to seek an ma_sound with index %d to pcm frame offset %I64d\n", soundIndex, frameOffset); + } + } + + float miniaudio_backend_sound_get_time(uint32_t soundIndex, float offset) { + uint32_t sampleRate; + if (ma_sound_get_data_format(&vec_access(soundVector, MABackendSound, soundIndex).sound, NULL, NULL, &sampleRate, NULL, 0) != MA_SUCCESS) { + fprintf(stderr, "[lime miniaudio backend]: WARNING: failed to get data format of an ma_sound with index %d\n", soundIndex); + } + + uint64_t cursorPcmFrames = 0; + + if (ma_sound_get_cursor_in_pcm_frames(&vec_access(soundVector, MABackendSound, soundIndex).sound, &cursorPcmFrames) != MA_SUCCESS) { + fprintf(stderr, "[lime miniaudio backend]: WARNING: failed to get cursor of an ma_sound with index %d\n", soundIndex); + } + + return cursorPcmFrames * 1000 / sampleRate - offset; + } + + void miniaudio_backend_sound_set_time(uint32_t soundIndex, float offset, float t) { + uint32_t sampleRate; + if (ma_sound_get_data_format(&vec_access(soundVector, MABackendSound, soundIndex).sound, NULL, NULL, &sampleRate, NULL, 0) != MA_SUCCESS) { + fprintf(stderr, "[lime miniaudio backend]: WARNING: failed to get data format of an ma_sound with index %d\n", soundIndex); + return; + } + + uint64_t frameOffset = offset * sampleRate / 1000; + + float tPcmFrames = t * sampleRate / 1000; + + if (ma_sound_seek_to_pcm_frame(&vec_access(soundVector, MABackendSound, soundIndex).sound, tPcmFrames - frameOffset) != MA_SUCCESS) { + fprintf(stderr, "[lime miniaudio backend]: WARNING: failed to seek an ma_sound with index %d to pcm frame offset %I64d\n", soundIndex, frameOffset); + } + } + + float miniaudio_backend_sound_get_volume(uint32_t soundIndex) { + return ma_sound_get_volume(&vec_access(soundVector, MABackendSound, soundIndex).sound); + } + + void miniaudio_backend_sound_set_volume(uint32_t soundIndex, float v) { + ma_sound_set_volume(&vec_access(soundVector, MABackendSound, soundIndex).sound, v); + } + + float miniaudio_backend_sound_get_pitch(uint32_t soundIndex) { + return ma_sound_get_pitch(&vec_access(soundVector, MABackendSound, soundIndex).sound); + } + + void miniaudio_backend_sound_set_pitch(uint32_t soundIndex, float p) { + ma_sound_set_pitch(&vec_access(soundVector, MABackendSound, soundIndex).sound, p); + } + + ma_vec3f miniaudio_backend_sound_get_position(uint32_t soundIndex) { + return ma_sound_get_position(&vec_access(soundVector, MABackendSound, soundIndex).sound); + } + + void miniaudio_backend_sound_set_position(uint32_t soundIndex, float x, float y, float z) { + ma_sound_set_position(&vec_access(soundVector, MABackendSound, soundIndex).sound, x, y, z); + } + + float miniaudio_backend_sound_get_length(uint32_t soundIndex, float offset) { + uint64_t lengthPcmFrames; + if (ma_sound_get_length_in_pcm_frames(&vec_access(soundVector, MABackendSound, soundIndex).sound, &lengthPcmFrames) != MA_SUCCESS) { + fprintf(stderr, "[lime miniaudio backend]: WARNING: failed to get length of an ma_sound with index\n", soundIndex); + } + + uint32_t sampleRate; + if (ma_sound_get_data_format(&vec_access(soundVector, MABackendSound, soundIndex).sound, NULL, NULL, &sampleRate, NULL, 0) != MA_SUCCESS) { + fprintf(stderr, "[lime miniaudio backend]: WARNING: failed to get data format of an ma_sound with index %d\n", soundIndex); + return vec_access(soundVector, MABackendSound, soundIndex).stopTime; + } + + float length = lengthPcmFrames * 1000 / sampleRate - offset; + + if (vec_access(soundVector, MABackendSound, soundIndex).stopTime < length && vec_access(soundVector, MABackendSound, soundIndex).stopTime > 0) + length = vec_access(soundVector, MABackendSound, soundIndex).stopTime; + + return length; + } + + void miniaudio_backend_sound_set_length(uint32_t soundIndex, float offset, float length) { + vec_access(soundVector, MABackendSound, soundIndex).stopTime = length; + + ma_sound_set_stop_time_in_milliseconds(&vec_access(soundVector, MABackendSound, soundIndex).sound, length + offset); + } + + int32_t miniaudio_backend_sound_get_loops(uint32_t soundIndex) { + return vec_access(soundVector, MABackendSound, soundIndex).loops; + } + + void miniaudio_backend_sound_set_loops(uint32_t soundIndex, int32_t loops) { + vec_access(soundVector, MABackendSound, soundIndex).loops = loops; + } + + bool miniaudio_backend_sound_is_playing(uint32_t soundIndex) { + return ma_sound_is_playing(&vec_access(soundVector, MABackendSound, soundIndex).sound) == MA_TRUE ? true : false; + } + + value miniaudio_backend_sound_readback_pcm(uint32_t soundIndex) { + ma_audio_buffer* pAudioBuffer = NULL; + value obj = alloc_empty_object(); + + switch (vec_access(soundVector, MABackendSound, soundIndex).dataSourceType) { + case MA_BACKEND_SOUND_DATA_SOURCE_TYPE_INTERNAL: + case MA_BACKEND_SOUND_DATA_SOURCE_TYPE_DECODER: + fprintf(stderr, "[lime miniaudio backend]: WARNING: ma_sound with index %d is streamed and does not support readback\n", soundIndex); + return alloc_null(); + case MA_BACKEND_SOUND_DATA_SOURCE_TYPE_AUDIO_BUFFER: + pAudioBuffer = &vec_access(soundVector, MABackendSound, soundIndex).dataSource.audioBuffer; + + alloc_field(obj, val_id("format"), alloc_int(pAudioBuffer->ref.format)); + alloc_field(obj, val_id("channels"), alloc_int(pAudioBuffer->ref.channels)); + alloc_field(obj, val_id("sampleRate"), alloc_int(pAudioBuffer->ref.sampleRate)); + alloc_field(obj, val_id("pcmFrameCount"), alloc_int(pAudioBuffer->ref.sizeInFrames)); + alloc_field(obj, val_id("pcmFrames"), cffi::alloc_pointer((void*)pAudioBuffer->ref.pData)); + + break; + } + + return obj; + } + +} \ No newline at end of file diff --git a/project/src/utils/File.cpp b/project/src/utils/File.cpp index 5d1ef28fcb..155dd764a0 100644 --- a/project/src/utils/File.cpp +++ b/project/src/utils/File.cpp @@ -41,6 +41,13 @@ namespace lime { } + File::File (void* handle) { + + this->handle = handle; + + } + + bool File::Close () { if (handle) { diff --git a/run.n b/run.n index 9bd56cf69062ec8f5012eafee2db193ccdb5b61c..c50d3c6746b55830162dc8939e9cb47fd66a44ca 100644 GIT binary patch delta 2169 zcmb7FJx{_=6xFCf6CEKj7$wo5k-`%p#<=)FP)Lv<;07_3R%w7f^7lX>+PPCN2~&~h&Gs!NRL*Fff5B#6~=LMdWF z{>vzE#YKf9qC2*+kY3mO>a8;w}W@WCmRhNxl{ClU(ndmyC z`5;OlNMG`9S-ZheM(;)aP`=No4}vW0qC2TklGLro3cb9d|8^90UE1#Wts6x!MiIx( zp4KoVtv6dp`wl~L&J9_Ep4GXS1hp|4yM-X{vE_5vFT#bF{AM5CRnt24=4N~eBDP=; M<@jqkKArpi0pl4cF8}}l delta 1727 zcma)6K}!Nb6s}vhh>QfKl%k-_$<-txgDfLKE7Ry0+wQC`tvk!^815;gA7HSTuH6gj z)UhAXp;PBhLD$Y*(uqoK-q?A*`QG=w_q})ZG<5Sgbo(|ZqncwQgDA3r4E-2Y}fpVoohgh(+rJrCo|)*RB_~*C1_dHm6mTBeIHZP+cQB z7TnSe*qBB%#5d+4I({ZBe$=*TQ(*c}gcvfjkIRtEZe(7j6^0z;)7M|&di1Dr!9;WeORn`h*FTz4G5>b|`LT#+>I$}~ zPE5!R?=BWthsYR(V!IY4Xv0?G637yk{QRsPN>luYO9}BGUZu&ZP|){srE8i7??>BE zroRQ>tx>B6CdX^W1Lb}dGp0h+&ZP7NrL$ayXrNIjF^2oq%9;YTorU(k*NL}H`H0_p zN%;6Ck1UlnNCvw7Qsx5;Zx9(K9eW!$7c`ej=A$ea&4(`2w0+hdI Ai~s-t diff --git a/src/lime/_internal/backend/native/NativeCFFI.hx b/src/lime/_internal/backend/native/NativeCFFI.hx index 0fd2a912f4..610376d16c 100644 --- a/src/lime/_internal/backend/native/NativeCFFI.hx +++ b/src/lime/_internal/backend/native/NativeCFFI.hx @@ -39,7 +39,7 @@ typedef CFFIPointer = Dynamic; #end // #if hl -// typedef TNative_Application = hl.Abstract<"Application">; +// typedef TNative_Application = Dynamic<"Application">; // #end #if (!macro && !lime_doc_gen) #if disable_cffi @@ -384,6 +384,56 @@ class NativeCFFI @:cffi private static function lime_window_event_manager_register(callback:Dynamic, eventObject:Dynamic):Void; + @:cffi private static function lime_miniaudio_backend_init():Void; + + @:cffi private static function lime_miniaudio_backend_uninit():Void; + + @:cffi private static function lime_miniaudio_backend_engine_init(sampleRate:Int, channels:Int, periodSizeInFrames:Int, gainSmoothTimeInFrames:Int):Int; + + @:cffi private static function lime_miniaudio_backend_engine_uninit(engineIndex:Int):Void; + + @:cffi private static function lime_miniaudio_backend_engine_start(engineIndex:Int):Void; + + @:cffi private static function lime_miniaudio_backend_engine_stop(engineIndex:Int):Void; + + @:cffi private static function lime_miniaudio_backend_sound_init_from_file(engineIndex:Int, offset:Float, path:Dynamic):Int; + + @:cffi private static function lime_miniaudio_backend_sound_init_from_bytes(engineIndex:Int, offset:Float, stream:Bool, bytes:Dynamic):Int; + + @:cffi private static function lime_miniaudio_backend_sound_start(soundIndex:Int):Void; + + @:cffi private static function lime_miniaudio_backend_sound_stop(soundIndex:Int):Void; + + @:cffi private static function lime_miniaudio_backend_sound_reset(soundIndex:Int, offset:Float):Void; + + @:cffi private static function lime_miniaudio_backend_sound_get_time(soundIndex:Int, offset:Float):Float; + + @:cffi private static function lime_miniaudio_backend_sound_set_time(soundIndex:Int, offset:Float, t:Float):Void; + + @:cffi private static function lime_miniaudio_backend_sound_get_volume(soundIndex:Int):Float; + + @:cffi private static function lime_miniaudio_backend_sound_set_volume(soundIndex:Int, v:Float):Void; + + @:cffi private static function lime_miniaudio_backend_sound_get_pitch(soundIndex:Int):Float; + + @:cffi private static function lime_miniaudio_backend_sound_set_pitch(soundIndex:Int, p:Float):Void; + + @:cffi private static function lime_miniaudio_backend_sound_set_position(soundIndex:Int, x:Float, y:Float, z:Float):Void; + + @:cffi private static function lime_miniaudio_backend_sound_get_position(soundIndex:Int):Array; + + @:cffi private static function lime_miniaudio_backend_sound_get_length(soundIndex:Int, offset:Float):Float; + + @:cffi private static function lime_miniaudio_backend_sound_set_length(soundIndex:Int, offset:Float, l:Float):Void; + + @:cffi private static function lime_miniaudio_backend_sound_get_loops(soundIndex:Int):Int; + + @:cffi private static function lime_miniaudio_backend_sound_set_loops(soundIndex:Int, l:Int):Void; + + @:cffi private static function lime_miniaudio_backend_sound_is_playing(soundIndex:Int):Bool; + + @:cffi private static function lime_miniaudio_backend_sound_readback_pcm(soundIndex:Int):Dynamic; + @:cffi private static function lime_audio_decoder_open_file(path:Dynamic, codec:Int):Dynamic; @:cffi private static function lime_audio_decoder_open_bytes(data:Dynamic, codec:Int):Dynamic; @@ -685,6 +735,58 @@ class NativeCFFI "oiiv", false)); private static var lime_window_event_manager_register = new cpp.Callablecpp.Object->cpp.Void>(cpp.Prime._loadPrime("lime", "lime_window_event_manager_register", "oov", false)); + private static var lime_miniaudio_backend_init = new cpp.Callablecpp.Void>(cpp.Prime._loadPrime("lime", + "lime_miniaudio_backend_init", "v", false)); + private static var lime_miniaudio_backend_uninit = new cpp.Callablecpp.Void>(cpp.Prime._loadPrime("lime", + "lime_miniaudio_backend_uninit", "v", false)); + private static var lime_miniaudio_backend_engine_init = new cpp.CallableInt->Int->Int->cpp.UInt32>(cpp.Prime._loadPrime("lime", + "lime_miniaudio_backend_engine_init", "iiiii", false)); + private static var lime_miniaudio_backend_engine_uninit = new cpp.Callablecpp.Void>(cpp.Prime._loadPrime("lime", + "lime_miniaudio_backend_engine_uninit", "iv", false)); + private static var lime_miniaudio_backend_engine_start = new cpp.Callablecpp.Void>(cpp.Prime._loadPrime("lime", + "lime_miniaudio_backend_engine_start", "iv", false)); + private static var lime_miniaudio_backend_engine_stop = new cpp.Callablecpp.Void>(cpp.Prime._loadPrime("lime", + "lime_miniaudio_backend_engine_stop", "iv", false)); + private static var lime_miniaudio_backend_sound_init_from_file = new cpp.Callablecpp.Float32->cpp.ConstCharStar->cpp.UInt32>(cpp.Prime._loadPrime("lime", + "lime_miniaudio_backend_sound_init_from_file", "ifci", false)); + private static var lime_miniaudio_backend_sound_init_from_bytes = new cpp.Callablecpp.Float32->Bool->cpp.Object->cpp.UInt32>(cpp.Prime._loadPrime("lime", + "lime_miniaudio_backend_sound_init_from_bytes", "ifboi", false)); + private static var lime_miniaudio_backend_sound_uninit = new cpp.Callablecpp.Void>(cpp.Prime._loadPrime("lime", + "lime_miniaudio_backend_sound_uninit", "iv", false)); + private static var lime_miniaudio_backend_sound_start = new cpp.Callablecpp.Void>(cpp.Prime._loadPrime("lime", + "lime_miniaudio_backend_sound_start", "iv", false)); + private static var lime_miniaudio_backend_sound_stop = new cpp.Callablecpp.Void>(cpp.Prime._loadPrime("lime", + "lime_miniaudio_backend_sound_stop", "iv", false)); + private static var lime_miniaudio_backend_sound_reset = new cpp.Callablecpp.Float32->cpp.Void>(cpp.Prime._loadPrime("lime", + "lime_miniaudio_backend_sound_reset", "ifv", false)); + private static var lime_miniaudio_backend_sound_get_time = new cpp.Callablecpp.Float32->cpp.Float32>(cpp.Prime._loadPrime("lime", + "lime_miniaudio_backend_sound_get_time", "iff", false)); + private static var lime_miniaudio_backend_sound_set_time = new cpp.Callablecpp.Float32->cpp.Float32->cpp.Void>(cpp.Prime._loadPrime("lime", + "lime_miniaudio_backend_sound_set_time", "iffv", false)); + private static var lime_miniaudio_backend_sound_get_volume = new cpp.Callablecpp.Float32>(cpp.Prime._loadPrime("lime", + "lime_miniaudio_backend_sound_get_volume", "if", false)); + private static var lime_miniaudio_backend_sound_set_volume = new cpp.Callablecpp.Float32->cpp.Void>(cpp.Prime._loadPrime("lime", + "lime_miniaudio_backend_sound_set_volume", "ifv", false)); + private static var lime_miniaudio_backend_sound_get_pitch = new cpp.Callablecpp.Float32>(cpp.Prime._loadPrime("lime", + "lime_miniaudio_backend_sound_get_pitch", "if", false)); + private static var lime_miniaudio_backend_sound_set_pitch = new cpp.Callablecpp.Float32->cpp.Void>(cpp.Prime._loadPrime("lime", + "lime_miniaudio_backend_sound_set_pitch", "ifv", false)); + private static var lime_miniaudio_backend_sound_get_position = new cpp.Callablecpp.Object>(cpp.Prime._loadPrime("lime", + "lime_miniaudio_backend_sound_get_position", "io", false)); + private static var lime_miniaudio_backend_sound_set_position = new cpp.Callablecpp.Float32->cpp.Float32->cpp.Float32->cpp.Void>(cpp.Prime._loadPrime("lime", + "lime_miniaudio_backend_sound_set_position", "ifffv", false)); + private static var lime_miniaudio_backend_sound_get_length = new cpp.Callablecpp.Float32->cpp.Float32>(cpp.Prime._loadPrime("lime", + "lime_miniaudio_backend_sound_get_length", "iff", false)); + private static var lime_miniaudio_backend_sound_set_length = new cpp.Callablecpp.Float32->cpp.Float32->cpp.Void>(cpp.Prime._loadPrime("lime", + "lime_miniaudio_backend_sound_set_length", "iffv", false)); + private static var lime_miniaudio_backend_sound_get_loops = new cpp.Callablecpp.Int32>(cpp.Prime._loadPrime("lime", + "lime_miniaudio_backend_sound_get_loops", "ii", false)); + private static var lime_miniaudio_backend_sound_set_loops = new cpp.CallableInt->cpp.Void>(cpp.Prime._loadPrime("lime", + "lime_miniaudio_backend_sound_set_loops", "iiv", false)); + private static var lime_miniaudio_backend_sound_is_playing = new cpp.CallableBool>(cpp.Prime._loadPrime("lime", + "lime_miniaudio_backend_sound_is_playing", "ib", false)); + private static var lime_miniaudio_backend_sound_readback_pcm = new cpp.Callablecpp.Object>(cpp.Prime._loadPrime("lime", + "lime_miniaudio_backend_sound_readback_pcm", "io", false)); private static var lime_audio_decoder_open_file = new cpp.CallableInt->cpp.Object>(cpp.Prime._loadPrime("lime", "lime_audio_decoder_open_file", "oio", false)); private static var lime_audio_decoder_open_bytes = new cpp.CallableInt->cpp.Object>(cpp.Prime._loadPrime("lime", @@ -870,6 +972,32 @@ class NativeCFFI private static var lime_window_set_always_on_top = CFFI.load("lime", "lime_window_set_always_on_top", 2); private static var lime_window_warp_mouse = CFFI.load("lime", "lime_window_warp_mouse", 3); private static var lime_window_event_manager_register = CFFI.load("lime", "lime_window_event_manager_register", 2); + private static var lime_miniaudio_backend_init = CFFI.load("lime", "lime_miniaudio_backend_init", 0); + private static var lime_miniaudio_backend_uninit = CFFI.load("lime", "lime_miniaudio_backend_uninit", 0); + private static var lime_miniaudio_backend_engine_init = CFFI.load("lime", "lime_miniaudio_backend_engine_init", 4); + private static var lime_miniaudio_backend_engine_uninit = CFFI.load("lime", "lime_miniaudio_backend_engine_uninit", 1); + private static var lime_miniaudio_backend_engine_start = CFFI.load("lime", "lime_miniaudio_backend_engine_start", 1); + private static var lime_miniaudio_backend_engine_stop = CFFI.load("lime", "lime_miniaudio_backend_engine_stop", 1); + private static var lime_miniaudio_backend_sound_init_from_file = CFFI.load("lime", "lime_miniaudio_backend_sound_init_from_file", 3); + private static var lime_miniaudio_backend_sound_init_from_bytes = CFFI.load("lime", "lime_miniaudio_backend_sound_init_from_bytes", 4); + private static var lime_miniaudio_backend_sound_uninit = CFFI.load("lime", "lime_miniaudio_backend_sound_uninit", 1); + private static var lime_miniaudio_backend_sound_start = CFFI.load("lime", "lime_miniaudio_backend_sound_start", 1); + private static var lime_miniaudio_backend_sound_stop = CFFI.load("lime", "lime_miniaudio_backend_sound_stop", 1); + private static var lime_miniaudio_backend_sound_reset = CFFI.load("lime", "lime_miniaudio_backend_sound_reset", 2); + private static var lime_miniaudio_backend_sound_get_time = CFFI.load("lime", "lime_miniaudio_backend_sound_get_time", 2); + private static var lime_miniaudio_backend_sound_set_time = CFFI.load("lime", "lime_miniaudio_backend_sound_set_time", 3); + private static var lime_miniaudio_backend_sound_get_volume = CFFI.load("lime", "lime_miniaudio_backend_sound_get_volume", 1); + private static var lime_miniaudio_backend_sound_set_volume = CFFI.load("lime", "lime_miniaudio_backend_sound_set_volume", 2); + private static var lime_miniaudio_backend_sound_get_pitch = CFFI.load("lime", "lime_miniaudio_backend_sound_get_pitch", 1); + private static var lime_miniaudio_backend_sound_set_pitch = CFFI.load("lime", "lime_miniaudio_backend_sound_set_pitch", 2); + private static var lime_miniaudio_backend_sound_get_position = CFFI.load("lime", "lime_miniaudio_backend_sound_get_position", 1); + private static var lime_miniaudio_backend_sound_set_position = CFFI.load("lime", "lime_miniaudio_backend_sound_set_position", 4); + private static var lime_miniaudio_backend_sound_get_length = CFFI.load("lime", "lime_miniaudio_backend_sound_get_length", 2); + private static var lime_miniaudio_backend_sound_set_length = CFFI.load("lime", "lime_miniaudio_backend_sound_set_length", 3); + private static var lime_miniaudio_backend_sound_get_loops = CFFI.load("lime", "lime_miniaudio_backend_sound_get_loops", 1); + private static var lime_miniaudio_backend_sound_set_loops = CFFI.load("lime", "lime_miniaudio_backend_sound_set_loops", 2); + private static var lime_miniaudio_backend_sound_is_playing = CFFI.load("lime", "lime_miniaudio_backend_sound_is_playing", 1); + private static var lime_miniaudio_backend_sound_readback_pcm = CFFI.load("lime", "lime_miniaudio_backend_sound_readback_pcm", 1); private static var lime_audio_decoder_open_file = CFFI.load("lime", "lime_audio_decoder_open_file", 2); private static var lime_audio_decoder_open_bytes = CFFI.load("lime", "lime_audio_decoder_open_bytes", 2); private static var lime_audio_decoder_info = CFFI.load("lime", "lime_audio_decoder_info", 1); diff --git a/src/lime/_internal/backend/native/NativeMiniaudioAudioSource.hx b/src/lime/_internal/backend/native/NativeMiniaudioAudioSource.hx new file mode 100644 index 0000000000..f66e4df22f --- /dev/null +++ b/src/lime/_internal/backend/native/NativeMiniaudioAudioSource.hx @@ -0,0 +1,264 @@ +package lime._internal.backend.native; + +import lime.app.Application; +import lime.math.Vector4; +import lime.media.MiniaudioFormat; +import lime.media.AudioSource; +import lime.media.AudioManager; +import lime._internal.backend.native.NativeCFFI; + +import haxe.io.Bytes; +import haxe.io.BytesData; + +typedef MiniaudioAudioSourceReadbackData = { + var format:MiniaudioFormat; + var channels:Int; + var sampleRate:Int; + var pcmFrameCount:Int; + final pcmFrames:BytesData; +} + +#if (lime_cffi && lime_miniaudio && !macro) +@:include("hx/GC.h") +#end +@:access(lime._internal.backend.native.NativeCFFI) +class NativeMiniaudioAudioSource +{ + private var parent:AudioSource; + private var soundIndex:Int; + private var stream:Bool; + private var completed:Bool; + private var paused:Bool; + + public function new(parent:AudioSource, stream:Bool) + { + this.parent = parent; + soundIndex = -1; + this.stream = stream; + completed = false; + paused = true; + } + + private function checkCallback(_):Void + { + if (!isPlaying() && !paused && !completed) { + parent.onComplete.dispatch(); + completed = true; + } + } + + public function dispose():Void + { + #if (lime_cffi && lime_miniaudio && !macro) + NativeCFFI.lime_miniaudio_backend_sound_uninit(soundIndex); + if (stream && parent.data != null) { + untyped __cpp__("hx::GCRemoveRoot((hx::Object**)(&{0}.mPtr))", parent.data.getData()); + untyped __cpp__("hx::GCRemoveRoot((hx::Object**)(&{0}.mPtr))", parent.data); + } + #end + if (Application.current.onUpdate.has(checkCallback)) { + Application.current.onUpdate.remove(checkCallback); + } + } + + public function initFromFile(path:String) + { + #if (lime_cffi && lime_miniaudio && !macro) + soundIndex = NativeCFFI.lime_miniaudio_backend_sound_init_from_file(AudioManager.context.maEngine, parent.offset, cpp.ConstCharStar.fromString(path)); + #end + if (soundIndex != -1 && !Application.current.onUpdate.has(checkCallback)) { + Application.current.onUpdate.add(checkCallback); + } + } + + public function initFromBytes():Void + { + #if (lime_cffi && lime_miniaudio && !macro) + if (stream) { + untyped __cpp__("hx::GCAddRoot((hx::Object**)(&{0}.mPtr))", parent.data); // not sure whether this one is required + untyped __cpp__("hx::GCAddRoot((hx::Object**)(&{0}.mPtr))", parent.data.getData()); // since we only reference and not copy the memory to play from (unless we dont stream, in that case we only do for copying time), we need to tell GC not to mess up addresses + } + soundIndex = NativeCFFI.lime_miniaudio_backend_sound_init_from_bytes(AudioManager.context.maEngine, parent.offset, stream, parent.data.getData()); + #end + if (soundIndex != -1 && !Application.current.onUpdate.has(checkCallback)) { + Application.current.onUpdate.add(checkCallback); + } + } + + public function play():Void + { + completed = false; // restarting if completed + paused = false; + #if (lime_cffi && lime_miniaudio && !macro) + NativeCFFI.lime_miniaudio_backend_sound_start(soundIndex); + #end + } + + public function pause():Void + { + paused = true; + #if (lime_cffi && lime_miniaudio && !macro) + NativeCFFI.lime_miniaudio_backend_sound_stop(soundIndex); + #end + } + + public function stop():Void + { + paused = true; + #if (lime_cffi && lime_miniaudio && !macro) + NativeCFFI.lime_miniaudio_backend_sound_reset(soundIndex, parent.offset); + #end + } + + public function getCurrentTime():Float + { + #if (lime_cffi && lime_miniaudio && !macro) + return NativeCFFI.lime_miniaudio_backend_sound_get_time(soundIndex, parent.offset); + #else + return 0; + #end + } + + public function setCurrentTime(value:Float):Float + { + #if (lime_cffi && lime_miniaudio && !macro) + NativeCFFI.lime_miniaudio_backend_sound_set_time(soundIndex, parent.offset, value); + #end + return value; + } + + public function getGain():Float + { + #if (lime_cffi && lime_miniaudio && !macro) + return NativeCFFI.lime_miniaudio_backend_sound_get_volume(soundIndex); + #else + return 0; + #end + } + + public function setGain(value:Float):Float + { + #if (lime_cffi && lime_miniaudio && !macro) + NativeCFFI.lime_miniaudio_backend_sound_set_volume(soundIndex, value); + #end + return value; + } + + public function getLength():Float + { + #if (lime_cffi && lime_miniaudio && !macro) + return NativeCFFI.lime_miniaudio_backend_sound_get_length(soundIndex, parent.offset); + #else + return 0; + #end + } + + public function setLength(value:Float):Float + { + #if (lime_cffi && lime_miniaudio && !macro) + NativeCFFI.lime_miniaudio_backend_sound_set_length(soundIndex, parent.offset, value); + #end + return value; + } + + public function getLoops():Int + { + #if (lime_cffi && lime_miniaudio && !macro) + return NativeCFFI.lime_miniaudio_backend_sound_get_loops(soundIndex); + #else + return 0; + #end + } + + public function setLoops(value:Int):Int + { + #if (lime_cffi && lime_miniaudio && !macro) + NativeCFFI.lime_miniaudio_backend_sound_set_loops(soundIndex, value); + #end + return value; + } + + public function getPitch():Float + { + #if (lime_cffi && lime_miniaudio && !macro) + return NativeCFFI.lime_miniaudio_backend_sound_get_pitch(soundIndex); + #else + return 0; + #end + } + + public function setPitch(value:Float):Float + { + #if (lime_cffi && lime_miniaudio && !macro) + NativeCFFI.lime_miniaudio_backend_sound_set_pitch(soundIndex, value); + #end + return value; + } + + public function getPosition():Vector4 + { + #if (lime_cffi && lime_miniaudio && !macro) + var pos:Array = NativeCFFI.lime_miniaudio_backend_sound_get_position(soundIndex); + return new Vector4(pos[0], pos[1], pos[2], 0.0); + #else + return new Vector4(); + #end + } + + public function setPosition(value:Vector4):Vector4 + { + #if (lime_cffi && lime_miniaudio && !macro) + NativeCFFI.lime_miniaudio_backend_sound_set_position(soundIndex, value.x, value.y, value.z); + #end + return value; + } + + public function isPlaying():Bool + { + #if (lime_cffi && lime_miniaudio && !macro) + return NativeCFFI.lime_miniaudio_backend_sound_is_playing(soundIndex); + #else + return 0; + #end + } + + public function readbackPcm():MiniaudioAudioSourceReadbackData + { + if (stream) { + lime.utils.Log.error("ma_sound with index " + soundIndex + " is streamed and does not support readback"); + return null; + } + + var cppReadbackData = { + format: 0, + channels: 0, + sampleRate: 0, + pcmFrameCount: 0, + pcmFrames: null + }; + #if (lime_cffi && lime_miniaudio && !macro) + cppReadbackData = NativeCFFI.lime_miniaudio_backend_sound_readback_pcm(soundIndex); + #end + + var readbackData:MiniaudioAudioSourceReadbackData = { + format: cppReadbackData.format, + channels: cppReadbackData.channels, + sampleRate: cppReadbackData.sampleRate, + pcmFrameCount: cppReadbackData.pcmFrameCount, + pcmFrames: new BytesData() + }; + + var pcmFrameCountMultiplier = readbackData.format.getBytesPerFrame(); + + #if (lime_cffi && lime_miniaudio && !macro) + cpp.NativeArray.setUnmanagedData(readbackData.pcmFrames, cppReadbackData.pcmFrames, readbackData.pcmFrameCount * pcmFrameCountMultiplier); + #end + + return readbackData; + } + + public function getLatency():Float + { + return 0; + } +} \ No newline at end of file diff --git a/src/lime/media/AudioContext.hx b/src/lime/media/AudioContext.hx index ae0ee70151..4ae710059b 100644 --- a/src/lime/media/AudioContext.hx +++ b/src/lime/media/AudioContext.hx @@ -4,17 +4,27 @@ package lime.media; import lime.media.howlerjs.Howler; #end import lime.utils.Log; +#if miniaudio +import lime._internal.backend.native.NativeCFFI; +#end @:access(lime.media.FlashAudioContext) @:access(lime.media.HTML5AudioContext) +@:access(lime.media.MiniaudioAudioContext) @:access(lime.media.OpenALAudioContext) @:access(lime.media.WebAudioContext) +#if miniaudio +@:access(lime._internal.backend.native.NativeCFFI) +#end class AudioContext { public var custom:Dynamic; #if (!lime_doc_gen || lime_openal) public var openal(default, null):OpenALAudioContext; #end + #if (!lime_doc_gen || lime_miniaudio) + public var maEngine(default, null):Int; + #end public var type(default, null):AudioContextType; #if (!lime_doc_gen || (js && html5)) public var web(default, null):WebAudioContext; @@ -61,6 +71,12 @@ class AudioContext #elseif flash flash = new FlashAudioContext(); this.type = FLASH; + #elseif miniaudio + #if (lime_cffi && lime_miniaudio && !macro) + NativeCFFI.lime_miniaudio_backend_init(); + maEngine = NativeCFFI.lime_miniaudio_backend_engine_init(44100, 0, 0, 0); + #end + this.type = MINIAUDIO; #else openal = new OpenALAudioContext(); this.type = OPENAL; diff --git a/src/lime/media/AudioContextType.hx b/src/lime/media/AudioContextType.hx index 58ab254085..dbc911407a 100644 --- a/src/lime/media/AudioContextType.hx +++ b/src/lime/media/AudioContextType.hx @@ -2,6 +2,7 @@ package lime.media; enum abstract AudioContextType(String) from String to String { + var MINIAUDIO = "miniaudio"; var OPENAL = "openal"; var WEB = "web"; var CUSTOM = "custom"; diff --git a/src/lime/media/AudioManager.hx b/src/lime/media/AudioManager.hx index 2681642c04..db489bf173 100644 --- a/src/lime/media/AudioManager.hx +++ b/src/lime/media/AudioManager.hx @@ -87,16 +87,25 @@ class AudioManager if (active) return; #if !lime_doc_gen - if (context != null && context.type == OPENAL) + if (context != null) { - var alc = context.openal; - var currentContext = alc.getCurrentContext(); + if (context.type == OPENAL) + { + var alc = context.openal; + var currentContext = alc.getCurrentContext(); - if (currentContext != null) + if (currentContext != null) + { + var device = alc.getContextsDevice(currentContext); + alc.resumeDevice(device); + alc.processContext(currentContext); + } + } + else if (context.type == MINIAUDIO) { - var device = alc.getContextsDevice(currentContext); - alc.resumeDevice(device); - alc.processContext(currentContext); + #if (lime_cffi && lime_miniaudio && !macro) + NativeCFFI.lime_miniaudio_backend_engine_start(context.maEngine); + #end } } #end @@ -107,22 +116,31 @@ class AudioManager public static function shutdown():Void { #if !lime_doc_gen - if (context != null && context.type == OPENAL) + if (context != null) { - var alc = context.openal; - var currentContext = alc.getCurrentContext(); - var device = alc.getContextsDevice(currentContext); - - if (currentContext != null) - { - alc.makeContextCurrent(null); - alc.destroyContext(currentContext); + if (context.type == OPENAL) { + var alc = context.openal; + var currentContext = alc.getCurrentContext(); + var device = alc.getContextsDevice(currentContext); - if (device != null) + if (currentContext != null) { - alc.closeDevice(device); + alc.makeContextCurrent(null); + alc.destroyContext(currentContext); + + if (device != null) + { + alc.closeDevice(device); + } } } + else if (context.type == MINIAUDIO) + { + #if (lime_cffi && lime_miniaudio && !macro) + NativeCFFI.lime_miniaudio_backend_engine_uninit(context.maEngine); + NativeCFFI.lime_miniaudio_backend_uninit(); + #end + } } #end @@ -134,21 +152,29 @@ class AudioManager if (!active) return; #if !lime_doc_gen - if (context != null && context.type == OPENAL) + if (context != null) { - var alc = context.openal; - var currentContext = alc.getCurrentContext(); - var device = alc.getContextsDevice(currentContext); - - if (currentContext != null) - { - alc.suspendContext(currentContext); + if (context.type == OPENAL) { + var alc = context.openal; + var currentContext = alc.getCurrentContext(); + var device = alc.getContextsDevice(currentContext); - if (device != null) + if (currentContext != null) { - alc.pauseDevice(device); + alc.suspendContext(currentContext); + + if (device != null) + { + alc.pauseDevice(device); + } } } + else if (context.type == MINIAUDIO) + { + #if (lime_cffi && lime_miniaudio && !macro) + NativeCFFI.lime_miniaudio_backend_engine_stop(context.maEngine); + #end + } } #end @@ -207,41 +233,43 @@ class AudioManager private static function setupConfig():Void { #if (lime_openal && (windows || mac || linux || android || ios)) - final alConfig:Array = []; - - alConfig.push('[general]'); - alConfig.push('frequency=48000'); - alConfig.push('sample-type=float32'); - alConfig.push('stereo-mode=speakers'); - alConfig.push('stereo-encoding=basic'); - alConfig.push('cf_level=0'); - alConfig.push('output-limiter=false'); - alConfig.push('front-stablizer=false'); - alConfig.push('volume-adjust=0'); - alConfig.push('period_size=480'); - alConfig.push('periods=4'); - alConfig.push('sends=64'); - alConfig.push('dither=false'); - alConfig.push('dither-depth=0'); - - alConfig.push('[decoder]'); - alConfig.push('hq-mode=false'); - alConfig.push('distance-comp=false'); - alConfig.push('nfc=false'); - - try - { - final directory:String = Path.directory(Path.withoutExtension(System.applicationStorageDirectory)); - final path:String = Path.withExtension(Path.join([directory, 'audio-config-${AUDIO_CONFIG_VERSION}']), #if windows 'ini' #else 'conf' #end); - final content:String = alConfig.join('\n'); + if (context.type == OPENAL) { + final alConfig:Array = []; + + alConfig.push('[general]'); + alConfig.push('frequency=48000'); + alConfig.push('sample-type=float32'); + alConfig.push('stereo-mode=speakers'); + alConfig.push('stereo-encoding=basic'); + alConfig.push('cf_level=0'); + alConfig.push('output-limiter=false'); + alConfig.push('front-stablizer=false'); + alConfig.push('volume-adjust=0'); + alConfig.push('period_size=480'); + alConfig.push('periods=4'); + alConfig.push('sends=64'); + alConfig.push('dither=false'); + alConfig.push('dither-depth=0'); + + alConfig.push('[decoder]'); + alConfig.push('hq-mode=false'); + alConfig.push('distance-comp=false'); + alConfig.push('nfc=false'); + + try + { + final directory:String = Path.directory(Path.withoutExtension(System.applicationStorageDirectory)); + final path:String = Path.withExtension(Path.join([directory, 'audio-config-${AUDIO_CONFIG_VERSION}']), #if windows 'ini' #else 'conf' #end); + final content:String = alConfig.join('\n'); - if (!FileSystem.exists(directory)) FileSystem.createDirectory(directory); + if (!FileSystem.exists(directory)) FileSystem.createDirectory(directory); - if (!FileSystem.exists(path)) File.saveContent(path, content); + if (!FileSystem.exists(path)) File.saveContent(path, content); - Sys.putEnv('ALSOFT_CONF', path); + Sys.putEnv('ALSOFT_CONF', path); + } + catch (e:Dynamic) {} } - catch (e:Dynamic) {} #end } } diff --git a/src/lime/media/AudioSource.hx b/src/lime/media/AudioSource.hx index 5afdff53f0..d5af4cf556 100644 --- a/src/lime/media/AudioSource.hx +++ b/src/lime/media/AudioSource.hx @@ -4,6 +4,13 @@ import lime.app.Event; import lime.media.openal.AL; import lime.media.openal.ALSource; import lime.math.Vector4; +import lime.utils.UInt8Array; + +#if miniaudio +import lime._internal.backend.native.NativeMiniaudioAudioSource.MiniaudioAudioSourceReadbackData; +#end + +import haxe.io.Bytes; /** The `AudioSource` class provides a way to control audio playback in a Lime application. @@ -21,10 +28,17 @@ class AudioSource **/ public var onComplete = new EventVoid>(); + #if miniaudio + /** + The audio data, if passed as bytes in the constructor. + **/ + public var data:Bytes = null; + #else /** The `AudioBuffer` associated with this `AudioSource`. **/ public var buffer:AudioBuffer; + #end /** The current playback position of the audio, in milliseconds. @@ -68,6 +82,41 @@ class AudioSource @:noCompletion private var __backend:AudioSourceBackend; + #if miniaudio + /** + Creates a new `AudioSource` instance. + @param bytes the bytes of an audio file. + NOTE: messing with Bytes object after it is passed here, like resizing, pushing etc., may cause undefined behaviour because of reallocation, unless `stream` is set to `false` + @param path The path to an audio file. + @param stream wheather audio data should be streamed. takes effect only if initializing from bytes. creating an `AudioSource` with this set to `false` is EXPENSIVE, use only if you need readback. + @param offset The starting offset within the audio buffer, in milliseconds. + @param length The length of the audio to play, in milliseconds. If `null`, the whole audio data is used. + @param loops The number of times to loop the audio. `0` means no looping. + **/ + public function new(?bytes:Bytes, ?path:String, stream:Bool = true, offset:Float = 0, length:Null = null, loops:Int = 0) + { + this.offset = offset; + + __backend = new AudioSourceBackend(this, stream); + + if (bytes != null) { + data = bytes; + initFromBytes(); + if (!stream) data = null; // drop the reference after storing the decoded pcm in cpp, so that the GC can free this + } else if (path != null) { + initFromFile(path); + } else { + lime.utils.Log.error("AudioSource's both *bytes* and *path* arguments were not provided, this will most likely result in a crash"); + } + + if (length != null && length != 0) + { + this.length = length; + } + + this.loops = loops; + } + #else /** Creates a new `AudioSource` instance. @param buffer The `AudioBuffer` to associate with this `AudioSource`. @@ -94,6 +143,7 @@ class AudioSource init(); } } + #end /** Releases any resources used by this `AudioSource`. @@ -103,10 +153,22 @@ class AudioSource __backend.dispose(); } + #if miniaudio + @:noCompletion private function initFromBytes():Void + { + __backend.initFromBytes(); + } + + @:noCompletion private function initFromFile(path:String):Void + { + __backend.initFromFile(path); + } + #else @:noCompletion private function init():Void { __backend.init(); } + #end /** Starts or resumes audio playback. @@ -132,6 +194,17 @@ class AudioSource __backend.stop(); } + #if miniaudio + /** + Reads back pcm frames from the source. + note that streaming must have been turned off and the `AudioSource` must have been created from bytes in order for this to work. + **/ + public function readbackPcm():MiniaudioAudioSourceReadbackData + { + return __backend.readbackPcm(); + } + #end + // Get & Set Methods @:noCompletion private function get_currentTime():Float { @@ -201,6 +274,8 @@ class AudioSource #if (js && html5) @:noCompletion private typedef AudioSourceBackend = lime._internal.backend.html5.HTML5AudioSource; +#elseif miniaudio +@:noCompletion private typedef AudioSourceBackend = lime._internal.backend.native.NativeMiniaudioAudioSource; #elseif lime_openal @:noCompletion private typedef AudioSourceBackend = lime._internal.backend.native.NativeOpenALAudioSource; #end diff --git a/src/lime/media/MiniaudioFormat.hx b/src/lime/media/MiniaudioFormat.hx new file mode 100644 index 0000000000..4d598e49d3 --- /dev/null +++ b/src/lime/media/MiniaudioFormat.hx @@ -0,0 +1,26 @@ +package lime.media; + +// this is a direct map to ma_format (with ma_format_unknown excluded) +enum abstract MiniaudioFormat(Int) from Int to Int +{ + var U8 = 1; + var S16 = 2; + var S24 = 3; + var S32 = 4; + var F32 = 5; + + public function getBytesPerFrame():Int + { + var bytesPerFrame = 0; + + switch (this) { + case U8: bytesPerFrame = 1; + case S16: bytesPerFrame = 2; + case S24: bytesPerFrame = 3; + case S32: bytesPerFrame = 4; + case F32: bytesPerFrame = 4; + } + + return bytesPerFrame; + } +} \ No newline at end of file diff --git a/tools/tools.n b/tools/tools.n index 0c4c810bef61c9ec4a5a1aabdd868e0877ab4262..b66416035b8e3be57bf295e7d1f7ed078d4a7e30 100644 GIT binary patch delta 68071 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 F{~ynE8c+ZL