2023-04-24 13:45:06 -07:00
|
|
|
#include "playback.h"
|
2023-07-10 12:45:24 -07:00
|
|
|
#include "SDL_mixer.h"
|
|
|
|
#include <SDL_audio.h>
|
2023-07-15 14:52:49 -07:00
|
|
|
#include <SDL.h>
|
2023-07-10 12:45:24 -07:00
|
|
|
#include <exception>
|
2023-04-24 13:45:06 -07:00
|
|
|
#include <thread>
|
|
|
|
using namespace std::chrono;
|
2023-07-15 14:52:49 -07:00
|
|
|
size_t CalculateBufSize(SDL_AudioSpec *obtained, double max_seconds, size_t samples_override = 0) {
|
|
|
|
return ((((samples_override == 0) ? obtained->samples : samples_override) * max_seconds) + 1) * sizeof(SAMPLETYPE) * obtained->channels;
|
|
|
|
}
|
|
|
|
void Playback::SDLCallbackInner(Uint8 *stream, int len) {
|
|
|
|
while (st->numSamples() < len) {
|
|
|
|
general_mixer(NULL, buf, bufsize);
|
|
|
|
st->putSamples((SAMPLETYPE*)buf, bufsize / sizeof(SAMPLETYPE) / spec.channels);
|
|
|
|
}
|
|
|
|
SDL_memset((void*)stream, 0, len);
|
|
|
|
st->receiveSamples((SAMPLETYPE*)stream, len / sizeof(SAMPLETYPE) / spec.channels);
|
|
|
|
}
|
|
|
|
void Playback::SDLCallback(void *userdata, Uint8 *stream, int len) {
|
|
|
|
((Playback*)userdata)->SDLCallbackInner(stream, len);
|
|
|
|
}
|
2023-07-10 12:45:24 -07:00
|
|
|
Mix_Music *Playback::Load(const char *file) {
|
|
|
|
Mix_Music *output = Mix_LoadMUS(file);
|
|
|
|
if (!output) {
|
|
|
|
printf("Error loading music '%s': %s\n", file, Mix_GetError());
|
|
|
|
throw std::exception();
|
|
|
|
}
|
|
|
|
Mix_PlayMusicStream(output, -1);
|
|
|
|
length = Mix_MusicDuration(output);
|
|
|
|
update.store(true);
|
|
|
|
return output;
|
|
|
|
}
|
|
|
|
void Playback::Unload(Mix_Music *music) {
|
|
|
|
Mix_HaltMusicStream(music);
|
|
|
|
Mix_FreeMusic(music);
|
|
|
|
}
|
2023-04-24 13:45:06 -07:00
|
|
|
void Playback::ThreadFunc() {
|
2023-07-15 14:52:49 -07:00
|
|
|
if (!SDL_WasInit(SDL_INIT_AUDIO)) {
|
|
|
|
if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) {
|
|
|
|
printf("Error initializing SDL: '%s'\n", SDL_GetError());
|
|
|
|
throw std::exception();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
SDL_AudioSpec obtained;
|
|
|
|
SDL_AudioSpec desired;
|
|
|
|
desired.format =
|
|
|
|
#ifdef SOUNDTOUCH_INTEGER_SAMPLES
|
|
|
|
AUDIO_S16SYS;
|
|
|
|
#else
|
|
|
|
AUDIO_F32SYS;
|
|
|
|
#endif
|
|
|
|
desired.freq = 48000;
|
|
|
|
desired.samples = 128;
|
|
|
|
desired.channels = 2;
|
|
|
|
desired.callback = Playback::SDLCallback;
|
|
|
|
desired.userdata = this;
|
|
|
|
st = new SoundTouch();
|
|
|
|
st->setSampleRate(desired.freq);
|
|
|
|
st->setChannels(desired.channels);
|
2023-07-10 12:45:24 -07:00
|
|
|
Mix_Init(MIX_INIT_FLAC|MIX_INIT_MID|MIX_INIT_MOD|MIX_INIT_MP3|MIX_INIT_OGG|MIX_INIT_OPUS|MIX_INIT_WAVPACK);
|
2023-07-15 14:52:49 -07:00
|
|
|
if ((device = SDL_OpenAudioDevice(NULL, 0, &desired, &obtained, 0)) == 0) {
|
|
|
|
printf("Error opening audio device: '%s'\n", SDL_GetError());
|
|
|
|
throw std::exception();
|
|
|
|
}
|
|
|
|
spec = obtained;
|
|
|
|
bufsize = spec.size;
|
|
|
|
buf = (Uint8*)malloc(bufsize);
|
|
|
|
general_mixer = Mix_GetGeneralMixer();
|
|
|
|
Mix_InitMixer(&spec, SDL_TRUE);
|
|
|
|
SDL_PauseAudioDevice(device, 0);
|
2023-07-10 12:45:24 -07:00
|
|
|
Mix_Music *music = Load(filePath.c_str());
|
|
|
|
|
2023-04-24 13:45:06 -07:00
|
|
|
while (running) {
|
|
|
|
if (file_changed.exchange(false)) {
|
2023-07-10 12:45:24 -07:00
|
|
|
Unload(music);
|
|
|
|
music = Load(filePath.c_str());
|
2023-04-24 13:45:06 -07:00
|
|
|
}
|
|
|
|
if (flag_mutex.try_lock()) {
|
|
|
|
if (seeking.exchange(false)) {
|
2023-07-10 12:45:24 -07:00
|
|
|
Mix_SetMusicPositionStream(music, position);
|
2023-04-24 13:45:06 -07:00
|
|
|
}
|
|
|
|
if (paused) {
|
2023-07-10 12:45:24 -07:00
|
|
|
Mix_PauseMusicStream(music);
|
2023-04-24 13:45:06 -07:00
|
|
|
} else {
|
2023-07-10 12:45:24 -07:00
|
|
|
Mix_ResumeMusicStream(music);
|
2023-04-24 13:45:06 -07:00
|
|
|
}
|
|
|
|
if (update.exchange(false)) {
|
2023-07-10 12:45:24 -07:00
|
|
|
Mix_VolumeMusicStream(music, (volume / 100.0 * MIX_MAX_VOLUME));
|
2023-07-15 14:52:49 -07:00
|
|
|
SDL_LockAudioDevice(device);
|
|
|
|
if (speed > 0.0f) {
|
|
|
|
st->setRate(speed);
|
|
|
|
}
|
|
|
|
if (tempo > 0.0f) {
|
|
|
|
st->setTempo(tempo);
|
|
|
|
}
|
|
|
|
if (pitch > 0.0f) {
|
|
|
|
st->setPitch(pitch);
|
|
|
|
}
|
|
|
|
SDL_UnlockAudioDevice(device);
|
2023-04-24 13:45:06 -07:00
|
|
|
}
|
|
|
|
flag_mutex.unlock();
|
|
|
|
}
|
2023-07-10 12:45:24 -07:00
|
|
|
position = Mix_GetMusicPosition(music);
|
2023-07-09 13:56:34 -07:00
|
|
|
std::this_thread::sleep_for(20ms);
|
2023-04-24 13:45:06 -07:00
|
|
|
}
|
2023-07-10 12:45:24 -07:00
|
|
|
// ====
|
|
|
|
Unload(music);
|
|
|
|
Mix_CloseAudio();
|
|
|
|
Mix_Quit();
|
2023-07-15 14:52:49 -07:00
|
|
|
SDL_CloseAudioDevice(device);
|
|
|
|
delete st;
|
2023-07-10 12:45:24 -07:00
|
|
|
free(buf);
|
2023-04-24 13:45:06 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
Playback::Playback() {
|
|
|
|
running = false;
|
|
|
|
paused = true;
|
|
|
|
position = 0;
|
|
|
|
length = 0;
|
|
|
|
volume = 100.0;
|
|
|
|
speed = 1.0;
|
2023-07-15 14:52:49 -07:00
|
|
|
pitch = 1.0;
|
|
|
|
tempo = 1.0;
|
2023-04-24 13:45:06 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
Playback::~Playback() {
|
|
|
|
Stop();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Playback::Start(std::string filePath) {
|
|
|
|
this->filePath = filePath;
|
|
|
|
printf("Playing %s...\n", filePath.c_str());
|
|
|
|
flag_mutex.lock();
|
|
|
|
paused = false;
|
|
|
|
Update();
|
|
|
|
if (running.exchange(true)) {
|
|
|
|
file_changed.store(true);
|
|
|
|
} else {
|
|
|
|
thread = std::thread(&Playback::ThreadFunc, this);
|
|
|
|
}
|
|
|
|
flag_mutex.unlock();
|
|
|
|
}
|
|
|
|
double Playback::GetPosition() {
|
|
|
|
return position;
|
|
|
|
}
|
|
|
|
double Playback::GetLength() {
|
|
|
|
return length;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Playback::Seek(double position) {
|
|
|
|
flag_mutex.lock();
|
|
|
|
this->position = position;
|
|
|
|
seeking.store(true);
|
|
|
|
flag_mutex.unlock();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Playback::Pause() {
|
|
|
|
flag_mutex.lock();
|
|
|
|
paused = !paused;
|
|
|
|
flag_mutex.unlock();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Playback::IsPaused() {
|
|
|
|
return paused;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Playback::Stop() {
|
|
|
|
if (running.exchange(false)) {
|
|
|
|
thread.join();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Playback::Update() {
|
|
|
|
update.store(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Playback::IsStopped() {
|
|
|
|
return !running;
|
|
|
|
}
|