diff --git a/Graphics/private/bindings/Window.cpp b/Graphics/private/bindings/Window.cpp index b2aa797..559342d 100644 --- a/Graphics/private/bindings/Window.cpp +++ b/Graphics/private/bindings/Window.cpp @@ -147,6 +147,7 @@ void Window::destroyWindow(Window* window) { bool Window::shouldClose() const { return glfwWindowShouldClose(mContext->window); } void Window::processEvents() { + mContext->inputManager.isEvent = false; glfwPollEvents(); int w, h; @@ -161,6 +162,7 @@ void Window::processEvents() { mGraphics.proc(); mContext->inputManager.update(); + get_time(); } @@ -188,4 +190,8 @@ bool Window::Events::isDown() const { halnf Window::Events::getScrollY() const { return mContext->inputManager.getScrollY(); +} + +bool Window::Events::isEvent() const { + return mContext->inputManager.isEvents(); } \ No newline at end of file diff --git a/Graphics/private/bindings/WindowContext.hpp b/Graphics/private/bindings/WindowContext.hpp index ed8bcae..77623ae 100644 --- a/Graphics/private/bindings/WindowContext.hpp +++ b/Graphics/private/bindings/WindowContext.hpp @@ -40,6 +40,10 @@ namespace tp { return mouseButtonStates[GLFW_MOUSE_BUTTON_LEFT] == GLFW_REPEAT || mouseButtonStates[GLFW_MOUSE_BUTTON_LEFT] == GLFW_PRESS; } + bool isEvents() const { + return isEvent; + } + double getScrollY() const { return scrollY; } // bool isScrollDown() const { return scrollY < 0.0; } @@ -57,6 +61,7 @@ namespace tp { } else if (action == GLFW_RELEASE) { inputManager->keyStates[key] = GLFW_RELEASE; } + inputManager->isEvent = true; } } @@ -66,6 +71,7 @@ namespace tp { if (inputManager) { // Update mouse button state inputManager->mouseButtonStates[button] = action; + inputManager->isEvent = true; } } @@ -76,13 +82,19 @@ namespace tp { // Update scroll state inputManager->scrollX = xoffset; inputManager->scrollY = yoffset; + inputManager->isEvent = true; } } void update() { double x, y; glfwGetCursorPos(window, &x, &y); + mousePosPrev = mousePos; mousePos = { x, y }; + + if ((mousePosPrev - mousePos).length2()) { + isEvent = true; + } } void resetScroll() { @@ -91,10 +103,12 @@ namespace tp { mouseButtonStates[GLFW_MOUSE_BUTTON_LEFT] = GLFW_REPEAT; } + bool isEvent = false; double scrollX = 0.0; double scrollY = 0.0; GLFWwindow* window; Vec2F mousePos; + Vec2F mousePosPrev; std::array keyStates = { GLFW_RELEASE }; std::array mouseButtonStates = { GLFW_RELEASE }; }; diff --git a/Graphics/public/Window.hpp b/Graphics/public/Window.hpp index 9de590f..477c95f 100644 --- a/Graphics/public/Window.hpp +++ b/Graphics/public/Window.hpp @@ -26,6 +26,7 @@ namespace tp { bool isPressed() const; bool isDown() const; halnf getScrollY() const; + bool isEvent() const; private: friend Window; diff --git a/LibraryViewer/applications/Entry.cpp b/LibraryViewer/applications/Entry.cpp index 8c6afe4..acf9258 100644 --- a/LibraryViewer/applications/Entry.cpp +++ b/LibraryViewer/applications/Entry.cpp @@ -3,6 +3,9 @@ #include "WavPlayer.hpp" #include "LibraryGui.hpp" +// load mpre track info!! +// display if track presents on fylesystem!! +// enable playback!! // monitor resource usage // sorting @@ -10,13 +13,10 @@ void runApp() { TrackPlayer player; - player.startStreamTrack(0); - player.playSong(); - Library library; - library.loadJson("library/Library.json"); + library.loadJson(getHome() + "Library.json"); - auto gui = LibraryWidget(&library); + auto gui = LibraryWidget(&library, &player); auto window = tp::Window::createWindow(800, 600, "Window 1"); @@ -29,6 +29,8 @@ void runApp() { gui.proc(window->getEvents(), {}, { area.x, area.y, area.z, area.w }); gui.draw(window->getCanvas(), {}, { area.x, area.y, area.z, area.w }); + // tp::sleep(100); + window->draw(); } } diff --git a/LibraryViewer/library/Example.wav b/LibraryViewer/library/traks/2437.wav similarity index 100% rename from LibraryViewer/library/Example.wav rename to LibraryViewer/library/traks/2437.wav diff --git a/LibraryViewer/private/LibraryViewer.cpp b/LibraryViewer/private/LibraryViewer.cpp index 2030141..96b84d2 100644 --- a/LibraryViewer/private/LibraryViewer.cpp +++ b/LibraryViewer/private/LibraryViewer.cpp @@ -11,6 +11,23 @@ namespace tp { using namespace tp; +#define AS_STR (int1*) trackProperty.second.to_str().c_str() +#define AS_INT std::stoll((int1*) trackProperty.second.to_str().c_str()) +#define AS_BOOL trackProperty.second.evaluate_as_boolean() +#define PROP(name) trackProperty.first == #name + +tp::String getHome() { + const char* envVarName = "LIB_VIEW_HOME"; + char* envVarValue = std::getenv(envVarName); + if (envVarValue != nullptr) { + tp::String out; + out = (int1*) (std::string(envVarValue) + "/").c_str(); + return out; + } else { + return ""; + } +} + bool Library::loadJson(const String& path) { LocalConnection libraryFile; Buffer libraryFileMem; @@ -34,12 +51,29 @@ bool Library::loadJson(const String& path) { auto & track = trackNode.get(); for (auto & trackProperty : track) { - if (trackProperty.first == "Name") { - newTrack.mName = (int1*) trackProperty.second.to_str().c_str(); - } - else if (trackProperty.first == "Artist") { - newTrack.mArtist = (int1*) trackProperty.second.to_str().c_str(); - } + if (PROP(Name)) newTrack.mName = AS_STR; + else if (PROP(Artist)) newTrack.mArtist = AS_STR; + else if (PROP(Track ID)) newTrack.mId = AS_INT; + else if (PROP(Album Artist)) newTrack.mAlbumArtist = AS_STR; + else if (PROP(Composer)) newTrack.mComposer = AS_STR; + else if (PROP(Album)) newTrack.mAlbum = AS_STR; + else if (PROP(Genre)) newTrack.mGenre = AS_STR; + else if (PROP(Size)) newTrack.mSize = AS_INT; + else if (PROP(Total Time)) newTrack.mTotalTime = AS_INT; + else if (PROP(Year)) newTrack.mYear = AS_INT; + else if (PROP(Date Modified)) newTrack.mDateModified = AS_STR; + else if (PROP(Date Added)) newTrack.mDateAdded = AS_STR; + else if (PROP(Play Count)) newTrack.mPlayCount = AS_INT; + else if (PROP(Play Date)) newTrack.mPlayDate = AS_STR; + else if (PROP(Play Date UTC)) newTrack.mPlayDateUTC = AS_STR; + else if (PROP(Skip Count)) newTrack.mSkipCount = AS_INT; + else if (PROP(Skip Date)) newTrack.mSkipDate = AS_STR; + else if (PROP(Release Date)) newTrack.mReleaseDate = AS_STR; + else if (PROP(Album Rating)) newTrack.mAlbumRating = AS_INT; + else if (PROP(Album Rating Computed)) newTrack.mAlbumRatingComputed = AS_BOOL; + else if (PROP(Loved)) newTrack.mLoved = AS_BOOL; + else if (PROP(Album Loved)) newTrack.mAlbumLoved = AS_BOOL; + else if (PROP(Explicit)) newTrack.mExplicit = AS_BOOL; } mTraks.append(newTrack); diff --git a/LibraryViewer/private/WavPlayer.cpp b/LibraryViewer/private/WavPlayer.cpp index 136a678..f7f58f4 100644 --- a/LibraryViewer/private/WavPlayer.cpp +++ b/LibraryViewer/private/WavPlayer.cpp @@ -5,6 +5,7 @@ #include "portaudio.h" #include "Buffer.hpp" +#include "Multithreading.hpp" typedef u_int8_t uint8_t; typedef u_int16_t uint16_t; @@ -13,6 +14,7 @@ typedef u_int32_t uint32_t; #include "wav_file_reader.h" class Sine { + friend class MusicPlayerContext; public: Sine() {} @@ -82,6 +84,7 @@ 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]; @@ -94,6 +97,7 @@ private: if (rightSampleIdx >= rightBuffer.size()) rightSampleIdx -= 1; } + mMutex.unlock(); return paContinue; } @@ -115,6 +119,8 @@ private: int rightSampleIdx = 0; int sampleRate = 44100; + + tp::Mutex mMutex; }; class MusicPlayerContext { @@ -128,14 +134,18 @@ public: } } - void load() { + bool load(SongId id) { if (connected) { stop(); sine.close(); } - exampleLoad(); - connected = sine.open(Pa_GetDefaultOutputDevice()); + if (loadWav(id)) { + connected = sine.open(Pa_GetDefaultOutputDevice()); + return true; + } + + return false; } void stop() { @@ -159,9 +169,12 @@ public: } } - void exampleLoad() { + bool loadWav(SongId id) { AudioFile audioFile; - audioFile.load("library/Example.wav"); + + if (!audioFile.load(std::string(getHome().read()) + std::string("traks/") + std::to_string(id) + ".wav")) { + return false; + } audioFile.printSummary(); @@ -180,6 +193,20 @@ public: 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: @@ -196,8 +223,8 @@ TrackPlayer::~TrackPlayer() { delete mContext; } -void TrackPlayer::startStreamTrack(SongId id) { - mContext->load(); +bool TrackPlayer::startStreamTrack(SongId id) { + return mContext->load(id); } void TrackPlayer::playSong() { @@ -210,8 +237,12 @@ void TrackPlayer::stopSong() { void TrackPlayer::setVolume(float volume) {} -float TrackPlayer::getProgress() const { return 0; } +float TrackPlayer::getProgress() const { + return mContext->getProgress(); +} float TrackPlayer::getDurationSec() const { return 0; } -void TrackPlayer::setProgress(float newProgress) {} \ No newline at end of file +void TrackPlayer::setProgress(float newProgress) { + mContext->setProgress(newProgress); +} \ No newline at end of file diff --git a/LibraryViewer/public/LibraryGui.hpp b/LibraryViewer/public/LibraryGui.hpp index 9537db5..f654d11 100644 --- a/LibraryViewer/public/LibraryGui.hpp +++ b/LibraryViewer/public/LibraryGui.hpp @@ -1,6 +1,7 @@ #pragma once #include "LibraryViewer.hpp" +#include "WavPlayer.hpp" #include "Rect.hpp" #include "Animations.hpp" #include "imgui.h" @@ -94,10 +95,38 @@ public: void RenderUI() { ImGui::Begin("InfoWindow", 0, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoResize); + if (mTrack && ImGui::Button("load")) { + mLoadStatus = mPlayer->startStreamTrack(mTrack->mId); + } + + ImGui::SameLine(); ImGui::Text(" Load Status : %s", mLoadStatus ? "Loaded" : "Not Loaded"); + + if (mTrack && mLoadStatus) { + auto songProgress = mPlayer->getProgress(); + ImGui::SliderFloat("Progress", &songProgress, 0.f, 1.f); + mPlayer->setProgress(songProgress); + } + + if (ImGui::Button("Play")) { + mPlayer->playSong(); + } + + ImGui::SameLine(); + if (ImGui::Button("Stop")) { + mPlayer->stopSong(); + } + ImGui::Text("Song Info:"); if (mTrack) { ImGui::Text("Name : %s", mTrack->mName.read()); ImGui::Text("Author : %s", mTrack->mArtist.read()); + ImGui::Text("Love : %s", mTrack->mLoved ? "true" : "false"); + ImGui::Text("Id : %lli", mTrack->mId); + ImGui::Text("Total Time : %lli", mTrack->mTotalTime); + ImGui::Text("Play Count : %lli", mTrack->mPlayCount); + ImGui::Text("Skip Count : %lli", mTrack->mSkipCount); + ImGui::Text("Date Added : %s", mTrack->mDateAdded.read()); + ImGui::Text("Albom : %s", mTrack->mAlbum.read()); } else { ImGui::Text("Not Selected"); } @@ -137,6 +166,8 @@ public: tp::Buffer items; const Track* mTrack; bool filterLoved = false; + TrackPlayer* mPlayer = nullptr; + bool mLoadStatus = false; }; @@ -261,14 +292,18 @@ public: template requires(DrawableConcept) class LibraryWidget { public: - LibraryWidget(Library* lib) : mLibrary(lib) { + LibraryWidget(Library* lib, TrackPlayer* player) : mLibrary(lib), mPlayer(player) { for (auto track : mLibrary->mTraks) { mTraks.append(TrackWidget(&track.data())); } mResizeWidget.value = 1000; + mCurrentTrackInfo.mPlayer = mPlayer; } void proc(const Events& events, const tp::RectF& areaParent, const tp::RectF& aArea) { + mNeedRedraw = events.isEvent(); + if (!mNeedRedraw) return; + mResizeWidget.max = aArea.z - trackInfoSize; mResizeWidget.min = 100; @@ -353,4 +388,7 @@ private: ResizerWidget mResizeWidget; Library* mLibrary; + TrackPlayer* mPlayer; + + bool mNeedRedraw = false; }; \ No newline at end of file diff --git a/LibraryViewer/public/LibraryViewer.hpp b/LibraryViewer/public/LibraryViewer.hpp index 9887a0b..d88eeea 100644 --- a/LibraryViewer/public/LibraryViewer.hpp +++ b/LibraryViewer/public/LibraryViewer.hpp @@ -2,6 +2,8 @@ #include "Window.hpp" +tp::String getHome(); + namespace tp { extern ModuleManifest gModuleLibraryViewer; } @@ -11,8 +13,43 @@ public: Track() = default; public: + tp::ualni mId = 0; tp::String mName = "undef"; tp::String mArtist = "undef"; + tp::String mAlbumArtist = "undef"; + tp::String mComposer = "undef"; + tp::String mAlbum = "undef"; + tp::String mGenre = "undef"; + tp::ualni mSize = 0; + tp::ualni mTotalTime = 0; + tp::ualni mYear = 0; + tp::String mDateModified = "undef"; + tp::String mDateAdded = "undef"; + tp::ualni mPlayCount = 0; + tp::String mPlayDate = "undef"; + tp::String mPlayDateUTC = "undef"; + tp::ualni mSkipCount = 0; + tp::String mSkipDate = "undef"; + tp::String mReleaseDate = "undef"; + tp::ualni mAlbumRating = 0; + bool mAlbumRatingComputed = false; + bool mLoved = false; + bool mAlbumLoved = false; + bool mExplicit = false; + // tp::String mKind = "undef"; + // 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 { diff --git a/LibraryViewer/public/WavPlayer.hpp b/LibraryViewer/public/WavPlayer.hpp index dac3eb9..2a6b8c1 100644 --- a/LibraryViewer/public/WavPlayer.hpp +++ b/LibraryViewer/public/WavPlayer.hpp @@ -1,6 +1,6 @@ #pragma once -#include "Common.hpp" +#include "LibraryViewer.hpp" class MusicPlayerContext; typedef tp::ualni SongId; @@ -10,7 +10,7 @@ public: TrackPlayer(); ~TrackPlayer(); - void startStreamTrack(SongId id); + bool startStreamTrack(SongId id); void playSong();