// mastering.cpp — Reference-based audio mastering CLI tool // // Usage: mastering --target input.wav --reference ref.wav --output mastered.wav // [--no-limiter] [--pcm24] // // Implements the matchering algorithm: spectral + RMS matching against a reference track. #include #include #include #include #include #include #include #include "mastering.h" // ─── Minimal WAV Reader/Writer ────────────────────────────────────── #pragma pack(push, 1) struct WavHeader { char riff[4]; // "RIFF" uint32_t file_size; // file size - 8 char wave[4]; // "WAVE" }; struct WavChunkHdr { char id[4]; uint32_t size; }; struct WavFmt { uint16_t format; // 1=PCM, 3=IEEE float uint16_t channels; uint32_t sample_rate; uint32_t byte_rate; uint16_t block_align; uint16_t bits_per_sample; }; #pragma pack(pop) struct WavData { std::vector L, R; int sample_rate; int channels; }; static bool wav_read(const char * path, WavData & out) { FILE * f = fopen(path, "rb"); if (!f) { fprintf(stderr, "[WAV] Cannot open: %s\n", path); return false; } WavHeader hdr; if (fread(&hdr, sizeof(hdr), 1, f) != 1 || memcmp(hdr.riff, "RIFF", 4) != 0 || memcmp(hdr.wave, "WAVE", 4) != 0) { fprintf(stderr, "[WAV] Invalid WAV header: %s\n", path); fclose(f); return false; } WavFmt fmt = {}; bool found_fmt = false, found_data = false; int data_size = 0; while (!feof(f)) { WavChunkHdr chunk; if (fread(&chunk, sizeof(chunk), 1, f) != 1) break; if (memcmp(chunk.id, "fmt ", 4) == 0) { int to_read = std::min((uint32_t) sizeof(fmt), chunk.size); if (fread(&fmt, to_read, 1, f) != 1) break; // Skip extra fmt bytes if (chunk.size > (uint32_t) to_read) { fseek(f, chunk.size - to_read, SEEK_CUR); } found_fmt = true; } else if (memcmp(chunk.id, "data", 4) == 0) { data_size = chunk.size; found_data = true; break; // data follows } else { // Skip unknown chunk fseek(f, chunk.size, SEEK_CUR); } } if (!found_fmt || !found_data) { fprintf(stderr, "[WAV] Missing fmt or data chunk: %s\n", path); fclose(f); return false; } // WAVE_FORMAT_EXTENSIBLE (0xFFFE): the real format code is in the sub-format // GUID at the end of the extended fmt chunk. The first 2 bytes of the GUID // encode the actual format (1=PCM, 3=float). Common for 24/32-bit and // multi-channel audio from DAWs. if (fmt.format == 0xFFFE) { // The extended fmt chunk has: cbSize(2) + validBitsPerSample(2) + // channelMask(4) + subFormat GUID(16). We already read the base WavFmt // (16 bytes), and we skipped any extra bytes. We need to re-read the // extension. Seek back to re-read the extension portion. // Actually, we skipped extra fmt bytes — we need to read them before skip. // Let's fix: the fmt chunk was already fully consumed (read + skip). // We need to handle this in the fmt reading section above. For now, // the simplest fix: re-open and re-parse just the fmt extension. fprintf(stderr, "[WAV] WAVE_FORMAT_EXTENSIBLE detected, attempting re-parse: %s\n", path); fclose(f); f = fopen(path, "rb"); if (!f) return false; fseek(f, sizeof(WavHeader), SEEK_SET); // Scan for fmt chunk again while (!feof(f)) { WavChunkHdr chunk2; if (fread(&chunk2, sizeof(chunk2), 1, f) != 1) break; if (memcmp(chunk2.id, "fmt ", 4) == 0) { // Read base fmt (16 bytes) WavFmt fmt2 = {}; int base = std::min((uint32_t)sizeof(fmt2), chunk2.size); if (fread(&fmt2, base, 1, f) != 1) break; // Read extension: cbSize(2), validBits(2), channelMask(4), subFormat(16) if (chunk2.size >= 40) { // 16 base + 2 cbSize + 2 validBits + 4 mask + 16 GUID uint16_t cb_size = 0; uint16_t valid_bits = 0; uint32_t channel_mask = 0; uint16_t sub_format = 0; fread(&cb_size, 2, 1, f); fread(&valid_bits, 2, 1, f); fread(&channel_mask, 4, 1, f); fread(&sub_format, 2, 1, f); // first 2 bytes of GUID = real format fmt.format = sub_format; if (valid_bits > 0) { fmt.bits_per_sample = valid_bits; } fprintf(stderr, "[WAV] EXTENSIBLE sub-format: %d (%s), valid bits: %d\n", sub_format, sub_format == 1 ? "PCM" : sub_format == 3 ? "float" : "unknown", valid_bits > 0 ? valid_bits : fmt.bits_per_sample); } break; } else { fseek(f, chunk2.size, SEEK_CUR); } } fclose(f); // Re-open and seek to data chunk f = fopen(path, "rb"); if (!f) return false; fseek(f, sizeof(WavHeader), SEEK_SET); found_data = false; while (!feof(f)) { WavChunkHdr chunk2; if (fread(&chunk2, sizeof(chunk2), 1, f) != 1) break; if (memcmp(chunk2.id, "data", 4) == 0) { data_size = chunk2.size; found_data = true; break; } else { fseek(f, chunk2.size, SEEK_CUR); } } if (!found_data) { fprintf(stderr, "[WAV] Cannot find data chunk on re-parse: %s\n", path); fclose(f); return false; } } if (fmt.format != 1 && fmt.format != 3) { fprintf(stderr, "[WAV] Unsupported format %d (need PCM=1 or float=3): %s\n", fmt.format, path); fclose(f); return false; } if (fmt.channels < 1 || fmt.channels > 2) { fprintf(stderr, "[WAV] Unsupported channel count %d: %s\n", fmt.channels, path); fclose(f); return false; } int bytes_per_sample = fmt.bits_per_sample / 8; int n_samples = data_size / (bytes_per_sample * fmt.channels); out.sample_rate = fmt.sample_rate; out.channels = fmt.channels; out.L.resize(n_samples); out.R.resize(n_samples); // Read raw data std::vector raw(data_size); if (fread(raw.data(), 1, data_size, f) != (size_t) data_size) { fprintf(stderr, "[WAV] Truncated data: %s\n", path); fclose(f); return false; } fclose(f); // Convert to float for (int i = 0; i < n_samples; i++) { for (int ch = 0; ch < fmt.channels; ch++) { int offset = (i * fmt.channels + ch) * bytes_per_sample; float val = 0.0f; if (fmt.format == 3) { // IEEE float if (bytes_per_sample == 4) { memcpy(&val, raw.data() + offset, 4); } else if (bytes_per_sample == 8) { double dval; memcpy(&dval, raw.data() + offset, 8); val = (float) dval; } } else { // PCM integer if (bytes_per_sample == 2) { int16_t ival; memcpy(&ival, raw.data() + offset, 2); val = ival / 32768.0f; } else if (bytes_per_sample == 3) { int32_t ival = 0; memcpy(&ival, raw.data() + offset, 3); if (ival & 0x800000) ival |= 0xFF000000; // sign extend val = ival / 8388608.0f; } else if (bytes_per_sample == 4) { int32_t ival; memcpy(&ival, raw.data() + offset, 4); val = (float) ((double) ival / 2147483648.0); } } if (ch == 0) out.L[i] = val; else out.R[i] = val; } } // Mono → stereo if (fmt.channels == 1) { out.R = out.L; out.channels = 2; } fprintf(stderr, "[WAV] Read %s: %d samples, %d ch, %d Hz, %d-bit %s\n", path, n_samples, fmt.channels, fmt.sample_rate, fmt.bits_per_sample, fmt.format == 3 ? "float" : "PCM"); return true; } static bool wav_write(const char * path, const float * L, const float * R, int n, int sample_rate, int bits = 16) { FILE * f = fopen(path, "wb"); if (!f) { fprintf(stderr, "[WAV] Cannot create: %s\n", path); return false; } int channels = 2; int bytes_per_sample = bits / 8; int data_size = n * channels * bytes_per_sample; WavHeader hdr; memcpy(hdr.riff, "RIFF", 4); hdr.file_size = 36 + data_size; memcpy(hdr.wave, "WAVE", 4); WavChunkHdr fmt_chunk; memcpy(fmt_chunk.id, "fmt ", 4); fmt_chunk.size = 16; WavFmt fmt; fmt.format = (bits == 32) ? 3 : 1; // float or PCM fmt.channels = channels; fmt.sample_rate = sample_rate; fmt.bits_per_sample = bits; fmt.block_align = channels * bytes_per_sample; fmt.byte_rate = sample_rate * fmt.block_align; WavChunkHdr data_chunk; memcpy(data_chunk.id, "data", 4); data_chunk.size = data_size; fwrite(&hdr, sizeof(hdr), 1, f); fwrite(&fmt_chunk, sizeof(fmt_chunk), 1, f); fwrite(&fmt, sizeof(fmt), 1, f); fwrite(&data_chunk, sizeof(data_chunk), 1, f); // Write interleaved samples for (int i = 0; i < n; i++) { float l = std::clamp(L[i], -1.0f, 1.0f); float r = std::clamp(R[i], -1.0f, 1.0f); if (bits == 16) { int16_t sl = (int16_t) (l * 32767.0f); int16_t sr = (int16_t) (r * 32767.0f); fwrite(&sl, 2, 1, f); fwrite(&sr, 2, 1, f); } else if (bits == 24) { int32_t sl = (int32_t) (l * 8388607.0f); int32_t sr_v = (int32_t) (r * 8388607.0f); fwrite(&sl, 3, 1, f); fwrite(&sr_v, 3, 1, f); } else if (bits == 32) { fwrite(&l, 4, 1, f); fwrite(&r, 4, 1, f); } } fclose(f); fprintf(stderr, "[WAV] Wrote %s: %d samples, %d Hz, %d-bit\n", path, n, sample_rate, bits); return true; } // ─── CLI ──────────────────────────────────────────────────────────── static void print_usage(const char * prog) { fprintf(stderr, "Usage: %s --target input.wav --reference ref.wav --output mastered.wav\n" " [--pcm24] [--pcm32f]\n" "\n" "Reference-based audio mastering using the matchering algorithm.\n" "Matches the RMS level, frequency spectrum, and dynamic range\n" "of the target track to the reference track.\n" "\n" "Options:\n" " --target PATH Input audio file to master (WAV)\n" " --reference PATH Reference track to match against (WAV)\n" " --output PATH Output mastered file (WAV)\n" " --pcm24 Write 24-bit PCM output (default: 16-bit)\n" " --pcm32f Write 32-bit float output\n", prog); } int main(int argc, char ** argv) { const char * target_path = nullptr; const char * ref_path = nullptr; const char * output_path = nullptr; int output_bits = 16; for (int i = 1; i < argc; i++) { if (strcmp(argv[i], "--target") == 0 && i + 1 < argc) { target_path = argv[++i]; } else if (strcmp(argv[i], "--reference") == 0 && i + 1 < argc) { ref_path = argv[++i]; } else if (strcmp(argv[i], "--output") == 0 && i + 1 < argc) { output_path = argv[++i]; } else if (strcmp(argv[i], "--pcm24") == 0) { output_bits = 24; } else if (strcmp(argv[i], "--pcm32f") == 0) { output_bits = 32; } else if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) { print_usage(argv[0]); return 0; } else { fprintf(stderr, "Unknown option: %s\n", argv[i]); print_usage(argv[0]); return 1; } } if (!target_path || !ref_path || !output_path) { fprintf(stderr, "Error: --target, --reference, and --output are required\n\n"); print_usage(argv[0]); return 1; } auto t_start = std::chrono::high_resolution_clock::now(); // Read input files WavData target, reference; if (!wav_read(target_path, target)) return 1; if (!wav_read(ref_path, reference)) return 1; // Run mastering auto result = mastering_process( target.L.data(), target.R.data(), (int) target.L.size(), target.sample_rate, reference.L.data(), reference.R.data(), (int) reference.L.size(), reference.sample_rate ); if (!result.success) { fprintf(stderr, "[Mastering] FAILED: %s\n", result.error ? result.error : "unknown"); return 1; } // Write output if (!wav_write(output_path, result.L.data(), result.R.data(), (int) result.L.size(), target.sample_rate, output_bits)) { return 1; } auto t_end = std::chrono::high_resolution_clock::now(); double elapsed = std::chrono::duration(t_end - t_start).count(); fprintf(stderr, "[Mastering] Total time: %.2f seconds\n", elapsed); return 0; }