Files
Hatsune-Miku-s-Shoshitsu-in…/modules/musicPlayer.cpp
T
NanamiAdmin 7edba3fbc0 feat: make everything threads-safe.
feat: complete `uninstall.sh` logic to make everything synced.
	feat: complete basic play logic to handle background, music,
	lyric play.
2026-08-30 15:13:28 +08:00

117 lines
3.7 KiB
C++

// musicPlayer.cpp
//
// miniaudio-backed playback of mp3 / wav / flac files. See musicPlayer.h.
//
// MINIAUDIO_IMPLEMENTATION is defined in this one translation unit so the
// large miniaudio implementation is compiled exactly once per binary.
#define MINIAUDIO_IMPLEMENTATION
#include "miniaudio.h"
#include "musicPlayer.h"
#include <atomic>
#include <cstring>
#include <string>
// When built with ALSA support, silence ALSA's own error printing: with no
// sound card present it dumps a lot of "cannot find card '0'" chatter to
// stderr, which would corrupt the TUI during playback.
#ifdef HMS_HAS_ALSA
#include <alsa/asoundlib.h>
#endif
namespace mp {
namespace {
#ifdef HMS_HAS_ALSA
void alsaErrorHandler(const char*, int, const char*, int, const char*, ...) {
// swallow ALSA's verbose configuration/device errors
}
#endif
struct PlaybackUser {
ma_decoder* decoder;
const std::atomic<bool>* stop;
std::atomic<bool> ended{false};
};
void dataCallback(ma_device* device, void* out, const void* /*in*/,
ma_uint32 frameCount) {
auto* user = static_cast<PlaybackUser*>(device->pUserData);
ma_decoder* decoder = user->decoder;
const size_t bytesPerFrame =
ma_get_bytes_per_frame(decoder->outputFormat, decoder->outputChannels);
if (user->stop && user->stop->load()) {
// Stop requested: feed silence and let the main loop tear the device
// down.
memset(out, 0, static_cast<size_t>(frameCount) * bytesPerFrame);
user->ended.store(true);
return;
}
ma_uint64 framesRead = 0;
const ma_result result =
ma_decoder_read_pcm_frames(decoder, out, frameCount, &framesRead);
if (framesRead > 0 && framesRead < frameCount) {
// Underrun / near EOF: zero-pad the rest of the buffer.
memset(static_cast<char*>(out) + framesRead * bytesPerFrame, 0,
static_cast<size_t>(frameCount - framesRead) * bytesPerFrame);
} else if (framesRead == 0) {
memset(out, 0, static_cast<size_t>(frameCount) * bytesPerFrame);
}
// A short read means the file is exhausted (MA_AT_END); any other
// non-success result is a decode error. Both end the playback loop.
if (framesRead < frameCount || (result != MA_SUCCESS && result != MA_AT_END)) {
user->ended.store(true);
}
}
} // namespace
int play(const std::string& path, Timeline* timeline, std::atomic<bool>* stop) {
if (path.empty()) return 1;
// Audio is the show's clock: anchor the timeline the moment playback is
// about to begin (a no-op if the caller already anchored it).
if (timeline && !timeline->started()) timeline->start();
ma_decoder_config decoderConfig = ma_decoder_config_init_default();
ma_decoder decoder;
if (ma_decoder_init_file(path.c_str(), &decoderConfig, &decoder) != MA_SUCCESS) {
return 2; // cannot open / no decoder for the format
}
#ifdef HMS_HAS_ALSA
snd_lib_error_set_handler(alsaErrorHandler);
#endif
ma_device_config deviceConfig = ma_device_config_init(ma_device_type_playback);
deviceConfig.playback.format = decoder.outputFormat;
deviceConfig.playback.channels = decoder.outputChannels;
deviceConfig.sampleRate = decoder.outputSampleRate;
deviceConfig.dataCallback = dataCallback;
PlaybackUser user{&decoder, stop};
deviceConfig.pUserData = &user;
ma_device device;
if (ma_device_init(nullptr, &deviceConfig, &device) != MA_SUCCESS) {
ma_decoder_uninit(&decoder);
return 3; // no audio backend / device available
}
ma_device_start(&device);
while (!user.ended.load() && !(stop && stop->load())) {
ma_sleep(50);
}
ma_device_uninit(&device);
ma_decoder_uninit(&decoder);
return 0;
}
} // namespace mp