P0.3-4: harness reads flat params (in/out/[conf]), byte-verified trim; verify_bit_exact.py uses SOURCE WAVE+RENDER_FILE from RPP, sample-report mono/stereo
This commit is contained in:
+92
-72
@@ -1,91 +1,68 @@
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include "spectral.hpp"
|
||||
#include "filter.hpp"
|
||||
#include "detect.hpp"
|
||||
#include "ms.hpp"
|
||||
#include "params.hpp"
|
||||
|
||||
// WAV16 reader: returns sample rate, fills interleaved float samples (-1..1).
|
||||
static float read_wav16(const char* path, std::vector<float>& out) {
|
||||
std::ifstream f(path, std::ios::binary);
|
||||
if (!f) return -1;
|
||||
|
||||
char riff[12];
|
||||
f.read(riff, 12);
|
||||
if (riff[0] != 'R' || riff[1] != 'I' || riff[2] != 'F' || riff[3] != 'F') return -1;
|
||||
if (riff[8] != 'W' || riff[9] != 'A' || riff[10] != 'V' || riff[11] != 'E') return -1;
|
||||
|
||||
if (memcmp(riff, "RIFF", 4) || memcmp(riff + 8, "WAVE", 4)) return -1;
|
||||
while (true) {
|
||||
char chunk_id[4];
|
||||
f.read(chunk_id, 4);
|
||||
if (!f.good()) return -1;
|
||||
|
||||
uint32_t chunk_size;
|
||||
f.read(reinterpret_cast<char*>(&chunk_size), 4);
|
||||
if (!f.good()) return -1;
|
||||
|
||||
if (chunk_id[0] == 'f' && chunk_id[1] == 'm' && chunk_id[2] == 't' && chunk_id[3] == ' ') {
|
||||
if (!f.read(chunk_id, 4) || !f.read(reinterpret_cast<char*>(&chunk_size), 4)) return -1;
|
||||
if (memcmp(chunk_id, "fmt ", 4) == 0) {
|
||||
if (chunk_size < 16) return -1;
|
||||
|
||||
int16_t audio_fmt, bits;
|
||||
uint16_t channels, block_align;
|
||||
uint16_t audio_fmt, channels, block_align, bits;
|
||||
uint32_t sample_rate, bytes_per_sec;
|
||||
|
||||
f.read(reinterpret_cast<char*>(&audio_fmt), 2);
|
||||
f.read(reinterpret_cast<char*>(&channels), 2);
|
||||
f.read(reinterpret_cast<char*>(&sample_rate), 4);
|
||||
f.read(reinterpret_cast<char*>(&bytes_per_sec), 4);
|
||||
f.read(reinterpret_cast<char*>(&block_align), 2);
|
||||
f.read(reinterpret_cast<char*>(&bits), 2);
|
||||
|
||||
if (chunk_size > 16) f.seekg(chunk_size - 16, std::ios::cur);
|
||||
|
||||
while (true) {
|
||||
char data_id[4];
|
||||
f.read(data_id, 4);
|
||||
if (!f.good()) return -1;
|
||||
|
||||
uint32_t data_size;
|
||||
f.read(reinterpret_cast<char*>(&data_size), 4);
|
||||
if (!f.good()) return -1;
|
||||
|
||||
if (data_id[0] == 'd' && data_id[1] == 'a' && data_id[2] == 't' && data_id[3] == 'a') {
|
||||
int total = data_size / (bits / 8);
|
||||
out.resize(total);
|
||||
std::vector<int16_t> raw(total);
|
||||
f.read(reinterpret_cast<char*>(raw.data()), data_size);
|
||||
for (int i = 0; i < total; i++) {
|
||||
out[i] = static_cast<float>(raw[i]) / 32768.0f;
|
||||
}
|
||||
char id[4];
|
||||
uint32_t dsize;
|
||||
if (!f.read(id, 4) || !f.read(reinterpret_cast<char*>(&dsize), 4)) return -1;
|
||||
if (memcmp(id, "data", 4) == 0) {
|
||||
size_t n = dsize / (bits / 8);
|
||||
out.resize(n);
|
||||
std::vector<int16_t> raw(n);
|
||||
f.read(reinterpret_cast<char*>(raw.data()), dsize);
|
||||
for (size_t i = 0; i < n; i++) out[i] = static_cast<float>(raw[i]) / 32768.0f;
|
||||
return static_cast<float>(sample_rate);
|
||||
} else {
|
||||
f.seekg(data_size, std::ios::cur);
|
||||
f.seekg(dsize, std::ios::cur);
|
||||
}
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
f.seekg(chunk_size, std::ios::cur);
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void write_wav24(const char* path, const float* data, int samples, int channels, int sample_rate) {
|
||||
static void write_wav24(const char* path, const float* data, size_t samples, int channels, int sample_rate) {
|
||||
std::ofstream f(path, std::ios::binary);
|
||||
|
||||
int bits = 24;
|
||||
int block_align = channels * bits / 8;
|
||||
int bytes_per_sec = sample_rate * block_align;
|
||||
int data_size = samples * channels * 3;
|
||||
|
||||
f.write("RIFF", 4);
|
||||
int block_align = channels * 3;
|
||||
int data_size = static_cast<int>(samples) * channels * 3;
|
||||
int file_size = 36 + data_size;
|
||||
f.write("RIFF", 4);
|
||||
f.write(reinterpret_cast<const char*>(&file_size), 4);
|
||||
f.write("WAVE", 4);
|
||||
|
||||
f.write("fmt ", 4);
|
||||
int fmt_size = 16;
|
||||
f.write(reinterpret_cast<const char*>(&fmt_size), 4);
|
||||
@@ -93,66 +70,109 @@ static void write_wav24(const char* path, const float* data, int samples, int ch
|
||||
f.write(reinterpret_cast<const char*>(&audio_fmt), 2);
|
||||
f.write(reinterpret_cast<const char*>(&channels), 2);
|
||||
f.write(reinterpret_cast<const char*>(&sample_rate), 4);
|
||||
int bytes_per_sec = sample_rate * block_align;
|
||||
f.write(reinterpret_cast<const char*>(&bytes_per_sec), 4);
|
||||
f.write(reinterpret_cast<const char*>(&block_align), 2);
|
||||
int16_t bits = 24;
|
||||
f.write(reinterpret_cast<const char*>(&bits), 2);
|
||||
|
||||
f.write("data", 4);
|
||||
f.write(reinterpret_cast<const char*>(&data_size), 4);
|
||||
|
||||
for (int i = 0; i < samples * channels; i++) {
|
||||
for (size_t i = 0; i < samples * static_cast<size_t>(channels); i++) {
|
||||
float val = std::max(-1.0f, std::min(1.0f, data[i]));
|
||||
int32_t ival = static_cast<int32_t>(val * 8388607.0f);
|
||||
unsigned char bytes[3];
|
||||
bytes[0] = ival & 0xff;
|
||||
bytes[1] = (ival >> 8) & 0xff;
|
||||
bytes[2] = (ival >> 16) & 0xff;
|
||||
unsigned char bytes[3] = { static_cast<unsigned char>(ival & 0xff),
|
||||
static_cast<unsigned char>((ival >> 8) & 0xff),
|
||||
static_cast<unsigned char>((ival >> 16) & 0xff) };
|
||||
f.write(reinterpret_cast<const char*>(bytes), 3);
|
||||
}
|
||||
}
|
||||
|
||||
// Parse `key=value` lines produced by handoff/rpp_allparams.py --flat.
|
||||
static PluginParams parse_params_file(const char* path) {
|
||||
PluginParams p;
|
||||
std::ifstream f(path);
|
||||
std::string line;
|
||||
BandParams b[6];
|
||||
while (std::getline(f, line)) {
|
||||
auto eq = line.find('=');
|
||||
if (eq == std::string::npos) continue;
|
||||
std::string k = line.substr(0, eq);
|
||||
double v = std::atof(line.c_str() + eq + 1);
|
||||
if (k == "depth") p.depth = v;
|
||||
else if (k == "mix") p.mix = v;
|
||||
else if (k == "mode") p.mode = v;
|
||||
else if (k == "attack") p.attack = v;
|
||||
else if (k == "release") p.release = v;
|
||||
else if (k == "selectivity") p.selectivity = v;
|
||||
else if (k == "sharpness") p.sharpness = v;
|
||||
else if (k == "resolution") p.resolution = v;
|
||||
else if (k == "offline resolution") p.offline_resolution = v;
|
||||
else if (k == "oversample") p.oversample = v;
|
||||
else if (k == "offline oversample") p.offline_oversample = v;
|
||||
else if (k == "stereo balance") p.stereo_balance = v;
|
||||
else if (k == "stereo link") p.stereo_link = v;
|
||||
else if (k == "stereo mode") p.stereo_mode = v;
|
||||
else if (k == "bypass") p.bypass = v;
|
||||
for (int i = 0; i < 6; i++) {
|
||||
std::string pre = "band" + std::to_string(i) + " ";
|
||||
if (k == pre + "freq") b[i].freq = v;
|
||||
else if (k == pre + "q") b[i].q = v;
|
||||
else if (k == pre + "sens") b[i].sens = v;
|
||||
else if (k == pre + "mode") b[i].mode = v;
|
||||
else if (k == pre + "on") b[i].on = v;
|
||||
else if (k == pre + "balance") b[i].balance = v;
|
||||
}
|
||||
}
|
||||
for (auto& bd : b) p.bands.push_back(bd);
|
||||
return p;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
if (argc < 3) {
|
||||
std::cerr << "Usage: " << argv[0] << " input.wav output.wav" << std::endl;
|
||||
std::cerr << "Usage: " << argv[0] << " input.wav output.wav [params.conf]\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
PluginParams params;
|
||||
if (argc > 3) params = parse_params_file(argv[3]);
|
||||
else {
|
||||
params.bands.push_back(BandParams{});
|
||||
}
|
||||
|
||||
std::vector<float> input;
|
||||
float sr = read_wav16(argv[1], input);
|
||||
if (sr <= 0 || input.empty()) {
|
||||
std::cerr << "Failed to read input file" << std::endl;
|
||||
std::cerr << "Failed to read input file\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
size_t total_samples = input.size();
|
||||
int channels = 2;
|
||||
size_t frames = total_samples / channels;
|
||||
|
||||
std::vector<float> output(total_samples);
|
||||
|
||||
SpectralProcessor sp(2048, 512);
|
||||
sp.setDetectorParams(10.0f, 10.0f, 0.864f);
|
||||
|
||||
int channels = 2; // all etalon renders are 2ch
|
||||
size_t frames = input.size() / static_cast<size_t>(channels);
|
||||
// Trim guard: output length == input length (honest metric, B.14).
|
||||
if (input.size() % channels != 0) frames = input.size() / channels;
|
||||
|
||||
std::vector<float> left_in(frames), right_in(frames);
|
||||
for (size_t i = 0; i < frames; i++) {
|
||||
left_in[i] = input[i * 2];
|
||||
right_in[i] = input[i * 2 + 1];
|
||||
}
|
||||
|
||||
|
||||
SpectralProcessor sp(2048, 512);
|
||||
sp.setDetectorParams(
|
||||
static_cast<float>(params.sharpness),
|
||||
static_cast<float>(params.selectivity),
|
||||
static_cast<float>(params.depth));
|
||||
|
||||
std::vector<float> left(frames, 0.0f), right(frames, 0.0f);
|
||||
encode_ms(left_in.data(), right_in.data(), frames);
|
||||
sp.processBlock(left_in.data(), left.data(), frames, 1);
|
||||
sp.processBlock(right_in.data(), right.data(), frames, 1);
|
||||
decode_ms(left.data(), right.data(), frames);
|
||||
|
||||
|
||||
std::vector<float> output(frames * 2);
|
||||
for (size_t i = 0; i < frames; i++) {
|
||||
output[i * 2] = left[i];
|
||||
output[i * 2 + 1] = right[i];
|
||||
}
|
||||
|
||||
write_wav24(argv[2], output.data(), frames, channels, static_cast<int>(sr));
|
||||
|
||||
std::cout << "Done!" << std::endl;
|
||||
|
||||
std::cout << "Done! frames=" << frames << " sr=" << sr << "\n";
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user