clean structure

This commit is contained in:
2026-06-11 14:03:35 +02:00
parent 326424f6fa
commit 677f263648
29 changed files with 119 additions and 82 deletions
+23 -19
View File
@@ -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
-7
View File
@@ -1,7 +0,0 @@
#pragma once
#include "Track.h"
class MetadataReader {
public:
static void populate(Track &track);
};
View File
+2 -1
View File
@@ -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();
}
+2 -2
View File
@@ -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] {};
@@ -5,7 +5,9 @@ extern "C" {
#include <libavutil/dict.h>
}
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);
}
}
+11
View File
@@ -0,0 +1,11 @@
#pragma once
#include "Track.h"
namespace engine {
class MetadataReader {
public:
static void populate(engine::Track &track);
};
}
@@ -3,9 +3,11 @@
#include <QDirIterator>
#include <QFileInfo>
namespace engine {
MusicLibrary::MusicLibrary(QObject *parent) : QObject(parent) {}
const QList<Track> &MusicLibrary::tracks() const { return m_tracks; }
const QList<engine::Track> &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(); }
}
}
@@ -5,6 +5,8 @@
#include <QMutex>
#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<Track> &tracks() const;
const QList<engine::Track> &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<Track> m_tracks;
QList<engine::Track> m_tracks;
QThread *m_scanThread { nullptr };
QMutex m_mtx;
};
};
}
+5 -1
View File
@@ -3,6 +3,8 @@
#include <QImage>
#include <QFileInfo>
namespace engine {
struct Track {
QString filePath;
QString title;
@@ -28,4 +30,6 @@ struct Track {
QString toString() const {
return artist + " \u2014 " + title;
}
};
};
}
@@ -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{};
@@ -1,13 +1,13 @@
#pragma once
#include <QWidget>
#include <QImage>
#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:
+11 -12
View File
@@ -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);
+8 -5
View File
@@ -9,10 +9,13 @@
#include <QTimer>
#include <QTabWidget>
#include <QGroupBox>
#include <QHBoxLayout> // needed for buildTransportRow() return type
#include <QHBoxLayout>
// 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");
@@ -1,5 +1,6 @@
#include "SpectrogramWidget.h"
#include "GradientDialog.h"
#include <QTimer>
#include <QSurfaceFormat>
#include <QFile>
#include <QMouseEvent>
@@ -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();
}
+4 -4
View File
@@ -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;
+4 -4
View File
@@ -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;
+3 -3
View File
@@ -2,13 +2,13 @@
#include <QString>
#include <QList>
#include "Track.h"
#include "engine/Track.h"
class PlaylistManager {
public:
struct Playlist {
QString name;
QList<Track> tracks;
QList<engine::Track> 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);
+10 -4
View File
@@ -3,13 +3,13 @@
PlaylistModel::PlaylistModel(QObject *parent)
: QAbstractListModel(parent) {}
void PlaylistModel::setTracks(const QList<Track> &tracks) {
void PlaylistModel::setTracks(const QList<engine::Track> &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();
+6 -5
View File
@@ -1,22 +1,23 @@
#pragma once
#include <QAbstractListModel>
#include <QList>
#include "Track.h"
#include "engine/Track.h"
class PlaylistModel : public QAbstractListModel {
Q_OBJECT
public:
explicit PlaylistModel(QObject *parent=nullptr);
void setTracks(const QList<Track> &tracks);
void appendTrack(const Track &t);
void setTracks(const QList<engine::Track> &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<Track> m_tracks;
QList<engine::Track> m_tracks;
int m_current { -1 };
};