Как быстро изменить частоту воспроизводимого тона без треска?

У меня есть цикл со скоростью 50-200 кадров в секунду, и я хочу визуализировать данные со звуком. Мне нужно быстро и плавно реагировать на изменение данных, как Therminvox. Какая библиотека и подход лучше всего подходят для этого?

Проблемы с simpleTones.js:

  • звук ухудшается в течение ~ 2 минут и, наконец, перестал работать
  • я не нахожу, как изменить частоту, а не только играть отдельные ноты

Проблемы с Tone.js:

  • не могу найти как изменить частоту

document.onmousemove=init
bInit=false;
Timeout=100;
NoteLength=100;
function init() //this is not solving alert "The AudioContext was not allowed to start"
{
    if(bInit) return;
    //const osc = new Tone.Oscillator(440, "sine").toDestination().start();
    setTimeout(loop, 200);
    bInit=true;
}
function freqRand(){return 50+Math.random()*1000;}

function loop( )
{
    playTone(freqRand(),'sine',NoteLength/1000)
    setTimeout(loop, Timeout);
}


//--------UI
function sliderTimeout_change(v){
    Timeout=v; document.getElementById('Timeout').innerText=v;
}
function sliderNoteLength_change(v){
    NoteLength=v; document.getElementById('NoteLength').innerText=v;
}
<script src="https://cdn.jsdelivr.net/gh/escottalexander/simpleTones.js/simpleTones.js"></script>
<!-- <script src="Tone.js"></script>  -->
<body>
Timeout = <i id='Timeout'></i><br>
<input id='sliderTimeout' type="range" min="10" max="200" value="100" oninput="sliderTimeout_change(this.value)"><br><br>
NoteLength = <i id='NoteLength'></i><br>
<input id='sliderNoteLength' type="range" min="10" max="4000" value="100" oninput="sliderNoteLength_change(this.value)">
</body>


person user1329019    schedule 21.08.2020    source источник
comment
Я бы не стал использовать какую-либо библиотеку для синтеза вашего аудио... начните с создания цикла для воспроизведения простой кривой синусоиды на заданной частоте... заставьте это работать... затем придумайте алгоритм для преобразования вашего потока данных в ряд точек на кривой, которая колеблется внутри диапазона звуковых частот, скажем, от 300 до 3000 Гц ... всегда полезно выйти из самоограничивающего предположения, что библиотека решит любую проблему и получить свободу, которую вы получаете, выполняя всю эту тяжелую работу в ваш собственный код   -  person Scott Stensland    schedule 21.08.2020
comment
Считаете ли вы, что заполнение некоторого буфера вручную было бы лучше, чем этот метод: /40291462/ Я пытался воспроизвести буферы с помощью NAudio C#, и у него плохая производительность, связанная с задержкой, подкачкой буфера и потрескиванием   -  person user1329019    schedule 21.08.2020
comment
вы должны быть в состоянии получить ответ в этой запущенной и работающей ссылке, которая будет отображать аудиовыход ... он использует API веб-аудио, который является отличной функцией, встроенной во все браузеры ... ваш секретный соус будет заключаться в том, как переводить ваши произвольные данные в последовательность частотных сдвигов (вверх или вниз) для управления вызовами этого API ... большой вопрос, ваш процесс в реальном времени? должен ли желаемый выходной звук быть синхронизирован с вашим входным потоком данных? поскольку ваши данные различаются, должно ли это управлять зеркалированием с различной частотой? или разный объем   -  person Scott Stensland    schedule 21.08.2020
comment
linearRampToValueAtTime работает хорошо. Но для тестирования буфера я не могу найти примеров того, как обновить или переключить буфер на непрерывную игру. Все они показывают, как создавать, заполнять и подкрашивать игру, например man.hubwiz.com/docset/JavaScript.docset/Contents/Resources/   -  person user1329019    schedule 23.08.2020
comment
Легко сделать на Java, но IDK, если JavaScript имеет те же возможности. Если вы можете создать массив данных PCM тона (например, A = 440 Гц) и использовать его в качестве потокового, циклического источника данных через JavaScript, я могу показать вам несколько изящных приемов интерполяции для плавного и плавного изменения высоты тона. в реальном времени, включая сглаживание точки петли, если там есть разрыв (создание щелчка). Линейная интерполяция является ключевым моментом.   -  person Phil Freihofner    schedule 23.08.2020


Ответы (1)


Задача решается с помощью следующего кода, используя встроенный linearRampToValueAtTime, но лучше иметь дополнительный пример того, как это сделать с помощью Buffer.

document.onclick=init //onmousemove is not solving alert "The AudioContext was not allowed to start"
bInit=false;
Timeout=100;
var osc;
var aContext;
function init()
{
    if(bInit) return;
    
    aContext = new(window.AudioContext || window.webkitAudioContext)();
    osc = aContext.createOscillator(); 
    osc.type = 'sine';  //sine square sawtooth triangle 
    var now = aContext.currentTime;
    osc.frequency.setValueAtTime(440, now);
    osc.connect(aContext.destination); 
    osc.frequency.setValueAtTime(440, now+1);
    osc.start(now);
    
    setTimeout(loop, 100);

    bInit=true;
}
function loop( )
{
    let freq=freqRand(); freq=smothFrequency(freq);
    
    osc.frequency.linearRampToValueAtTime(freq,aContext.currentTime+Timeout/1000)
    
    setTimeout(loop, Timeout);
}

function freqRand(){return 300+Math.random()*300+ Math.sin(aContext.currentTime/10)*250;}

//============================== end of main part, next is for playing
//--------signal conditioning, smooth
var freq_avg=0;
var freq_avg_k=0.1;
var freq_RangeMul=1;

function smothFrequency(freq_new)
{
    freq_new*=freq_RangeMul;
    if(freq_avg==0 || bNoSmooth) freq_avg=freq_new;
    else //smooth freq change
    {
        if(Math.abs(freq_avg-freq_new)<Timeout/4) // no smooth when changes 4hz/ms
                freq_avg=freq_avg;
        else 
        {
            if(Timeout>50) //less smooth for long timeout, or 'sharp'
            {
                freq_avg=freq_avg*(1-freq_avg_k*4)+freq_new*freq_avg_k*4;
                if(freq_avg_k>=0.25)
                {
                    if(bSharpKeepRange)
                    {
                        if(freq_avg<0) freq_avg=-freq_avg;
                        if(freq_avg>1600) freq_RangeMul/=1.05
                        else if(freq_avg<40) freq_RangeMul*=1.05
                    }
                }
            }
            else
                freq_avg=freq_avg*(1-freq_avg_k)+freq_new*freq_avg_k; 
        }
    }
    //console.log(aContext.currentTime, freq, freq_RangeMul);
    return freq_avg;
}

//--------UI
var cc=null
function sliderTimeout_change(v){
    cc=v;
    v=v.value
    Timeout=v; document.getElementById('Timeout').innerText=v;
}
function sliderNoteLength_change(v){
    NoteLength=v; document.getElementById('NoteLength').innerText=v;
}
bNoSmooth=false;
function sliderfreq_avg_k_change(v){
    freq_avg_k=v/1000; 
    bNoSmooth=(freq_avg_k>0.22 && freq_avg_k<0.33);
    let str=freq_avg_k;
    if(bNoSmooth)str='no smooth';
    if(freq_avg_k>0.33)
    {
        document.getElementById('bSharpKeepRange').style.display='block';
        str=str+' sharp';
    }
    else document.getElementById('bSharpKeepRange').style.display='none';
    document.getElementById('freq_avg_k').innerText=str;
}
function checkbox_bSharpKeepRange_click(checkbox)
{
  bSharpKeepRange=checkbox.checked
   freq_avg=100;
   freq_RangeMul=1;
}
bSharpKeepRange=false;
<body>
Timeout ms = <i id='Timeout'></i><br>
<input id='sliderTimeout' type="range" min="10" max="1000" value="10" oninput="sliderTimeout_change(this)"><br><br>
freq change smooth factor = <i id='freq_avg_k'></i><br>
<input id='sliderfreq_avg_k' type="range" min="2" max="550" value="10" oninput="sliderfreq_avg_k_change(this.value)"><br>
<i id="bSharpKeepRange">
keep range 
 <input id='checkbox_bSharpKeepRange' type="checkbox" onclick='checkbox_bSharpKeepRange_click(this);'>
</i>
</body>

person user1329019    schedule 22.08.2020