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 9bd56cf690..c50d3c6746 100644 Binary files a/run.n and b/run.n differ 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/_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/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 e2f5cefe80..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; -#else -@:noCompletion private typedef AudioSourceBackend = lime._internal.backend.native.NativeAudioSource; +#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 0c4c810bef..b66416035b 100644 Binary files a/tools/tools.n and b/tools/tools.n differ