Pracuję nad programem muzycznym, który wymaga synchronizacji wielu elementów JavaScript z innymi. Używałem setInterval
, co początkowo działa bardzo dobrze. Jednak z biegiem czasu elementy stopniowo tracą synchronizację, co jest złe w programie muzycznym.
Czytałem w Internecie, że setTimeout
jest dokładniejszy i setTimeout
jakoś możesz mieć pętle. Jednak nie znalazłem ogólnej wersji, która ilustruje, jak to jest możliwe.
Zasadniczo mam kilka funkcji, takich jak:
//drums
setInterval(function {
//code for the drums playing goes here
}, 8000);
//chords
setInterval(function {
//code for the chords playing goes here
}, 1000);
//bass
setInterval(function {
//code for the bass playing goes here
}, 500);
Początkowo działa bardzo dobrze, ale w ciągu około minuty dźwięki stają się zauważalnie niezsynchronizowane, jak to czytałem setInterval
. Czytałem, że setTimeout
może być bardziej konsekwentnie dokładne.
Czy ktoś mógłby mi po prostu pokazać podstawowy przykład użycia setTimeout
do zapętlenia czegoś w nieskończoność? Ewentualnie, jeśli istnieje sposób na osiągnięcie bardziej synchronicznych wyników z setInterval
inną funkcją lub nawet inną, proszę o informację.
źródło
setTimeout
którym możesz obliczyć, jak długo naprawdę było opóźnienie, dostosuj czas do następnego limitu czasu.requestAnimationFrame
? Wystarczy, że odniesiesz się do czasu, w którym dźwięk jest za każdym razem, gdyrequestAnimationFrame
uruchomione jest wywołanie zwrotne.Odpowiedzi:
Możesz utworzyć
setTimeout
pętlę za pomocą rekurencji:function timeout() { setTimeout(function () { // Do Something Here // Then recall the parent function to // create a recursive loop. timeout(); }, 1000); }
Problem z
setInterval()
isetTimeout()
polega na tym, że nie ma gwarancji, że kod zostanie uruchomiony w określonym czasie. UżywającsetTimeout()
i wywołując go rekurencyjnie, masz pewność, że wszystkie poprzednie operacje w ramach limitu czasu zostaną zakończone przed rozpoczęciem następnej iteracji kodu.źródło
setInterval
?setInterval()
ponownym wykonaniem, Twoje zmienne i / lub dane mogą bardzo szybko stracić synchronizację. Jeśli na przykład AJAX wyszukuje dane, a odpowiedź serwera trwa dłużej niż 1 sekundę,setInterval()
moje poprzednie dane nie zostałyby zakończone przed kontynuowaniem mojej następnej operacji. Przy takim podejściu nie ma gwarancji, że Twoja funkcja będzie się uruchamiać co sekundę. Jednak gwarantuje się, że poprzednie dane zostaną przetworzone przed rozpoczęciem kolejnego interwału.Tylko do uzupełnienia. Jeśli potrzebujesz przekazać zmienną i ją iterować, możesz zrobić tak:
function start(counter){ if(counter < 10){ setTimeout(function(){ counter++; console.log(counter); start(counter); }, 1000); } } start(0);
Wynik:
1 2 3 ... 9 10
Jedna linia na sekundę.
źródło
Biorąc pod uwagę, że żaden z tych czasów nie będzie bardzo dokładny, jednym ze sposobów, aby
setTimeout
być nieco dokładniejszym, jest obliczenie czasu opóźnienia od ostatniej iteracji, a następnie odpowiednie dostosowanie następnej iteracji. Na przykład:var myDelay = 1000; var thisDelay = 1000; var start = Date.now(); function startTimer() { setTimeout(function() { // your code here... // calculate the actual number of ms since last time var actual = Date.now() - start; // subtract any extra ms from the delay for the next cycle thisDelay = myDelay - (actual - myDelay); start = Date.now(); // start the timer again startTimer(); }, thisDelay); }
Więc za pierwszym razem, gdy będzie czekał (przynajmniej) 1000 ms, kiedy twój kod zostanie wykonany, może być trochę spóźniony, powiedzmy 1046 ms, więc odejmujemy 46 ms od naszego opóźnienia na następny cykl, a następne opóźnienie będzie tylko 954 ms. Nie zatrzyma to opóźnionego uruchamiania timera (należy się tego spodziewać), ale pomoże ci zapobiec narastaniu opóźnień. (Uwaga: możesz chcieć sprawdzić,
thisDelay < 0
co oznacza, że opóźnienie było ponad dwukrotnie większe od docelowego opóźnienia i przegapiłeś cykl - zależy od Ciebie, jak chcesz sobie z tym poradzić).Oczywiście prawdopodobnie nie pomoże ci to zsynchronizować kilku timerów, w takim przypadku możesz chcieć dowiedzieć się, jak kontrolować je wszystkie za pomocą tego samego timera.
Patrząc na kod, wszystkie opóźnienia są wielokrotnością 500, więc możesz zrobić coś takiego:
var myDelay = 500; var thisDelay = 500; var start = Date.now(); var beatCount = 0; function startTimer() { setTimeout(function() { beatCount++; // your code here... //code for the bass playing goes here if (count%2 === 0) { //code for the chords playing goes here (every 1000 ms) } if (count%16) { //code for the drums playing goes here (every 8000 ms) } // calculate the actual number of ms since last time var actual = Date.now() - start; // subtract any extra ms from the delay for the next cycle thisDelay = myDelay - (actual - myDelay); start = Date.now(); // start the timer again startTimer(); }, thisDelay); }
źródło
Najlepszym sposobem radzenia sobie z synchronizacją dźwięku jest interfejs Web Audio Api, który ma oddzielny zegar, który jest dokładny niezależnie od tego, co dzieje się w głównym wątku. Tutaj jest świetne wyjaśnienie, przykłady itp. Od Chrisa Wilsona:
http://www.html5rocks.com/en/tutorials/audio/scheduling/
Rozejrzyj się po tej witrynie, aby znaleźć więcej interfejsu API Web Audio, został on opracowany, aby robić dokładnie to, czego szukasz.
źródło
setTimeout
zakresu od niewystarczającego do strasznie skomplikowanego. Używanie funkcji natywnej wydaje się znacznie lepszym pomysłem. Jeśli interfejs API nie służy Twojemu celowi, polecam znalezienie niezawodnej biblioteki planowania innej firmy.Posługiwać się
setInterval()
setInterval(function(){ alert("Hello"); }, 3000);
Powyższe będzie wykonywane
alert("Hello");
co 3 sekundy.źródło
Zgodnie z twoim wymaganiem
mamy następujący przykład, który może ci pomóc
var itr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; var interval = 1000; //one second itr.forEach((itr, index) => { setTimeout(() => { console.log(itr) }, index * interval) })
źródło
W życiu zawodowym używam tego sposobu: „Zapomnij o zwykłych pętlach” w tym przypadku i używam tej kombinacji „setInterval” zawiera „setTimeOut”:
function iAsk(lvl){ var i=0; var intr =setInterval(function(){ // start the loop i++; // increment it if(i>lvl){ // check if the end round reached. clearInterval(intr); return; } setTimeout(function(){ $(".imag").prop("src",pPng); // do first bla bla bla after 50 millisecond },50); setTimeout(function(){ // do another bla bla bla after 100 millisecond. seq[i-1]=(Math.ceil(Math.random()*4)).toString(); $("#hh").after('<br>'+i + ' : rand= '+(Math.ceil(Math.random()*4)).toString()+' > '+seq[i-1]); $("#d"+seq[i-1]).prop("src",pGif); var d =document.getElementById('aud'); d.play(); },100); setTimeout(function(){ // keep adding bla bla bla till you done :) $("#d"+seq[i-1]).prop("src",pPng); },900); },1000); // loop waiting time must be >= 900 (biggest timeOut for inside actions) }
PS: Zrozum, że prawdziwe zachowanie (setTimeOut): wszystkie zaczną się w tym samym czasie „trzy bla bla bla zaczną odliczać w tym samym momencie”, więc ustaw inny czas na wykonanie.
PS 2: przykład pętli czasowej, ale w przypadku pętli reakcji można używać zdarzeń, obiecywać asynchroniczne oczekiwanie.
źródło
// it will print 5 times 5. for(var i=0;i<5;i++){ setTimeout(()=> console.log(i), 2000) } // 5 5 5 5 5 // improved using let for(let i=0;i<5;i++){ setTimeout(()=> console.log('improved using let: '+i), 2000) } // improved using closure for(var i=0;i<5;i++){ ((x)=>{ setTimeout(()=> console.log('improved using closure: '+x), 2000) })(i); }
źródło
Jak ktoś zauważył, interfejs API Web Audio ma lepszy zegar.
Ale ogólnie, jeśli te zdarzenia mają miejsce konsekwentnie, co powiesz na ustawienie ich wszystkich na tym samym zegarze? Myślę o tym, jak działa sekwencer krokowy .
Praktycznie, czy mogłoby to wyglądać mniej więcej tak?
var timer = 0; var limit = 8000; // 8000 will be the point at which the loop repeats var drumInterval = 8000; var chordInterval = 1000; var bassInterval = 500; setInterval(function { timer += 500; if (timer == drumInterval) { // Do drum stuff } if (timer == chordInterval) { // Do chord stuff } if (timer == bassInterval) { // Do bass stuff } // Reset timer once it reaches limit if (timer == limit) { timer = 0; } }, 500); // Set the timer to the smallest common denominator
źródło
function appendTaskOnStack(task, ms, loop) { window.nextTaskAfter = (window.nextTaskAfter || 0) + ms; if (!loop) { setTimeout(function() { appendTaskOnStack(task, ms, true); }, window.nextTaskAfter); } else { if (task) task.apply(Array(arguments).slice(3,)); window.nextTaskAfter = 0; } } for (var n=0; n < 10; n++) { appendTaskOnStack(function(){ console.log(n) }, 100); }
źródło
Myślę, że lepiej jest przekroczyć limit czasu na końcu funkcji.
function main(){ var something; make=function(walkNr){ if(walkNr===0){ // var something for this step // do something } else if(walkNr===1){ // var something for that step // do something different } // *** // finally else if(walkNr===10){ return something; } // show progress if you like setTimeout(funkion(){make(walkNr)},15,walkNr++); } return make(0); }
Te trzy funkcje są niezbędne, ponieważ zmienne w drugiej funkcji będą za każdym razem nadpisywane wartością domyślną. Gdy wskaźnik programu osiągnie wartość setTimeout, jeden krok jest już obliczany. Wtedy tylko ekran potrzebuje trochę czasu.
źródło
Użyj let zamiast var w kodzie:
for(let i=1;i<=5;i++){setTimeout(()=>{console.log(i)},1000);}
źródło