Orby w onboardingu Meduzy mają nurkować dokładnie w momentach, w których pianista uderza klawisze Nokturnu Chopina — a my nie mieliśmy partytury w formie danych, tylko samo nagranie audio.
Nokturn — tak nazywa się pierwsze uruchomienie Meduzy — otwiera się sceną nad nocnym Bałtykiem: Nokturn Es-dur op. 9 nr 2 (nagranie public domain z Wikimedia Commons) i orby, które schodzą pod fale razem z dźwiękiem. Żeby ta choreografia trzymała się muzyki, potrzebowaliśmy listy zdarzeń: kiedy pada nuta i jaka to nuta. Najprostsza droga to plik MIDI z sieci. Odrzuciliśmy ją świadomie: cudze MIDI ma cudze tempo i niepewny status prawny, a rubato w wykonaniu, które wybraliśmy, i tak rozjechałoby się z nim po kilku taktach. Zdecydowaliśmy: czytamy partyturę wprost z fali dźwiękowej. Czystym numpy — zero MIDI, zero bibliotek DSP.
Zatonęło
Pierwsza wersja detektora uderzeń trafiła 1 nutę z 15 testowych i podniosła 58 fałszywych alarmów. Powody były dwa. Raz: systematyczny offset -56 ms — raportowaliśmy czas z końca okna analizy, a atak nuty wpadał w jego ogon, więc każde wykrycie było spóźnione o stałą wartość. Dwa: próg trzepotał na szumie — liczony ze zwykłej średniej, strzelał na pogłosie i tle nagrania. Tę wersję utopiliśmy w całości i naprawę zaczęliśmy od zegara i progu, nie od „lepszego algorytmu".
Jak to działa
Nagranie tniemy na ramki STFT i liczymy spectral flux: sumę dodatnich przyrostów energii między kolejnymi widmami. Uderzenie klawisza pianina to wystrzał energii w wielu pasmach naraz, więc flux skacze; pogłos i oddech sali ledwo go ruszają.
Trzy decyzje wyciągnęły wynik z 1/15 na 15/15:
- Kalibracja zegara. Od każdego wykrycia odejmujemy stały offset 0.6 * N_FFT / SR. Atak nie siedzi na końcu okna analizy, tylko mniej więcej w 60 procentach jego długości — tamte -56 ms systematycznego błędu znikły jednym odejmowaniem.
- Próg odporny na szum. Zamiast średniej: mediana fluxu plus 3.0 * MAD (odchylenie medianowe). MAD nie daje się wybić pojedynczym pikom, więc próg stoi spokojnie nawet tam, gdzie pogłos faluje.
- Podłoga siły. Wykrycie musi mieć siłę co najmniej MIN_STRENGTH = 0.12 — to ucina resztkę trzepotu na najcichszych fragmentach.
Serce detektora mieści się w czterech linijkach:
flux = np.maximum(mag[1:] - mag[:-1], 0.0).sum(axis=1)
prog = np.median(flux) + 3.0 * mad(flux)
hity = np.where((flux > prog) & (flux > MIN_STRENGTH))[0]
t = hity * HOP / SR - 0.6 * N_FFT / SR # kalibracja: -56 ms znika
Test na syntetycznym pianinie: 15 z 15 uderzeń wykrytych, zero fałszywych alarmów.
Drugi etap to wysokości dźwięków. W oknie 40–160 ms po każdym ataku liczymy HPS (harmonic product spectrum): widmo mnożymy przez jego kopie ściśnięte 2x, 3x i 4x, a składowe harmoniczne nakładają się wtedy na częstotliwość podstawową — i to ona wygrywa, nawet gdy druga harmoniczna gra głośniej niż pierwsza. Okno startuje celowo dopiero 40 ms po ataku, bo sam moment uderzenia to głównie szum młoteczka; ton stabilizuje się chwilę później. Test na dźwiękach o znanej wysokości: 6 z 6.
Potem puściliśmy całość na prawdziwym nagraniu. Wynik: 157 nut. Pierwsza z nich to B♭4 — słynny przedtakt, od którego Chopin zaczyna cały Nokturn, i najlepszy znak, że pipeline czyta muzykę, a nie artefakty widma. Takty wyznaczyliśmy z pulsu ósemek metrum 12/8: jeden takt tego wykonania trwa około 5,57 sekundy, więc orby wiedzą nie tylko kiedy nurkować, ale też w którym miejscu frazy właśnie są.
Efekt końcowy to lista 157 zdarzeń — czas, wysokość, takt — którą orby dostają jako gotową choreografię. Analiza była jednorazowa i lokalna, jak wszystko w Meduzie: nagranie nie wyjechało poza nasz komputer, a do środka projektu nie wjechała żadna nowa zależność.
Co z tego masz
Pierwsza minuta z Meduzą to orby nurkujące co do milisekundy razem z Chopinem — choreografia zsynchronizowana z żywym nagraniem, nie zapętlona animacja.
Ta funkcja gra w Largo — Nokturn zobaczysz w każdym planie, także darmowym, od pierwszego uruchomienia.