2015-05-07 11 views
11

Ich bin daran interessiert, mit dem JavaScript WebAudioAPI Songbeats zu erkennen und sie dann in einer Leinwand zu rendern.Wie kann ich JS WebAudioAPI für die Erkennung von Beats verwenden?

Ich kann mit dem Canvas-Teil umgehen, aber ich bin kein großer Audio-Typ und verstehe wirklich nicht, wie man einen Beat-Detektor in JavaScript macht.

Ich habe versucht, folgende this article, aber kann nicht, für das Leben von mir, verbinden Sie die Punkte zwischen den einzelnen Funktionen, um ein funktionales Programm zu machen.

Ich weiß, ich sollte Ihnen einen Code zeigen, aber ehrlich gesagt habe ich keine, alle meine Versuche sind kläglich gescheitert und der entsprechende Code ist es in dem zuvor erwähnten Artikel.

Wie auch immer, ich würde wirklich einige Anleitung schätzen, oder noch besser eine Demo, wie man tatsächlich Beats mit der WebAudioAPI erkennt.

Danke!

+0

Der Artikel, den Sie das ganze Thema verknüpft deckt vielmehr darn Nun, was speziell hast du schwer mit? – Nit

+0

Ja, ich weiß! Deshalb ist es so frustrierend, ich kann es nicht funktionieren lassen, ich meine, wo bekomme ich die Schwelle oder die Daten für die erste Funktion, benutze ich die Filter, bevor ich die Peaks bekomme oder danach? Ich fühle mich dumm. – undefined

+0

Die Schwelle hängt sehr davon ab, mit welcher Art von Audio Sie arbeiten - in Kürze, es ist eine Nummer, die Sie wählen. Die Daten sind genau die Audiodateien, mit denen Sie arbeiten möchten. – Nit

Antwort

9

Die wichtigste Sache zu verstehen über the referenced article by Joe Sullivan ist, dass, obwohl es eine Menge Quellcode gibt, es weit von endgültigen und vollständigen Code ist. Um eine funktionierende Lösung zu erreichen, benötigen Sie sowohl Programmier- als auch Debugging-Fähigkeiten.

Diese Antwort bezieht sich größtenteils auf den Quellcode des Artikels, auf den verwiesen wird.

Unten ist eine naive Beispielimplementierung für die Verwendung der Funktionen, die in dem obigen Artikel beschrieben werden.


Der Code besteht aus Vorbereitung Code für die Antwort geschrieben:

und dann zu lesen, wie in dem Artikel beschrieben:

  • Filtern das Audio, in diesem Beispiel mit einem low-pass filter
  • Berechnung Peaks einen Schwellenwert
  • Gruppierungsintervallzählungen verwendet und zählen dann Tempo

für den Schwellenwert I einen willkürlichen Wert von .98 von dem verwendeten Bereich zwischen maximalen und minimalen Werten; Beim Gruppieren habe ich einige zusätzliche Checks und willkürliches Runden hinzugefügt, um mögliche Endlosschleifen zu vermeiden und es zu einem leicht zu debuggenden Beispiel zu machen.

Beachten Sie, dass Kommentierung ist knapp die Beispielimplementierung kurz zu halten, weil:

  • die Logik hinter Verarbeitung in dem zitierten Artikel
  • die Syntax referenziert werden kann in der API-Dokumentation der zugehörigen Methoden erklärt

audio_file.onchange = function() { 
 
    var file = this.files[0]; 
 
    var reader = new FileReader(); 
 
    var context = new(window.AudioContext || window.webkitAudioContext)(); 
 
    reader.onload = function() { 
 
    context.decodeAudioData(reader.result, function(buffer) { 
 
     prepare(buffer); 
 
    }); 
 
    }; 
 
    reader.readAsArrayBuffer(file); 
 
}; 
 

 
function prepare(buffer) { 
 
    var offlineContext = new OfflineAudioContext(1, buffer.length, buffer.sampleRate); 
 
    var source = offlineContext.createBufferSource(); 
 
    source.buffer = buffer; 
 
    var filter = offlineContext.createBiquadFilter(); 
 
    filter.type = "lowpass"; 
 
    source.connect(filter); 
 
    filter.connect(offlineContext.destination); 
 
    source.start(0); 
 
    offlineContext.startRendering(); 
 
    offlineContext.oncomplete = function(e) { 
 
    process(e); 
 
    }; 
 
} 
 

 
function process(e) { 
 
    var filteredBuffer = e.renderedBuffer; 
 
    //If you want to analyze both channels, use the other channel later 
 
    var data = filteredBuffer.getChannelData(0); 
 
    var max = arrayMax(data); 
 
    var min = arrayMin(data); 
 
    var threshold = min + (max - min) * 0.98; 
 
    var peaks = getPeaksAtThreshold(data, threshold); 
 
    var intervalCounts = countIntervalsBetweenNearbyPeaks(peaks); 
 
    var tempoCounts = groupNeighborsByTempo(intervalCounts); 
 
    tempoCounts.sort(function(a, b) { 
 
    return b.count - a.count; 
 
    }); 
 
    if (tempoCounts.length) { 
 
    output.innerHTML = tempoCounts[0].tempo; 
 
    } 
 
} 
 

 
// http://tech.beatport.com/2014/web-audio/beat-detection-using-web-audio/ 
 
function getPeaksAtThreshold(data, threshold) { 
 
    var peaksArray = []; 
 
    var length = data.length; 
 
    for (var i = 0; i < length;) { 
 
    if (data[i] > threshold) { 
 
     peaksArray.push(i); 
 
     // Skip forward ~ 1/4s to get past this peak. 
 
     i += 10000; 
 
    } 
 
    i++; 
 
    } 
 
    return peaksArray; 
 
} 
 

 
function countIntervalsBetweenNearbyPeaks(peaks) { 
 
    var intervalCounts = []; 
 
    peaks.forEach(function(peak, index) { 
 
    for (var i = 0; i < 10; i++) { 
 
     var interval = peaks[index + i] - peak; 
 
     var foundInterval = intervalCounts.some(function(intervalCount) { 
 
     if (intervalCount.interval === interval) return intervalCount.count++; 
 
     }); 
 
     //Additional checks to avoid infinite loops in later processing 
 
     if (!isNaN(interval) && interval !== 0 && !foundInterval) { 
 
     intervalCounts.push({ 
 
      interval: interval, 
 
      count: 1 
 
     }); 
 
     } 
 
    } 
 
    }); 
 
    return intervalCounts; 
 
} 
 

 
function groupNeighborsByTempo(intervalCounts) { 
 
    var tempoCounts = []; 
 
    intervalCounts.forEach(function(intervalCount) { 
 
    //Convert an interval to tempo 
 
    var theoreticalTempo = 60/(intervalCount.interval/44100); 
 
    theoreticalTempo = Math.round(theoreticalTempo); 
 
    if (theoreticalTempo === 0) { 
 
     return; 
 
    } 
 
    // Adjust the tempo to fit within the 90-180 BPM range 
 
    while (theoreticalTempo < 90) theoreticalTempo *= 2; 
 
    while (theoreticalTempo > 180) theoreticalTempo /= 2; 
 

 
    var foundTempo = tempoCounts.some(function(tempoCount) { 
 
     if (tempoCount.tempo === theoreticalTempo) return tempoCount.count += intervalCount.count; 
 
    }); 
 
    if (!foundTempo) { 
 
     tempoCounts.push({ 
 
     tempo: theoreticalTempo, 
 
     count: intervalCount.count 
 
     }); 
 
    } 
 
    }); 
 
    return tempoCounts; 
 
} 
 

 
// http://stackoverflow.com/questions/1669190/javascript-min-max-array-values 
 
function arrayMin(arr) { 
 
    var len = arr.length, 
 
    min = Infinity; 
 
    while (len--) { 
 
    if (arr[len] < min) { 
 
     min = arr[len]; 
 
    } 
 
    } 
 
    return min; 
 
} 
 

 
function arrayMax(arr) { 
 
    var len = arr.length, 
 
    max = -Infinity; 
 
    while (len--) { 
 
    if (arr[len] > max) { 
 
     max = arr[len]; 
 
    } 
 
    } 
 
    return max; 
 
}
<input id="audio_file" type="file" accept="audio/*"></input> 
 
<audio id="audio_player"></audio> 
 
<p> 
 
    Most likely tempo: <span id="output"></span> 
 
</p>

+0

Danke! Dies ist eine wirklich vollständige Antwort, wenn man bedenkt, wie wenig ich zu der Diskussion beigetragen habe. Obwohl ich nicht sicher bin, wie effektiv diese Methode wirklich ist, da das Lied, X-Ambassadors - Renegades *, mir zum Beispiel 128 BPM mit deinem Snippet gibt und wenn sie durch diese Webseite laufen (https://songbpm.com/x-ambassadors/renegades), 90BPM. Wie auch immer, das ist natürlich nicht deine Schuld, sondern der Algorithmus, also vielen Dank :) – undefined

+0

Ein Problem ist, dass der verknüpfte Code und das obige Snippet die Tiefpassfilterfrequenz nicht einstellen. Wer weiß, wie es standardmäßig aussieht? Wenn ich mir die Quelle der Seite ansehe, wird sie für die Schaltfläche 'Mit Tiefpassfilter' auf 200Hz gesetzt. 'filter.frequency.value = 200' – jaket

+0

@jaket Dies ist ein sehr einfaches Beispiel für die Verwendung der Funktionalität, es gibt natürlich viel Raum für Verbesserungen. Aber danke für den Kommentar. – Nit

6

Ich habe hier ein Tutorial geschrieben, das zeigt, wie man das mit der JavaScript Web Audio API macht.

https://askmacgyver.com/blog/tutorial/how-to-implement-tempo-detection-in-your-application

Übersicht über die Schritte

  1. Transform Audio-Datei in ein Array Buffer
  2. Run Array Buffer Durch Tiefpassfilter
  3. Schneiden einer 10-sekündigen Clip aus dem Array Buffer
  4. Nach unten die Daten abtasten
  5. Normalisieren der Daten
  6. Count Volume Groupings
  7. Infer Tempo von Groupings Graf

Dieser Code unten funktioniert das Heben schwerer Lasten.

laden Audio-Datei in Array-Puffer und führen durch Tiefpassfilter

function createBuffers(url) { 

// Fetch Audio Track via AJAX with URL 
request = new XMLHttpRequest(); 

request.open('GET', url, true); 
request.responseType = 'arraybuffer'; 

request.onload = function(ajaxResponseBuffer) { 

    // Create and Save Original Buffer Audio Context in 'originalBuffer' 
    var audioCtx = new AudioContext(); 
    var songLength = ajaxResponseBuffer.total; 

    // Arguments: Channels, Length, Sample Rate 
    var offlineCtx = new OfflineAudioContext(1, songLength, 44100); 
    source = offlineCtx.createBufferSource(); 
    var audioData = request.response; 
    audioCtx.decodeAudioData(audioData, function(buffer) { 

     window.originalBuffer = buffer.getChannelData(0); 
     var source = offlineCtx.createBufferSource(); 
     source.buffer = buffer; 

     // Create a Low Pass Filter to Isolate Low End Beat 
     var filter = offlineCtx.createBiquadFilter(); 
     filter.type = "lowpass"; 
     filter.frequency.value = 140; 
     source.connect(filter); 
     filter.connect(offlineCtx.destination); 

      // Render this low pass filter data to new Audio Context and Save in 'lowPassBuffer' 
      offlineCtx.startRendering().then(function(lowPassAudioBuffer) { 

      var audioCtx = new(window.AudioContext || window.webkitAudioContext)(); 
      var song = audioCtx.createBufferSource(); 
      song.buffer = lowPassAudioBuffer; 
      song.connect(audioCtx.destination); 

      // Save lowPassBuffer in Global Array 
      window.lowPassBuffer = song.buffer.getChannelData(0); 
      console.log("Low Pass Buffer Rendered!"); 
      }); 

     }, 
     function(e) {}); 
} 
request.send(); 
} 


createBuffers('https://askmacgyver.com/test/Maroon5-Moves-Like-Jagger-128bpm.mp3'); 

Sie jetzt ein Array-Buffer des tiefpaßgefilterten Lied (und Original)

Es besteht aus eine Anzahl von Einträgen, sampleRate (44100 multipliziert mit der Anzahl der Sekunden des Songs).

window.lowPassBuffer // Low Pass Array Buffer 
window.originalBuffer // Original Non Filtered Array Buffer 

Trim eine 10 Sekunden-Clip aus der Song-

function getClip(length, startTime, data) { 

    var clip_length = length * 44100; 
    var section = startTime * 44100; 
    var newArr = []; 

    for (var i = 0; i < clip_length; i++) { 
    newArr.push(data[section + i]); 
    } 

    return newArr; 
} 

// Overwrite our array buffer to a 10 second clip starting from 00:10s 
window.lowPassFilter = getClip(10, 10, lowPassFilter); 

Verkleinerungs Ihr Clip

function getSampleClip(data, samples) { 

    var newArray = []; 
    var modulus_coefficient = Math.round(data.length/samples); 

    for (var i = 0; i < data.length; i++) { 
    if (i % modulus_coefficient == 0) { 
     newArray.push(data[i]); 
    } 
    } 
    return newArray; 
} 

// Overwrite our array to down-sampled array. 
lowPassBuffer = getSampleClip(lowPassFilter, 300); 

Normalisieren Sie Ihre Daten

function normalizeArray(data) { 

var newArray = []; 

for (var i = 0; i < data.length; i++) { 
    newArray.push(Math.abs(Math.round((data[i + 1] - data[i]) * 1000))); 
} 

return newArray; 
} 

// Overwrite our array to the normalized array 
lowPassBuffer = normalizeArray(lowPassBuffer); 

Graf die Flat Line Groupings

function countFlatLineGroupings(data) { 

var groupings = 0; 
var newArray = normalizeArray(data); 

function getMax(a) { 
    var m = -Infinity, 
     i = 0, 
     n = a.length; 

    for (; i != n; ++i) { 
     if (a[i] > m) { 
      m = a[i]; 
     } 
    } 
    return m; 
} 

function getMin(a) { 
    var m = Infinity, 
     i = 0, 
     n = a.length; 

    for (; i != n; ++i) { 
     if (a[i] < m) { 
      m = a[i]; 
     } 
    } 
    return m; 
} 

var max = getMax(newArray); 
var min = getMin(newArray); 
var count = 0; 
var threshold = Math.round((max - min) * 0.2); 

for (var i = 0; i < newArray.length; i++) { 

    if (newArray[i] > threshold && newArray[i + 1] < threshold && newArray[i + 2] < threshold && newArray[i + 3] < threshold && newArray[i + 6] < threshold) { 
     count++; 
    } 
} 

return count; 
} 

// Count the Groupings 
countFlatLineGroupings(lowPassBuffer); 

Skala 10 Zweite Gruppierung bis 60 Sekunden Count Beats Derive Per Minute

var final_tempo = countFlatLineGroupings(lowPassBuffer); 

// final_tempo will be 21 
final_tempo = final_tempo * 6; 

console.log("Tempo: " + final_tempo); 
// final_tempo will be 126