Skip to content

Commit eeb5aab

Browse files
committed
Add FLAC support to the sampler
- Allow loading FLAC files as samples: extend the sample file browser filter to accept FLAC alongside WAV. Decoding already goes through libsndfile, so no backend change is needed. Add a real round-trip test that writes a FLAC file and reads it back via SndFileReader, mirroring SamplerDevice::loadSample.
1 parent 16a3e14 commit eeb5aab

4 files changed

Lines changed: 58 additions & 1 deletion

File tree

CHANGELOG

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ Release date:
55

66
New features:
77

8+
* Add FLAC support to the Sampler
9+
- Load FLAC files as samples in addition to WAV
10+
- The sample file browser now lists both WAV and FLAC files
11+
- Embedding a FLAC sample into the project works as with WAV
12+
813
* Add selectable audio quality (oversampling) for playback and export
914
- Separate "Playback quality" (Audio settings) and export "Quality"
1015
(Render dialog): Draft (1x), Normal (2x) or High (4x)

src/unit_tests/audio_file_io_test/audio_file_io_test.cpp

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,11 @@
1818
#include "../../infra/audio/audio_file_recorder.hpp"
1919
#include "../../infra/audio/audio_file_streamer.hpp"
2020
#include "../../infra/audio/backend/audio_file_reader.hpp"
21+
#include "../../infra/audio/backend/sndfile_reader.hpp"
2122

23+
#include <QDir>
2224
#include <QTest>
25+
#include <cmath>
2326
#include <condition_variable>
2427
#include <mutex>
2528
#include <thread>
@@ -54,6 +57,8 @@ class MockAudioFileIO : public AudioFileReader
5457

5558
void setTag(TagType type, const std::string & value) override
5659
{
60+
(void)type;
61+
(void)value;
5762
}
5863

5964
int64_t readFloat(std::span<float> data) override
@@ -277,6 +282,52 @@ void AudioFileIoTest::test_position_shouldSeekAndReportCorrectPosition()
277282
}
278283
}
279284

285+
void AudioFileIoTest::test_sndFileReader_flac_shouldWriteAndReadBackCorrectly()
286+
{
287+
const uint32_t sampleRate { 44100 };
288+
const uint32_t channels { 2 };
289+
const size_t bufferSize { 8192 };
290+
const size_t frames { 1024 };
291+
292+
// A ramp-based signal in [-1, 1] with distinct per-channel values
293+
std::vector<float> sourceData(frames * channels);
294+
for (size_t i = 0; i < frames; i++) {
295+
sourceData[i * channels] = std::sin(static_cast<float>(i) * 0.05f);
296+
sourceData[i * channels + 1] = -std::sin(static_cast<float>(i) * 0.05f);
297+
}
298+
299+
const auto filePath = QDir { QDir::tempPath() }.absoluteFilePath("noteahead_sampler_flac_test.flac").toStdString();
300+
301+
// Write a real FLAC file using the same backend the sampler reads with
302+
{
303+
AudioFileRecorder recorder {};
304+
recorder.start(filePath, sampleRate, channels, bufferSize, BitDepth::PCM_16, AudioFormat::Flac);
305+
QVERIFY(recorder.push(sourceData.data(), sourceData.size()));
306+
recorder.stop();
307+
}
308+
309+
// Read it back exactly as SamplerDevice::loadSample does
310+
SndFileReader reader;
311+
AudioFileReader::Info info {};
312+
QVERIFY(reader.open(filePath, AudioFileReader::Mode::Read, info));
313+
QCOMPARE(info.channels, static_cast<int>(channels));
314+
QCOMPARE(info.samplerate, static_cast<int>(sampleRate));
315+
QCOMPARE(info.frames, static_cast<int64_t>(frames));
316+
317+
std::vector<float> readData(static_cast<size_t>(info.frames * info.channels));
318+
const auto readFrames = reader.readFloat(std::span<float> { readData });
319+
reader.close();
320+
QCOMPARE(readFrames, static_cast<int64_t>(frames));
321+
322+
// PCM_16 quantization tolerance
323+
const float tolerance = 2.0f / 32768.0f;
324+
for (size_t i = 0; i < readData.size(); i++) {
325+
QVERIFY(std::abs(readData[i] - sourceData[i]) < tolerance);
326+
}
327+
328+
QDir {}.remove(QString::fromStdString(filePath));
329+
}
330+
280331
} // namespace noteahead
281332

282333
QTEST_GUILESS_MAIN(noteahead::AudioFileIoTest)

src/unit_tests/audio_file_io_test/audio_file_io_test.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class AudioFileIoTest : public QObject
2727
private slots:
2828
void test_recordingAndStreaming_shouldWriteToDiskAndReadBackCorrectly();
2929
void test_position_shouldSeekAndReportCorrectPosition();
30+
void test_sndFileReader_flac_shouldWriteAndReadBackCorrectly();
3031
};
3132

3233
} // namespace noteahead

src/view/qml/Dialogs/SamplerDialog.qml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ Dialog {
6767
FileDialog {
6868
id: sampleFileDialog
6969
title: qsTr("Select Sample")
70-
nameFilters: [qsTr("Audio files (*.wav *.WAV)")]
70+
nameFilters: [qsTr("Audio files") + " (*.wav *.WAV *.flac *.FLAC)", qsTr("WAV files") + " (*.wav *.WAV)", qsTr("FLAC files") + " (*.flac *.FLAC)"]
7171
property int padToAssign: -1
7272
onAccepted: {
7373
if (padToAssign !== -1) {

0 commit comments

Comments
 (0)