2012-10-31 7 views
7

Edit: oczyścić kod i odtwarzacz (na Github) trochę tak, że łatwiej jest ustawić częstotliwośćWeb Audio: Synteza Karplus Strong String

jestem trying to synthesize ciągów za pomocą algorytmu z Karplus Strong string synthesis, ale mogę” t uzyskać ciąg, aby odpowiednio dostroić. Czy ktokolwiek ma jakiś pomysł?

Jak podano powyżej, kod znajduje się na Github: https://github.com/achalddave/Audio-API-Frequency-Generator (odpowiednie bity są w strings.js).

Wiki ma następujący schemat:

Karplus Strong String Synthesis diagram

Więc zasadniczo generować hałas, który następnie pobiera dane wyjściowe i wysłany do filtra opóźnienia jednocześnie. Filtr opóźniający jest podłączony do filtra dolnoprzepustowego, który następnie jest mieszany z wyjściem. Według Wikipedii opóźnienie powinno wynosić N próbek, gdzie N jest częstotliwością próbkowania podzieloną przez częstotliwość podstawową (N = f_s/f_0).

Fragmenty z mojego kodu:

generowania hałasu (bufferSize jest 2048, ale to nie powinno mieć znaczenia, zbyt dużo)

var buffer = context.createBuffer(1, bufferSize, context.sampleRate); 
var bufferSource = context.createBufferSource(); 
bufferSource.buffer = buffer; 

var bufferData = buffer.getChannelData(0); 
for (var i = 0; i < delaySamples+1; i++) { 
    bufferData[i] = 2*(Math.random()-0.5); // random noise from -1 to 1 
} 

Utwórz węzeł opóźnienia

var delayNode = context.createDelayNode(); 

Czekamy opóźnić o f_s/f_0 próbek. Jednak węzeł opóźnienia przyjmuje opóźnienie w sekundach, więc musimy podzielić to przez próbki na sekundę, a otrzymamy (f_s/f_0)/f_s, który jest tylko 1/f_0.

var delaySeconds = 1/(frequency); 
delayNode.delayTime.value = delaySeconds; 

Utwórz filtr dolnoprzepustowy (odcięcia częstotliwości, o ile wiem, nie powinno mieć wpływu na częstotliwość i jest bardziej kwestia tego, czy ciąg „brzmi” naturalne):

var lowpassFilter = context.createBiquadFilter(); 
lowpassFilter.type = lowpassFilter.LOWPASS; // explicitly set type 
lowpassFilter.frequency.value = 20000; // make things sound better 

Połączyć szumu do produkcji i węzła opóźnienia (destination = context.destination i zdefiniowano wcześniej)

bufferSource.connect(destination); 
bufferSource.connect(delayNode); 

połączyć opóźnienie do filtra dolnoprzepustowego:

delayNode.connect(lowpassFilter); 

Podłączyć dolnoprzepustowy na wyjście i powrót do opóźnienia *:

lowpassFilter.connect(destination); 
lowpassFilter.connect(delayNode); 

Czy ktoś ma jakieś pomysły? Nie mogę się dowiedzieć, czy problemem jest mój kod, moja interpretacja algorytmu, moje zrozumienie API, czy (choć jest to najmniej prawdopodobne) problem z samym API.


* Należy pamiętać, że na Github, tam faktycznie węzeł Zysk między dolnoprzepustowy i wyjście, ale to naprawdę nie robi dużą różnicę w wydajności.

+0

Po prostu bawiam się tym i naprawdę nie wiem, co robię. Ale spróbuj ustawić częstotliwość na 241. Na moim Macu, który tworzy dziwny hałas. Może to ci coś mówi? Wydajesz się dużo bardziej biegły w zakresie matematyki i teorii. :) –

+0

Hm, to interesujące. Szczerze mówiąc, poza jednym kursem EE nie jestem zbyt obeznany z teorią, tak wiele z tego składa się razem i pyta. Dzięki za pomoc, to może dać trochę wglądu, jeśli zajrzę więcej. –

+0

To prawdopodobnie nie jest problem, ponieważ myślę, że Lowpass jest domyślny, ale powinieneś prawdopodobnie ustawić swój typ filtra jawnie w kodzie ... coś jak 'lowpassFilter.type = lowpassFilter.LOWPASS'. –

Odpowiedz

6

Oto, co uważam za problem. Nie sądzę, aby implementacja DelayNode została zaprojektowana do obsługi tak ścisłych pętli sprzężenia zwrotnego.Na przykład w przypadku częstotliwości 441 Hz to tylko 100 próbek opóźnienia, a implementacja DelayNode prawdopodobnie przetwarza dane wejściowe w blokach 128 lub więcej. (Atrybut delayTime to "stopa k", co oznacza, że ​​zmiany w nim są przetwarzane tylko w blokach zawierających 128 próbek, co nie jest moim dowodem, ale wskazuje na to.) Tak więc opinie przychodzą za późno lub tylko częściowo lub coś.

Edycja/aktualizacja: Jak określić w komentarzu poniżej rzeczywista problemem jest to, że w cyklu DelayNode dodaje 128 próbek ramek pomiędzy wyjściem a wejściem, dzięki czemu obserwuje się opóźnienie 128/sampleRate sekundy dłuższy niż określony.

Moja rada (i co już zacząłem robić) to wdrożenie całego Karplus-Strong, w tym własnej linii opóźniającej w JavaScriptNode (obecnie znanej jako ScriptProcessorNode). Nie jest to trudne i opublikuję mój kod, gdy pozbędę się irytującego błędu, który nie może istnieć, ale jakoś tak.

Nawiasem mówiąc, ton (i ja) z delayTime z 1/440 (który ma być A) wydaje się być G, dwa półtony poniżej, gdzie powinien być. Podwojenie częstotliwości podnosi ją do B, cztery półtony wyżej. (Mógłbym zejść o oktawę lub dwa - trudne do opowiedzenia.) Prawdopodobnie można było dowiedzieć się, co się dzieje (matematycznie) z kilku kolejnych punktów danych, ale nie będę się tym przejmował.

EDYCJA: Oto mój kod, certyfikowany bez błędów.

var context = new webkitAudioContext(); 

var frequency = 440; 
var impulse = 0.001 * context.sampleRate; 

var node = context.createJavaScriptNode(4096, 0, 1); 
var N = Math.round(context.sampleRate/frequency); 
var y = new Float32Array(N); 
var n = 0; 
node.onaudioprocess = function (e) { 
    var output = e.outputBuffer.getChannelData(0); 
    for (var i = 0; i < e.outputBuffer.length; ++i) { 
    var xn = (--impulse >= 0) ? Math.random()-0.5 : 0; 
    output[i] = y[n] = xn + (y[n] + y[(n + 1) % N])/2; 
    if (++n >= N) n = 0; 
    } 
} 

node.connect(context.destination); 
+0

To działa? Spróbuję tego jutro. Jeśli tak, nie mogę przecenić tego, co jest niesamowite (i jak bardzo doceniam pomoc). Rozważałem wdrożenie go jako JavascriptNode, ale w ostatnim eksperymencie napotkałem problemy z wydajnością. –

+0

Co to jest "błąd, który nie może istnieć"?Po drugie, czy raport o błędzie implementacji DelayNode jest wart? Jeśli tak, to robiłbym błąd, aby to zgłosić (sam bym, ale wydaje się, że rozumiesz to lepiej) i opublikował link tutaj. –

+0

Działa - naprawdę! W każdym razie dla mnie. – Grumdrig

Powiązane problemy