looper/playback.cpp

296 lines
9 KiB
C++
Raw Normal View History

2023-04-24 13:45:06 -07:00
#include "playback.h"
#include "SDL_mixer.h"
#include <SDL_audio.h>
#include <SDL.h>
#include <exception>
2023-04-24 13:45:06 -07:00
#include <thread>
#include <cmath>
#ifdef __linux__
#include <pthread.h>
#endif
2023-04-24 13:45:06 -07:00
using namespace std::chrono;
size_t CalculateBufSize(SDL_AudioSpec *obtained, double seconds, double max_seconds, size_t samples_override = 0) {
return ((((samples_override == 0) ? obtained->samples : samples_override) * std::min(seconds, max_seconds)) + 1) * sizeof(SAMPLETYPE) * obtained->channels;
}
void Playback::SDLCallbackInner(Uint8 *stream, int len) {
SDL_memset((void*)stream, 0, len);
if (!playback_ready.load()) {
return;
}
if (st == nullptr) {
return;
}
size_t i = 0;
size_t max = 0;
size_t unit = sizeof(SAMPLETYPE) * spec.channels;
size_t bytes_per_iter = std::min(((bufsize / unit)) * unit, (size_t)fakespec.size);
while (st->numSamples() < (size_t)len) {
if (general_mixer == nullptr) {
return;
}
general_mixer(nullptr, buf + i, (int)bytes_per_iter);
i += bytes_per_iter;
max = i + bytes_per_iter;
if (max >= bufsize) {
st->putSamples((SAMPLETYPE*)buf, i/unit);
i = 0;
max = i + bytes_per_iter;
}
}
st->receiveSamples((SAMPLETYPE*)stream, len / unit);
}
void Playback::SDLCallback(void *userdata, Uint8 *stream, int len) {
((Playback*)userdata)->SDLCallbackInner(stream, len);
}
Mix_Music *Playback::Load(const char *file) {
Mix_Music *output = Mix_LoadMUS(file);
if (output == nullptr) {
printf("Error loading music '%s': %s\n", file, Mix_GetError());
error_mutex.lock();
errors.emplace("Error loading music!");
error_mutex.unlock();
return nullptr;
}
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);
}
void Playback::UpdateST() {
if (speed > 0.0f) {
pitch = std::max(std::min(speed, MaxSpeed), MinSpeed);
st->setRate(speed);
}
if (tempo > 0.0f) {
pitch = std::max(std::min(tempo, MaxTempo), MinTempo);
st->setTempo(tempo);
}
if (pitch > 0.0f) {
pitch = std::max(std::min(pitch, MaxPitch), MinPitch);
st->setPitch(pitch);
}
}
double Playback::GetMaxSeconds() {
return std::max((double)(MaxSpeed * MaxTempo), st->getInputOutputSampleRatio());
}
void Playback::ThreadFunc() {
#ifdef __linux__
pthread_setname_np(pthread_self(), "Playback control thread");
#endif
bool reload = false;
2023-04-24 13:45:06 -07:00
while (running) {
playback_ready.store(false);
if (!SDL_WasInit(SDL_INIT_AUDIO)) {
if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) {
printf("Error initializing SDL: '%s'\n", SDL_GetError());
error_mutex.lock();
errors.emplace("Failed to initialize SDL!");
error_mutex.unlock();
return;
2023-04-24 13:45:06 -07:00
}
}
SDL_AudioSpec obtained;
SDL_AudioSpec desired;
desired.format =
#ifdef SOUNDTOUCH_INTEGER_SAMPLES
AUDIO_S16SYS;
#else
AUDIO_F32SYS;
#endif
desired.freq = 48000;
desired.samples = 1024;
desired.channels = 2;
desired.callback = Playback::SDLCallback;
desired.userdata = this;
st = new SoundTouch();
Mix_Init(MIX_INIT_FLAC|MIX_INIT_MID|MIX_INIT_MOD|MIX_INIT_MP3|MIX_INIT_OGG|MIX_INIT_OPUS|MIX_INIT_WAVPACK);
if ((device = SDL_OpenAudioDevice(nullptr, 0, &desired, &obtained, SDL_AUDIO_ALLOW_CHANNELS_CHANGE|SDL_AUDIO_ALLOW_FREQUENCY_CHANGE|SDL_AUDIO_ALLOW_SAMPLES_CHANGE)) == 0) {
printf("Error opening audio device: '%s'\n", SDL_GetError());
error_mutex.lock();
errors.emplace("Failed to open audio device!");
error_mutex.unlock();
running = false;
break;
}
spec = obtained;
st->setSampleRate(spec.freq);
st->setChannels(spec.channels);
UpdateST();
bufsize = 0;
fakespec = spec;
double maxSeconds = GetMaxSeconds();
fakespec.size *= maxSeconds;
fakespec.samples *= maxSeconds;
size_t new_bufsize = CalculateBufSize(&spec, GetMaxSeconds(), MaxSeconds);
buf = (Uint8*)malloc(new_bufsize);
if (buf == nullptr) {
error_mutex.lock();
errors.emplace("Failed to allocate memory for playback!");
error_mutex.unlock();
running = false;
break;
}
bufsize = new_bufsize;
general_mixer = Mix_GetGeneralMixer();
Mix_InitMixer(&fakespec, SDL_FALSE);
SDL_PauseAudioDevice(device, 0);
Mix_Music *music = Load(filePath.c_str());
reload = false;
if (music) {
playback_ready.store(true);
} else {
playback_ready.store(false);
}
while (running) {
if (file_changed.exchange(false)) {
Unload(music);
music = Load(filePath.c_str());
if (music) {
playback_ready.store(true);
} else {
playback_ready.store(false);
}
2023-04-24 13:45:06 -07:00
}
if (flag_mutex.try_lock()) {
if (seeking.exchange(false)) {
Mix_SetMusicPositionStream(music, position);
}
if (paused) {
Mix_PauseMusicStream(music);
} else {
Mix_ResumeMusicStream(music);
}
if (update.exchange(false)) {
Mix_VolumeMusicStream(music, (int)(volume / 100.0 * MIX_MAX_VOLUME));
SDL_LockAudioDevice(device);
UpdateST();
size_t correct_buf_size = CalculateBufSize(&spec, GetMaxSeconds(), MaxSeconds);
size_t max_buf_size = correct_buf_size * 10;
bool too_large = max_buf_size < bufsize;
bool too_small = correct_buf_size > bufsize;
if (too_large) {
printf("Bufsize is too large - ");
} else if (too_small) {
printf("Bufsize is too small - ");
}
if (too_large || too_small) {
printf("Resizing buffer...\n");
general_mixer = nullptr;
bufsize = 0;
buf = (Uint8*)realloc((void*)buf, correct_buf_size);
if (buf == nullptr) {
error_mutex.lock();
errors.emplace("Failed to allocate memory for playback!");
error_mutex.unlock();
running = false;
break;
}
bufsize = correct_buf_size;
}
SDL_UnlockAudioDevice(device);
}
flag_mutex.unlock();
2023-04-24 13:45:06 -07:00
}
position = Mix_GetMusicPosition(music);
std::this_thread::sleep_for(20ms);
2023-04-24 13:45:06 -07:00
}
playback_ready.store(false);
// ====
Unload(music);
SDL_CloseAudioDevice(device);
Mix_CloseAudio();
Mix_Quit();
SDL_QuitSubSystem(SDL_INIT_AUDIO);
delete st;
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;
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();
this->position = 0.0;
seeking.store(true);
2023-04-24 13:45:06 -07:00
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;
}
optional<std::string> Playback::GetError() {
if (ErrorExists()) {
error_mutex.lock();
std::string error = errors.back();
errors.pop();
error_mutex.unlock();
return error;
} else {
return {};
}
}
bool Playback::ErrorExists() {
error_mutex.lock();
bool output = !errors.empty();
error_mutex.unlock();
return output;
}