"""Source-time fallback contracts on a known hum-like signal and plain noise.""" from __future__ import annotations from pathlib import Path import tempfile import unittest class F0FallbackTest(unittest.TestCase): def test_short_stable_note_survives_sheet_sage_empty_melody(self): import numpy as np import pretty_midi import soundfile as sf from f0_fallback import transcribe_f0 rate = 24000 wave = np.zeros(round(.6 * rate), dtype="float32") t = np.arange(round(.48 * rate)) / rate wave[round(.08 * rate):round(.56 * rate)] = .12 * np.sin(2 * np.pi * 261.626 * t) with tempfile.TemporaryDirectory() as directory: source, target = (Path(directory) / name for name in ("hum.wav", "notes.mid")) sf.write(source, wave, rate) self.assertEqual(transcribe_f0(source, target), 1) note = pretty_midi.PrettyMIDI(str(target)).instruments[0].notes[0] self.assertEqual(note.pitch, 60) self.assertAlmostEqual(note.start, .08, delta=.05) self.assertAlmostEqual(note.end, .56, delta=.05) def test_unpitched_noise_does_not_create_midi(self): import numpy as np import soundfile as sf from f0_fallback import transcribe_f0 with tempfile.TemporaryDirectory() as directory: source, target = (Path(directory) / name for name in ("noise.wav", "notes.mid")) sf.write(source, np.random.default_rng(20260926).normal(0, .02, 72000), 24000) with self.assertRaisesRegex(ValueError, "устойчивую высоту"): transcribe_f0(source, target) self.assertFalse(target.exists()) def test_glide_stays_one_note_but_connected_syllables_retrigger(self): import numpy as np import pretty_midi import soundfile as sf from f0_fallback import transcribe_f0 rate = 24000 times = np.arange(round(2.5 * rate)) / rate active = (times >= .2) & (times < 2.2) for name in ("glide", "repeats"): pitch = np.linspace(57, 62, active.sum()) if name == "glide" else np.full(active.sum(), 57) frequency = 440 * 2 ** ((pitch - 69) / 12) phase = 2 * np.pi * np.cumsum(frequency) / rate signal = np.zeros(len(times), dtype=np.float32) signal[active] = .13 * (np.sin(phase) + .28 * np.sin(2 * phase)) if name == "repeats": rng = np.random.default_rng(818) for boundary in (.8, 1.5): center = round(boundary * rate) width = round(.035 * rate) indices = np.arange(-width, width) dip = .82 * np.exp(-.5 * (indices / (width / 2.5)) ** 2) signal[center - width:center + width] *= 1 - dip signal[center:center + round(.018 * rate)] += rng.normal( 0, .018, round(.018 * rate)).astype(np.float32) with self.subTest(name=name), tempfile.TemporaryDirectory() as directory: source, target = (Path(directory) / filename for filename in ("hum.wav", "notes.mid")) sf.write(source, signal, rate) expected = 1 if name == "glide" else 3 self.assertEqual(transcribe_f0(source, target), expected) notes = pretty_midi.PrettyMIDI(str(target)).instruments[0].notes if name == "repeats": for note, start in zip(notes, (.2, .8, 1.5)): self.assertEqual(note.pitch, 57) self.assertAlmostEqual(note.start, start, delta=.06)