audio rework

This commit is contained in:
h4570
2022-08-11 23:18:50 +02:00
parent 74c642bd83
commit 472ab5c2ad
20 changed files with 706 additions and 386 deletions
+20
View File
@@ -44,6 +44,14 @@
}, },
"command": "./run.ps1" "command": "./run.ps1"
}, },
{
"type": "shell",
"label": "Convert WAV to ADPCM",
"dependsOn": [
"1. Copy source code to docker volume"
],
"command": "docker exec -t tyra-compiler sh -c 'adpenc ${input:wav2adpcmSource} ${input:wav2adpcmTarget} && rsync -zac /src/${input:wav2adpcmTarget} /host/${input:wav2adpcmTarget}'"
},
{ {
"type": "shell", "type": "shell",
"label": "Cleanup", "label": "Cleanup",
@@ -78,6 +86,18 @@
} }
], ],
"inputs": [ "inputs": [
{
"id": "wav2adpcmSource",
"description": "Source file path (ex. ):",
"default": "demo/res/sound.wav",
"type": "promptString"
},
{
"id": "wav2adpcmTarget",
"description": "Target file path (ex. ):",
"default": "demo/res/sound.adpcm",
"type": "promptString"
},
{ {
"id": "ps2Ip", "id": "ps2Ip",
"description": "PS2link ip address:", "description": "PS2link ip address:",
-4
View File
@@ -1,11 +1,7 @@
------------ Tyra's v2.0 roadmap to publish on GitHub ------------ ------------ Tyra's v2.0 roadmap to publish on GitHub ------------
- [Demo] Game fadein - [Demo] Game fadein
- [Demo] Larger map
- [Demo] Height map
- [Demo] Ambient sound - [Demo] Ambient sound
- [Demo] Shoot sound
- [Demo] Shoot animation
- [Demo] Running soldier - [Demo] Running soldier
- [Demo] Heightmap for soldier - [Demo] Heightmap for soldier
- [Demo] Running animation for soldier - [Demo] Running animation for soldier
+20 -2
View File
@@ -13,28 +13,46 @@
#include <renderer/3d/mesh/static/static_mesh.hpp> #include <renderer/3d/mesh/static/static_mesh.hpp>
#include <renderer/3d/pipeline/static/stapip_options.hpp> #include <renderer/3d/pipeline/static/stapip_options.hpp>
#include <renderer/renderer.hpp> #include <renderer/renderer.hpp>
#include <engine.hpp>
#include <pad/pad.hpp>
#include <audio/audio.hpp>
#include <time/timer.hpp>
using Tyra::Audio;
using Tyra::Engine;
using Tyra::Pad;
using Tyra::Renderer; using Tyra::Renderer;
using Tyra::StaPipOptions; using Tyra::StaPipOptions;
using Tyra::StaticMesh; using Tyra::StaticMesh;
using Tyra::TextureRepository; using Tyra::TextureRepository;
using Tyra::Timer;
using Tyra::Vec4; using Tyra::Vec4;
namespace Demo { namespace Demo {
class Weapon { class Weapon {
public: public:
Weapon(TextureRepository* repo); Weapon(Engine* engine);
~Weapon(); ~Weapon();
StaticMesh* mesh; StaticMesh* mesh;
StaPipOptions* options; StaPipOptions* options;
void update(); void update();
// void debugPosition(Pad* pad);
private: private:
bool isShootAnimation1, isShootAnimation2;
Pad* pad;
Audio* audio;
Timer shootTimer;
Vec4 initialPosition;
u8 adpcmCurrentChannel;
u8 adpcmChannelsCount;
audsrv_adpcm_t* shootAdpcm;
void allocateOptions(); void allocateOptions();
u8 getShootChannel();
}; };
} // namespace Demo } // namespace Demo
+21 -18
View File
@@ -19,31 +19,32 @@ using Tyra::ObjLoaderOptions;
namespace Demo { namespace Demo {
Enemy::Enemy(TextureRepository* repo) { Enemy::Enemy(TextureRepository* repo) {
ObjLoader loader; // ObjLoader loader;
ObjLoaderOptions objOptions; // ObjLoaderOptions objOptions;
objOptions.animation.count = 4; // objOptions.animation.count = 4;
objOptions.flipUVs = true; // objOptions.flipUVs = true;
objOptions.scale = 25.0F; // objOptions.scale = 35.0F;
auto* bodyData = loader.load( // auto* bodyData = loader.load(
FileUtils::fromCwd("game/models/soldier/soldier.obj"), objOptions); // FileUtils::fromCwd("game/models/soldier/soldier.obj"), objOptions);
bodyData->normalsEnabled = false; // bodyData->normalsEnabled = false;
bodyMesh = new DynamicMesh(*bodyData); // bodyMesh = new DynamicMesh(*bodyData);
delete bodyData; // delete bodyData;
bodyMesh->playAnimation(0, bodyMesh->frames.size() - 1); // bodyMesh->playAnimation(0, bodyMesh->frames.size() - 1);
bodyMesh->setAnimSpeed(0.1F); // bodyMesh->setAnimSpeed(0.1F);
repo->addByMesh(bodyMesh, FileUtils::fromCwd("game/models/soldier/"), "png"); // repo->addByMesh(bodyMesh, FileUtils::fromCwd("game/models/soldier/"),
// "png");
bodyMesh->translation.translateY(-5.0F); // bodyMesh->translation.translateY(-5.0F);
allocateOptions(); // allocateOptions();
pairs.push_back(new RendererDynamicPair{bodyMesh, options}); // pairs.push_back(new RendererDynamicPair{bodyMesh, options});
// pairs.push_back(new RendererDynamicPair{gunMesh, options}); // // pairs.push_back(new RendererDynamicPair{gunMesh, options});
} }
Enemy::~Enemy() { Enemy::~Enemy() {
@@ -55,7 +56,9 @@ Enemy::~Enemy() {
} }
} }
void Enemy::update(const Vec4& playerPosition) { bodyMesh->animate(); } void Enemy::update(const Vec4& playerPosition) {
// bodyMesh->animate();
}
void Enemy::allocateOptions() { options = new DynPipOptions(); } void Enemy::allocateOptions() { options = new DynPipOptions(); }
+5 -4
View File
@@ -26,7 +26,7 @@ GameState::~GameState() {}
void GameState::onStart() { void GameState::onStart() {
TYRA_LOG("Game. RAM: ", engine->info.getAvailableRAM(), "MB"); TYRA_LOG("Game. RAM: ", engine->info.getAvailableRAM(), "MB");
engine->audio.stopSong(); engine->audio.song.stop();
auto* repository = &engine->renderer.core.texture.repository; auto* repository = &engine->renderer.core.texture.repository;
player = new Player(engine); player = new Player(engine);
@@ -62,7 +62,8 @@ void GameState::update() {
engine->renderer.beginFrame(cameraInfo); engine->renderer.beginFrame(cameraInfo);
dbgObj->setPosition(*cameraInfo.looksAt); dbgObj->setPosition(*cameraInfo.looksAt);
float terrainHeight = terrain->getHeightOffset(player->getPosition()); float terrainHeight = 0.0F;
// float terrainHeight = terrain->getHeightOffset(player->getPosition());
player->update(terrainHeight); player->update(terrainHeight);
enemy->update(player->getPosition()); enemy->update(player->getPosition());
@@ -70,8 +71,8 @@ void GameState::update() {
{ {
renderer.add(skybox->pair); renderer.add(skybox->pair);
renderer.add(player->pair); renderer.add(player->pair);
renderer.add(enemy->pairs); // renderer.add(enemy->pairs);
renderer.add(terrain->pair); // renderer.add(terrain->pair);
renderer.add(dbgObj->pair); renderer.add(dbgObj->pair);
} }
renderer.render(); renderer.render();
+1 -1
View File
@@ -22,7 +22,7 @@ Camera::Camera(Pad* t_pad) : lookAt(0.0F), position(0.0F), pad(t_pad) {
Camera::~Camera() {} Camera::~Camera() {}
void Camera::update(const Vec4& playerPosition, const float& terrainHeight) { void Camera::update(const Vec4& playerPosition, const float& terrainHeight) {
const float rotationOffset = 0.03F; const float rotationOffset = 0.045F;
const float heightOffset = 0.5F; const float heightOffset = 0.5F;
const auto& rightJoy = pad->getRightJoyPad(); const auto& rightJoy = pad->getRightJoyPad();
+3 -5
View File
@@ -13,14 +13,12 @@
namespace Demo { namespace Demo {
Player::Player(Engine* engine) Player::Player(Engine* engine)
: position(0.0F), : position(0.0F), weapon(engine), camera(&engine->pad) {
weapon(&engine->renderer.core.texture.repository),
camera(&engine->pad) {
pair = new RendererStaticPair{weapon.mesh, weapon.options}; pair = new RendererStaticPair{weapon.mesh, weapon.options};
pad = &engine->pad; pad = &engine->pad;
position = Vec4(3.0F, 50.0F, 0.0F); position = Vec4(3.0F, 50.0F, 0.0F);
camera.lookAt = Vec4(0.0F, 0.0F, -30.0F); camera.lookAt = Vec4(0.0F, 0.0F, -30.0F);
speed = 2.0F; speed = 3.5F;
} }
Player::~Player() { delete pair; } Player::~Player() { delete pair; }
@@ -39,7 +37,7 @@ void Player::handlePlayerPosition(const float& terrainHeight) {
normalizedCamera *= speed; normalizedCamera *= speed;
Vec4 nextPosition(position); Vec4 nextPosition(position);
nextPosition.y = terrainHeight; nextPosition.y = terrainHeight - 100.0F;
if (leftJoy.v <= 100) { if (leftJoy.v <= 100) {
nextPosition.x += normalizedCamera.x; nextPosition.x += normalizedCamera.x;
+70 -7
View File
@@ -18,13 +18,16 @@ using Tyra::ObjLoaderOptions;
namespace Demo { namespace Demo {
Weapon::Weapon(TextureRepository* repo) { Weapon::Weapon(Engine* engine) {
ObjLoader loader; ObjLoader loader;
ObjLoaderOptions objOptions; ObjLoaderOptions objOptions;
objOptions.flipUVs = true; objOptions.flipUVs = true;
objOptions.scale = .5F; objOptions.scale = .5F;
pad = &engine->pad;
audio = &engine->audio;
auto* repo = &engine->renderer.core.texture.repository;
auto* data = auto* data =
loader.load(FileUtils::fromCwd("game/models/ak47/ak47.obj"), objOptions); loader.load(FileUtils::fromCwd("game/models/ak47/ak47.obj"), objOptions);
data->normalsEnabled = false; data->normalsEnabled = false;
@@ -42,10 +45,75 @@ Weapon::Weapon(TextureRepository* repo) {
repo->addByMesh(mesh, FileUtils::fromCwd("game/models/ak47/"), "png"); repo->addByMesh(mesh, FileUtils::fromCwd("game/models/ak47/"), "png");
initialPosition = *mesh->getPosition();
allocateOptions(); allocateOptions();
shootAdpcm =
audio->adpcm.load(FileUtils::fromCwd("game/models/ak47/ak47.adpcm"));
isShootAnimation1 = isShootAnimation2 = false;
adpcmChannelsCount = 8;
adpcmCurrentChannel = 0;
const u8 shootVolume = 40;
for (u8 i = 0; i < adpcmChannelsCount; i++) {
audio->adpcm.setVolume(shootVolume, i);
}
} }
void Weapon::update() {} Weapon::~Weapon() {
delete mesh;
delete options;
delete shootAdpcm;
}
u8 Weapon::getShootChannel() {
auto result = adpcmCurrentChannel++;
if (adpcmCurrentChannel >= adpcmChannelsCount) {
adpcmCurrentChannel = 0;
}
return result;
}
void Weapon::update() {
if (!isShootAnimation1 && !isShootAnimation2 && pad->getPressed().Cross) {
shootTimer.prime();
audio->adpcm.tryPlay(shootAdpcm, getShootChannel());
isShootAnimation1 = true;
}
auto* position = mesh->getPosition();
const float recoil = 0.5F;
const float speed = 500;
if (isShootAnimation1) {
position->z += recoil;
position->y -= recoil / 4;
if (shootTimer.getTimeDelta() > speed) {
isShootAnimation1 = false;
shootTimer.prime();
isShootAnimation2 = true;
}
}
if (isShootAnimation2) {
position->z -= recoil / 2;
position->y += recoil / 8;
if (shootTimer.getTimeDelta() > speed * 2) {
isShootAnimation2 = false;
*position = initialPosition;
if (pad->getPressed().Cross) {
shootTimer.prime();
audio->adpcm.tryPlay(shootAdpcm, getShootChannel());
isShootAnimation1 = true;
}
}
}
}
// float tX = 0.0F; // float tX = 0.0F;
// float tY = -3.0F; // float tY = -3.0F;
@@ -100,11 +168,6 @@ void Weapon::update() {}
// TYRA_LOG("tX: ", tX, " tY: ", tY, " tZ: ", tZ, " rY: ", rY, " rZ: ", rZ); // TYRA_LOG("tX: ", tX, " tY: ", tY, " tZ: ", tZ, " rY: ", rY, " rZ: ", rZ);
// } // }
Weapon::~Weapon() {
delete mesh;
delete options;
}
void Weapon::allocateOptions() { void Weapon::allocateOptions() {
options = new StaPipOptions(); options = new StaPipOptions();
options->textureMappingType = Tyra::TyraNearest; options->textureMappingType = Tyra::TyraNearest;
+15 -15
View File
@@ -19,28 +19,28 @@ using Tyra::ObjLoaderOptions;
namespace Demo { namespace Demo {
Terrain::Terrain(TextureRepository* repo) Terrain::Terrain(TextureRepository* repo)
: heightmap(-5.0F, // Min height : heightmap(-5.0F, // Min height
100.0F, // Max height 450.0F, // Max height
Vec4(-196.6F, 0.0F, -412.0F, 1.0F), // Left up Vec4(-784.6F, 0.0F, -1648.0F, 1.0F), // Left up
Vec4(286.0F, 0.0F, 443.3F, 1.0F) // Right down Vec4(1144.0F, 0.0F, 1772.3F, 1.0F) // Right down
) { ) {
ObjLoader loader; // ObjLoader loader;
ObjLoaderOptions objOptions; // ObjLoaderOptions objOptions;
objOptions.flipUVs = true; // objOptions.flipUVs = true;
objOptions.scale = 25.0F; // objOptions.scale = 100.0F;
auto* data = loader.load( // auto* data = loader.load(
FileUtils::fromCwd("game/models/terrain/terrain.obj"), objOptions); // FileUtils::fromCwd("game/models/terrain/terrain.obj"), objOptions);
data->normalsEnabled = false; // data->normalsEnabled = false;
mesh = new StaticMesh(*data); // mesh = new StaticMesh(*data);
delete data; // delete data;
repo->addByMesh(mesh, FileUtils::fromCwd("game/models/terrain/"), "png"); // repo->addByMesh(mesh, FileUtils::fromCwd("game/models/terrain/"), "png");
allocateOptions(); // allocateOptions();
pair = new RendererStaticPair{mesh, options}; // pair = new RendererStaticPair{mesh, options};
} }
float Terrain::getHeightOffset(const Vec4& playerPosition) { float Terrain::getHeightOffset(const Vec4& playerPosition) {
+4 -4
View File
@@ -32,10 +32,10 @@ IntroState::~IntroState() {}
void IntroState::onStart() { void IntroState::onStart() {
TYRA_LOG("Intro. RAM: ", engine->info.getAvailableRAM(), "MB"); TYRA_LOG("Intro. RAM: ", engine->info.getAvailableRAM(), "MB");
engine->audio.loadSong(FileUtils::fromCwd("intro/intro.wav")); engine->audio.song.load(FileUtils::fromCwd("intro/intro.wav"));
engine->audio.setSongLoop(false); engine->audio.song.inLoop = false;
engine->audio.setSongVolume(80); engine->audio.song.setVolume(80);
engine->audio.playSong(); engine->audio.song.play();
stateManager.add(new IntroPs2DevState(engine)); stateManager.add(new IntroPs2DevState(engine));
stateManager.add(new IntroTyraState(engine)); stateManager.add(new IntroTyraState(engine));
+28
View File
@@ -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
View File
@@ -11,151 +11,30 @@
#pragma once #pragma once
#include "./audio_listener.hpp" #include "./audio_adpcm.hpp"
#include <audsrv.h> #include "./audio_song.hpp"
#include <string>
namespace Tyra { namespace Tyra {
struct AudioListenerRef { /** Class responsible for audio. */
AudioListener* listener;
u32 id;
};
/**
* Class responsible for audio playing.
* There are two main features:
* - WAV song (background music)
* - ADPCM samples
*/
class Audio { class Audio {
public: public:
/** Constructor is called by the engine. */
Audio(); Audio();
~Audio(); ~Audio();
void init(); void init();
/** AudioSong song;
* Load 24bit 22kHz Stereo WAV. AudioAdpcm adpcm;
* 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);
}
void work(); void work();
private: private:
u8 songLoaded, tyraVolume, audsrvVolume, songPlaying, songInLoop,
songFinished;
FILE* wav;
audsrv_fmt_t format;
std::vector<AudioListenerRef*> songListeners;
ee_thread_t thread; ee_thread_t thread;
int threadId; int threadId;
static const u16 threadStackSize; static const u16 threadStackSize;
u8* threadStack; 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 initAUDSRV();
void initThread(); void initThread();
}; };
+77
View File
@@ -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
+24
View File
@@ -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
+90
View File
@@ -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
View File
@@ -9,20 +9,11 @@
# Sandro Sobczyński <sandro.sobczynski@gmail.com> # 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 "audio/audio.hpp"
#include "debug/debug.hpp"
#include "thread/threading.hpp"
#include <kernel.h>
#include <malloc.h>
extern void* _gp; extern void* _gp;
@@ -32,94 +23,32 @@ void audioThread(Tyra::Audio* audio) {
namespace Tyra { namespace Tyra {
const u16 Audio::chunkSize = 4 * 1024;
const u16 Audio::threadStackSize = 2 * 1024; const u16 Audio::threadStackSize = 2 * 1024;
Audio::Audio() { Audio::Audio() {
chunkReadStatus = 0; threadStack = static_cast<u8*>(memalign(sizeof(u8), threadStackSize));
tyraVolume = 0;
audsrvVolume = 0;
songLoaded = false;
songPlaying = false;
songInLoop = false;
songFinished = false;
chunk = nullptr;
threadStack = nullptr;
} }
Audio::~Audio() { Audio::~Audio() {
if (chunk) delete[] chunk;
if (threadStack) delete[] threadStack; if (threadStack) delete[] threadStack;
} }
void Audio::init() { void Audio::init() {
chunk = static_cast<char*>(memalign(sizeof(char), chunkSize));
threadStack = static_cast<u8*>(memalign(sizeof(u8), threadStackSize));
initSema();
initAUDSRV(); initAUDSRV();
setSongFormat();
song.init();
adpcm.init();
initThread(); 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() { void Audio::initAUDSRV() {
TYRA_LOG("Initializing AUDSRV");
int ret = audsrv_init(); int ret = audsrv_init();
TYRA_ASSERT(ret >= 0, TYRA_ASSERT(ret >= 0,
"AUDSRV returned error string: ", audsrv_get_error_string()); "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() { void Audio::initThread() {
@@ -133,120 +62,10 @@ void Audio::initThread() {
StartThread(threadId, this); 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() { void Audio::work() {
Threading::switchThread(); Threading::switchThread();
if (!songPlaying || !songLoaded) return; song.work();
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!");
}
} }
} // namespace Tyra } // namespace Tyra
+95
View File
@@ -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
+189
View File
@@ -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
@@ -44,6 +44,14 @@
}, },
"command": "./run.ps1" "command": "./run.ps1"
}, },
{
"type": "shell",
"label": "Convert WAV to ADPCM",
"dependsOn": [
"1. Copy source code to docker volume"
],
"command": "docker exec -t tyra-compiler sh -c 'adpenc ${input:wav2adpcmSource} ${input:wav2adpcmTarget} && rsync -zac /src/${input:wav2adpcmTarget} /host/${input:wav2adpcmTarget}'"
},
{ {
"type": "shell", "type": "shell",
"label": "Cleanup", "label": "Cleanup",
@@ -83,6 +91,18 @@
} }
], ],
"inputs": [ "inputs": [
{
"id": "wav2adpcmSource",
"description": "Source file path (ex. ):",
"default": "res/sound.wav",
"type": "promptString"
},
{
"id": "wav2adpcmTarget",
"description": "Target file path (ex. ):",
"default": "res/sound.adpcm",
"type": "promptString"
},
{ {
"id": "ps2Ip", "id": "ps2Ip",
"description": "PS2link ip address:", "description": "PS2link ip address:",
+5 -5
View File
@@ -39,12 +39,12 @@ Sprite* get2DPicture(Renderer* renderer);
void Tutorial01 ::init() { void Tutorial01 ::init() {
// Song // Song
engine->audio.loadSong(FileUtils::fromCwd("mafikizolo-loot.wav")); engine->audio.load(FileUtils::fromCwd("mafikizolo-loot.wav"));
engine->audio.setSongLoop(true); engine->audio.setSongLoop(true);
engine->audio.setSongVolume(30); engine->audio.setVolume(30);
adpcmSample = engine->audio.loadADPCM(FileUtils::fromCwd("walk.adpcm")); adpcmSample = engine->audio.load(FileUtils::fromCwd("walk.adpcm"));
engine->audio.setADPCMVolume(80, 1); engine->audio.setVolume(80, 1);
engine->renderer.setClearScreenColor(Color(64.0F, 64.0F, 64.0F)); engine->renderer.setClearScreenColor(Color(64.0F, 64.0F, 64.0F));
@@ -134,7 +134,7 @@ void Tutorial01 ::init() {
blocks.push_back(block); blocks.push_back(block);
} }
// engine->audio.playSong(); // engine->audio.play();
// engine->renderer.setFrameLimit(false); // engine->renderer.setFrameLimit(false);
} }