From f3a1bcb47d844f52a8af62982a647f5902d868c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sandro=20Sobczy=C5=84ski?= Date: Tue, 10 Nov 2020 19:31:30 +0100 Subject: [PATCH] added adpcm support --- src/engine/include/modules/audio.hpp | 149 +++++++--- src/engine/modules/audio.cpp | 427 ++++++++++++++------------- src/samples/ari/ari.cpp | 13 +- src/samples/ari/ari.hpp | 2 + 4 files changed, 337 insertions(+), 254 deletions(-) diff --git a/src/engine/include/modules/audio.hpp b/src/engine/include/modules/audio.hpp index 5bb4be4..03d9989 100644 --- a/src/engine/include/modules/audio.hpp +++ b/src/engine/include/modules/audio.hpp @@ -11,61 +11,142 @@ #ifndef _TYRA_AUDIO_ #define _TYRA_AUDIO_ +#include "../models/audio_listener.hpp" #include #include #include #include -#include "../models/audio_listener.hpp" +#include -#define STACK_SIZE 8 * 1024 +struct AudioListenerRef +{ + AudioListener *listener; + u32 id; +}; -/** Class responsible for audio playing */ +/** + * Class responsible for audio playing. + * There are two main features: + * - WAV song (background music) + * - ADPCM samples + */ class Audio { public: + /** Constructor is called by the engine. */ Audio(); ~Audio(); - u8 isTrackDone; - ee_sema_t sema; - int fillbufferSema; + // Song - void init(u32 t_listenersAmount); - void play(); - void stop(); - void loadSong(char *t_filename); - void test(); - void unloadSong(); - void setVolume(u8 t_volume); - void addListener(AudioListener *t_listener); + /** + * Load 24bit 22050Hz Stereo WAV. + * Can be used multiple times for song switching. + * @param t_path Example: "song.wav" or "folder/song.wav" + */ + void loadSong(char *t_path); + + void playSong(); + + void stopSong(); + + /** + * Set song volume. + * @param t_vol Value 0-100 + */ + void setSongVolume(const u8 &t_vol); + + /** + * If you want to synchronize some actions with + * background song, add audio listener. + * @returns Listener id. + */ + u32 addSongListener(AudioListener *t_listener); + + /** Remove song listener. */ + void removeSongListener(const u32 &t_id); + + const u8 &isSongPlaying() const { return songPlaying; } + + const u8 &isSongLoaded() const { return songLoaded; } + + const u8 &isSongInLoop() const { return songInLoop; } + + u32 getSongListenersCount() const { return songListeners.size(); } + + const u8 &getVolume() const { return volume; } + + /** + * Set song loop/noloop. + * @param t_set false - noloop, true - loop + */ + void setSongLoop(const u8 &t_set) { songInLoop = t_set; } + + // ADPCM + + /** + * Load ADPCM sample. + * @param t_path Example: "hit.adpcm" or "folder/jump.adpcm" + */ + audsrv_adpcm_t *loadADPCM(char *t_path); + + /** + * Play ADPCM sample. + * ADPCM sample can't be stopped. + * @param t_adpcm ADPCM data, created by loadADPCM(); + */ + void playADPCM(audsrv_adpcm_t *t_adpcm); + + /** + * Play ADPCM sample. + * ADPCM sample can't be stopped. + * @param t_adpcm ADPCM data, created by loadADPCM(); + * @param t_ch Channel (0-23). Type -1 for use any free channel. + */ + void playADPCM(audsrv_adpcm_t *t_adpcm, const s8 &t_ch); + + /** + * Set ADPCM volume. + * @param t_vol Value 0-100 + * @param t_ch Channel (0-23) + */ + void setADPCMVolume(u8 t_vol, const s8 &t_ch) { audsrv_adpcm_set_volume(t_ch, t_vol); } + + // Other + + /** + * Start audio thread. + * Do not call this method unless you know what you do. + * Should be called by engine. + */ void startThread(); - const u8 isPlaying() const { return shouldPlay; } private: - void work(); - static void audioThread(); - u8 isInitialized, shouldPlay, songLoaded, isVolumeSet, latestVolume, isADPCMLoaded; - audsrv_adpcm_t adpcmSettings; - ee_thread_t audioThreadAttr; - u8 audioThreadStack[STACK_SIZE] ALIGNED(16); - int audioThreadId; + u8 songLoaded, volume, realVolume, songPlaying, songInLoop, songFinished; + std::vector songListeners; + FILE *wav; + audsrv_fmt_t format; + char wavChunk[2048]; + s32 chunkReadStatus; + + u8 threadStack[8 * 1024] __attribute__((aligned(16))); + u32 getThreadStackSize() { return 8 * 1024; } + u32 getSongBufferSize() { return 4 * 1024; } + ee_thread_t thread; + int threadId; + ee_sema_t sema; + s32 fillbufferSema; + + void setSongFormat(); + void unloadSong(); + void rewindSongToStart(); - AudioListener **listeners; - u32 listenersAmount; - u32 addedListeners; void initSema(); void loadModules(); void initAUDSRV(); - - u8 *sample1; - u32 adpcmFileSize; - - int ret; - char chunk[2048]; - FILE *wav; - audsrv_fmt_t format; - static int fillbuffer(void *arg); + void threadLoop(); + static void audioThread(); }; #endif diff --git a/src/engine/modules/audio.cpp b/src/engine/modules/audio.cpp index 1ba2f51..19756dc 100644 --- a/src/engine/modules/audio.cpp +++ b/src/engine/modules/audio.cpp @@ -9,31 +9,34 @@ */ #include "../include/modules/audio.hpp" - -#include #include "../include/utils/string.hpp" #include "../include/utils/debug.hpp" - -#define SONG_NAME "MOV-CIRC.WAV" -const int AUDSRV_BUFFER_SIZE = 1024 * 4; +#include +#include // ---- // Constructors/Destructors // ---- + Audio *audioRef; Audio::Audio() { - addedListeners = 0; - isInitialized = 0; - songLoaded = 0; - isVolumeSet = 0; - shouldPlay = 0; - isADPCMLoaded = false; + // We must set it to 0 on songStop(); + realVolume = 100; + volume = 100; + chunkReadStatus = 0; + + songLoaded = false; + songPlaying = false; + songInLoop = false; + songFinished = false; + audioRef = this; initSema(); loadModules(); initAUDSRV(); + setSongFormat(); } Audio::~Audio() {} @@ -42,247 +45,249 @@ Audio::~Audio() {} // Methods // ---- -/** Initialize audio module - * - * - Initialize threading semaphore - * - Load modules - * - Initialize AUDSRV - * - Load background song +void Audio::loadSong(char *t_path) +{ + if (songLoaded) + unloadSong(); + char *fullFilename = String::createConcatenated("host:", t_path); + wav = fopen(fullFilename, "rb"); + delete[] fullFilename; + if (wav == NULL) + PRINT_ERR("Failed to open wav file!"); + else + { + rewindSongToStart(); + songLoaded = true; + PRINT_LOG("Song loaded!"); + } +} + +void Audio::playSong() +{ + if (!songLoaded) + PRINT_ERR("Cant play song because was not loaded!"); + volume = realVolume; + songPlaying = true; +} + +void Audio::stopSong() +{ + volume = 0; + audsrv_set_volume(volume); + songPlaying = false; +} + +void Audio::setSongVolume(const u8 &t_vol) +{ + realVolume = t_vol; + if (songPlaying) + volume = t_vol; + audsrv_set_volume(volume); +} + +u32 Audio::addSongListener(AudioListener *t_listener) +{ + AudioListenerRef ref; + ref.id = rand() % 1000000; + ref.listener = t_listener; + songListeners.push_back(ref); + return ref.id; +} + +void Audio::removeSongListener(const u32 &t_id) +{ + s32 index = -1; + for (u32 i = 0; i < songListeners.size(); i++) + if (songListeners[i].id == t_id) + { + index = i; + break; + } + if (index == -1) + PRINT_ERR("Cant remove listener because given id was not found!"); + songListeners.erase(songListeners.begin() + index); +} + +// ADPCM + +audsrv_adpcm_t *Audio::loadADPCM(char *t_path) +{ + char *fullFilename = String::createConcatenated("host:", t_path); + FILE *file = fopen(fullFilename, "rb"); + delete[] fullFilename; + fseek(file, 0L, SEEK_END); + u32 adpcmFileSize = ftell(file); + u8 data[adpcmFileSize]; + rewind(file); + fread(data, sizeof(u8), adpcmFileSize, file); + audsrv_adpcm_t *result = new audsrv_adpcm_t(); + result->size = 0; + result->buffer = 0; + result->loop = 0; + result->pitch = 0; + result->channels = 0; + if (audsrv_load_adpcm(result, data, adpcmFileSize)) + { + printf("AUDSRV returned error string: %s", audsrv_get_error_string()); + PRINT_ERR("audsrv_load_adpcm() failed!"); + } + fclose(file); + return result; +} + +void Audio::playADPCM(audsrv_adpcm_t *t_adpcm) +{ + if (audsrv_play_adpcm(t_adpcm)) + { + printf("AUDSRV returned error string: %s", audsrv_get_error_string()); + PRINT_ERR("audsrv_play_adpcm() failed!"); + } +} + +void Audio::playADPCM(audsrv_adpcm_t *t_adpcm, const s8 &t_ch) +{ + if (audsrv_ch_play_adpcm(t_ch, t_adpcm)) + { + printf("AUDSRV returned error string: %s", audsrv_get_error_string()); + PRINT_ERR("audsrv_play_adpcm() failed!"); + } +} + +// Other + +void Audio::startThread() +{ + PRINT_LOG("Creating audio thread"); + extern void *_gp; + thread.func = (void *)Audio::audioThread; + thread.stack = threadStack; + thread.stack_size = getThreadStackSize(); + thread.gp_reg = (void *)&_gp; + thread.initial_priority = 0x17; + if ((threadId = CreateThread(&thread)) < 0) + PRINT_ERR("Create audio thread failed!"); + PRINT_LOG("Audio thread created"); + StartThread(threadId, NULL); + PRINT_LOG("Audio thread started"); +} + +/** Main thread loop */ +void Audio::threadLoop() +{ + if (!songPlaying || !songLoaded) + return; + if (songFinished) + { + if (songInLoop) + { + printf("Audio: Song finished. Running again.\n"); + for (u32 i = 0; i < getSongListenersCount(); i++) + songListeners[i].listener->onAudioFinish(); + rewindSongToStart(); + } + else + return; + } + + if (chunkReadStatus > 0) + { + WaitSema(fillbufferSema); // wait until previous chunk wasn't finished + audsrv_play_audio(wavChunk, chunkReadStatus); + for (u32 i = 0; i < getSongListenersCount(); i++) + songListeners[i].listener->onAudioTick(); + } + + chunkReadStatus = fread(wavChunk, 1, sizeof(wavChunk), wav); + + if (chunkReadStatus < (s32)sizeof(wavChunk)) + songFinished = true; +} + +/** + * Close file. + * Delete song path from memory. */ -void Audio::init(u32 t_listenersAmount) +void Audio::unloadSong() { - PRINT_LOG("Initialize audio module started"); - listenersAmount = t_listenersAmount; - listeners = new AudioListener *[t_listenersAmount]; - isInitialized = true; - latestVolume = 100; - PRINT_LOG("Audio module initialized!"); + songLoaded = false; + fclose(wav); } -void Audio::addListener(AudioListener *t_listener) +/** Set WAV format to 16bit, 22050Hz, stereo. */ +void Audio::setSongFormat() { - listeners[addedListeners++] = t_listener; + format.bits = 16; + format.freq = 22050; + format.channels = 2; + audsrv_set_format(&format); } -/** Initialize threading semaphore */ +/** Fseek on wav. */ +void Audio::rewindSongToStart() +{ + if (wav != NULL) + fseek(wav, 0x30, SEEK_SET); + songFinished = false; +} + +/** Initialize semaphore which will wait until chunk of the song is not finished. */ void Audio::initSema() { - PRINT_LOG("Creating semaphore started"); + PRINT_LOG("Creating audio semaphore"); sema.init_count = 0; sema.max_count = 1; sema.option = 0; fillbufferSema = CreateSema(&sema); - PRINT_LOG("Semaphore created"); + PRINT_LOG("Audio semaphore created"); } -/** Load LIBSD and AUDSRV */ +/** Load LIBSD and AUDSRV modules */ void Audio::loadModules() { PRINT_LOG("Modules loading started (LIBSD, AUDSRV)"); - ret = SifLoadModule("rom0:LIBSD", 0, NULL); + int ret = SifLoadModule("rom0:LIBSD", 0, NULL); if (ret == -203) PRINT_ERR("LIBSD loading failed!"); ret = SifLoadModule("host:AUDSRV.IRX", 0, NULL); if (ret == -203) PRINT_ERR("AUDSRV.IRX loading failed!"); - PRINT_LOG("Modules loaded"); + PRINT_LOG("Audio modules loaded"); } -/** Initialize AUDSRV and install fillbuffer callback */ +/** + * Initialize AUDSRV main and ADPCM module. + * Install AUDSRV callback. + */ void Audio::initAUDSRV() { - PRINT_LOG("Initialize AUDSRV started"); - ret = audsrv_init(); + PRINT_LOG("Initializing AUDSRV"); + int ret = audsrv_init(); + if (ret != 0) + { + printf("AUDSRV returned error string: %s", audsrv_get_error_string()); + PRINT_ERR("Failed to initialize AUDSRV!"); + } ret = audsrv_adpcm_init(); if (ret != 0) { - PRINT_ERR("Failed to initialize AUDSRV!"); printf("AUDSRV returned error string: %s", audsrv_get_error_string()); + PRINT_ERR("Failed to initialize AUDSRV ADPCM!"); } - else + ret = audsrv_on_fillbuf(getSongBufferSize(), (audsrv_callback_t)iSignalSema, (void *)fillbufferSema); + if (ret != 0) { - ret = audsrv_on_fillbuf(AUDSRV_BUFFER_SIZE, Audio::fillbuffer, (void *)fillbufferSema); - PRINT_LOG("AUDSRV initialized!"); - } -} - -/** Set audio format, volume and load song */ -void Audio::loadSong(char *t_filename) -{ - if (!isInitialized) - { - PRINT_ERR("Please initialize audio class first!"); - return; - } - PRINT_LOG("Song loading started"); - format.bits = 16; - format.freq = 22050; - format.channels = 2; - char *fullFilename = String::createConcatenated("host:", t_filename); - int err = audsrv_set_format(&format); - if (err == 0) - { - PRINT_LOG("Audio format set"); - if (!isVolumeSet) - setVolume(MAX_VOLUME); - PRINT_LOG("Opening song file: " SONG_NAME); - wav = fopen(fullFilename, "rb"); - delete[] fullFilename; - if (wav == NULL) - { - PRINT_ERR("Failed to open wav file!"); - audsrv_quit(); - } - else - { - fseek(wav, 0x30, SEEK_SET); - songLoaded = 1; - PRINT_LOG("Song loaded!"); - } - } - else - { - PRINT_ERR("Failed to set audio format!"); printf("AUDSRV returned error string: %s", audsrv_get_error_string()); + PRINT_ERR("Failed to initialize AUDSRV fillbuffer!"); } + PRINT_LOG("AUDSRV initialized!"); } -void Audio::setVolume(u8 t_volume) -{ - audsrv_set_volume(t_volume); - latestVolume = t_volume; - isVolumeSet = true; -} - -/** Create and start audio thread. */ -void Audio::startThread() -{ - PRINT_LOG("Creating audio thread"); - extern void *_gp; - audioThreadAttr.func = (void *)Audio::audioThread; - audioThreadAttr.stack = audioThreadStack; - audioThreadAttr.stack_size = STACK_SIZE; - audioThreadAttr.gp_reg = (void *)&_gp; - audioThreadAttr.initial_priority = 0x17; - if ((audioThreadId = CreateThread(&audioThreadAttr)) < 0) - PRINT_ERR("Create audio thread failed!"); - PRINT_LOG("Audio thread created"); - StartThread(audioThreadId, NULL); - PRINT_LOG("Audio thread started"); -} - -/** Play song and run it again on finish */ -void Audio::work() -{ - if (isADPCMLoaded) - { - if (audsrv_load_adpcm(&adpcmSettings, sample1, adpcmFileSize)) - PRINT_ERR("audsrv_load_adpcm() failed!"); - if (audsrv_play_adpcm(&adpcmSettings)) - PRINT_ERR("audsrv_play_adpcm() failed!"); - isADPCMLoaded = false; - } - if (!shouldPlay) - return; - if (!isTrackDone) - { - ret = fread(chunk, 1, sizeof(chunk), wav); - if (ret > 0) - { - WaitSema(fillbufferSema); - audsrv_play_audio(chunk, ret); - } - if (ret < (int)sizeof(chunk)) - { - isTrackDone = true; - return; - } - for (u32 i = 0; i < addedListeners; i++) - listeners[i]->onAudioTick(); - } - else - { - PRINT_LOG("Song " SONG_NAME " finished. Running again..."); - for (u32 i = 0; i < addedListeners; i++) - listeners[i]->onAudioFinish(); - fseek(wav, 0x30, SEEK_SET); - isTrackDone = false; - } -} - -/** Unload audio module by closing file stream and stopping AUDSRV */ -void Audio::unloadSong() -{ - PRINT_LOG("Song unloaded"); - fclose(wav); -} - -void Audio::test() -{ - // audsrv_adpcm_set_volume(100); - - FILE *file = fopen("host:ziobro.adpcm", "rb"); - fseek(file, 0L, SEEK_END); - adpcmFileSize = ftell(file); - sample1 = new u8[adpcmFileSize]; - rewind(file); - fread(sample1, sizeof(u8), adpcmFileSize, file); - fclose(file); - adpcmSettings.size = 0; - adpcmSettings.buffer = 0; - adpcmSettings.loop = 0; - adpcmSettings.pitch = 0; - adpcmSettings.channels = 0; - isADPCMLoaded = true; -} - -void Audio::play() -{ - if (!isInitialized) - { - PRINT_ERR("Please initialize audio class first!"); - return; - } - if (!shouldPlay) - { - shouldPlay = 1; - audsrv_set_volume(latestVolume); - } -} - -void Audio::stop() -{ - if (!isInitialized) - { - PRINT_ERR("Please initialize audio class first!"); - return; - } - if (shouldPlay) - { - shouldPlay = 0; - audsrv_set_volume(0); - } -} - -// --- - /** * Do not call this function. - * This is a audio thread, runned by AudioThread::start() + * This is a audio thread, runned by engine */ void Audio::audioThread() { while (true) - audioRef->work(); -} - -/** - * Do not call this function. - * This is a static callback function - * for AUDSRV fillbuffer (semaphore) - */ -int Audio::fillbuffer(void *arg) -{ - iSignalSema((int)arg); - return 0; + audioRef->threadLoop(); } diff --git a/src/samples/ari/ari.cpp b/src/samples/ari/ari.cpp index a0ade54..3fbb597 100644 --- a/src/samples/ari/ari.cpp +++ b/src/samples/ari/ari.cpp @@ -33,10 +33,9 @@ Ari::~Ari() {} void Ari::onInit() { engine->renderer->setCameraDefinitions(&camera.worldView, &camera.position, camera.planes); - engine->audio.init(0); - engine->audio.setVolume(60); + engine->audio.setSongVolume(60); engine->audio.loadSong("MOV-CIRC.WAV"); - + adpcm = engine->audio.loadADPCM("ziobro.adpcm"); texRepo = engine->renderer->getTextureRepository(); island.loadDff("sunnyisl/", "sunnyisl", 0.1F, false); @@ -78,7 +77,7 @@ void Ari::onInit() // texRepo->addByMesh("ari/", player.mesh); - // engine->audio.play(); + engine->audio.playSong(); } void Ari::initBulb() @@ -91,12 +90,8 @@ void Ari::onUpdate() { if (engine->pad.isCrossClicked) { - engine->audio.test(); - engine->audio.stop(); - engine->audio.unloadSong(); engine->audio.loadSong("nob-else.wav"); - engine->audio.play(); - + engine->audio.playADPCM(adpcm); printf("FPS:%f\n", engine->fps); } camera.update(engine->pad, player.mesh); diff --git a/src/samples/ari/ari.hpp b/src/samples/ari/ari.hpp index 9f86c1f..bb594b3 100644 --- a/src/samples/ari/ari.hpp +++ b/src/samples/ari/ari.hpp @@ -12,6 +12,7 @@ #define _ARI_ #include +#include #include #include #include "objects/player.hpp" @@ -38,6 +39,7 @@ private: Player player; Mesh island, islandAddons, skybox; Mesh *waterFloors; + audsrv_adpcm_t *adpcm; Point *spirals; Camera camera; TextureRepository *texRepo;