audio rework
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
# _____ ____ ___
|
||||
# | \/ ____| |___|
|
||||
# | | | \ | |
|
||||
#-----------------------------------------------------------------------
|
||||
# Copyright 2022-2022, Tyra - https://github.com/h4570/tyrav2
|
||||
# Licensed under Apache License 2.0
|
||||
# Sandro Sobczyński <sandro.sobczynski@gmail.com>
|
||||
# Wellinator Carvalho <wellcoj@gmail.com>
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace Tyra {
|
||||
|
||||
enum AdpcmResult {
|
||||
ADPCM_OK,
|
||||
|
||||
/** When provided channel by user is currently in use */
|
||||
ADPCM_CHANNEL_USED,
|
||||
|
||||
/** When user did not provided channel and all are in use */
|
||||
ADPCM_NO_FREE_CHANNELS,
|
||||
|
||||
ADPCM_ERROR
|
||||
};
|
||||
|
||||
} // namespace Tyra
|
||||
+5
-126
@@ -11,151 +11,30 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "./audio_listener.hpp"
|
||||
#include <audsrv.h>
|
||||
#include <string>
|
||||
#include "./audio_adpcm.hpp"
|
||||
#include "./audio_song.hpp"
|
||||
|
||||
namespace Tyra {
|
||||
|
||||
struct AudioListenerRef {
|
||||
AudioListener* listener;
|
||||
u32 id;
|
||||
};
|
||||
|
||||
/**
|
||||
* Class responsible for audio playing.
|
||||
* There are two main features:
|
||||
* - WAV song (background music)
|
||||
* - ADPCM samples
|
||||
*/
|
||||
/** Class responsible for audio. */
|
||||
class Audio {
|
||||
public:
|
||||
/** Constructor is called by the engine. */
|
||||
Audio();
|
||||
~Audio();
|
||||
|
||||
void init();
|
||||
|
||||
/**
|
||||
* Load 24bit 22kHz Stereo WAV.
|
||||
* Can be used multiple times for song switching.
|
||||
* @param t_path Example: "host:song.wav" or "host:folder/song.wav"
|
||||
*/
|
||||
void loadSong(const char* t_path);
|
||||
|
||||
/**
|
||||
* Load 24bit 22kHz Stereo WAV.
|
||||
* Can be used multiple times for song switching.
|
||||
* @param t_path Example: "host:song.wav" or "host:folder/song.wav"
|
||||
*/
|
||||
inline void loadSong(const std::string& t_path) { loadSong(t_path.c_str()); }
|
||||
|
||||
void playSong();
|
||||
|
||||
void stopSong();
|
||||
|
||||
const u8& isSongPlaying() const { return songPlaying; }
|
||||
|
||||
const u8& isSongLoaded() const { return songLoaded; }
|
||||
|
||||
/**
|
||||
* Set song loop/noloop.
|
||||
* @returns Looping state is 0 - noloop / 1 - loop
|
||||
*/
|
||||
const u8& isSongInLoop() const { return songInLoop; }
|
||||
|
||||
/**
|
||||
* Set song loop/noloop.
|
||||
* @param t_set false - noloop, true - loop
|
||||
*/
|
||||
void setSongLoop(const u8& t_set) { songInLoop = t_set; }
|
||||
|
||||
const u8& getVolume() const { return tyraVolume; }
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* Load ADPCM sample.
|
||||
* ADPCM sample is an output from "Convert WAV to ADPCM" task (adpenc tool).
|
||||
* Adpenc expects 22kHz 16bit file.
|
||||
* @param t_path Example: "host:hit.adpcm" or "host:folder/jump.adpcm"
|
||||
*/
|
||||
audsrv_adpcm_t* loadADPCM(const char* t_path);
|
||||
|
||||
/**
|
||||
* Load ADPCM sample.
|
||||
* ADPCM sample is an output from "Convert WAV to ADPCM" task (adpenc tool).
|
||||
* Adpenc expects 22kHz 16bit file.
|
||||
* @param t_path Example: "host:hit.adpcm" or "host:folder/jump.adpcm"
|
||||
*/
|
||||
inline audsrv_adpcm_t* loadADPCM(const std::string& t_path) {
|
||||
return loadADPCM(t_path.c_str());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(const u8& t_vol, const s8& t_ch) {
|
||||
audsrv_adpcm_set_volume(t_ch, t_vol);
|
||||
}
|
||||
AudioSong song;
|
||||
AudioAdpcm adpcm;
|
||||
|
||||
void work();
|
||||
|
||||
private:
|
||||
u8 songLoaded, tyraVolume, audsrvVolume, songPlaying, songInLoop,
|
||||
songFinished;
|
||||
FILE* wav;
|
||||
audsrv_fmt_t format;
|
||||
std::vector<AudioListenerRef*> songListeners;
|
||||
|
||||
ee_thread_t thread;
|
||||
int threadId;
|
||||
static const u16 threadStackSize;
|
||||
u8* threadStack;
|
||||
|
||||
ee_sema_t sema;
|
||||
s32 fillbufferSema;
|
||||
static const u16 chunkSize;
|
||||
char* chunk;
|
||||
s32 chunkReadStatus;
|
||||
|
||||
void setSongFormat();
|
||||
void unloadSong();
|
||||
void rewindSongToStart();
|
||||
u32 getSongListenersCount() const { return songListeners.size(); }
|
||||
|
||||
void initSema();
|
||||
void initAUDSRV();
|
||||
void initThread();
|
||||
};
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
# _____ ____ ___
|
||||
# | \/ ____| |___|
|
||||
# | | | \ | |
|
||||
#-----------------------------------------------------------------------
|
||||
# Copyright 2022-2022, Tyra - https://github.com/h4570/tyrav2
|
||||
# Licensed under Apache License 2.0
|
||||
# Sandro Sobczyński <sandro.sobczynski@gmail.com>
|
||||
# Wellinator Carvalho <wellcoj@gmail.com>
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "./audio_listener_ref.hpp"
|
||||
#include "./adpcm_result.hpp"
|
||||
#include <audsrv.h>
|
||||
#include <string>
|
||||
|
||||
namespace Tyra {
|
||||
|
||||
/**
|
||||
* Class responsible for playing ADPCM samples.
|
||||
* ADPCM sample can't be stopped.
|
||||
*/
|
||||
class AudioAdpcm {
|
||||
public:
|
||||
AudioAdpcm();
|
||||
~AudioAdpcm();
|
||||
|
||||
void init();
|
||||
|
||||
/**
|
||||
* Load ADPCM sample.
|
||||
* ADPCM sample is an output from "Convert WAV to ADPCM" task (adpenc tool).
|
||||
* Adpenc expects 22kHz 16bit file.
|
||||
* @param t_path Example: "host:hit.adpcm" or "host:folder/jump.adpcm"
|
||||
*/
|
||||
audsrv_adpcm_t* load(const char* t_path);
|
||||
audsrv_adpcm_t* load(const std::string& t_path);
|
||||
|
||||
/**
|
||||
* Frees up all memory taken by samples, and stops all voices from
|
||||
* being played.
|
||||
*/
|
||||
void reset();
|
||||
|
||||
/**
|
||||
* Try play ADPCM sample if channel(s) is not occupied.
|
||||
* @param t_adpcm ADPCM data, created by load();
|
||||
* @param t_ch Channel (0-23). Type -1 for use any free channel.
|
||||
*/
|
||||
AdpcmResult tryPlay(audsrv_adpcm_t* t_adpcm);
|
||||
AdpcmResult tryPlay(audsrv_adpcm_t* t_adpcm, const s8& t_ch);
|
||||
|
||||
/**
|
||||
* Play ADPCM sample, if channel is occupied, wait for it.
|
||||
* If not used properly, can hugely reduce performance.
|
||||
* @param t_adpcm ADPCM data, created by load();
|
||||
* @param t_ch Channel (0-23). Type -1 for use any free channel.
|
||||
*/
|
||||
void playWait(audsrv_adpcm_t* t_adpcm);
|
||||
void playWait(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 setVolume(const u8& t_vol, const s8& t_ch) {
|
||||
audsrv_adpcm_set_volume(t_ch, t_vol);
|
||||
}
|
||||
|
||||
private:
|
||||
void initAUDSRV();
|
||||
};
|
||||
|
||||
} // namespace Tyra
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
# _____ ____ ___
|
||||
# | \/ ____| |___|
|
||||
# | | | \ | |
|
||||
#-----------------------------------------------------------------------
|
||||
# Copyright 2022-2022, Tyra - https://github.com/h4570/tyrav2
|
||||
# Licensed under Apache License 2.0
|
||||
# Sandro Sobczyński <sandro.sobczynski@gmail.com>
|
||||
# Wellinator Carvalho <wellcoj@gmail.com>
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "./audio_listener.hpp"
|
||||
#include <tamtypes.h>
|
||||
|
||||
namespace Tyra {
|
||||
|
||||
struct AudioListenerRef {
|
||||
AudioListener* listener;
|
||||
u32 id;
|
||||
};
|
||||
|
||||
} // namespace Tyra
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
# _____ ____ ___
|
||||
# | \/ ____| |___|
|
||||
# | | | \ | |
|
||||
#-----------------------------------------------------------------------
|
||||
# Copyright 2022-2022, Tyra - https://github.com/h4570/tyrav2
|
||||
# Licensed under Apache License 2.0
|
||||
# Sandro Sobczyński <sandro.sobczynski@gmail.com>
|
||||
# Wellinator Carvalho <wellcoj@gmail.com>
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "./audio_listener_ref.hpp"
|
||||
#include <audsrv.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <kernel.h>
|
||||
|
||||
namespace Tyra {
|
||||
|
||||
/** Class responsible for background audio playing. */
|
||||
class AudioSong {
|
||||
public:
|
||||
AudioSong();
|
||||
~AudioSong();
|
||||
|
||||
void init();
|
||||
|
||||
bool inLoop;
|
||||
|
||||
/**
|
||||
* Load 24bit 22kHz Stereo WAV.
|
||||
* Can be used multiple times for song switching.
|
||||
* @param t_path Example: "host:song.wav" or "host:folder/song.wav"
|
||||
*/
|
||||
void load(const char* t_path);
|
||||
void load(const std::string& t_path);
|
||||
|
||||
void play();
|
||||
|
||||
void stop();
|
||||
|
||||
const bool& isPlaying() const;
|
||||
|
||||
const bool& isLoaded() const;
|
||||
|
||||
const u8& getVolume() const;
|
||||
|
||||
/**
|
||||
* Set song volume.
|
||||
* @param t_vol Value 0-100
|
||||
*/
|
||||
void setVolume(const u8& t_vol);
|
||||
|
||||
/**
|
||||
* If you want to synchronize some actions with
|
||||
* background song, add audio listener.
|
||||
* @returns Listener id.
|
||||
*/
|
||||
u32 addListener(AudioListener* t_listener);
|
||||
|
||||
/** Remove song listener. */
|
||||
void removeListener(const u32& t_id);
|
||||
|
||||
std::size_t getListenersCount() const;
|
||||
|
||||
void work();
|
||||
|
||||
private:
|
||||
bool songLoaded, songPlaying, songFinished;
|
||||
u8 tyraVolume, audsrvVolume;
|
||||
FILE* wav;
|
||||
audsrv_fmt_t format;
|
||||
std::vector<AudioListenerRef*> songListeners;
|
||||
|
||||
ee_sema_t sema;
|
||||
s32 fillbufferSema;
|
||||
static const u16 chunkSize;
|
||||
char* chunk;
|
||||
s32 chunkReadStatus;
|
||||
|
||||
void initSema();
|
||||
void initAUDSRV();
|
||||
void setSongFormat();
|
||||
void unloadSong();
|
||||
void rewindSongToStart();
|
||||
};
|
||||
|
||||
} // namespace Tyra
|
||||
+13
-194
@@ -9,20 +9,11 @@
|
||||
# Sandro Sobczyński <sandro.sobczynski@gmail.com>
|
||||
*/
|
||||
|
||||
#include "debug/debug.hpp"
|
||||
#include <tamtypes.h>
|
||||
#include <vector>
|
||||
#include <stdio.h>
|
||||
#include <malloc.h>
|
||||
#include <kernel.h>
|
||||
#include <cstdlib>
|
||||
#include <loadfile.h>
|
||||
#include <sifrpc.h>
|
||||
#include <sbv_patches.h>
|
||||
#include <audsrv.h>
|
||||
#include <string.h>
|
||||
#include "thread/threading.hpp"
|
||||
#include "audio/audio.hpp"
|
||||
#include "debug/debug.hpp"
|
||||
#include "thread/threading.hpp"
|
||||
#include <kernel.h>
|
||||
#include <malloc.h>
|
||||
|
||||
extern void* _gp;
|
||||
|
||||
@@ -32,94 +23,32 @@ void audioThread(Tyra::Audio* audio) {
|
||||
|
||||
namespace Tyra {
|
||||
|
||||
const u16 Audio::chunkSize = 4 * 1024;
|
||||
const u16 Audio::threadStackSize = 2 * 1024;
|
||||
|
||||
Audio::Audio() {
|
||||
chunkReadStatus = 0;
|
||||
tyraVolume = 0;
|
||||
audsrvVolume = 0;
|
||||
|
||||
songLoaded = false;
|
||||
songPlaying = false;
|
||||
songInLoop = false;
|
||||
songFinished = false;
|
||||
|
||||
chunk = nullptr;
|
||||
threadStack = nullptr;
|
||||
threadStack = static_cast<u8*>(memalign(sizeof(u8), threadStackSize));
|
||||
}
|
||||
|
||||
Audio::~Audio() {
|
||||
if (chunk) delete[] chunk;
|
||||
if (threadStack) delete[] threadStack;
|
||||
}
|
||||
|
||||
void Audio::init() {
|
||||
chunk = static_cast<char*>(memalign(sizeof(char), chunkSize));
|
||||
threadStack = static_cast<u8*>(memalign(sizeof(u8), threadStackSize));
|
||||
|
||||
initSema();
|
||||
initAUDSRV();
|
||||
setSongFormat();
|
||||
|
||||
song.init();
|
||||
adpcm.init();
|
||||
|
||||
initThread();
|
||||
|
||||
TYRA_LOG("Audio initialized!");
|
||||
}
|
||||
|
||||
void Audio::loadSong(const char* t_path) {
|
||||
if (songLoaded) unloadSong();
|
||||
wav = fopen(t_path, "rb");
|
||||
TYRA_ASSERT(wav != NULL, "Failed to open wav file!");
|
||||
rewindSongToStart();
|
||||
songLoaded = true;
|
||||
TYRA_LOG("Song loaded!");
|
||||
}
|
||||
|
||||
void Audio::playSong() {
|
||||
TYRA_ASSERT(songLoaded, "Cant play song because was not loaded!");
|
||||
if (songFinished) rewindSongToStart();
|
||||
tyraVolume = audsrvVolume;
|
||||
audsrv_set_volume(tyraVolume);
|
||||
songPlaying = true;
|
||||
}
|
||||
|
||||
void Audio::stopSong() {
|
||||
tyraVolume = 0;
|
||||
audsrv_set_volume(tyraVolume);
|
||||
songPlaying = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize AUDSRV main and ADPCM module.
|
||||
* Install AUDSRV callback.
|
||||
*/
|
||||
void Audio::initAUDSRV() {
|
||||
TYRA_LOG("Initializing AUDSRV");
|
||||
|
||||
int ret = audsrv_init();
|
||||
|
||||
TYRA_ASSERT(ret >= 0,
|
||||
"AUDSRV returned error string: ", audsrv_get_error_string());
|
||||
|
||||
ret = audsrv_adpcm_init();
|
||||
TYRA_ASSERT(ret >= 0,
|
||||
"AUDSRV returned error string: ", audsrv_get_error_string());
|
||||
|
||||
ret = audsrv_on_fillbuf(chunkSize, (audsrv_callback_t)iSignalSema,
|
||||
(void*)fillbufferSema);
|
||||
|
||||
TYRA_ASSERT(ret >= 0,
|
||||
"AUDSRV returned error string:", audsrv_get_error_string());
|
||||
|
||||
TYRA_LOG("AUDSRV initialized!");
|
||||
}
|
||||
|
||||
/** Initialize semaphore which will wait until chunk of the song is not
|
||||
* finished. */
|
||||
void Audio::initSema() {
|
||||
TYRA_LOG("Creating audio semaphore");
|
||||
sema.init_count = 0;
|
||||
sema.max_count = 1;
|
||||
sema.option = 0;
|
||||
fillbufferSema = CreateSema(&sema);
|
||||
TYRA_LOG("Audio semaphore created");
|
||||
}
|
||||
|
||||
void Audio::initThread() {
|
||||
@@ -133,120 +62,10 @@ void Audio::initThread() {
|
||||
StartThread(threadId, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close file.
|
||||
* Delete song path from memory.
|
||||
*/
|
||||
void Audio::unloadSong() {
|
||||
songLoaded = false;
|
||||
fclose(wav);
|
||||
}
|
||||
|
||||
/** Fseek on wav. */
|
||||
void Audio::rewindSongToStart() {
|
||||
if (wav != NULL) fseek(wav, 0x30, SEEK_SET);
|
||||
chunkReadStatus = 0;
|
||||
songFinished = false;
|
||||
}
|
||||
|
||||
/** Set WAV format to 16bit, 22050Hz, stereo. */
|
||||
void Audio::setSongFormat() {
|
||||
format.bits = 16;
|
||||
format.freq = 22050;
|
||||
format.channels = 2;
|
||||
audsrv_set_format(&format);
|
||||
}
|
||||
|
||||
void Audio::setSongVolume(const u8& t_vol) {
|
||||
audsrvVolume = t_vol;
|
||||
if (songPlaying) tyraVolume = t_vol;
|
||||
audsrv_set_volume(tyraVolume);
|
||||
}
|
||||
|
||||
void Audio::work() {
|
||||
Threading::switchThread();
|
||||
|
||||
if (!songPlaying || !songLoaded) return;
|
||||
if (songFinished) {
|
||||
TYRA_LOG("Audio: Song finished. ");
|
||||
if (songInLoop) {
|
||||
TYRA_LOG("Running again.");
|
||||
for (u32 i = 0; i < getSongListenersCount(); i++)
|
||||
songListeners[i]->listener->onAudioFinish();
|
||||
rewindSongToStart();
|
||||
} else {
|
||||
TYRA_LOG("Stopping song.");
|
||||
stopSong();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (chunkReadStatus > 0) {
|
||||
WaitSema(fillbufferSema); // wait until previous chunk wasn't finished
|
||||
audsrv_play_audio(chunk, chunkReadStatus);
|
||||
for (u32 i = 0; i < getSongListenersCount(); i++)
|
||||
songListeners[i]->listener->onAudioTick();
|
||||
}
|
||||
|
||||
chunkReadStatus = fread(chunk, 1, chunkSize, wav);
|
||||
|
||||
if (chunkReadStatus < (s32)chunkSize) songFinished = true;
|
||||
}
|
||||
|
||||
u32 Audio::addSongListener(AudioListener* t_listener) {
|
||||
AudioListenerRef* ref = new AudioListenerRef;
|
||||
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;
|
||||
}
|
||||
TYRA_ASSERT(index != -1,
|
||||
"Cant remove listener because given id was not found!");
|
||||
delete songListeners[index];
|
||||
songListeners.erase(songListeners.begin() + index);
|
||||
}
|
||||
|
||||
audsrv_adpcm_t* Audio::loadADPCM(const char* t_path) {
|
||||
FILE* file = fopen(t_path, "rb");
|
||||
fseek(file, 0, SEEK_END);
|
||||
u32 adpcmFileSize = ftell(file);
|
||||
u8 data[adpcmFileSize];
|
||||
rewind(file);
|
||||
fread(data, sizeof(u8), adpcmFileSize, file);
|
||||
auto* 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)) {
|
||||
TYRA_LOG("AUDSRV returned error string: ", audsrv_get_error_string());
|
||||
TYRA_TRAP("audsrv_load_adpcm() failed!");
|
||||
}
|
||||
fclose(file);
|
||||
return result;
|
||||
}
|
||||
|
||||
void Audio::playADPCM(audsrv_adpcm_t* t_adpcm) {
|
||||
if (audsrv_play_adpcm(t_adpcm)) {
|
||||
TYRA_LOG("AUDSRV returned error string: ", audsrv_get_error_string());
|
||||
TYRA_TRAP("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)) {
|
||||
TYRA_LOG("AUDSRV returned error string: ", audsrv_get_error_string());
|
||||
TYRA_TRAP("audsrv_ch_play_adpcm() failed!");
|
||||
}
|
||||
song.work();
|
||||
}
|
||||
|
||||
} // namespace Tyra
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
# _____ ____ ___
|
||||
# | \/ ____| |___|
|
||||
# | | | \ | |
|
||||
#-----------------------------------------------------------------------
|
||||
# Copyright 2022, tyra - https://github.com/h4570/tyra
|
||||
# Licensed under Apache License 2.0
|
||||
# Sandro Sobczyński <sandro.sobczynski@gmail.com>
|
||||
# Sandro Sobczyński <sandro.sobczynski@gmail.com>
|
||||
*/
|
||||
|
||||
#include "audio/audio_adpcm.hpp"
|
||||
#include "debug/debug.hpp"
|
||||
#include <tamtypes.h>
|
||||
#include <malloc.h>
|
||||
#include <kernel.h>
|
||||
#include <cstdlib>
|
||||
#include <audsrv.h>
|
||||
#include "thread/threading.hpp"
|
||||
|
||||
namespace Tyra {
|
||||
|
||||
AudioAdpcm::AudioAdpcm() {}
|
||||
|
||||
AudioAdpcm::~AudioAdpcm() {}
|
||||
|
||||
void AudioAdpcm::init() { initAUDSRV(); }
|
||||
|
||||
void AudioAdpcm::initAUDSRV() {
|
||||
int ret = audsrv_adpcm_init();
|
||||
|
||||
TYRA_ASSERT(ret >= 0,
|
||||
"AUDSRV returned error string:", audsrv_get_error_string());
|
||||
}
|
||||
|
||||
void AudioAdpcm::reset() { audsrv_adpcm_init(); }
|
||||
|
||||
audsrv_adpcm_t* AudioAdpcm::load(const char* t_path) {
|
||||
FILE* file = fopen(t_path, "rb");
|
||||
fseek(file, 0, SEEK_END);
|
||||
u32 adpcmFileSize = ftell(file);
|
||||
u8 data[adpcmFileSize];
|
||||
rewind(file);
|
||||
fread(data, sizeof(u8), adpcmFileSize, file);
|
||||
auto* 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)) {
|
||||
TYRA_ERROR("AUDSRV returned error string: ", audsrv_get_error_string());
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
return result;
|
||||
}
|
||||
|
||||
audsrv_adpcm_t* AudioAdpcm::load(const std::string& t_path) {
|
||||
return load(t_path.c_str());
|
||||
}
|
||||
|
||||
AdpcmResult AudioAdpcm::tryPlay(audsrv_adpcm_t* t_adpcm) {
|
||||
return tryPlay(t_adpcm, -1);
|
||||
}
|
||||
|
||||
AdpcmResult AudioAdpcm::tryPlay(audsrv_adpcm_t* t_adpcm, const s8& t_ch) {
|
||||
int res = audsrv_ch_play_adpcm(t_ch, t_adpcm);
|
||||
if (res >= 0) {
|
||||
return AdpcmResult::ADPCM_OK;
|
||||
} else if (res == -AUDSRV_ERR_NO_MORE_CHANNELS) {
|
||||
if (t_ch < 0) {
|
||||
return AdpcmResult::ADPCM_NO_FREE_CHANNELS;
|
||||
} else {
|
||||
return AdpcmResult::ADPCM_CHANNEL_USED;
|
||||
}
|
||||
} else {
|
||||
return AdpcmResult::ADPCM_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioAdpcm::playWait(audsrv_adpcm_t* t_adpcm) { playWait(t_adpcm, -1); }
|
||||
|
||||
void AudioAdpcm::playWait(audsrv_adpcm_t* t_adpcm, const s8& t_ch) {
|
||||
int res = tryPlay(t_adpcm, t_ch);
|
||||
|
||||
while (res == AdpcmResult::ADPCM_NO_FREE_CHANNELS ||
|
||||
res == AdpcmResult::ADPCM_CHANNEL_USED) {
|
||||
Threading::switchThread();
|
||||
res = tryPlay(t_adpcm, t_ch);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Tyra
|
||||
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
# _____ ____ ___
|
||||
# | \/ ____| |___|
|
||||
# | | | \ | |
|
||||
#-----------------------------------------------------------------------
|
||||
# Copyright 2022, tyra - https://github.com/h4570/tyra
|
||||
# Licensed under Apache License 2.0
|
||||
# Sandro Sobczyński <sandro.sobczynski@gmail.com>
|
||||
# Sandro Sobczyński <sandro.sobczynski@gmail.com>
|
||||
*/
|
||||
|
||||
#include "audio/audio_song.hpp"
|
||||
#include "debug/debug.hpp"
|
||||
#include <tamtypes.h>
|
||||
#include <malloc.h>
|
||||
#include <cstdlib>
|
||||
#include <audsrv.h>
|
||||
|
||||
namespace Tyra {
|
||||
|
||||
const u16 AudioSong::chunkSize = 4 * 1024;
|
||||
|
||||
AudioSong::AudioSong() {
|
||||
chunkReadStatus = 0;
|
||||
tyraVolume = 0;
|
||||
audsrvVolume = 0;
|
||||
|
||||
songLoaded = false;
|
||||
songPlaying = false;
|
||||
inLoop = false;
|
||||
songFinished = false;
|
||||
|
||||
chunk = nullptr;
|
||||
}
|
||||
|
||||
AudioSong::~AudioSong() {
|
||||
if (chunk) delete[] chunk;
|
||||
}
|
||||
|
||||
void AudioSong::init() {
|
||||
chunk = static_cast<char*>(memalign(sizeof(char), chunkSize));
|
||||
|
||||
initSema();
|
||||
initAUDSRV();
|
||||
setSongFormat();
|
||||
}
|
||||
|
||||
void AudioSong::load(const char* t_path) {
|
||||
if (songLoaded) unloadSong();
|
||||
wav = fopen(t_path, "rb");
|
||||
TYRA_ASSERT(wav != NULL, "Failed to open wav file!");
|
||||
rewindSongToStart();
|
||||
songLoaded = true;
|
||||
TYRA_LOG("Song loaded!");
|
||||
}
|
||||
|
||||
void AudioSong::load(const std::string& t_path) { load(t_path.c_str()); }
|
||||
|
||||
void AudioSong::play() {
|
||||
TYRA_ASSERT(songLoaded, "Cant play song because was not loaded!");
|
||||
if (songFinished) rewindSongToStart();
|
||||
tyraVolume = audsrvVolume;
|
||||
audsrv_set_volume(tyraVolume);
|
||||
songPlaying = true;
|
||||
}
|
||||
|
||||
void AudioSong::stop() {
|
||||
tyraVolume = 0;
|
||||
audsrv_set_volume(tyraVolume);
|
||||
songPlaying = false;
|
||||
}
|
||||
|
||||
const bool& AudioSong::isPlaying() const { return songPlaying; }
|
||||
|
||||
const bool& AudioSong::isLoaded() const { return songLoaded; }
|
||||
|
||||
const u8& AudioSong::getVolume() const { return tyraVolume; }
|
||||
|
||||
std::size_t AudioSong::getListenersCount() const {
|
||||
return songListeners.size();
|
||||
}
|
||||
|
||||
void AudioSong::initAUDSRV() {
|
||||
int ret = audsrv_on_fillbuf(chunkSize, (audsrv_callback_t)iSignalSema,
|
||||
(void*)fillbufferSema);
|
||||
|
||||
TYRA_ASSERT(ret >= 0,
|
||||
"AUDSRV returned error string:", audsrv_get_error_string());
|
||||
}
|
||||
|
||||
/** Initialize semaphore which will wait until chunk of the song is not
|
||||
* finished. */
|
||||
void AudioSong::initSema() {
|
||||
TYRA_LOG("Creating audio semaphore");
|
||||
sema.init_count = 0;
|
||||
sema.max_count = 1;
|
||||
sema.option = 0;
|
||||
fillbufferSema = CreateSema(&sema);
|
||||
TYRA_LOG("AudioSong semaphore created");
|
||||
}
|
||||
|
||||
/**
|
||||
* Close file.
|
||||
* Delete song path from memory.
|
||||
*/
|
||||
void AudioSong::unloadSong() {
|
||||
songLoaded = false;
|
||||
fclose(wav);
|
||||
}
|
||||
|
||||
/** Fseek on wav. */
|
||||
void AudioSong::rewindSongToStart() {
|
||||
if (wav != NULL) fseek(wav, 0x30, SEEK_SET);
|
||||
chunkReadStatus = 0;
|
||||
songFinished = false;
|
||||
}
|
||||
|
||||
/** Set WAV format to 16bit, 22050Hz, stereo. */
|
||||
void AudioSong::setSongFormat() {
|
||||
format.bits = 16;
|
||||
format.freq = 22050;
|
||||
format.channels = 2;
|
||||
|
||||
if (audsrv_set_format(&format)) {
|
||||
TYRA_ERROR("AUDSRV returned error string: ", audsrv_get_error_string());
|
||||
}
|
||||
}
|
||||
|
||||
void AudioSong::setVolume(const u8& t_vol) {
|
||||
audsrvVolume = t_vol;
|
||||
if (songPlaying) tyraVolume = t_vol;
|
||||
|
||||
if (audsrv_set_volume(tyraVolume)) {
|
||||
TYRA_ERROR("AUDSRV returned error string: ", audsrv_get_error_string());
|
||||
}
|
||||
}
|
||||
|
||||
void AudioSong::work() {
|
||||
if (!songPlaying || !songLoaded) return;
|
||||
if (songFinished) {
|
||||
TYRA_LOG("AudioSong: Song finished. ");
|
||||
if (inLoop) {
|
||||
TYRA_LOG("Running again.");
|
||||
for (u32 i = 0; i < getListenersCount(); i++)
|
||||
songListeners[i]->listener->onAudioFinish();
|
||||
rewindSongToStart();
|
||||
} else {
|
||||
TYRA_LOG("Stopping song.");
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (chunkReadStatus > 0) {
|
||||
WaitSema(fillbufferSema); // wait until previous chunk wasn't finished
|
||||
audsrv_play_audio(chunk, chunkReadStatus);
|
||||
for (u32 i = 0; i < getListenersCount(); i++)
|
||||
songListeners[i]->listener->onAudioTick();
|
||||
}
|
||||
|
||||
chunkReadStatus = fread(chunk, 1, chunkSize, wav);
|
||||
|
||||
if (chunkReadStatus < (s32)chunkSize) songFinished = true;
|
||||
}
|
||||
|
||||
u32 AudioSong::addListener(AudioListener* t_listener) {
|
||||
AudioListenerRef* ref = new AudioListenerRef;
|
||||
ref->id = rand() % 1000000;
|
||||
ref->listener = t_listener;
|
||||
songListeners.push_back(ref);
|
||||
return ref->id;
|
||||
}
|
||||
|
||||
void AudioSong::removeListener(const u32& t_id) {
|
||||
s32 index = -1;
|
||||
for (u32 i = 0; i < songListeners.size(); i++)
|
||||
if (songListeners[i]->id == t_id) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
|
||||
TYRA_ASSERT(index != -1,
|
||||
"Cant remove listener because given id was not found!");
|
||||
|
||||
delete songListeners[index];
|
||||
songListeners.erase(songListeners.begin() + index);
|
||||
}
|
||||
|
||||
} // namespace Tyra
|
||||
Reference in New Issue
Block a user