diff --git a/CMakeLists.txt b/CMakeLists.txt index 1973a6c..3c02585 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,6 +63,7 @@ set(HEADERS src/gui/CoverArtWidget.h src/gui/SpectrogramWidget.h src/gui/GradientDialog.h + src/gui/GradientPresetHeader.h src/gui/EqualizerWidget.h src/gui/EqualizerPresetManager.h src/gui/FreqCurveWidget.h @@ -87,10 +88,10 @@ set(HEADERS src/engine/encoder/StageBuffer.h src/engine/encoder/FfmpegGuards.h src/engine/encoder/SampleRateUtils.h - src/engine/encoder/CodecUtils.h # new: pickSampleFormat/pickChannelCount/clampBitrate/ChannelLayoutGuard - src/engine/encoder/OutputPathBuilder.h # new: buildOutputPath - src/engine/encoder/DecoderPipeline.h # new: input open + decoder context - src/engine/encoder/EncoderPipeline.h # new: encoder + muxer + SWR + encode frame + src/engine/encoder/CodecUtils.h + src/engine/encoder/OutputPathBuilder.h + src/engine/encoder/DecoderPipeline.h + src/engine/encoder/EncoderPipeline.h # ffmpeg wrapper src/engine/ffmpeg/FormatContext.h src/engine/ffmpeg/CodecContext.h @@ -131,7 +132,7 @@ target_include_directories(SubWave PRIVATE src/config src/controllers src/engine - src/engine/encoder # covers CodecUtils.h, OutputPathBuilder.h, DecoderPipeline.h, EncoderPipeline.h + src/engine/encoder src/engine/ffmpeg src/playlists src/gui diff --git a/scripts/windeploy.ps1 b/scripts/windeploy.ps1 index e0429fb..be04949 100644 --- a/scripts/windeploy.ps1 +++ b/scripts/windeploy.ps1 @@ -1,7 +1,9 @@ # completely written by chatgpt btw. dont bother for winslop +# except one part, which is the dll deletion! Set-StrictMode -Version Latest $ErrorActionPreference = "Stop" +# this is bullshit, only needed cause QtCreator is needed to compile the App ; which is also bullshit $BuildDir = Join-Path $env:USERPROFILE "Documents\SubWave\build\Desktop_Qt_6_10_2_MinGW_64_bit-Release" $ZipOutDir = Join-Path $env:USERPROFILE "Documents\SubWave\build" $QtBin = "C:\Qt\6.10.2\mingw_64\bin" @@ -11,7 +13,7 @@ $AppExe = Join-Path $BuildDir "SubWave.exe" $StagingDir = Join-Path $env:TEMP "SubWave_staging" $ZipPath = Join-Path $ZipOutDir "SubWave.zip" -$UnpackedDir = Join-Path $ZipOutDir "SubWave_uncompressed" +$UnpackedDir = Join-Path $ZipOutDir "SubWave" function Copy-Dll($src, $dst) { if (Test-Path $src) { @@ -125,7 +127,8 @@ $dllsToRemove = @( "avdevice-*.dll","libavdevice-*.dll", "swscale-*.dll","libswscale-*.dll", "postproc-*.dll","libpostproc-*.dll", - "libfftw3f-3.dll","libfftw3l-3.dll" + "libfftw3f-3.dll","libfftw3l-3.dll", + "opengl32sw.dll" # opengl is usually shipped withwindows, so we can safely delete it, mesa is not needed ) foreach ($pattern in $dllsToRemove) { diff --git a/src/engine/AudioEngine.cpp b/src/engine/AudioEngine.cpp index 8c9e237..b45848a 100644 --- a/src/engine/AudioEngine.cpp +++ b/src/engine/AudioEngine.cpp @@ -21,6 +21,11 @@ AudioEngine::State AudioEngine::state() const { return m_state.load(); } qint64 AudioEngine::positionFrames() const { return m_posFrames.load(); } qint64 AudioEngine::totalFrames() const { return m_totalFrames.load(); } int AudioEngine::sampleRate() const { return m_sampleRate.load(); } +int AudioEngine::bitsPerSample() const { return m_bitsPerSample.load(); } +QString AudioEngine::codecName() const { + QMutexLocker lock(&m_metaMtx); + return m_codecName; +} void AudioEngine::setVolume(float v) { m_volume = std::clamp(v, 0.f, 1.f); @@ -32,6 +37,11 @@ void AudioEngine::play(const engine::Track &track) { m_stopReq = false; m_pauseReq = false; m_seekPending = false; + m_bitsPerSample = 0; + { + QMutexLocker lock(&m_metaMtx); + m_codecName.clear(); + } m_track = track; m_thread = QThread::create([this]{ playbackLoop(); }); m_thread->setObjectName("subwave-audio"); @@ -99,6 +109,11 @@ void AudioEngine::playbackLoop() { } m_sampleRate = decoder.sampleRate(); + m_bitsPerSample = decoder.bitsPerSample(); + { + QMutexLocker lock(&m_metaMtx); + m_codecName = decoder.codecName(); + } m_eq.setSampleRate(m_sampleRate); m_eq.reset(); diff --git a/src/engine/AudioEngine.h b/src/engine/AudioEngine.h index ca650d1..6dad29a 100644 --- a/src/engine/AudioEngine.h +++ b/src/engine/AudioEngine.h @@ -27,6 +27,8 @@ public: qint64 positionFrames() const; qint64 totalFrames() const; int sampleRate() const; + int bitsPerSample() const; + QString codecName() const; void setVolume(float v); void play(const engine::Track &track); @@ -55,6 +57,7 @@ private: std::atomic m_posFrames { 0 }; std::atomic m_totalFrames { 0 }; std::atomic m_sampleRate { 44100 }; + std::atomic m_bitsPerSample { 0 }; std::atomic m_stopReq { false }; std::atomic m_pauseReq { false }; std::atomic m_seekPending { false }; @@ -67,8 +70,10 @@ private: QMutex m_pauseMtx; QWaitCondition m_pauseCv; + mutable QMutex m_metaMtx; engine::Track m_track; + QString m_codecName; engine::EqProcessor m_eq; float m_eqGainDb[EQ_BANDS] {}; diff --git a/src/engine/ffmpeg/CodecContext.cpp b/src/engine/ffmpeg/CodecContext.cpp index 611297b..23085e8 100644 --- a/src/engine/ffmpeg/CodecContext.cpp +++ b/src/engine/ffmpeg/CodecContext.cpp @@ -3,6 +3,7 @@ extern "C" { #include +#include } namespace engine::ffmpeg { @@ -64,4 +65,18 @@ int CodecContext::channels() const { return m_ctx ? m_ctx->ch_layout.nb_channels : 0; } +int CodecContext::bitsPerSample() const { + if (!m_ctx) return 0; + + if (m_ctx->bits_per_raw_sample > 0) + return m_ctx->bits_per_raw_sample; + + if (m_ctx->sample_fmt != AV_SAMPLE_FMT_NONE && m_ctx->sample_fmt != AV_SAMPLE_FMT_NB) { + const int bytesPerSample = av_get_bytes_per_sample(m_ctx->sample_fmt); + return bytesPerSample > 0 ? bytesPerSample * 8 : 0; + } + + return 0; +} + } // namespace engine::ffmpeg diff --git a/src/engine/ffmpeg/CodecContext.h b/src/engine/ffmpeg/CodecContext.h index 4084689..cfb70b3 100644 --- a/src/engine/ffmpeg/CodecContext.h +++ b/src/engine/ffmpeg/CodecContext.h @@ -27,6 +27,7 @@ public: bool isOpen() const { return m_ctx != nullptr; } int sampleRate()const; int channels() const; + int bitsPerSample() const; const AVCodecContext *get() const { return m_ctx.get(); } diff --git a/src/engine/ffmpeg/FFmpegDecoder.cpp b/src/engine/ffmpeg/FFmpegDecoder.cpp index a850f74..8d5d638 100644 --- a/src/engine/ffmpeg/FFmpegDecoder.cpp +++ b/src/engine/ffmpeg/FFmpegDecoder.cpp @@ -61,6 +61,28 @@ void FFmpegDecoder::seek(double fraction) { m_codec.flush(); } +QString FFmpegDecoder::codecName() const { + const AVCodecParameters *par = m_format.audioCodecPar(); + if (!par) return {}; + + const AVCodecDescriptor *desc = avcodec_descriptor_get(par->codec_id); + if (!desc) { + const char *fallback = avcodec_get_name(par->codec_id); + return fallback ? QString::fromUtf8(fallback).toUpper() : QString(); + } + + QString name = QString::fromUtf8(desc->long_name && desc->long_name[0] ? desc->long_name : desc->name); + const int parenPos = name.indexOf(" ("); + if (parenPos >= 0) + name.truncate(parenPos); + + return name.trimmed().toUpper(); +} + +int FFmpegDecoder::bitsPerSample() const { + return m_codec.bitsPerSample(); +} + bool FFmpegDecoder::decodeNext(AudioBuffer &buf) { if (!m_format.isOpen()) return false; diff --git a/src/engine/ffmpeg/FFmpegDecoder.h b/src/engine/ffmpeg/FFmpegDecoder.h index 5b37bec..abee56d 100644 --- a/src/engine/ffmpeg/FFmpegDecoder.h +++ b/src/engine/ffmpeg/FFmpegDecoder.h @@ -24,6 +24,8 @@ public: int channels() const { return m_codec.channels(); } double durationSeconds() const { return m_format.durationSecs(); } qint64 totalFrames() const { return m_totalFrames; } + QString codecName() const; + int bitsPerSample() const; private: FormatContext m_format; diff --git a/src/gui/GradientDialog.cpp b/src/gui/GradientDialog.cpp index d95763e..39a6dc6 100644 --- a/src/gui/GradientDialog.cpp +++ b/src/gui/GradientDialog.cpp @@ -1,4 +1,5 @@ #include "GradientDialog.h" +#include "GradientPresetHeader.h" #include #include #include @@ -8,47 +9,6 @@ #include #include -static QVector makePreset4(const QColor &c0, const QColor &c1, - const QColor &c2, const QColor &c3, - const QColor &c4) -{ - return { c0, c2, c3, c4 }; -} - -struct Preset { - QString name; - QVector colors; -}; - -// Presets are defined in a Vector for simplicity of adding new ones -static const QVector PRESETS = { - { "Default", - { QColor("#078D70"), QColor("#26CEAA"), QColor("#98E8C1"), QColor("#FFFFFF") } }, - - { "Pastels", - makePreset4(QColor("#92ccdd"), QColor("#c7eff0"), - QColor("#f5d5fd"), QColor("#fdc4ec"), QColor("#ffc2cb")) }, - - { "Raddish", - makePreset4(QColor("#ff0000"), QColor("#b40000"), - QColor("#890000"), QColor("#540000"), QColor("#240000")) }, - - { "Blau", - makePreset4(QColor("#72c8dd"), QColor("#43b5d2"), - QColor("#14a3c7"), QColor("#1293b3"), QColor("#10829f")) }, - - { "Turquoise", - makePreset4(QColor("#d8f2f6"), QColor("#8bd9ef"), - QColor("#3ec1d5"), QColor("#319aaa"), QColor("#25737f")) }, - - { "Aqua", - makePreset4(QColor("#2278FB"), QColor("#3A9AF0"), - QColor("#53BDE6"), QColor("#6BDFDB"), QColor("#1A5BB8")) }, - - { "Peaches", - makePreset4(QColor("#FFCAA6"), QColor("#FDA8A0"), - QColor("#FA879A"), QColor("#F86594"), QColor("#FFE0CC")) } -}; static int findPresetIndex(const QVector &stops) { diff --git a/src/gui/GradientPresetHeader.h b/src/gui/GradientPresetHeader.h new file mode 100644 index 0000000..1e27b37 --- /dev/null +++ b/src/gui/GradientPresetHeader.h @@ -0,0 +1,54 @@ +#pragma once + +#include +#include +#include + +/** preset function */ +inline QVector makePreset4(const QColor &c0, const QColor &c1, + const QColor &c2, const QColor &c3, + const QColor &c4) +{ + // if you dont define c1 as #FFFF worry, cause it does NOT get used! + return { c0, c2, c3, c4 }; +} + +/** Preset Struct */ +struct Preset { + QString name; + QVector colors; +}; + +/** Defined Presets */ +inline const QVector PRESETS = { + { "Default", + { QColor("#078D70"), QColor("#26CEAA"), QColor("#98E8C1"), QColor("#FFFFFF") } }, + + { "Pastels", + makePreset4(QColor("#92ccdd"), QColor("#c7eff0"), + QColor("#f5d5fd"), QColor("#fdc4ec"), QColor("#ffc2cb")) }, + + { "Raddish", + makePreset4(QColor("#ff0000"), QColor("#b40000"), + QColor("#890000"), QColor("#540000"), QColor("#240000")) }, + + { "Blau", + makePreset4(QColor("#72c8dd"), QColor("#43b5d2"), + QColor("#14a3c7"), QColor("#1293b3"), QColor("#10829f")) }, + + { "Turquoise", + makePreset4(QColor("#d8f2f6"), QColor("#8bd9ef"), + QColor("#3ec1d5"), QColor("#319aaa"), QColor("#25737f")) }, + + { "Aqua", + makePreset4(QColor("#2278FB"), QColor("#3A9AF0"), + QColor("#53BDE6"), QColor("#6BDFDB"), QColor("#1A5BB8")) }, + + { "Peaches", + makePreset4(QColor("#FFCAA6"), QColor("#FDA8A0"), + QColor("#FA879A"), QColor("#F86594"), QColor("#FFE0CC")) }, + + { "Windows Green", + makePreset4(QColor("#BFFFE3"), QColor("#7FFFC2"), + QColor("#43FFAC"), QColor("#27FF97"), QColor("#0EDB7D")) }, +}; diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index f98269b..ee1e766 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -48,6 +48,10 @@ MainWindow::MainWindow(QWidget *parent) e->positionFrames(), e->totalFrames(), float(e->sampleRate())); + m_nowPlaying->updateAudioInfo( + e->sampleRate(), + e->bitsPerSample(), + e->codecName()); if (e->state() == AudioEngine::State::Stopped) m_nowPlaying->setPlaybackActive(false); diff --git a/src/gui/NowPlayingPanel.cpp b/src/gui/NowPlayingPanel.cpp index c9720d1..8bdc223 100644 --- a/src/gui/NowPlayingPanel.cpp +++ b/src/gui/NowPlayingPanel.cpp @@ -60,11 +60,22 @@ void NowPlayingPanel::buildLayout() transport->addWidget(new QLabel("Volume:")); transport->addWidget(m_volSlider); + auto *metaRow = new QHBoxLayout; + metaRow->setSpacing(8); + metaRow->addWidget(new QLabel("Sample rate:")); + metaRow->addWidget(m_lblSampleRate); + metaRow->addWidget(new QLabel("Bits/Sample:")); + metaRow->addWidget(m_lblBitsPerSample); + metaRow->addWidget(new QLabel("Codec:")); + metaRow->addWidget(m_lblCodec); + metaRow->addStretch(); + auto *right = new QVBoxLayout; right->setSpacing(4); right->addWidget(m_lblTitle); right->addWidget(m_lblArtist); right->addWidget(m_lblAlbum); + right->addLayout(metaRow); right->addLayout(seekRow); right->addLayout(transport); @@ -73,14 +84,25 @@ void NowPlayingPanel::buildLayout() hlay->addLayout(right, 1); } +void NowPlayingPanel::clearAudioInfo() +{ + m_lblSampleRate->setText("-"); + m_lblBitsPerSample->setText("-"); + m_lblCodec->setText("-"); +} + void NowPlayingPanel::updateTrack(const engine::Track *t) { - if (!t) return; + if (!t) { + clearAudioInfo(); + return; + } m_cover->setTrack(t); m_lblTitle->setText(t->displayTitle()); m_lblArtist->setText(t->artist); m_lblAlbum->setText(t->album + (t->year > 0 ? " (" + QString::number(t->year) + ")" : "")); + clearAudioInfo(); m_seekBar->setValue(0); m_btnPlay->setText("Pause"); } @@ -101,6 +123,13 @@ void NowPlayingPanel::updatePosition(qint64 posFrames, qint64 totalFrames, float m_seekBar->setValue(int(posFrames * 1000 / totalFrames)); } +void NowPlayingPanel::updateAudioInfo(int sampleRate, int bitsPerSample, const QString &codec) +{ + m_lblSampleRate->setText(sampleRate > 0 ? QString("%1 Hz").arg(sampleRate) : "-"); + m_lblBitsPerSample->setText(bitsPerSample > 0 ? QString("%1").arg(bitsPerSample) : "-"); + m_lblCodec->setText(!codec.isEmpty() ? codec : "-"); +} + void NowPlayingPanel::setPlaybackActive(bool playing) { m_btnPlay->setText(playing ? "Pause" : "Play"); diff --git a/src/gui/NowPlayingPanel.h b/src/gui/NowPlayingPanel.h index e8e4dde..c2f3912 100644 --- a/src/gui/NowPlayingPanel.h +++ b/src/gui/NowPlayingPanel.h @@ -20,7 +20,8 @@ public: public slots: void updateTrack(const engine::Track *track); void updatePosition(qint64 posFrames, qint64 totalFrames, float sampleRate); - void setPlaybackActive(bool playing); + void updateAudioInfo(int sampleRate, int bitsPerSample, const QString &codec); + void setPlaybackActive(bool playing); signals: void playPauseClicked(); @@ -31,12 +32,16 @@ signals: private: void buildLayout(); + void clearAudioInfo(); CoverArtWidget *m_cover; QLabel *m_lblTitle = new QLabel("No track selected"); QLabel *m_lblArtist = new QLabel(" "); QLabel *m_lblAlbum = new QLabel(" "); QLabel *m_lblTime = new QLabel("0:00 / 0:00"); + QLabel *m_lblSampleRate = new QLabel("—"); + QLabel *m_lblBitsPerSample = new QLabel("-"); + QLabel *m_lblCodec = new QLabel("—"); QSlider *m_seekBar = new QSlider(Qt::Horizontal); QPushButton *m_btnPrev = new QPushButton("|<"); QPushButton *m_btnPlay = new QPushButton("Play");