【问题标题】:Chord frequency continuous change by Tone.js通过 Tone.js 连续改变和弦频率
【发布时间】:2021-06-27 23:08:08
【问题描述】:

如何使用tone.js为吉他制作和弦基频连续交互变化?

显然将 Tone.Sampler 与可能命名为“pitchBend”或“pitchShift”的 API 一起使用是一种可行的方法,但我不确定实际选择。也就是说,如何制作“变调夹”?

我解决了本机 window.AudioContext 的这个问题,用于一组振荡器(每个振荡器用于一个吉他弦),使用滑块的频率更新,例如:

oscillator.frequency.setValueAtTime(频率, context.currentTime);

当振荡器仍然活跃并播放时,它可以很好地改变所有弦的音调。

但是 1) 音色不完全是默认正弦声波形状的吉他,并且 2) 弦有时会干扰或回响,使声音过于不自然。

也许tone.js 平台以外的平台可以解决问题?

谢谢。

【问题讨论】:

    标签: web-audio-api tone.js


    【解决方案1】:

    我做了更多的研究,现在看起来原生 WebAudio API 解决了这个问题,但还不清楚它的可靠性和精确度。我确实加载了吉他弦样本,并让滑块改变了它们的速度比以重新调整。下面是代码:

        <!DOCTYPE html>
        <html>
          <head>
            <title>Continuous chord. Capo effect.</title>
            <meta charset="utf-8">
            <meta name="viewport"
                  content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
            <script>
        
                window.onload = function () {
        
        
                    //==================================================
                    // //\\ configures sound parameters
                    //==================================================
        
                    // //\\ scale constants
                    /*
                    var logStep         = Math.log( 2 ) / 12;
                    var semitone        = Math.exp( logStep );
                    console.log( semitone );
                    var semi2           = semitone * semitone;
                    var semi4           = semi2 * semi2;
                    var semi7           = semi2 * semitone;
                    */
                    // \\// scale constants
        
        
                    //E3, A3, C3#,
                    //165, 220, 277
                    var initSpeed       = 1;
                    var clipStart       = 0; //0.65; //0.7;
                    var clipLenOriginal = 3.2; //0.43;
                    var doLoop          = false; //true;
        
        
        
                    //speed raise
                    //if it is expected only one url for all tones in chord,
                    //adjust here speeds to generate different tones according to
                    //frequency ratios,
                    var clipLenSpeeds   = [
                        //minor
                        /*
                        var smR0  = 1;
                        var smR1  = 262/220;
                        var smR2  = 330/220;
                        */
                        //major
                        1,                      
                        1, // 277/220, //220/165
                        1, // 330/220, //277/165
                    ];
                    var clipLengs       = clipLenSpeeds.map( sp => clipLenOriginal*sp );
                    var clipLenSpeeds   = clipLenSpeeds.map( sp => initSpeed*sp );
        
                    var dataPath        = 'data';
                    //data are from this path:
                    //var dataPath      = 'https://github.com/nbrosowsky/tonejs-instruments/tree/master/samples/guitar-acoustic';
                    var urls            = [
                        dataPath + '/' + 'A3.wav',
                        dataPath + '/' + 'E3.wav',
                        dataPath + '/' + 'Cs3.wav',
                    ];
                    //var fname = 'second-line-captured.mkv.mp3';
                    //var urls            = clipLenSpeeds.map( el => fname );
                    //==================================================
                    // \\// configures sound parameters
                    //==================================================
        
        
                    //==================================================
                    // //\\ loads and prepares sourceNodes
                    //==================================================
                    let audioContext    = new AudioContext();
                    var sourceNodes     = [];
                    var audioBuffers    = [];
        
                    Promise.all(
                        clipLenSpeeds.map( ( el, ix ) => 
                            getSample( ix )
                        )
                    ).then(
                        ( values ) => {
                            //console.log( 'values from last promise-then:', values );
                            button.removeAttribute( 'disabled' );
                        },
                    ).catch( (err) => {
                        console.log( err );
                    });
                    //==================================================
                    // \\// loads and prepares sourceNodes
                    //==================================================
        
        
        
                    //==================================================
                    // //\\ GUI
                    //==================================================
                    const button            = document.querySelector( '.run' );
                    const playbackControl   = document.querySelector( '.playback-rate-control' );
                    const playbackValue     = document.querySelector( '.playback-rate-value' );
                    button.setAttribute( 'disabled', 'disabled' );
                    button.addEventListener( 'click', () => {
                        audioBuffers.forEach( (val,vix) => 
                            startLoopByIx( vix )
                        );
                    });
                    playbackControl.oninput = function() {
                        playbackValue.innerHTML = playbackControl.value;
                        sourceNodes.forEach( (sn,i) => {
                            sn.playbackRate.value = clipLenSpeeds[i] * playbackControl.value;
                        });
                    }
                    //==================================================
                    // \\// GUI
                    //==================================================
                    return;
        
        
        
        
        
        
                    ///part of the load and initation of audio buffer,
                    ///returns promise from the last ".then"
                    function getSample( ix )
                    {
                        return fetch( urls[ ix ] )
                            .then( response => response.arrayBuffer() )
                            .then( arrayBuffer => audioContext.decodeAudioData( arrayBuffer ) )
                            .then( audioBuffer => {
                                audioBuffers[ ix ] = audioBuffer;
        
                                //if uncommented, this returned value populates
                                //values in Promise.all.then first param,
                                //return audioBuffer;
                            })
                            ;
                    }
        
                    ///fires up loop belonging to sourceNode[ ix ],
                    ///options: either doLoop or one-time play,
                    function startLoopByIx( ix )
                    {
                        var audioBuffer = audioBuffers[ ix ];
                        var clipLen     = clipLengs[ ix ];
                        var rate        = clipLenSpeeds[ ix ];
                        var pan         = 0; //stereo channels balance
        
                        let sourceNode = audioContext.createBufferSource();
                        let pannerNode = audioContext.createStereoPanner();
        
                        sourceNode.buffer               = audioBuffer;
                        sourceNode.loop                 = doLoop;
                        sourceNode.loopStart            = clipStart;
                        sourceNode.loopEnd              = clipStart + clipLen;
                        sourceNode.playbackRate.value   = rate; //does retuning
                        pannerNode.pan.value            = pan;
        
                        sourceNode.connect( pannerNode );
                        pannerNode.connect( audioContext.destination );
        
                        if( doLoop ) {
                            sourceNode.start( 0, clipStart, );
                        } else {
                            sourceNode.start( 0, clipStart, clipLen );
                        }
                        sourceNodes[ ix ] = sourceNode;
                    }
                };
            </script>
          </head>
          <body>
        
            <button class="run">Run samples</button><br>
        
            <h2>Set playback rate</h2>
            <input class="playback-rate-control" type="range" min="0.25" max="3" step="0.05" value="1">
            <span class="playback-rate-value">1.0</span>
        
        
          </body>
        </html>
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-12-11
      • 2017-09-22
      • 2015-07-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-12-12
      相关资源
      最近更新 更多