diff --git a/CMakeLists.txt b/CMakeLists.txt index b432039..e9e9fa2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,20 +26,21 @@ pkg_check_modules(FFTW REQUIRED IMPORTED_TARGET fftw3) # sources set(SOURCES src/main.cpp - src/MetadataReader.cpp - src/Config.cpp - src/MusicLibrary.cpp - src/CoverArtWidget.cpp - src/SpectrogramWidget.cpp - src/GradientDialog.cpp - src/EqualizerWidget.cpp - src/TrackDelegate.cpp - src/MainWindow.cpp + src/config/Config.cpp + + src/gui/CoverArtWidget.cpp + src/gui/SpectrogramWidget.cpp + src/gui/GradientDialog.cpp + src/gui/EqualizerWidget.cpp + src/gui/TrackDelegate.cpp + src/gui/MainWindow.cpp # engine src/engine/AudioEngine.cpp src/engine/EqProcessor.cpp src/engine/SpectrumAnalyzer.cpp + src/engine/MusicLibrary.cpp + src/engine/MetadataReader.cpp # ffmpeg src/engine/ffmpeg/FormatContext.cpp @@ -55,22 +56,23 @@ set(SOURCES ) set(HEADERS - src/Track.h - src/MetadataReader.h - src/Config.h - src/MusicLibrary.h - src/CoverArtWidget.h - src/SpectrogramWidget.h - src/GradientDialog.h - src/EqualizerWidget.h - src/TrackDelegate.h - src/MainWindow.h + src/config/Config.h + + src/gui/CoverArtWidget.h + src/gui/SpectrogramWidget.h + src/gui/GradientDialog.h + src/gui/EqualizerWidget.h + src/gui/TrackDelegate.h + src/gui/MainWindow.h # engine src/engine/AudioEngine.h src/engine/EqProcessor.h src/engine/SpectrumAnalyzer.h src/engine/AudioBuffer.h + src/engine/Track.h + src/engine/MetadataReader.h + src/engine/MusicLibrary.h # ffmpeg wrapper src/engine/ffmpeg/FormatContext.h @@ -97,9 +99,11 @@ add_executable(SubWave ${SOURCES} ${HEADERS} ${RESOURCES}) # headers target_include_directories(SubWave PRIVATE src + src/config src/engine src/engine/ffmpeg src/playlists + src/gui ) # link to qt and ffmpeg and fftw diff --git a/src/MetadataReader.h b/src/MetadataReader.h deleted file mode 100644 index 96c7cd8..0000000 --- a/src/MetadataReader.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once -#include "Track.h" - -class MetadataReader { -public: - static void populate(Track &track); -}; \ No newline at end of file diff --git a/src/Track.cpp b/src/Track.cpp deleted file mode 100644 index e69de29..0000000 diff --git a/src/Config.cpp b/src/config/Config.cpp similarity index 100% rename from src/Config.cpp rename to src/config/Config.cpp diff --git a/src/Config.h b/src/config/Config.h similarity index 100% rename from src/Config.h rename to src/config/Config.h diff --git a/src/engine/AudioEngine.cpp b/src/engine/AudioEngine.cpp index 52ae6f3..8c9e237 100644 --- a/src/engine/AudioEngine.cpp +++ b/src/engine/AudioEngine.cpp @@ -27,7 +27,7 @@ void AudioEngine::setVolume(float v) { if (m_sink) m_sink->setVolume(m_volume); } -void AudioEngine::play(const Track &track) { +void AudioEngine::play(const engine::Track &track) { stop(); m_stopReq = false; m_pauseReq = false; @@ -197,3 +197,4 @@ void AudioEngine::playbackLoop() { if (natural) emit trackEnded(); } + diff --git a/src/engine/AudioEngine.h b/src/engine/AudioEngine.h index cde5198..bc9eb97 100644 --- a/src/engine/AudioEngine.h +++ b/src/engine/AudioEngine.h @@ -29,7 +29,7 @@ public: int sampleRate() const; void setVolume(float v); - void play(const Track &track); + void play(const engine::Track &track); void togglePause(); void stop(); void seekFraction(double fraction); @@ -68,7 +68,7 @@ private: QMutex m_pauseMtx; QWaitCondition m_pauseCv; - Track m_track; + engine::Track m_track; engine::EqProcessor m_eq; float m_eqGainDb[EQ_BANDS] {}; diff --git a/src/MetadataReader.cpp b/src/engine/MetadataReader.cpp similarity index 96% rename from src/MetadataReader.cpp rename to src/engine/MetadataReader.cpp index 859ba2c..9d4e352 100644 --- a/src/MetadataReader.cpp +++ b/src/engine/MetadataReader.cpp @@ -5,7 +5,9 @@ extern "C" { #include } -void MetadataReader::populate(Track &track) { +namespace engine { + +void MetadataReader::populate(engine::Track &track) { AVFormatContext *fmt = nullptr; if (avformat_open_input(&fmt, track.filePath.toUtf8().constData(), nullptr, nullptr) < 0) return; @@ -74,4 +76,5 @@ void MetadataReader::populate(Track &track) { } avformat_close_input(&fmt); +} } \ No newline at end of file diff --git a/src/engine/MetadataReader.h b/src/engine/MetadataReader.h new file mode 100644 index 0000000..21805df --- /dev/null +++ b/src/engine/MetadataReader.h @@ -0,0 +1,11 @@ +#pragma once +#include "Track.h" + +namespace engine { + +class MetadataReader { +public: + static void populate(engine::Track &track); +}; + +} \ No newline at end of file diff --git a/src/MusicLibrary.cpp b/src/engine/MusicLibrary.cpp similarity index 89% rename from src/MusicLibrary.cpp rename to src/engine/MusicLibrary.cpp index 18280ac..3d8f9bc 100644 --- a/src/MusicLibrary.cpp +++ b/src/engine/MusicLibrary.cpp @@ -3,9 +3,11 @@ #include #include +namespace engine { + MusicLibrary::MusicLibrary(QObject *parent) : QObject(parent) {} -const QList &MusicLibrary::tracks() const { return m_tracks; } +const QList &MusicLibrary::tracks() const { return m_tracks; } const QString &MusicLibrary::rootPath() const { return m_root; } void MusicLibrary::setRootPath(const QString &path) { m_root = path; } @@ -27,7 +29,7 @@ void MusicLibrary::scan() { while (it.hasNext() && !QThread::currentThread()->isInterruptionRequested()) { QString path = it.next(); - Track t; + engine::Track t; t.filePath = path; QString ext = QFileInfo(path).suffix().toLower(); t.format = ext.toUpper(); @@ -48,7 +50,7 @@ void MusicLibrary::scan() { void MusicLibrary::sortByArtistTitle() { QMutexLocker lk(&m_mtx); - std::sort(m_tracks.begin(), m_tracks.end(), [](const Track &a, const Track &b){ + std::sort(m_tracks.begin(), m_tracks.end(), [](const engine::Track &a, const engine::Track &b){ int c = a.artist.compare(b.artist, Qt::CaseInsensitive); return c != 0 ? c < 0 : a.title.compare(b.title, Qt::CaseInsensitive) < 0; }); @@ -56,4 +58,6 @@ void MusicLibrary::sortByArtistTitle() { void MusicLibrary::shutdown() { if (m_scanThread) { m_scanThread->requestInterruption(); m_scanThread->wait(); } +} + } \ No newline at end of file diff --git a/src/MusicLibrary.h b/src/engine/MusicLibrary.h similarity index 80% rename from src/MusicLibrary.h rename to src/engine/MusicLibrary.h index 74b5212..55c09f1 100644 --- a/src/MusicLibrary.h +++ b/src/engine/MusicLibrary.h @@ -5,6 +5,8 @@ #include #include "Track.h" +namespace engine { + class MusicLibrary : public QObject { Q_OBJECT public: @@ -12,7 +14,7 @@ public: { "mp3", "flac", "wav", "ogg", "aac", "m4a", "opus", "wma" }; explicit MusicLibrary(QObject *parent = nullptr); - const QList &tracks() const; + const QList &tracks() const; const QString &rootPath() const; void setRootPath(const QString &path); void scan(); @@ -21,12 +23,14 @@ public: signals: void scanStarted(); - void trackFound(const Track &track); + void trackFound(const engine::Track &track); void scanComplete(int count); private: QString m_root; - QList m_tracks; + QList m_tracks; QThread *m_scanThread { nullptr }; QMutex m_mtx; -}; \ No newline at end of file +}; + +} \ No newline at end of file diff --git a/src/Track.h b/src/engine/Track.h similarity index 96% rename from src/Track.h rename to src/engine/Track.h index 606a09d..a6e20a7 100644 --- a/src/Track.h +++ b/src/engine/Track.h @@ -3,6 +3,8 @@ #include #include +namespace engine { + struct Track { QString filePath; QString title; @@ -28,4 +30,6 @@ struct Track { QString toString() const { return artist + " \u2014 " + title; } -}; \ No newline at end of file +}; + +} \ No newline at end of file diff --git a/src/CoverArtWidget.cpp b/src/gui/CoverArtWidget.cpp similarity index 96% rename from src/CoverArtWidget.cpp rename to src/gui/CoverArtWidget.cpp index 6afa850..bc6c748 100644 --- a/src/CoverArtWidget.cpp +++ b/src/gui/CoverArtWidget.cpp @@ -7,7 +7,7 @@ CoverArtWidget::CoverArtWidget(QWidget *parent) : QWidget(parent) { setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); } -void CoverArtWidget::setTrack(const Track *t) { +void CoverArtWidget::setTrack(const engine::Track *t) { m_cover = (t && !t->coverArt.isNull()) ? t->coverArt : QImage{}; m_title = t ? t->displayTitle() : "No track"; m_artist = t ? t->artist : QString{}; diff --git a/src/CoverArtWidget.h b/src/gui/CoverArtWidget.h similarity index 82% rename from src/CoverArtWidget.h rename to src/gui/CoverArtWidget.h index 580275d..80fec99 100644 --- a/src/CoverArtWidget.h +++ b/src/gui/CoverArtWidget.h @@ -1,13 +1,13 @@ #pragma once #include #include -#include "Track.h" +#include "engine/Track.h" class CoverArtWidget : public QWidget { Q_OBJECT public: explicit CoverArtWidget(QWidget *parent = nullptr); - void setTrack(const Track *t); + void setTrack(const engine::Track *t); protected: void paintEvent(QPaintEvent *) override; private: diff --git a/src/EqualizerWidget.cpp b/src/gui/EqualizerWidget.cpp similarity index 100% rename from src/EqualizerWidget.cpp rename to src/gui/EqualizerWidget.cpp diff --git a/src/EqualizerWidget.h b/src/gui/EqualizerWidget.h similarity index 100% rename from src/EqualizerWidget.h rename to src/gui/EqualizerWidget.h diff --git a/src/GradientDialog.cpp b/src/gui/GradientDialog.cpp similarity index 100% rename from src/GradientDialog.cpp rename to src/gui/GradientDialog.cpp diff --git a/src/GradientDialog.h b/src/gui/GradientDialog.h similarity index 100% rename from src/GradientDialog.h rename to src/gui/GradientDialog.h diff --git a/src/MainWindow.cpp b/src/gui/MainWindow.cpp similarity index 94% rename from src/MainWindow.cpp rename to src/gui/MainWindow.cpp index 649df35..d8fb1e7 100644 --- a/src/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -1,6 +1,6 @@ #include "MainWindow.h" #include "AudioEngine.h" -#include "MusicLibrary.h" +#include "engine/MusicLibrary.h" #include "PlaylistModel.h" #include "TrackDelegate.h" #include "CoverArtWidget.h" @@ -31,7 +31,7 @@ MainWindow::MainWindow(QWidget *parent) setMinimumSize(700, 760); m_engine = new AudioEngine(this); - m_library = new MusicLibrary(this); + m_library = new engine::MusicLibrary(this); m_model = new PlaylistModel(this); m_library->setRootPath(Config::loadMusicRoot()); @@ -258,7 +258,7 @@ void MainWindow::wireSignals() connect(m_engine, &AudioEngine::durationKnown, this, [this](qint64 secs){ if (m_currentIndex >= 0) { - Track *t = m_model->trackAt(m_currentIndex); + engine::Track *t = m_model->trackAt(m_currentIndex); if (t && t->durationSecs == 0) { t->durationSecs = secs; m_model->refreshRow(m_currentIndex); @@ -267,15 +267,15 @@ void MainWindow::wireSignals() }, Qt::QueuedConnection); // Library scanning - connect(m_library, &MusicLibrary::scanStarted, this, [this]{ + connect(m_library, &engine::MusicLibrary::scanStarted, this, [this]{ m_model->setTracks({}); m_lblStatus->setText("Scanning..."); }); - connect(m_library, &MusicLibrary::trackFound, this, [this](const Track &t){ + connect(m_library, &engine::MusicLibrary::trackFound, this, [this](const engine::Track &t){ m_model->appendTrack(t); m_lblStatus->setText(QString("%1 tracks found").arg(m_model->rowCount({}))); }, Qt::QueuedConnection); - connect(m_library, &MusicLibrary::scanComplete, this, [this](int n){ + connect(m_library, &engine::MusicLibrary::scanComplete, this, [this](int n){ m_model->setTracks(m_library->tracks()); // sorted version m_lblStatus->setText(QString("%1 tracks.").arg(n)); }, Qt::QueuedConnection); @@ -290,7 +290,7 @@ void MainWindow::reloadLibrary() void MainWindow::playTrack(int index) { - Track *t = m_model->trackAt(index); + engine::Track *t = m_model->trackAt(index); if (!t) return; m_currentIndex = index; m_model->setCurrentIndex(index); @@ -351,15 +351,14 @@ void MainWindow::onUiTimer() qint64 posF = m_engine->positionFrames(); qint64 totF = m_engine->totalFrames(); qint64 posSec = qint64(posF / sr); + + engine::Track *currentTrack = (m_currentIndex >= 0) ? m_model->trackAt(m_currentIndex) : nullptr; qint64 totSec = totF > 0 ? qint64(totF / sr) - : (m_currentIndex >= 0 - ? (m_model->trackAt(m_currentIndex) - ? m_model->trackAt(m_currentIndex)->durationSecs : 0) - : 0); + : (currentTrack ? currentTrack->durationSecs : 0); // Write back duration if we now know it if (totF > 0 && m_currentIndex >= 0) { - Track *t = m_model->trackAt(m_currentIndex); + engine::Track *t = m_model->trackAt(m_currentIndex); if (t && t->durationSecs == 0) { t->durationSecs = totSec; m_model->refreshRow(m_currentIndex); diff --git a/src/MainWindow.h b/src/gui/MainWindow.h similarity index 91% rename from src/MainWindow.h rename to src/gui/MainWindow.h index 50cdf0a..cc9f3b6 100644 --- a/src/MainWindow.h +++ b/src/gui/MainWindow.h @@ -9,10 +9,13 @@ #include #include #include -#include // needed for buildTransportRow() return type +#include +// Forward declarations class AudioEngine; -class MusicLibrary; +namespace engine { + class MusicLibrary; +} class PlaylistModel; class CoverArtWidget; class SpectrogramWidget; @@ -50,9 +53,9 @@ private: void loadPlaylistIntoModel(const QString &playlistName); // Core - AudioEngine *m_engine; - MusicLibrary *m_library; - PlaylistModel *m_model; + AudioEngine *m_engine; + engine::MusicLibrary *m_library; + PlaylistModel *m_model; // Transport – plain ASCII labels now QLabel *m_lblTitle = new QLabel("No track selected"); diff --git a/src/SpectrogramWidget.cpp b/src/gui/SpectrogramWidget.cpp similarity index 97% rename from src/SpectrogramWidget.cpp rename to src/gui/SpectrogramWidget.cpp index 7db39de..2d83556 100644 --- a/src/SpectrogramWidget.cpp +++ b/src/gui/SpectrogramWidget.cpp @@ -1,5 +1,6 @@ #include "SpectrogramWidget.h" #include "GradientDialog.h" +#include #include #include #include @@ -27,10 +28,15 @@ SpectrogramWidget::SpectrogramWidget(QWidget *parent) setMinimumSize(400, 200); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - m_barHeights .resize(BAR_COUNT); + m_barHeights.resize(BAR_COUNT); m_barPeaksVec.resize(BAR_COUNT); - loadBarColors(); + loadBarColors(); + + auto *timer = new QTimer(this); + connect(timer, &QTimer::timeout, + this, QOverload<>::of(&SpectrogramWidget::update)); + timer->start(16); } SpectrogramWidget::~SpectrogramWidget() @@ -287,6 +293,4 @@ void SpectrogramWidget::paintGL() glBindVertexArray(0); m_barsProg.release(); } - - update(); } \ No newline at end of file diff --git a/src/SpectrogramWidget.h b/src/gui/SpectrogramWidget.h similarity index 100% rename from src/SpectrogramWidget.h rename to src/gui/SpectrogramWidget.h diff --git a/src/TrackDelegate.cpp b/src/gui/TrackDelegate.cpp similarity index 100% rename from src/TrackDelegate.cpp rename to src/gui/TrackDelegate.cpp diff --git a/src/TrackDelegate.h b/src/gui/TrackDelegate.h similarity index 100% rename from src/TrackDelegate.h rename to src/gui/TrackDelegate.h diff --git a/src/playlists/PlaylistCreatorDialog.cpp b/src/playlists/PlaylistCreatorDialog.cpp index 0b68165..d21b587 100644 --- a/src/playlists/PlaylistCreatorDialog.cpp +++ b/src/playlists/PlaylistCreatorDialog.cpp @@ -77,7 +77,7 @@ void PlaylistCreatorDialog::onAddFiles() // Check if already in playlist bool alreadyAdded = false; - for (const Track &t : m_playlist.tracks) { + for (const engine::Track &t : m_playlist.tracks) { if (t.filePath == filePath) { alreadyAdded = true; break; @@ -85,7 +85,7 @@ void PlaylistCreatorDialog::onAddFiles() } if (!alreadyAdded) { - Track track; + engine::Track track; track.filePath = filePath; // Set default metadata @@ -96,7 +96,7 @@ void PlaylistCreatorDialog::onAddFiles() track.album = "Unknown Album"; // Extract actual metadata and cover art - MetadataReader::populate(track); + engine::MetadataReader::populate(track); m_playlist.tracks.append(track); } @@ -145,7 +145,7 @@ void PlaylistCreatorDialog::updateTrackList() { m_trackList->clear(); - for (const Track &track : m_playlist.tracks) { + for (const engine::Track &track : m_playlist.tracks) { QString display = track.displayTitle(); if (!track.artist.isEmpty()) { display = track.artist + " - " + display; diff --git a/src/playlists/PlaylistManager.cpp b/src/playlists/PlaylistManager.cpp index bc13014..dfe6d47 100644 --- a/src/playlists/PlaylistManager.cpp +++ b/src/playlists/PlaylistManager.cpp @@ -111,7 +111,7 @@ QStringList PlaylistManager::listPlaylists() return result; } -void PlaylistManager::addTrackToPlaylist(Playlist &playlist, const Track &track) +void PlaylistManager::addTrackToPlaylist(Playlist &playlist, const engine::Track &track) { playlist.tracks.append(track); } @@ -144,7 +144,7 @@ PlaylistManager::Playlist PlaylistManager::parsePlaylistJson(const QString &json for (int i = 0; i < tracksArray.size(); ++i) { QJsonObject trackObj = tracksArray[i].toObject(); - Track track; + engine::Track track; track.filePath = trackObj["filePath"].toString(); track.title = trackObj["title"].toString(); track.artist = trackObj["artist"].toString(); @@ -157,7 +157,7 @@ PlaylistManager::Playlist PlaylistManager::parsePlaylistJson(const QString &json if (!track.filePath.isEmpty()) { // Re-extract metadata and cover art from the file // in case it has changed or cover art wasn't saved - MetadataReader::populate(track); + engine::MetadataReader::populate(track); playlist.tracks.append(track); } } @@ -171,7 +171,7 @@ QString PlaylistManager::playlistToJson(const Playlist &playlist) QJsonObject root; QJsonArray tracksArray; - for (const Track &track : playlist.tracks) { + for (const engine::Track &track : playlist.tracks) { QJsonObject trackObj; trackObj["filePath"] = track.filePath; trackObj["title"] = track.title; diff --git a/src/playlists/PlaylistManager.h b/src/playlists/PlaylistManager.h index 5a8615f..95dc8d7 100644 --- a/src/playlists/PlaylistManager.h +++ b/src/playlists/PlaylistManager.h @@ -2,13 +2,13 @@ #include #include -#include "Track.h" +#include "engine/Track.h" class PlaylistManager { public: struct Playlist { QString name; - QList tracks; + QList tracks; QString filePath; }; @@ -22,7 +22,7 @@ public: static QStringList listPlaylists(); // Track management - static void addTrackToPlaylist(Playlist &playlist, const Track &track); + static void addTrackToPlaylist(Playlist &playlist, const engine::Track &track); static void removeTrackFromPlaylist(Playlist &playlist, int index); static void clearPlaylist(Playlist &playlist); diff --git a/src/playlists/PlaylistModel.cpp b/src/playlists/PlaylistModel.cpp index cccbff5..c37fc0e 100644 --- a/src/playlists/PlaylistModel.cpp +++ b/src/playlists/PlaylistModel.cpp @@ -3,13 +3,13 @@ PlaylistModel::PlaylistModel(QObject *parent) : QAbstractListModel(parent) {} -void PlaylistModel::setTracks(const QList &tracks) { +void PlaylistModel::setTracks(const QList &tracks) { beginResetModel(); m_tracks = tracks; endResetModel(); } -void PlaylistModel::appendTrack(const Track &t) { +void PlaylistModel::appendTrack(const engine::Track &t) { beginInsertRows({}, m_tracks.size(), m_tracks.size()); m_tracks.append(t); endInsertRows(); @@ -27,11 +27,17 @@ void PlaylistModel::refreshRow(int idx) { emit dataChanged(index(idx), index(idx)); } -Track *PlaylistModel::trackAt(int idx) { +engine::Track *PlaylistModel::trackAt(int idx) { if (idx < 0 || idx >= m_tracks.size()) return nullptr; return &m_tracks[idx]; } +const engine::Track &PlaylistModel::trackAt(int idx) const { + static const engine::Track s_invalid; + if (idx < 0 || idx >= m_tracks.size()) return s_invalid; + return m_tracks[idx]; +} + int PlaylistModel::rowCount(const QModelIndex &) const { return m_tracks.size(); } @@ -39,7 +45,7 @@ int PlaylistModel::rowCount(const QModelIndex &) const { QVariant PlaylistModel::data(const QModelIndex &idx, int role) const { if (!idx.isValid() || idx.row() >= m_tracks.size()) return {}; - const Track &t = m_tracks[idx.row()]; + const engine::Track &t = m_tracks[idx.row()]; switch (role) { case Qt::DisplayRole: return t.displayTitle(); diff --git a/src/playlists/PlaylistModel.h b/src/playlists/PlaylistModel.h index 34f1942..b9d3391 100644 --- a/src/playlists/PlaylistModel.h +++ b/src/playlists/PlaylistModel.h @@ -1,22 +1,23 @@ #pragma once #include #include -#include "Track.h" +#include "engine/Track.h" class PlaylistModel : public QAbstractListModel { Q_OBJECT public: explicit PlaylistModel(QObject *parent=nullptr); - void setTracks(const QList &tracks); - void appendTrack(const Track &t); + void setTracks(const QList &tracks); + void appendTrack(const engine::Track &t); void setCurrentIndex(int idx); void refreshRow(int idx); - Track *trackAt(int idx); + engine::Track *trackAt(int idx); + const engine::Track &trackAt(int idx) const; int rowCount(const QModelIndex &) const override; QVariant data(const QModelIndex &idx, int role) const override; int currentIndex() const; private: - QList m_tracks; + QList m_tracks; int m_current { -1 }; }; \ No newline at end of file