Refactor!
This commit is contained in:
parent
cb09a6f5de
commit
cd451d1c2a
14 changed files with 13024 additions and 1920 deletions
|
|
@ -13,7 +13,7 @@ file(GLOB HEADERS "./public/*.hpp" "./public/*/*.hpp" "./applications/*.hpp")
|
||||||
|
|
||||||
add_library(${PROJECT_NAME} STATIC ${SOURCES} ${HEADERS})
|
add_library(${PROJECT_NAME} STATIC ${SOURCES} ${HEADERS})
|
||||||
|
|
||||||
target_include_directories(${PROJECT_NAME} PUBLIC ./public/ ${BINDINGS_INCLUDE})
|
target_include_directories(${PROJECT_NAME} PUBLIC ./public/ ${BINDINGS_INCLUDE} ./ext/)
|
||||||
target_link_libraries(${PROJECT_NAME} PUBLIC Graphics Connection)
|
target_link_libraries(${PROJECT_NAME} PUBLIC Graphics Connection)
|
||||||
target_link_libraries(${PROJECT_NAME} PUBLIC ${BINDINGS_LIBS})
|
target_link_libraries(${PROJECT_NAME} PUBLIC ${BINDINGS_LIBS})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,25 @@
|
||||||
|
|
||||||
#include "NewPlacement.hpp"
|
#include "NewPlacement.hpp"
|
||||||
#include "WavPlayer.hpp"
|
|
||||||
#include "LibraryGui.hpp"
|
|
||||||
|
|
||||||
// load mpre track info!!
|
#include "Player.hpp"
|
||||||
// display if track presents on fylesystem!!
|
#include "GUI.hpp"
|
||||||
// enable playback!!
|
|
||||||
// monitor resource usage
|
// 1) artworks
|
||||||
// sorting
|
// 2) how to easy add more songs?
|
||||||
|
// 3) GUi :
|
||||||
|
// - seeker
|
||||||
|
// - song idx
|
||||||
|
// - non existing highlight
|
||||||
|
// - prev next
|
||||||
|
// - remove debug gui
|
||||||
|
// 4) queue & repeat & shuffle...
|
||||||
|
// 5) new database with history
|
||||||
|
|
||||||
void runApp() {
|
void runApp() {
|
||||||
|
|
||||||
TrackPlayer player;
|
Player player;
|
||||||
|
Library library; library.loadJson(getHome() + "Library.json"); library.checkExisting();
|
||||||
Library library;
|
LibraryWidget<tp::Window::Events, tp::Graphics::Canvas> gui(&library, &player);
|
||||||
library.loadJson(getHome() + "Library.json");
|
|
||||||
|
|
||||||
auto gui = LibraryWidget<tp::Window::Events, tp::Graphics::Canvas>(&library, &player);
|
|
||||||
|
|
||||||
auto window = tp::Window::createWindow(800, 600, "Window 1");
|
auto window = tp::Window::createWindow(800, 600, "Window 1");
|
||||||
|
|
||||||
|
|
|
||||||
12536
LibraryViewer/ext/dr_flac.h
Normal file
12536
LibraryViewer/ext/dr_flac.h
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,8 +1,9 @@
|
||||||
|
|
||||||
#include "LibraryViewer.hpp"
|
#include "Library.hpp"
|
||||||
#include "LocalConnection.hpp"
|
#include "LocalConnection.hpp"
|
||||||
|
|
||||||
#include "picojson.h"
|
#include "picojson.h"
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
namespace tp {
|
namespace tp {
|
||||||
static ModuleManifest* deps[] = { &gModuleGraphics, &gModuleConnection, nullptr };
|
static ModuleManifest* deps[] = { &gModuleGraphics, &gModuleConnection, nullptr };
|
||||||
|
|
@ -28,6 +29,25 @@ tp::String getHome() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tp::String getSongLocalPath(SongId id) {
|
||||||
|
return getHome() + "tracks/" + tp::String((tp::alni)id);
|
||||||
|
}
|
||||||
|
|
||||||
|
SONG_FORMAT getSongFormat(const String& localPath) {
|
||||||
|
std::filesystem::path wavFormat((localPath + ".wav").read());
|
||||||
|
std::filesystem::path flacFormat((localPath + ".flac").read());
|
||||||
|
if (std::filesystem::exists(flacFormat)) return SONG_FORMAT::FLAC;
|
||||||
|
if (std::filesystem::exists(wavFormat)) return SONG_FORMAT::WAV;
|
||||||
|
return SONG_FORMAT::NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Library::checkExisting() {
|
||||||
|
for (auto track : mTraks) {
|
||||||
|
auto const path = getSongLocalPath(track->mId);
|
||||||
|
track->mExists = getSongFormat(path) != SONG_FORMAT::NONE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool Library::loadJson(const String& path) {
|
bool Library::loadJson(const String& path) {
|
||||||
LocalConnection libraryFile;
|
LocalConnection libraryFile;
|
||||||
Buffer<int1> libraryFileMem;
|
Buffer<int1> libraryFileMem;
|
||||||
203
LibraryViewer/private/Player.cpp
Normal file
203
LibraryViewer/private/Player.cpp
Normal file
|
|
@ -0,0 +1,203 @@
|
||||||
|
#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;
|
||||||
|
}
|
||||||
125
LibraryViewer/private/Streamer.cpp
Normal file
125
LibraryViewer/private/Streamer.cpp
Normal file
|
|
@ -0,0 +1,125 @@
|
||||||
|
#include "Streamer.hpp"
|
||||||
|
|
||||||
|
typedef u_int8_t uint8_t;
|
||||||
|
typedef u_int16_t uint16_t;
|
||||||
|
typedef u_int32_t uint32_t;
|
||||||
|
|
||||||
|
#define DR_FLAC_IMPLEMENTATION
|
||||||
|
#include "dr_flac.h"
|
||||||
|
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
class Context {
|
||||||
|
public:
|
||||||
|
Context() = default;
|
||||||
|
|
||||||
|
bool preloadInfo(const tp::String& path, MusicStream* owner) {
|
||||||
|
mOwner = owner;
|
||||||
|
|
||||||
|
pFlac = drflac_open_file((path + ".flac").read(), NULL);
|
||||||
|
|
||||||
|
if (pFlac == NULL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pFlac->channels != 2) {
|
||||||
|
drflac_close(pFlac);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
mOwner->mLengthFrames = pFlac->totalPCMFrameCount;
|
||||||
|
mOwner->mChanels = pFlac->channels;
|
||||||
|
mOwner->mRate = pFlac->sampleRate;
|
||||||
|
|
||||||
|
mOwner->mBuffer.reserve(mOwner->mLengthFrames * 2);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void runThread() {
|
||||||
|
if (threadRunning) return;
|
||||||
|
threadRunning = true;
|
||||||
|
threadObj = std::thread(&Context::threadWorkStatic, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void stop() {
|
||||||
|
if (threadRunning) {
|
||||||
|
exitThread = true;
|
||||||
|
threadObj.join();
|
||||||
|
threadRunning = false;
|
||||||
|
exitThread = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pFlac) drflac_close(pFlac);
|
||||||
|
pFlac = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
~Context() {
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::thread threadObj;
|
||||||
|
bool threadRunning = false;
|
||||||
|
bool exitThread = false;
|
||||||
|
|
||||||
|
void threadWork() {
|
||||||
|
int framesRead = 0;
|
||||||
|
while (!exitThread && (framesRead = drflac_read_pcm_frames_f32(pFlac, mOwner->mRate, mOwner->mBuffer.getBuff() + mOwner->mProgress * 2)) > 0) {
|
||||||
|
mOwner->mProgress += framesRead;
|
||||||
|
// drflac_seek_to_pcm_frame(pFlac, mOwner->mProgress);
|
||||||
|
// tp::sleep(500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void threadWorkStatic(void* p) { ((Context*) p)->threadWork(); }
|
||||||
|
|
||||||
|
public:
|
||||||
|
drflac* pFlac = nullptr;
|
||||||
|
MusicStream* mOwner;
|
||||||
|
};
|
||||||
|
|
||||||
|
MusicStream::MusicStream() {
|
||||||
|
mContext = new Context();
|
||||||
|
}
|
||||||
|
|
||||||
|
MusicStream::~MusicStream() {
|
||||||
|
delete mContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MusicStream::openStream(SongId id) {
|
||||||
|
closeStream();
|
||||||
|
auto const path = getSongLocalPath(id);
|
||||||
|
auto format = getSongFormat(path);
|
||||||
|
if (format != SONG_FORMAT::FLAC) return false;
|
||||||
|
return mContext->preloadInfo(path, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MusicStream::closeStream() {
|
||||||
|
mContext->stop();
|
||||||
|
mProgress = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
tp::halnf* MusicStream::getBuffer() {
|
||||||
|
return mBuffer.getBuff();
|
||||||
|
}
|
||||||
|
|
||||||
|
tp::ualni* MusicStream::getProgressLive() {
|
||||||
|
return &mProgress;
|
||||||
|
}
|
||||||
|
|
||||||
|
tp::uhalni MusicStream::getRate() const {
|
||||||
|
return mRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MusicStream::continueStream() {
|
||||||
|
mContext->runThread();
|
||||||
|
}
|
||||||
|
|
||||||
|
tp::ualni MusicStream::getFramesLength() const {
|
||||||
|
return mLengthFrames;
|
||||||
|
}
|
||||||
|
|
||||||
|
tp::ualni MusicStream::getFramesLoaded() const {
|
||||||
|
return mProgress;
|
||||||
|
}
|
||||||
|
|
@ -1,248 +0,0 @@
|
||||||
#include "WavPlayer.hpp"
|
|
||||||
|
|
||||||
#include <math.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
#include "portaudio.h"
|
|
||||||
#include "Buffer.hpp"
|
|
||||||
#include "Multithreading.hpp"
|
|
||||||
|
|
||||||
typedef u_int8_t uint8_t;
|
|
||||||
typedef u_int16_t uint16_t;
|
|
||||||
typedef u_int32_t uint32_t;
|
|
||||||
|
|
||||||
#include "wav_file_reader.h"
|
|
||||||
|
|
||||||
class Sine {
|
|
||||||
friend class MusicPlayerContext;
|
|
||||||
public:
|
|
||||||
Sine() {}
|
|
||||||
|
|
||||||
void setSampleRate(int aSampleRate) {
|
|
||||||
sampleRate = aSampleRate;
|
|
||||||
}
|
|
||||||
|
|
||||||
tp::Buffer<float>* geLeftBuffer() { return &leftBuffer; }
|
|
||||||
tp::Buffer<float>* geRightBuffer() { return &rightBuffer; }
|
|
||||||
|
|
||||||
bool open(PaDeviceIndex index) {
|
|
||||||
PaStreamParameters outputParameters;
|
|
||||||
|
|
||||||
outputParameters.device = index;
|
|
||||||
if (outputParameters.device == paNoDevice) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const PaDeviceInfo* pInfo = Pa_GetDeviceInfo(index);
|
|
||||||
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(&stream, NULL, &outputParameters, sampleRate, paFramesPerBufferUnspecified, paClipOff, &Sine::paCallback, this);
|
|
||||||
|
|
||||||
/* Failed to open stream to device !!! */
|
|
||||||
if (err != paNoError) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
err = Pa_SetStreamFinishedCallback(stream, &Sine::paStreamFinished);
|
|
||||||
|
|
||||||
if (err != paNoError) {
|
|
||||||
Pa_CloseStream(stream);
|
|
||||||
stream = 0;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool close() {
|
|
||||||
if (stream == 0) return false;
|
|
||||||
PaError err = Pa_CloseStream(stream);
|
|
||||||
stream = 0;
|
|
||||||
return (err == paNoError);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool start() {
|
|
||||||
if (stream == 0) return false;
|
|
||||||
PaError err = Pa_StartStream(stream);
|
|
||||||
return (err == paNoError);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool stop() {
|
|
||||||
if (stream == 0) return false;
|
|
||||||
PaError err = Pa_StopStream(stream);
|
|
||||||
return (err == paNoError);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
int paCallbackMethod(const void* inputBuffer, void* outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags) {
|
|
||||||
float* out = (float*) outputBuffer;
|
|
||||||
|
|
||||||
mMutex.lock();
|
|
||||||
for (unsigned long i = 0; i < framesPerBuffer; i++) {
|
|
||||||
*out++ = leftBuffer[leftSampleIdx];
|
|
||||||
*out++ = rightBuffer[rightSampleIdx];
|
|
||||||
|
|
||||||
leftSampleIdx += 1;
|
|
||||||
rightSampleIdx += 1;
|
|
||||||
|
|
||||||
if (leftSampleIdx >= leftBuffer.size())
|
|
||||||
leftSampleIdx -= 1;
|
|
||||||
if (rightSampleIdx >= rightBuffer.size())
|
|
||||||
rightSampleIdx -= 1;
|
|
||||||
}
|
|
||||||
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 ((Sine*) userData)->paCallbackMethod(inputBuffer, outputBuffer, framesPerBuffer, timeInfo, statusFlags);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void paStreamFinished(void* userData) { return ((Sine*) userData)->paStreamFinishedMethod(); }
|
|
||||||
|
|
||||||
PaStream* stream = 0;
|
|
||||||
|
|
||||||
tp::Buffer<float> leftBuffer;
|
|
||||||
tp::Buffer<float> rightBuffer;
|
|
||||||
|
|
||||||
int leftSampleIdx = 0;
|
|
||||||
int rightSampleIdx = 0;
|
|
||||||
|
|
||||||
int sampleRate = 44100;
|
|
||||||
|
|
||||||
tp::Mutex mMutex;
|
|
||||||
};
|
|
||||||
|
|
||||||
class MusicPlayerContext {
|
|
||||||
public:
|
|
||||||
MusicPlayerContext() {
|
|
||||||
initStatus = Pa_Initialize();
|
|
||||||
if (initStatus != paNoError) {
|
|
||||||
fprintf(stderr, "An error occurred while using the portaudio stream\n");
|
|
||||||
fprintf(stderr, "Error number: %d\n", initStatus);
|
|
||||||
fprintf(stderr, "Error message: %s\n", Pa_GetErrorText(initStatus));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool load(SongId id) {
|
|
||||||
if (connected) {
|
|
||||||
stop();
|
|
||||||
sine.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loadWav(id)) {
|
|
||||||
connected = sine.open(Pa_GetDefaultOutputDevice());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void stop() {
|
|
||||||
if (!connected) return;
|
|
||||||
sine.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
void start() {
|
|
||||||
if (!connected) return;
|
|
||||||
sine.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
~MusicPlayerContext() {
|
|
||||||
if (connected) {
|
|
||||||
stop();
|
|
||||||
sine.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (initStatus == paNoError) {
|
|
||||||
Pa_Terminate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool loadWav(SongId id) {
|
|
||||||
AudioFile<double> audioFile;
|
|
||||||
|
|
||||||
if (!audioFile.load(std::string(getHome().read()) + std::string("traks/") + std::to_string(id) + ".wav")) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
audioFile.printSummary();
|
|
||||||
|
|
||||||
sine.setSampleRate(audioFile.getSampleRate());
|
|
||||||
|
|
||||||
int numSamples = audioFile.getNumSamplesPerChannel();
|
|
||||||
|
|
||||||
auto left = sine.geLeftBuffer();
|
|
||||||
auto right = sine.geRightBuffer();
|
|
||||||
|
|
||||||
left->reserve(numSamples);
|
|
||||||
right->reserve(numSamples);
|
|
||||||
|
|
||||||
int channel = 0;
|
|
||||||
for (int i = 0; i < numSamples; i++) {
|
|
||||||
left->getBuff()[i] = audioFile.samples[0][i];
|
|
||||||
right->getBuff()[i] = audioFile.samples[1][i];
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
float getProgress() const {
|
|
||||||
return float(sine.leftSampleIdx) / sine.leftBuffer.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
void setProgress(float newProgress) {
|
|
||||||
newProgress = tp::clamp(newProgress, 0.f, 1.f);
|
|
||||||
sine.mMutex.lock();
|
|
||||||
sine.leftSampleIdx = sine.leftBuffer.size() * newProgress;
|
|
||||||
sine.rightSampleIdx = sine.leftBuffer.size() * newProgress;
|
|
||||||
sine.mMutex.unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
Sine sine;
|
|
||||||
PaError initStatus;
|
|
||||||
bool connected = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
TrackPlayer::TrackPlayer() {
|
|
||||||
mContext = new MusicPlayerContext();
|
|
||||||
}
|
|
||||||
|
|
||||||
TrackPlayer::~TrackPlayer() {
|
|
||||||
delete mContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TrackPlayer::startStreamTrack(SongId id) {
|
|
||||||
return mContext->load(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TrackPlayer::playSong() {
|
|
||||||
mContext->start();
|
|
||||||
}
|
|
||||||
|
|
||||||
void TrackPlayer::stopSong() {
|
|
||||||
mContext->stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
void TrackPlayer::setVolume(float volume) {}
|
|
||||||
|
|
||||||
float TrackPlayer::getProgress() const {
|
|
||||||
return mContext->getProgress();
|
|
||||||
}
|
|
||||||
|
|
||||||
float TrackPlayer::getDurationSec() const { return 0; }
|
|
||||||
|
|
||||||
void TrackPlayer::setProgress(float newProgress) {
|
|
||||||
mContext->setProgress(newProgress);
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "LibraryViewer.hpp"
|
#include "Library.hpp"
|
||||||
#include "WavPlayer.hpp"
|
#include "Player.hpp"
|
||||||
#include "Rect.hpp"
|
#include "Rect.hpp"
|
||||||
#include "Animations.hpp"
|
#include "Animations.hpp"
|
||||||
#include "imgui.h"
|
#include "imgui.h"
|
||||||
|
|
@ -96,25 +96,34 @@ public:
|
||||||
void RenderUI() {
|
void RenderUI() {
|
||||||
ImGui::Begin("InfoWindow", 0, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoResize);
|
ImGui::Begin("InfoWindow", 0, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoResize);
|
||||||
|
|
||||||
if (mTrack && ImGui::Button("load")) {
|
if (mTrack) {
|
||||||
mLoadStatus = mPlayer->startStreamTrack(mTrack->mId);
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::SameLine(); ImGui::Text(" Load Status : %s", mLoadStatus ? "Loaded" : "Not Loaded");
|
ImGui::Text(" Load Status : %s", mLoadStatus ? "Loaded" : "Not Loaded");
|
||||||
|
|
||||||
if (mTrack && mLoadStatus) {
|
if (ImGui::Button("Play")) {
|
||||||
auto songProgress = mPlayer->getProgress();
|
if (mPlayer->getPlayingId() != mTrack->mId) {
|
||||||
ImGui::SliderFloat("Progress", &songProgress, 0.f, 1.f);
|
mLoadStatus = mPlayer->startStreamTrack(mTrack->mId);
|
||||||
mPlayer->setProgress(songProgress);
|
}
|
||||||
}
|
mPlayer->continuePlayback();
|
||||||
|
}
|
||||||
|
|
||||||
if (ImGui::Button("Play")) {
|
if (mLoadStatus) {
|
||||||
mPlayer->playSong();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
if (ImGui::Button("Stop")) {
|
if (ImGui::Button("Stop")) {
|
||||||
mPlayer->stopSong();
|
mPlayer->freezePlayback();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto songProgress = mPlayer->getPlaybackProgress();
|
||||||
|
auto vol = mPlayer->getVolume();
|
||||||
|
ImGui::Text("Load Progress : %f", mPlayer->getLoadProgress());
|
||||||
|
if (ImGui::SliderFloat("Progress", &songProgress, 0.f, 1.f)) {
|
||||||
|
mPlayer->setPlaybackProgress(songProgress);
|
||||||
|
}
|
||||||
|
if (ImGui::SliderFloat("Volume", &vol, 0.f, 1.f)) {
|
||||||
|
mPlayer->setVolume(vol);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::Text("Song Info:");
|
ImGui::Text("Song Info:");
|
||||||
|
|
@ -138,6 +147,10 @@ public:
|
||||||
isSongFilterChanged |= true;
|
isSongFilterChanged |= true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ImGui::SliderInt("Existing only", &filterExisting, 0, 3)) {
|
||||||
|
isSongFilterChanged |= true;
|
||||||
|
}
|
||||||
|
|
||||||
isSongFilterChanged |= songFilter.Draw("Song Filter");
|
isSongFilterChanged |= songFilter.Draw("Song Filter");
|
||||||
|
|
||||||
sortFilter.Draw("Sorting Type");
|
sortFilter.Draw("Sorting Type");
|
||||||
|
|
@ -170,9 +183,10 @@ public:
|
||||||
tp::Buffer<SortType> items;
|
tp::Buffer<SortType> items;
|
||||||
const Track* mTrack;
|
const Track* mTrack;
|
||||||
bool filterLoved = false;
|
bool filterLoved = false;
|
||||||
TrackPlayer* mPlayer = nullptr;
|
Player* mPlayer = nullptr;
|
||||||
bool mLoadStatus = false;
|
bool mLoadStatus = false;
|
||||||
bool isSongFilterChanged = true;
|
bool isSongFilterChanged = true;
|
||||||
|
int filterExisting = 0; // all existing no-existing
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -300,7 +314,7 @@ public:
|
||||||
template <typename Events, typename Canvas> requires(DrawableConcept<Canvas>)
|
template <typename Events, typename Canvas> requires(DrawableConcept<Canvas>)
|
||||||
class LibraryWidget {
|
class LibraryWidget {
|
||||||
public:
|
public:
|
||||||
LibraryWidget(Library* lib, TrackPlayer* player) : mLibrary(lib), mPlayer(player) {
|
LibraryWidget(Library* lib, Player* player) : mLibrary(lib), mPlayer(player) {
|
||||||
for (auto track : mLibrary->mTraks) {
|
for (auto track : mLibrary->mTraks) {
|
||||||
mTraks.append(TrackWidget<Events, Canvas>(&track.data()));
|
mTraks.append(TrackWidget<Events, Canvas>(&track.data()));
|
||||||
}
|
}
|
||||||
|
|
@ -358,7 +372,9 @@ public:
|
||||||
auto idx = 0;
|
auto idx = 0;
|
||||||
|
|
||||||
for (auto track : mTraksFiltered) {
|
for (auto track : mTraksFiltered) {
|
||||||
track->draw(canvas, area, { area.x + 10, area.y + 10 + idx * trackSize - offset, area.z - 20 - scrollSize, trackSize - 5 } );
|
auto const trackArea = tp::RectF{ area.x + 10, area.y + 10 + idx * trackSize - offset, area.z - 20 - scrollSize, trackSize - 5 };
|
||||||
|
if (track->mTrack == mCurrentTrack.mTrack) canvas.rect(trackArea, { 0.2f, 0.2f, 0.2f, 0.5f }, 5);
|
||||||
|
track->draw(canvas, area, trackArea);
|
||||||
idx++;
|
idx++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -390,6 +406,16 @@ public:
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch (mCurrentTrackInfo.filterExisting) {
|
||||||
|
case 1:
|
||||||
|
if (!track->mTrack->mExists) continue;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
if (track->mTrack->mExists) continue;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
mTraksFiltered.append( &track.data() );
|
mTraksFiltered.append( &track.data() );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -419,7 +445,7 @@ private:
|
||||||
ResizerWidget<Events, Canvas> mResizeWidget;
|
ResizerWidget<Events, Canvas> mResizeWidget;
|
||||||
|
|
||||||
Library* mLibrary;
|
Library* mLibrary;
|
||||||
TrackPlayer* mPlayer;
|
Player* mPlayer;
|
||||||
|
|
||||||
bool mNeedRedraw = false;
|
bool mNeedRedraw = false;
|
||||||
};
|
};
|
||||||
|
|
@ -2,7 +2,12 @@
|
||||||
|
|
||||||
#include "Window.hpp"
|
#include "Window.hpp"
|
||||||
|
|
||||||
|
typedef tp::ualni SongId;
|
||||||
|
enum class SONG_FORMAT { WAV, FLAC, NONE };
|
||||||
|
|
||||||
tp::String getHome();
|
tp::String getHome();
|
||||||
|
tp::String getSongLocalPath(SongId id);
|
||||||
|
SONG_FORMAT getSongFormat(const tp::String& path);
|
||||||
|
|
||||||
namespace tp {
|
namespace tp {
|
||||||
extern ModuleManifest gModuleLibraryViewer;
|
extern ModuleManifest gModuleLibraryViewer;
|
||||||
|
|
@ -13,7 +18,7 @@ public:
|
||||||
Track() = default;
|
Track() = default;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
tp::ualni mId = 0;
|
SongId mId = 0;
|
||||||
tp::String mName = "undef";
|
tp::String mName = "undef";
|
||||||
tp::String mArtist = "undef";
|
tp::String mArtist = "undef";
|
||||||
tp::String mAlbumArtist = "undef";
|
tp::String mAlbumArtist = "undef";
|
||||||
|
|
@ -36,20 +41,7 @@ public:
|
||||||
bool mLoved = false;
|
bool mLoved = false;
|
||||||
bool mAlbumLoved = false;
|
bool mAlbumLoved = false;
|
||||||
bool mExplicit = false;
|
bool mExplicit = false;
|
||||||
// tp::String mKind = "undef";
|
bool mExists = false;
|
||||||
// tp::ualni mDiscNumber = 0;
|
|
||||||
// tp::ualni mDiscCount = 0;
|
|
||||||
// tp::ualni mTrackNumber = 0;
|
|
||||||
// tp::ualni mTrackCount = 0;
|
|
||||||
// tp::ualni mBitRate = 0;
|
|
||||||
// tp::ualni mSampleRate = 0;
|
|
||||||
// tp::ualni Compilation
|
|
||||||
// tp::ualni Artwork Count
|
|
||||||
// tp::ualni Sort Album
|
|
||||||
// tp::ualni Sort Artist
|
|
||||||
// tp::ualni Sort Name
|
|
||||||
// tp::ualni Persistent ID
|
|
||||||
// tp::ualni Track Type
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class Library {
|
class Library {
|
||||||
|
|
@ -58,11 +50,8 @@ public:
|
||||||
|
|
||||||
public:
|
public:
|
||||||
bool loadJson(const tp::String& path);
|
bool loadJson(const tp::String& path);
|
||||||
|
void checkExisting();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
tp::Buffer<Track> mTraks;
|
tp::Buffer<Track> mTraks;
|
||||||
};
|
};
|
||||||
|
|
||||||
class LibraryViewverGui {};
|
|
||||||
|
|
||||||
void runApp();
|
|
||||||
32
LibraryViewer/public/Player.hpp
Normal file
32
LibraryViewer/public/Player.hpp
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Library.hpp"
|
||||||
|
#include "Streamer.hpp"
|
||||||
|
|
||||||
|
class DeviceStream;
|
||||||
|
|
||||||
|
class Player {
|
||||||
|
public:
|
||||||
|
Player();
|
||||||
|
~Player();
|
||||||
|
|
||||||
|
bool startStreamTrack(SongId id);
|
||||||
|
SongId getPlayingId() const;
|
||||||
|
void continuePlayback();
|
||||||
|
void freezePlayback();
|
||||||
|
|
||||||
|
tp::halnf getDurationSec() const;
|
||||||
|
|
||||||
|
void setVolume(tp::halnf volume);
|
||||||
|
tp::halnf getVolume();
|
||||||
|
|
||||||
|
tp::halnf getPlaybackProgress() const;
|
||||||
|
void setPlaybackProgress(tp::halnf newProgress);
|
||||||
|
|
||||||
|
tp::halnf getLoadProgress() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
SongId connected = 0;
|
||||||
|
MusicStream mMusicStream;
|
||||||
|
DeviceStream* mDeviceStream;
|
||||||
|
};
|
||||||
34
LibraryViewer/public/Streamer.hpp
Normal file
34
LibraryViewer/public/Streamer.hpp
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Library.hpp"
|
||||||
|
|
||||||
|
class Context;
|
||||||
|
|
||||||
|
class MusicStream {
|
||||||
|
friend Context;
|
||||||
|
|
||||||
|
public:
|
||||||
|
MusicStream();
|
||||||
|
~MusicStream();
|
||||||
|
|
||||||
|
bool openStream(SongId id);
|
||||||
|
void closeStream();
|
||||||
|
|
||||||
|
tp::halnf* getBuffer();
|
||||||
|
tp::ualni* getProgressLive();
|
||||||
|
tp::uhalni getRate() const;
|
||||||
|
|
||||||
|
void continueStream();
|
||||||
|
tp::ualni getFramesLength() const;
|
||||||
|
tp::ualni getFramesLoaded() const;
|
||||||
|
|
||||||
|
public:
|
||||||
|
class Context* mContext = nullptr;
|
||||||
|
// tp::Mutex mMutex;
|
||||||
|
|
||||||
|
tp::ualni mLengthFrames = 0;
|
||||||
|
tp::ualni mChanels = 0;
|
||||||
|
tp::ualni mRate = 0;
|
||||||
|
tp::ualni mProgress;
|
||||||
|
tp::Buffer<tp::halnf> mBuffer;
|
||||||
|
};
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "LibraryViewer.hpp"
|
|
||||||
|
|
||||||
class MusicPlayerContext;
|
|
||||||
typedef tp::ualni SongId;
|
|
||||||
|
|
||||||
class TrackPlayer {
|
|
||||||
public:
|
|
||||||
TrackPlayer();
|
|
||||||
~TrackPlayer();
|
|
||||||
|
|
||||||
bool startStreamTrack(SongId id);
|
|
||||||
|
|
||||||
void playSong();
|
|
||||||
|
|
||||||
void stopSong();
|
|
||||||
|
|
||||||
void setVolume(float volume);
|
|
||||||
|
|
||||||
float getProgress() const;
|
|
||||||
|
|
||||||
float getDurationSec() const;
|
|
||||||
|
|
||||||
void setProgress(float newProgress);
|
|
||||||
|
|
||||||
private:
|
|
||||||
MusicPlayerContext* mContext;
|
|
||||||
};
|
|
||||||
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue