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 <algorithm>
|
||||
#include <chrono>
|
||||
|
||||
using namespace std;
|
||||
using namespace quokka_gfx;
|
||||
@@ -24,16 +25,15 @@ WindowManager *Application::GetWindowManager() noexcept
|
||||
void Application::Run() const
|
||||
{
|
||||
// Main loop
|
||||
bool running = true;
|
||||
uint64_t lastTime = SDL_GetTicksNS();
|
||||
bool running = true;
|
||||
auto lastTime = std::chrono::nanoseconds(SDL_GetTicksNS());
|
||||
|
||||
while (running && m_windowManager.HasAnyWindow())
|
||||
{
|
||||
// Calculate delta time
|
||||
const uint64_t currentTime = SDL_GetTicksNS();
|
||||
float dt = static_cast<float>(currentTime - lastTime) / 1e9f; // Convert nanoseconds to seconds
|
||||
lastTime = currentTime;
|
||||
dt = std::max(dt, 0.00001f); // Avoid zero or negative delta time
|
||||
const auto currentTime = std::chrono::nanoseconds(SDL_GetTicksNS());
|
||||
const auto dt = currentTime - lastTime;
|
||||
lastTime = currentTime;
|
||||
|
||||
// Poll and process events
|
||||
const auto result = m_eventManager.ProcessEvents();
|
||||
|
||||
@@ -84,7 +84,7 @@ void Window::SetMinSize(const Size &size) const
|
||||
void Window::OnEvent(SDL_Event *event) {}
|
||||
//--------------------------------------------------------------
|
||||
/* Update the window state */
|
||||
void Window::Update(const float dt)
|
||||
void Window::Update(std::chrono::nanoseconds dt)
|
||||
{
|
||||
// Nothing to do
|
||||
}
|
||||
@@ -101,10 +101,6 @@ void Window::Render()
|
||||
if (!m_renderer)
|
||||
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();
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "utils/Coords.h"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
@@ -41,14 +42,14 @@ class Window
|
||||
void Show(bool show = true) const; // Show or hide the window
|
||||
void Hide() const; // Hide the window
|
||||
|
||||
void Close(); // Close the window
|
||||
void SetMinSize(const Size &size) const; // Set the minimum size of the window
|
||||
void Close(); // Close the window
|
||||
void SetMinSize(const Size &size) const; // Set the minimum size of the window
|
||||
|
||||
// Window lifecycle functions
|
||||
virtual void OnEvent(SDL_Event *event); // Handle event
|
||||
virtual void Update(float dt); // Update the window state
|
||||
virtual bool Draw(); // Draw the window content
|
||||
void Render(); // Render the window
|
||||
virtual void OnEvent(SDL_Event *event); // Handle event
|
||||
virtual void Update(std::chrono::nanoseconds dt); // Update the window state
|
||||
virtual bool Draw(); // Draw the window content
|
||||
void Render(); // Render the window
|
||||
|
||||
protected:
|
||||
Application &m_appOwner; // Reference to the owning Application instance
|
||||
|
||||
@@ -63,7 +63,7 @@ void WindowManager::OnEvent(SDL_Event *event)
|
||||
}
|
||||
//--------------------------------------------------------------
|
||||
/* 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
|
||||
for (const auto &window : m_windows)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "../Window/Window.h"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <chrono>
|
||||
#include <vector>
|
||||
|
||||
namespace quokka_gfx
|
||||
@@ -33,9 +34,9 @@ class WindowManager
|
||||
protected:
|
||||
WindowList m_windows; // Vector to store unique pointers to windows
|
||||
|
||||
void OnEvent(SDL_Event *event); // Handle event for all windows
|
||||
void Update(float dt) const; // Update all windows
|
||||
void Render() const; // Render all windows
|
||||
void OnEvent(SDL_Event *event); // Handle event for all windows
|
||||
void Update(std::chrono::nanoseconds dt) const; // Update all windows
|
||||
void Render() const; // Render all windows
|
||||
};
|
||||
//--------------------------------------------------------------
|
||||
} // namespace quokka_gfx
|
||||
|
||||
@@ -27,6 +27,11 @@ struct FPos
|
||||
{
|
||||
float x = 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
|
||||
|
||||
@@ -95,21 +95,7 @@ constexpr std::uint64_t fnv1a_64(std::span<const std::byte> data) noexcept;
|
||||
* @param sv Input string view.
|
||||
* @return std::uint64_t The 64-bit FNV-1a hash.
|
||||
*/
|
||||
constexpr std::uint64_t fnv1a_64(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;
|
||||
constexpr std::uint64_t fnv1a_64_sv(std::string_view sv) 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 */
|
||||
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
|
||||
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;
|
||||
}
|
||||
//--------------------------------------------------------------
|
||||
/* 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
|
||||
|
||||
@@ -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
|
||||
{
|
||||
scoped_lock lock(m_notes.mtx); // Lock the mutex to protect access to the data structures
|
||||
m_notes.list = std::move(notes);
|
||||
m_notes = std::move(notes);
|
||||
|
||||
ostringstream logMessage;
|
||||
logMessage << "MIDI parsing completed\n"
|
||||
<< " Total notes: " << m_notes.list.size() << "\n"
|
||||
<< " Total notes: " << m_notes.size() << "\n"
|
||||
<< " Total duration: " << lastTime;
|
||||
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 */
|
||||
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.list.empty();
|
||||
return m_notes.empty();
|
||||
}
|
||||
//--------------------------------------------------------------
|
||||
/* Get the duration of the track */
|
||||
Seconds Track::getDuration() const
|
||||
{
|
||||
scoped_lock lock(m_notes.mtx); // Lock the mutex to protect access to the data structures
|
||||
|
||||
if (m_notes.list.empty())
|
||||
if (m_notes.empty())
|
||||
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
|
||||
}
|
||||
//--------------------------------------------------------------
|
||||
/* Debug function to print the track data */
|
||||
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.list)
|
||||
for (const auto ¬e : m_notes)
|
||||
{
|
||||
logInfo(std::format(
|
||||
"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 */
|
||||
TrackLockProxy Track::getNotes() const
|
||||
{
|
||||
// The TrackLockProxy is automatically moved when returned (NRVO)
|
||||
return TrackLockProxy(m_notes.mtx, m_notes.list);
|
||||
}
|
||||
//--------------------------------------------------------------
|
||||
///* Get a lock proxy to access the notes safely without copying the data */
|
||||
//TrackLockProxy Track::getNotes() const
|
||||
//{
|
||||
// // The TrackLockProxy is automatically moved when returned (NRVO)
|
||||
// return TrackLockProxy(m_notes.mtx, m_notes.list);
|
||||
//}
|
||||
////--------------------------------------------------------------
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "trackDefs.h"
|
||||
#include "trackLockProxy.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
@@ -16,35 +15,28 @@ namespace track
|
||||
//--------------------------------------------------------------
|
||||
class Track
|
||||
{
|
||||
friend class TrackLockProxy; // Allow TrackLockProxy to access private members of Track
|
||||
|
||||
public:
|
||||
Track() = default; // Default constructor
|
||||
virtual ~Track() = default; // Default destructor
|
||||
Track(const Track &obj) = delete; // Copy constructor
|
||||
Track(Track &&obj) noexcept = delete; // Move constructor
|
||||
Track &operator=(const Track &obj) = delete; // Copy assignment operator
|
||||
Track &operator=(Track &&obj) noexcept = delete; // Move assignment operator
|
||||
Track(const Track &obj) = default; // Copy constructor
|
||||
Track(Track &&obj) noexcept = default; // Move constructor
|
||||
Track &operator=(const Track &obj) = default; // Copy assignment operator
|
||||
Track &operator=(Track &&obj) noexcept = default; // Move assignment operator
|
||||
|
||||
void logInfo(const std::string &message) const; // Log an informational 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 loadFromMemory(std::span<uint8_t> midiBytes); // Load a MIDI file from memory
|
||||
void clear(); // Clear the track data
|
||||
[[nodiscard]] bool isLoaded() const; // Check if a MIDI file is loaded
|
||||
[[nodiscard]] bool isEmpty() const; // Check if a MIDI file is loaded
|
||||
[[nodiscard]] Seconds getDuration() const; // Get the duration of the track
|
||||
|
||||
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:
|
||||
struct
|
||||
{
|
||||
mutable std::mutex mtx; // Protects access to the notes vector
|
||||
NotesBuffer list; // Vector to store notes
|
||||
} m_notes;
|
||||
NotesBuffer m_notes; // Vector to store notes
|
||||
};
|
||||
//--------------------------------------------------------------
|
||||
} // namespace track
|
||||
|
||||
@@ -7,11 +7,12 @@ using namespace std;
|
||||
//--------------------------------------------------------------
|
||||
/* Default constructor */
|
||||
AppContext::AppContext()
|
||||
: logger(eventBus)
|
||||
{
|
||||
// Create an instance of the Track
|
||||
m_hTrack = make_unique<Track>();
|
||||
|
||||
// Create an instance of the HTTP server
|
||||
m_hServer = make_unique<HttpServer>();
|
||||
|
||||
// Create an instance of the Player
|
||||
player = make_unique<player::Player>(eventBus);
|
||||
}
|
||||
//--------------------------------------------------------------
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "core/HttpServer/IHttpServer.h"
|
||||
#include "core/Track/ITrack.h"
|
||||
#include "logger/logger.h"
|
||||
#include "player/player.h"
|
||||
|
||||
#include <memory>
|
||||
#include <sdi_toolBox/desktop/eventBus/bus.h>
|
||||
|
||||
//--------------------------------------------------------------
|
||||
class AppContext
|
||||
@@ -16,7 +18,11 @@ class AppContext
|
||||
AppContext &operator=(const AppContext &obj) = delete; // Copy 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<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 "core/track/TrackLockProxy.h"
|
||||
#include "playerDefs.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
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
|
||||
|
||||
//--------------------------------------------------------------
|
||||
class Player
|
||||
#include "core/eventBus/eventBus.h"
|
||||
#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:
|
||||
Player() = default; // Default constructor
|
||||
Player() = delete; // Default constructor
|
||||
virtual ~Player() = default; // Default destructor
|
||||
Player(const Player &obj) = delete; // Copy constructor
|
||||
Player(Player &&obj) noexcept = delete; // Move constructor
|
||||
Player &operator=(const Player &obj) = delete; // Copy 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 "Track.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace track;
|
||||
//--------------------------------------------------------------
|
||||
/* Constructor */
|
||||
TrackLockProxy::TrackLockProxy(std::mutex &mtx, const NotesView notes)
|
||||
TrackLockProxy::TrackLockProxy(std::mutex &mtx, const Track *track)
|
||||
: m_lock(mtx)
|
||||
, m_notes(notes)
|
||||
, m_track(track)
|
||||
{
|
||||
m_notes = m_track->m_notes;
|
||||
}
|
||||
//--------------------------------------------------------------
|
||||
/* Access a note by index */
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
namespace track
|
||||
{
|
||||
class Track;
|
||||
//--------------------------------------------------------------
|
||||
class TrackLockProxy final
|
||||
{
|
||||
@@ -15,9 +16,9 @@ class TrackLockProxy final
|
||||
TrackLockProxy(const TrackLockProxy &obj) = delete; // Copy constructor
|
||||
TrackLockProxy(TrackLockProxy &&obj) noexcept = default; // Move constructor
|
||||
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
|
||||
|
||||
@@ -39,7 +40,8 @@ class TrackLockProxy final
|
||||
protected:
|
||||
std::unique_lock<std::mutex> m_lock; // Keeps the mutex locked for the lifetime of the proxy
|
||||
|
||||
NotesView m_notes; // Span of notes to access without copying the data
|
||||
const Track *m_track; // Pointer to the track to access the notes
|
||||
NotesView m_notes; // Span of notes to access without copying the data
|
||||
};
|
||||
//--------------------------------------------------------------
|
||||
} // namespace track
|
||||
|
||||
@@ -16,7 +16,7 @@ class BackgroundLayer : public Layer
|
||||
|
||||
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
|
||||
|
||||
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 */
|
||||
const quokka_gfx::Renderer *Layer::GetRenderer() const
|
||||
{
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "core/appContext.h"
|
||||
|
||||
#include <quokka_gfx.h>
|
||||
|
||||
class MainWindow;
|
||||
@@ -16,10 +18,12 @@ class Layer
|
||||
|
||||
explicit Layer(MainWindow &owner); // Constructor
|
||||
|
||||
[[nodiscard]] const quokka_gfx::Renderer *GetRenderer() const; // Get the renderer instance from the main window
|
||||
[[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
|
||||
|
||||
virtual void update(float dt) = 0; // Update the layer state
|
||||
virtual void render() const = 0; // Render the layer
|
||||
virtual void update(std::chrono::nanoseconds dt) = 0; // Update the layer state
|
||||
virtual void render() const = 0; // Render the layer
|
||||
|
||||
protected:
|
||||
MainWindow &m_owner; // Reference to the main window
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#include "keyboardLayer.h"
|
||||
#include "pianoKeyboardLayer.h"
|
||||
|
||||
#include "gui/mainWindow/mainWindow.h"
|
||||
|
||||
#include <ranges>
|
||||
|
||||
@@ -6,25 +8,38 @@ using namespace std;
|
||||
using namespace quokka_gfx;
|
||||
//--------------------------------------------------------------
|
||||
/* Constructor */
|
||||
KeyboardLayer::KeyboardLayer(MainWindow &owner, const Size &size)
|
||||
PianoKeyboardLayer::PianoKeyboardLayer(MainWindow &owner, const Size &size)
|
||||
: Layer(owner)
|
||||
{
|
||||
// Initialize the piano keys
|
||||
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 */
|
||||
void KeyboardLayer::render() const
|
||||
void PianoKeyboardLayer::render() const
|
||||
{
|
||||
const auto renderer = GetRenderer(); // Get the renderer instance from the main window
|
||||
|
||||
drawKeys(renderer); // Draw the white keys first
|
||||
|
||||
drawKeySymbols(renderer); // Draw a symbol for the Middle C key (MIDI note 60)
|
||||
}
|
||||
//--------------------------------------------------------------
|
||||
/* 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)
|
||||
{
|
||||
@@ -57,7 +72,7 @@ void KeyboardLayer::drawKeys(const Renderer *renderer) const
|
||||
}
|
||||
//--------------------------------------------------------------
|
||||
/* 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)
|
||||
constexpr float markerSize = 12.0f;
|
||||
@@ -74,7 +89,7 @@ void KeyboardLayer::drawKeySymbols(const Renderer *renderer) const
|
||||
}
|
||||
//--------------------------------------------------------------
|
||||
/* 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 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) */
|
||||
void KeyboardLayer::updateActiveNotes(const std::span<const int> notes)
|
||||
void PianoKeyboardLayer::updateActiveNotes(const std::span<const int> notes)
|
||||
{
|
||||
// Reset all keys to not pressed
|
||||
for (auto &key : m_keys | views::values)
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
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 MAX_MIDI_NOTE = 108; // Maximum MIDI note number for a standard piano (C8)
|
||||
@@ -28,20 +28,20 @@ class KeyboardLayer : public Layer
|
||||
};
|
||||
|
||||
public:
|
||||
KeyboardLayer() = delete; // Default constructor
|
||||
virtual ~KeyboardLayer() = default; // Default destructor
|
||||
KeyboardLayer(const KeyboardLayer &obj) = delete; // Copy constructor
|
||||
KeyboardLayer(KeyboardLayer &&obj) noexcept = delete; // Move constructor
|
||||
KeyboardLayer &operator=(const KeyboardLayer &obj) = delete; // Copy assignment operator
|
||||
KeyboardLayer &operator=(KeyboardLayer &&obj) noexcept = delete; // Move assignment operator
|
||||
PianoKeyboardLayer() = delete; // Default constructor
|
||||
virtual ~PianoKeyboardLayer() = default; // Default destructor
|
||||
PianoKeyboardLayer(const PianoKeyboardLayer &obj) = delete; // Copy constructor
|
||||
PianoKeyboardLayer(PianoKeyboardLayer &&obj) noexcept = delete; // Move constructor
|
||||
PianoKeyboardLayer &operator=(const PianoKeyboardLayer &obj) = delete; // Copy 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 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 render() const override; // Render the layer
|
||||
void update(std::chrono::nanoseconds dt) override; // Update the layer state
|
||||
void render() const override; // Render the layer
|
||||
|
||||
protected:
|
||||
std::unordered_map<int, PianoKey> m_keys; // Map of MIDI note to piano key
|
||||
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 */
|
||||
MainWindow::MainWindow(AppContext &appContext, quokka_gfx::Application &app)
|
||||
: Window(app, "VolaTile", { .w = 1024, .h = 768 }, SDL_WINDOW_RESIZABLE /*| SDL_WINDOW_MAXIMIZED*/)
|
||||
, Node(appContext.eventBus)
|
||||
, m_appContext(appContext)
|
||||
, m_backgroundLayer(*this)
|
||||
, m_keyboardLayer(*this, { .w = 1024, .h = 768 })
|
||||
, m_controlsLayer(*this)
|
||||
, m_pianoRollLayer(*this)
|
||||
, m_pianoKeyboardLayer(*this, { .w = 1024, .h = 768 })
|
||||
{
|
||||
// Initialization
|
||||
SetMinSize({ .w = 1024, .h = 768 });
|
||||
@@ -48,39 +51,37 @@ void MainWindow::OnEvent(SDL_Event *event)
|
||||
}
|
||||
//--------------------------------------------------------------
|
||||
/* 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();
|
||||
if (inputManager->isKeyPressed(SDL_SCANCODE_ESCAPE))
|
||||
Close();
|
||||
|
||||
// Manage playback state based on user input
|
||||
if (inputManager->isKeyPressed(SDL_SCANCODE_S))
|
||||
m_playbackState = PlaybackState::Stopped;
|
||||
if (inputManager->isKeyPressed(SDL_SCANCODE_P))
|
||||
if (inputManager->isKeyPressed(SDL_SCANCODE_SPACE))
|
||||
{
|
||||
if (m_playbackState == PlaybackState::Playing)
|
||||
m_playbackState = PlaybackState::Paused;
|
||||
else
|
||||
m_playbackState = PlaybackState::Playing;
|
||||
SDL_ShowOpenFileDialog([](void *userdata, const char *const *filelist, int filter)
|
||||
{
|
||||
if (!filelist || !filelist[0])
|
||||
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
|
||||
updateMusicTime(dt); // Update music time
|
||||
const auto notes = m_appContext.m_hTrack->getTrackWindow(m_musicTime, m_musicTime + 1000ms);
|
||||
// Update the current music time based on playback state
|
||||
m_appContext.player->update();
|
||||
|
||||
// Update the visual piano with the active notes
|
||||
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
|
||||
// Update the layers state and layout
|
||||
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) */
|
||||
@@ -94,7 +95,9 @@ bool MainWindow::Draw()
|
||||
|
||||
// Render the layers in the correct Z-order (from back to front)
|
||||
m_backgroundLayer.render();
|
||||
m_keyboardLayer.render();
|
||||
m_controlsLayer.render();
|
||||
m_pianoRollLayer.render();
|
||||
m_pianoKeyboardLayer.render();
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -105,26 +108,6 @@ void MainWindow::onResize(const Size &newSize)
|
||||
// The window has been resized, it is necessary to update
|
||||
// the visual piano layout to fit the new window size. For
|
||||
// simplicity, we will just reinitialize the keys
|
||||
m_keyboardLayer.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;
|
||||
}
|
||||
m_pianoKeyboardLayer.initKeys(newSize);
|
||||
}
|
||||
//--------------------------------------------------------------
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include "core/eventBus/eventBus.h"
|
||||
#include "layers/backgroundLayer.h"
|
||||
#include "layers/keyboardLayer.h"
|
||||
#include "layers/controlsLayer.h"
|
||||
#include "layers/pianoKeyboardLayer.h"
|
||||
#include "layers/pianoRollLayer.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <quokka_gfx.h>
|
||||
@@ -9,6 +12,7 @@
|
||||
class AppContext;
|
||||
//--------------------------------------------------------------
|
||||
class MainWindow : public quokka_gfx::Window
|
||||
, public sdi_toolBox::desktop::eventBus::Node
|
||||
{
|
||||
friend class Layer;
|
||||
|
||||
@@ -29,26 +33,26 @@ class MainWindow : public quokka_gfx::Window
|
||||
MainWindow &operator=(const MainWindow &obj) = delete; // Copy assignment operator
|
||||
MainWindow &operator=(MainWindow &&obj) noexcept = delete; // Move assignment operator
|
||||
|
||||
explicit MainWindow(AppContext &appContext, // Constructor
|
||||
explicit MainWindow(AppContext &appContext, // Constructor
|
||||
quokka_gfx::Application &app);
|
||||
|
||||
void OnEvent(SDL_Event *event) override; // Handle event
|
||||
void Update(float dt) override; // Update the window state
|
||||
bool Draw() override; // Draw the window content
|
||||
void OnEvent(SDL_Event *event) override; // Handle event
|
||||
void Update(std::chrono::nanoseconds dt) override; // Update the window state
|
||||
bool Draw() override; // Draw the window content
|
||||
|
||||
protected:
|
||||
AppContext &m_appContext; // Reference to the application context
|
||||
|
||||
// Layers
|
||||
BackgroundLayer m_backgroundLayer; // Background layer instance
|
||||
KeyboardLayer m_keyboardLayer; // Keyboard layer instance
|
||||
BackgroundLayer m_backgroundLayer; // Background 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
|
||||
Timestamp m_musicTime = {}; // Current music time in milliseconds
|
||||
|
||||
private:
|
||||
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/track/Track.h"
|
||||
#include "gui/mainWindow/mainWindow.h"
|
||||
|
||||
#include <iostream>
|
||||
@@ -20,8 +21,48 @@ int main(int argc, char *argv[])
|
||||
|
||||
// appContext.m_hServer->enable("127.0.0.1", 4000);
|
||||
|
||||
appContext.m_hTrack->loadFromFile("testFile.mid");
|
||||
// appContext.m_hTrack->debug();
|
||||
// track.loadFromFile(R"(c:\Users\sschn\dev\projects\gitea.hub.saturnux.com\volaTile\do4.mid)");
|
||||
// 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
|
||||
quokka_gfx::Application app;
|
||||
|
||||
Reference in New Issue
Block a user