add: Audio Info in NowPlayingPanel.cpp ; Includes: Samplerate, bits/sample, and codec

change: opengl dll for windows now is not included, cause it is too large
refactor: Spectrogram colors for bars are now in a Header (GradientPresetHeader.h), with accompaning methods
This commit is contained in:
2026-07-02 16:35:04 +02:00
parent 515404a220
commit 7297ae5f6b
13 changed files with 166 additions and 50 deletions
+6 -5
View File
@@ -63,6 +63,7 @@ set(HEADERS
src/gui/CoverArtWidget.h src/gui/CoverArtWidget.h
src/gui/SpectrogramWidget.h src/gui/SpectrogramWidget.h
src/gui/GradientDialog.h src/gui/GradientDialog.h
src/gui/GradientPresetHeader.h
src/gui/EqualizerWidget.h src/gui/EqualizerWidget.h
src/gui/EqualizerPresetManager.h src/gui/EqualizerPresetManager.h
src/gui/FreqCurveWidget.h src/gui/FreqCurveWidget.h
@@ -87,10 +88,10 @@ set(HEADERS
src/engine/encoder/StageBuffer.h src/engine/encoder/StageBuffer.h
src/engine/encoder/FfmpegGuards.h src/engine/encoder/FfmpegGuards.h
src/engine/encoder/SampleRateUtils.h src/engine/encoder/SampleRateUtils.h
src/engine/encoder/CodecUtils.h # new: pickSampleFormat/pickChannelCount/clampBitrate/ChannelLayoutGuard src/engine/encoder/CodecUtils.h
src/engine/encoder/OutputPathBuilder.h # new: buildOutputPath src/engine/encoder/OutputPathBuilder.h
src/engine/encoder/DecoderPipeline.h # new: input open + decoder context src/engine/encoder/DecoderPipeline.h
src/engine/encoder/EncoderPipeline.h # new: encoder + muxer + SWR + encode frame src/engine/encoder/EncoderPipeline.h
# ffmpeg wrapper # ffmpeg wrapper
src/engine/ffmpeg/FormatContext.h src/engine/ffmpeg/FormatContext.h
src/engine/ffmpeg/CodecContext.h src/engine/ffmpeg/CodecContext.h
@@ -131,7 +132,7 @@ target_include_directories(SubWave PRIVATE
src/config src/config
src/controllers src/controllers
src/engine src/engine
src/engine/encoder # covers CodecUtils.h, OutputPathBuilder.h, DecoderPipeline.h, EncoderPipeline.h src/engine/encoder
src/engine/ffmpeg src/engine/ffmpeg
src/playlists src/playlists
src/gui src/gui
+5 -2
View File
@@ -1,7 +1,9 @@
# completely written by chatgpt btw. dont bother for winslop # completely written by chatgpt btw. dont bother for winslop
# except one part, which is the dll deletion!
Set-StrictMode -Version Latest Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop" $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" $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" $ZipOutDir = Join-Path $env:USERPROFILE "Documents\SubWave\build"
$QtBin = "C:\Qt\6.10.2\mingw_64\bin" $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" $StagingDir = Join-Path $env:TEMP "SubWave_staging"
$ZipPath = Join-Path $ZipOutDir "SubWave.zip" $ZipPath = Join-Path $ZipOutDir "SubWave.zip"
$UnpackedDir = Join-Path $ZipOutDir "SubWave_uncompressed" $UnpackedDir = Join-Path $ZipOutDir "SubWave"
function Copy-Dll($src, $dst) { function Copy-Dll($src, $dst) {
if (Test-Path $src) { if (Test-Path $src) {
@@ -125,7 +127,8 @@ $dllsToRemove = @(
"avdevice-*.dll","libavdevice-*.dll", "avdevice-*.dll","libavdevice-*.dll",
"swscale-*.dll","libswscale-*.dll", "swscale-*.dll","libswscale-*.dll",
"postproc-*.dll","libpostproc-*.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) { foreach ($pattern in $dllsToRemove) {
+15
View File
@@ -21,6 +21,11 @@ AudioEngine::State AudioEngine::state() const { return m_state.load(); }
qint64 AudioEngine::positionFrames() const { return m_posFrames.load(); } qint64 AudioEngine::positionFrames() const { return m_posFrames.load(); }
qint64 AudioEngine::totalFrames() const { return m_totalFrames.load(); } qint64 AudioEngine::totalFrames() const { return m_totalFrames.load(); }
int AudioEngine::sampleRate() const { return m_sampleRate.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) { void AudioEngine::setVolume(float v) {
m_volume = std::clamp(v, 0.f, 1.f); m_volume = std::clamp(v, 0.f, 1.f);
@@ -32,6 +37,11 @@ void AudioEngine::play(const engine::Track &track) {
m_stopReq = false; m_stopReq = false;
m_pauseReq = false; m_pauseReq = false;
m_seekPending = false; m_seekPending = false;
m_bitsPerSample = 0;
{
QMutexLocker lock(&m_metaMtx);
m_codecName.clear();
}
m_track = track; m_track = track;
m_thread = QThread::create([this]{ playbackLoop(); }); m_thread = QThread::create([this]{ playbackLoop(); });
m_thread->setObjectName("subwave-audio"); m_thread->setObjectName("subwave-audio");
@@ -99,6 +109,11 @@ void AudioEngine::playbackLoop() {
} }
m_sampleRate = decoder.sampleRate(); m_sampleRate = decoder.sampleRate();
m_bitsPerSample = decoder.bitsPerSample();
{
QMutexLocker lock(&m_metaMtx);
m_codecName = decoder.codecName();
}
m_eq.setSampleRate(m_sampleRate); m_eq.setSampleRate(m_sampleRate);
m_eq.reset(); m_eq.reset();
+5
View File
@@ -27,6 +27,8 @@ public:
qint64 positionFrames() const; qint64 positionFrames() const;
qint64 totalFrames() const; qint64 totalFrames() const;
int sampleRate() const; int sampleRate() const;
int bitsPerSample() const;
QString codecName() const;
void setVolume(float v); void setVolume(float v);
void play(const engine::Track &track); void play(const engine::Track &track);
@@ -55,6 +57,7 @@ private:
std::atomic<qint64> m_posFrames { 0 }; std::atomic<qint64> m_posFrames { 0 };
std::atomic<qint64> m_totalFrames { 0 }; std::atomic<qint64> m_totalFrames { 0 };
std::atomic<int> m_sampleRate { 44100 }; std::atomic<int> m_sampleRate { 44100 };
std::atomic<int> m_bitsPerSample { 0 };
std::atomic<bool> m_stopReq { false }; std::atomic<bool> m_stopReq { false };
std::atomic<bool> m_pauseReq { false }; std::atomic<bool> m_pauseReq { false };
std::atomic<bool> m_seekPending { false }; std::atomic<bool> m_seekPending { false };
@@ -67,8 +70,10 @@ private:
QMutex m_pauseMtx; QMutex m_pauseMtx;
QWaitCondition m_pauseCv; QWaitCondition m_pauseCv;
mutable QMutex m_metaMtx;
engine::Track m_track; engine::Track m_track;
QString m_codecName;
engine::EqProcessor m_eq; engine::EqProcessor m_eq;
float m_eqGainDb[EQ_BANDS] {}; float m_eqGainDb[EQ_BANDS] {};
+15
View File
@@ -3,6 +3,7 @@
extern "C" { extern "C" {
#include <libavcodec/avcodec.h> #include <libavcodec/avcodec.h>
#include <libavutil/samplefmt.h>
} }
namespace engine::ffmpeg { namespace engine::ffmpeg {
@@ -64,4 +65,18 @@ int CodecContext::channels() const {
return m_ctx ? m_ctx->ch_layout.nb_channels : 0; 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 } // namespace engine::ffmpeg
+1
View File
@@ -27,6 +27,7 @@ public:
bool isOpen() const { return m_ctx != nullptr; } bool isOpen() const { return m_ctx != nullptr; }
int sampleRate()const; int sampleRate()const;
int channels() const; int channels() const;
int bitsPerSample() const;
const AVCodecContext *get() const { return m_ctx.get(); } const AVCodecContext *get() const { return m_ctx.get(); }
+22
View File
@@ -61,6 +61,28 @@ void FFmpegDecoder::seek(double fraction) {
m_codec.flush(); 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) { bool FFmpegDecoder::decodeNext(AudioBuffer &buf) {
if (!m_format.isOpen()) return false; if (!m_format.isOpen()) return false;
+2
View File
@@ -24,6 +24,8 @@ public:
int channels() const { return m_codec.channels(); } int channels() const { return m_codec.channels(); }
double durationSeconds() const { return m_format.durationSecs(); } double durationSeconds() const { return m_format.durationSecs(); }
qint64 totalFrames() const { return m_totalFrames; } qint64 totalFrames() const { return m_totalFrames; }
QString codecName() const;
int bitsPerSample() const;
private: private:
FormatContext m_format; FormatContext m_format;
+1 -41
View File
@@ -1,4 +1,5 @@
#include "GradientDialog.h" #include "GradientDialog.h"
#include "GradientPresetHeader.h"
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QVBoxLayout> #include <QVBoxLayout>
#include <QPushButton> #include <QPushButton>
@@ -8,47 +9,6 @@
#include <QPixmap> #include <QPixmap>
#include <QComboBox> #include <QComboBox>
static QVector<QColor> 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<QColor> colors;
};
// Presets are defined in a Vector for simplicity of adding new ones
static const QVector<Preset> 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<QColor> &stops) static int findPresetIndex(const QVector<QColor> &stops)
{ {
+54
View File
@@ -0,0 +1,54 @@
#pragma once
#include <QVector>
#include <QColor>
#include <QString>
/** preset function */
inline QVector<QColor> 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<QColor> colors;
};
/** Defined Presets */
inline const QVector<Preset> 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")) },
};
+4
View File
@@ -48,6 +48,10 @@ MainWindow::MainWindow(QWidget *parent)
e->positionFrames(), e->positionFrames(),
e->totalFrames(), e->totalFrames(),
float(e->sampleRate())); float(e->sampleRate()));
m_nowPlaying->updateAudioInfo(
e->sampleRate(),
e->bitsPerSample(),
e->codecName());
if (e->state() == AudioEngine::State::Stopped) if (e->state() == AudioEngine::State::Stopped)
m_nowPlaying->setPlaybackActive(false); m_nowPlaying->setPlaybackActive(false);
+30 -1
View File
@@ -60,11 +60,22 @@ void NowPlayingPanel::buildLayout()
transport->addWidget(new QLabel("Volume:")); transport->addWidget(new QLabel("Volume:"));
transport->addWidget(m_volSlider); 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; auto *right = new QVBoxLayout;
right->setSpacing(4); right->setSpacing(4);
right->addWidget(m_lblTitle); right->addWidget(m_lblTitle);
right->addWidget(m_lblArtist); right->addWidget(m_lblArtist);
right->addWidget(m_lblAlbum); right->addWidget(m_lblAlbum);
right->addLayout(metaRow);
right->addLayout(seekRow); right->addLayout(seekRow);
right->addLayout(transport); right->addLayout(transport);
@@ -73,14 +84,25 @@ void NowPlayingPanel::buildLayout()
hlay->addLayout(right, 1); hlay->addLayout(right, 1);
} }
void NowPlayingPanel::clearAudioInfo()
{
m_lblSampleRate->setText("-");
m_lblBitsPerSample->setText("-");
m_lblCodec->setText("-");
}
void NowPlayingPanel::updateTrack(const engine::Track *t) void NowPlayingPanel::updateTrack(const engine::Track *t)
{ {
if (!t) return; if (!t) {
clearAudioInfo();
return;
}
m_cover->setTrack(t); m_cover->setTrack(t);
m_lblTitle->setText(t->displayTitle()); m_lblTitle->setText(t->displayTitle());
m_lblArtist->setText(t->artist); m_lblArtist->setText(t->artist);
m_lblAlbum->setText(t->album + m_lblAlbum->setText(t->album +
(t->year > 0 ? " (" + QString::number(t->year) + ")" : "")); (t->year > 0 ? " (" + QString::number(t->year) + ")" : ""));
clearAudioInfo();
m_seekBar->setValue(0); m_seekBar->setValue(0);
m_btnPlay->setText("Pause"); m_btnPlay->setText("Pause");
} }
@@ -101,6 +123,13 @@ void NowPlayingPanel::updatePosition(qint64 posFrames, qint64 totalFrames, float
m_seekBar->setValue(int(posFrames * 1000 / totalFrames)); 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) void NowPlayingPanel::setPlaybackActive(bool playing)
{ {
m_btnPlay->setText(playing ? "Pause" : "Play"); m_btnPlay->setText(playing ? "Pause" : "Play");
+6 -1
View File
@@ -20,7 +20,8 @@ public:
public slots: public slots:
void updateTrack(const engine::Track *track); void updateTrack(const engine::Track *track);
void updatePosition(qint64 posFrames, qint64 totalFrames, float sampleRate); 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: signals:
void playPauseClicked(); void playPauseClicked();
@@ -31,12 +32,16 @@ signals:
private: private:
void buildLayout(); void buildLayout();
void clearAudioInfo();
CoverArtWidget *m_cover; CoverArtWidget *m_cover;
QLabel *m_lblTitle = new QLabel("No track selected"); QLabel *m_lblTitle = new QLabel("No track selected");
QLabel *m_lblArtist = new QLabel(" "); QLabel *m_lblArtist = new QLabel(" ");
QLabel *m_lblAlbum = new QLabel(" "); QLabel *m_lblAlbum = new QLabel(" ");
QLabel *m_lblTime = new QLabel("0:00 / 0:00"); 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); QSlider *m_seekBar = new QSlider(Qt::Horizontal);
QPushButton *m_btnPrev = new QPushButton("|<"); QPushButton *m_btnPrev = new QPushButton("|<");
QPushButton *m_btnPlay = new QPushButton("Play"); QPushButton *m_btnPlay = new QPushButton("Play");