From 98def09292f62ae0de17c1f60a2599c6e0b4fbce Mon Sep 17 00:00:00 2001 From: skal Date: Thu, 5 Mar 2026 22:14:25 +0100 Subject: chore: remove dead specplay tool and all references specplay was removed from the build but source/docs remained. Delete tools/specplay.cc, tools/specplay_README.md, and remove specplay sections from TOOLS_REFERENCE.md and BACKLOG.md. Co-Authored-By: Claude Sonnet 4.6 --- tools/specplay.cc | 212 ----------------------------------------------- tools/specplay_README.md | 164 ------------------------------------ 2 files changed, 376 deletions(-) delete mode 100644 tools/specplay.cc delete mode 100644 tools/specplay_README.md (limited to 'tools') diff --git a/tools/specplay.cc b/tools/specplay.cc deleted file mode 100644 index 9fa9355..0000000 --- a/tools/specplay.cc +++ /dev/null @@ -1,212 +0,0 @@ -// Standalone tool to play .spec or .wav files for debugging -// Usage: ./specplay - -#include "audio/dct.h" -#include "audio/window.h" -#include -#include -#include - -#define MINIAUDIO_IMPLEMENTATION -#include "miniaudio.h" - -struct PlaybackState { - float* pcm_data; - size_t num_samples; - size_t playback_pos; -}; - -void audio_callback(ma_device* device, void* output, const void* input, - ma_uint32 frame_count) { - PlaybackState* state = (PlaybackState*)device->pUserData; - float* out = (float*)output; - - for (ma_uint32 i = 0; i < frame_count; i++) { - if (state->playback_pos < state->num_samples) { - float sample = state->pcm_data[state->playback_pos++]; - // Clamp to [-1, 1] and warn if clipping - if (sample > 1.0f || sample < -1.0f) { - fprintf(stderr, "[CLIP at sample %zu: %.3f]\n", state->playback_pos - 1, - sample); - sample = (sample > 1.0f) ? 1.0f : -1.0f; - } - out[i * 2] = sample; // Left - out[i * 2 + 1] = sample; // Right (mono) - } else { - out[i * 2] = 0.0f; - out[i * 2 + 1] = 0.0f; - } - } -} - -float* load_spec(const char* path, size_t* out_num_samples) { - FILE* f = fopen(path, "rb"); - if (!f) { - fprintf(stderr, "Failed to open %s\n", path); - return nullptr; - } - - // Read SpecHeader - struct SpecHeader { - char magic[4]; - int32_t version; - int32_t dct_size; - int32_t num_frames; - }; - - SpecHeader header; - if (fread(&header, sizeof(SpecHeader), 1, f) != 1) { - fprintf(stderr, "Failed to read SpecHeader\n"); - fclose(f); - return nullptr; - } - - // Validate header - if (memcmp(header.magic, "SPEC", 4) != 0) { - fprintf(stderr, "Invalid magic bytes (expected 'SPEC')\n"); - fclose(f); - return nullptr; - } - - printf("Loading .spec: version=%d, dct_size=%d, frames=%d\n", header.version, - header.dct_size, header.num_frames); - - uint32_t num_frames = header.num_frames; - - // Read spectral data - size_t spec_size = num_frames * DCT_SIZE; - float* spec_data = (float*)malloc(spec_size * sizeof(float)); - if (fread(spec_data, sizeof(float), spec_size, f) != spec_size) { - fprintf(stderr, "Failed to read spectral data\n"); - free(spec_data); - fclose(f); - return nullptr; - } - fclose(f); - - // Convert to PCM via IDCT - *out_num_samples = spec_size; - float* pcm_data = (float*)malloc(*out_num_samples * sizeof(float)); - - for (uint32_t frame = 0; frame < num_frames; frame++) { - const float* spectral_frame = spec_data + (frame * DCT_SIZE); - float* time_frame = pcm_data + (frame * DCT_SIZE); - idct_512(spectral_frame, time_frame); - } - - free(spec_data); - - // Analyze PCM statistics - float peak = 0.0f, rms_sum = 0.0f; - for (size_t i = 0; i < *out_num_samples; i++) { - float abs_val = fabsf(pcm_data[i]); - if (abs_val > peak) - peak = abs_val; - rms_sum += pcm_data[i] * pcm_data[i]; - } - float rms = sqrtf(rms_sum / *out_num_samples); - - printf("PCM stats: Peak=%.3f, RMS=%.3f\n", peak, rms); - if (peak > 1.0f) { - printf("[WARNING] Peak exceeds 1.0! Will clip during playback.\n"); - } - - return pcm_data; -} - -float* load_wav(const char* path, size_t* out_num_samples) { - ma_decoder decoder; - ma_decoder_config config = ma_decoder_config_init(ma_format_f32, 1, 32000); - - if (ma_decoder_init_file(path, &config, &decoder) != MA_SUCCESS) { - fprintf(stderr, "Failed to open WAV file: %s\n", path); - return nullptr; - } - - ma_uint64 frame_count; - ma_decoder_get_length_in_pcm_frames(&decoder, &frame_count); - *out_num_samples = (size_t)frame_count; - - float* pcm_data = (float*)malloc(*out_num_samples * sizeof(float)); - ma_decoder_read_pcm_frames(&decoder, pcm_data, frame_count, nullptr); - ma_decoder_uninit(&decoder); - - printf("Loaded .wav: %zu samples\n", *out_num_samples); - - // Analyze PCM statistics - float peak = 0.0f, rms_sum = 0.0f; - for (size_t i = 0; i < *out_num_samples; i++) { - float abs_val = fabsf(pcm_data[i]); - if (abs_val > peak) - peak = abs_val; - rms_sum += pcm_data[i] * pcm_data[i]; - } - float rms = sqrtf(rms_sum / *out_num_samples); - - printf("PCM stats: Peak=%.3f, RMS=%.3f\n", peak, rms); - - return pcm_data; -} - -int main(int argc, char** argv) { - if (argc != 2) { - fprintf(stderr, "Usage: %s \n", argv[0]); - return 1; - } - - const char* path = argv[1]; - const char* ext = strrchr(path, '.'); - - PlaybackState state = {}; - - if (ext && strcmp(ext, ".spec") == 0) { - state.pcm_data = load_spec(path, &state.num_samples); - } else if (ext && (strcmp(ext, ".wav") == 0 || strcmp(ext, ".aif") == 0)) { - state.pcm_data = load_wav(path, &state.num_samples); - } else { - fprintf(stderr, "Unknown file type: %s\n", path); - return 1; - } - - if (!state.pcm_data) { - fprintf(stderr, "Failed to load audio\n"); - return 1; - } - - printf("Playing %.2f seconds... Press Ctrl+C to stop.\n", - (float)state.num_samples / 32000.0f); - - // Initialize miniaudio - ma_device_config device_config = - ma_device_config_init(ma_device_type_playback); - device_config.playback.format = ma_format_f32; - device_config.playback.channels = 2; - device_config.sampleRate = 32000; - device_config.dataCallback = audio_callback; - device_config.pUserData = &state; - - ma_device device; - if (ma_device_init(NULL, &device_config, &device) != MA_SUCCESS) { - fprintf(stderr, "Failed to initialize audio device\n"); - free(state.pcm_data); - return 1; - } - - if (ma_device_start(&device) != MA_SUCCESS) { - fprintf(stderr, "Failed to start audio device\n"); - ma_device_uninit(&device); - free(state.pcm_data); - return 1; - } - - // Wait for playback to finish - while (state.playback_pos < state.num_samples) { - ma_sleep(100); - } - - ma_device_uninit(&device); - free(state.pcm_data); - - printf("Playback complete.\n"); - return 0; -} diff --git a/tools/specplay_README.md b/tools/specplay_README.md deleted file mode 100644 index 72d9ec1..0000000 --- a/tools/specplay_README.md +++ /dev/null @@ -1,164 +0,0 @@ -# specplay - Audio Analysis & Playback Tool - -Standalone diagnostic tool for analyzing and playing .spec spectrogram files and .wav audio files. - -## Usage - -```bash -./build/specplay -``` - -## Features - -### Current (v1.0) -- ✅ Plays .spec files via IDCT synthesis (matches demo playback exactly) -- ✅ Plays .wav files for comparison -- ✅ Reports PCM statistics: - - **Peak level**: Maximum absolute sample value (detects clipping if > 1.0) - - **RMS level**: Root-mean-square energy (loudness measure) -- ✅ Real-time clipping detection during playback with sample position -- ✅ Validates .spec file format (magic bytes, version, DCT size) - -### Example Output -``` -Loading .spec: version=1, dct_size=512, frames=68 -PCM stats: Peak=0.403, RMS=0.058 -Playing 1.09 seconds... Press Ctrl+C to stop. -Playback complete. -``` - -## Use Cases - -1. **Debugging Audio Issues** - - Quickly identify which samples have clipping (Peak > 1.0) - - Compare .wav source vs .spec output to detect analysis artifacts - - Verify spectrogram regeneration after DCT changes - -2. **Quality Assurance** - - Batch test all .spec files for clipping before committing - - Measure loudness consistency across samples (RMS levels) - - Validate spectrograms match expected characteristics - -3. **Development Workflow** - - Test individual samples without running full demo - - A/B compare different spectrogram generation parameters - - Verify procedural note generation output - -## Technical Details - -- **Sample Rate**: 32kHz (matches demo audio engine) -- **Format**: Mono (duplicated to stereo for playback) -- **IDCT**: Uses same `idct_512()` function as demo (bit-exact) -- **Clamping**: Clipping detected but samples clamped to [-1, 1] for playback - -## Future Enhancement Ideas - -### Priority 1: Analysis Features -- [ ] **Spectral visualization**: ASCII art frequency plot -- [ ] **Waveform display**: Time-domain amplitude graph -- [ ] **Frequency analysis**: Dominant frequency, spectral centroid -- [ ] **Dynamic range**: Measure headroom, suggest normalization -- [ ] **Duration info**: Show seconds, samples, frames - -### Priority 2: Comparison Tools -- [ ] **Diff mode**: `specplay --compare file.wav file.spec` - - Side-by-side PCM stats - - RMS error, peak difference - - Correlation coefficient -- [ ] **Batch mode**: `specplay --batch assets/final/*.spec` - - Generate CSV report of all stats - - Sort by peak level (find clipping candidates) - - Find outliers (unusually loud/quiet samples) - -### Priority 3: Export Features -- [ ] **WAV export**: `specplay file.spec --export output.wav` - - Convert .spec → .wav for external analysis - - Useful for importing into audio editors -- [ ] **Normalization**: `specplay file.spec --normalize --export normalized.wav` - - Auto-scale to target peak/RMS - - Preserve transients - -### Priority 4: Advanced Analysis -- [ ] **Spectral envelope**: Extract formants, resonances -- [ ] **Harmonic analysis**: Detect fundamental frequency, harmonics -- [ ] **Onset detection**: Find transient attacks (kick/snare hits) -- [ ] **Spectral flux**: Measure frequency content change over time - -### Priority 5: Interactive Mode -- [ ] **Seek controls**: Play from specific time offset -- [ ] **Loop mode**: Repeat playback indefinitely -- [ ] **Volume control**: Adjust playback gain -- [ ] **Real-time waveform**: Live visualization during playback - -## Integration with Build System - -Built automatically when `DEMO_BUILD_TOOLS=ON`: -```bash -cmake -S . -B build -DDEMO_BUILD_TOOLS=ON -cmake --build build --target specplay -``` - -Or with all options: -```bash -cmake -S . -B build -DDEMO_ALL_OPTIONS=ON -cmake --build build -``` - -## Code Architecture - -- **Minimal dependencies**: Only uses audio subsystem + miniaudio -- **Self-contained**: No GPU, no platform layer (pure audio) -- **Size**: ~250 lines (easy to extend) -- **Format support**: - - `.spec` via SpecHeader parser - - `.wav` via miniaudio decoder (also handles .aif, .mp3) - -## Related Tools - -- **spectool**: Analyzes .wav → generates .spec (batch processing) -- **specview**: ASCII visualization of .spec frequency content -- **specplay**: This tool (playback + analysis) - -## Troubleshooting - -**"Failed to read SpecHeader"** -- File is not a valid .spec file -- Try with spectool to regenerate: `./build/spectool analyze input.wav output.spec` - -**"Peak exceeds 1.0! Will clip during playback."** -- Spectrogram has too much energy -- Likely needs regeneration after DCT changes -- Or source .wav is already clipping - -**No audio output** -- Check system audio device -- Ensure 32kHz sample rate is supported -- Try with a different .spec file (may be empty/silent) - -## Examples - -### Find all clipping samples -```bash -for f in assets/final/*.spec; do - ./build/specplay "$f" | grep "WARNING" && echo "$f" -done -``` - -### Compare wav source to spec output -```bash -./build/specplay assets/originals/kick.wav -./build/specplay assets/final/KICK.spec -# Compare Peak/RMS values -``` - -### Quick loudness check -```bash -./build/specplay assets/final/KICK_606.spec | grep "RMS" -# RMS > 0.3: loud, RMS < 0.1: quiet -``` - ---- - -**Last Updated**: February 6, 2026 -**Author**: Claude (AI assistant) -**Status**: Production-ready, actively used for debugging -- cgit v1.2.3