File size: 3,711 Bytes
21c2288
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fcf203b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
"""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)