[DEV] update new etk Uri API

This commit is contained in:
Edouard DUPIN 2018-10-23 22:19:32 +02:00
parent 06b8d5d65e
commit 398828ab6e
7 changed files with 91 additions and 81 deletions

View File

@ -5,7 +5,6 @@
*/ */
#include <etk/types.hpp> #include <etk/types.hpp>
#include <etk/os/FSNode.hpp>
#include <audio/ess/debug.hpp> #include <audio/ess/debug.hpp>
#include <audio/ess/decOgg.hpp> #include <audio/ess/decOgg.hpp>
#include <tremor/ivorbiscodec.h> #include <tremor/ivorbiscodec.h>
@ -14,21 +13,21 @@
static size_t LocalReadFunc(void *ptr, size_t size, size_t nmemb, void *datasource) { static size_t LocalReadFunc(void *ptr, size_t size, size_t nmemb, void *datasource) {
etk::FSNode* file = static_cast<etk::FSNode*>(datasource); etk::io::Interface* fileIO = static_cast<etk::io::Interface*>(datasource);
return file->fileRead(ptr, size, nmemb); return fileIO->read(ptr, size, nmemb);
} }
static int localSeekFunc(void *datasource, ogg_int64_t offset, int whence) { static int localSeekFunc(void *datasource, ogg_int64_t offset, int whence) {
etk::FSNode* file = static_cast<etk::FSNode*>(datasource); etk::io::Interface* fileIO = static_cast<etk::io::Interface*>(datasource);
enum etk::seekNode mode = etk::seekNode_start; etk::io::SeekMode mode = etk::io::SeekMode::Start;
if (whence == SEEK_SET) { if (whence == SEEK_SET) {
mode = etk::seekNode_start; mode = etk::io::SeekMode::Start;
} else if (whence == SEEK_END) { } else if (whence == SEEK_END) {
mode = etk::seekNode_end; mode = etk::io::SeekMode::End;
} else if (whence == SEEK_CUR) { } else if (whence == SEEK_CUR) {
mode = etk::seekNode_current; mode = etk::io::SeekMode::Current;
} }
if (file->fileSeek(offset, mode) == true) { if (fileIO->seek(offset, mode) == true) {
return 0; return 0;
} else { } else {
return -1; return -1;
@ -36,17 +35,17 @@ static int localSeekFunc(void *datasource, ogg_int64_t offset, int whence) {
} }
static int localCloseFunc(void *datasource) { static int localCloseFunc(void *datasource) {
etk::FSNode* file = static_cast<etk::FSNode*>(datasource); etk::io::Interface* fileIO = static_cast<etk::io::Interface*>(datasource);
file->fileClose(); fileIO->close();
return 0; return 0;
} }
static long localTellFunc(void *datasource) { static long localTellFunc(void *datasource) {
etk::FSNode* file = static_cast<etk::FSNode*>(datasource); etk::io::Interface* fileIO = static_cast<etk::io::Interface*>(datasource);
return file->fileTell(); return fileIO->tell();
} }
etk::Vector<float> audio::ess::ogg::loadAudioFile(const etk::String& _filename, int8_t _nbChan) { etk::Vector<float> audio::ess::ogg::loadAudioFile(const etk::Uri& _uri, int8_t _nbChan) {
etk::Vector<float> out; etk::Vector<float> out;
OggVorbis_File vf; OggVorbis_File vf;
int32_t eof=0; int32_t eof=0;
@ -57,24 +56,28 @@ etk::Vector<float> audio::ess::ogg::loadAudioFile(const etk::String& _filename,
localCloseFunc, localCloseFunc,
localTellFunc localTellFunc
}; };
ememory::UniquePtr<etk::FSNode> fileAccess = ememory::UniquePtr<etk::FSNode>(ETK_NEW(etk::FSNode, _filename));
// Start loading the XML : // Start loading the XML :
//EWOLSA_DEBUG("open file (OGG) \"" << fileAccess << "\""); EWOLSA_DEBUG("open file (OGG) " << _uri);
if (false == fileAccess->exist()) { if (etk::uri::exist(_uri) == false) {
EWOLSA_ERROR("File Does not exist : \"" << *fileAccess << "\""); EWOLSA_ERROR("File Does not exist : " << _uri);
return out; return out;
} }
int32_t fileSize = fileAccess->fileSize(); int32_t fileSize = etk::uri::fileSize(_uri);
if (0 == fileSize) { if (fileSize == 0) {
EWOLSA_ERROR("This file is empty : \"" << *fileAccess << "\""); EWOLSA_ERROR("This file is empty : " << _uri);
return out; return out;
} }
if (false == fileAccess->fileOpenRead()) { ememory::SharedPtr<etk::io::Interface> fileIO = etk::uri::get(_uri);
EWOLSA_ERROR("Can not open the file : \"" << *fileAccess << "\""); if (fileIO == null) {
EWOLSA_ERROR("CAn not get file interface : " << _uri);
return out; return out;
} }
if (ov_open_callbacks(&(*fileAccess), &vf, null, 0, tmpCallback) < 0) { if (fileIO->open(etk::io::OpenMode::Read) == false) {
EWOLSA_ERROR("Input does not appear to be an Ogg bitstream."); EWOLSA_ERROR("Can not open the file : " << _uri);
return out;
}
if (ov_open_callbacks(fileIO.get(), &vf, null, 0, tmpCallback) < 0) {
EWOLSA_ERROR("Input does not appear to be an Ogg bitstream: " << _uri);
return out; return out;
} }
vorbis_info *vi=ov_info(&vf,-1); vorbis_info *vi=ov_info(&vf,-1);

View File

@ -6,11 +6,12 @@
#pragma once #pragma once
#include <etk/types.hpp> #include <etk/types.hpp>
#include <etk/uri/uri.hpp>
namespace audio { namespace audio {
namespace ess { namespace ess {
namespace ogg { namespace ogg {
etk::Vector<float> loadAudioFile(const etk::String& _filename, int8_t _nbChan); etk::Vector<float> loadAudioFile(const etk::Uri& _uri, int8_t _nbChan);
} }
} }

View File

@ -5,7 +5,6 @@
*/ */
#include <etk/types.hpp> #include <etk/types.hpp>
#include <etk/os/FSNode.hpp>
#include <audio/ess/debug.hpp> #include <audio/ess/debug.hpp>
#include <audio/ess/decWav.hpp> #include <audio/ess/decWav.hpp>
@ -57,37 +56,40 @@ typedef struct {
#define COMPR_G721 (64) #define COMPR_G721 (64)
#define COMPR_MPEG (80) #define COMPR_MPEG (80)
etk::Vector<float> audio::ess::wav::loadAudioFile(const etk::String& _filename, int8_t _nbChan) { etk::Vector<float> audio::ess::wav::loadAudioFile(const etk::Uri& _uri, int8_t _nbChan) {
etk::Vector<float> out; etk::Vector<float> out;
waveHeader myHeader; waveHeader myHeader;
memset(&myHeader, 0, sizeof(waveHeader)); memset(&myHeader, 0, sizeof(waveHeader));
etk::FSNode fileAccess(_filename);
// Start loading the XML : // Start loading the XML :
EWOLSA_DEBUG("open file (WAV) \"" << fileAccess << "\""); EWOLSA_DEBUG("open file (WAV) " << _uri);
if (etk::uri::exist(_uri) == false) {
if (false == fileAccess.exist()) { EWOLSA_ERROR("File Does not exist : " << _uri);
EWOLSA_ERROR("File Does not exist : \"" << fileAccess << "\"");
return out; return out;
} }
int32_t fileSize = fileAccess.fileSize(); int32_t fileSize = etk::uri::fileSize(_uri);
if (0 == fileSize) { if (0 == fileSize) {
EWOLSA_ERROR("This file is empty : \"" << fileAccess << "\""); EWOLSA_ERROR("This file is empty : " << _uri);
return out; return out;
} }
if (false == fileAccess.fileOpenRead()) { ememory::SharedPtr<etk::io::Interface> fileIO = etk::uri::get(_uri);
EWOLSA_ERROR("Can not open the file : \"" << fileAccess << "\""); if (fileIO == null) {
EWOLSA_ERROR("CAn not get file interface : " << _uri);
return out;
}
if (fileIO->open(etk::io::OpenMode::Read) == false) {
EWOLSA_ERROR("Can not open the file : " << _uri);
return out; return out;
} }
// try to find endienness : // try to find endienness :
if (fileSize < (int64_t)sizeof(waveHeader)) { if (fileSize < (int64_t)sizeof(waveHeader)) {
EWOLSA_ERROR("File : \"" << fileAccess << "\" == > has not enouth data inside might be minumum of " << (int32_t)(sizeof(waveHeader))); EWOLSA_ERROR("File : " << _uri << " == > has not enouth data inside might be minumum of " << (int32_t)(sizeof(waveHeader)));
return out; return out;
} }
// ---------------------------------------------- // ----------------------------------------------
// read the header : // read the header :
// ---------------------------------------------- // ----------------------------------------------
if (fileAccess.fileRead(&myHeader.riffTag, 1, 4)!=4) { if (fileIO->read(&myHeader.riffTag, 1, 4)!=4) {
EWOLSA_ERROR("Can not 4 element in the file : \"" << fileAccess << "\""); EWOLSA_ERROR("Can not 4 element in the file : " << _uri);
return out; return out;
} }
bool littleEndien = false; bool littleEndien = false;
@ -99,55 +101,55 @@ etk::Vector<float> audio::ess::wav::loadAudioFile(const etk::String& _filename,
littleEndien = true; littleEndien = true;
} }
} else { } else {
EWOLSA_ERROR("file: \"" << fileAccess << "\" Does not start with \"RIF\" " ); EWOLSA_ERROR("file: " << _uri << " Does not start with 'RIF' " );
return out; return out;
} }
// get the data size : // get the data size :
unsigned char tmpData[32]; unsigned char tmpData[32];
if (fileAccess.fileRead(tmpData, 1, 4)!=4) { if (fileIO->read(tmpData, 1, 4)!=4) {
EWOLSA_ERROR("Can not 4 element in the file : \"" << fileAccess << "\""); EWOLSA_ERROR("Can not 4 element in the file : " << _uri);
return out; return out;
} }
myHeader.size = CONVERT_UINT32(littleEndien, tmpData); myHeader.size = CONVERT_UINT32(littleEndien, tmpData);
// get the data size : // get the data size :
if (fileAccess.fileRead(&myHeader.waveTag, 1, 4)!=4) { if (fileIO->read(&myHeader.waveTag, 1, 4)!=4) {
EWOLSA_ERROR("Can not 4 element in the file : \"" << fileAccess << "\""); EWOLSA_ERROR("Can not 4 element in the file : " << _uri);
return out; return out;
} }
if( myHeader.waveTag[0] != 'W' if( myHeader.waveTag[0] != 'W'
|| myHeader.waveTag[1] != 'A' || myHeader.waveTag[1] != 'A'
|| myHeader.waveTag[2] != 'V' || myHeader.waveTag[2] != 'V'
|| myHeader.waveTag[3] != 'E' ) { || myHeader.waveTag[3] != 'E' ) {
EWOLSA_ERROR("file: \"" << fileAccess << "\" This is not a wave file " << myHeader.waveTag[0] << myHeader.waveTag[1] << myHeader.waveTag[2] << myHeader.waveTag[3] ); EWOLSA_ERROR("file: " << _uri << " This is not a wave file " << myHeader.waveTag[0] << myHeader.waveTag[1] << myHeader.waveTag[2] << myHeader.waveTag[3] );
return out; return out;
} }
// get the data size : // get the data size :
if (fileAccess.fileRead(&myHeader.fmtTag, 1, 4)!=4) { if (fileIO->read(&myHeader.fmtTag, 1, 4)!=4) {
EWOLSA_ERROR("Can not 4 element in the file : \"" << fileAccess << "\""); EWOLSA_ERROR("Can not 4 element in the file : " << _uri);
return out; return out;
} }
if( myHeader.fmtTag[0] != 'f' if( myHeader.fmtTag[0] != 'f'
|| myHeader.fmtTag[1] != 'm' || myHeader.fmtTag[1] != 'm'
|| myHeader.fmtTag[2] != 't' || myHeader.fmtTag[2] != 't'
|| myHeader.fmtTag[3] != ' ' ) { || myHeader.fmtTag[3] != ' ' ) {
EWOLSA_ERROR("file: \"" << fileAccess << "\" header error ..." << myHeader.fmtTag[0] << myHeader.fmtTag[1] << myHeader.fmtTag[2] << myHeader.fmtTag[3]); EWOLSA_ERROR("file: " << _uri << " header error ..." << myHeader.fmtTag[0] << myHeader.fmtTag[1] << myHeader.fmtTag[2] << myHeader.fmtTag[3]);
return out; return out;
} }
// get the data size : // get the data size :
if (fileAccess.fileRead(tmpData, 1, 4)!=4) { if (fileIO->read(tmpData, 1, 4)!=4) {
EWOLSA_ERROR("Can not 4 element in the file : \"" << fileAccess << "\""); EWOLSA_ERROR("Can not 4 element in the file : " << _uri);
return out; return out;
} }
myHeader.waveFormatSize = CONVERT_UINT32(littleEndien, tmpData); myHeader.waveFormatSize = CONVERT_UINT32(littleEndien, tmpData);
if (myHeader.waveFormatSize != 16) { if (myHeader.waveFormatSize != 16) {
EWOLSA_ERROR("file : \"" << fileAccess << "\" == > header error ..."); EWOLSA_ERROR("file : " << _uri << " == > header error ...");
return out; return out;
} }
if (fileAccess.fileRead(tmpData, 1, 16)!=16) { if (fileIO->read(tmpData, 1, 16)!=16) {
EWOLSA_ERROR("Can not 16 element in the file : \"" << fileAccess << "\""); EWOLSA_ERROR("Can not 16 element in the file : " << _uri);
return out; return out;
} }
unsigned char * tmppp = tmpData; unsigned char * tmppp = tmpData;
@ -170,20 +172,20 @@ etk::Vector<float> audio::ess::wav::loadAudioFile(const etk::String& _filename,
EWOLSA_DEBUG(" bytesPerFrame : " << myHeader.waveFormat.bytesPerFrame); EWOLSA_DEBUG(" bytesPerFrame : " << myHeader.waveFormat.bytesPerFrame);
EWOLSA_DEBUG(" bitsPerSample : " << myHeader.waveFormat.bitsPerSample); EWOLSA_DEBUG(" bitsPerSample : " << myHeader.waveFormat.bitsPerSample);
// get the data size : // get the data size :
if (fileAccess.fileRead(&myHeader.dataTag, 1, 4)!=4) { if (fileIO->read(&myHeader.dataTag, 1, 4)!=4) {
EWOLSA_ERROR("Can not 4 element in the file : \"" << fileAccess << "\""); EWOLSA_ERROR("Can not 4 element in the file : " << _uri);
return out; return out;
} }
if( myHeader.dataTag[0] != 'd' if( myHeader.dataTag[0] != 'd'
|| myHeader.dataTag[1] != 'a' || myHeader.dataTag[1] != 'a'
|| myHeader.dataTag[2] != 't' || myHeader.dataTag[2] != 't'
|| myHeader.dataTag[3] != 'a' ) { || myHeader.dataTag[3] != 'a' ) {
EWOLSA_ERROR("file: \"" << fileAccess << "\" header error ..." << myHeader.dataTag[0] << myHeader.dataTag[1] << myHeader.dataTag[2] << myHeader.dataTag[3]); EWOLSA_ERROR("file: " << _uri << " header error ..." << myHeader.dataTag[0] << myHeader.dataTag[1] << myHeader.dataTag[2] << myHeader.dataTag[3]);
return out; return out;
} }
// get the data size : // get the data size :
if (fileAccess.fileRead(tmpData, 1, 4)!=4) { if (fileIO->read(tmpData, 1, 4)!=4) {
EWOLSA_ERROR("Can not 4 element in the file : \"" << fileAccess << "\""); EWOLSA_ERROR("Can not 4 element in the file : " << _uri);
return out; return out;
} }
myHeader.dataSize = CONVERT_UINT32(littleEndien, tmpData); myHeader.dataSize = CONVERT_UINT32(littleEndien, tmpData);
@ -194,22 +196,22 @@ etk::Vector<float> audio::ess::wav::loadAudioFile(const etk::String& _filename,
//Parse the data and transform it if needed ... //Parse the data and transform it if needed ...
if (COMPR_PCM != myHeader.waveFormat.type) { if (COMPR_PCM != myHeader.waveFormat.type) {
EWOLSA_ERROR("File : \"" << fileAccess << "\" == > support only PCM compression ..."); EWOLSA_ERROR("File : " << _uri << " == > support only PCM compression ...");
return out; return out;
} }
if (myHeader.waveFormat.channelCount == 0 || myHeader.waveFormat.channelCount>2) { if (myHeader.waveFormat.channelCount == 0 || myHeader.waveFormat.channelCount>2) {
EWOLSA_ERROR("File : \"" << fileAccess << "\" == > support only mono or stereo ..." << myHeader.waveFormat.channelCount); EWOLSA_ERROR("File : " << _uri << " == > support only mono or stereo ..." << myHeader.waveFormat.channelCount);
return out; return out;
} }
if ( ! ( myHeader.waveFormat.bitsPerSample == 16 if ( ! ( myHeader.waveFormat.bitsPerSample == 16
|| myHeader.waveFormat.bitsPerSample == 24 || myHeader.waveFormat.bitsPerSample == 24
|| myHeader.waveFormat.bitsPerSample == 32 ) ) { || myHeader.waveFormat.bitsPerSample == 32 ) ) {
EWOLSA_ERROR("File : \"" << fileAccess << "\" == > not supported bit/sample ..." << myHeader.waveFormat.bitsPerSample); EWOLSA_ERROR("File : " << _uri << " == > not supported bit/sample ..." << myHeader.waveFormat.bitsPerSample);
return out; return out;
} }
if( ! ( 44100 == myHeader.waveFormat.samplesPerSec if( ! ( 44100 == myHeader.waveFormat.samplesPerSec
|| 48000 == myHeader.waveFormat.samplesPerSec) ) { || 48000 == myHeader.waveFormat.samplesPerSec) ) {
EWOLSA_ERROR("File : \"" << fileAccess << "\" == > not supported frequency " << myHeader.waveFormat.samplesPerSec << " != 48000"); EWOLSA_ERROR("File : " << _uri << " == > not supported frequency " << myHeader.waveFormat.samplesPerSec << " != 48000");
return out; return out;
} }
EWOLSA_DEBUG(" dataSize : " << myHeader.dataSize); EWOLSA_DEBUG(" dataSize : " << myHeader.dataSize);
@ -224,14 +226,14 @@ etk::Vector<float> audio::ess::wav::loadAudioFile(const etk::String& _filename,
char audioSample[8]; char audioSample[8];
if (myHeader.waveFormat.bitsPerSample == 16) { if (myHeader.waveFormat.bitsPerSample == 16) {
if (myHeader.waveFormat.channelCount == 1) { if (myHeader.waveFormat.channelCount == 1) {
if (fileAccess.fileRead(audioSample, 1, 2)!=2) { if (fileIO->read(audioSample, 1, 2)!=2) {
EWOLSA_ERROR("Read Error at position : " << iii); EWOLSA_ERROR("Read Error at position : " << iii);
return out; return out;
} }
left = ((int32_t)((int16_t)CONVERT_INT16(littleEndien, audioSample))) << 16; left = ((int32_t)((int16_t)CONVERT_INT16(littleEndien, audioSample))) << 16;
right = left; right = left;
} else { } else {
if (fileAccess.fileRead(audioSample, 1, 4)!=4) { if (fileIO->read(audioSample, 1, 4)!=4) {
EWOLSA_ERROR("Read Error at position : " << iii); EWOLSA_ERROR("Read Error at position : " << iii);
return out; return out;
} }
@ -240,14 +242,14 @@ etk::Vector<float> audio::ess::wav::loadAudioFile(const etk::String& _filename,
} }
} else if (myHeader.waveFormat.bitsPerSample == 24) { } else if (myHeader.waveFormat.bitsPerSample == 24) {
if (myHeader.waveFormat.channelCount == 1) { if (myHeader.waveFormat.channelCount == 1) {
if (fileAccess.fileRead(audioSample, 1, 3)!=3) { if (fileIO->read(audioSample, 1, 3)!=3) {
EWOLSA_ERROR("Read Error at position : " << iii); EWOLSA_ERROR("Read Error at position : " << iii);
return out; return out;
} }
left = CONVERT_INT24(littleEndien, audioSample); left = CONVERT_INT24(littleEndien, audioSample);
right = left; right = left;
} else { } else {
if (fileAccess.fileRead(audioSample, 1, 6)!=6) { if (fileIO->read(audioSample, 1, 6)!=6) {
EWOLSA_ERROR("Read Error at position : " << iii); EWOLSA_ERROR("Read Error at position : " << iii);
return out; return out;
} }
@ -256,14 +258,14 @@ etk::Vector<float> audio::ess::wav::loadAudioFile(const etk::String& _filename,
} }
} else if (myHeader.waveFormat.bitsPerSample == 32) { } else if (myHeader.waveFormat.bitsPerSample == 32) {
if (myHeader.waveFormat.channelCount == 1) { if (myHeader.waveFormat.channelCount == 1) {
if (fileAccess.fileRead(audioSample, 1, 4)!=4) { if (fileIO->read(audioSample, 1, 4)!=4) {
EWOLSA_ERROR("Read Error at position : " << iii); EWOLSA_ERROR("Read Error at position : " << iii);
return out; return out;
} }
left = CONVERT_INT32(littleEndien, audioSample); left = CONVERT_INT32(littleEndien, audioSample);
right = left; right = left;
} else { } else {
if (fileAccess.fileRead(audioSample, 1, 8)!=8) { if (fileIO->read(audioSample, 1, 8)!=8) {
EWOLSA_ERROR("Read Error at position : " << iii); EWOLSA_ERROR("Read Error at position : " << iii);
return out; return out;
} }
@ -280,7 +282,7 @@ etk::Vector<float> audio::ess::wav::loadAudioFile(const etk::String& _filename,
} }
} }
// close the file: // close the file:
fileAccess.fileClose(); fileIO->close();
return out; return out;
} }

View File

@ -6,11 +6,12 @@
#pragma once #pragma once
#include <etk/types.hpp> #include <etk/types.hpp>
#include <etk/uri/uri.hpp>
namespace audio { namespace audio {
namespace ess { namespace ess {
namespace wav { namespace wav {
etk::Vector<float> loadAudioFile(const etk::String& _filename, int8_t _nbChan); etk::Vector<float> loadAudioFile(const etk::Uri& _uri, int8_t _nbChan);
} }
} }
} }

View File

@ -12,6 +12,7 @@
#include <audio/ess/ess.hpp> #include <audio/ess/ess.hpp>
#include <audio/ess/debug.hpp> #include <audio/ess/debug.hpp>
#include <ejson/ejson.hpp> #include <ejson/ejson.hpp>
#include <etk/uri/uri.hpp>
ememory::SharedPtr<audio::river::Manager> g_audioManager; ememory::SharedPtr<audio::river::Manager> g_audioManager;
ememory::SharedPtr<audio::ess::Effects> g_effects; ememory::SharedPtr<audio::ess::Effects> g_effects;
@ -52,8 +53,10 @@ void audio::ess::soundSetParse(const etk::String& _data) {
} }
} }
void audio::ess::soundSetLoad(const etk::String& _file) { void audio::ess::soundSetLoad(const etk::Uri& _uri) {
soundSetParse(etk::FSNodeReadAllData(_file)); etk::String data;
etk::uri::readAll(_uri, data);
soundSetParse(data);
} }
void audio::ess::musicPlay(const etk::String& _name) { void audio::ess::musicPlay(const etk::String& _name) {

View File

@ -32,9 +32,9 @@ namespace audio {
void soundSetParse(const etk::String& _data); void soundSetParse(const etk::String& _data);
/** /**
* @brief Parse a configuration file of a soundset * @brief Parse a configuration file of a soundset
* @param[in] _file JSON file to parse * @param[in] _uri JSON file to parse
*/ */
void soundSetLoad(const etk::String& _file); void soundSetLoad(const etk::Uri& _uri);
/** /**
* @brief Play a music with his name * @brief Play a music with his name

View File

@ -24,14 +24,14 @@ A simple example:
```{.json} ```{.json}
{ {
musics:{ musics:{
"BG-1":"DATA:audio/Clean Soul.ogg" "BG-1":"DATA:///audio/Clean Soul.ogg"
"BG-22":"DATA:audio/Dark knight.ogg" "BG-22":"DATA:///audio/Dark knight.ogg"
}, },
effects:{ effects:{
"end":"DATA:audio/end.wav", "end":"DATA:///audio/end.wav",
"error":"DATA:audio/error.wav", "error":"DATA:///audio/error.wav",
"levelup":"DATA:audio/levelup.wav", "levelup":"DATA:///audio/levelup.wav",
"ok":"DATA:audio/ok.wav" "ok":"DATA:///audio/ok.wav"
} }
} }
``` ```