Clean release snapshot

This commit is contained in:
byrongamatos
2026-06-16 18:47:13 +02:00
commit 6c110398b4
574 changed files with 162566 additions and 0 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,53 @@
# Real-Instrument WAV Fixtures
Drop WAV recordings from your actual instruments here and they will be picked up automatically by the real-instrument test suite (`tests/js/yin.realinstrument.test.js`).
## Naming Convention
```text
<NoteClass><Octave>_<FreqHz>Hz[_label].wav
```
An optional label suffix (e.g. `_8string`, `_guitar`, `_take2`) can be added between `Hz` and `.wav` — it is ignored by the test runner.
Examples:
| File | Note | Expected frequency |
|------|------|--------------------|
| `E2_82.41Hz.wav` | E2 | 82.41 Hz |
| `E2_82.41Hz_8string.wav` | E2 | 82.41 Hz |
| `A2_110Hz_bass.wav` | A2 | 110.00 Hz |
| `A#3_233.08Hz.wav` | A#3 | 233.08 Hz |
| `D3_146.83Hz.wav` | D3 | 146.83 Hz |
The test asserts that YIN detects a frequency within **±20 cents** of the value in the filename.
## Recording Guidelines
1. **Capture the sustain portion** — not the attack or release. A 12 second clip of a note ringing cleanly is ideal. The test extracts a 4096-sample window from the middle of the file to skip transients.
2. **Length** — aim for ≤ 2 seconds to keep repository size small.
3. **Format****16-bit PCM WAV** (mono or stereo). Export at 44100 Hz or 48000 Hz from your DAW or audio interface.
- 32-bit float WAV is not supported by the parser — convert to 16-bit PCM first.
4. **Channels** — mono or stereo both work. Stereo files are averaged to mono before detection.
5. **Tune first** — play the note in tune against a reference tuner before recording. The file name encodes the expected frequency; a note recorded sharp or flat will fail the test.
## Frequency Reference (standard MIDI A4 = 440 Hz)
| Note | Hz |
|------|----|
| E2 | 82.41 |
| A2 | 110.00 |
| D3 | 146.83 |
| G3 | 196.00 |
| B3 | 246.94 |
| E4 | 329.63 |
| A4 | 440.00 |
| A#3 / Bb3 | 233.08 |
For any other note: `f = 440 * 2^((midi - 69) / 12)`.
## Committing Recordings
These WAV files **are committed to the repository** as regression fixtures. If a code change causes the detected pitch to drift outside ±20 cents, CI will flag it.
> **Repository size note:** Keep clips short (≤ 2 s). If the fixture corpus grows large, Git LFS is a future option — configure it with `git lfs track "tests/fixtures/audio/real/*.wav"`.
@@ -0,0 +1,62 @@
#!/usr/bin/env python3
"""
Generate WAV fixture files for YIN pitch detection tests.
Naming convention: <Note><Octave>_<FreqHz>Hz.wav
e.g. A4_440.0Hz.wav → 440.0 Hz sine wave
E2_82.41Hz.wav → 82.41 Hz sine wave
To add more fixtures, just call generate() with the desired note/frequency
and re-run this script, or drop in your own WAV files following the naming
convention (16-bit PCM mono, any sample rate ≥ 8000 Hz).
"""
import math
import struct
import wave
from pathlib import Path
FIXTURES_DIR = Path(__file__).parent / "audio" # tests/plugins/tuner/fixtures/audio/
SAMPLE_RATE = 44100
DURATION_SEC = 0.5
AMPLITUDE = 0.8
FIXTURES = [
("E2", 82.41),
("A2", 110.00),
("D3", 146.83),
("G3", 196.00),
("B3", 246.94),
("E4", 329.63),
("A4", 440.00),
]
def _freq_to_str(hz: float) -> str:
return f"{hz:.2f}".rstrip("0").rstrip(".")
def generate(note: str, freq_hz: float, sample_rate: int = SAMPLE_RATE,
duration_sec: float = DURATION_SEC, amplitude: float = AMPLITUDE) -> Path:
filename = f"{note}_{_freq_to_str(freq_hz)}Hz.wav"
path = FIXTURES_DIR / filename
n = int(sample_rate * duration_sec)
frames = b"".join(
struct.pack("<h", int(amplitude * math.sin(2 * math.pi * freq_hz * i / sample_rate) * 32767))
for i in range(n)
)
with wave.open(str(path), "w") as wf:
wf.setnchannels(1)
wf.setsampwidth(2)
wf.setframerate(sample_rate)
wf.writeframes(frames)
print(f" wrote {path.name} ({freq_hz} Hz, {n} samples)")
return path
if __name__ == "__main__":
FIXTURES_DIR.mkdir(parents=True, exist_ok=True)
print(f"Generating {len(FIXTURES)} WAV fixtures in {FIXTURES_DIR}/")
for note, freq in FIXTURES:
generate(note, freq)
print("Done.")