2022-08-26 15:15:16 +00:00
//----------------------------------------------------------------------------------------------------
// ___ ___ __ _ _ ___ ___ _ _ _ ___ _
// / _ \| _ ) / /| | || _ \ __| /_\ _ _ __| (_)___ | __|_ _ __ _(_)_ _ ___
// | (_) | _ \/ _ \_ _| _/ _| / _ \ || / _` | / _ \ | _|| ' \/ _` | | ' \/ -_)
// \__\_\___/\___/ |_||_| |___| /_/ \_\_,_\__,_|_\___/ |___|_||_\__, |_|_||_\___|
// |___/
//
// QB64-PE Audio Engine powered by miniaudio (https://miniaud.io/)
//
// Copyright (c) 2022 Samuel Gomes
// https://github.com/a740g
//
//-----------------------------------------------------------------------------------------------------
# include <vector>
# define STB_VORBIS_HEADER_ONLY
# include "extras/stb_vorbis.c"
# include "miniaudio.h"
2022-12-21 06:17:41 +00:00
// Set this to 1 if we want to print debug messages to stderr
2023-01-07 18:44:59 +00:00
# define AUDIO_DEBUG 1
2022-12-21 06:17:41 +00:00
# include "audio.h"
# include "mutex.h"
// We need 'qbs' and 'mem' stuff from here. This should eventually change when things are moved to smaller, logical and self-contained files
2022-08-26 15:15:16 +00:00
# include "../../libqb.h"
// This should be defined elsewhere (in libqb?). Since it is not, we are doing it here
# define INVALID_MEM_LOCK 1073741821
// This should be defined elsewhere (in libqb?). Since it is not, we are doing it here
# define MEM_TYPE_SOUND 5
// In QuickBASIC false means 0 and true means -1 (sad, but true XD)
# define QB_FALSE MA_FALSE
# define QB_TRUE -MA_TRUE
2022-09-09 22:38:31 +00:00
// This is returned to the caller if handle allocation fails with a -1
// AllocateSoundHandle() does not return 0 because it is a valid internal handle
// Handle 0 is 'handled' as a special case
# define INVALID_SOUND_HANDLE 0
2022-12-22 15:12:27 +00:00
// This is the string that can be passed in the requirements parameter to stream a sound from storage
2022-08-26 15:15:16 +00:00
# define REQUIREMENT_STRING_STREAM "STREAM"
2022-12-22 15:12:27 +00:00
// This is the string that can be passed in the requirements parameter to load a sound from memory
# define REQUIREMENT_STRING_MEMORY "MEMORY"
2022-08-26 15:15:16 +00:00
# define SAMPLE_FRAME_SIZE(_type_, _channels_) (sizeof(_type_) * (_channels_))
// This basically checks if the handle is within vector limits and 'isUsed' is set to true
// We are relying on C's boolean short-circuit to not evaluate the last 'isUsed' if previous conditions are false
// Here we are checking > 0 because this is meant to check user handles only
# define IS_SOUND_HANDLE_VALID(_handle_) \
( ( _handle_ ) > 0 & & ( _handle_ ) < audioEngine . soundHandles . size ( ) & & audioEngine . soundHandles [ _handle_ ] - > isUsed & & \
! audioEngine . soundHandles [ _handle_ ] - > autoKill )
# ifdef QB64_WINDOWS
# define ZERO_VARIABLE(_v_) ZeroMemory(&(_v_), sizeof(_v_))
# else
# define ZERO_VARIABLE(_v_) memset(&(_v_), 0, sizeof(_v_))
# endif
2022-12-22 15:12:27 +00:00
// These attaches our customer backend (format decoders) VTables to various miniaudio structs
2022-08-26 15:15:16 +00:00
void AudioEngineAttachCustomBackendVTables ( ma_resource_manager_config * maResourceManagerConfig ) ;
2022-12-22 15:12:27 +00:00
void AudioEngineAttachCustomBackendVTables ( ma_decoder_config * maDecoderConfig ) ;
2022-08-26 15:15:16 +00:00
// These are stuff that was not declared anywhere else
// We will wait for Matt to cleanup the C/C++ source file and include header files that declare this stuff
int32 func_instr ( int32 start , qbs * str , qbs * substr , int32 passed ) ; // Did not find this declared anywhere
void new_mem_lock ( ) ; // This is required for MemSound()
void free_mem_lock ( mem_lock * lock ) ; // Same as above
# ifndef QB64_WINDOWS
void Sleep ( uint32 milliseconds ) ; // There is a non-Windows implementation. However it is not declared anywhere
# endif
extern ptrszint dblock ; // Required for Play(). Did not find this declared anywhere
extern uint64 mem_lock_id ; // Another one that we need for the mem stuff
extern mem_lock * mem_lock_base ; // Same as above
extern mem_lock * mem_lock_tmp ; // Same as above
2022-12-13 03:42:52 +00:00
/// @brief Type of sound
2022-08-26 15:15:16 +00:00
enum struct SoundType {
None , // No sound or internal sound whose buffer is managed by the QBPE audio engine
Static , // Static sounds that are completely managed by miniaudio
Raw // Raw sound stream that is managed by the QBPE audio engine
} ;
2022-12-13 03:42:52 +00:00
/// @brief A simple FP32 stereo sample frame
struct SampleFrame {
float l ;
float r ;
2022-12-15 03:46:42 +00:00
} ;
2022-12-21 06:17:41 +00:00
/// @brief A miniaudiio raw audio stream datasource
struct RawStream {
2022-12-30 20:19:23 +00:00
ma_data_source_base maDataSource ; // miniaudio data source (this must be the first member of our struct)
ma_data_source_config maDataSourceConfig ; // config struct for the data source
ma_engine * maEngine ; // pointer to a ma_engine object that was passed while creating the data source
ma_sound * maSound ; // pointer to a ma_sound object that was passed while creating the data source
ma_uint32 sampleRate ; // the sample rate reported by ma_engine
struct Buffer { // we'll give this a name that we'll use below
std : : vector < SampleFrame > data ; // this holds the actual sample frames
size_t cursor ; // the read cursor (in frames) in the stream
} buffer [ 2 ] ; // we need two of these to do a proper ping-pong
Buffer * consumer ; // this is what the miniaudio thread will use to pull data from
Buffer * producer ; // this is what the main thread will use to push data to
libqb_mutex * m ; // we'll use a mutex to give exclusive access to resources used by both threads
bool stop ; // set this to true to stop supply of samples completely (including silent samples)
static const size_t DEFAULT_SIZE = 1024 ; // this is almost twice the amout what miniaudio actually asks for in frameCount
2022-12-21 06:17:41 +00:00
// Delete default, copy and move constructors and assignments
RawStream ( ) = delete ;
RawStream ( const RawStream & ) = delete ;
RawStream & operator = ( const RawStream & ) = delete ;
RawStream & operator = ( RawStream & & ) = delete ;
RawStream ( RawStream & & ) = delete ;
/// @brief This is use to setup the vectors, mutex and set some defaults
RawStream ( ma_engine * pmaEngine , ma_sound * pmaSound ) {
maSound = pmaSound ; // Save the pointer to the ma_sound object (this is basically from a QBPE sound handle)
maEngine = pmaEngine ; // Save the pointer to the ma_engine object (this should come from the QBPE sound engine)
sampleRate = ma_engine_get_sample_rate ( maEngine ) ; // Save the sample rate
2022-12-30 20:19:23 +00:00
buffer [ 0 ] . cursor = buffer [ 1 ] . cursor = 0 ; // reset the cursors
buffer [ 0 ] . data . reserve ( DEFAULT_SIZE ) ; // ensure we have a contigious block to account for expansion without reallocation
buffer [ 1 ] . data . reserve ( DEFAULT_SIZE ) ; // ensure we have a contigious block to account for expansion without reallocation
consumer = & buffer [ 0 ] ; // set default consumer
producer = & buffer [ 1 ] ; // set default producer
stop = false ; // by default we will send silent samples to keep the playback going
2022-12-15 03:46:42 +00:00
m = libqb_mutex_new ( ) ;
}
2022-08-26 15:15:16 +00:00
2022-12-21 06:17:41 +00:00
/// @brief We use this to destroy the mutex
~ RawStream ( ) { libqb_mutex_free ( m ) ; }
2022-12-15 03:46:42 +00:00
2022-12-30 20:19:23 +00:00
/// @brief This swaps the consumer and producer Buffers. This is mutex protected and called by the miniaudio thread
void SwapBuffers ( ) {
libqb_mutex_guard lock ( m ) ; // lock the mutex before accessing the vectors
consumer - > cursor = 0 ; // reset the cursor
2022-12-22 09:41:20 +00:00
consumer - > data . clear ( ) ; // clear the consumer vector
2022-12-21 06:17:41 +00:00
std : : swap ( consumer , producer ) ; // quicky swap the Buffer pointers
}
2022-12-15 03:46:42 +00:00
2022-12-21 06:17:41 +00:00
/// @brief This pushes a sample frame at the end of the queue. This is mutex protected and called by the main thread
/// @param l Sample frame left channel data
/// @param r Sample frame right channel data
void PushSampleFrame ( float l , float r ) {
2022-12-30 20:19:23 +00:00
libqb_mutex_guard lock ( m ) ; // lock the mutex before accessing the vectors
2022-12-21 06:17:41 +00:00
producer - > data . push_back ( { l , r } ) ; // push the sample frame to the back of the producer queue
2022-12-15 03:46:42 +00:00
}
2023-01-06 06:51:17 +00:00
/// @brief This pushes a whole buffer of mono sample frames to the queue. This is mutex protected and called by the main thread
/// @param buffer The buffer containing the sample frames. This cannot be NULL
/// @param frames The total number of frames in the buffer
void PushMonoSampleFrames ( float * buffer , ma_uint64 frames ) {
libqb_mutex_guard lock ( m ) ; // lock the mutex before accessing the vectors
for ( ma_uint64 i = 0 ; i < frames ; i + + ) {
producer - > data . push_back ( { buffer [ i ] , buffer [ i ] } ) ;
}
}
2022-12-21 06:17:41 +00:00
/// @brief Returns the length, in sample frames of sound queued
/// @return The length left to play in sample frames
ma_uint64 GetSampleFramesRemaining ( ) {
2022-12-30 20:19:23 +00:00
libqb_mutex_guard lock ( m ) ; // lock the mutex before accessing the vectors
return ( consumer - > data . size ( ) - consumer - > cursor ) + ( producer - > data . size ( ) - producer - > cursor ) ; // sum of producer and consumer sample frames
2022-12-21 06:17:41 +00:00
}
/// @brief Returns the length, in seconds of sound queued
/// @return The length left to play in seconds
double GetTimeRemaining ( ) { return ( double ) GetSampleFramesRemaining ( ) / ( double ) sampleRate ; }
2022-12-15 03:46:42 +00:00
} ;
2022-08-26 15:15:16 +00:00
2022-12-15 03:46:42 +00:00
/// @brief This is what is used by miniaudio to pull a chunk of raw sample frames to play. The samples being read is removed from the queue
/// @param pDataSource Pointer to the raw stream data source (cast to RawStream type)
/// @param pFramesOut The sample frames sent to miniaudio
/// @param frameCount The sample frame count requested by miniaudio
/// @param pFramesRead The sample frame count that was actually sent (this must not exceed frameCount)
/// @return MA_SUCCESS on success or an appropriate MA_FAILED_* value on failure
static ma_result RawStreamOnRead ( ma_data_source * pDataSource , void * pFramesOut , ma_uint64 frameCount , ma_uint64 * pFramesRead ) {
if ( pFramesRead )
* pFramesRead = 0 ;
if ( frameCount = = 0 )
return MA_INVALID_ARGS ;
if ( ! pDataSource )
return MA_INVALID_ARGS ;
2022-12-21 06:17:41 +00:00
auto pRawStream = ( RawStream * ) pDataSource ; // cast to RawStream instance pointer
auto result = MA_SUCCESS ; // must be initialized to MA_SUCCESS
auto maBuffer = ( SampleFrame * ) pFramesOut ; // cast to sample frame pointer
2022-12-30 23:48:22 +00:00
ma_uint64 sampleFramesCount = pRawStream - > consumer - > data . size ( ) - pRawStream - > consumer - > cursor ; // total amount of samples we need to send to miniaudio
2022-12-30 20:19:23 +00:00
// Swap buffers if we do not have anything left to play
2022-12-21 06:17:41 +00:00
if ( ! sampleFramesCount ) {
2022-12-30 20:19:23 +00:00
pRawStream - > SwapBuffers ( ) ;
2022-12-21 06:17:41 +00:00
sampleFramesCount = pRawStream - > consumer - > data . size ( ) - pRawStream - > consumer - > cursor ; // get the total number of samples again
}
sampleFramesCount = std : : min ( sampleFramesCount , frameCount ) ; // we'll always send lower of what miniaudio wants or what we have
2022-12-15 03:46:42 +00:00
2022-12-21 06:17:41 +00:00
ma_uint64 sampleFramesRead = 0 ; // sample frame counter
// Now send the samples to miniaudio
2022-12-15 22:20:32 +00:00
while ( sampleFramesRead < sampleFramesCount ) {
2022-12-21 06:17:41 +00:00
maBuffer [ sampleFramesRead ] = pRawStream - > consumer - > data [ pRawStream - > consumer - > cursor ] ;
+ + sampleFramesRead ; // increment the frame counter
pRawStream - > consumer - > cursor + + ; // increment the read cursor
2022-12-15 03:46:42 +00:00
}
// To keep the stream going, play silence if there are no frames to play
2022-12-21 06:17:41 +00:00
if ( ! sampleFramesRead & & ! pRawStream - > stop ) {
2022-12-15 03:46:42 +00:00
while ( sampleFramesRead < frameCount ) {
2022-12-21 06:17:41 +00:00
maBuffer [ sampleFramesRead ] = { } ;
2022-12-15 03:46:42 +00:00
+ + sampleFramesRead ;
2022-08-26 15:15:16 +00:00
}
2022-12-15 03:46:42 +00:00
}
if ( pFramesRead )
* pFramesRead = sampleFramesRead ;
return result ;
}
/// @brief This is a dummy callback function which just tells miniaudio that it succeeded
/// @param pDataSource Pointer to the raw stream data source (cast to RawStream type)
/// @param frameIndex The frame index to seek to (unused)
/// @return Always MA_SUCCESS
static ma_result RawStreamOnSeek ( ma_data_source * pDataSource , ma_uint64 frameIndex ) {
// NOP. Just pretend to be successful.
( void ) pDataSource ;
( void ) frameIndex ;
return MA_SUCCESS ;
}
/// @brief Returns the audio format to miniaudio
/// @param pDataSource Pointer to the raw stream data source (cast to RawStream type)
/// @param pFormat The ma_format to use (we always return ma_format_f32 because that is what QB64 uses)
/// @param pChannels The number of audio channels (always 2 - stereo)
/// @param pSampleRate The sample rate of the stream (we always return the engine sample rate)
/// @param pChannelMap Sent to ma_channel_map_init_standard
/// @param channelMapCap Sent to ma_channel_map_init_standard
/// @return Always MA_SUCCESS
static ma_result RawStreamOnGetDataFormat ( ma_data_source * pDataSource , ma_format * pFormat , ma_uint32 * pChannels , ma_uint32 * pSampleRate ,
ma_channel * pChannelMap , size_t channelMapCap ) {
auto pRawStream = ( RawStream * ) pDataSource ;
if ( pFormat )
* pFormat = ma_format : : ma_format_f32 ; // QB64 SndRaw API uses FP32 samples
if ( pChannels )
* pChannels = 2 ; // stereo
if ( pSampleRate )
* pSampleRate = pRawStream - > sampleRate ; // we'll play at the audio engine sampling rate
2022-08-26 15:15:16 +00:00
2022-12-15 03:46:42 +00:00
if ( pChannelMap )
ma_channel_map_init_standard ( ma_standard_channel_map_default , pChannelMap , channelMapCap , 2 ) ; // stereo
return MA_SUCCESS ;
}
/// @brief Raw stream data source vtable
2022-12-21 06:17:41 +00:00
static ma_data_source_vtable rawStreamDataSourceVtable = {
2022-12-15 03:46:42 +00:00
RawStreamOnRead , // Returns a bunch of samples from a raw sample stream queue. The samples being returned is removed from the queue
RawStreamOnSeek , // NOP for raw sample stream
RawStreamOnGetDataFormat , // Returns the audio format to miniaudio
NULL , // No notion of a cursor for raw sample stream
NULL , // No notion of a length for raw sample stream
NULL , // Cannot loop raw sample stream
0 // flags
} ;
/// @brief This creates, initializes and sets up a raw stream for playback
/// @param pmaEngine This should come from the QBPE sound engine
/// @param pmaSound This should come from a QBPE sound handle
/// @return Returns a pointer to a data souce if successful, NULL otherwise
static RawStream * RawStreamCreate ( ma_engine * pmaEngine , ma_sound * pmaSound ) {
if ( ! pmaEngine | | ! pmaSound ) { // these should not be NULL
AUDIO_DEBUG_PRINT ( " Invalid arguments " ) ;
return nullptr ;
2022-08-26 15:15:16 +00:00
}
2022-12-21 06:17:41 +00:00
auto pRawStream = new RawStream ( pmaEngine , pmaSound ) ; // create the data source object
2022-12-15 03:46:42 +00:00
if ( ! pRawStream ) {
AUDIO_DEBUG_PRINT ( " Failed to create data source " ) ;
2022-08-26 15:15:16 +00:00
2022-12-15 03:46:42 +00:00
return nullptr ;
}
2022-08-26 15:15:16 +00:00
2022-12-30 20:19:23 +00:00
ZERO_VARIABLE ( pRawStream - > maDataSource ) ;
2022-08-26 15:15:16 +00:00
2022-12-30 20:19:23 +00:00
pRawStream - > maDataSourceConfig = ma_data_source_config_init ( ) ;
pRawStream - > maDataSourceConfig . vtable = & rawStreamDataSourceVtable ; // attach the vtable to the data source
2022-08-26 15:15:16 +00:00
2022-12-30 20:19:23 +00:00
auto result = ma_data_source_init ( & pRawStream - > maDataSourceConfig , & pRawStream - > maDataSource ) ;
2022-12-15 03:46:42 +00:00
if ( result ! = MA_SUCCESS ) {
AUDIO_DEBUG_PRINT ( " Error %i: failed to initialize data source " , result ) ;
delete pRawStream ;
return nullptr ;
2022-08-26 15:15:16 +00:00
}
2022-12-30 20:19:23 +00:00
result = ma_sound_init_from_data_source ( pmaEngine , & pRawStream - > maDataSource , MA_SOUND_FLAG_NO_PITCH | MA_SOUND_FLAG_NO_SPATIALIZATION , NULL ,
2022-12-16 06:58:22 +00:00
pmaSound ) ; // attach data source to the ma_sound
2022-12-15 03:46:42 +00:00
if ( result ! = MA_SUCCESS ) {
AUDIO_DEBUG_PRINT ( " Error %i: failed to initalize sound from data source " , result ) ;
2022-08-26 15:15:16 +00:00
2022-12-15 03:46:42 +00:00
delete pRawStream ;
2022-08-26 15:15:16 +00:00
2022-12-15 03:46:42 +00:00
return nullptr ;
2022-08-26 15:15:16 +00:00
}
2022-12-15 03:46:42 +00:00
result = ma_sound_start ( pmaSound ) ; // play the ma_sound
if ( result ! = MA_SUCCESS ) {
AUDIO_DEBUG_PRINT ( " Error %i: failed to start sound playback " , result ) ;
2022-08-26 15:15:16 +00:00
2022-12-15 03:46:42 +00:00
ma_sound_uninit ( pmaSound ) ; // delete the ma_sound object
delete pRawStream ;
return nullptr ;
2022-08-26 15:15:16 +00:00
}
2022-12-15 03:46:42 +00:00
AUDIO_DEBUG_PRINT ( " Raw sound stream created " ) ;
2022-08-26 15:15:16 +00:00
2022-12-15 03:46:42 +00:00
return pRawStream ;
}
2022-08-26 15:15:16 +00:00
2022-12-15 03:46:42 +00:00
/// @brief Stops and then frees a raw stream data source previously created with RawStreamCreate()
/// @param pRawStream Pointer to the data source object
static void RawStreamDestroy ( RawStream * pRawStream ) {
if ( pRawStream ) {
auto result = ma_sound_stop ( pRawStream - > maSound ) ; // stop playback
AUDIO_DEBUG_CHECK ( result = = MA_SUCCESS ) ;
2022-12-13 03:42:52 +00:00
2022-12-15 03:46:42 +00:00
ma_sound_uninit ( pRawStream - > maSound ) ; // delete the ma_sound object
2022-08-26 15:15:16 +00:00
2022-12-15 03:46:42 +00:00
delete pRawStream ; // delete the raw stream object
AUDIO_DEBUG_PRINT ( " Raw sound stream destroyed " ) ;
2022-08-26 15:15:16 +00:00
}
2022-12-15 03:46:42 +00:00
}
2022-08-26 15:15:16 +00:00
/// <summary>
/// Sound handle type
/// This describes every sound the system will ever play (including raw streams).
/// </summary>
struct SoundHandle {
2022-12-30 20:19:23 +00:00
bool isUsed ; // Is this handle in active use?
SoundType type ; // Type of sound (see SoundType enum class)
bool autoKill ; // Do we need to auto-clean this sample / stream after playback is done?
ma_sound maSound ; // miniaudio sound
ma_uint32 maFlags ; // miniaudio flags that were used when initializing the sound
ma_decoder_config maDecoderConfig ; // miniaudio decoder configuration
ma_decoder * maDecoder ; // this is used for files that are loaded directly from memory
ma_audio_buffer_config maAudioBufferConfig ; // miniaudio buffer configuration
ma_audio_buffer * maAudioBuffer ; // this is used for user created audio buffers (memory is managed by miniaudio)
RawStream * rawStream ; // Raw sample frame queue
void * memLockOffset ; // This is a pointer from new_mem_lock()
uint64 memLockId ; // This is mem_lock_id created by new_mem_lock()
2022-08-26 15:15:16 +00:00
2022-12-21 06:17:41 +00:00
// Delete copy and move constructors and assignments
SoundHandle ( const SoundHandle & ) = delete ;
SoundHandle & operator = ( const SoundHandle & ) = delete ;
SoundHandle ( SoundHandle & & ) = delete ;
SoundHandle & operator = ( SoundHandle & & ) = delete ;
2022-08-26 15:15:16 +00:00
/// <summary>
/// Just initializes some important members.
/// 'inUse' will be set to true by AllocateSoundHandle().
/// This is done here, as well as slightly differently in AllocateSoundHandle() for safety.
/// </summary>
SoundHandle ( ) {
isUsed = false ;
type = SoundType : : None ;
autoKill = false ;
ZERO_VARIABLE ( maSound ) ;
maFlags = MA_SOUND_FLAG_NO_PITCH | MA_SOUND_FLAG_NO_SPATIALIZATION | MA_SOUND_FLAG_WAIT_INIT ;
2022-12-22 15:12:27 +00:00
maDecoder = nullptr ;
2022-12-30 20:19:23 +00:00
maAudioBuffer = nullptr ;
rawStream = nullptr ;
2022-08-26 15:15:16 +00:00
memLockId = INVALID_MEM_LOCK ;
memLockOffset = nullptr ;
}
} ;
/// <summary>
/// Type will help us keep track of the audio engine state
/// </summary>
struct AudioEngine {
bool isInitialized ; // This is set to true if we were able to initialize miniaudio and allocated all required resources
bool initializationFailed ; // This is set to true if a past initialization attempt failed
ma_resource_manager_config maResourceManagerConfig ; // miniaudio resource manager configuration
ma_resource_manager maResourceManager ; // miniaudio resource manager
2022-12-22 15:12:27 +00:00
ma_engine_config maEngineConfig ; // miniaudio engine configuration (will be used to pass in the resource manager)
2022-08-26 15:15:16 +00:00
ma_engine maEngine ; // This is the primary miniaudio engine 'context'. Everything happens using this!
ma_result maResult ; // This is the result of the last miniaudio operation (used for trapping errors)
ma_uint32 sampleRate ; // Sample rate used by the miniaudio engine
int32_t sndInternal ; // Internal sound handle that we will use for Play(), Beep() & Sound()
int32_t sndInternalRaw ; // Internal sound handle that we will use for the QB64 'handle-less' raw stream
std : : vector < SoundHandle * > soundHandles ; // This is the audio handle list used by the engine and by everything else
int32_t lowestFreeHandle ; // This is the lowest handle then was recently freed. We'll start checking for free handles from here
2022-09-09 22:38:31 +00:00
bool musicBackground ; // Should 'Sound' and 'Play' work in the background or block the caller?
2022-08-26 15:15:16 +00:00
2022-12-21 06:17:41 +00:00
// Delete copy and move constructors and assignments
AudioEngine ( const AudioEngine & ) = delete ;
AudioEngine & operator = ( const AudioEngine & ) = delete ;
AudioEngine & operator = ( AudioEngine & & ) = delete ;
AudioEngine ( AudioEngine & & ) = delete ;
2022-08-26 15:15:16 +00:00
/// <summary>
/// Just initializes some important members.
/// </summary>
AudioEngine ( ) {
isInitialized = initializationFailed = false ;
sampleRate = 0 ;
lowestFreeHandle = 0 ;
sndInternal = sndInternalRaw = INVALID_SOUND_HANDLE ;
2022-09-09 22:38:31 +00:00
musicBackground = false ;
2022-08-26 15:15:16 +00:00
}
/// <summary>
/// This allocates a sound handle. It will return -1 on error.
/// Handle 0 is used internally for Beep, Sound and Play and thus cannot be used by the user.
/// Basically, we go through the vector and find an object pointer were 'isUsed' is set as false and return the index.
/// If such an object pointer is not found, then we add a pointer to a new object at the end of the vector and return the index.
/// We are using pointers because miniaudio keeps using stuff from ma_sound and these cannot move in memory when the vector is resized.
/// The handle is put-up for recycling simply by setting the 'isUsed' member to false.
/// Note that this means the vector will keep growing until the largest handle (index) and never shrink.
/// The choice of using a vector was simple - performance. Vector performance when using 'indexes' is next to no other.
/// The vector will be pruned only when snd_un_init gets called.
/// We will however, be good citizens and will also 'delete' the objects when snd_un_init gets called.
/// This also increments 'lowestFreeHandle' to allocated handle + 1.
/// </summary>
/// <returns>Returns a non-negative handle if successful</returns>
int32_t AllocateSoundHandle ( ) {
if ( ! isInitialized )
2022-09-09 22:38:31 +00:00
return - 1 ; // We cannot return 0 here. Since 0 is a valid internal handle
2022-08-26 15:15:16 +00:00
size_t h , vectorSize = soundHandles . size ( ) ; // Save the vector size
// Scan the vector starting from lowestFreeHandle
// This will help us quickly allocate a free handle and should be a decent optimization for SndPlayCopy()
for ( h = lowestFreeHandle ; h < vectorSize ; h + + ) {
if ( ! soundHandles [ h ] - > isUsed ) {
AUDIO_DEBUG_PRINT ( " Recent sound handle %i recycled " , h ) ;
break ;
}
}
if ( h > = vectorSize ) {
// Scan through the entire vector and return a slot that is not being used
// Ideally this should execute in extremely few (if at all) senarios
// Also, this loop should not execute if size is 0
for ( h = 0 ; h < vectorSize ; h + + ) {
if ( ! soundHandles [ h ] - > isUsed ) {
AUDIO_DEBUG_PRINT ( " Sound handle %i recycled " , h ) ;
break ;
}
}
}
if ( h > = vectorSize ) {
// If we have reached here then either the vector is empty or there are no empty slots
// Simply create a new SoundHandle at the back of the vector
SoundHandle * newHandle = new SoundHandle ;
if ( ! newHandle )
2022-09-09 22:38:31 +00:00
return - 1 ; // We cannot return 0 here. Since 0 is a valid internal handle
2022-08-26 15:15:16 +00:00
soundHandles . push_back ( newHandle ) ;
size_t newVectorSize = soundHandles . size ( ) ;
// If newVectorSize == vectorSize then push_back() failed
if ( newVectorSize < = vectorSize ) {
delete newHandle ;
2022-09-09 22:38:31 +00:00
return - 1 ; // We cannot return 0 here. Since 0 is a valid internal handle
2022-08-26 15:15:16 +00:00
}
h = newVectorSize - 1 ; // The handle is simply newVectorSize - 1
AUDIO_DEBUG_PRINT ( " Sound handle %i created " , h ) ;
}
AUDIO_DEBUG_CHECK ( soundHandles [ h ] - > isUsed = = false ) ;
// Initializes a sound handle that was just allocated.
// This will set it to 'in use' after applying some defaults.
soundHandles [ h ] - > type = SoundType : : None ;
soundHandles [ h ] - > autoKill = false ;
ZERO_VARIABLE ( soundHandles [ h ] - > maSound ) ;
// We do not use pitch shifting, so this will give a little performance boost
// Spatialization is disabled by default but will be enabled on the fly if required
soundHandles [ h ] - > maFlags = MA_SOUND_FLAG_NO_PITCH | MA_SOUND_FLAG_NO_SPATIALIZATION | MA_SOUND_FLAG_WAIT_INIT ;
2022-12-22 15:12:27 +00:00
soundHandles [ h ] - > maDecoder = nullptr ;
2022-12-30 20:19:23 +00:00
soundHandles [ h ] - > maAudioBuffer = nullptr ;
soundHandles [ h ] - > rawStream = nullptr ;
2022-08-26 15:15:16 +00:00
soundHandles [ h ] - > memLockId = INVALID_MEM_LOCK ;
soundHandles [ h ] - > memLockOffset = nullptr ;
soundHandles [ h ] - > isUsed = true ;
AUDIO_DEBUG_PRINT ( " Sound handle %i returned " , h ) ;
lowestFreeHandle = h + 1 ; // Set lowestFreeHandle to allocated handle + 1
return ( int32_t ) ( h ) ;
}
/// <summary>
/// The frees and unloads an open sound.
/// If the sound is playing or looping, it will be stopped.
/// If the sound is a stream of raw samples then it is stopped and freed.
/// Finally the handle is invalidated and put-up for recycling.
/// If the handle being freed is lower than 'lowestFreeHandle' then this saves the handle to 'lowestFreeHandle'.
/// </summary>
/// <param name="handle">A sound handle</param>
void FreeSoundHandle ( int32_t handle ) {
if ( isInitialized & & handle > = 0 & & handle < soundHandles . size ( ) & & soundHandles [ handle ] - > isUsed ) {
// Sound type specific cleanup
switch ( soundHandles [ handle ] - > type ) {
case SoundType : : Static :
ma_sound_uninit ( & soundHandles [ handle ] - > maSound ) ;
break ;
case SoundType : : Raw :
2022-12-15 03:46:42 +00:00
RawStreamDestroy ( soundHandles [ handle ] - > rawStream ) ;
soundHandles [ handle ] - > rawStream = nullptr ;
2022-08-26 15:15:16 +00:00
break ;
case SoundType : : None :
if ( handle ! = 0 )
AUDIO_DEBUG_PRINT ( " Sound type is 'None' when handle value is not 0 " ) ;
break ;
default :
AUDIO_DEBUG_PRINT ( " Condition not handled " ) ; // It should not come here
}
2022-12-30 20:19:23 +00:00
// Free any initialized miniaudio decoder
2022-12-22 15:12:27 +00:00
if ( soundHandles [ handle ] - > maDecoder ) {
ma_decoder_uninit ( soundHandles [ handle ] - > maDecoder ) ;
delete soundHandles [ handle ] - > maDecoder ;
soundHandles [ handle ] - > maDecoder = nullptr ;
2022-12-30 20:19:23 +00:00
AUDIO_DEBUG_PRINT ( " Decoder uninitialized " ) ;
}
// Free any initialized audio buffer
if ( soundHandles [ handle ] - > maAudioBuffer ) {
ma_audio_buffer_uninit_and_free ( soundHandles [ handle ] - > maAudioBuffer ) ;
soundHandles [ handle ] - > maAudioBuffer = nullptr ;
AUDIO_DEBUG_PRINT ( " Audio buffer uninitialized & freed " ) ;
2022-12-22 15:12:27 +00:00
}
2022-08-26 15:15:16 +00:00
// Invalidate any memsound stuff
if ( soundHandles [ handle ] - > memLockOffset ) {
free_mem_lock ( ( mem_lock * ) soundHandles [ handle ] - > memLockOffset ) ;
soundHandles [ handle ] - > memLockId = INVALID_MEM_LOCK ;
soundHandles [ handle ] - > memLockOffset = nullptr ;
2022-12-30 20:19:23 +00:00
AUDIO_DEBUG_PRINT ( " MemSound stuff invalidated " ) ;
2022-08-26 15:15:16 +00:00
}
// Now simply set the 'isUsed' member to false so that the handle can be recycled
soundHandles [ handle ] - > isUsed = false ;
soundHandles [ handle ] - > type = SoundType : : None ;
// Save the free hanndle to lowestFreeHandle if it is lower than lowestFreeHandle
if ( handle < lowestFreeHandle )
lowestFreeHandle = handle ;
AUDIO_DEBUG_PRINT ( " Sound handle %i marked as free " , handle ) ;
}
}
} ;
// This keeps track of the audio engine state
static AudioEngine audioEngine ;
2022-12-30 20:19:23 +00:00
/// @brief This creates a mono FP32 waveform based on frequency, length and volume
2022-12-22 09:41:20 +00:00
/// @param frequency The sound frequency
/// @param length The duration of the sound in seconds
/// @param volume The volume of the sound (0.0 - 1.0)
/// @param soundwave_bytes A pointer to an integer that will receive the buffer size in bytes. This cannot be NULL
2022-12-30 20:19:23 +00:00
/// @return A buffer containing the floating point waveform
2022-12-22 09:41:20 +00:00
static float * GenerateWaveform ( double frequency , double length , double volume , int * soundwave_bytes ) {
// Calculate the number of samples
auto samples = length * audioEngine . sampleRate ;
auto samplesi = ( int ) samples ;
2022-08-26 15:15:16 +00:00
if ( ! samplesi )
samplesi = 1 ;
2022-12-22 09:41:20 +00:00
// Calculate the number of bytes in the waveform data
* soundwave_bytes = samplesi * SAMPLE_FRAME_SIZE ( float , 1 ) ;
// Allocate memory for the waveform data
auto data = ( float * ) malloc ( * soundwave_bytes ) ;
if ( ! data )
return nullptr ;
2022-08-26 15:15:16 +00:00
2022-12-22 09:41:20 +00:00
// Set all bytes to 0 (silence) if frequency is >= 20000. This is per QuickBASIC 4.5 behavior
if ( frequency > = 20000 ) {
memset ( data , 0 , * soundwave_bytes ) ;
2022-08-26 15:15:16 +00:00
return data ;
}
2022-12-22 09:41:20 +00:00
// Generate the waveform
auto direction = 1 ;
auto value = 0.0 ;
// frequency * 4.0 * length is the total distance value will travel (+1,-2,+1[repeated])
// samples is the number of steps to do this in
auto gradient = samples ? ( frequency * 4.0 * length ) / samples : 0.0 ;
auto waveend = 0 ;
auto lastx = 1.0f ; // set to 1 to avoid passing initial comparison
2022-08-26 15:15:16 +00:00
2023-01-06 15:02:03 +00:00
for ( auto i = 0 ; i < samplesi ; i + + ) {
2022-12-22 09:41:20 +00:00
auto x = ( float ) ( value * volume ) ;
data [ i ] = x ;
2022-08-26 15:15:16 +00:00
2022-12-22 09:41:20 +00:00
if ( x > 0 & & lastx < = 0 )
waveend = i ;
2022-08-26 15:15:16 +00:00
lastx = x ;
2022-12-22 09:41:20 +00:00
// Update value and direction
2022-08-26 15:15:16 +00:00
if ( direction ) {
if ( ( value + = gradient ) > = 1.0 ) {
direction = 0 ;
value = 2.0 - value ;
}
} else {
if ( ( value - = gradient ) < = - 1.0 ) {
direction = 1 ;
value = - 2.0 - value ;
}
}
2022-12-22 09:41:20 +00:00
}
2022-08-26 15:15:16 +00:00
2022-12-22 09:41:20 +00:00
// Update reduced size if waveend is non-zero
2022-08-26 15:15:16 +00:00
if ( waveend )
2022-12-22 09:41:20 +00:00
* soundwave_bytes = waveend * SAMPLE_FRAME_SIZE ( float , 1 ) ;
2022-08-26 15:15:16 +00:00
return data ;
}
2022-12-22 09:41:20 +00:00
/// @brief Returns the of a sound buffer in bytes
/// @param length Length in seconds
/// @return Length in bytes
static int WaveformBufferSize ( double length ) {
auto samples = ( int ) ( length * audioEngine . sampleRate ) ;
2022-08-26 15:15:16 +00:00
if ( ! samples )
samples = 1 ;
2022-12-22 09:41:20 +00:00
return samples * SAMPLE_FRAME_SIZE ( float , 1 ) ;
2022-08-26 15:15:16 +00:00
}
2022-12-22 09:41:20 +00:00
/// @brief This sends a FP32 mono buffer to a raw stream for playback and then frees the buffer
/// @param data FP32 mono sound buffer
/// @param bytes Length of buffer in bytes
/// @param block If this is true the function is wait until the wavefrom has finished playing
static void SendWaveformToQueue ( float * data , int bytes , bool block ) {
2022-08-26 15:15:16 +00:00
if ( ! data )
return ;
2023-01-06 06:51:17 +00:00
// Push data to the raw stream
audioEngine . soundHandles [ audioEngine . sndInternal ] - > rawStream - > PushMonoSampleFrames ( data , bytes / SAMPLE_FRAME_SIZE ( float , 1 ) ) ;
2022-08-26 15:15:16 +00:00
free ( data ) ; // free the sound data
// This will wait for the block to finish (if specified)
// We'll be good citizens and give-up our time-slices while waiting
if ( block ) {
2022-12-21 06:17:41 +00:00
auto time_ms = ( audioEngine . soundHandles [ audioEngine . sndInternal ] - > rawStream - > GetSampleFramesRemaining ( ) * 1000 ) / audioEngine . sampleRate ;
2022-08-26 15:15:16 +00:00
if ( time_ms > 0 )
Sleep ( time_ms ) ;
}
}
/// <summary>
/// This generates a sound at the specified frequency for the specified amount of time.
/// </summary>
/// <param name="frequency">Sound frequency</param>
/// <param name="lengthInClockTicks">Duration in clock ticks. There are 18.2 clock ticks per second</param>
void sub_sound ( double frequency , double lengthInClockTicks ) {
2022-12-22 09:41:20 +00:00
if ( new_error | | ! audioEngine . isInitialized | | audioEngine . sndInternal ! = 0 | | lengthInClockTicks = = 0.0 )
2022-08-26 15:15:16 +00:00
return ;
2022-12-22 09:41:20 +00:00
if ( ( frequency < 37.0 & & frequency ! = 0 ) | | frequency > 32767.0 | | lengthInClockTicks < 0.0 | | lengthInClockTicks > 65535.0 ) {
error ( 5 ) ;
2022-08-26 15:15:16 +00:00
return ;
2022-12-22 09:41:20 +00:00
}
2022-08-26 15:15:16 +00:00
2022-12-22 09:41:20 +00:00
// Kickstart the raw stream if it is not already
2022-12-15 03:46:42 +00:00
if ( ! audioEngine . soundHandles [ audioEngine . sndInternal ] - > rawStream ) {
audioEngine . soundHandles [ audioEngine . sndInternal ] - > rawStream =
RawStreamCreate ( & audioEngine . maEngine , & audioEngine . soundHandles [ audioEngine . sndInternal ] - > maSound ) ;
AUDIO_DEBUG_CHECK ( audioEngine . soundHandles [ audioEngine . sndInternal ] - > rawStream ! = nullptr ) ;
if ( ! audioEngine . soundHandles [ audioEngine . sndInternal ] - > rawStream )
return ;
audioEngine . soundHandles [ audioEngine . sndInternal ] - > type = SoundType : : Raw ;
}
2022-08-26 15:15:16 +00:00
2022-12-22 09:41:20 +00:00
int soundwave_bytes ;
auto data = GenerateWaveform ( frequency , lengthInClockTicks / 18.2 , 1 , & soundwave_bytes ) ;
2022-09-09 22:38:31 +00:00
SendWaveformToQueue ( data , soundwave_bytes , ! audioEngine . musicBackground ) ;
2022-08-26 15:15:16 +00:00
}
/// <summary>
/// This generates a default 'beep' sound.
/// </summary>
void sub_beep ( ) { sub_sound ( 900 , 5 ) ; }
/// <summary>
/// This was designed to returned the number of notes in the background music queue.
/// However, here we'll just return the number of sample frame remaining to play when Play(), Sound() or Beep() are used.
/// This allows programs like the following to compile and work.
///
/// Music$ = "MBT180o2P2P8L8GGGL2E-P24P8L8FFFL2D"
/// PLAY Music$
/// WHILE PLAY(0) > 5: WEND
/// PRINT "Just about done!"
/// </summary>
/// <param name="ignore">Well, it's ignored</param>
/// <returns>Returns the number of sample frames left to play for Play(), Sound() & Beep()</returns>
int32_t func_play ( int32_t ignore ) {
if ( audioEngine . isInitialized & & audioEngine . sndInternal = = 0 ) {
2022-12-21 06:17:41 +00:00
return ( int32_t ) audioEngine . soundHandles [ audioEngine . sndInternal ] - > rawStream - > GetSampleFramesRemaining ( ) ;
2022-08-26 15:15:16 +00:00
}
return 0 ;
}
/// <summary>
/// Processes and plays the MML specified in the string.
/// Mmmmm. Spaghetti goodness.
/// Formats:
/// A[#|+|-][0-64]
/// 0-64 is like temp. Lnumber, 0 is whatever the current default is
/// </summary>
/// <param name="str">The string to play</param>
void sub_play ( qbs * str ) {
2022-12-22 09:41:20 +00:00
static int soundwave_bytes ;
static uint8_t * b , * wave , * wave2 ;
2022-08-26 15:15:16 +00:00
static double d ;
2022-12-22 09:41:20 +00:00
static int i , bytes_left , a , x , x2 , wave_bytes , wave_base ;
static int o = 4 ;
2022-08-26 15:15:16 +00:00
static double t = 120 ; // quarter notes per minute (120/60=2 per second)
static double l = 4 ;
static double pause = 1.0 / 8.0 ; // ML 0.0, MN 1.0/8.0, MS 1.0/4.0
static double length , length2 ; // derived from l and t
static double frequency ;
static double v = 50 ;
2022-12-22 09:41:20 +00:00
static int n ; // the semitone-intervaled note to be played
static int n_changed ; //+,#,- applied?
static int64_t number ;
static int number_entered ;
static int followup ; // 1=play note
static bool playit ;
static int fullstops = 0 ;
2022-08-26 15:15:16 +00:00
if ( new_error | | ! audioEngine . isInitialized | | audioEngine . sndInternal ! = 0 )
return ;
2022-12-22 09:41:20 +00:00
// Kickstart the raw stream if it is not already
2022-12-15 03:46:42 +00:00
if ( ! audioEngine . soundHandles [ audioEngine . sndInternal ] - > rawStream ) {
audioEngine . soundHandles [ audioEngine . sndInternal ] - > rawStream =
RawStreamCreate ( & audioEngine . maEngine , & audioEngine . soundHandles [ audioEngine . sndInternal ] - > maSound ) ;
AUDIO_DEBUG_CHECK ( audioEngine . soundHandles [ audioEngine . sndInternal ] - > rawStream ! = nullptr ) ;
if ( ! audioEngine . soundHandles [ audioEngine . sndInternal ] - > rawStream )
return ;
audioEngine . soundHandles [ audioEngine . sndInternal ] - > type = SoundType : : Raw ;
}
2022-08-26 15:15:16 +00:00
b = str - > chr ;
bytes_left = str - > len ;
wave = NULL ;
wave_bytes = 0 ;
n_changed = 0 ;
n = 0 ;
number_entered = 0 ;
number = 0 ;
followup = 0 ;
length = 1.0 / ( t / 60.0 ) * ( 4.0 / l ) ;
2022-12-22 09:41:20 +00:00
playit = false ;
2022-08-26 15:15:16 +00:00
wave_base = 0 ; // point at which new sounds will be inserted
next_byte :
if ( ( bytes_left - - ) | | followup ) {
if ( bytes_left < 0 ) {
i = 32 ;
goto follow_up ;
}
i = * b + + ;
if ( i = = 32 )
goto next_byte ;
if ( i > = 97 & & i < = 122 )
a = i - 32 ;
else
a = i ;
if ( i = = 61 ) { //= (+VARPTR$)
if ( fullstops ) {
error ( 5 ) ;
return ;
}
if ( number_entered ) {
error ( 5 ) ;
return ;
}
number_entered = 2 ;
// VARPTR$ reference
/*
' BYTE = 1
' INTEGER = 2
' STRING = 3 SUB - STRINGS must use " X " + VARPTR $ ( string $ )
' SINGLE = 4
' INT64 = 5
' FLOAT = 6
' DOUBLE = 8
' LONG = 20
' BIT = 64 + n
*/
if ( bytes_left < 3 ) {
error ( 5 ) ;
return ;
}
i = * b + + ;
bytes_left - - ; // read type byte
x = * ( ma_uint16 * ) b ;
b + = 2 ;
bytes_left - = 2 ; // read offset within DBLOCK
// note: allowable _BIT type variables in VARPTR$ are all at a byte offset and are all
// padded until the next byte
d = 0 ;
switch ( i ) {
case 1 :
d = * ( char * ) ( dblock + x ) ;
break ;
case ( 1 + 128 ) :
d = * ( ma_uint8 * ) ( dblock + x ) ;
break ;
case 2 :
d = * ( ma_int16 * ) ( dblock + x ) ;
break ;
case ( 2 + 128 ) :
d = * ( ma_uint16 * ) ( dblock + x ) ;
break ;
case 4 :
d = * ( float * ) ( dblock + x ) ;
break ;
case 5 :
d = * ( ma_int64 * ) ( dblock + x ) ;
break ;
case ( 5 + 128 ) :
d = * ( ma_int64 * ) ( dblock + x ) ; // unsigned conversion is unsupported!
break ;
case 6 :
d = * ( long double * ) ( dblock + x ) ;
break ;
case 8 :
d = * ( double * ) ( dblock + x ) ;
break ;
case 20 :
d = * ( ma_int32 * ) ( dblock + x ) ;
break ;
case ( 20 + 128 ) :
d = * ( ma_uint32 * ) ( dblock + x ) ;
break ;
default :
// bit type?
if ( ( i & 64 ) = = 0 ) {
error ( 5 ) ;
return ;
}
x2 = i & 63 ;
if ( x2 > 56 ) {
error ( 5 ) ;
return ;
} // valid number of bits?
// create a mask
2022-12-22 09:41:20 +00:00
auto mask = ( ( ( int64_t ) 1 ) < < x2 ) - 1 ;
auto i64num = ( * ( int64_t * ) ( dblock + x ) ) & mask ;
2022-08-26 15:15:16 +00:00
// signed?
if ( i & 128 ) {
2022-12-22 09:41:20 +00:00
mask = ( ( int64_t ) 1 ) < < ( x2 - 1 ) ;
2022-08-26 15:15:16 +00:00
if ( i64num & mask ) { // top bit on?
mask = - 1 ;
mask < < = x2 ;
i64num + = mask ;
}
} // signed
d = i64num ;
}
if ( d > 2147483647.0 | | d < - 2147483648.0 ) {
error ( 5 ) ;
return ;
} // out of range value!
number = round ( d ) ;
goto next_byte ;
}
// read in a number
if ( ( i > = 48 ) & & ( i < = 57 ) ) {
if ( fullstops | | ( number_entered = = 2 ) ) {
error ( 5 ) ;
return ;
}
if ( ! number_entered ) {
number = 0 ;
number_entered = 1 ;
}
number = number * 10 + i - 48 ;
goto next_byte ;
}
// read fullstops
if ( i = = 46 ) {
if ( followup ! = 7 & & followup ! = 1 & & followup ! = 4 ) {
error ( 5 ) ;
return ;
}
fullstops + + ;
goto next_byte ;
}
follow_up :
if ( followup = = 8 ) { // V...
if ( ! number_entered ) {
error ( 5 ) ;
return ;
}
number_entered = 0 ;
if ( number > 100 ) {
error ( 5 ) ;
return ;
}
v = number ;
followup = 0 ;
if ( bytes_left < 0 )
goto done ;
} // 8
if ( followup = = 7 ) { // P...
if ( number_entered ) {
number_entered = 0 ;
if ( number < 1 | | number > 64 ) {
error ( 5 ) ;
return ;
}
length2 = 1.0 / ( t / 60.0 ) * ( 4.0 / ( ( double ) number ) ) ;
} else {
length2 = length ;
}
d = length2 ;
for ( x = 1 ; x < = fullstops ; x + + ) {
d / = 2.0 ;
length2 = length2 + d ;
}
fullstops = 0 ;
soundwave_bytes = WaveformBufferSize ( length2 ) ;
if ( ! wave ) {
// create buffer
wave = ( ma_uint8 * ) calloc ( soundwave_bytes , 1 ) ;
wave_bytes = soundwave_bytes ;
wave_base = 0 ;
} else {
// increase buffer?
if ( ( wave_base + soundwave_bytes ) > wave_bytes ) {
wave = ( ma_uint8 * ) realloc ( wave , wave_base + soundwave_bytes ) ;
memset ( wave + wave_base , 0 , wave_base + soundwave_bytes - wave_bytes ) ;
wave_bytes = wave_base + soundwave_bytes ;
}
}
if ( i ! = 44 ) {
wave_base + = soundwave_bytes ;
}
2022-12-22 09:41:20 +00:00
playit = true ;
2022-08-26 15:15:16 +00:00
followup = 0 ;
if ( i = = 44 )
goto next_byte ;
if ( bytes_left < 0 )
goto done ;
} // 7
if ( followup = = 6 ) { // T...
if ( ! number_entered ) {
error ( 5 ) ;
return ;
}
number_entered = 0 ;
if ( number < 32 | | number > 255 ) {
number = 120 ;
}
t = number ;
length = 1.0 / ( t / 60.0 ) * ( 4.0 / l ) ;
followup = 0 ;
if ( bytes_left < 0 )
goto done ;
} // 6
if ( followup = = 5 ) { // M...
if ( number_entered ) {
error ( 5 ) ;
return ;
}
switch ( a ) {
case 76 : // L
pause = 0 ;
break ;
case 78 : // N
pause = 1.0 / 8.0 ;
break ;
case 83 : // S
pause = 1.0 / 4.0 ;
break ;
case 66 : // MB
2022-09-09 22:38:31 +00:00
if ( ! audioEngine . musicBackground ) {
audioEngine . musicBackground = true ;
2022-08-26 15:15:16 +00:00
if ( playit ) {
2022-12-22 09:41:20 +00:00
playit = false ;
SendWaveformToQueue ( ( float * ) wave , wave_bytes , true ) ;
2022-08-26 15:15:16 +00:00
}
wave = NULL ;
}
break ;
case 70 : // MF
2022-09-09 22:38:31 +00:00
if ( audioEngine . musicBackground ) {
audioEngine . musicBackground = false ;
2022-08-26 15:15:16 +00:00
// preceding MB content incorporated into MF block
}
break ;
default :
error ( 5 ) ;
return ;
}
followup = 0 ;
goto next_byte ;
} // 5
if ( followup = = 4 ) { // N...
if ( ! number_entered ) {
error ( 5 ) ;
return ;
}
number_entered = 0 ;
if ( number > 84 ) {
error ( 5 ) ;
return ;
}
2022-12-22 09:41:20 +00:00
if ( number = = 0 )
number = 125 ; // #217: this will generate a frequency > 44k and will force GenerateWaveform() to generate silence
n = - 45 + number ; // #217: fixes incorrect octave
2022-08-26 15:15:16 +00:00
goto followup1 ;
followup = 0 ;
if ( bytes_left < 0 )
goto done ;
} // 4
if ( followup = = 3 ) { // O...
if ( ! number_entered ) {
error ( 5 ) ;
return ;
}
number_entered = 0 ;
if ( number > 6 ) {
error ( 5 ) ;
return ;
}
o = number ;
followup = 0 ;
if ( bytes_left < 0 )
goto done ;
} // 3
if ( followup = = 2 ) { // L...
if ( ! number_entered ) {
error ( 5 ) ;
return ;
}
number_entered = 0 ;
if ( number < 1 | | number > 64 ) {
error ( 5 ) ;
return ;
}
l = number ;
length = 1.0 / ( t / 60.0 ) * ( 4.0 / l ) ;
followup = 0 ;
if ( bytes_left < 0 )
goto done ;
} // 2
if ( followup = = 1 ) { // A-G...
if ( i = = 45 ) { //-
if ( n_changed | | number_entered ) {
error ( 5 ) ;
return ;
}
n_changed = 1 ;
n - - ;
goto next_byte ;
}
if ( i = = 43 | | i = = 35 ) { //+,#
if ( n_changed | | number_entered ) {
error ( 5 ) ;
return ;
}
n_changed = 1 ;
n + + ;
goto next_byte ;
}
followup1 :
if ( number_entered ) {
number_entered = 0 ;
if ( number < 0 | | number > 64 ) {
error ( 5 ) ;
return ;
}
if ( ! number )
length2 = length ;
else
length2 = 1.0 / ( t / 60.0 ) * ( 4.0 / ( ( double ) number ) ) ;
} else {
length2 = length ;
} // number_entered
d = length2 ;
for ( x = 1 ; x < = fullstops ; x + + ) {
d / = 2.0 ;
length2 = length2 + d ;
}
fullstops = 0 ;
// frequency=(2^(note/12))*440
frequency = pow ( 2.0 , ( ( double ) n ) / 12.0 ) * 440.0 ;
// create wave
2022-12-22 09:41:20 +00:00
wave2 = ( ma_uint8 * ) GenerateWaveform ( frequency , length2 * ( 1.0 - pause ) , v / 100.0 , & soundwave_bytes ) ;
2022-08-26 15:15:16 +00:00
if ( pause > 0 ) {
wave2 = ( ma_uint8 * ) realloc ( wave2 , soundwave_bytes + WaveformBufferSize ( length2 * pause ) ) ;
memset ( wave2 + soundwave_bytes , 0 , WaveformBufferSize ( length2 * pause ) ) ;
soundwave_bytes + = WaveformBufferSize ( length2 * pause ) ;
}
if ( ! wave ) {
// adopt buffer
wave = wave2 ;
wave_bytes = soundwave_bytes ;
wave_base = 0 ;
} else {
// mix required?
if ( wave_base = = wave_bytes )
x = 0 ;
else
x = 1 ;
// increase buffer?
if ( ( wave_base + soundwave_bytes ) > wave_bytes ) {
wave = ( ma_uint8 * ) realloc ( wave , wave_base + soundwave_bytes ) ;
memset ( wave + wave_base , 0 , wave_base + soundwave_bytes - wave_bytes ) ;
wave_bytes = wave_base + soundwave_bytes ;
}
// mix or copy
if ( x ) {
2022-12-22 09:41:20 +00:00
auto sp = ( float * ) ( wave + wave_base ) ;
auto sp2 = ( float * ) wave2 ;
auto samples = soundwave_bytes / SAMPLE_FRAME_SIZE ( float , 1 ) ;
for ( x = 0 ; x < samples ; x + + ) {
sp [ x ] + = sp2 [ x ] ;
}
2022-08-26 15:15:16 +00:00
} else {
// copy
memcpy ( wave + wave_base , wave2 , soundwave_bytes ) ;
} // x
free ( wave2 ) ;
}
if ( i ! = 44 ) {
wave_base + = soundwave_bytes ;
}
2022-12-22 09:41:20 +00:00
playit = true ;
2022-08-26 15:15:16 +00:00
n_changed = 0 ;
followup = 0 ;
if ( i = = 44 )
goto next_byte ;
if ( bytes_left < 0 )
goto done ;
} // 1
if ( a > = 65 & & a < = 71 ) {
// modify a to represent a semitonal note (n) interval
switch ( a ) {
//[c][ ][d][ ][e][f][ ][g][ ][a][ ][b]
// 0 1 2 3 4 5 6 7 8 9 0 1
case 65 :
n = 9 ;
break ;
case 66 :
n = 11 ;
break ;
case 67 :
n = 0 ;
break ;
case 68 :
n = 2 ;
break ;
case 69 :
n = 4 ;
break ;
case 70 :
n = 5 ;
break ;
case 71 :
n = 7 ;
break ;
}
n = n + ( o - 2 ) * 12 - 9 ;
followup = 1 ;
goto next_byte ;
} // a
if ( a = = 76 ) { // L
followup = 2 ;
goto next_byte ;
}
if ( a = = 77 ) { // M
followup = 5 ;
goto next_byte ;
}
if ( a = = 78 ) { // N
followup = 4 ;
goto next_byte ;
}
if ( a = = 79 ) { // O
followup = 3 ;
goto next_byte ;
}
if ( a = = 84 ) { // T
followup = 6 ;
goto next_byte ;
}
if ( a = = 60 ) { //<
o - - ;
if ( o < 0 )
o = 0 ;
goto next_byte ;
}
if ( a = = 62 ) { //>
o + + ;
if ( o > 6 )
o = 6 ;
goto next_byte ;
}
if ( a = = 80 ) { // P
followup = 7 ;
goto next_byte ;
}
if ( a = = 86 ) { // V
followup = 8 ;
goto next_byte ;
}
error ( 5 ) ;
return ;
} // bytes_left
done :
if ( number_entered | | followup ) {
error ( 5 ) ;
return ;
} // unhandled data
2023-01-06 06:51:17 +00:00
if ( playit )
2022-12-22 09:41:20 +00:00
SendWaveformToQueue ( ( float * ) wave , wave_bytes , ! audioEngine . musicBackground ) ;
2022-08-26 15:15:16 +00:00
}
/// <summary>
/// This returns the sample rate from ma engine if ma is initialized.
/// </summary>
/// <returns>miniaudio sample rtate</returns>
int32_t func__sndrate ( ) { return audioEngine . sampleRate ; }
/// <summary>
/// This loads a sound file into memory and returns a LONG handle value above 0.
/// </summary>
/// <param name="fileName">The is the pathname for the sound file. This can be any format that miniaudio or a miniaudio plugin supports</param>
/// <param name="requirements">This is leftover from the old QB64-SDL days. But we use this to pass some parameters like 'stream'</param>
/// <param name="passed">How many parameters were passed?</param>
/// <returns>Returns a valid sound handle (> 0) if successful or 0 if it fails</returns>
int32_t func__sndopen ( qbs * fileName , qbs * requirements , int32_t passed ) {
// Some QB strings that we'll need
static qbs * fileNameZ = nullptr ;
static qbs * reqs = nullptr ;
2022-12-22 15:12:27 +00:00
if ( ! audioEngine . isInitialized | | ! fileName - > len )
2022-08-26 15:15:16 +00:00
return INVALID_SOUND_HANDLE ;
if ( ! fileNameZ )
fileNameZ = qbs_new ( 0 , 0 ) ;
if ( ! reqs )
reqs = qbs_new ( 0 , 0 ) ;
// Alocate a sound handle
int32_t handle = audioEngine . AllocateSoundHandle ( ) ;
if ( handle < 1 ) // We are not expected to open files with handle 0
return INVALID_SOUND_HANDLE ;
// Set some handle properties
audioEngine . soundHandles [ handle ] - > type = SoundType : : Static ;
2022-12-22 15:12:27 +00:00
// Prepare the requirements string
if ( passed & & requirements - > len )
2022-08-26 15:15:16 +00:00
qbs_set ( reqs , qbs_ucase ( requirements ) ) ; // Convert tmp str to perm str
2022-12-22 15:12:27 +00:00
// Set the flags to specifiy how we want the audio file to be opened
if ( passed & & requirements - > len & & func_instr ( 1 , reqs , qbs_new_txt ( REQUIREMENT_STRING_STREAM ) , 1 ) ) {
audioEngine . soundHandles [ handle ] - > maFlags | = MA_SOUND_FLAG_STREAM ; // Check if the user wants to stream the file
AUDIO_DEBUG_PRINT ( " Sound will stream " ) ;
2022-08-26 15:15:16 +00:00
} else {
audioEngine . soundHandles [ handle ] - > maFlags | = MA_SOUND_FLAG_DECODE ; // Else decode and load the whole sound in memory
2022-12-22 15:12:27 +00:00
AUDIO_DEBUG_PRINT ( " Sound will be fully decoded " ) ;
2022-08-26 15:15:16 +00:00
}
2022-12-22 15:12:27 +00:00
// Load the file from file or memory based on the requirements string
if ( passed & & requirements - > len & & func_instr ( 1 , reqs , qbs_new_txt ( REQUIREMENT_STRING_MEMORY ) , 1 ) ) {
// Configure a miniaudio decoder to load the sound from memory
AUDIO_DEBUG_PRINT ( " Loading sound from memory " ) ;
audioEngine . soundHandles [ handle ] - > maDecoder = new ma_decoder ( ) ; // allocate and zero memory
if ( ! audioEngine . soundHandles [ handle ] - > maDecoder ) {
AUDIO_DEBUG_PRINT ( " Failed to allocate memory for miniaudio decoder " ) ;
audioEngine . soundHandles [ handle ] - > isUsed = false ;
return INVALID_SOUND_HANDLE ;
}
// Setup the decoder & attach the custom backed vtables
audioEngine . soundHandles [ handle ] - > maDecoderConfig = ma_decoder_config_init_default ( ) ;
AudioEngineAttachCustomBackendVTables ( & audioEngine . soundHandles [ handle ] - > maDecoderConfig ) ;
audioEngine . soundHandles [ handle ] - > maDecoderConfig . sampleRate = audioEngine . sampleRate ;
audioEngine . maResult = ma_decoder_init_memory ( fileName - > chr , fileName - > len , & audioEngine . soundHandles [ handle ] - > maDecoderConfig ,
audioEngine . soundHandles [ handle ] - > maDecoder ) ; // initialize the decoder
if ( audioEngine . maResult ! = MA_SUCCESS ) {
delete audioEngine . soundHandles [ handle ] - > maDecoder ;
audioEngine . soundHandles [ handle ] - > maDecoder = nullptr ;
AUDIO_DEBUG_PRINT ( " Failed to initialize miniaudio decoder " ) ;
audioEngine . soundHandles [ handle ] - > isUsed = false ;
return INVALID_SOUND_HANDLE ;
}
// Finally, load the sound as a data source
audioEngine . maResult = ma_sound_init_from_data_source ( & audioEngine . maEngine , audioEngine . soundHandles [ handle ] - > maDecoder ,
audioEngine . soundHandles [ handle ] - > maFlags , NULL , & audioEngine . soundHandles [ handle ] - > maSound ) ;
} else {
AUDIO_DEBUG_PRINT ( " Loading sound from file '%s' " , fileNameZ - > chr ) ;
qbs_set ( fileNameZ , qbs_add ( fileName , qbs_new_txt_len ( " \0 " , 1 ) ) ) ; // s1 = filename + CHR$(0)
// Forward the request to miniaudio to open the sound file
audioEngine . maResult = ma_sound_init_from_file ( & audioEngine . maEngine , ( const char * ) fileNameZ - > chr , audioEngine . soundHandles [ handle ] - > maFlags , NULL ,
NULL , & audioEngine . soundHandles [ handle ] - > maSound ) ;
}
2022-08-26 15:15:16 +00:00
2022-12-30 20:19:23 +00:00
// If the sound failed to initialize, then free the handle and return INVALID_SOUND_HANDLE
2022-08-26 15:15:16 +00:00
if ( audioEngine . maResult ! = MA_SUCCESS ) {
2022-12-22 15:12:27 +00:00
if ( audioEngine . soundHandles [ handle ] - > maDecoder ) {
delete audioEngine . soundHandles [ handle ] - > maDecoder ;
audioEngine . soundHandles [ handle ] - > maDecoder = nullptr ;
}
AUDIO_DEBUG_PRINT ( " Error %i: '%s' failed to open " , audioEngine . maResult , fileNameZ - > chr ) ;
2022-08-26 15:15:16 +00:00
audioEngine . soundHandles [ handle ] - > isUsed = false ;
return INVALID_SOUND_HANDLE ;
}
2022-12-22 15:12:27 +00:00
AUDIO_DEBUG_PRINT ( " Sound successfully loaded " ) ;
2022-08-26 15:15:16 +00:00
return handle ;
}
/// <summary>
/// The frees and unloads an open sound.
/// If the sound is playing, it'll let it finish. Looping sounds will loop until the program is closed.
/// If the sound is a stream of raw samples then any remaining samples pending for playback will be sent to miniaudio and then the handle will be freed.
/// </summary>
/// <param name="handle">A sound handle</param>
void sub__sndclose ( int32_t handle ) {
if ( audioEngine . isInitialized & & IS_SOUND_HANDLE_VALID ( handle ) ) {
// If we have a raw stream then force it to push all it's data to miniaudio
// Note that this will take care of checking if the handle is a raw steam and other stuff
// So it is completly safe to call it this way
sub__sndrawdone ( handle , true ) ;
2022-12-21 06:17:41 +00:00
if ( audioEngine . soundHandles [ handle ] - > type = = SoundType : : Raw )
audioEngine . soundHandles [ handle ] - > rawStream - > stop = true ; // Signal miniaudio thread that we are going to end playback
2022-08-26 15:15:16 +00:00
// Simply set the autokill flag to true and let the sound loop handle disposing the sound
audioEngine . soundHandles [ handle ] - > autoKill = true ;
}
}
/// <summary>
/// This copies a sound to a new handle so that two or more of the same sound can be played at once.
/// </summary>
/// <param name="src_handle">A source sound handle</param>
/// <returns>A new sound handle if successful or 0 on failure</returns>
int32_t func__sndcopy ( int32_t src_handle ) {
// Check for all invalid cases
if ( ! audioEngine . isInitialized | | ! IS_SOUND_HANDLE_VALID ( src_handle ) | | audioEngine . soundHandles [ src_handle ] - > type ! = SoundType : : Static )
return INVALID_SOUND_HANDLE ;
2023-01-07 18:44:59 +00:00
int32_t dst_handle = INVALID_SOUND_HANDLE ;
2022-08-26 15:15:16 +00:00
2023-01-07 18:44:59 +00:00
// Miniaudio will not copy sounds attached to ma_audio_buffers so we'll handle the duplication ourselves
// Sadly, since this involves a buffer copy there may be a delay before the sound can play (especially if the sound is lengthy)
// The delay may be noticeable when _SNDPLAYCOPY is used multiple times on the a _SNDNEW sound
if ( audioEngine . soundHandles [ src_handle ] - > maAudioBuffer ) {
AUDIO_DEBUG_PRINT ( " Doing custom sound copy " ) ;
2022-08-26 15:15:16 +00:00
2023-01-07 18:44:59 +00:00
auto frames = audioEngine . soundHandles [ src_handle ] - > maAudioBuffer - > ref . sizeInFrames ;
auto channels = audioEngine . soundHandles [ src_handle ] - > maAudioBuffer - > ref . channels ;
auto format = audioEngine . soundHandles [ src_handle ] - > maAudioBuffer - > ref . format ;
2022-08-26 15:15:16 +00:00
2023-01-07 18:44:59 +00:00
// First create a new _SNDNEW sound with the same properties at the source
dst_handle = func__sndnew ( frames , channels , CHAR_BIT * ma_get_bytes_per_sample ( format ) ) ;
if ( dst_handle < 1 )
return INVALID_SOUND_HANDLE ;
// Next memcopy the samples from the source to the dest
memcpy ( ( void * ) audioEngine . soundHandles [ dst_handle ] - > maAudioBuffer - > ref . pData , audioEngine . soundHandles [ src_handle ] - > maAudioBuffer - > ref . pData ,
frames * ma_get_bytes_per_frame ( format , channels ) ) ; // naughty const void* casting, but should be OK
} else {
AUDIO_DEBUG_PRINT ( " Doing regular miniaudio sound copy " ) ;
dst_handle = audioEngine . AllocateSoundHandle ( ) ; // alocate a sound handle
if ( dst_handle < 1 )
return INVALID_SOUND_HANDLE ;
audioEngine . soundHandles [ dst_handle ] - > type = SoundType : : Static ; // set some handle properties
audioEngine . soundHandles [ dst_handle ] - > maFlags = audioEngine . soundHandles [ src_handle ] - > maFlags ; // copy the flags
// Initialize a new copy of the sound
audioEngine . maResult = ma_sound_init_copy ( & audioEngine . maEngine , & audioEngine . soundHandles [ src_handle ] - > maSound ,
audioEngine . soundHandles [ dst_handle ] - > maFlags , NULL , & audioEngine . soundHandles [ dst_handle ] - > maSound ) ;
// If the sound failed to copy, then free the handle and return INVALID_SOUND_HANDLE
if ( audioEngine . maResult ! = MA_SUCCESS ) {
audioEngine . soundHandles [ dst_handle ] - > isUsed = false ;
AUDIO_DEBUG_PRINT ( " Error %i: failed to copy sound " , audioEngine . maResult ) ;
return INVALID_SOUND_HANDLE ;
}
2022-08-26 15:15:16 +00:00
}
return dst_handle ;
}
/// <summary>
/// This plays a sound designated by a sound handle.
/// </summary>
/// <param name="handle">A sound handle</param>
void sub__sndplay ( int32_t handle ) {
if ( audioEngine . isInitialized & & IS_SOUND_HANDLE_VALID ( handle ) & & audioEngine . soundHandles [ handle ] - > type = = SoundType : : Static ) {
// Reset position to zero only if we are playing and (not looping or we've reached the end of the sound)
// This is based on the old OpenAl-soft code behavior
if ( ma_sound_is_playing ( & audioEngine . soundHandles [ handle ] - > maSound ) & &
( ! ma_sound_is_looping ( & audioEngine . soundHandles [ handle ] - > maSound ) | | ma_sound_at_end ( & audioEngine . soundHandles [ handle ] - > maSound ) ) ) {
audioEngine . maResult = ma_sound_seek_to_pcm_frame ( & audioEngine . soundHandles [ handle ] - > maSound , 0 ) ;
AUDIO_DEBUG_CHECK ( audioEngine . maResult = = MA_SUCCESS ) ;
}
// Kickstart playback
audioEngine . maResult = ma_sound_start ( & audioEngine . soundHandles [ handle ] - > maSound ) ;
AUDIO_DEBUG_CHECK ( audioEngine . maResult = = MA_SUCCESS ) ;
// Stop looping the sound if it is
if ( ma_sound_is_looping ( & audioEngine . soundHandles [ handle ] - > maSound ) ) {
ma_sound_set_looping ( & audioEngine . soundHandles [ handle ] - > maSound , MA_FALSE ) ;
}
2023-01-07 18:44:59 +00:00
AUDIO_DEBUG_PRINT ( " Playing sound %i " , handle ) ;
2022-08-26 15:15:16 +00:00
}
}
/// <summary>
/// This copies a sound, plays it, and automatically closes the copy.
/// </summary>
/// <param name="handle">A sound handle to copy</param>
/// <param name="volume">The volume at which the sound should be played (0.0 - 1.0)</param>
2022-10-04 21:03:44 +00:00
/// <param name="x">x distance values go from left (negative) to right (positive)</param>
/// <param name="y">y distance values go from below (negative) to above (positive).</param>
/// <param name="z">z distance values go from behind (negative) to in front (positive).</param>
2022-08-26 15:15:16 +00:00
/// <param name="passed">How many parameters were passed?</param>
2022-10-04 21:03:44 +00:00
void sub__sndplaycopy ( int32_t src_handle , double volume , double x , double y , double z , int32_t passed ) {
2022-08-26 15:15:16 +00:00
// We are simply going to use sndcopy, then setup some stuff like volume and autokill and then use sndplay
// We are not checking if the audio engine was initialized because if not we'll get an invalid handle anyway
2023-01-07 18:44:59 +00:00
auto dst_handle = func__sndcopy ( src_handle ) ;
AUDIO_DEBUG_PRINT ( " Source handle = %i, destination handle = %i " , src_handle , dst_handle ) ;
2022-08-26 15:15:16 +00:00
// Check if we succeeded and then proceed
if ( dst_handle > 0 ) {
// Set the volume if requested
2022-10-04 21:03:44 +00:00
if ( passed & 1 )
2022-08-26 15:15:16 +00:00
ma_sound_set_volume ( & audioEngine . soundHandles [ dst_handle ] - > maSound , volume ) ;
2022-10-05 09:58:04 +00:00
if ( passed & 4 | | passed & 8 ) { // If y or z or both are passed
ma_sound_set_spatialization_enabled ( & audioEngine . soundHandles [ dst_handle ] - > maSound , MA_TRUE ) ; // Enable 3D spatialization
ma_sound_set_position ( & audioEngine . soundHandles [ dst_handle ] - > maSound , x , y , z ) ; // Use full 3D positioning
} else if ( passed & 2 ) { // If x is passed
2022-10-04 21:03:44 +00:00
ma_sound_set_spatialization_enabled ( & audioEngine . soundHandles [ dst_handle ] - > maSound , MA_FALSE ) ; // Disable spatialization for better stereo sound
ma_sound_set_pan_mode ( & audioEngine . soundHandles [ dst_handle ] - > maSound , ma_pan_mode_pan ) ; // Set true panning
ma_sound_set_pan ( & audioEngine . soundHandles [ dst_handle ] - > maSound , x ) ; // Just use stereo panning
}
2022-08-26 15:15:16 +00:00
sub__sndplay ( dst_handle ) ; // Play the sound
audioEngine . soundHandles [ dst_handle ] - > autoKill = true ; // Set to auto kill
2022-10-05 01:01:46 +00:00
2023-01-07 18:44:59 +00:00
AUDIO_DEBUG_PRINT ( " Playing sound copy %i: volume %lf, 3D (%lf, %lf, %lf) " , dst_handle , volume , x , y , z ) ;
}
2022-08-26 15:15:16 +00:00
}
/// <summary>
/// This is a "fire and forget" style of function.
/// The engine will manage the sound handle internally.
/// When the sound finishes playing, the handle will be put up for recycling.
/// Playback starts asynchronously.
/// </summary>
/// <param name="fileName">The is the name of the file to be played</param>
/// <param name="sync">This paramater is ignored</param>
/// <param name="volume">This the sound playback volume (0 - silent ... 1 - full)</param>
/// <param name="passed">How many parameters were passed?</param>
void sub__sndplayfile ( qbs * fileName , int32_t sync , double volume , int32_t passed ) {
// We need this to send requirements to SndOpen
static qbs * reqs = nullptr ;
if ( ! reqs ) {
// Since this never changes, we can get away by doing this just once
reqs = qbs_new ( 0 , 0 ) ;
qbs_set ( reqs , qbs_new_txt ( REQUIREMENT_STRING_STREAM ) ) ;
}
// We will not wrap this in a 'if initialized' block because SndOpen will take care of that
int32_t handle = func__sndopen ( fileName , reqs , 1 ) ;
if ( handle > 0 ) {
if ( passed & 2 )
ma_sound_set_volume ( & audioEngine . soundHandles [ handle ] - > maSound , volume ) ;
sub__sndplay ( handle ) ; // Play the sound
audioEngine . soundHandles [ handle ] - > autoKill = true ; // Set to auto kill
}
}
/// <summary>
/// This pauses a sound using a sound handle.
/// </summary>
/// <param name="handle">A sound handle</param>
void sub__sndpause ( int32_t handle ) {
if ( audioEngine . isInitialized & & IS_SOUND_HANDLE_VALID ( handle ) & & audioEngine . soundHandles [ handle ] - > type = = SoundType : : Static ) {
// Stop the sound and just leave it at that
// miniaudio does not reset the play cursor
audioEngine . maResult = ma_sound_stop ( & audioEngine . soundHandles [ handle ] - > maSound ) ;
AUDIO_DEBUG_CHECK ( audioEngine . maResult = = MA_SUCCESS ) ;
}
}
/// <summary>
/// This returns whether a sound is being played.
/// </summary>
/// <param name="handle">A sound handle</param>
/// <returns>Return true if the sound is playing. False otherwise</returns>
int32_t func__sndplaying ( int32_t handle ) {
if ( audioEngine . isInitialized & & IS_SOUND_HANDLE_VALID ( handle ) & & audioEngine . soundHandles [ handle ] - > type = = SoundType : : Static ) {
return ma_sound_is_playing ( & audioEngine . soundHandles [ handle ] - > maSound ) ? QB_TRUE : QB_FALSE ;
}
return QB_FALSE ;
}
/// <summary>
/// This checks if a sound is paused.
/// </summary>
/// <param name="handle">A sound handle</param>
/// <returns>Returns true if the sound is paused. False otherwise</returns>
int32_t func__sndpaused ( int32_t handle ) {
if ( audioEngine . isInitialized & & IS_SOUND_HANDLE_VALID ( handle ) & & audioEngine . soundHandles [ handle ] - > type = = SoundType : : Static ) {
return ! ma_sound_is_playing ( & audioEngine . soundHandles [ handle ] - > maSound ) & &
( ma_sound_is_looping ( & audioEngine . soundHandles [ handle ] - > maSound ) | | ! ma_sound_at_end ( & audioEngine . soundHandles [ handle ] - > maSound ) )
? QB_TRUE
: QB_FALSE ;
}
return QB_FALSE ;
}
/// <summary>
/// This sets the volume of a sound loaded in memory using a sound handle.
/// New: This works for both static and raw sounds.
/// </summary>
/// <param name="handle">A sound handle</param>
/// <param name="volume">A float point value with 0 resulting in silence and anything above 1 resulting in amplification</param>
void sub__sndvol ( int32_t handle , float volume ) {
if ( audioEngine . isInitialized & & IS_SOUND_HANDLE_VALID ( handle ) & &
( audioEngine . soundHandles [ handle ] - > type = = SoundType : : Static | | audioEngine . soundHandles [ handle ] - > type = = SoundType : : Raw ) ) {
ma_sound_set_volume ( & audioEngine . soundHandles [ handle ] - > maSound , volume ) ;
}
}
/// <summary>
/// This is like sub__sndplay but the sound is looped.
/// </summary>
/// <param name="handle"></param>
void sub__sndloop ( int32_t handle ) {
if ( audioEngine . isInitialized & & IS_SOUND_HANDLE_VALID ( handle ) & & audioEngine . soundHandles [ handle ] - > type = = SoundType : : Static ) {
// Reset position to zero only if we are playing and (not looping or we've reached the end of the sound)
// This is based on the old OpenAl-soft code behavior
if ( ma_sound_is_playing ( & audioEngine . soundHandles [ handle ] - > maSound ) & &
( ! ma_sound_is_looping ( & audioEngine . soundHandles [ handle ] - > maSound ) | | ma_sound_at_end ( & audioEngine . soundHandles [ handle ] - > maSound ) ) ) {
audioEngine . maResult = ma_sound_seek_to_pcm_frame ( & audioEngine . soundHandles [ handle ] - > maSound , 0 ) ;
AUDIO_DEBUG_CHECK ( audioEngine . maResult = = MA_SUCCESS ) ;
}
// Kickstart playback
audioEngine . maResult = ma_sound_start ( & audioEngine . soundHandles [ handle ] - > maSound ) ;
AUDIO_DEBUG_CHECK ( audioEngine . maResult = = MA_SUCCESS ) ;
// Start looping the sound if it is not
if ( ! ma_sound_is_looping ( & audioEngine . soundHandles [ handle ] - > maSound ) ) {
ma_sound_set_looping ( & audioEngine . soundHandles [ handle ] - > maSound , MA_TRUE ) ;
}
}
}
/// <summary>
/// This will attempt to set the balance or 3D position of a sound.
/// Note that unlike the OpenAL code, we will do pure stereo panning if y & z are absent.
/// New: This works for both static and raw sounds.
/// </summary>
/// <param name="handle">A sound handle</param>
/// <param name="x">x distance values go from left (negative) to right (positive)</param>
/// <param name="y">y distance values go from below (negative) to above (positive).</param>
/// <param name="z">z distance values go from behind (negative) to in front (positive).</param>
/// <param name="channel">channel value 1 denotes left (mono) and 2 denotes right (stereo) channel. This has no meaning for miniaudio and is ignored</param>
/// <param name="passed">How many parameters were passed?</param>
void sub__sndbal ( int32_t handle , double x , double y , double z , int32_t channel , int32_t passed ) {
if ( audioEngine . isInitialized & & IS_SOUND_HANDLE_VALID ( handle ) & &
( audioEngine . soundHandles [ handle ] - > type = = SoundType : : Static | | audioEngine . soundHandles [ handle ] - > type = = SoundType : : Raw ) ) {
if ( passed & 2 | | passed & 4 ) { // If y or z or both are passed
ma_sound_set_spatialization_enabled ( & audioEngine . soundHandles [ handle ] - > maSound , MA_TRUE ) ; // Enable 3D spatialization
ma_vec3f v = ma_sound_get_position ( & audioEngine . soundHandles [ handle ] - > maSound ) ; // Get the current position in 3D space
// Set the previous values of x, y, z if these were not passed
if ( ! ( passed & 1 ) )
x = v . x ;
if ( ! ( passed & 2 ) )
y = v . y ;
if ( ! ( passed & 4 ) )
z = v . z ;
2022-10-05 09:58:04 +00:00
ma_sound_set_position ( & audioEngine . soundHandles [ handle ] - > maSound , x , y , z ) ; // Use full 3D positioning
} else if ( passed & 1 ) { // Only bother if x is passed
2022-08-26 15:15:16 +00:00
ma_sound_set_spatialization_enabled ( & audioEngine . soundHandles [ handle ] - > maSound , MA_FALSE ) ; // Disable spatialization for better stereo sound
ma_sound_set_pan_mode ( & audioEngine . soundHandles [ handle ] - > maSound , ma_pan_mode_pan ) ; // Set true panning
ma_sound_set_pan ( & audioEngine . soundHandles [ handle ] - > maSound , x ) ; // Just use stereo panning
}
}
}
/// <summary>
/// This returns the length in seconds of a loaded sound using a sound handle.
/// </summary>
/// <param name="handle">A sound handle</param>
/// <returns>Returns the length of a sound in seconds</returns>
double func__sndlen ( int32_t handle ) {
if ( audioEngine . isInitialized & & IS_SOUND_HANDLE_VALID ( handle ) & & audioEngine . soundHandles [ handle ] - > type = = SoundType : : Static ) {
float lengthSeconds = 0 ;
audioEngine . maResult = ma_sound_get_length_in_seconds ( & audioEngine . soundHandles [ handle ] - > maSound , & lengthSeconds ) ;
AUDIO_DEBUG_CHECK ( audioEngine . maResult = = MA_SUCCESS ) ;
return lengthSeconds ;
}
return 0 ;
}
/// <summary>
/// This returns the current playing position in seconds using a sound handle.
/// </summary>
/// <param name="handle">A sound handle</param>
/// <returns>Returns the current playing position in seconds from an open sound file</returns>
double func__sndgetpos ( int32_t handle ) {
if ( audioEngine . isInitialized & & IS_SOUND_HANDLE_VALID ( handle ) & & audioEngine . soundHandles [ handle ] - > type = = SoundType : : Static ) {
float playCursorSeconds = 0 ;
audioEngine . maResult = ma_sound_get_cursor_in_seconds ( & audioEngine . soundHandles [ handle ] - > maSound , & playCursorSeconds ) ;
AUDIO_DEBUG_CHECK ( audioEngine . maResult = = MA_SUCCESS ) ;
return playCursorSeconds ;
}
return 0 ;
}
/// <summary>
/// This changes the current/starting playing position in seconds of a sound.
/// </summary>
/// <param name="handle">A sound handle</param>
/// <param name="seconds">The position to set in seconds</param>
void sub__sndsetpos ( int32_t handle , double seconds ) {
if ( audioEngine . isInitialized & & IS_SOUND_HANDLE_VALID ( handle ) & & audioEngine . soundHandles [ handle ] - > type = = SoundType : : Static ) {
2023-01-06 15:02:03 +00:00
float lengthSeconds = 0 ;
2022-08-26 15:15:16 +00:00
audioEngine . maResult = ma_sound_get_length_in_seconds ( & audioEngine . soundHandles [ handle ] - > maSound , & lengthSeconds ) ; // Get the length in seconds
if ( audioEngine . maResult ! = MA_SUCCESS )
return ;
if ( seconds > lengthSeconds ) // If position is beyond length then simply stop playback and exit
{
audioEngine . maResult = ma_sound_stop ( & audioEngine . soundHandles [ handle ] - > maSound ) ;
AUDIO_DEBUG_CHECK ( audioEngine . maResult = = MA_SUCCESS ) ;
return ;
}
ma_uint64 lengthSampleFrames ;
audioEngine . maResult =
ma_sound_get_length_in_pcm_frames ( & audioEngine . soundHandles [ handle ] - > maSound , & lengthSampleFrames ) ; // Get the total sample frames
if ( audioEngine . maResult ! = MA_SUCCESS )
return ;
audioEngine . maResult = ma_sound_seek_to_pcm_frame ( & audioEngine . soundHandles [ handle ] - > maSound ,
lengthSampleFrames * ( seconds / lengthSeconds ) ) ; // Set the postion in PCM frames
AUDIO_DEBUG_CHECK ( audioEngine . maResult = = MA_SUCCESS ) ;
}
}
/// <summary>
/// This stops playing a sound after it has been playing for a set number of seconds.
/// </summary>
/// <param name="handle">A sound handle</param>
/// <param name="limit">The number of seconds that the sound will play</param>
void sub__sndlimit ( int32_t handle , double limit ) {
if ( audioEngine . isInitialized & & IS_SOUND_HANDLE_VALID ( handle ) & & audioEngine . soundHandles [ handle ] - > type = = SoundType : : Static ) {
ma_sound_set_stop_time_in_milliseconds ( & audioEngine . soundHandles [ handle ] - > maSound , limit * 1000 ) ;
}
}
/// <summary>
/// This stops a playing or paused sound using a sound handle.
/// </summary>
/// <param name="handle">A sound handle</param>
void sub__sndstop ( int32_t handle ) {
if ( audioEngine . isInitialized & & IS_SOUND_HANDLE_VALID ( handle ) & & audioEngine . soundHandles [ handle ] - > type = = SoundType : : Static ) {
// Stop the sound first
audioEngine . maResult = ma_sound_stop ( & audioEngine . soundHandles [ handle ] - > maSound ) ;
AUDIO_DEBUG_CHECK ( audioEngine . maResult = = MA_SUCCESS ) ;
// Also reset the playback cursor to zero
audioEngine . maResult = ma_sound_seek_to_pcm_frame ( & audioEngine . soundHandles [ handle ] - > maSound , 0 ) ;
AUDIO_DEBUG_CHECK ( audioEngine . maResult = = MA_SUCCESS ) ;
}
}
/// <summary>
/// This function opens a new channel to fill with _SNDRAW content to manage multiple dynamically generated sounds.
/// </summary>
/// <returns>A new sound handle if successful or 0 on failure</returns>
int32_t func__sndopenraw ( ) {
// Return invalid handle if audio engine is not initialized
if ( ! audioEngine . isInitialized )
return INVALID_SOUND_HANDLE ;
// Alocate a sound handle
int32_t handle = audioEngine . AllocateSoundHandle ( ) ;
if ( handle < 1 )
return INVALID_SOUND_HANDLE ;
// Set some handle properties
audioEngine . soundHandles [ handle ] - > type = SoundType : : Raw ;
// Create the raw sound object
2022-12-15 03:46:42 +00:00
audioEngine . soundHandles [ handle ] - > rawStream = RawStreamCreate ( & audioEngine . maEngine , & audioEngine . soundHandles [ handle ] - > maSound ) ;
if ( ! audioEngine . soundHandles [ handle ] - > rawStream )
2022-08-26 15:15:16 +00:00
return INVALID_SOUND_HANDLE ;
return handle ;
}
/// <summary>
/// This plays sound wave sample frequencies created by a program.
/// </summary>
/// <param name="left">Left channel sample</param>
/// <param name="right">Right channel sample</param>
/// <param name="handle">A sound handle</param>
/// <param name="passed">How many parameters were passed?</param>
void sub__sndraw ( float left , float right , int32_t handle , int32_t passed ) {
// Use the default raw handle if handle was not passed
if ( ! ( passed & 2 ) ) {
// Check if the default handle was created
if ( audioEngine . sndInternalRaw < 1 ) {
audioEngine . sndInternalRaw = func__sndopenraw ( ) ;
}
handle = audioEngine . sndInternalRaw ;
}
if ( audioEngine . isInitialized & & IS_SOUND_HANDLE_VALID ( handle ) & & audioEngine . soundHandles [ handle ] - > type = = SoundType : : Raw ) {
if ( ! ( passed & 1 ) )
right = left ;
2022-12-21 06:17:41 +00:00
audioEngine . soundHandles [ handle ] - > rawStream - > PushSampleFrame ( left , right ) ;
2022-08-26 15:15:16 +00:00
}
}
/// <summary>
/// This ensures that the final buffer portion is played in short sound effects even if it is incomplete.
/// </summary>
/// <param name="handle">A sound handle</param>
/// <param name="passed">How many parameters were passed?</param>
void sub__sndrawdone ( int32_t handle , int32_t passed ) {
2022-12-15 03:46:42 +00:00
// This is NOP now because miniaudio data source automatically pulls in all the samples without us doing anything
// As such, we need to think about the future of this function. Probably just leave it this way?
( void ) handle ;
( void ) passed ;
/*
2022-08-26 15:15:16 +00:00
// Use the default raw handle if handle was not passed
if ( ! passed )
handle = audioEngine . sndInternalRaw ;
if ( audioEngine . isInitialized & & IS_SOUND_HANDLE_VALID ( handle ) & & audioEngine . soundHandles [ handle ] - > type = = SoundType : : Raw ) {
2022-12-15 03:46:42 +00:00
// NOP
2022-08-26 15:15:16 +00:00
}
2022-12-15 03:46:42 +00:00
*/
2022-08-26 15:15:16 +00:00
}
/// <summary>
/// This function returns the length, in seconds, of a _SNDRAW sound currently queued.
/// </summary>
/// <param name="handle">A sound handle</param>
/// <param name="passed">How many parameters were passed?</param>
/// <returns></returns>
double func__sndrawlen ( int32_t handle , int32_t passed ) {
// Use the default raw handle if handle was not passed
if ( ! passed )
handle = audioEngine . sndInternalRaw ;
if ( audioEngine . isInitialized & & IS_SOUND_HANDLE_VALID ( handle ) & & audioEngine . soundHandles [ handle ] - > type = = SoundType : : Raw ) {
2022-12-21 06:17:41 +00:00
return audioEngine . soundHandles [ handle ] - > rawStream - > GetTimeRemaining ( ) ;
2022-08-26 15:15:16 +00:00
}
return 0 ;
}
2022-12-30 20:19:23 +00:00
/// <summary>
/// This returns a sound handle to a newly created sound's raw data in memory with the given specification.
/// The user can then fill the buffer with whatever they want (using _MEMSOUND) and play it.
/// This is basically the sound equivalent of _NEWIMAGE.
/// </summary>
/// <param name="frames">The number of sample frames required</param>
/// <param name="channels">The number of sound channels. This can be 1 (mono) or 2 (stereo)/param>
/// <param name="bits">The bit depth of the sound. This can be 8 (unsigned 8-bit), 16 (signed 16-bit) or 32 (FP32)</param>
/// <returns>A new sound handle if successful or 0 on failure</returns>
2022-12-31 06:30:38 +00:00
int32_t func__sndnew ( int32_t frames , int32_t channels , int32_t bits ) {
2022-12-30 20:19:23 +00:00
if ( ! audioEngine . isInitialized | | frames < = 0 ) {
AUDIO_DEBUG_CHECK ( frames > 0 ) ;
return INVALID_SOUND_HANDLE ;
}
// Validate all parameters
2022-12-31 06:30:38 +00:00
if ( ( channels ! = 1 & & channels ! = 2 ) | | ( bits ! = 16 & & bits ! = 32 & & bits ! = 8 ) ) {
AUDIO_DEBUG_PRINT ( " Invalid channels (%i) or bits (%i) " , channels , bits ) ;
2022-12-30 20:19:23 +00:00
return INVALID_SOUND_HANDLE ;
}
// Alocate a sound handle
int32_t handle = audioEngine . AllocateSoundHandle ( ) ;
if ( handle < 1 )
return INVALID_SOUND_HANDLE ;
// Set some handle properties
audioEngine . soundHandles [ handle ] - > type = SoundType : : Static ;
// Setup the ma_audio_buffer
audioEngine . soundHandles [ handle ] - > maAudioBufferConfig = ma_audio_buffer_config_init (
( bits = = 32 ? ma_format : : ma_format_f32 : ( bits = = 16 ? ma_format : : ma_format_s16 : ma_format : : ma_format_u8 ) ) , channels , frames , NULL , NULL ) ;
2022-12-31 06:30:38 +00:00
// This currently has no effect. Sample rate always defaults to engine sample rate
// Sample rate support for audio buffer is coming in miniaudio version 0.12
// Once we have support, we can add sample rate as an optional 4th parameter
// audioEngine.soundHandles[handle]->maAudioBufferConfig.sampleRate = audioEngine.sampleRate;
2022-12-30 20:19:23 +00:00
// Allocate and initialize ma_audio_buffer
audioEngine . maResult =
ma_audio_buffer_alloc_and_init ( & audioEngine . soundHandles [ handle ] - > maAudioBufferConfig , & audioEngine . soundHandles [ handle ] - > maAudioBuffer ) ;
if ( audioEngine . maResult ! = MA_SUCCESS ) {
AUDIO_DEBUG_PRINT ( " Error %i: failed to initialize audio buffer " , audioEngine . maResult ) ;
audioEngine . soundHandles [ handle ] - > isUsed = false ;
return INVALID_SOUND_HANDLE ;
}
// Create a ma_sound from the ma_audio_buffer
audioEngine . maResult = ma_sound_init_from_data_source ( & audioEngine . maEngine , audioEngine . soundHandles [ handle ] - > maAudioBuffer ,
audioEngine . soundHandles [ handle ] - > maFlags , NULL , & audioEngine . soundHandles [ handle ] - > maSound ) ;
if ( audioEngine . maResult ! = MA_SUCCESS ) {
AUDIO_DEBUG_PRINT ( " Error %i: failed to initialize data source " , audioEngine . maResult ) ;
ma_audio_buffer_uninit_and_free ( audioEngine . soundHandles [ handle ] - > maAudioBuffer ) ;
audioEngine . soundHandles [ handle ] - > maAudioBuffer = nullptr ;
2023-01-07 18:44:59 +00:00
audioEngine . soundHandles [ handle ] - > isUsed = false ;
2022-12-30 20:19:23 +00:00
return INVALID_SOUND_HANDLE ;
}
2023-01-07 18:44:59 +00:00
AUDIO_DEBUG_PRINT ( " Frames = %i, channels = %i, bits = %i, ma_format = %i, pointer = %p " , audioEngine . soundHandles [ handle ] - > maAudioBuffer - > ref . sizeInFrames ,
audioEngine . soundHandles [ handle ] - > maAudioBuffer - > ref . channels , bits , audioEngine . soundHandles [ handle ] - > maAudioBuffer - > ref . format ,
audioEngine . soundHandles [ handle ] - > maAudioBuffer - > ref . pData ) ;
2022-12-30 20:19:23 +00:00
return handle ;
}
2022-08-26 15:15:16 +00:00
/// <summary>
/// This function returns a _MEM value referring to a sound's raw data in memory using a designated sound handle created by the _SNDOPEN function.
/// miniaudio supports a variety of sample and channel formats. Translating all of that to basic 2 channel 16-bit format that
/// MemSound was originally supporting would require significant overhead both in terms of system resources and code.
/// For now we are just exposing the underlying PCM data directly from miniaudio. This fits rather well using the existing mem structure.
/// Mono sounds should continue to work just as it was before. Stereo and multi-channel sounds however will be required to be handled correctly
/// by the user by checking the 'elementsize' (for frame size in bytes) and 'type' (for data type) members.
/// </summary>
/// <param name="handle">A sound handle</param>
/// <param name="targetChannel">This should be 0 (for interleaved) or 1 (for mono). Anything else will result in failure</param>
/// <returns>A _MEM value that can be used to access the sound data</returns>
mem_block func__memsound ( int32_t handle , int32_t targetChannel ) {
2023-01-07 18:44:59 +00:00
ma_format maFormat = ma_format : : ma_format_unknown ;
ma_uint32 channels = 0 ;
ma_uint64 sampleFrames = 0 ;
ptrszint data = NULL ;
2022-12-30 20:19:23 +00:00
// Setup mem_block (assuming failure)
mem_block mb = { } ;
mb . lock_offset = ( ptrszint ) mem_lock_base ;
mb . lock_id = INVALID_MEM_LOCK ;
2022-08-26 15:15:16 +00:00
2022-12-30 20:19:23 +00:00
// Return invalid mem_block if audio is not initialized, handle is invalid or sound type is not static
2022-08-26 15:15:16 +00:00
if ( ! audioEngine . isInitialized | | ! IS_SOUND_HANDLE_VALID ( handle ) | | audioEngine . soundHandles [ handle ] - > type ! = SoundType : : Static | |
2022-12-30 20:19:23 +00:00
( targetChannel ! = 0 & & targetChannel ! = 1 ) ) {
AUDIO_DEBUG_PRINT ( " Invalid handle (%i), sound type (%i) or channel (%i) " , handle , audioEngine . soundHandles [ handle ] - > type , targetChannel ) ;
return mb ;
2022-08-26 15:15:16 +00:00
}
2022-12-30 20:19:23 +00:00
// Check what kind of sound we are dealing with and take appropriate path
if ( audioEngine . soundHandles [ handle ] - > maAudioBuffer ) { // we are dealing with a user created audio buffer
AUDIO_DEBUG_PRINT ( " Entering ma_audio_buffer path " ) ;
2023-01-07 18:44:59 +00:00
maFormat = audioEngine . soundHandles [ handle ] - > maAudioBuffer - > ref . format ;
channels = audioEngine . soundHandles [ handle ] - > maAudioBuffer - > ref . channels ;
sampleFrames = audioEngine . soundHandles [ handle ] - > maAudioBuffer - > ref . sizeInFrames ;
data = ( ptrszint ) audioEngine . soundHandles [ handle ] - > maAudioBuffer - > ref . pData ;
2022-12-30 20:19:23 +00:00
} else { // we are dealing with a sound loaded from file or memory
AUDIO_DEBUG_PRINT ( " Entering ma_resource_manager_data_buffer path " ) ;
// The sound cannot be steaming and must be completely decoded in memory
if ( audioEngine . soundHandles [ handle ] - > maFlags & MA_SOUND_FLAG_STREAM | | ! ( audioEngine . soundHandles [ handle ] - > maFlags & MA_SOUND_FLAG_DECODE ) ) {
AUDIO_DEBUG_PRINT ( " Sound data is not completely decoded " ) ;
return mb ;
}
2022-08-26 15:15:16 +00:00
2022-12-30 20:19:23 +00:00
// Get the pointer to the data source
auto ds = ( ma_resource_manager_data_buffer * ) ma_sound_get_data_source ( & audioEngine . soundHandles [ handle ] - > maSound ) ;
if ( ! ds | | ! ds - > pNode ) {
AUDIO_DEBUG_PRINT ( " Data source pointer OR data source node pointer is NULL " ) ;
return mb ;
}
2022-08-26 15:15:16 +00:00
2022-12-30 20:19:23 +00:00
// Check if the data is one contigious buffer or a link list of decoded pages
// We cannot have a mem object for a link list of decoded pages for obvious reasons
if ( ds - > pNode - > data . type ! = ma_resource_manager_data_supply_type : : ma_resource_manager_data_supply_type_decoded ) {
AUDIO_DEBUG_PRINT ( " Data is not a contigious buffer. Type = %u " , ds - > pNode - > data . type ) ;
return mb ;
}
2022-08-26 15:15:16 +00:00
2022-12-30 20:19:23 +00:00
// Check the data pointer
if ( ! ds - > pNode - > data . backend . decoded . pData ) {
AUDIO_DEBUG_PRINT ( " Data source data pointer is NULL " ) ;
return mb ;
}
2022-08-26 15:15:16 +00:00
2022-12-30 20:19:23 +00:00
// Query the data format
if ( ma_sound_get_data_format ( & audioEngine . soundHandles [ handle ] - > maSound , & maFormat , & channels , NULL , NULL , 0 ) ! = MA_SUCCESS ) {
AUDIO_DEBUG_PRINT ( " Data format query failed " ) ;
return mb ;
}
2022-08-26 15:15:16 +00:00
2022-12-30 20:19:23 +00:00
// Get the length in sample frames
if ( ma_sound_get_length_in_pcm_frames ( & audioEngine . soundHandles [ handle ] - > maSound , & sampleFrames ) ! = MA_SUCCESS ) {
AUDIO_DEBUG_PRINT ( " PCM frames query failed " ) ;
return mb ;
}
data = ( ptrszint ) ds - > pNode - > data . backend . decoded . pData ;
2022-08-26 15:15:16 +00:00
}
2023-01-07 18:44:59 +00:00
AUDIO_DEBUG_PRINT ( " Format = %u, channels = %u, frames = %llu " , maFormat , channels , sampleFrames ) ;
2022-08-26 15:15:16 +00:00
2022-12-30 20:19:23 +00:00
// Setup type: This was not done in the old code
// But we are doing it here. By examing the type the user can now figure out if they have to use FP32 or integers
switch ( maFormat ) {
case ma_format : : ma_format_f32 :
mb . type = 4 + 256 ; // FP32
break ;
case ma_format : : ma_format_s32 :
mb . type = 4 + 128 ; // signed int32
break ;
case ma_format : : ma_format_s16 :
mb . type = 2 + 128 ; // signed int16
break ;
case ma_format : : ma_format_u8 :
mb . type = 1 + 128 + 1024 ; // unsigned int8
break ;
default :
AUDIO_DEBUG_PRINT ( " Unsupported audio format " ) ;
return mb ;
}
2022-08-26 15:15:16 +00:00
if ( audioEngine . soundHandles [ handle ] - > memLockOffset ) {
2022-12-30 20:19:23 +00:00
AUDIO_DEBUG_PRINT ( " Returning previously created mem_lock " ) ;
2022-08-26 15:15:16 +00:00
mb . lock_offset = ( ptrszint ) audioEngine . soundHandles [ handle ] - > memLockOffset ;
mb . lock_id = audioEngine . soundHandles [ handle ] - > memLockId ;
} else {
2022-12-30 20:19:23 +00:00
AUDIO_DEBUG_PRINT ( " Returning new mem_lock " ) ;
2022-08-26 15:15:16 +00:00
new_mem_lock ( ) ;
mem_lock_tmp - > type = MEM_TYPE_SOUND ;
mb . lock_offset = ( ptrszint ) mem_lock_tmp ;
mb . lock_id = mem_lock_id ;
audioEngine . soundHandles [ handle ] - > memLockOffset = ( void * ) mem_lock_tmp ;
audioEngine . soundHandles [ handle ] - > memLockId = mem_lock_id ;
}
mb . elementsize = ma_get_bytes_per_frame ( maFormat , channels ) ; // Set the element size. This is the size of each PCM frame in bytes
2022-12-30 20:19:23 +00:00
mb . offset = data ; // Setup offset
2022-08-26 15:15:16 +00:00
mb . size = sampleFrames * mb . elementsize ; // Setup size (in bytes)
mb . sound = handle ; // Copy the handle
mb . image = 0 ; // Not needed. Set to 0
2023-01-07 18:44:59 +00:00
AUDIO_DEBUG_PRINT ( " ElementSize = %lli, size = %lli, type = %lli, pointer = %p " , mb . elementsize , mb . size , mb . type , mb . offset ) ;
2022-08-26 15:15:16 +00:00
return mb ;
}
/// <summary>
/// This initializes the QBPE audio subsystem.
/// We simply attempt to initialize and then set some globals with the results.
/// </summary>
void snd_init ( ) {
// Exit if engine is initialize or already initialization was attempted but failed
if ( audioEngine . isInitialized | | audioEngine . initializationFailed )
return ;
// Initialize the miniaudio resource manager
audioEngine . maResourceManagerConfig = ma_resource_manager_config_init ( ) ;
AudioEngineAttachCustomBackendVTables ( & audioEngine . maResourceManagerConfig ) ;
audioEngine . maResourceManagerConfig . pCustomDecodingBackendUserData = NULL ; // <- pUserData parameter of each function in the decoding backend vtables
audioEngine . maResult = ma_resource_manager_init ( & audioEngine . maResourceManagerConfig , & audioEngine . maResourceManager ) ;
if ( audioEngine . maResult ! = MA_SUCCESS ) {
audioEngine . initializationFailed = true ;
AUDIO_DEBUG_PRINT ( " Failed to initialize miniaudio resource manager " ) ;
return ;
}
// Once we have a resource manager we can create the engine
audioEngine . maEngineConfig = ma_engine_config_init ( ) ;
audioEngine . maEngineConfig . pResourceManager = & audioEngine . maResourceManager ;
// Attempt to initialize with miniaudio defaults
audioEngine . maResult = ma_engine_init ( & audioEngine . maEngineConfig , & audioEngine . maEngine ) ;
// If failed, then set the global flag so that we don't attempt to initialize again
if ( audioEngine . maResult ! = MA_SUCCESS ) {
ma_resource_manager_uninit ( & audioEngine . maResourceManager ) ;
audioEngine . initializationFailed = true ;
AUDIO_DEBUG_PRINT ( " miniaudio initialization failed " ) ;
return ;
}
// Get and save the engine sample rate. We will let miniaudio choose the device sample rate for us
// This ensures we get the lowest latency
// Set the resource manager decorder sample rate to the device sample rate (miniaudio engine bug?)
audioEngine . maResourceManager . config . decodedSampleRate = audioEngine . sampleRate = ma_engine_get_sample_rate ( & audioEngine . maEngine ) ;
// Set the initialized flag as true
audioEngine . isInitialized = true ;
AUDIO_DEBUG_PRINT ( " Audio engine initialized at %uHz sample rate " , audioEngine . sampleRate ) ;
// Reserve sound handle 0 so that nothing else can use it
// We will use this handle internally for Play(), Beep(), Sound() etc.
audioEngine . sndInternal = audioEngine . AllocateSoundHandle ( ) ;
AUDIO_DEBUG_CHECK ( audioEngine . sndInternal = = 0 ) ; // The first handle must return 0 and this is what is used by Beep and Sound
}
/// <summary>
/// This shuts down the audio engine and frees any resources used.
/// </summary>
void snd_un_init ( ) {
if ( audioEngine . isInitialized ) {
// Free all sound handles here
for ( size_t handle = 0 ; handle < audioEngine . soundHandles . size ( ) ; handle + + ) {
audioEngine . FreeSoundHandle ( handle ) ; // Let FreeSoundHandle do it's thing
delete audioEngine . soundHandles [ handle ] ; // Now free the object created by AllocateSoundHandle()
}
// Now that all sounds are closed and SoundHandle objects are freed, clear the vector
audioEngine . soundHandles . clear ( ) ;
// Invalidate internal handles
audioEngine . sndInternal = audioEngine . sndInternalRaw = INVALID_SOUND_HANDLE ;
// Shutdown miniaudio
ma_engine_uninit ( & audioEngine . maEngine ) ;
// Shutdown the miniaudio resource manager
ma_resource_manager_uninit ( & audioEngine . maResourceManager ) ;
// Set engine initialized flag as false
audioEngine . isInitialized = false ;
AUDIO_DEBUG_PRINT ( " Audio engine shutdown " ) ;
}
}
/// <summary>
/// This is called by the QBPE library code.
/// We use this for housekeeping and other stuff.
/// </summary>
void snd_mainloop ( ) {
if ( audioEngine . isInitialized ) {
// Scan through the whole handle vector to find anything we need to update or close
for ( size_t handle = 0 ; handle < audioEngine . soundHandles . size ( ) ; handle + + ) {
// Only process handles that are in use
if ( audioEngine . soundHandles [ handle ] - > isUsed ) {
// Look for stuff that is set to auto-destruct
if ( audioEngine . soundHandles [ handle ] - > autoKill ) {
switch ( audioEngine . soundHandles [ handle ] - > type ) {
case SoundType : : Static :
2022-12-21 06:17:41 +00:00
case SoundType : : Raw :
2022-08-26 15:15:16 +00:00
// Dispose the sound if it has finished playing
// Note that this means that temporary looping sounds will never close
// Well thats on the programmer. Probably they want it that way
if ( ! ma_sound_is_playing ( & audioEngine . soundHandles [ handle ] - > maSound ) )
audioEngine . FreeSoundHandle ( handle ) ;
break ;
case SoundType : : None :
if ( handle ! = 0 )
AUDIO_DEBUG_PRINT ( " Sound type is 'None' when handle value is not 0 " ) ;
break ;
default :
AUDIO_DEBUG_PRINT ( " Condition not handled " ) ; // It should not come here
}
}
}
}
}
}