2017-01-06 8 views
1

Веб-аудио api предоставляет метод .stop(), чтобы остановить звук. Я хочу, чтобы мой звук уменьшался в объеме перед остановкой. Для этого я использовал узел усиления. Однако я сталкиваюсь с такими странными проблемами, когда некоторые звуки просто не играют, и я не могу понять, почему.Веб-аудио api, стоп-звук изящно

Вот упрощенных вниз вариант того, что я делаю:

https://jsfiddle.net/01p1t09n/1/

Вы услышите, что если вы удалите строку с setTimeout() что каждый звук играет. Когда setTimeout не воспроизводится не каждый звук. Что меня действительно смущает, так это то, что я использую push и shift соответственно, чтобы найти правильный источник звука, однако кажется, что это другое, что перестает играть. Единственное, что я вижу, это то, что AudioContext.decodeAudioData не является синхронным. Просто попробуйте jsfiddle, чтобы лучше понять и, очевидно, надеть гарнитуру.

Вот код jsfiddle:

let url = "https://raw.githubusercontent.com/gleitz/midi-js-soundfonts/gh-pages/MusyngKite/acoustic_guitar_steel-mp3/A4.mp3"; 
    let soundContainer = {}; 
    let notesMap = {"A4": [] }; 
    let _AudioContext_ = AudioContext || webkitAudioContext; 
    let audioContext = new _AudioContext_(); 

    var oReq = new XMLHttpRequest(); 
    oReq.open("GET", url, true); 
    oReq.responseType = "arraybuffer"; 
    oReq.onload = function (oEvent) { 
    var arrayBuffer = oReq.response; 
    makeLoop(arrayBuffer); 
    }; 
    oReq.send(null); 

    function makeLoop(arrayBuffer){ 
    soundContainer["A4"] = arrayBuffer; 
    let currentTime = audioContext.currentTime; 
    for(let i = 0; i < 10; i++){ 
     //playing at same intervals 
      play("A4", currentTime + i * 0.5); 
     setTimeout(() => stop("A4"), 500 + i * 500); //remove this line you will hear all the sounds. 
    } 
    } 

    function play(notePlayed, start) {  

     audioContext.decodeAudioData(soundContainer[notePlayed], (buffer) => { 
     let source; 
     let gainNode; 
     source = audioContext.createBufferSource(); 
     gainNode = audioContext.createGain(); 
     // pushing notes in note map 
     notesMap[notePlayed].push({ source, gainNode }); 
     source.buffer = buffer;     
     source.connect(gainNode); 
     gainNode.connect(audioContext.destination); 
     gainNode.gain.value = 1; 
     source.start(start); 
     }); 
    } 

     function stop(notePlayed){  
     let note = notesMap[notePlayed].shift(); 

     note.source.stop(); 
    } 


Это просто объяснить, почему я делаю это так, вы можете пропустить его, это просто объяснить, почему я не 't use stop()

Причина, по которой я все это делаю, потому что я хочу прекратить звук изящно, поэтому, если есть возможность сделать это без использования setTimeout, я бы с радостью принял Это.

В основном у меня есть карта наверху, содержащая мои звуки (примечания типа A1, A # 1, B1, ...).

soundMap = {"A": [], "lot": [], "of": [], "sounds": []}; 

и play() ГЦТ, где я заполнить массивы один раз я играю звуки:

play(sound) { 
    // sound is just { soundName, velocity, start} 
    let source; 
    let gainNode; 
    // sound container is just a map from soundname to the sound data. 
    this.audioContext.decodeAudioData(this.soundContainer[sound.soundName], (buffer) => { 
     source = this.audioContext.createBufferSource(); 
     gainNode = this.audioContext.createGain(); 
     gainNode.gain.value = sound.velocity; 
     // pushing sound in sound map 
     this.soundMap[sound.soundName].push({ source, gainNode }); 
     source.buffer = buffer;     
     source.connect(gainNode); 
     gainNode.connect(this.audioContext.destination); 
     source.start(sound.start); 
    }); 
    } 

А теперь часть, которая останавливает звуки:

stop(sound){ 
    //remember above, soundMap is a map from "soundName" to {gain, source} 
    let dasound = this.soundMap[sound.soundName].shift(); 
    let gain = dasound.gainNode.gain.value - 0.1; 

    // we lower the gain via incremental values to not have the sound stop abruptly 
    let i = 0; 
    for(; gain > 0; i++, gain -= 0.1){ // watchout funky syntax 
     ((gain, i) => { 
     setTimeout(() => dasound.gainNode.gain.value = gain, 50 * i); 
     })(gain, i) 
    } 
    // we stop the source after the gain is set at 0. stop is in sec 
    setTimeout(() => note.source.stop(), i * 50); 
    } 
+1

_ «Единственное, что я вижу, это то, что AudioContext.decodeAudioData не является синхронным». _ Вы правы: '.decodeAudioData' не является синхронным. – guest271314

+0

@ guest271314 ну, черт возьми, мне придется переделать все – Ced

+0

Что ожидается результат вызова 'setTimeout' в цикле' for' в jsfiddle, который не вызывается в закрытии? '.decodeAudioData()' также возвращает 'Promise', где' .then() 'может быть закодирован для получения декодированных аудиоданных. – guest271314

ответ

1

Ааа, да, да , да! Я, наконец, нашел много вещей, в конце концов, потрудившись прочитать «все» в документе (по диагонали). И позвольте мне сказать вам, что этот апи - это бриллиант в грубой форме. Во всяком случае, они на самом деле есть то, что я хотел с Audio param:

Интерфейс AudioParam представляет собой параметр аудио связанных, как правило, параметр в AudioNode (таких как GainNode.gain). Значение AudioParam может быть установлено на определенное значение или изменение значения, а может быть запланировано на определенное время и после определенного шаблона .

Он имеет функцию linearRampToValueAtTime()

И у них даже есть пример с тем, что я спросил!

// create audio context 
var AudioContext = window.AudioContext || window.webkitAudioContext; 
var audioCtx = new AudioContext(); 

// set basic variables for example 
var myAudio = document.querySelector('audio'); 
var pre = document.querySelector('pre'); 
var myScript = document.querySelector('script'); 

pre.innerHTML = myScript.innerHTML; 

var linearRampPlus = document.querySelector('.linear-ramp-plus'); 
var linearRampMinus = document.querySelector('.linear-ramp-minus'); 

// Create a MediaElementAudioSourceNode 
// Feed the HTMLMediaElement into it 
var source = audioCtx.createMediaElementSource(myAudio); 

// Create a gain node and set it's gain value to 0.5 
var gainNode = audioCtx.createGain(); 

// connect the AudioBufferSourceNode to the gainNode 
// and the gainNode to the destination 
gainNode.gain.setValueAtTime(0, audioCtx.currentTime); 
source.connect(gainNode); 
gainNode.connect(audioCtx.destination); 

// set buttons to do something onclick 
linearRampPlus.onclick = function() { 
    gainNode.gain.linearRampToValueAtTime(1.0, audioCtx.currentTime + 2); 
} 

linearRampMinus.onclick = function() { 
    gainNode.gain.linearRampToValueAtTime(0, audioCtx.currentTime + 2); 
} 

Working example here

Они также имеют различный тип тайминги, как экспоненциальный вместо линейного пандус, который я думаю будет соответствовать этому сценарию больше.

+0

Приятный пример, но как насчет того, что вы хотели сделать -> «Я хочу, чтобы мой звук уменьшался в объеме перед остановкой» , Вы нашли какие-либо способы остановить звук (source.stop()) сразу после уменьшения громкости около 0? Я пытаюсь обнаружить конец функции linearRamp. Но, к сожалению, в DOC нет ничего об этом. Я могу установить интервал и проверить значение gain.value X раз тогда, когда значение близко к 0, я мог бы остановить источник. Но я предпочел бы остановить источник в самом «реальном конце» процесса linearRamp. Есть идеи? – Faks

+0

@ Хорошо, если продолжительность составляет 0,3 сек, вы можете остановиться после 0,3 секунды. Я не уверен, что это будет идеально, но это то, что я сделал, если правильно вспомню. Если вы пойдете немного больше, звук будет равен 0, так что не должно быть никаких проблем, не так ли? Например, остановка после 0,7 сек. – Ced

+0

Спасибо;) Это то, что я уже сделал, но это не идеально, было бы лучше, если бы сама функция могла выполнить обратный вызов в конце fade. Кроме того: просто информация для разработчиков, использующих API веб-аудио с Cordova. В iOS функция linearRampToValueAtTime() работает, но если у вас одновременно есть несколько звуков, это вызовет случайные скрипы/крики/клики/грязные звуки на ваших динамиках. Если ваше приложение предназначено как для Android, так и для iOS, вы должны использовать две разные функции linearRampToValueAtTime() для Android, а затем значение gainNode.gain.value (устарело на Chrome 64) в SetInterval на iOS. – Faks