|
18 | 18 | #include "../../infra/audio/audio_file_recorder.hpp" |
19 | 19 | #include "../../infra/audio/audio_file_streamer.hpp" |
20 | 20 | #include "../../infra/audio/backend/audio_file_reader.hpp" |
| 21 | +#include "../../infra/audio/backend/sndfile_reader.hpp" |
21 | 22 |
|
| 23 | +#include <QDir> |
22 | 24 | #include <QTest> |
| 25 | +#include <cmath> |
23 | 26 | #include <condition_variable> |
24 | 27 | #include <mutex> |
25 | 28 | #include <thread> |
@@ -54,6 +57,8 @@ class MockAudioFileIO : public AudioFileReader |
54 | 57 |
|
55 | 58 | void setTag(TagType type, const std::string & value) override |
56 | 59 | { |
| 60 | + (void)type; |
| 61 | + (void)value; |
57 | 62 | } |
58 | 63 |
|
59 | 64 | int64_t readFloat(std::span<float> data) override |
@@ -277,6 +282,52 @@ void AudioFileIoTest::test_position_shouldSeekAndReportCorrectPosition() |
277 | 282 | } |
278 | 283 | } |
279 | 284 |
|
| 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 | + |
280 | 331 | } // namespace noteahead |
281 | 332 |
|
282 | 333 | QTEST_GUILESS_MAIN(noteahead::AudioFileIoTest) |
0 commit comments