audio rework
This commit is contained in:
@@ -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:",
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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(); }
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -18,29 +18,29 @@ 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) {
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|||||||
@@ -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
|
#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();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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>
|
# 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
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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:",
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user