203 lines
5 KiB
C++
203 lines
5 KiB
C++
#include "Player.hpp"
|
|
#include "Buffer.hpp"
|
|
#include "Multithreading.hpp"
|
|
|
|
#include "portaudio.h"
|
|
|
|
class DeviceStream {
|
|
friend class Player;
|
|
|
|
public:
|
|
DeviceStream() {
|
|
auto paStatus = Pa_Initialize() ;
|
|
mInitializationStatus = paStatus == paNoError;
|
|
if (!mInitializationStatus) {
|
|
fprintf(stderr, "An error occurred while using the portaudio stream\n");
|
|
fprintf(stderr, "Error number: %d\n", paStatus);
|
|
fprintf(stderr, "Error message: %s\n", Pa_GetErrorText(paStatus));
|
|
}
|
|
}
|
|
|
|
~DeviceStream() {
|
|
if (mInitializationStatus) {
|
|
Pa_Terminate();
|
|
}
|
|
}
|
|
|
|
bool openStream(const tp::halnf* buffer, const tp::ualni* pointerMax, tp::ualni sampleRate) {
|
|
if (!mInitializationStatus) return false;
|
|
|
|
mPointerMax = pointerMax;
|
|
mBuffer = buffer;
|
|
|
|
PaStreamParameters outputParameters;
|
|
|
|
outputParameters.device = Pa_GetDefaultOutputDevice();
|
|
if (outputParameters.device == paNoDevice) {
|
|
return false;
|
|
}
|
|
|
|
const PaDeviceInfo* pInfo = Pa_GetDeviceInfo(outputParameters.device);
|
|
if (pInfo != 0) {
|
|
printf("Output device name: '%s'\r", pInfo->name);
|
|
}
|
|
|
|
outputParameters.channelCount = 2;
|
|
outputParameters.sampleFormat = paFloat32; /* 32 bit floating point output */
|
|
outputParameters.suggestedLatency = Pa_GetDeviceInfo(outputParameters.device)->defaultLowOutputLatency;
|
|
outputParameters.hostApiSpecificStreamInfo = NULL;
|
|
|
|
PaError err = Pa_OpenStream(&mPaStream, NULL, &outputParameters, sampleRate, 44100 * 0.2f, 0, &DeviceStream::paCallback, this);
|
|
|
|
if (err != paNoError) {
|
|
return false;
|
|
}
|
|
|
|
err = Pa_SetStreamFinishedCallback(mPaStream, &DeviceStream::paStreamFinished);
|
|
|
|
if (err != paNoError) {
|
|
Pa_CloseStream(mPaStream);
|
|
mPaStream = 0;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool closeStream() {
|
|
if (mPaStream == 0) return false;
|
|
PaError err = Pa_CloseStream(mPaStream);
|
|
|
|
mPaStream = 0;
|
|
mBuffer = nullptr;
|
|
mPointerMax = 0;
|
|
mPointer = 0;
|
|
|
|
return (err == paNoError);
|
|
}
|
|
|
|
bool continueStream() {
|
|
if (mPaStream == 0) return false;
|
|
PaError err = Pa_StartStream(mPaStream);
|
|
return (err == paNoError);
|
|
}
|
|
|
|
bool freezeStream() {
|
|
if (mPaStream == 0) return false;
|
|
PaError err = Pa_StopStream(mPaStream);
|
|
return (err == paNoError);
|
|
}
|
|
|
|
tp::ualni getCurrentFrame() const {
|
|
return mPointer;
|
|
}
|
|
|
|
void setCurrentFrame(tp::ualni ptr) {
|
|
ptr = tp::clamp(ptr, (tp::ualni) 0, *mPointerMax);
|
|
mMutex.lock();
|
|
mPointer = ptr;
|
|
mMutex.unlock();
|
|
}
|
|
|
|
private:
|
|
int paCallbackMethod(const void* inputBuffer, void* outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags) {
|
|
if (mPointer + framesPerBuffer >= *mPointerMax) return paContinue;
|
|
|
|
float* out = (float*) outputBuffer;
|
|
mMutex.lock();
|
|
for (unsigned long i = 0; i < framesPerBuffer; i++) {
|
|
*out++ = (mBuffer)[mPointer * 2] * mVolume;
|
|
*out++ = (mBuffer)[mPointer * 2 + 1] * mVolume;
|
|
mPointer++;
|
|
}
|
|
mMutex.unlock();
|
|
return paContinue;
|
|
}
|
|
|
|
void paStreamFinishedMethod() { printf("Stream Completed\n"); }
|
|
|
|
static int paCallback(const void* inputBuffer, void* outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void* userData) {
|
|
return ((DeviceStream*) userData)->paCallbackMethod(inputBuffer, outputBuffer, framesPerBuffer, timeInfo, statusFlags);
|
|
}
|
|
|
|
static void paStreamFinished(void* userData) { return ((DeviceStream*) userData)->paStreamFinishedMethod(); }
|
|
|
|
private:
|
|
bool mInitializationStatus = false;
|
|
PaStream* mPaStream = nullptr;
|
|
tp::Mutex mMutex;
|
|
|
|
const tp::halnf* mBuffer = nullptr;
|
|
const tp::ualni* mPointerMax = nullptr;
|
|
|
|
tp::ualni mPointer = 0;
|
|
tp::halnf mVolume = 1.f;
|
|
};
|
|
|
|
Player::Player() {
|
|
mDeviceStream = new DeviceStream();
|
|
}
|
|
|
|
Player::~Player() {
|
|
delete mDeviceStream;
|
|
}
|
|
|
|
bool Player::startStreamTrack(SongId id) {
|
|
mDeviceStream->closeStream();
|
|
|
|
if (!mMusicStream.openStream(id)) {
|
|
connected = 0;
|
|
return false;
|
|
}
|
|
|
|
if (!mDeviceStream->openStream(mMusicStream.getBuffer(), mMusicStream.getProgressLive(), mMusicStream.getRate())) {
|
|
connected = 0;
|
|
return false;
|
|
}
|
|
|
|
mMusicStream.continueStream();
|
|
|
|
connected = id;
|
|
return true;
|
|
}
|
|
|
|
SongId Player::getPlayingId() const {
|
|
return connected;
|
|
}
|
|
|
|
void Player::continuePlayback() {
|
|
if (!connected) return;
|
|
mDeviceStream->continueStream();
|
|
}
|
|
|
|
void Player::freezePlayback() {
|
|
if (!connected) return;
|
|
mDeviceStream->freezeStream();
|
|
}
|
|
|
|
tp::halnf Player::getPlaybackProgress() const {
|
|
if (!connected) return 0;
|
|
return (tp::halnf) mDeviceStream->getCurrentFrame() / (tp::halnf) mMusicStream.getFramesLength();
|
|
}
|
|
|
|
tp::halnf Player::getDurationSec() const { return 0; }
|
|
|
|
void Player::setPlaybackProgress(tp::halnf val) {
|
|
if (!connected) return;
|
|
mDeviceStream->setCurrentFrame(val * mMusicStream.getFramesLength());
|
|
}
|
|
|
|
void Player::setVolume(tp::halnf vol) {
|
|
if (!connected) return;
|
|
mDeviceStream->mVolume = vol;
|
|
}
|
|
|
|
tp::halnf Player::getLoadProgress() const {
|
|
if (!connected) return 0;
|
|
return (tp::halnf) mMusicStream.getFramesLoaded() / (tp::halnf) mMusicStream.getFramesLength();
|
|
}
|
|
|
|
tp::halnf Player::getVolume() {
|
|
if (!connected) return 0;
|
|
return mDeviceStream->mVolume;
|
|
}
|