javascriptweb-audio-apihowler.jstone.jspitch-shifting

Tone.PitchShift and Howler.js issues


I enjoy using Howler.js for my (Meteor) application. However, the playback rate function is causing a pitch shift that I don't want (I just want the time stretch, and to preserve the pitch). My solution was to therefore implement a pitch shift to it to 'correct' the pitch. Seemed simple enough, which is why I chose to use https://tonejs.github.io/

Only problem is, I cannot for the life of me get it to work correctly. After hours of reading up on Web Audio API, and Tone.js documentation, and online discussion/troubleshoot forums, the closest to a potential solution I got was something like so (called during render of my application, just in case the issue had to do with loading prematurely):

Tone.setContext(Howler.ctx); //set tone's context to the Howler.js audiocontext
var pShift = new Tone.PitchShift(3); //create the PitchShift effect, +3 semi-tones transposition
pShift.context = Howler.ctx; //set the PitchShift's context to the Howler.js audiocontext
pShift.connect(Howler.ctx.destination); //connect the PitchShift's output to the Howler's destination
Howler.masterGain.connect(pShift); //connect the Howler's master GainNode output to the PitchShift effect

//For debugging purposes:
console.log(Howler.masterGain)
console.log(pShift);

When I do run this I get this error message:

Exception from Tracker afterFlush function: meteor.js?hash=857dafb4b9dff17e29ed8498a22ea5b1a3d6b41d:1059 TypeError: Failed to execute 'connect' on 'AudioNode': Overload resolution failed.

I also noticed that the console.log() commands below those don't even show up in the console, strangely enough. They do, however, when I remove the last line (mastergain.connect to the pShift).

I tried a few other techniques, such as https://github.com/mmckegg/soundbank-pitch-shift/ (which worked but it played both the pitch shifted sound and the non-pitch shifted sound, no matter what settings I put it at), Or simply using AudioBufferSourceNode.detune (I couldn't figure out how to get it to work with Howler.js because Howler only has functions that can expose the GainNode and the AudioContext, wasn't able to figure out how to read output from there while still using Howler).

Any help/leads would be greatly appreciated!


Solution

  • I think you don't need the 3rd line in your snippet. Since your first line is telling Tone.js to use the AudioContext created by howler.js already. Therefore pShift.context should be equal to Howler.ctx. But it might make sense to double check.

    console.assert(pShift.context === Howler.ctx);
    

    The masterGain exposed by howler.js is a native audio node. That means it can't be connected to a node created with Tone.js directly since these are not native audio nodes. But Tone.js offers a helper to do that.

    Tone.connect(Howler.masterGain, pShift);
    

    I think you also need to call disconnect() on the masterGain to remove any existing connections.

    The following snippet should work.

    Tone.setContext(Howler.ctx);
    
    const pShift = new Tone.PitchShift(3);
    
    Howler.masterGain.disconnect();
    
    Tone.connect(Howler.masterGain, pShift);
    pShift.toDestination();