Initial release

This commit is contained in:
civ
2026-08-16 18:24:52 +07:00
commit 876886a39a
13244 changed files with 2353959 additions and 0 deletions
+401
View File
@@ -0,0 +1,401 @@
// 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 <chrono>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <vector>
#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<float> 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<uint8_t> 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<double>(t_end - t_start).count();
fprintf(stderr, "[Mastering] Total time: %.2f seconds\n", elapsed);
return 0;
}