Unverified Commit 629f0e9c authored by RobLoach's avatar RobLoach Committed by GitHub
Browse files

Update Audio System to libretro-common (#260)

* Update libretro-common

* stub the audio

* Fix conversation

* Update

* Compile

* Fix defines

* Init

* Update

* Update libretro-common

* Add initial conver

* deps: Update libretro-common

* Add debugging

* debugging

* Update libretro-common

* debug

* Add more initializing of libretro-audio

* Update audio

* Update audio

* Add love.filesystem.readBuffer()

* Remove unneedded header

* Fix SD

* Clean up vars

* Update

* Use hard coded array size

* Update audio
parent 458931dc
......@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Added `love.system.setClipboardText()`
- Added `love.data.encode()`
- Added `love.data.decode()`
- New audio system from libretro-common
### Chores
- Added `love.filesystem.mount()` test
......
......@@ -22,6 +22,30 @@ FLAGS += -I$(CORE_DIR)/vendor/filesystem
# libretro-common
FLAGS += -I$(CORE_DIR)/vendor/libretro-common/include
# libretro-common audio
SOURCES_C += $(wildcard \
$(CORE_DIR)/vendor/libretro-common/audio/audio_mix.c \
$(CORE_DIR)/vendor/libretro-common/audio/audio_mixer.c \
$(CORE_DIR)/vendor/libretro-common/formats/wav/rwav.c \
$(CORE_DIR)/vendor/libretro-common/memmap/memalign.c \
$(CORE_DIR)/vendor/libretro-common/audio/conversion/*.c \
$(CORE_DIR)/vendor/libretro-common/streams/file_stream.c \
$(CORE_DIR)/vendor/libretro-common/audio/resampler/audio_resampler.c \
$(CORE_DIR)/vendor/libretro-common/audio/resampler/drivers/sinc_resampler.c \
$(CORE_DIR)/vendor/libretro-common/audio/resampler/drivers/null_resampler.c \
$(CORE_DIR)/vendor/libretro-common/audio/resampler/drivers/nearest_resampler.c \
$(CORE_DIR)/vendor/libretro-common/vfs/*.c \
$(CORE_DIR)/vendor/libretro-common/features/features_cpu.c \
$(CORE_DIR)/vendor/libretro-common/file/config_file_userdata.c \
$(CORE_DIR)/vendor/libretro-common/file/file_path.c \
$(CORE_DIR)/vendor/libretro-common/compat/compat_strl.c \
$(CORE_DIR)/vendor/libretro-common/file/config_file_userdata.c \
$(CORE_DIR)/vendor/libretro-common/file/config_file.c \
$(CORE_DIR)/vendor/libretro-common/lists/string_list.c \
$(CORE_DIR)/vendor/libretro-common/compat/compat_strcasestr.c \
$(CORE_DIR)/vendor/libretro-common/lists/string_list.c \
)
# zlib
SOURCES_C += \
$(CORE_DIR)/vendor/libretro-deps/libz/zutil.c \
......
......@@ -64,6 +64,9 @@ bool ChaiLove::load(const std::string& file) {
std::string version = CHAILOVE_VERSION_STRING GIT_VERSION;
std::cout << "[ChaiLove] ChaiLove " << version.c_str() << std::endl;
// Iniitalize some of the initial subsystems.
sound.load();
// Initalize the file system.
bool loaded = filesystem.init(file);
if (!loaded) {
......@@ -85,11 +88,6 @@ bool ChaiLove::load(const std::string& file) {
// Load up the window dimensions.
window.load(config);
// Initialize the soud, if needed.
if (config.modules.sound) {
sound.load();
}
console.load(config);
graphics.load();
image.load();
......@@ -169,6 +167,9 @@ std::string ChaiLove::demo() {
}
void ChaiLove::update() {
// Update the sound system.
sound.update();
// Update and poll all the events.
event.update();
......
......@@ -6,22 +6,21 @@
#include "libretro.h"
#include "ChaiLove.h"
static bool use_audio_cb;
int16_t audio_buffer[2 * (44100 / 60)];
static retro_video_refresh_t video_cb;
// This is needed to allow SDL-libretro to compile.
// @see SDL_LIBRETROaudio.c:37
retro_audio_sample_t audio_cb;
static retro_audio_sample_batch_t audio_batch_cb;
void retro_set_video_refresh(retro_video_refresh_t cb) {
video_cb = cb;
}
void retro_set_audio_sample(retro_audio_sample_t cb) {
audio_cb = cb;
ChaiLove::getInstance()->sound.audio_cb = cb;
}
void retro_set_audio_sample_batch(retro_audio_sample_batch_t cb) {
audio_batch_cb = cb;
ChaiLove::getInstance()->sound.audio_batch_cb = cb;
}
void retro_set_input_poll(retro_input_poll_t cb) {
......@@ -32,25 +31,13 @@ void retro_set_input_state(retro_input_state_t cb) {
ChaiLove::input_state_cb = cb;
}
static void emit_audio(void) {
if (ChaiLove::hasInstance()) {
ChaiLove::getInstance()->audio.mixer_render(audio_buffer);
audio_batch_cb(audio_buffer, 44100 / 60);
}
}
static void audio_set_state(bool enable) {
(void)enable;
}
#ifdef __cplusplus
extern "C" {
#endif
/**
* libretro-sdl callback; Send through the audio.
*/
void libretro_audio_cb(int16_t left, int16_t right) {
audio_cb(left, right);
// Nothing?
// audio_cb(left, right);
}
/**
......@@ -335,10 +322,6 @@ bool retro_load_game(const struct retro_game_info *info) {
struct retro_frame_time_callback frame_cb = { frame_time_cb, 1000000 / 60 };
ChaiLove::environ_cb(RETRO_ENVIRONMENT_SET_FRAME_TIME_CALLBACK, &frame_cb);
// Make the audio callback.
struct retro_audio_callback audio_cb = { emit_audio, audio_set_state };
use_audio_cb = ChaiLove::environ_cb(RETRO_ENVIRONMENT_SET_AUDIO_CALLBACK, &audio_cb);
// Find the game path.
std::string gamePath(info ? info->path : "");
if (gamePath == ".") {
......
#ifndef SRC_LOVE_TYPES_AUDIO_AUDIOSTATE_H_
#define SRC_LOVE_TYPES_AUDIO_AUDIOSTATE_H_
namespace love {
namespace Types {
namespace Audio {
enum AudioState {
Stopped = 0,
Paused,
Playing
};
} // namespace Audio
} // namespace Types
} // namespace love
#endif // SRC_LOVE_TYPES_AUDIO_AUDIOSTATE_H_
......@@ -2,29 +2,34 @@
#include <string>
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include "../../../ChaiLove.h"
#include "AudioState.h"
#include "physfs.h"
namespace love {
namespace Types {
namespace Audio {
SoundData::SoundData(const std::string& filename) {
// Load the file.
ChaiLove* app = ChaiLove::getInstance();
PHYSFS_file* file = app->filesystem.openFile(filename);
if (file == NULL) {
return;
}
int result = PHYSFS_readBytes(file, &sndta.head, sizeof(uint8_t) * WAV_HEADER_SIZE);
if (result < 0) {
std::cout << "[ChaiLove] Failed to load SoundData " << filename << app->filesystem.getLastError() << std::endl;
// Load the file.
int size = 0;
void* buffer = app->filesystem.readBuffer(filename, size);
if (buffer == NULL) {
std::cout << "[ChaiLove] [SoundData] Failed to load file buffer " << filename << std::endl;
return;
}
sndta.fp = file;
bps = sndta.head.NumChannels * sndta.head.BitsPerSample / 8;
// Load the file into the buffer.
// TODO(RobLoach): Check the audio file extensions of ".wav".
m_sound = audio_mixer_load_wav(buffer, size);
free(buffer);
if (m_sound == NULL) {
std::cout << "[ChaiLove] [SoundData] Failed to load wav from buffer " << filename << std::endl;
}
}
SoundData::~SoundData() {
......@@ -32,6 +37,10 @@ SoundData::~SoundData() {
}
float SoundData::getVolume() {
if (m_voice != NULL) {
m_volume = audio_mixer_voice_get_volume(m_voice);
}
return m_volume;
}
......@@ -42,66 +51,88 @@ SoundData& SoundData::setVolume(float volume) {
volume = 0.0f;
}
m_volume = volume;
if (m_voice != NULL) {
audio_mixer_voice_set_volume(m_voice, m_volume);
}
return *this;
}
void SoundData::unload() {
if (isLoaded()) {
PHYSFS_close(sndta.fp);
sndta.fp = NULL;
if (m_voice != NULL) {
audio_mixer_stop(m_voice);
}
}
bool SoundData::play() {
if (isLoaded()) {
PHYSFS_seek(sndta.fp, WAV_HEADER_SIZE);
state = Playing;
return true;
audio_mixer_destroy(m_sound);
m_sound = NULL;
}
return false;
}
bool SoundData::resume() {
if (isLoaded()) {
state = Playing;
return true;
}
return false;
}
bool SoundData::pause() {
bool SoundData::play() {
if (isLoaded()) {
state = Paused;
m_voice = audio_mixer_play(m_sound, m_loop, m_volume, audioCallback);
if (m_voice != NULL) {
m_playing = true;
}
return true;
}
return false;
}
bool SoundData::stop() {
state = Stopped;
if (isLoaded()) {
PHYSFS_seek(sndta.fp, WAV_HEADER_SIZE);
if (m_voice != NULL) {
audio_mixer_stop(m_voice);
}
}
return true;
}
bool SoundData::isLoaded() {
return sndta.fp != NULL;
return m_sound != NULL;
}
bool SoundData::isPlaying() {
return state == Playing;
return m_playing;
}
bool SoundData::isLooping() {
return loop;
return m_loop;
}
SoundData& SoundData::setLooping(bool looping) {
loop = looping;
m_loop = looping;
return *this;
}
void SoundData::audioCallback(audio_mixer_sound_t* sound, unsigned reason) {
// This is called when an audio finishes.
ChaiLove* app = ChaiLove::getInstance();
// Loop through all sounds, and find the given one.
std::vector<Types::Audio::SoundData*> v = app->sound.sounds;
for(std::vector<SoundData*>::iterator it = v.begin(); it != v.end(); ++it) {
SoundData* currentsound = *it;
if (currentsound == NULL) {
continue;
}
// We found the active sound.
if (currentsound->m_sound == sound) {
switch (reason) {
case AUDIO_MIXER_SOUND_FINISHED:
case AUDIO_MIXER_SOUND_STOPPED:
currentsound->m_playing = false;
break;
case AUDIO_MIXER_SOUND_REPEATED:
currentsound->m_playing = true;
break;
}
break;
}
}
}
} // namespace Audio
} // namespace Types
} // namespace love
......@@ -2,34 +2,13 @@
#define SRC_LOVE_TYPES_AUDIO_SOUNDDATA_H_
#include <string>
#include "AudioState.h"
#include "physfs.h"
#include "audio/audio_mixer.h"
namespace love {
namespace Types {
namespace Audio {
typedef struct {
char ChunkID[4];
uint32_t ChunkSize;
char Format[4];
char Subchunk1ID[4];
uint32_t Subchunk1Size;
uint16_t AudioFormat;
uint16_t NumChannels;
uint32_t SampleRate;
uint32_t ByteRate;
uint16_t BlockAlign;
uint16_t BitsPerSample;
char Subchunk2ID[4];
uint32_t Subchunk2Size;
} wavhead_t;
typedef struct {
PHYSFS_File* fp = NULL;
wavhead_t head;
} snd_SoundData;
/**
* Contains audio samples that you can playback.
*/
......@@ -49,12 +28,6 @@ class SoundData {
bool stop();
void unload();
snd_SoundData sndta;
unsigned bps = 0;
bool loop = false;
float m_volume = 1.0f;
float pitch = 1.0f;
AudioState state = Stopped;
bool isLoaded();
/**
......@@ -80,16 +53,6 @@ class SoundData {
*/
bool isPlaying();
/**
* Resumes playing the given Source.
*/
bool resume();
/**
* Pauses the given Source.
*/
bool pause();
/**
* Returns whether the Source will loop.
*/
......@@ -100,7 +63,16 @@ class SoundData {
*/
SoundData& setLooping(bool loop);
int WAV_HEADER_SIZE = 44;
// Properties
bool m_playing = false;
bool m_loop = false;
float m_volume = 1.0f;
audio_mixer_sound* m_sound = NULL;
// TODO(RobLoach): Make voice a vector.
audio_mixer_voice_t* m_voice = NULL;
// The audio callback for when a sound finishes.
static void audioCallback(audio_mixer_sound_t* sound, unsigned reason);
};
} // namespace Audio
......
#include "audio.h"
#include <string>
#include "Types/Audio/SoundData.h"
#include "Types/Audio/AudioState.h"
#include "../ChaiLove.h"
#include "sound.h"
#include "physfs.h"
#include "audio/conversion/float_to_s16.h"
using love::Types::Audio::SoundData;
using love::Types::Audio::AudioState::Stopped;
namespace love {
......@@ -44,60 +43,4 @@ audio& audio::setVolume(float volume) {
m_volume = volume;
return *this;
}
void audio::mixer_render(int16_t *buffer) {
if (!ChaiLove::hasInstance()) {
return;
}
ChaiLove* app = ChaiLove::getInstance();
// Clear buffer
memset(buffer, 0, AUDIO_FRAMES * 2 * sizeof(int16_t));
// Mix all the sounds together.
for (std::vector<SoundData*>::size_type i = 0; i != app->sound.sounds.size(); i++) {
SoundData* currentSound = app->sound.sounds[i];
if (currentSound == NULL || !currentSound->isLoaded() || currentSound->state == Stopped) {
continue;
}
// Figure out what is playing.
uint8_t* rawsamples8 = reinterpret_cast<uint8_t*>(calloc(AUDIO_FRAMES * currentSound->bps, sizeof(uint8_t)));
// bool end = !fread(rawsamples8, sizeof(uint8_t), AUDIO_FRAMES * currentSound->bps, currentSound->sndta.fp);
PHYSFS_readBytes(currentSound->sndta.fp, rawsamples8, sizeof(uint8_t) * AUDIO_FRAMES * currentSound->bps);
bool end = PHYSFS_eof(currentSound->sndta.fp);
int16_t* rawsamples16 = reinterpret_cast<int16_t*>(rawsamples8);
for (unsigned j = 0; j < AUDIO_FRAMES; j++) {
int16_t left = 0;
int16_t right = 0;
if (currentSound->sndta.head.NumChannels == 1 && currentSound->sndta.head.BitsPerSample == 8) {
left = right = rawsamples8[j] * 64;
}
if (currentSound->sndta.head.NumChannels == 2 && currentSound->sndta.head.BitsPerSample == 8) {
left = rawsamples8[j * 2 + 0] * 64;
right = rawsamples8[j * 2 + 1] * 64;
}
if (currentSound->sndta.head.NumChannels == 1 && currentSound->sndta.head.BitsPerSample == 16) {
left = right = rawsamples16[j];
}
if (currentSound->sndta.head.NumChannels == 2 && currentSound->sndta.head.BitsPerSample == 16) {
left = rawsamples16[j * 2 + 0];
right = rawsamples16[j * 2 + 1];
}
buffer[j * 2 + 0] += left * currentSound->getVolume() * m_volume;
buffer[j * 2 + 1] += right * currentSound->getVolume() * m_volume;
}
if (end) {
if (currentSound->loop) {
currentSound->play();
} else {
currentSound->stop();
}
}
free(rawsamples8);
}
}
} // namespace love
......@@ -33,10 +33,6 @@ class audio {
SoundData* newSource(const std::string& filename, const std::string& type);
SoundData* newSource(const std::string& filename);
void mixer_render(int16_t *buffer);
int AUDIO_FRAMES = 735; // 44100 / 60
int WAV_HEADER_SIZE = 44;
float m_volume = 1.0f;
/**
......
......@@ -169,6 +169,39 @@ std::string filesystem::read(const std::string& filename) {
return std::string(myBuf);
}
void* filesystem::readBuffer(const std::string& filename, int& size) {
PHYSFS_file* file = openFile(filename);
if (file == NULL) {
return NULL;
}
// Find the file size.
size = getSize(file);
if (size <= 0) {
PHYSFS_close(file);
return NULL;
}
// Create the buffer.
void* buffer = (void*)malloc(size + 1);
if (!buffer) {
std::cout << "[ChaiLove] [filesystem] Failed to allocate buffer of size " << size + 1 << "." << std::endl;
PHYSFS_close(file);
return NULL;
}
// Read the file into the buffer.
int result = PHYSFS_readBytes(file, buffer, size);
if (result < 0) {
std::cout << "[ChaiLove] [filesystem] Failed to load SoundData " << filename << getLastError() << std::endl;
free(buffer);
PHYSFS_close(file);
return NULL;
}
PHYSFS_close(file);
return buffer;
}
bool filesystem::unmount(const std::string& archive) {
std::cout << "[filesystem] Unmounting " << archive << std::endl;
......
......@@ -51,6 +51,7 @@ class filesystem {
* @endcode
*/
std::string read(const std::string& filename);
void* readBuffer(const std::string& filename, int& size);
/**
* Check whether a file or directory exists.
......
......@@ -152,8 +152,6 @@ script::script(const std::string& file) {
chai.add(fun(&SoundData::setLooping), "setLooping");
chai.add(fun(&SoundData::stop), "stop");
chai.add(fun(&SoundData::play), "play");
chai.add(fun(&SoundData::resume), "resume");
chai.add(fun(&SoundData::pause), "pause");
chai.add(fun(&SoundData::setVolume), "setVolume");
chai.add(fun(&SoundData::getVolume), "getVolume");
......
......@@ -5,6 +5,9 @@
#include "SDL.h"
#include "../ChaiLove.h"
#include "Types/Audio/SoundData.h"
#include "audio/audio_mixer.h"
#include "audio/conversion/float_to_s16.h"
#include "libretro.h"
using ::ChaiLove;
using love::Types::Audio::SoundData;
......@@ -12,6 +15,8 @@ using love::Types::Audio::SoundData;
namespace love {
bool sound::load() {
audio_mixer_init(44100);
m_loaded = true;
return true;
}
......@@ -19,6 +24,10 @@ sound::~sound() {
unload();
}
bool sound::isLoaded() {
return m_loaded;
}
void sound::unload() {
// Unload all sounds.
for (std::vector<SoundData*>::size_type i = 0; i != sounds.size(); i++) {
......@@ -27,10 +36,25 @@ void sound::unload() {
}
}
sounds.clear();
// Close the audio mixer.
audio_mixer_done();
m_loaded = false;
}
SoundData* sound::newSoundData(const std::string& filename) {
return ChaiLove::getInstance()->audio.newSource(filename);
}
void sound::update() {
int bufferSize = 44100 / 60;
float samples[1470] = { 0 }; // 44100 / 60 * 2
int16_t samples2[1470] = { 0 }; // 44100 / 60 * 2
audio_mixer_mix(samples, bufferSize, 1.0, false);
convert_float_to_s16(samples2, samples, 2 * bufferSize);
audio_batch_cb(samples2, bufferSize);
}
} // namespace love
......@@ -3,6 +3,7 @@
#include "Types/Audio/SoundData.h"
#include <vector>
#include "libretro.h"
namespace love {
......@@ -12,10 +13,15 @@ namespace love {
class sound {
public: