Refactoring of player management and integration with the main window rendering
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace quokka_gfx;
|
using namespace quokka_gfx;
|
||||||
@@ -25,15 +26,14 @@ void Application::Run() const
|
|||||||
{
|
{
|
||||||
// Main loop
|
// Main loop
|
||||||
bool running = true;
|
bool running = true;
|
||||||
uint64_t lastTime = SDL_GetTicksNS();
|
auto lastTime = std::chrono::nanoseconds(SDL_GetTicksNS());
|
||||||
|
|
||||||
while (running && m_windowManager.HasAnyWindow())
|
while (running && m_windowManager.HasAnyWindow())
|
||||||
{
|
{
|
||||||
// Calculate delta time
|
// Calculate delta time
|
||||||
const uint64_t currentTime = SDL_GetTicksNS();
|
const auto currentTime = std::chrono::nanoseconds(SDL_GetTicksNS());
|
||||||
float dt = static_cast<float>(currentTime - lastTime) / 1e9f; // Convert nanoseconds to seconds
|
const auto dt = currentTime - lastTime;
|
||||||
lastTime = currentTime;
|
lastTime = currentTime;
|
||||||
dt = std::max(dt, 0.00001f); // Avoid zero or negative delta time
|
|
||||||
|
|
||||||
// Poll and process events
|
// Poll and process events
|
||||||
const auto result = m_eventManager.ProcessEvents();
|
const auto result = m_eventManager.ProcessEvents();
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ void Window::SetMinSize(const Size &size) const
|
|||||||
void Window::OnEvent(SDL_Event *event) {}
|
void Window::OnEvent(SDL_Event *event) {}
|
||||||
//--------------------------------------------------------------
|
//--------------------------------------------------------------
|
||||||
/* Update the window state */
|
/* Update the window state */
|
||||||
void Window::Update(const float dt)
|
void Window::Update(std::chrono::nanoseconds dt)
|
||||||
{
|
{
|
||||||
// Nothing to do
|
// Nothing to do
|
||||||
}
|
}
|
||||||
@@ -101,10 +101,6 @@ void Window::Render()
|
|||||||
if (!m_renderer)
|
if (!m_renderer)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Set the drawing color to black and clear the rendering target
|
|
||||||
(void)m_renderer->setDrawColor(Color::Black);
|
|
||||||
(void)m_renderer->clear();
|
|
||||||
|
|
||||||
// Draw the window content
|
// Draw the window content
|
||||||
Draw();
|
Draw();
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include "utils/Coords.h"
|
#include "utils/Coords.h"
|
||||||
|
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
#include <chrono>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
@@ -46,7 +47,7 @@ class Window
|
|||||||
|
|
||||||
// Window lifecycle functions
|
// Window lifecycle functions
|
||||||
virtual void OnEvent(SDL_Event *event); // Handle event
|
virtual void OnEvent(SDL_Event *event); // Handle event
|
||||||
virtual void Update(float dt); // Update the window state
|
virtual void Update(std::chrono::nanoseconds dt); // Update the window state
|
||||||
virtual bool Draw(); // Draw the window content
|
virtual bool Draw(); // Draw the window content
|
||||||
void Render(); // Render the window
|
void Render(); // Render the window
|
||||||
|
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ void WindowManager::OnEvent(SDL_Event *event)
|
|||||||
}
|
}
|
||||||
//--------------------------------------------------------------
|
//--------------------------------------------------------------
|
||||||
/* Update all windows */
|
/* Update all windows */
|
||||||
void WindowManager::Update(const float dt) const
|
void WindowManager::Update(std::chrono::nanoseconds dt) const
|
||||||
{
|
{
|
||||||
// Propagate the Update call to all registered windows
|
// Propagate the Update call to all registered windows
|
||||||
for (const auto &window : m_windows)
|
for (const auto &window : m_windows)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#include "../Window/Window.h"
|
#include "../Window/Window.h"
|
||||||
|
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
#include <chrono>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace quokka_gfx
|
namespace quokka_gfx
|
||||||
@@ -34,7 +35,7 @@ class WindowManager
|
|||||||
WindowList m_windows; // Vector to store unique pointers to windows
|
WindowList m_windows; // Vector to store unique pointers to windows
|
||||||
|
|
||||||
void OnEvent(SDL_Event *event); // Handle event for all windows
|
void OnEvent(SDL_Event *event); // Handle event for all windows
|
||||||
void Update(float dt) const; // Update all windows
|
void Update(std::chrono::nanoseconds dt) const; // Update all windows
|
||||||
void Render() const; // Render all windows
|
void Render() const; // Render all windows
|
||||||
};
|
};
|
||||||
//--------------------------------------------------------------
|
//--------------------------------------------------------------
|
||||||
|
|||||||
@@ -27,6 +27,11 @@ struct FPos
|
|||||||
{
|
{
|
||||||
float x = 0.0f;
|
float x = 0.0f;
|
||||||
float y = 0.0f;
|
float y = 0.0f;
|
||||||
|
|
||||||
|
[[nodiscard]] bool isInBox(const FPos &boxPos, const FSize &boxSize) const
|
||||||
|
{
|
||||||
|
return x >= boxPos.x && y >= boxPos.y && x < boxPos.x + boxSize.w && y < boxPos.y + boxSize.h;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
//--------------------------------------------------------------
|
//--------------------------------------------------------------
|
||||||
} // namespace quokka_gfx
|
} // namespace quokka_gfx
|
||||||
|
|||||||
@@ -95,21 +95,7 @@ constexpr std::uint64_t fnv1a_64(std::span<const std::byte> data) noexcept;
|
|||||||
* @param sv Input string view.
|
* @param sv Input string view.
|
||||||
* @return std::uint64_t The 64-bit FNV-1a hash.
|
* @return std::uint64_t The 64-bit FNV-1a hash.
|
||||||
*/
|
*/
|
||||||
constexpr std::uint64_t fnv1a_64(std::string_view sv) noexcept;
|
constexpr std::uint64_t fnv1a_64_sv(std::string_view sv) noexcept;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Compute the FNV-1a 64-bit hash for a trivially copyable POD object.
|
|
||||||
*
|
|
||||||
* The object is hashed as its raw bytes (binary representation).
|
|
||||||
* Use only for POD/trivially-copyable types where this behaviour is intended.
|
|
||||||
*
|
|
||||||
* @tparam T Trivially copyable type.
|
|
||||||
* @param value Reference to the object to hash.
|
|
||||||
* @return std::uint64_t 64-bit FNV-1a hash.
|
|
||||||
*/
|
|
||||||
template<typename T>
|
|
||||||
requires std::is_trivially_copyable_v<T>
|
|
||||||
constexpr std::uint64_t fnv1a_64(const T &value) noexcept;
|
|
||||||
//--------------------------------------------------------------
|
//--------------------------------------------------------------
|
||||||
|
|
||||||
//--------------------------------------------------------------
|
//--------------------------------------------------------------
|
||||||
@@ -170,7 +156,7 @@ inline constexpr std::uint64_t fnv1a_64(const std::span<const std::byte> data) n
|
|||||||
}
|
}
|
||||||
//--------------------------------------------------------------
|
//--------------------------------------------------------------
|
||||||
/* Compute the FNV-1a 64-bit hash for a std::string_view */
|
/* Compute the FNV-1a 64-bit hash for a std::string_view */
|
||||||
inline constexpr std::uint64_t fnv1a_64(const std::string_view sv) noexcept
|
inline constexpr std::uint64_t fnv1a_64_sv(const std::string_view sv) noexcept
|
||||||
{
|
{
|
||||||
// Iterate characters to remain constexpr-friendly
|
// Iterate characters to remain constexpr-friendly
|
||||||
constexpr std::uint64_t FNV_OFFSET_BASIS = 14695981039346656037ull;
|
constexpr std::uint64_t FNV_OFFSET_BASIS = 14695981039346656037ull;
|
||||||
@@ -185,14 +171,4 @@ inline constexpr std::uint64_t fnv1a_64(const std::string_view sv) noexcept
|
|||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
//--------------------------------------------------------------
|
//--------------------------------------------------------------
|
||||||
/* Compute the FNV-1a 64-bit hash for a trivially copyable POD object */
|
|
||||||
template<typename T>
|
|
||||||
requires std::is_trivially_copyable_v<T>
|
|
||||||
inline constexpr std::uint64_t fnv1a_64(const T &value) noexcept
|
|
||||||
{
|
|
||||||
// Use std::bit_cast to get bytes in a constexpr-friendly way
|
|
||||||
auto bytes = std::bit_cast<std::array<std::byte, sizeof(T)>>(value);
|
|
||||||
return fnv1a_64(std::span<const std::byte>(bytes.data(), bytes.size()));
|
|
||||||
}
|
|
||||||
//--------------------------------------------------------------
|
|
||||||
} // namespace sdi_toolBox::common::utils::hash
|
} // namespace sdi_toolBox::common::utils::hash
|
||||||
|
|||||||
@@ -144,12 +144,11 @@ void Track::loadFromMemory(const std::span<uint8_t> midiBytes)
|
|||||||
|
|
||||||
// Store the parsed notes in the track's data structure and log statistics
|
// Store the parsed notes in the track's data structure and log statistics
|
||||||
{
|
{
|
||||||
scoped_lock lock(m_notes.mtx); // Lock the mutex to protect access to the data structures
|
m_notes = std::move(notes);
|
||||||
m_notes.list = std::move(notes);
|
|
||||||
|
|
||||||
ostringstream logMessage;
|
ostringstream logMessage;
|
||||||
logMessage << "MIDI parsing completed\n"
|
logMessage << "MIDI parsing completed\n"
|
||||||
<< " Total notes: " << m_notes.list.size() << "\n"
|
<< " Total notes: " << m_notes.size() << "\n"
|
||||||
<< " Total duration: " << lastTime;
|
<< " Total duration: " << lastTime;
|
||||||
logInfo(logMessage.str());
|
logInfo(logMessage.str());
|
||||||
}
|
}
|
||||||
@@ -160,37 +159,26 @@ void Track::loadFromMemory(const std::span<uint8_t> midiBytes)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
//--------------------------------------------------------------
|
//--------------------------------------------------------------
|
||||||
/* Clear the track data */
|
|
||||||
void Track::clear()
|
|
||||||
{
|
|
||||||
scoped_lock lock(m_notes.mtx); // Lock the mutex to protect access to the data structures
|
|
||||||
m_notes.list.clear();
|
|
||||||
}
|
|
||||||
//--------------------------------------------------------------
|
|
||||||
/* Check if a MIDI file is loaded */
|
/* Check if a MIDI file is loaded */
|
||||||
bool Track::isLoaded() const
|
bool Track::isEmpty() const
|
||||||
{
|
{
|
||||||
scoped_lock lock(m_notes.mtx); // Lock the mutex to protect access to the data structures
|
return m_notes.empty();
|
||||||
return !m_notes.list.empty();
|
|
||||||
}
|
}
|
||||||
//--------------------------------------------------------------
|
//--------------------------------------------------------------
|
||||||
/* Get the duration of the track */
|
/* Get the duration of the track */
|
||||||
Seconds Track::getDuration() const
|
Seconds Track::getDuration() const
|
||||||
{
|
{
|
||||||
scoped_lock lock(m_notes.mtx); // Lock the mutex to protect access to the data structures
|
if (m_notes.empty())
|
||||||
|
|
||||||
if (m_notes.list.empty())
|
|
||||||
return Seconds(0);
|
return Seconds(0);
|
||||||
|
|
||||||
const auto &lastNote = m_notes.list.back();
|
const auto &lastNote = m_notes.back();
|
||||||
return lastNote.endTime; // Return the end timestamp of the last note
|
return lastNote.endTime; // Return the end timestamp of the last note
|
||||||
}
|
}
|
||||||
//--------------------------------------------------------------
|
//--------------------------------------------------------------
|
||||||
/* Debug function to print the track data */
|
/* Debug function to print the track data */
|
||||||
void Track::debug() const
|
void Track::debug() const
|
||||||
{
|
{
|
||||||
scoped_lock lock(m_notes.mtx); // Lock the mutex to protect access to the data structures
|
for (const auto ¬e : m_notes)
|
||||||
for (const auto ¬e : m_notes.list)
|
|
||||||
{
|
{
|
||||||
logInfo(std::format(
|
logInfo(std::format(
|
||||||
"Note: channel={:<2} | note={:<3} | startTime={:>7.3f}s | duration={:>7.3f}s | frequency={:>7.2f} Hz",
|
"Note: channel={:<2} | note={:<3} | startTime={:>7.3f}s | duration={:>7.3f}s | frequency={:>7.2f} Hz",
|
||||||
@@ -202,10 +190,10 @@ void Track::debug() const
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
//--------------------------------------------------------------
|
//--------------------------------------------------------------
|
||||||
/* Get a lock proxy to access the notes safely without copying the data */
|
///* Get a lock proxy to access the notes safely without copying the data */
|
||||||
TrackLockProxy Track::getNotes() const
|
//TrackLockProxy Track::getNotes() const
|
||||||
{
|
//{
|
||||||
// The TrackLockProxy is automatically moved when returned (NRVO)
|
// // The TrackLockProxy is automatically moved when returned (NRVO)
|
||||||
return TrackLockProxy(m_notes.mtx, m_notes.list);
|
// return TrackLockProxy(m_notes.mtx, m_notes.list);
|
||||||
}
|
//}
|
||||||
//--------------------------------------------------------------
|
////--------------------------------------------------------------
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "trackDefs.h"
|
#include "trackDefs.h"
|
||||||
#include "trackLockProxy.h"
|
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
@@ -16,35 +15,28 @@ namespace track
|
|||||||
//--------------------------------------------------------------
|
//--------------------------------------------------------------
|
||||||
class Track
|
class Track
|
||||||
{
|
{
|
||||||
|
friend class TrackLockProxy; // Allow TrackLockProxy to access private members of Track
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Track() = default; // Default constructor
|
Track() = default; // Default constructor
|
||||||
virtual ~Track() = default; // Default destructor
|
virtual ~Track() = default; // Default destructor
|
||||||
Track(const Track &obj) = delete; // Copy constructor
|
Track(const Track &obj) = default; // Copy constructor
|
||||||
Track(Track &&obj) noexcept = delete; // Move constructor
|
Track(Track &&obj) noexcept = default; // Move constructor
|
||||||
Track &operator=(const Track &obj) = delete; // Copy assignment operator
|
Track &operator=(const Track &obj) = default; // Copy assignment operator
|
||||||
Track &operator=(Track &&obj) noexcept = delete; // Move assignment operator
|
Track &operator=(Track &&obj) noexcept = default; // Move assignment operator
|
||||||
|
|
||||||
void logInfo(const std::string &message) const; // Log an informational message
|
void logInfo(const std::string &message) const; // Log an informational message
|
||||||
void logError(const std::string &message) const; // Log an error message
|
void logError(const std::string &message) const; // Log an error message
|
||||||
|
|
||||||
// --- File management ---
|
|
||||||
void loadFromFile(const std::filesystem::path &filePath); // Load a MIDI file from disk
|
void loadFromFile(const std::filesystem::path &filePath); // Load a MIDI file from disk
|
||||||
void loadFromMemory(std::span<uint8_t> midiBytes); // Load a MIDI file from memory
|
void loadFromMemory(std::span<uint8_t> midiBytes); // Load a MIDI file from memory
|
||||||
void clear(); // Clear the track data
|
[[nodiscard]] bool isEmpty() const; // Check if a MIDI file is loaded
|
||||||
[[nodiscard]] bool isLoaded() const; // Check if a MIDI file is loaded
|
|
||||||
[[nodiscard]] Seconds getDuration() const; // Get the duration of the track
|
[[nodiscard]] Seconds getDuration() const; // Get the duration of the track
|
||||||
|
|
||||||
void debug() const; // Debug function to print the track data
|
void debug() const; // Debug function to print the track data
|
||||||
|
|
||||||
// --- Notes access ---
|
|
||||||
TrackLockProxy getNotes() const; // Get a lock proxy to access the notes safely without copying the data
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
struct
|
NotesBuffer m_notes; // Vector to store notes
|
||||||
{
|
|
||||||
mutable std::mutex mtx; // Protects access to the notes vector
|
|
||||||
NotesBuffer list; // Vector to store notes
|
|
||||||
} m_notes;
|
|
||||||
};
|
};
|
||||||
//--------------------------------------------------------------
|
//--------------------------------------------------------------
|
||||||
} // namespace track
|
} // namespace track
|
||||||
|
|||||||
@@ -7,11 +7,12 @@ using namespace std;
|
|||||||
//--------------------------------------------------------------
|
//--------------------------------------------------------------
|
||||||
/* Default constructor */
|
/* Default constructor */
|
||||||
AppContext::AppContext()
|
AppContext::AppContext()
|
||||||
|
: logger(eventBus)
|
||||||
{
|
{
|
||||||
// Create an instance of the Track
|
|
||||||
m_hTrack = make_unique<Track>();
|
|
||||||
|
|
||||||
// Create an instance of the HTTP server
|
// Create an instance of the HTTP server
|
||||||
m_hServer = make_unique<HttpServer>();
|
m_hServer = make_unique<HttpServer>();
|
||||||
|
|
||||||
|
// Create an instance of the Player
|
||||||
|
player = make_unique<player::Player>(eventBus);
|
||||||
}
|
}
|
||||||
//--------------------------------------------------------------
|
//--------------------------------------------------------------
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "core/HttpServer/IHttpServer.h"
|
#include "core/HttpServer/IHttpServer.h"
|
||||||
#include "core/Track/ITrack.h"
|
#include "logger/logger.h"
|
||||||
|
#include "player/player.h"
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <sdi_toolBox/desktop/eventBus/bus.h>
|
||||||
|
|
||||||
//--------------------------------------------------------------
|
//--------------------------------------------------------------
|
||||||
class AppContext
|
class AppContext
|
||||||
@@ -16,7 +18,11 @@ class AppContext
|
|||||||
AppContext &operator=(const AppContext &obj) = delete; // Copy assignment operator
|
AppContext &operator=(const AppContext &obj) = delete; // Copy assignment operator
|
||||||
AppContext &operator=(AppContext &&obj) noexcept = delete; // Move assignment operator
|
AppContext &operator=(AppContext &&obj) noexcept = delete; // Move assignment operator
|
||||||
|
|
||||||
std::unique_ptr<ITrack> m_hTrack; // Track instance
|
sdi_toolBox::desktop::eventBus::Bus eventBus; // Event bus for inter-component communication
|
||||||
|
|
||||||
|
Logger logger; // Logger instance
|
||||||
std::unique_ptr<IHttpServer> m_hServer; // HTTP server instance
|
std::unique_ptr<IHttpServer> m_hServer; // HTTP server instance
|
||||||
|
|
||||||
|
std::unique_ptr<player::Player> player; // Player instance
|
||||||
};
|
};
|
||||||
//--------------------------------------------------------------
|
//--------------------------------------------------------------
|
||||||
|
|||||||
11
src/core/eventBus/eventBus.h
Normal file
11
src/core/eventBus/eventBus.h
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
#include <sdi_toolBox/common/utils/hash.h>
|
||||||
|
#include <sdi_toolBox/desktop/eventBus/bus.h>
|
||||||
|
#include <sdi_toolBox/desktop/eventBus/message.h>
|
||||||
|
#include <sdi_toolBox/desktop/eventBus/node.h>
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
#include "genericMessages.h"
|
||||||
|
#include "logMessages.h"
|
||||||
|
//--------------------------------------------------------------
|
||||||
50
src/core/eventBus/genericMessages.h
Normal file
50
src/core/eventBus/genericMessages.h
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "volatileMessages.h"
|
||||||
|
|
||||||
|
#include <any>
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
class GenericMessageEvent : public sdi_toolBox::desktop::eventBus::Message
|
||||||
|
, public BasicMessage
|
||||||
|
{
|
||||||
|
using MessageTypeID = sdi_toolBox::desktop::eventBus::MessageTypeID;
|
||||||
|
|
||||||
|
public:
|
||||||
|
std::any m_payload;
|
||||||
|
|
||||||
|
public:
|
||||||
|
GenericMessageEvent() = delete;
|
||||||
|
virtual ~GenericMessageEvent() = default;
|
||||||
|
explicit GenericMessageEvent(const MessageTypeID messageTypeID,
|
||||||
|
std::any payload = {})
|
||||||
|
: Message(messageTypeID)
|
||||||
|
, m_payload(std::move(payload))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the payload as a specific type if possible, otherwise return an empty optional
|
||||||
|
template<class T>
|
||||||
|
[[nodiscard]] std::optional<T> getPayloadAs() const
|
||||||
|
{
|
||||||
|
if (m_payload.has_value())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return std::any_cast<T>(m_payload);
|
||||||
|
}
|
||||||
|
catch (const std::bad_any_cast &)
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return a string representation of the message
|
||||||
|
std::string debug() const override
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
59
src/core/eventBus/logMessages.h
Normal file
59
src/core/eventBus/logMessages.h
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "volatileMessages.h"
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
class LogMessage : public sdi_toolBox::desktop::eventBus::Message
|
||||||
|
, public BasicMessage
|
||||||
|
{
|
||||||
|
using MessageTypeID = sdi_toolBox::desktop::eventBus::MessageTypeID;
|
||||||
|
static constexpr auto LogMessageId = HashMessageType("Log");
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum class LogLevel : std::uint8_t
|
||||||
|
{
|
||||||
|
Info = 0,
|
||||||
|
Debug,
|
||||||
|
Warning,
|
||||||
|
Error
|
||||||
|
};
|
||||||
|
|
||||||
|
LogLevel m_logLevel;
|
||||||
|
std::string m_logMessage;
|
||||||
|
|
||||||
|
public:
|
||||||
|
LogMessage() = delete;
|
||||||
|
virtual ~LogMessage() = default;
|
||||||
|
explicit LogMessage(const LogLevel logLevel, std::string logMessage = "")
|
||||||
|
: Message(LogMessageId)
|
||||||
|
, m_logLevel(logLevel)
|
||||||
|
, m_logMessage(std::move(logMessage))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return a string representation of the message
|
||||||
|
[[nodiscard]] std::string debug() const override
|
||||||
|
{
|
||||||
|
std::ostringstream oss;
|
||||||
|
|
||||||
|
switch (m_logLevel)
|
||||||
|
{
|
||||||
|
case LogLevel::Info:
|
||||||
|
oss << "[INFO] ";
|
||||||
|
break;
|
||||||
|
case LogLevel::Debug:
|
||||||
|
oss << "[DEBUG] ";
|
||||||
|
break;
|
||||||
|
case LogLevel::Warning:
|
||||||
|
oss << "[WARNING] ";
|
||||||
|
break;
|
||||||
|
case LogLevel::Error:
|
||||||
|
oss << "[ERROR] ";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
oss << m_logMessage;
|
||||||
|
|
||||||
|
return oss.str();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
20
src/core/eventBus/volatileMessages.h
Normal file
20
src/core/eventBus/volatileMessages.h
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
#include <sdi_toolBox/common/utils/hash.h>
|
||||||
|
#include <sdi_toolBox/desktop/eventBus/message.h>
|
||||||
|
|
||||||
|
constexpr auto HashMessageType = sdi_toolBox::common::utils::hash::fnv1a_64_sv;
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
class BasicMessage
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
BasicMessage() = default;
|
||||||
|
virtual ~BasicMessage() = default;
|
||||||
|
|
||||||
|
virtual std::string debug() const = 0; // Return a string representation of the message
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
62
src/core/logger/logger.cpp
Normal file
62
src/core/logger/logger.cpp
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
#include "logger.h"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <iostream>
|
||||||
|
#include <syncstream>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Constructor */
|
||||||
|
Logger::Logger(sdi_toolBox::desktop::eventBus::Bus &hBus)
|
||||||
|
: Node(hBus)
|
||||||
|
{
|
||||||
|
// Subscribe to log message types to receive messages
|
||||||
|
subscribe(HashMessageType("Log"));
|
||||||
|
|
||||||
|
// Start the logger's main thread to process incoming messages
|
||||||
|
run();
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Default destructor */
|
||||||
|
Logger::~Logger()
|
||||||
|
{
|
||||||
|
// Stop the logger's main thread and clean up resources
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Start the logger's main thread to process incoming messages */
|
||||||
|
void Logger::run()
|
||||||
|
{
|
||||||
|
stop();
|
||||||
|
m_thread = std::jthread([this](const std::stop_token &token)
|
||||||
|
{ processLogMessages(token); });
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Stop the logger's main thread and clean up resources */
|
||||||
|
void Logger::stop()
|
||||||
|
{
|
||||||
|
m_thread.request_stop();
|
||||||
|
messageNotify();
|
||||||
|
if (m_thread.joinable())
|
||||||
|
m_thread.join();
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Process incoming log messages from the event bus */
|
||||||
|
void Logger::processLogMessages(const std::stop_token &token)
|
||||||
|
{
|
||||||
|
while (!token.stop_requested())
|
||||||
|
{
|
||||||
|
syncWaitForMessage();
|
||||||
|
|
||||||
|
while (getMessageCount() > 0)
|
||||||
|
{
|
||||||
|
const auto message = dynamic_pointer_cast<BasicMessage>(popMessage());
|
||||||
|
if (!message)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
osyncstream synced_out(std::cout);
|
||||||
|
synced_out << message->debug() << endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
28
src/core/logger/logger.h
Normal file
28
src/core/logger/logger.h
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "core/eventBus/eventBus.h"
|
||||||
|
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
class Logger : public sdi_toolBox::desktop::eventBus::Node
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Logger() = delete; // Default constructor
|
||||||
|
virtual ~Logger(); // Default destructor
|
||||||
|
Logger(const Logger &obj) = delete; // Copy constructor
|
||||||
|
Logger(Logger &&obj) noexcept = delete; // Move constructor
|
||||||
|
Logger &operator=(const Logger &obj) = delete; // Copy assignment operator
|
||||||
|
Logger &operator=(Logger &&obj) noexcept = delete; // Move assignment operator
|
||||||
|
|
||||||
|
explicit Logger(sdi_toolBox::desktop::eventBus::Bus &hBus); // Constructor
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::jthread m_thread;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void run(); // Start the logger's main thread to process incoming messages
|
||||||
|
void stop(); // Stop the logger's main thread and clean up resources
|
||||||
|
void processLogMessages(const std::stop_token &token); // Process incoming log messages from the event bus
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
@@ -1,4 +1,277 @@
|
|||||||
#include "player.h"
|
#include "player.h"
|
||||||
|
|
||||||
|
#include "core/track/TrackLockProxy.h"
|
||||||
|
#include "playerDefs.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
using namespace player;
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Constructor */
|
||||||
|
Player::Player(sdi_toolBox::desktop::eventBus::Bus &hBus)
|
||||||
|
: Node(hBus)
|
||||||
|
, m_eventProcessing(*this)
|
||||||
|
{
|
||||||
|
// Initialization
|
||||||
|
|
||||||
|
// Event subscriptions
|
||||||
|
subscribe(HashMessageType("remote.loadFile"));
|
||||||
|
subscribe(HashMessageType("remote.setState"));
|
||||||
|
// subscribe(HashMessageType("remote.setSongTime"));
|
||||||
|
// subscribe(HashMessageType("remote.setSpeed"));
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Update the player state based on the current state and time */
|
||||||
|
void Player::update()
|
||||||
|
{
|
||||||
|
if (m_playback.state == State::Stopped)
|
||||||
|
{
|
||||||
|
m_activeNotes.playing.clear(); // Clear the currently playing notes buffer
|
||||||
|
m_activeNotes.upcoming.clear(); // Clear the upcoming notes buffer
|
||||||
|
}
|
||||||
|
else if (m_playback.state == State::WaitingForStart || m_playback.state == State::WaitingForResume)
|
||||||
|
{
|
||||||
|
// First time the update is called after receiving a "Play" command, initialize the playback state
|
||||||
|
m_playback.startPlaying();
|
||||||
|
|
||||||
|
const auto ¬es = getNotes(); // Get a lock proxy to access the notes safely without copying the data
|
||||||
|
notes.getActiveNotesAt(m_playback.currentPosition, WindowDuration, m_activeNotes);
|
||||||
|
}
|
||||||
|
else if (m_playback.state == State::Playing)
|
||||||
|
{
|
||||||
|
// Update the current position based on the elapsed time since the start of playback
|
||||||
|
const auto now = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
const auto elapsedSinceStart = chrono::duration_cast<Seconds>(now - m_playback.startTime);
|
||||||
|
m_playback.currentPosition = m_playback.pauseOffset + elapsedSinceStart;
|
||||||
|
|
||||||
|
// Check if the current position exceeds the track duration and stop playback if necessary
|
||||||
|
if (m_playback.currentPosition >= m_data.track.getDuration())
|
||||||
|
{
|
||||||
|
m_playback.reset();
|
||||||
|
m_playback.currentPosition = m_data.track.getDuration(); // Set the current position to the end of the track
|
||||||
|
emit<LogMessage>(LogMessage::LogLevel::Info, "Playback finished");
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto ¬es = getNotes(); // Get a lock proxy to access the notes safely without copying the data
|
||||||
|
notes.getActiveNotesAt(m_playback.currentPosition, WindowDuration, m_activeNotes);
|
||||||
|
}
|
||||||
|
else if (m_playback.state == State::Paused)
|
||||||
|
{
|
||||||
|
// In the paused state, we do not update the current position or time.
|
||||||
|
// We simply keep the current position and active notes as they were when paused.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Get the currently active notes */
|
||||||
|
const track::ActiveNotes &Player::getActiveNotes() const
|
||||||
|
{
|
||||||
|
return m_activeNotes;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Load a MIDI file from disk */
|
||||||
|
void Player::loadFile(const std::filesystem::path &filePath)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
track::Track tmpTrack;
|
||||||
|
tmpTrack.loadFromFile(filePath);
|
||||||
|
|
||||||
|
scoped_lock lock(m_data.mtx); // Lock the mutex to protect access to the track data
|
||||||
|
m_data.track = std::move(tmpTrack);
|
||||||
|
}
|
||||||
|
catch (const std::exception &e)
|
||||||
|
{
|
||||||
|
cerr << "Error loading file: " << e.what() << endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Close the currently loaded MIDI file */
|
||||||
|
void Player::closeFile()
|
||||||
|
{
|
||||||
|
scoped_lock lock(m_data.mtx); // Lock the mutex to protect access to the track data
|
||||||
|
m_data.track = {}; // Reset the track to an empty state
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Get a lock proxy to access the notes safely without copying the data */
|
||||||
|
track::TrackLockProxy Player::getNotes() const
|
||||||
|
{
|
||||||
|
// The TrackLockProxy is automatically moved when returned (NRVO)
|
||||||
|
return track::TrackLockProxy(m_data.mtx, &m_data.track);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Reset the playback clock to its initial state (stopped, zero position) */
|
||||||
|
void Player::PlaybackClock::reset()
|
||||||
|
{
|
||||||
|
state = State::Stopped;
|
||||||
|
startTime = TimePoint();
|
||||||
|
pauseOffset = Seconds(0);
|
||||||
|
currentPosition = Seconds(0);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Start a new playback session, resetting the clock and position */
|
||||||
|
void Player::PlaybackClock::startNew()
|
||||||
|
{
|
||||||
|
state = State::WaitingForStart;
|
||||||
|
pauseOffset = Seconds(0);
|
||||||
|
currentPosition = Seconds(0);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Start playing from the current position */
|
||||||
|
void Player::PlaybackClock::startPlaying()
|
||||||
|
{
|
||||||
|
startTime = std::chrono::steady_clock::now();
|
||||||
|
state = State::Playing;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Pause the playback clock and store the current position */
|
||||||
|
void Player::PlaybackClock::pause()
|
||||||
|
{
|
||||||
|
if (state == State::Playing)
|
||||||
|
{
|
||||||
|
pauseOffset = currentPosition;
|
||||||
|
state = State::Paused;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Resume the playback clock from the paused position */
|
||||||
|
void Player::PlaybackClock::resume()
|
||||||
|
{
|
||||||
|
if (state == State::Paused)
|
||||||
|
state = State::WaitingForResume;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Default constructor */
|
||||||
|
Player::EventProcessing::EventProcessing(Player &pPlayer)
|
||||||
|
: player(pPlayer)
|
||||||
|
{
|
||||||
|
start(); // Start the event processing thread
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Default destructor */
|
||||||
|
Player::EventProcessing::~EventProcessing()
|
||||||
|
{
|
||||||
|
stop(); // Stop any existing thread before starting a new one
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Start the event processing thread */
|
||||||
|
void Player::EventProcessing::start()
|
||||||
|
{
|
||||||
|
// If the thread is already running, do nothing
|
||||||
|
if (thread.joinable())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Start the event processing thread
|
||||||
|
thread = std::jthread([&](const std::stop_token &stopToken)
|
||||||
|
{ process(stopToken); });
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Stop the event processing thread */
|
||||||
|
void Player::EventProcessing::stop()
|
||||||
|
{
|
||||||
|
thread.request_stop();
|
||||||
|
player.messageNotify();
|
||||||
|
if (thread.joinable())
|
||||||
|
thread.join();
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Event processing loop */
|
||||||
|
void Player::EventProcessing::process(const std::stop_token &stopToken) const
|
||||||
|
{
|
||||||
|
while (!stopToken.stop_requested())
|
||||||
|
{
|
||||||
|
player.syncWaitForMessage();
|
||||||
|
|
||||||
|
while (player.getMessageCount() > 0)
|
||||||
|
{
|
||||||
|
auto message = player.popMessage();
|
||||||
|
if (message)
|
||||||
|
{
|
||||||
|
const auto messageType = message->getMessageTypeID();
|
||||||
|
|
||||||
|
switch (messageType)
|
||||||
|
{
|
||||||
|
case HashMessageType("remote.loadFile"):
|
||||||
|
{
|
||||||
|
player.on_remoteLoadFile(dynamic_pointer_cast<GenericMessageEvent>(message));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case HashMessageType("remote.setState"):
|
||||||
|
player.on_remoteSetState(dynamic_pointer_cast<GenericMessageEvent>(message));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Handle the "remote.loadFile" message */
|
||||||
|
void Player::on_remoteLoadFile(const std::shared_ptr<GenericMessageEvent> &message)
|
||||||
|
{
|
||||||
|
// Sanity check: Ensure the message is valid
|
||||||
|
if (!message)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const auto filePath = std::any_cast<std::string>(message->m_payload);
|
||||||
|
loadFile(filePath);
|
||||||
|
}
|
||||||
|
catch (const std::exception &e)
|
||||||
|
{
|
||||||
|
emit<LogMessage>(LogMessage::LogLevel::Error, "Failed to load file: " + std::string(e.what()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Handle the "remote.setState" message */
|
||||||
|
void Player::on_remoteSetState(const std::shared_ptr<GenericMessageEvent> &message)
|
||||||
|
{
|
||||||
|
// Sanity check: Ensure the message is valid
|
||||||
|
if (!message)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const auto command = std::any_cast<Command>(message->m_payload);
|
||||||
|
switch (command)
|
||||||
|
{
|
||||||
|
case Command::Pause:
|
||||||
|
{
|
||||||
|
// Handle paused state
|
||||||
|
m_playback.pause();
|
||||||
|
emit<LogMessage>(LogMessage::LogLevel::Info, "Player paused");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Command::Play:
|
||||||
|
{
|
||||||
|
// Handle playing state
|
||||||
|
if (m_playback.state == State::Paused)
|
||||||
|
{
|
||||||
|
m_playback.resume();
|
||||||
|
emit<LogMessage>(LogMessage::LogLevel::Info, "Player resumed");
|
||||||
|
}
|
||||||
|
else if (m_playback.state == State::Stopped)
|
||||||
|
{
|
||||||
|
m_playback.startNew();
|
||||||
|
emit<LogMessage>(LogMessage::LogLevel::Info, "Player playing");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Command::Stop:
|
||||||
|
{
|
||||||
|
// Handle stopped state
|
||||||
|
m_playback.reset();
|
||||||
|
emit<LogMessage>(LogMessage::LogLevel::Info, "Player stopped");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const std::exception &e)
|
||||||
|
{
|
||||||
|
emit<LogMessage>(LogMessage::LogLevel::Error, "Failed to set state: " + std::string(e.what()));
|
||||||
|
}
|
||||||
|
}
|
||||||
//--------------------------------------------------------------
|
//--------------------------------------------------------------
|
||||||
|
|||||||
@@ -1,14 +1,80 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
//--------------------------------------------------------------
|
#include "core/eventBus/eventBus.h"
|
||||||
class Player
|
#include "core/track/track.h"
|
||||||
|
#include "playerDefs.h"
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
namespace player
|
||||||
{
|
{
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
class Player : public sdi_toolBox::desktop::eventBus::Node
|
||||||
|
{
|
||||||
|
// using DefaultMessageType = std::shared_ptr<sdi_toolBox::desktop::eventBus::Message>;
|
||||||
|
static constexpr auto WindowDuration = track::Seconds(1.5);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Player() = default; // Default constructor
|
Player() = delete; // Default constructor
|
||||||
virtual ~Player() = default; // Default destructor
|
virtual ~Player() = default; // Default destructor
|
||||||
Player(const Player &obj) = delete; // Copy constructor
|
Player(const Player &obj) = delete; // Copy constructor
|
||||||
Player(Player &&obj) noexcept = delete; // Move constructor
|
Player(Player &&obj) noexcept = delete; // Move constructor
|
||||||
Player &operator=(const Player &obj) = delete; // Copy assignment operator
|
Player &operator=(const Player &obj) = delete; // Copy assignment operator
|
||||||
Player &operator=(Player &&obj) noexcept = delete; // Move assignment operator
|
Player &operator=(Player &&obj) noexcept = delete; // Move assignment operator
|
||||||
|
|
||||||
|
explicit Player(sdi_toolBox::desktop::eventBus::Bus &hBus); // Constructor
|
||||||
|
|
||||||
|
void update(); // Update the player state based on the current state and time
|
||||||
|
const track::ActiveNotes &getActiveNotes() const; // Get the currently active notes
|
||||||
|
|
||||||
|
// Track management
|
||||||
|
void loadFile(const std::filesystem::path &filePath); // Load a MIDI file from disk
|
||||||
|
void closeFile(); // Close the currently loaded MIDI file
|
||||||
|
|
||||||
|
// --- Notes access ---
|
||||||
|
track::TrackLockProxy getNotes() const; // Get a lock proxy to access the notes safely without copying the data
|
||||||
|
|
||||||
|
protected:
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
mutable std::mutex mtx;
|
||||||
|
track::Track track; // Track instance to be played
|
||||||
|
} m_data;
|
||||||
|
|
||||||
|
struct PlaybackClock
|
||||||
|
{
|
||||||
|
std::atomic<State> state = State::Stopped;
|
||||||
|
TimePoint startTime = TimePoint();
|
||||||
|
Seconds pauseOffset = Seconds(0);
|
||||||
|
Seconds currentPosition = Seconds(0);
|
||||||
|
|
||||||
|
void reset(); // Reset the playback clock to its initial state (stopped, zero position)
|
||||||
|
void startNew(); // Start a new playback session, resetting the clock and position
|
||||||
|
void startPlaying(); // Start playing from the current position
|
||||||
|
void pause(); // Pause the playback clock and store the current position
|
||||||
|
void resume(); // Resume the playback clock from the paused position
|
||||||
|
} m_playback;
|
||||||
|
|
||||||
|
track::ActiveNotes m_activeNotes; // Currently active notes
|
||||||
|
|
||||||
|
struct EventProcessing
|
||||||
|
{
|
||||||
|
EventProcessing() = delete; // Default constructor
|
||||||
|
explicit EventProcessing(Player &pPlayer); // Constructor
|
||||||
|
~EventProcessing(); // Default destructor
|
||||||
|
void start(); // Start the event processing thread
|
||||||
|
void stop(); // Stop the event processing thread
|
||||||
|
void process(const std::stop_token &stopToken) const; // Event processing loop
|
||||||
|
|
||||||
|
std::jthread thread;
|
||||||
|
Player &player;
|
||||||
|
} m_eventProcessing;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void on_remoteLoadFile(const std::shared_ptr<GenericMessageEvent> &message); // Handle the "remote.loadFile" message
|
||||||
|
void on_remoteSetState(const std::shared_ptr<GenericMessageEvent> &message); // Handle the "remote.setState" message
|
||||||
};
|
};
|
||||||
//--------------------------------------------------------------
|
//--------------------------------------------------------------
|
||||||
|
} // namespace player
|
||||||
|
|||||||
28
src/core/player/playerDefs.h
Normal file
28
src/core/player/playerDefs.h
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace player
|
||||||
|
{
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
using TimePoint = std::chrono::steady_clock::time_point;
|
||||||
|
using Seconds = std::chrono::duration<double>;
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
enum class Command : uint8_t
|
||||||
|
{
|
||||||
|
Stop = 0,
|
||||||
|
Play,
|
||||||
|
Pause,
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
enum class State : uint8_t
|
||||||
|
{
|
||||||
|
Stopped = 0,
|
||||||
|
WaitingForStart,
|
||||||
|
WaitingForResume,
|
||||||
|
Playing,
|
||||||
|
Paused
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
} // namespace player
|
||||||
@@ -1,13 +1,16 @@
|
|||||||
#include "TrackLockProxy.h"
|
#include "TrackLockProxy.h"
|
||||||
|
|
||||||
|
#include "Track.h"
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace track;
|
using namespace track;
|
||||||
//--------------------------------------------------------------
|
//--------------------------------------------------------------
|
||||||
/* Constructor */
|
/* Constructor */
|
||||||
TrackLockProxy::TrackLockProxy(std::mutex &mtx, const NotesView notes)
|
TrackLockProxy::TrackLockProxy(std::mutex &mtx, const Track *track)
|
||||||
: m_lock(mtx)
|
: m_lock(mtx)
|
||||||
, m_notes(notes)
|
, m_track(track)
|
||||||
{
|
{
|
||||||
|
m_notes = m_track->m_notes;
|
||||||
}
|
}
|
||||||
//--------------------------------------------------------------
|
//--------------------------------------------------------------
|
||||||
/* Access a note by index */
|
/* Access a note by index */
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
namespace track
|
namespace track
|
||||||
{
|
{
|
||||||
|
class Track;
|
||||||
//--------------------------------------------------------------
|
//--------------------------------------------------------------
|
||||||
class TrackLockProxy final
|
class TrackLockProxy final
|
||||||
{
|
{
|
||||||
@@ -15,9 +16,9 @@ class TrackLockProxy final
|
|||||||
TrackLockProxy(const TrackLockProxy &obj) = delete; // Copy constructor
|
TrackLockProxy(const TrackLockProxy &obj) = delete; // Copy constructor
|
||||||
TrackLockProxy(TrackLockProxy &&obj) noexcept = default; // Move constructor
|
TrackLockProxy(TrackLockProxy &&obj) noexcept = default; // Move constructor
|
||||||
TrackLockProxy &operator=(const TrackLockProxy &obj) = delete; // Copy assignment operator
|
TrackLockProxy &operator=(const TrackLockProxy &obj) = delete; // Copy assignment operator
|
||||||
TrackLockProxy &operator=(TrackLockProxy &&obj) noexcept = default; // Move assignment operator
|
TrackLockProxy &operator=(TrackLockProxy &&obj) noexcept = delete; // Move assignment operator
|
||||||
|
|
||||||
explicit TrackLockProxy(std::mutex &mtx, NotesView notes); // Constructor
|
explicit TrackLockProxy(std::mutex &mtx, const Track *track); // Constructor
|
||||||
|
|
||||||
const Note &operator[](size_t index) const; // Access a note by index
|
const Note &operator[](size_t index) const; // Access a note by index
|
||||||
|
|
||||||
@@ -39,6 +40,7 @@ class TrackLockProxy final
|
|||||||
protected:
|
protected:
|
||||||
std::unique_lock<std::mutex> m_lock; // Keeps the mutex locked for the lifetime of the proxy
|
std::unique_lock<std::mutex> m_lock; // Keeps the mutex locked for the lifetime of the proxy
|
||||||
|
|
||||||
|
const Track *m_track; // Pointer to the track to access the notes
|
||||||
NotesView m_notes; // Span of notes to access without copying the data
|
NotesView m_notes; // Span of notes to access without copying the data
|
||||||
};
|
};
|
||||||
//--------------------------------------------------------------
|
//--------------------------------------------------------------
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ class BackgroundLayer : public Layer
|
|||||||
|
|
||||||
explicit BackgroundLayer(MainWindow &owner); // Constructor
|
explicit BackgroundLayer(MainWindow &owner); // Constructor
|
||||||
|
|
||||||
void update(float dt) override {} // Update the layer state
|
void update(std::chrono::nanoseconds dt) override {} // Update the layer state
|
||||||
void render() const override; // Render the layer
|
void render() const override; // Render the layer
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|||||||
214
src/gui/mainWindow/layers/controlsLayer.cpp
Normal file
214
src/gui/mainWindow/layers/controlsLayer.cpp
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
#include "controlsLayer.h"
|
||||||
|
|
||||||
|
#include "core/player/playerDefs.h"
|
||||||
|
#include "gui/mainWindow/mainWindow.h"
|
||||||
|
|
||||||
|
#include <quokka_gfx.h>
|
||||||
|
#include <ranges>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
using namespace quokka_gfx;
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Constructor */
|
||||||
|
ControlsLayer::ControlsLayer(MainWindow &owner)
|
||||||
|
: Layer(owner)
|
||||||
|
{
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Update the layer state */
|
||||||
|
void ControlsLayer::update(std::chrono::nanoseconds dt)
|
||||||
|
{
|
||||||
|
const auto &inputManager = GetInputManager();
|
||||||
|
const auto &mousePosition = inputManager->getMousePosition();
|
||||||
|
|
||||||
|
const auto &mouseDownState = inputManager->isMouseButtonDown(quokka_gfx::InputManager::MouseButton::Left);
|
||||||
|
const auto &mousePressedState = inputManager->isMouseButtonPressed(quokka_gfx::InputManager::MouseButton::Left);
|
||||||
|
|
||||||
|
m_playButtonStyle.isHovered = false;
|
||||||
|
m_pauseButtonStyle.isHovered = false;
|
||||||
|
m_stopButtonStyle.isHovered = false;
|
||||||
|
m_playButtonStyle.isPressed = false;
|
||||||
|
m_pauseButtonStyle.isPressed = false;
|
||||||
|
m_stopButtonStyle.isPressed = false;
|
||||||
|
|
||||||
|
if (mousePosition.isInBox(m_playButtonStyle.pos, m_playButtonStyle.size))
|
||||||
|
{
|
||||||
|
m_playButtonStyle.isHovered = true;
|
||||||
|
m_playButtonStyle.isPressed = mouseDownState;
|
||||||
|
if (mousePressedState)
|
||||||
|
m_owner.emit<GenericMessageEvent>(HashMessageType("remote.setState"), player::Command::Play);
|
||||||
|
}
|
||||||
|
if (mousePosition.isInBox(m_pauseButtonStyle.pos, m_pauseButtonStyle.size))
|
||||||
|
{
|
||||||
|
m_pauseButtonStyle.isHovered = true;
|
||||||
|
m_pauseButtonStyle.isPressed = mouseDownState;
|
||||||
|
if (mousePressedState)
|
||||||
|
m_owner.emit<GenericMessageEvent>(HashMessageType("remote.setState"), player::Command::Pause);
|
||||||
|
}
|
||||||
|
if (mousePosition.isInBox(m_stopButtonStyle.pos, m_stopButtonStyle.size))
|
||||||
|
{
|
||||||
|
m_stopButtonStyle.isHovered = true;
|
||||||
|
m_stopButtonStyle.isPressed = mouseDownState;
|
||||||
|
if (mousePressedState)
|
||||||
|
m_owner.emit<GenericMessageEvent>(HashMessageType("remote.setState"), player::Command::Stop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Render the layer */
|
||||||
|
void ControlsLayer::render() const
|
||||||
|
{
|
||||||
|
const auto renderer = GetRenderer(); // Get the renderer instance from the main window
|
||||||
|
|
||||||
|
drawPlayButton(*renderer);
|
||||||
|
drawPauseButton(*renderer);
|
||||||
|
drawStopButton(*renderer);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
void ControlsLayer::init()
|
||||||
|
{
|
||||||
|
// Play button style
|
||||||
|
m_playButtonStyle.pos = { .x = 50.0f, .y = 50.0f };
|
||||||
|
m_playButtonStyle.size = { .w = 50.0f, .h = 50.0f };
|
||||||
|
|
||||||
|
// Pause button style
|
||||||
|
m_pauseButtonStyle.pos = { .x = 120.0f, .y = 50.0f };
|
||||||
|
m_pauseButtonStyle.size = { .w = 50.0f, .h = 50.0f };
|
||||||
|
|
||||||
|
// Stop button style
|
||||||
|
m_stopButtonStyle.pos = { .x = 190.0f, .y = 50.0f };
|
||||||
|
m_stopButtonStyle.size = { .w = 50.0f, .h = 50.0f };
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
void ControlsLayer::drawPlayButton(const quokka_gfx::Renderer &renderer) const
|
||||||
|
{
|
||||||
|
|
||||||
|
// 1. Dessiner le fond/contour du bouton
|
||||||
|
if (m_playButtonStyle.isHovered || m_playButtonStyle.isPressed)
|
||||||
|
{
|
||||||
|
renderer.setDrawColor(Color::Blue);
|
||||||
|
renderer.fillRect(m_playButtonStyle.pos, m_playButtonStyle.size);
|
||||||
|
renderer.setDrawColor(Color::Yellow);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
renderer.setDrawColor(Color::Yellow);
|
||||||
|
renderer.drawRect(m_playButtonStyle.pos, m_playButtonStyle.size);
|
||||||
|
renderer.setDrawColor(Color::Blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Dessiner l'icône "Play" (un triangle pointant vers la droite)
|
||||||
|
// On définit une marge interne (padding) pour que l'icône ne colle pas aux bords
|
||||||
|
float paddingX = m_playButtonStyle.size.w * 0.3f;
|
||||||
|
float paddingY = m_playButtonStyle.size.h * 0.25f;
|
||||||
|
|
||||||
|
float startX = m_playButtonStyle.pos.x + paddingX;
|
||||||
|
float endX = m_playButtonStyle.pos.x + m_playButtonStyle.size.w - paddingX;
|
||||||
|
float topY = m_playButtonStyle.pos.y + paddingY;
|
||||||
|
float bottomY = m_playButtonStyle.pos.y + m_playButtonStyle.size.h - paddingY;
|
||||||
|
float centerY = m_playButtonStyle.pos.y + (m_playButtonStyle.size.h / 2.0f);
|
||||||
|
|
||||||
|
// N'ayant pas de fillTriangle, on remplit le triangle verticalement colonne par colonne
|
||||||
|
// (ou horizontalement de la base vers la pointe)
|
||||||
|
float totalWidth = endX - startX;
|
||||||
|
if (totalWidth > 0.0f)
|
||||||
|
{
|
||||||
|
for (float x = startX; x <= endX; x += 1.0f)
|
||||||
|
{
|
||||||
|
// Interpolation linéaire pour trouver la hauteur haute et basse à l'abscisse x
|
||||||
|
float progress = (x - startX) / totalWidth;
|
||||||
|
float currentTopY = topY + (centerY - topY) * progress;
|
||||||
|
float currentBottomY = bottomY - (bottomY - centerY) * progress;
|
||||||
|
|
||||||
|
// On dessine une ligne verticale pour cette colonne du triangle
|
||||||
|
renderer.drawLine({ x, currentTopY }, { x, currentBottomY });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
void ControlsLayer::drawPauseButton(const quokka_gfx::Renderer &renderer) const
|
||||||
|
{
|
||||||
|
// 1. Dessiner le fond/contour
|
||||||
|
if (m_pauseButtonStyle.isHovered || m_pauseButtonStyle.isPressed)
|
||||||
|
{
|
||||||
|
renderer.setDrawColor(Color::Blue);
|
||||||
|
renderer.fillRect(m_pauseButtonStyle.pos, m_pauseButtonStyle.size);
|
||||||
|
renderer.setDrawColor(Color::Yellow);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
renderer.setDrawColor(Color::Yellow);
|
||||||
|
renderer.drawRect(m_pauseButtonStyle.pos, m_pauseButtonStyle.size);
|
||||||
|
renderer.setDrawColor(Color::Blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Dessiner l'icône "Pause" (deux barres verticales)
|
||||||
|
float paddingX = m_pauseButtonStyle.size.w * 0.3f;
|
||||||
|
float paddingY = m_pauseButtonStyle.size.h * 0.25f;
|
||||||
|
|
||||||
|
float barWidth = (m_pauseButtonStyle.size.w - (2.0f * paddingX)) * 0.35f; // Largeur d'une barre
|
||||||
|
float gap = m_pauseButtonStyle.size.w - (2.0f * paddingX) - (2.0f * barWidth); // Espace central
|
||||||
|
|
||||||
|
quokka_gfx::FSize barSize{ barWidth, m_pauseButtonStyle.size.h - (2.0f * paddingY) };
|
||||||
|
|
||||||
|
// Barre Gauche
|
||||||
|
quokka_gfx::FPos leftBarPos{ m_pauseButtonStyle.pos.x + paddingX, m_pauseButtonStyle.pos.y + paddingY };
|
||||||
|
renderer.fillRect(leftBarPos, barSize);
|
||||||
|
|
||||||
|
// Barre Droite
|
||||||
|
quokka_gfx::FPos rightBarPos{ m_pauseButtonStyle.pos.x + paddingX + barWidth + gap, m_pauseButtonStyle.pos.y + paddingY };
|
||||||
|
renderer.fillRect(rightBarPos, barSize);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
void ControlsLayer::drawStopButton(const quokka_gfx::Renderer &renderer) const
|
||||||
|
{
|
||||||
|
// 1. Dessiner le fond/contour
|
||||||
|
if (m_stopButtonStyle.isHovered || m_stopButtonStyle.isPressed)
|
||||||
|
{
|
||||||
|
renderer.setDrawColor(Color::Blue);
|
||||||
|
renderer.fillRect(m_stopButtonStyle.pos, m_stopButtonStyle.size);
|
||||||
|
renderer.setDrawColor(Color::Yellow);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
renderer.setDrawColor(Color::Yellow);
|
||||||
|
renderer.drawRect(m_stopButtonStyle.pos, m_stopButtonStyle.size);
|
||||||
|
renderer.setDrawColor(Color::Blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Dessiner l'icône "Reset" (une flèche circulaire ou un carré de stop + flèche)
|
||||||
|
// Version rapide et propre pour un lecteur MIDI : Le symbole "Retour au début" / "Skip Back"
|
||||||
|
// Composé d'un triangle pointant vers la gauche ACCOLÉ à une barre verticale stable.
|
||||||
|
|
||||||
|
float paddingX = m_stopButtonStyle.size.w * 0.3f;
|
||||||
|
float paddingY = m_stopButtonStyle.size.h * 0.25f;
|
||||||
|
|
||||||
|
float startX = m_stopButtonStyle.pos.x + paddingX;
|
||||||
|
float endX = m_stopButtonStyle.pos.x + m_stopButtonStyle.size.w - paddingX;
|
||||||
|
float topY = m_stopButtonStyle.pos.y + paddingY;
|
||||||
|
float bottomY = m_stopButtonStyle.pos.y + m_stopButtonStyle.size.h - paddingY;
|
||||||
|
float centerY = m_stopButtonStyle.pos.y + (m_stopButtonStyle.size.h / 2.0f);
|
||||||
|
|
||||||
|
float barWidth = m_stopButtonStyle.size.w * 0.08f; // Épaisseur de la barre de butée
|
||||||
|
|
||||||
|
// A. Dessin de la barre verticale à gauche
|
||||||
|
renderer.fillRect({ startX, topY }, { barWidth, bottomY - topY });
|
||||||
|
|
||||||
|
// B. Dessin du triangle pointant vers la gauche (de la butée jusqu'à endX)
|
||||||
|
float triangleStartX = startX + barWidth + 2.0f; // +2px d'espace
|
||||||
|
float totalWidth = endX - triangleStartX;
|
||||||
|
|
||||||
|
if (totalWidth > 0.0f)
|
||||||
|
{
|
||||||
|
for (float x = triangleStartX; x <= endX; x += 1.0f)
|
||||||
|
{
|
||||||
|
float progress = (x - triangleStartX) / totalWidth;
|
||||||
|
// Cette fois, plus on avance vers endX (la droite/base), plus le triangle s'élargit
|
||||||
|
float currentTopY = centerY - (centerY - topY) * progress;
|
||||||
|
float currentBottomY = centerY + (bottomY - centerY) * progress;
|
||||||
|
|
||||||
|
renderer.drawLine({ x, currentTopY }, { x, currentBottomY });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
50
src/gui/mainWindow/layers/controlsLayer.h
Normal file
50
src/gui/mainWindow/layers/controlsLayer.h
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "layer.h"
|
||||||
|
|
||||||
|
class MainWindow;
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
class ControlsLayer : public Layer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum class ButtonType
|
||||||
|
{
|
||||||
|
Play,
|
||||||
|
Pause,
|
||||||
|
Reset
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ButtonStyle
|
||||||
|
{
|
||||||
|
quokka_gfx::FPos pos;
|
||||||
|
quokka_gfx::FSize size;
|
||||||
|
bool isHovered = false;
|
||||||
|
bool isPressed = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
ControlsLayer() = delete; // Default constructor
|
||||||
|
virtual ~ControlsLayer() = default; // Default destructor
|
||||||
|
ControlsLayer(const ControlsLayer &obj) = delete; // Copy constructor
|
||||||
|
ControlsLayer(ControlsLayer &&obj) noexcept = delete; // Move constructor
|
||||||
|
ControlsLayer &operator=(const ControlsLayer &obj) = delete; // Copy assignment operator
|
||||||
|
ControlsLayer &operator=(ControlsLayer &&obj) noexcept = delete; // Move assignment operator
|
||||||
|
|
||||||
|
explicit ControlsLayer(MainWindow &owner); // Constructor
|
||||||
|
|
||||||
|
void update(std::chrono::nanoseconds dt) override; // Update the layer state
|
||||||
|
void render() const override; // Render the layer
|
||||||
|
|
||||||
|
protected:
|
||||||
|
ButtonStyle m_playButtonStyle; // Style for the play button
|
||||||
|
ButtonStyle m_pauseButtonStyle; // Style for the pause button
|
||||||
|
ButtonStyle m_stopButtonStyle; // Style for the stop button
|
||||||
|
|
||||||
|
private:
|
||||||
|
void init();
|
||||||
|
|
||||||
|
void drawPlayButton(const quokka_gfx::Renderer &renderer) const;
|
||||||
|
void drawPauseButton(const quokka_gfx::Renderer &renderer) const;
|
||||||
|
void drawStopButton(const quokka_gfx::Renderer &renderer) const;
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
@@ -9,6 +9,18 @@ Layer::Layer(MainWindow &owner)
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
//--------------------------------------------------------------
|
//--------------------------------------------------------------
|
||||||
|
/* Get the application context instance from the main window */
|
||||||
|
const AppContext *Layer::GetAppContext() const
|
||||||
|
{
|
||||||
|
return &m_owner.m_appContext;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Get the input manager instance from the main window */
|
||||||
|
const quokka_gfx::InputManager *Layer::GetInputManager() const
|
||||||
|
{
|
||||||
|
return m_owner.GetInputManager();
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
/* Get the renderer instance from the main window */
|
/* Get the renderer instance from the main window */
|
||||||
const quokka_gfx::Renderer *Layer::GetRenderer() const
|
const quokka_gfx::Renderer *Layer::GetRenderer() const
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "core/appContext.h"
|
||||||
|
|
||||||
#include <quokka_gfx.h>
|
#include <quokka_gfx.h>
|
||||||
|
|
||||||
class MainWindow;
|
class MainWindow;
|
||||||
@@ -16,9 +18,11 @@ class Layer
|
|||||||
|
|
||||||
explicit Layer(MainWindow &owner); // Constructor
|
explicit Layer(MainWindow &owner); // Constructor
|
||||||
|
|
||||||
|
[[nodiscard]] const AppContext *GetAppContext() const; // Get the application context instance from the main window
|
||||||
|
[[nodiscard]] const quokka_gfx::InputManager *GetInputManager() const; // Get the input manager instance from the main window
|
||||||
[[nodiscard]] const quokka_gfx::Renderer *GetRenderer() const; // Get the renderer instance from the main window
|
[[nodiscard]] const quokka_gfx::Renderer *GetRenderer() const; // Get the renderer instance from the main window
|
||||||
|
|
||||||
virtual void update(float dt) = 0; // Update the layer state
|
virtual void update(std::chrono::nanoseconds dt) = 0; // Update the layer state
|
||||||
virtual void render() const = 0; // Render the layer
|
virtual void render() const = 0; // Render the layer
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
#include "keyboardLayer.h"
|
#include "pianoKeyboardLayer.h"
|
||||||
|
|
||||||
|
#include "gui/mainWindow/mainWindow.h"
|
||||||
|
|
||||||
#include <ranges>
|
#include <ranges>
|
||||||
|
|
||||||
@@ -6,25 +8,38 @@ using namespace std;
|
|||||||
using namespace quokka_gfx;
|
using namespace quokka_gfx;
|
||||||
//--------------------------------------------------------------
|
//--------------------------------------------------------------
|
||||||
/* Constructor */
|
/* Constructor */
|
||||||
KeyboardLayer::KeyboardLayer(MainWindow &owner, const Size &size)
|
PianoKeyboardLayer::PianoKeyboardLayer(MainWindow &owner, const Size &size)
|
||||||
: Layer(owner)
|
: Layer(owner)
|
||||||
{
|
{
|
||||||
// Initialize the piano keys
|
// Initialize the piano keys
|
||||||
initKeys(size);
|
initKeys(size);
|
||||||
}
|
}
|
||||||
//--------------------------------------------------------------
|
//--------------------------------------------------------------
|
||||||
|
/* Update the layer state */
|
||||||
|
void PianoKeyboardLayer::update(std::chrono::nanoseconds dt)
|
||||||
|
{
|
||||||
|
// Reset the pressed state of all keys
|
||||||
|
for (auto ¬e : m_keys | views::values)
|
||||||
|
note.isPressed = false;
|
||||||
|
|
||||||
|
// Get the currently active notes from the player and
|
||||||
|
// update the pressed state of the corresponding keys
|
||||||
|
const auto playingNotes = GetAppContext()->player->getActiveNotes().playing;
|
||||||
|
for (const auto ¬e : playingNotes)
|
||||||
|
m_keys.at(note.pitch).isPressed = true;
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
/* Render the visual piano */
|
/* Render the visual piano */
|
||||||
void KeyboardLayer::render() const
|
void PianoKeyboardLayer::render() const
|
||||||
{
|
{
|
||||||
const auto renderer = GetRenderer(); // Get the renderer instance from the main window
|
const auto renderer = GetRenderer(); // Get the renderer instance from the main window
|
||||||
|
|
||||||
drawKeys(renderer); // Draw the white keys first
|
drawKeys(renderer); // Draw the white keys first
|
||||||
|
|
||||||
drawKeySymbols(renderer); // Draw a symbol for the Middle C key (MIDI note 60)
|
drawKeySymbols(renderer); // Draw a symbol for the Middle C key (MIDI note 60)
|
||||||
}
|
}
|
||||||
//--------------------------------------------------------------
|
//--------------------------------------------------------------
|
||||||
/* Draw the piano keys */
|
/* Draw the piano keys */
|
||||||
void KeyboardLayer::drawKeys(const Renderer *renderer) const
|
void PianoKeyboardLayer::drawKeys(const Renderer *renderer) const
|
||||||
{
|
{
|
||||||
for (const auto &key : m_keys | views::values)
|
for (const auto &key : m_keys | views::values)
|
||||||
{
|
{
|
||||||
@@ -57,7 +72,7 @@ void KeyboardLayer::drawKeys(const Renderer *renderer) const
|
|||||||
}
|
}
|
||||||
//--------------------------------------------------------------
|
//--------------------------------------------------------------
|
||||||
/* Draw symbols on the piano keys (e.g., Middle C marker, ...) */
|
/* Draw symbols on the piano keys (e.g., Middle C marker, ...) */
|
||||||
void KeyboardLayer::drawKeySymbols(const Renderer *renderer) const
|
void PianoKeyboardLayer::drawKeySymbols(const Renderer *renderer) const
|
||||||
{
|
{
|
||||||
// Draw a symbol for the Middle C key (MIDI note 60)
|
// Draw a symbol for the Middle C key (MIDI note 60)
|
||||||
constexpr float markerSize = 12.0f;
|
constexpr float markerSize = 12.0f;
|
||||||
@@ -74,7 +89,7 @@ void KeyboardLayer::drawKeySymbols(const Renderer *renderer) const
|
|||||||
}
|
}
|
||||||
//--------------------------------------------------------------
|
//--------------------------------------------------------------
|
||||||
/* Initialize the piano keys */
|
/* Initialize the piano keys */
|
||||||
void KeyboardLayer::initKeys(const Size &size)
|
void PianoKeyboardLayer::initKeys(const Size &size)
|
||||||
{
|
{
|
||||||
constexpr float WhiteKeyHeight = 200.0f; // Height of the white keys
|
constexpr float WhiteKeyHeight = 200.0f; // Height of the white keys
|
||||||
constexpr float BlackKeyHeight = 120.0f; // Height of the black keys
|
constexpr float BlackKeyHeight = 120.0f; // Height of the black keys
|
||||||
@@ -144,7 +159,7 @@ void KeyboardLayer::initKeys(const Size &size)
|
|||||||
}
|
}
|
||||||
//--------------------------------------------------------------
|
//--------------------------------------------------------------
|
||||||
/* Update the active notes based on the midi notes (0-127) */
|
/* Update the active notes based on the midi notes (0-127) */
|
||||||
void KeyboardLayer::updateActiveNotes(const std::span<const int> notes)
|
void PianoKeyboardLayer::updateActiveNotes(const std::span<const int> notes)
|
||||||
{
|
{
|
||||||
// Reset all keys to not pressed
|
// Reset all keys to not pressed
|
||||||
for (auto &key : m_keys | views::values)
|
for (auto &key : m_keys | views::values)
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
class MainWindow;
|
class MainWindow;
|
||||||
//--------------------------------------------------------------
|
//--------------------------------------------------------------
|
||||||
class KeyboardLayer : public Layer
|
class PianoKeyboardLayer : public Layer
|
||||||
{
|
{
|
||||||
static constexpr int MIN_MIDI_NOTE = 21; // Minimum MIDI note number for a standard piano (A0)
|
static constexpr int MIN_MIDI_NOTE = 21; // Minimum MIDI note number for a standard piano (A0)
|
||||||
static constexpr int MAX_MIDI_NOTE = 108; // Maximum MIDI note number for a standard piano (C8)
|
static constexpr int MAX_MIDI_NOTE = 108; // Maximum MIDI note number for a standard piano (C8)
|
||||||
@@ -28,19 +28,19 @@ class KeyboardLayer : public Layer
|
|||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
KeyboardLayer() = delete; // Default constructor
|
PianoKeyboardLayer() = delete; // Default constructor
|
||||||
virtual ~KeyboardLayer() = default; // Default destructor
|
virtual ~PianoKeyboardLayer() = default; // Default destructor
|
||||||
KeyboardLayer(const KeyboardLayer &obj) = delete; // Copy constructor
|
PianoKeyboardLayer(const PianoKeyboardLayer &obj) = delete; // Copy constructor
|
||||||
KeyboardLayer(KeyboardLayer &&obj) noexcept = delete; // Move constructor
|
PianoKeyboardLayer(PianoKeyboardLayer &&obj) noexcept = delete; // Move constructor
|
||||||
KeyboardLayer &operator=(const KeyboardLayer &obj) = delete; // Copy assignment operator
|
PianoKeyboardLayer &operator=(const PianoKeyboardLayer &obj) = delete; // Copy assignment operator
|
||||||
KeyboardLayer &operator=(KeyboardLayer &&obj) noexcept = delete; // Move assignment operator
|
PianoKeyboardLayer &operator=(PianoKeyboardLayer &&obj) noexcept = delete; // Move assignment operator
|
||||||
|
|
||||||
explicit KeyboardLayer(MainWindow &owner, const quokka_gfx::Size &size); // Constructor
|
explicit PianoKeyboardLayer(MainWindow &owner, const quokka_gfx::Size &size); // Constructor
|
||||||
|
|
||||||
void initKeys(const quokka_gfx::Size &size); // Initialize the piano keys
|
void initKeys(const quokka_gfx::Size &size); // Initialize the piano keys
|
||||||
void updateActiveNotes(const std::span<const int> notes); // Update the active notes based on the midi notes (0-127)
|
void updateActiveNotes(const std::span<const int> notes); // Update the active notes based on the midi notes (0-127)
|
||||||
|
|
||||||
void update(float dt) override {} // Update the layer state
|
void update(std::chrono::nanoseconds dt) override; // Update the layer state
|
||||||
void render() const override; // Render the layer
|
void render() const override; // Render the layer
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
24
src/gui/mainWindow/layers/pianoRollLayer.cpp
Normal file
24
src/gui/mainWindow/layers/pianoRollLayer.cpp
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#include "pianoRollLayer.h"
|
||||||
|
|
||||||
|
#include "gui/mainWindow/mainWindow.h"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
using namespace quokka_gfx;
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Constructor */
|
||||||
|
PianoRollLayer::PianoRollLayer(MainWindow &owner)
|
||||||
|
: Layer(owner)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Update the layer state */
|
||||||
|
void PianoRollLayer::update(std::chrono::nanoseconds dt)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
/* Render the layer */
|
||||||
|
void PianoRollLayer::render() const
|
||||||
|
{
|
||||||
|
const auto renderer = GetRenderer(); // Get the renderer instance from the main window
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------------
|
||||||
26
src/gui/mainWindow/layers/pianoRollLayer.h
Normal file
26
src/gui/mainWindow/layers/pianoRollLayer.h
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "core/track/trackDefs.h"
|
||||||
|
#include "layer.h"
|
||||||
|
|
||||||
|
class MainWindow;
|
||||||
|
//--------------------------------------------------------------
|
||||||
|
class PianoRollLayer : public Layer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
PianoRollLayer() = delete; // Default constructor
|
||||||
|
virtual ~PianoRollLayer() = default; // Default destructor
|
||||||
|
PianoRollLayer(const PianoRollLayer &obj) = delete; // Copy constructor
|
||||||
|
PianoRollLayer(PianoRollLayer &&obj) noexcept = delete; // Move constructor
|
||||||
|
PianoRollLayer &operator=(const PianoRollLayer &obj) = delete; // Copy assignment operator
|
||||||
|
PianoRollLayer &operator=(PianoRollLayer &&obj) noexcept = delete; // Move assignment operator
|
||||||
|
|
||||||
|
explicit PianoRollLayer(MainWindow &owner); // Constructor
|
||||||
|
|
||||||
|
void update(std::chrono::nanoseconds dt) override; // Update the layer state
|
||||||
|
void render() const override; // Render the layer
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::chrono::milliseconds m_visibleDuration = std::chrono::seconds(15); // Duration of the visible track window in milliseconds
|
||||||
|
};
|
||||||
|
//--------------------------------------------------------------
|
||||||
@@ -8,9 +8,12 @@ using namespace quokka_gfx;
|
|||||||
/* Constructor */
|
/* Constructor */
|
||||||
MainWindow::MainWindow(AppContext &appContext, quokka_gfx::Application &app)
|
MainWindow::MainWindow(AppContext &appContext, quokka_gfx::Application &app)
|
||||||
: Window(app, "VolaTile", { .w = 1024, .h = 768 }, SDL_WINDOW_RESIZABLE /*| SDL_WINDOW_MAXIMIZED*/)
|
: Window(app, "VolaTile", { .w = 1024, .h = 768 }, SDL_WINDOW_RESIZABLE /*| SDL_WINDOW_MAXIMIZED*/)
|
||||||
|
, Node(appContext.eventBus)
|
||||||
, m_appContext(appContext)
|
, m_appContext(appContext)
|
||||||
, m_backgroundLayer(*this)
|
, m_backgroundLayer(*this)
|
||||||
, m_keyboardLayer(*this, { .w = 1024, .h = 768 })
|
, m_controlsLayer(*this)
|
||||||
|
, m_pianoRollLayer(*this)
|
||||||
|
, m_pianoKeyboardLayer(*this, { .w = 1024, .h = 768 })
|
||||||
{
|
{
|
||||||
// Initialization
|
// Initialization
|
||||||
SetMinSize({ .w = 1024, .h = 768 });
|
SetMinSize({ .w = 1024, .h = 768 });
|
||||||
@@ -48,39 +51,37 @@ void MainWindow::OnEvent(SDL_Event *event)
|
|||||||
}
|
}
|
||||||
//--------------------------------------------------------------
|
//--------------------------------------------------------------
|
||||||
/* Update the window state */
|
/* Update the window state */
|
||||||
void MainWindow::Update(const float dt)
|
void MainWindow::Update(const std::chrono::nanoseconds dt)
|
||||||
{
|
{
|
||||||
|
// Handle input events (e.g., check for ESC key to close the window)
|
||||||
const auto &inputManager = GetInputManager();
|
const auto &inputManager = GetInputManager();
|
||||||
if (inputManager->isKeyPressed(SDL_SCANCODE_ESCAPE))
|
if (inputManager->isKeyPressed(SDL_SCANCODE_ESCAPE))
|
||||||
Close();
|
Close();
|
||||||
|
|
||||||
// Manage playback state based on user input
|
if (inputManager->isKeyPressed(SDL_SCANCODE_SPACE))
|
||||||
if (inputManager->isKeyPressed(SDL_SCANCODE_S))
|
|
||||||
m_playbackState = PlaybackState::Stopped;
|
|
||||||
if (inputManager->isKeyPressed(SDL_SCANCODE_P))
|
|
||||||
{
|
{
|
||||||
if (m_playbackState == PlaybackState::Playing)
|
SDL_ShowOpenFileDialog([](void *userdata, const char *const *filelist, int filter)
|
||||||
m_playbackState = PlaybackState::Paused;
|
{
|
||||||
else
|
if (!filelist || !filelist[0])
|
||||||
m_playbackState = PlaybackState::Playing;
|
return;
|
||||||
|
const auto pThis = static_cast<MainWindow *>(userdata);
|
||||||
|
pThis->emit<GenericMessageEvent>(HashMessageType("remote.loadFile"), string(filelist[0])); },
|
||||||
|
this,
|
||||||
|
GetNativeWindow(),
|
||||||
|
nullptr,
|
||||||
|
0,
|
||||||
|
nullptr,
|
||||||
|
false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve active notes and upcoming notes from the track based on the current music time
|
// Update the current music time based on playback state
|
||||||
updateMusicTime(dt); // Update music time
|
m_appContext.player->update();
|
||||||
const auto notes = m_appContext.m_hTrack->getTrackWindow(m_musicTime, m_musicTime + 1000ms);
|
|
||||||
|
|
||||||
// Update the visual piano with the active notes
|
// Update the layers state and layout
|
||||||
std::vector<int> activeMidiNotes;
|
|
||||||
for (const auto note : notes.activeNotes)
|
|
||||||
{
|
|
||||||
if (note.noteOn)
|
|
||||||
activeMidiNotes.push_back(note.pitch);
|
|
||||||
}
|
|
||||||
m_keyboardLayer.updateActiveNotes(activeMidiNotes);
|
|
||||||
|
|
||||||
// Update the layers
|
|
||||||
m_backgroundLayer.update(dt);
|
m_backgroundLayer.update(dt);
|
||||||
m_keyboardLayer.update(dt);
|
m_controlsLayer.update(dt);
|
||||||
|
m_pianoRollLayer.update(dt);
|
||||||
|
m_pianoKeyboardLayer.update(dt);
|
||||||
}
|
}
|
||||||
//--------------------------------------------------------------
|
//--------------------------------------------------------------
|
||||||
/* Draw the window content (returns true if the window content was drawn, false otherwise) */
|
/* Draw the window content (returns true if the window content was drawn, false otherwise) */
|
||||||
@@ -94,7 +95,9 @@ bool MainWindow::Draw()
|
|||||||
|
|
||||||
// Render the layers in the correct Z-order (from back to front)
|
// Render the layers in the correct Z-order (from back to front)
|
||||||
m_backgroundLayer.render();
|
m_backgroundLayer.render();
|
||||||
m_keyboardLayer.render();
|
m_controlsLayer.render();
|
||||||
|
m_pianoRollLayer.render();
|
||||||
|
m_pianoKeyboardLayer.render();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -105,26 +108,6 @@ void MainWindow::onResize(const Size &newSize)
|
|||||||
// The window has been resized, it is necessary to update
|
// The window has been resized, it is necessary to update
|
||||||
// the visual piano layout to fit the new window size. For
|
// the visual piano layout to fit the new window size. For
|
||||||
// simplicity, we will just reinitialize the keys
|
// simplicity, we will just reinitialize the keys
|
||||||
m_keyboardLayer.initKeys(newSize);
|
m_pianoKeyboardLayer.initKeys(newSize);
|
||||||
}
|
|
||||||
//--------------------------------------------------------------
|
|
||||||
/* Update the current music time based on playback state */
|
|
||||||
void MainWindow::updateMusicTime(const float dt)
|
|
||||||
{
|
|
||||||
// dt is the delta time in seconds since the last update
|
|
||||||
switch (m_playbackState)
|
|
||||||
{
|
|
||||||
case PlaybackState::Stopped:
|
|
||||||
// Reset music time to zero
|
|
||||||
m_musicTime = {};
|
|
||||||
break;
|
|
||||||
case PlaybackState::Playing:
|
|
||||||
// Update music time based on delta time
|
|
||||||
m_musicTime += std::chrono::milliseconds(static_cast<int>((dt * 5) * 1000.0f));
|
|
||||||
break;
|
|
||||||
case PlaybackState::Paused:
|
|
||||||
// Do not update music time when paused
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
//--------------------------------------------------------------
|
//--------------------------------------------------------------
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "core/eventBus/eventBus.h"
|
||||||
#include "layers/backgroundLayer.h"
|
#include "layers/backgroundLayer.h"
|
||||||
#include "layers/keyboardLayer.h"
|
#include "layers/controlsLayer.h"
|
||||||
|
#include "layers/pianoKeyboardLayer.h"
|
||||||
|
#include "layers/pianoRollLayer.h"
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <quokka_gfx.h>
|
#include <quokka_gfx.h>
|
||||||
@@ -9,6 +12,7 @@
|
|||||||
class AppContext;
|
class AppContext;
|
||||||
//--------------------------------------------------------------
|
//--------------------------------------------------------------
|
||||||
class MainWindow : public quokka_gfx::Window
|
class MainWindow : public quokka_gfx::Window
|
||||||
|
, public sdi_toolBox::desktop::eventBus::Node
|
||||||
{
|
{
|
||||||
friend class Layer;
|
friend class Layer;
|
||||||
|
|
||||||
@@ -33,7 +37,7 @@ class MainWindow : public quokka_gfx::Window
|
|||||||
quokka_gfx::Application &app);
|
quokka_gfx::Application &app);
|
||||||
|
|
||||||
void OnEvent(SDL_Event *event) override; // Handle event
|
void OnEvent(SDL_Event *event) override; // Handle event
|
||||||
void Update(float dt) override; // Update the window state
|
void Update(std::chrono::nanoseconds dt) override; // Update the window state
|
||||||
bool Draw() override; // Draw the window content
|
bool Draw() override; // Draw the window content
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
@@ -41,14 +45,14 @@ class MainWindow : public quokka_gfx::Window
|
|||||||
|
|
||||||
// Layers
|
// Layers
|
||||||
BackgroundLayer m_backgroundLayer; // Background layer instance
|
BackgroundLayer m_backgroundLayer; // Background layer instance
|
||||||
KeyboardLayer m_keyboardLayer; // Keyboard layer instance
|
ControlsLayer m_controlsLayer; // Player controls layer instance
|
||||||
|
PianoRollLayer m_pianoRollLayer; // Piano roll layer instance
|
||||||
|
PianoKeyboardLayer m_pianoKeyboardLayer; // Piano keyboard layer instance
|
||||||
|
|
||||||
PlaybackState m_playbackState = PlaybackState::Playing; // Current playback state
|
PlaybackState m_playbackState = PlaybackState::Playing; // Current playback state
|
||||||
Timestamp m_musicTime = {}; // Current music time in milliseconds
|
Timestamp m_musicTime = {}; // Current music time in milliseconds
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void onResize(const quokka_gfx::Size &newSize); // Handle window resize event
|
void onResize(const quokka_gfx::Size &newSize); // Handle window resize event
|
||||||
|
|
||||||
void updateMusicTime(float dt); // Update the current music time based on playback state
|
|
||||||
};
|
};
|
||||||
//--------------------------------------------------------------
|
//--------------------------------------------------------------
|
||||||
|
|||||||
45
src/main.cpp
45
src/main.cpp
@@ -1,4 +1,5 @@
|
|||||||
#include "core/appContext.h"
|
#include "core/appContext.h"
|
||||||
|
#include "core/track/Track.h"
|
||||||
#include "gui/mainWindow/mainWindow.h"
|
#include "gui/mainWindow/mainWindow.h"
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
@@ -20,8 +21,48 @@ int main(int argc, char *argv[])
|
|||||||
|
|
||||||
// appContext.m_hServer->enable("127.0.0.1", 4000);
|
// appContext.m_hServer->enable("127.0.0.1", 4000);
|
||||||
|
|
||||||
appContext.m_hTrack->loadFromFile("testFile.mid");
|
// track.loadFromFile(R"(c:\Users\sschn\dev\projects\gitea.hub.saturnux.com\volaTile\do4.mid)");
|
||||||
// appContext.m_hTrack->debug();
|
// track.loadFromFile(R"(c:\Users\sschn\dev\projects\gitea.hub.saturnux.com\volaTile\gameUP.mid)");
|
||||||
|
appContext.player->loadFile(R"(c:\Users\sschn\dev\projects\gitea.hub.saturnux.com\volaTile\export.mid)");
|
||||||
|
////track.debug();
|
||||||
|
|
||||||
|
//const auto trackDuration = track::Seconds(5);
|
||||||
|
//auto startTime = track::Seconds();
|
||||||
|
//constexpr auto stepInterval = chrono::milliseconds(100);
|
||||||
|
|
||||||
|
//track::ActiveNotes notes; // Instance to hold the active notes at each time step with limited memory allocation overhead
|
||||||
|
//while (startTime < trackDuration)
|
||||||
|
//{
|
||||||
|
// const auto notesProxy = appContext.player->getNotes();
|
||||||
|
// notesProxy.getActiveNotesAt(startTime, WindowDuration, notes);
|
||||||
|
|
||||||
|
// ostringstream notesDisplay;
|
||||||
|
// notesDisplay << "Time: " << startTime << "\n";
|
||||||
|
|
||||||
|
// // Display the active notes for the current time step
|
||||||
|
// notesDisplay << "Active Notes:\n";
|
||||||
|
// for (const auto ¬e : notes.playing)
|
||||||
|
// {
|
||||||
|
// notesDisplay << format("{}{} ",
|
||||||
|
// note.name,
|
||||||
|
// note.octave);
|
||||||
|
// }
|
||||||
|
// notesDisplay << "\n";
|
||||||
|
|
||||||
|
// // Display the upcoming notes for the current time step
|
||||||
|
// notesDisplay << "Upcoming Notes:\n";
|
||||||
|
// for (const auto ¬e : notes.upcoming)
|
||||||
|
// {
|
||||||
|
// notesDisplay << format("{}{} ",
|
||||||
|
// note.name,
|
||||||
|
// note.octave);
|
||||||
|
// }
|
||||||
|
// notesDisplay << "\n";
|
||||||
|
|
||||||
|
// cout << notesDisplay.str() << endl;
|
||||||
|
// // Increase the start time by the step interval for the next iteration
|
||||||
|
// startTime += stepInterval;
|
||||||
|
//}
|
||||||
|
|
||||||
// Create an instance of the GUI application class
|
// Create an instance of the GUI application class
|
||||||
quokka_gfx::Application app;
|
quokka_gfx::Application app;
|
||||||
|
|||||||
Reference in New Issue
Block a user