FFT พร้อม AudioContext ออฟไลน์

ฉันกำลังพยายามวิเคราะห์ความถี่ของไฟล์เสียง แต่ฉันต้องการให้มันเกิดขึ้นโดยไม่ต้องเล่น ฉันพบว่ามี offlineAudioContextซึ่งอาจเป็นสิ่งที่ฉันต้องการที่นี่

กรอกโค้ดใน jsfiddle นี้

Web Audio API เป็นพื้นที่ที่ยังไม่ได้สำรวจสำหรับฉัน และฉันไม่แน่ใจนักว่ากำลังทำอะไรอยู่ บทช่วยสอนจำนวนมากเน้นไปที่เสียงแบบเรียลไทม์ และนั่นคือสิ่งที่ฉันต้องการป้องกันจริงๆ

ภายใน context.oncomplete ฉันจัดการเพื่อให้ได้ข้อมูลบัฟเฟอร์เสียงที่แสดงผลอย่างแม่นยำ เมื่อรับข้อมูลจาก fft ฉันดูเหมือนจะได้รับชุดข้อมูลขนาดเล็กมาก ซึ่งฉันเดาว่ามันเป็นเพียงข้อมูลจากตัวอย่างสุดท้าย ฉันอยากมีข้อมูลนี้สำหรับทุกๆ x ms ของไฟล์เสียงที่ฉันกำลังโหลด ฉันต้องการรับแนวคิดเกี่ยวกับวิธีการรับข้อมูลรูปแบบนี้

โดยพื้นฐานแล้วสิ่งที่ฉันคาดหวังคือ:

[
  // array with frequency data for every (nth) frequency band for 1st sample,
  // array with frequency data for every (nth) frequency band for 2nd sample,
  // array with frequency data for every (nth) frequency band for 3rd sample,
  …
]

person Marco    schedule 10.09.2013    source แหล่งที่มา
comment
โปรดตรวจสอบคำตอบของฉันและทำเครื่องหมายว่ายอมรับแล้ว: stackoverflow.com/a/57744228/236062   -  person Zibri    schedule 01.09.2019
comment
@Zibri ดูเหมือนว่ามันอาจเป็นวิธีแก้ปัญหาที่ถูกต้อง แต่ก็ละทิ้งโครงการนี้ไม่นานหลังจากโพสต์คำถาม ดังนั้นฉันจึงไม่มีหนทางหรือพลังงานในการตรวจสอบจริงๆ ขออภัย   -  person Marco    schedule 02.09.2019


คำตอบ (2)


เมื่อคุณตั้งค่า fftSize บน AnalyserNode ของคุณ คุณจะได้รับ (fftSize / 2) จำนวนถังขยะ นั่นเป็นสาเหตุว่าทำไมคุณจึงเห็นข้อมูลน้อยกว่าที่คาดไว้มาก

โดยพื้นฐานแล้วสิ่งที่เกิดขึ้นคือ getByteFrequencyData ดูเฉพาะตัวอย่าง 128 ตัวอย่างแรกของบัฟเฟอร์ที่แสดงผลของคุณ และส่วนที่เหลือก็จะถูกเพิกเฉย

คุณอาจต้องการลองใช้ ScriptProcessorNode โดยที่ bufferSize เท่ากับ fftSize ของคุณแทน จากนั้น ในตัวจัดการเหตุการณ์ onaudioprocess ของ ScriptProcessorNode คุณสามารถคว้าบัฟเฟอร์และรับ FFT ของมันได้ บางสิ่งเช่นนี้:

var processor = context.createScriptProcessor(fftSize, 1, 1);
source.connect(processor);

processor.onaudioprocess = function( e ) {
  var input = e.inputBuffer.getChannelData(0),
      data = new Uint8Array(fftSamples);
  fft.getByteFrequencyData(data);
  // do whatever you want with `data`
}
person Kevin Ennis    schedule 10.09.2013
comment
ฉันไม่สามารถทำให้ onaudioprocess เริ่มทำงานได้จริง แต่จากสิ่งที่ฉันบอกได้เป็นเพราะ audioContext ไม่ได้เริ่มต้นอย่างถูกต้อง เมื่อฉันใช้ new webkitAudioContext() เหตุการณ์เกิดขึ้นตามที่คาดไว้ แต่ไม่มีโชคกับ webkitOfflineAudioContext ดูเหมือนว่าจะต้องมีพารามิเตอร์บางตัว แต่ถึงแม้จะมี 1 แชนเนล ซึ่งมีความยาวสูงมากและมีอัตราการสุ่มตัวอย่าง 44100 ก็ดูเหมือนจะไม่ทำอะไรเลย มีความคิดอะไรบ้าง? - person Marco; 10.09.2013
comment
คุณสามารถโพสต์ jsFiddle ของสิ่งที่คุณมีจนถึงตอนนี้ได้หรือไม่? - person Kevin Ennis; 10.09.2013
comment
โอ้เพื่อน คุณ มาก ใกล้แล้ว! เพียงเพิ่ม context.startRendering() ต่อท้ายฟังก์ชัน processBuffer แล้วตัวจัดการ onaudioprocess ของคุณจะเริ่มทำงาน - person Kevin Ennis; 11.09.2013
comment
อ๋อ.. มันดูเหมือนมันมากกว่านะ ยังไม่สามารถทำงานได้ตามที่คาดไว้ แต่ฉันจะดำดิ่งลงไปอีก พิจารณาคำตอบนี้ :) - person Marco; 11.09.2013
comment
คุณรู้หรือไม่ว่าต้องตั้งค่าพารามิเตอร์ความยาวเป็นเท่าใด ฉันไม่แน่ใจว่าพวกเขาคาดหวังค่าอะไร และจะรับมันได้อย่างไรเมื่อฉันโหลดเป็น mp3 หรืออะไรสักอย่าง มีความคิดอะไรบ้าง? - person Marco; 22.09.2013
comment
โดยพื้นฐานแล้วมันจะเป็นเพียง processBuffer.length -- หรือความยาวของสิ่งใดก็ตาม AudioBuffer ที่คุณต้องการแสดงผล เคล็ดลับที่นี่คือคุณอาจต้องมี AudioContext และ OfflineAudioContext ใช้ AudioContext สำหรับ decodeAudioData เพื่อให้คุณได้รับความยาวบัฟเฟอร์ จากนั้น จากนั้น คุณสามารถสร้างบริบทออฟไลน์ของคุณเพื่อสร้าง BufferSourceNode และ ScriptProcessorNode ของคุณและทำการเรนเดอร์ - person Kevin Ennis; 23.09.2013
comment
@Marco คุณทำให้มันใช้งานได้ไหม? ฉันกำลังพยายามทำสิ่งเดียวกันและใช้เวลามากมายในการพยายามหาวิธีแก้ปัญหา แต่ก็ยังไม่มีอะไรทำงาน .. - person okram; 16.02.2019
comment
@okram ฉันไม่คิดอย่างนั้น แต่ฉันทิ้งโปรเจ็กต์นี้ไว้นานแล้ว ;) - person Marco; 18.02.2019

สมมติว่าบัฟเฟอร์นั้นเป็น float32array ที่มีเสียงหนึ่งช่องสัญญาณ
นี่เป็นโค้ดที่ดีสำหรับการวิเคราะห์ออฟไลน์ที่ฉันเขียน

ฟังก์ชันที่ส่งผ่านไปยังการโทรกลับจะถูกดำเนินการในทุกขั้นตอนของการวิเคราะห์
ฟังก์ชันที่ส่งไปยัง onend จะถูกเรียกใช้เมื่อสิ้นสุดกระบวนการ

function callback(data) {
    callback.count = callback.count || 0;
    console.log(callback.count++, data);
}
fftAnalyser = function(buff, sampleRate, fftSize, callback, onend) {

    // by Zibri (2019)
    var frequencies = [1000,1200,1400,1600]; //for example
    var frequencyBinValue = (f)=>{
        const hzPerBin = (ctx.sampleRate) / (2 * analyser.frequencyBinCount);
        const index = parseInt((f + hzPerBin / 2) / hzPerBin);
        return data[index]+1;
    }
    ctx = new OfflineAudioContext(1,buff.length,sampleRate);
    ctx.sampleRate = sampleRate;
    ctx.destination.channelCount = 1;

    processor = ctx.createScriptProcessor(1024, 1, 1);
    processor.connect(ctx.destination);

    analyser = ctx.createAnalyser();
    analyser.fftSize = fftSize;
    analyser.smoothingTimeConstant = 0.0;

    //analyser.minDecibels = -60;
    //analyser.maxDecibels = -20;

    source = ctx.createBufferSource();
    source.connect(analyser);

    processor.onaudioprocess = function(e) {
        data = new Uint8Array(analyser.frequencyBinCount);
        analyser.getByteFrequencyData(data);
        state = frequencies.map(frequencyBinValue.bind(this))
        if ("function" == typeof callback)
            onend();
        callback(state);
    }

    analyser.connect(processor);

    var sbuffer = ctx.createBuffer(1, buff.length, ctx.sampleRate);
    sbuffer.getChannelData(0).set(buff);
    source.buffer = sbuffer;

    source.onended = function(e) {
        console.log("Analysys ended.");
        if ("function" == typeof onend)
            onend();
    }

    console.log("Start Analysis.");

    source.start(0);
    ctx.startRendering();
}
person Zibri    schedule 01.09.2019
comment
คำถามอาจนอกหัวข้อแต่... เป็นไปได้ไหมที่จะรับข้อมูลความถี่ / FrequencyBinCount ในช่วงเวลาที่กำหนด ฉันต้องการวาดรูปคลื่นสำหรับข้อมูลความถี่ในช่วงเวลาที่กำหนดเมื่อผู้ใช้ค้นหาผ่านไฟล์เสียง โดยไม่ต้องเปิดเสียง - person frizurd; 03.05.2020
comment
@frizurd แน่นอน .. เพียงตั้งเวลาก่อน source.start - person Zibri; 12.07.2020