#include #include #include #include #include #include #include #include #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& out) { std::ifstream f(path, std::ios::binary); if (!f) return -1; char riff[12]; f.read(riff, 12); if (memcmp(riff, "RIFF", 4) || memcmp(riff + 8, "WAVE", 4)) return -1; while (true) { char chunk_id[4]; uint32_t chunk_size; if (!f.read(chunk_id, 4) || !f.read(reinterpret_cast(&chunk_size), 4)) return -1; if (memcmp(chunk_id, "fmt ", 4) == 0) { if (chunk_size < 16) return -1; uint16_t audio_fmt, channels, block_align, bits; uint32_t sample_rate, bytes_per_sec; f.read(reinterpret_cast(&audio_fmt), 2); f.read(reinterpret_cast(&channels), 2); f.read(reinterpret_cast(&sample_rate), 4); f.read(reinterpret_cast(&bytes_per_sec), 4); f.read(reinterpret_cast(&block_align), 2); f.read(reinterpret_cast(&bits), 2); if (chunk_size > 16) f.seekg(chunk_size - 16, std::ios::cur); while (true) { char id[4]; uint32_t dsize; if (!f.read(id, 4) || !f.read(reinterpret_cast(&dsize), 4)) return -1; if (memcmp(id, "data", 4) == 0) { size_t n = dsize / (bits / 8); out.resize(n); std::vector raw(n); f.read(reinterpret_cast(raw.data()), dsize); for (size_t i = 0; i < n; i++) out[i] = static_cast(raw[i]) / 32768.0f; return static_cast(sample_rate); } else { f.seekg(dsize, std::ios::cur); } } } else { f.seekg(chunk_size, std::ios::cur); } } } 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 block_align = channels * 3; int data_size = static_cast(samples) * channels * 3; int file_size = 36 + data_size; f.write("RIFF", 4); f.write(reinterpret_cast(&file_size), 4); f.write("WAVE", 4); f.write("fmt ", 4); int fmt_size = 16; f.write(reinterpret_cast(&fmt_size), 4); int16_t audio_fmt = 1; f.write(reinterpret_cast(&audio_fmt), 2); f.write(reinterpret_cast(&channels), 2); f.write(reinterpret_cast(&sample_rate), 4); int bytes_per_sec = sample_rate * block_align; f.write(reinterpret_cast(&bytes_per_sec), 4); f.write(reinterpret_cast(&block_align), 2); int16_t bits = 24; f.write(reinterpret_cast(&bits), 2); f.write("data", 4); f.write(reinterpret_cast(&data_size), 4); for (size_t i = 0; i < samples * static_cast(channels); i++) { float val = std::max(-1.0f, std::min(1.0f, data[i])); int32_t ival = static_cast(val * 8388607.0f); unsigned char bytes[3] = { static_cast(ival & 0xff), static_cast((ival >> 8) & 0xff), static_cast((ival >> 16) & 0xff) }; f.write(reinterpret_cast(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 [params.conf]\n"; return 1; } PluginParams params; if (argc > 3) params = parse_params_file(argv[3]); else { params.bands.push_back(BandParams{}); } std::vector input; float sr = read_wav16(argv[1], input); if (sr <= 0 || input.empty()) { std::cerr << "Failed to read input file\n"; return 1; } int channels = 2; // all etalon renders are 2ch size_t frames = input.size() / static_cast(channels); // Trim guard: output length == input length (honest metric, B.14). if (input.size() % channels != 0) frames = input.size() / channels; std::vector 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); std::vector bands; for (const auto& b : params.bands) { if (b.on > 0.5f && b.freq > 1.0f) { DetectorBand db; db.fc = static_cast(b.freq); db.q = static_cast(b.q > 0.0f ? b.q : 1.0f); db.sens = static_cast(b.sens); bands.push_back(db); } } if (bands.empty()) { DetectorBand db{500.0f, 1.0f, 12.0f}; bands.push_back(db); } sp.setDetectorParams(bands); std::vector 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 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(sr)); std::cout << "Done! frames=" << frames << " sr=" << sr << "\n"; return 0; }