mirror of
https://github.com/QB64-Phoenix-Edition/QB64pe.git
synced 2024-09-20 09:04:43 +00:00
cd5e312f5a
Changing midiaudio.h will make it harder to incorporate new versions into QB64-PE as they come out. To fix that I have reverted all the changes to midiaudio.h and moved the few private parts we were using into a separate 'filepath' API that's part of libqb.
619 lines
19 KiB
C++
619 lines
19 KiB
C++
//----------------------------------------------------------------------------------------------------
|
|
// ___ ___ __ _ _ ___ ___ _ _ _ ___ _
|
|
// / _ \| _ ) / /| | || _ \ __| /_\ _ _ __| (_)___ | __|_ _ __ _(_)_ _ ___
|
|
// | (_) | _ \/ _ \_ _| _/ _| / _ \ || / _` | / _ \ | _|| ' \/ _` | | ' \/ -_)
|
|
// \__\_\___/\___/ |_||_| |___| /_/ \_\_,_\__,_|_\___/ |___|_||_\__, |_|_||_\___|
|
|
// |___/
|
|
//
|
|
// QB64-PE Audio Engine powered by miniaudio (https://miniaud.io/)
|
|
//
|
|
// This implements a data source that decodes Reality Adlib Tracker 2 tunes
|
|
// https://realityproductions.itch.io/rad (Public Domain)
|
|
//
|
|
// From: Willy Reeve <shayde0@gmail.com>
|
|
// Sent: Sunday, 7 August, 2022 01:23 PM
|
|
// To: Samuel Gomes <v_2samg@hotmail.com>
|
|
// Cc: Carl Pettitt <carl@clonestudios.co.uk>
|
|
// Subject: Re: Contact from Reality website
|
|
//
|
|
// Hi Samuel,
|
|
//
|
|
// The player source code is Public Domain.
|
|
//
|
|
// Shayde
|
|
//
|
|
// On Fri, Aug 5, 2022 at 6:33 AM Reality website <rogue@3eality.com> wrote:
|
|
// Contact from the Reality website:
|
|
// Name: Samuel Gomes
|
|
// Email: v_2samg@hotmail.com
|
|
// Message:
|
|
//
|
|
// RADv2 - great stuff! I am planning to integrate the included player code in my projects. Can you please
|
|
// let me know the license type for the player code? If there is one, putting it somewhere on the site or
|
|
// zip would be fantastic!
|
|
//
|
|
// Thanks!
|
|
//
|
|
// Copyright (c) 2022 Samuel Gomes
|
|
// https://github.com/a740g
|
|
//
|
|
//-----------------------------------------------------------------------------------------------------
|
|
|
|
//-----------------------------------------------------------------------------------------------------
|
|
// HEADER FILES
|
|
//-----------------------------------------------------------------------------------------------------
|
|
#include "libqb-common.h"
|
|
#include "audio.h"
|
|
#include "filepath.h"
|
|
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
|
|
#include "../miniaudio.h"
|
|
#include "radv2/opal.cpp"
|
|
#define RAD_DETECT_REPEATS 1
|
|
#include "radv2/player20.cpp"
|
|
#include "radv2/validate20.cpp"
|
|
|
|
#include "vtables.h"
|
|
//-----------------------------------------------------------------------------------------------------
|
|
|
|
struct ma_radv2 {
|
|
// This part is for miniaudio
|
|
ma_data_source_base ds; /* The decoder can be used independently as a data source. */
|
|
ma_read_proc onRead;
|
|
ma_seek_proc onSeek;
|
|
ma_tell_proc onTell;
|
|
void *pReadSeekTellUserData;
|
|
ma_format format;
|
|
|
|
// This part is format specific
|
|
RADPlayer *player; // RADv2 player object
|
|
Opal *adlib; // Opal Adlib emulator object
|
|
ma_uint8 *tune; // The song data (RADv2 needs this to be alive for rendering samples!)
|
|
uint32_t totalTime; // Total time in seconds
|
|
int sampleCount; // The number of samples generated in each update
|
|
int sampleUpdate; // Size of each update in samples after which the player must be updated
|
|
};
|
|
|
|
static ma_result ma_radv2_seek_to_pcm_frame(ma_radv2 *pRadv2, ma_uint64 frameIndex) {
|
|
if (!pRadv2) {
|
|
return MA_INVALID_ARGS;
|
|
}
|
|
|
|
// We can only reset the player to the beginning
|
|
if (frameIndex == 0) {
|
|
pRadv2->player->Stop();
|
|
return MA_SUCCESS;
|
|
}
|
|
|
|
return MA_INVALID_OPERATION; // Anything else is not seekable
|
|
}
|
|
|
|
static ma_result ma_radv2_get_data_format(ma_radv2 *pRadv2, ma_format *pFormat, ma_uint32 *pChannels, ma_uint32 *pSampleRate, ma_channel *pChannelMap,
|
|
size_t channelMapCap) {
|
|
/* Defaults for safety. */
|
|
if (pFormat != NULL) {
|
|
*pFormat = ma_format_unknown;
|
|
}
|
|
if (pChannels != NULL) {
|
|
*pChannels = 0;
|
|
}
|
|
if (pSampleRate != NULL) {
|
|
*pSampleRate = 0;
|
|
}
|
|
if (pChannelMap != NULL) {
|
|
memset(pChannelMap, 0, sizeof(*pChannelMap) * channelMapCap);
|
|
}
|
|
|
|
if (!pRadv2) {
|
|
return MA_INVALID_OPERATION;
|
|
}
|
|
|
|
if (pFormat != NULL) {
|
|
*pFormat = pRadv2->format;
|
|
}
|
|
|
|
if (pChannels != NULL) {
|
|
*pChannels = 2; // Stereo
|
|
}
|
|
|
|
if (pSampleRate != NULL) {
|
|
*pSampleRate = MA_DEFAULT_SAMPLE_RATE;
|
|
}
|
|
|
|
if (pChannelMap != NULL) {
|
|
ma_channel_map_init_standard(ma_standard_channel_map_default, pChannelMap, channelMapCap, 2);
|
|
}
|
|
|
|
return MA_SUCCESS;
|
|
}
|
|
|
|
static ma_result ma_radv2_read_pcm_frames(ma_radv2 *pRadv2, void *pFramesOut, ma_uint64 frameCount, ma_uint64 *pFramesRead) {
|
|
if (pFramesRead != NULL) {
|
|
*pFramesRead = 0;
|
|
}
|
|
|
|
if (frameCount == 0) {
|
|
return MA_INVALID_ARGS;
|
|
}
|
|
|
|
if (!pRadv2) {
|
|
return MA_INVALID_ARGS;
|
|
}
|
|
|
|
ma_result result = MA_SUCCESS; /* Must be initialized to MA_SUCCESS. */
|
|
ma_uint64 totalFramesRead = 0;
|
|
ma_int16 *buffer = (ma_int16 *)pFramesOut;
|
|
bool repeat = false;
|
|
|
|
while (totalFramesRead < frameCount) {
|
|
pRadv2->adlib->Sample(&buffer[0], &buffer[1]); // Get the left and right sample
|
|
buffer += 2; // Increment the buffer pointer twice for 2 channels
|
|
++totalFramesRead; // Increment the frame counter
|
|
|
|
// Time to update player?
|
|
pRadv2->sampleCount++;
|
|
if (pRadv2->sampleCount >= pRadv2->sampleUpdate) {
|
|
pRadv2->sampleCount = 0;
|
|
repeat = pRadv2->player->Update();
|
|
}
|
|
|
|
if (repeat) {
|
|
result = MA_AT_END;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (pFramesRead != NULL) {
|
|
*pFramesRead = totalFramesRead;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static ma_result ma_radv2_get_cursor_in_pcm_frames(ma_radv2 *pRadv2, ma_uint64 *pCursor) {
|
|
if (!pCursor) {
|
|
return MA_INVALID_ARGS;
|
|
}
|
|
|
|
*pCursor = 0; /* Safety. */
|
|
|
|
if (!pRadv2) {
|
|
return MA_INVALID_ARGS;
|
|
}
|
|
|
|
ma_int64 offset = (ma_int64)pRadv2->player->GetPlayTimeInSeconds() * MA_DEFAULT_SAMPLE_RATE;
|
|
if (offset < 0) {
|
|
return MA_INVALID_FILE;
|
|
}
|
|
|
|
*pCursor = (ma_uint64)offset;
|
|
|
|
return MA_SUCCESS;
|
|
}
|
|
|
|
static ma_result ma_radv2_get_length_in_pcm_frames(ma_radv2 *pRadv2, ma_uint64 *pLength) {
|
|
if (!pLength) {
|
|
return MA_INVALID_ARGS;
|
|
}
|
|
|
|
*pLength = 0; /* Safety. */
|
|
|
|
if (!pRadv2) {
|
|
return MA_INVALID_ARGS;
|
|
}
|
|
|
|
// Total time in seconds * Opal sample rate
|
|
ma_int64 length = (ma_int64)pRadv2->totalTime * MA_DEFAULT_SAMPLE_RATE;
|
|
if (length < 0) {
|
|
return MA_INVALID_FILE;
|
|
}
|
|
|
|
*pLength = (ma_uint64)length;
|
|
|
|
return MA_SUCCESS;
|
|
}
|
|
|
|
static ma_result ma_radv2_ds_read(ma_data_source *pDataSource, void *pFramesOut, ma_uint64 frameCount, ma_uint64 *pFramesRead) {
|
|
return ma_radv2_read_pcm_frames((ma_radv2 *)pDataSource, pFramesOut, frameCount, pFramesRead);
|
|
}
|
|
|
|
static ma_result ma_radv2_ds_seek(ma_data_source *pDataSource, ma_uint64 frameIndex) { return ma_radv2_seek_to_pcm_frame((ma_radv2 *)pDataSource, frameIndex); }
|
|
|
|
static ma_result ma_radv2_ds_get_data_format(ma_data_source *pDataSource, ma_format *pFormat, ma_uint32 *pChannels, ma_uint32 *pSampleRate,
|
|
ma_channel *pChannelMap, size_t channelMapCap) {
|
|
return ma_radv2_get_data_format((ma_radv2 *)pDataSource, pFormat, pChannels, pSampleRate, pChannelMap, channelMapCap);
|
|
}
|
|
|
|
static ma_result ma_radv2_ds_get_cursor(ma_data_source *pDataSource, ma_uint64 *pCursor) {
|
|
return ma_radv2_get_cursor_in_pcm_frames((ma_radv2 *)pDataSource, pCursor);
|
|
}
|
|
|
|
static ma_result ma_radv2_ds_get_length(ma_data_source *pDataSource, ma_uint64 *pLength) {
|
|
return ma_radv2_get_length_in_pcm_frames((ma_radv2 *)pDataSource, pLength);
|
|
}
|
|
|
|
static ma_data_source_vtable ma_data_source_vtable_radv2 = {ma_radv2_ds_read, ma_radv2_ds_seek, ma_radv2_ds_get_data_format, ma_radv2_ds_get_cursor,
|
|
ma_radv2_ds_get_length};
|
|
|
|
static int ma_radv2_of_callback__read(void *pUserData, unsigned char *pBufferOut, int bytesToRead) {
|
|
ma_radv2 *pRadv2 = (ma_radv2 *)pUserData;
|
|
ma_result result;
|
|
size_t bytesRead;
|
|
|
|
result = pRadv2->onRead(pRadv2->pReadSeekTellUserData, (void *)pBufferOut, bytesToRead, &bytesRead);
|
|
|
|
if (result != MA_SUCCESS) {
|
|
return -1;
|
|
}
|
|
|
|
return (int)bytesRead;
|
|
}
|
|
|
|
static int ma_radv2_of_callback__seek(void *pUserData, ma_int64 offset, int whence) {
|
|
ma_radv2 *pRadv2 = (ma_radv2 *)pUserData;
|
|
ma_result result;
|
|
ma_seek_origin origin;
|
|
|
|
if (whence == SEEK_SET) {
|
|
origin = ma_seek_origin_start;
|
|
} else if (whence == SEEK_END) {
|
|
origin = ma_seek_origin_end;
|
|
} else {
|
|
origin = ma_seek_origin_current;
|
|
}
|
|
|
|
result = pRadv2->onSeek(pRadv2->pReadSeekTellUserData, offset, origin);
|
|
if (result != MA_SUCCESS) {
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static ma_int64 ma_radv2_of_callback__tell(void *pUserData) {
|
|
ma_radv2 *pRadv2 = (ma_radv2 *)pUserData;
|
|
ma_result result;
|
|
ma_int64 cursor;
|
|
|
|
if (pRadv2->onTell == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
result = pRadv2->onTell(pRadv2->pReadSeekTellUserData, &cursor);
|
|
if (result != MA_SUCCESS) {
|
|
return -1;
|
|
}
|
|
|
|
return cursor;
|
|
}
|
|
|
|
static ma_result ma_radv2_init_internal(const ma_decoding_backend_config *pConfig, ma_radv2 *pRadv2) {
|
|
ma_result result;
|
|
ma_data_source_config dataSourceConfig;
|
|
|
|
if (!pRadv2) {
|
|
return MA_INVALID_ARGS;
|
|
}
|
|
|
|
memset(pRadv2, 0, sizeof(*pRadv2));
|
|
pRadv2->format = ma_format::ma_format_s16; // RADv2 Opal outputs 16-bit signed samples by default
|
|
|
|
if (pConfig != NULL && pConfig->preferredFormat == ma_format::ma_format_s16) {
|
|
pRadv2->format = pConfig->preferredFormat;
|
|
} else {
|
|
/* Getting here means something other than s16 was specified. Just leave this unset to use the default format. */
|
|
}
|
|
|
|
dataSourceConfig = ma_data_source_config_init();
|
|
dataSourceConfig.vtable = &ma_data_source_vtable_radv2;
|
|
|
|
result = ma_data_source_init(&dataSourceConfig, &pRadv2->ds);
|
|
if (result != MA_SUCCESS) {
|
|
return result; /* Failed to initialize the base data source. */
|
|
}
|
|
|
|
return MA_SUCCESS;
|
|
}
|
|
|
|
static ma_result ma_radv2_init(ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void *pReadSeekTellUserData,
|
|
const ma_decoding_backend_config *pConfig, const ma_allocation_callbacks *pAllocationCallbacks, ma_radv2 *pRadv2) {
|
|
ma_result result;
|
|
|
|
(void)pAllocationCallbacks; /* Can't seem to find a way to configure memory allocations in libopus. */
|
|
|
|
result = ma_radv2_init_internal(pConfig, pRadv2);
|
|
if (result != MA_SUCCESS) {
|
|
return result;
|
|
}
|
|
|
|
if (onRead == NULL || onSeek == NULL) {
|
|
return MA_INVALID_ARGS; /* onRead and onSeek are mandatory. */
|
|
}
|
|
|
|
pRadv2->onRead = onRead;
|
|
pRadv2->onSeek = onSeek;
|
|
pRadv2->onTell = onTell;
|
|
pRadv2->pReadSeekTellUserData = pReadSeekTellUserData;
|
|
|
|
// Find the size of the file
|
|
if (ma_radv2_of_callback__seek(pRadv2, 0, SEEK_END) != 0) {
|
|
return MA_BAD_SEEK;
|
|
}
|
|
|
|
// Calculate the length
|
|
ma_int64 file_size = ma_radv2_of_callback__tell(pRadv2);
|
|
if (file_size < 1) {
|
|
return MA_INVALID_FILE;
|
|
}
|
|
|
|
// See to the beginning of the file
|
|
if (ma_radv2_of_callback__seek(pRadv2, 0, SEEK_SET) != 0) {
|
|
return MA_BAD_SEEK;
|
|
}
|
|
|
|
// Allocate some memory for the tune
|
|
pRadv2->tune = new uint8_t[file_size];
|
|
if (!pRadv2->tune) {
|
|
return MA_OUT_OF_MEMORY;
|
|
}
|
|
|
|
// Read the file
|
|
if (ma_radv2_of_callback__read(pRadv2, pRadv2->tune, (int)file_size) < 1) {
|
|
delete[] pRadv2->tune;
|
|
pRadv2->tune = nullptr;
|
|
return MA_IO_ERROR;
|
|
}
|
|
|
|
// Create the RADv2 Player objects
|
|
pRadv2->player = new RADPlayer();
|
|
if (!pRadv2->player) {
|
|
delete[] pRadv2->tune;
|
|
pRadv2->tune = nullptr;
|
|
return MA_OUT_OF_MEMORY;
|
|
}
|
|
|
|
// Create the RADv2 Opal object
|
|
pRadv2->adlib = new Opal(MA_DEFAULT_SAMPLE_RATE);
|
|
if (!pRadv2->adlib) {
|
|
delete pRadv2->player;
|
|
pRadv2->player = nullptr;
|
|
delete[] pRadv2->tune;
|
|
pRadv2->tune = nullptr;
|
|
return MA_OUT_OF_MEMORY;
|
|
}
|
|
|
|
// Check if the file is valid
|
|
if (RADValidate(pRadv2->tune, file_size)) {
|
|
delete pRadv2->adlib;
|
|
pRadv2->adlib = nullptr;
|
|
delete pRadv2->player;
|
|
pRadv2->player = nullptr;
|
|
delete[] pRadv2->tune;
|
|
pRadv2->tune = nullptr;
|
|
return MA_INVALID_FILE;
|
|
}
|
|
|
|
// Initialize the player
|
|
// We'll use a lambda here and pass the pRadv2 pointer using 'arg'
|
|
pRadv2->player->Init(
|
|
pRadv2->tune, [](void *arg, uint16_t reg, uint8_t data) { ((ma_radv2 *)arg)->adlib->Port(reg, data); }, pRadv2);
|
|
|
|
// Get the playback rate
|
|
if (pRadv2->player->GetHertz() < 0) {
|
|
delete pRadv2->adlib;
|
|
pRadv2->adlib = nullptr;
|
|
delete pRadv2->player;
|
|
pRadv2->player = nullptr;
|
|
delete[] pRadv2->tune;
|
|
pRadv2->tune = nullptr;
|
|
return MA_INVALID_FILE;
|
|
}
|
|
|
|
// Get the total playback time
|
|
pRadv2->totalTime = pRadv2->player->ComputeTotalTime();
|
|
// Setup some stuff
|
|
pRadv2->sampleCount = 0;
|
|
pRadv2->sampleUpdate = MA_DEFAULT_SAMPLE_RATE / pRadv2->player->GetHertz();
|
|
|
|
return MA_SUCCESS;
|
|
}
|
|
|
|
static ma_result ma_radv2_init_file(const char *pFilePath, const ma_decoding_backend_config *pConfig, const ma_allocation_callbacks *pAllocationCallbacks,
|
|
ma_radv2 *pRadv2) {
|
|
ma_result result;
|
|
|
|
(void)pAllocationCallbacks; /* Can't seem to find a way to configure memory allocations in libopus. */
|
|
|
|
result = ma_radv2_init_internal(pConfig, pRadv2);
|
|
if (result != MA_SUCCESS) {
|
|
return result;
|
|
}
|
|
|
|
// Check the file extension
|
|
if (!filepath_has_extension(pFilePath, "rad")) {
|
|
return MA_INVALID_FILE;
|
|
}
|
|
|
|
// Open the file for reading
|
|
FILE *fd = fopen(pFilePath, "rb");
|
|
if (!fd) {
|
|
return MA_INVALID_FILE;
|
|
}
|
|
|
|
// Find the size of the file
|
|
if (fseek(fd, 0, SEEK_END) != 0) {
|
|
fclose(fd);
|
|
return MA_BAD_SEEK;
|
|
}
|
|
|
|
// Calculate the length
|
|
ma_int64 file_size = ftell(fd);
|
|
if (file_size < 1) {
|
|
fclose(fd);
|
|
return MA_INVALID_FILE;
|
|
}
|
|
|
|
// Seek to the beginning of the file
|
|
if (fseek(fd, 0, SEEK_SET) != 0) {
|
|
fclose(fd);
|
|
return MA_BAD_SEEK;
|
|
}
|
|
|
|
// Allocate some memory for the tune
|
|
pRadv2->tune = new uint8_t[file_size];
|
|
if (!pRadv2->tune) {
|
|
fclose(fd);
|
|
return MA_OUT_OF_MEMORY;
|
|
}
|
|
|
|
// Read the file
|
|
if (fread(pRadv2->tune, file_size, sizeof(uint8_t), fd) < 1) {
|
|
delete[] pRadv2->tune;
|
|
pRadv2->tune = nullptr;
|
|
fclose(fd);
|
|
return MA_IO_ERROR;
|
|
}
|
|
|
|
// Close the file now that we've read it into memory
|
|
fclose(fd);
|
|
|
|
// Create the RADv2 Player objects
|
|
pRadv2->player = new RADPlayer();
|
|
if (!pRadv2->player) {
|
|
delete[] pRadv2->tune;
|
|
pRadv2->tune = nullptr;
|
|
return MA_OUT_OF_MEMORY;
|
|
}
|
|
|
|
// Create the RADv2 Opal object
|
|
pRadv2->adlib = new Opal(MA_DEFAULT_SAMPLE_RATE);
|
|
if (!pRadv2->adlib) {
|
|
delete pRadv2->player;
|
|
pRadv2->player = nullptr;
|
|
delete[] pRadv2->tune;
|
|
pRadv2->tune = nullptr;
|
|
return MA_OUT_OF_MEMORY;
|
|
}
|
|
|
|
// Check if the file is valid
|
|
if (RADValidate(pRadv2->tune, file_size)) {
|
|
delete pRadv2->adlib;
|
|
pRadv2->adlib = nullptr;
|
|
delete pRadv2->player;
|
|
pRadv2->player = nullptr;
|
|
delete[] pRadv2->tune;
|
|
pRadv2->tune = nullptr;
|
|
return MA_INVALID_FILE;
|
|
}
|
|
|
|
// Initialize the player
|
|
// We'll use a lambda here and pass the pRadv2 pointer using 'arg'
|
|
pRadv2->player->Init(
|
|
pRadv2->tune, [](void *arg, uint16_t reg, uint8_t data) { ((ma_radv2 *)arg)->adlib->Port(reg, data); }, pRadv2);
|
|
|
|
// Get the playback rate
|
|
if (pRadv2->player->GetHertz() < 0) {
|
|
delete pRadv2->adlib;
|
|
pRadv2->adlib = nullptr;
|
|
delete pRadv2->player;
|
|
pRadv2->player = nullptr;
|
|
delete[] pRadv2->tune;
|
|
pRadv2->tune = nullptr;
|
|
return MA_INVALID_FILE;
|
|
}
|
|
|
|
// Get the total playback time
|
|
pRadv2->totalTime = pRadv2->player->ComputeTotalTime();
|
|
// Setup some stuff
|
|
pRadv2->sampleCount = 0;
|
|
pRadv2->sampleUpdate = MA_DEFAULT_SAMPLE_RATE / pRadv2->player->GetHertz();
|
|
|
|
return MA_SUCCESS;
|
|
}
|
|
|
|
static void ma_radv2_uninit(ma_radv2 *pRadv2, const ma_allocation_callbacks *pAllocationCallbacks) {
|
|
if (!pRadv2) {
|
|
return;
|
|
}
|
|
|
|
(void)pAllocationCallbacks;
|
|
|
|
// Stop any ongoing playback
|
|
pRadv2->player->Stop();
|
|
|
|
delete pRadv2->adlib;
|
|
pRadv2->adlib = nullptr;
|
|
delete pRadv2->player;
|
|
pRadv2->player = nullptr;
|
|
delete[] pRadv2->tune;
|
|
pRadv2->tune = nullptr;
|
|
|
|
ma_data_source_uninit(&pRadv2->ds);
|
|
}
|
|
|
|
static ma_result ma_decoding_backend_init__radv2(void *pUserData, ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void *pReadSeekTellUserData,
|
|
const ma_decoding_backend_config *pConfig, const ma_allocation_callbacks *pAllocationCallbacks,
|
|
ma_data_source **ppBackend) {
|
|
ma_result result;
|
|
ma_radv2 *pRadv2;
|
|
|
|
(void)pUserData;
|
|
|
|
pRadv2 = (ma_radv2 *)ma_malloc(sizeof(ma_radv2), pAllocationCallbacks);
|
|
if (!pRadv2) {
|
|
return MA_OUT_OF_MEMORY;
|
|
}
|
|
|
|
result = ma_radv2_init(onRead, onSeek, onTell, pReadSeekTellUserData, pConfig, pAllocationCallbacks, pRadv2);
|
|
if (result != MA_SUCCESS) {
|
|
ma_free(pRadv2, pAllocationCallbacks);
|
|
return result;
|
|
}
|
|
|
|
*ppBackend = pRadv2;
|
|
|
|
return MA_SUCCESS;
|
|
}
|
|
|
|
static ma_result ma_decoding_backend_init_file__radv2(void *pUserData, const char *pFilePath, const ma_decoding_backend_config *pConfig,
|
|
const ma_allocation_callbacks *pAllocationCallbacks, ma_data_source **ppBackend) {
|
|
ma_result result;
|
|
ma_radv2 *pRadv2;
|
|
|
|
(void)pUserData;
|
|
|
|
pRadv2 = (ma_radv2 *)ma_malloc(sizeof(ma_radv2), pAllocationCallbacks);
|
|
if (!pRadv2) {
|
|
return MA_OUT_OF_MEMORY;
|
|
}
|
|
|
|
result = ma_radv2_init_file(pFilePath, pConfig, pAllocationCallbacks, pRadv2);
|
|
if (result != MA_SUCCESS) {
|
|
ma_free(pRadv2, pAllocationCallbacks);
|
|
return result;
|
|
}
|
|
|
|
*ppBackend = pRadv2;
|
|
|
|
return MA_SUCCESS;
|
|
}
|
|
|
|
static void ma_decoding_backend_uninit__radv2(void *pUserData, ma_data_source *pBackend, const ma_allocation_callbacks *pAllocationCallbacks) {
|
|
ma_radv2 *pRadv2 = (ma_radv2 *)pBackend;
|
|
|
|
(void)pUserData;
|
|
|
|
ma_radv2_uninit(pRadv2, pAllocationCallbacks);
|
|
ma_free(pRadv2, pAllocationCallbacks);
|
|
}
|
|
|
|
ma_decoding_backend_vtable ma_vtable_radv2 = {
|
|
ma_decoding_backend_init__radv2,
|
|
ma_decoding_backend_init_file__radv2,
|
|
NULL, /* onInitFileW() */
|
|
NULL, /* onInitMemory() */
|
|
ma_decoding_backend_uninit__radv2
|
|
};
|
|
//-----------------------------------------------------------------------------------------------------
|