diff --git a/playback.cpp b/playback.cpp index 26aeb5a..cdb1f24 100644 --- a/playback.cpp +++ b/playback.cpp @@ -31,47 +31,93 @@ void PlaybackInstance::SDLCallbackInner(Uint8 *stream, int len) { SDL_memset((void*)stream, 0, len); if (!playback_ready.load()) return; if (process == nullptr) return; - if (st == nullptr) return; size_t unit = sizeof(SAMPLETYPE) * spec.channels; size_t bytes_per_iter = (bufsize / unit) * unit; - while (st->numSamples() * unit <= (size_t)len) { - if (process == nullptr) { - return; - } - Uint8 *new_buf; - int new_bufsize; - new_buf = buf; - bytes_per_iter = backend_spec.size; - if (bytes_per_iter > bufsize) { - bytes_per_iter = bufsize; - } - new_bufsize = bytes_per_iter; - if (this->process != nullptr) { - size_t new_bytes = this->process->render(buf, bytes_per_iter); - if (SDL_AudioStreamPut(sdl_stream, buf, new_bytes) < 0) { + SAMPLETYPE *tmpbuf = (SAMPLETYPE*)malloc(bytes_per_iter); + memset(tmpbuf, 0, bytes_per_iter); + while (m_st->numSamples() * unit <= (size_t)len) { + for (auto &data : m_playback_data) { + Uint8 *new_buf; + int new_bufsize; + new_buf = buf; + bytes_per_iter = data.m_backend_spec.size; + if (bytes_per_iter > bufsize) { + bytes_per_iter = bufsize; + } + new_bufsize = bytes_per_iter; + size_t new_bytes = data.m_pProcess->render(buf, bytes_per_iter); + if (SDL_AudioStreamPut(data.m_pStream, buf, new_bytes) < 0) { ERROR.writefln("SDL_AudioStreamPut: %s", SDL_GetError()); - DEBUG.writefln("Current audio backend: %s", process->get_backend_id().c_str()); - DEBUG.writefln("SDL_AudioStream *sdl_stream = %0lx", (size_t)sdl_stream); + DEBUG.writefln("Current audio backend: %s", data.m_pProcess->get_backend_id().c_str()); + DEBUG.writefln("SDL_AudioStream *sdl_stream = %0lx", (size_t)data.m_pStream); return; } do { - new_bufsize = SDL_AudioStreamGet(sdl_stream, buf, std::min((size_t)backend_spec.samples, bufsize / unit) * unit); - if (new_bufsize < 0) { - ERROR.writefln("SDL_AudioStreamGet: %s", SDL_GetError()); - DEBUG.writefln("Current audio backend: %s", process->get_backend_id().c_str()); - return; - } - SAMPLETYPE *sbuf = (SAMPLETYPE*)buf; - for (size_t i = 0; i < (new_bufsize / unit) * spec.channels; i++) { - sbuf[i] *= real_volume; - } - st->putSamples(sbuf, new_bufsize / unit); + new_bufsize = SDL_AudioStreamGet(data.m_pStream, buf, bytes_per_iter); + if (new_bufsize < 0) { + ERROR.writefln("SDL_AudioStreamGet: %s", SDL_GetError()); + DEBUG.writefln("Current audio backend: %s", data.m_pProcess->get_backend_id().c_str()); + return; + } + SAMPLETYPE *sbuf = (SAMPLETYPE*)buf; + for (size_t i = 0; i < (new_bufsize / unit) * spec.channels; i++) { + if (data.m_fade_time >= 0.0) { + if ((i % spec.channels) == 0) { + data.m_fade_time -= 1.0 / (spec.freq); + if (data.m_fade_time <= 0.0) { + data.delete_needed = true; + break; + } + } + sbuf[i] *= data.m_fade_time; + } + tmpbuf[i] += sbuf[i]; + } + data.m_st->putSamples(sbuf, new_bufsize / unit); + if (data.delete_needed) { + break; + } } while (new_bufsize > 0); - } else { - return; } - } - st->receiveSamples((SAMPLETYPE*)stream, len / unit); + for (auto &data : m_playback_data) { + size_t tmplen = 0; + size_t bufused = (bufsize / unit) * unit; + bool done = false; + { + uint samples; + do { + samples = data.m_st->receiveSamples((SAMPLETYPE*)(void*)buf, tmplen / unit); + m_st->putSamples((SAMPLETYPE*)(void*)buf, samples); + } while (samples > 0); + } + for (size_t i = 0; i < len; i += bufused) { + if (i + bufused >= len) { + tmplen = len - i; + } else { + tmplen = bufused; + } + uint samples = m_st->receiveSamples((SAMPLETYPE*)(void*)buf, tmplen / unit); + for (size_t j = 0; j < samples * spec.channels; j++) { + ((SAMPLETYPE*)(void*)stream)[j] += ((SAMPLETYPE*)(void*)buf)[(i * bufused / unit) + j]; + } + if (samples == 0) { + done = true; + break; + } + } + if (data.delete_needed) { + continue; + } + } + for (size_t i = 0; i > m_playback_data.size();) { + auto &data = m_playback_data[i]; + if (data.delete_needed) { + m_playback_data.erase(m_playback_data.begin() + i); + } else { + i++; + } + } + } } #ifdef __ANDROID__ oboe::DataCallbackResult PlaybackInstance::onAudioReady( @@ -109,18 +155,23 @@ void PlaybackInstance::Load(const char *file, int idx) { LockAudioDevice(); load_finished.store(false); playback_ready.store(false); - if (process != nullptr) delete process; + for (auto &data : m_playback_data) { + data.m_fade_time = 1.0; + } + PlaybackProcess *process = nullptr; + playback_data data; try { process = new PlaybackProcess(file, idx); } catch (std::exception e) { ERROR.writefln("Exception caught when creating process: %s", e.what()); - process = nullptr; + data.delete_needed = true; } length = 0.0; - if (process != nullptr && process->process_running()) { + if (!data.delete_needed && process->process_running()) { length = process->get_length(); auto backend_spec_proxy = process->get_audio_spec(); - memset(&backend_spec, 0, sizeof(backend_spec)); + SDL_AudioSpec backend_spec; + memset(&backend_spec, 0, sizeof(data.m_backend_spec)); backend_spec.channels = backend_spec_proxy->channel_count(); audio_data_t sample_fmt; sample_fmt.size = backend_spec_proxy->bits() / 8; @@ -136,23 +187,22 @@ void PlaybackInstance::Load(const char *file, int idx) { DEBUG.writefln("\tChannels: %d", backend_spec.channels); DEBUG.writefln("\tSample rate: %d", backend_spec.freq); DEBUG.writefln("\tSamples: %d", backend_spec.samples); - if (sdl_stream != nullptr) { - SDL_FreeAudioStream(sdl_stream); - } - sdl_stream = SDL_NewAudioStream(backend_spec.format, backend_spec.channels, backend_spec.freq, spec.format, spec.channels, spec.freq); - if (sdl_stream == nullptr) { - ERROR.writefln("SDL_NewAudioStream: %s", SDL_GetError()); - DEBUG.writefln("format: AUDIO_%s%d%s", sample_fmt.is_float ? "F" : sample_fmt.is_signed ? "S" : "U", sample_fmt.size * 8, sample_fmt.endian ? "MSB" : "LSB"); - set_error("Failed to create SDL audio stream"); + try { + data = playback_data(backend_spec, spec, process); + } catch (CustomException &e) { + set_error(e.what()); set_signal(PlaybackSignalErrorOccurred); + ERROR.writeln(e.what()); + } + size_t required_size = sample_fmt.size * data.m_backend_spec.samples; + m_st->setChannels(spec.channels); + m_st->setRate(spec.freq); + m_st->flush(); + data.m_backend_spec.size = required_size; + if (bufsize < required_size || buf == nullptr) { + bufsize = required_size; + buf = (Uint8*)((buf == nullptr) ? malloc(bufsize) : realloc(buf, bufsize)); } - size_t required_size = sample_fmt.size * backend_spec.samples; - st->setChannels(spec.channels); - st->setRate(spec.freq); - st->flush(); - backend_spec.size = required_size; - bufsize = backend_spec.size; - buf = (Uint8*)malloc(bufsize); if (buf == nullptr) { ERROR.writeln("Failed to allocate memory for playback!"); set_error("Failed to allocate memory for playback!"); @@ -176,12 +226,8 @@ void PlaybackInstance::Load(const char *file, int idx) { flag_mutex.unlock(); } void PlaybackInstance::Unload() { - if (process == nullptr) return; LockAudioDevice(); - delete process; - process = nullptr; - SDL_FreeAudioStream(sdl_stream); - sdl_stream = nullptr; + m_playback_data.clear(); if (buf) free(buf); buf = nullptr; UnlockAudioDevice(); @@ -190,25 +236,25 @@ void PlaybackInstance::UpdateST() { bool any_changed = false; if (speed > 0.0f && speed_changed.exchange(false)) { any_changed = true; - st->setRate(speed); + m_st->setRate(speed); set_signal(PlaybackSignalSpeedChanged); } if (tempo > 0.0f && tempo_changed.exchange(false)) { any_changed = true; - st->setTempo(tempo); + m_st->setTempo(tempo); set_signal(PlaybackSignalTempoChanged); } if (pitch > 0.0f && pitch_changed.exchange(false)) { any_changed = true; - st->setPitch(pitch); + m_st->setPitch(pitch); set_signal(PlaybackSignalPitchChanged); } if (any_changed && process != nullptr) { - process->set_rate(st->getInputOutputSampleRatio()); + process->set_rate(m_st->getInputOutputSampleRatio()); } } double PlaybackInstance::GetMaxSeconds() { - return std::max((double)(MaxSpeed * MaxTempo), st->getInputOutputSampleRatio()); + return std::max((double)(MaxSpeed * MaxTempo), m_st->getInputOutputSampleRatio()); } void PlaybackInstance::InitLoopFunction() { bool reload = false; @@ -232,7 +278,7 @@ void PlaybackInstance::InitLoopFunction() { desired.channels = 2; desired.callback = PlaybackInstance::SDLCallback; desired.userdata = this; - st = new SoundTouch(); + m_st = new SoundTouch(); #ifdef USE_SDL if ((device = SDL_OpenAudioDevice(nullptr, 0, &desired, &obtained, SDL_AUDIO_ALLOW_CHANNELS_CHANGE|SDL_AUDIO_ALLOW_FREQUENCY_CHANGE|SDL_AUDIO_ALLOW_SAMPLES_CHANGE)) == 0) { ERROR.writefln("Error opening audio device: '%s'", SDL_GetError()); @@ -289,8 +335,8 @@ Total samples: %u" #error Invalid configuration detected. #endif spec = obtained; - st->setSampleRate(spec.freq); - st->setChannels(spec.channels); + m_st->setSampleRate(spec.freq); + m_st->setChannels(spec.channels); UpdateST(); reload = false; @@ -302,9 +348,7 @@ Total samples: %u" flag_mutex.unlock(); playback_ready.store(false); length = 0.0; - memset(&backend_spec, 0, sizeof(backend_spec)); - if (sdl_stream) SDL_FreeAudioStream(sdl_stream); - sdl_stream = nullptr; + m_playback_data.clear(); bufsize = 0; if (buf) free((void*)buf); buf = nullptr; @@ -406,8 +450,8 @@ void PlaybackInstance::DeinitLoopFunction() { SDL_CloseAudioDevice(device); SDL_QuitSubSystem(SDL_INIT_AUDIO); #endif - delete st; - st = nullptr; + delete m_st; + m_st = nullptr; if (buf) free(buf); current_file_mutex.lock(); current_file = {}; @@ -430,7 +474,6 @@ PlaybackInstance::PlaybackInstance() { running = false; paused = true; position = 0; - sdl_stream = nullptr; length = 0; volume = 100.0; real_volume = 1.0; diff --git a/playback.h b/playback.h index 317d52b..87bb9f0 100644 --- a/playback.h +++ b/playback.h @@ -21,6 +21,8 @@ #include "playback_process.hpp" #include #include +#include "playlist.hpp" +#include "util.hpp" using namespace soundtouch; using std::span; using std::optional; @@ -272,12 +274,45 @@ private: double length; bool paused; Uint8* buf; + size_t tmpbuf_len; size_t bufsize; SDL_AudioDeviceID device; - SoundTouch *st; SDL_AudioSpec spec; - SDL_AudioSpec backend_spec; - SDL_AudioStream *sdl_stream = nullptr; + struct playback_data { + SDL_AudioStream *m_pStream = nullptr; + SDL_AudioSpec m_backend_spec = {0}; + PlaybackProcess *m_pProcess = nullptr; + SoundTouch *m_st = nullptr; + bool delete_needed = false; + inline bool fading() { + return m_fade_time >= 0.0; + } + double m_fade_time = -1.0; + inline bool valid() { + return m_st != nullptr && m_pStream != nullptr && m_pProcess != nullptr; + } + inline playback_data() {} + inline playback_data(SDL_AudioSpec backend_spec, SDL_AudioSpec spec, PlaybackProcess *process, double speed = 1.0, double tempo = 1.0, double pitch = 1.0) { + m_backend_spec = backend_spec; + m_pStream = SDL_NewAudioStream(backend_spec.format, backend_spec.channels, backend_spec.freq, spec.format, spec.channels, spec.freq); + if (m_pStream == nullptr) throw CustomException(fmt::format("Could not create stream: {}", SDL_GetError())); + m_st = new SoundTouch(); + m_st->setChannels(spec.channels); + m_st->setSampleRate(spec.freq); + m_st->setRate(speed); + m_st->setPitch(pitch); + m_st->setTempo(tempo); + } + inline ~playback_data() { + if (m_pStream != nullptr) SDL_FreeAudioStream(m_pStream); + delete m_pProcess; + delete m_st; + } + }; + Playlist playlist; + SoundTouch *m_st; + std::vector m_playback_data; + void CleanPlaybackData(); void SDLCallbackInner(Uint8 *stream, int len); static void SDLCallback(void *userdata, Uint8 *stream, int len); void Load(const char *file, int idx);