Reputation: 332
For an application that makes one able to determine ones tinnitus frequency, the following scenario occurs:
A user 'mousedown' a html5 slider -> an oscillator at a certain frequency is started. With moving the handle the user can change the volume of the note.
Here is the (CoffeeScript-)code which is responsible for the whole audio processing:
# CoffeScript for Tuner element
# Enables playing notes via Web Audio API
class @Tuner
constructor: () ->
@playing = false
@whobbling = false
@stopping = false
@atVolumeChange = false
@atFadeIn = false
@activeBtn = undefined
if typeof AudioContext isnt "undefined"
@audioCtx ?= new AudioContext()
else if typeof webkitAudioContext isnt "undefined"
@audioCtx = new webkitAudioContext()
else
console.log "Browser hat keine WebAudioAPI"
# make sure listener is set correct:
listener = @audioCtx.listener
listener.setOrientation(0,0,-1,0,1,0)
listener.setPosition(0,0,0)
# Create PannerNode and set initially to L and R
@pannerNode = @audioCtx.createPanner()
@pannerNode.setOrientation(0,0,0)
@pannerNode.setPosition(0,0,1)
@pannerNode.connect( @audioCtx.destination )
# Create GainNode and set to 0.0
@gainNode = @audioCtx.createGain()
@gainNode.gain.value = 0.0
@gainNode.connect @pannerNode
@actualFreq = 500
@currentVolume = 0.0
start: (volume, elem_id) ->
if @playing or @stopping
return
@playing = true
@currentVolume = volume
@oscillator = @audioCtx.createOscillator()
@oscillator.type = "sine"
@oscillator.frequency.value = @actualFreq
@oscillator.connect @gainNode
# if whobbling is active, create LFO & LFOGain for effect
if @whobbling
lfo = @audioCtx.createOscillator()
lfo.type = "sine"
lfo.frequency.value = 5
whobGain = @actualFreq * 5.0 / 100.0
lfo_gain = @audioCtx.createGain()
lfo_gain.gain.value = whobGain
lfo.connect(lfo_gain)
lfo_gain.connect(@oscillator.frequency)
lfo.start(0)
# starting process
@activeBtn = elem_id
matchingGUI.highlightPlay( @activeBtn ) if @activeBtn
now = @audioCtx.currentTime
@atFadeIn = true
@gainNode.gain.setValueAtTime 0.0, now + 0.01
@oscillator.start(now + 0.02)
@gainNode.gain.setValueAtTime 0.0, now + 0.03
@gainNode.gain.linearRampToValueAtTime volume, now + 0.2
that = @
setTimeout ->
that.atFadeIn = false
, 200
stop: () ->
if @playing and not @stopping
@stopping = true
now = @audioCtx.currentTime
now += 0.3 if @atVolumeChange
now += 0.3 if @atFadeIn
@gainNode.gain.setValueAtTime @currentVolume, now + 0.01
@gainNode.gain.linearRampToValueAtTime 0.0, now + 0.45
@oscillator.stop( now + 0.5 )
matchingGUI.highlightStop(@activeBtn) if @activeBtn
that = @
setTimeout ->
that.stopping = false
that.playing = false
that.whobbling = false
, 750
setFrequency: (newFreq) ->
@actualFreq = newFreq
@oscillator.frequency.value = newFreq if @playing and not @stopping
changeVolume: (newVolume) ->
newVolumeDb = @linearToDb newVolume
if not @stopping
@atVolumeChange = true
now = @audioCtx.currentTime
@currentVolume = newVolumeDb
@gainNode.gain.exponentialRampToValueAtTime @currentVolume, now + 0.333
that = @
setTimeout ->
that.atVolumeChange = false
, 300
setWhobbling: () ->
@whobbling = true
setPanToLR: () ->
@pannerNode.setPosition(0,0,1) unless @playing
setPanToL: () ->
@pannerNode.setPosition(-3,0,0) unless @playing
setPanToR: () ->
@pannerNode.setPosition(3,0,0) unless @playing
play: (frequency, volume, whob, elem_id) ->
whob ?= false
@setFrequency frequency
@setWhobbling() if whob
volumeDb = @linearToDb( volume )
@start( volumeDb, elem_id )
linearToDb: (s) ->
dbStart = 90
s = s * dbStart - dbStart
return Math.pow 10, s/20
# === End Class Tuner ===
#
# export Tuner
root = exports ? window
root.Tuner = Tuner
Tuner.play() is connected to the sliders 'mousedown' callback and Tuner.changeVolume() to the sliders 'input' callback.
The problem is as follows:
Every time the slider is moved, a clicking occurs. I guess the reason for this is that every time the slider fires the 'input', the ramp, currently driven by the Tuner.changeVolume method, is overlaid by another ramp.
I experimented a lot (no scheduling, cancelScheduledValues, ...) and the version above leaves only one last clicking when the slider is moved initially for various browsers on Mac. But for browsers on a Windows machine the clicking increases significantly.
(Btw. it seems not to be possible to read out the @gainNode.gain.value when a ramp is driven.)
Any idea how to handle this problem? I'd be thankful for any hint...
Upvotes: 0
Views: 220
Reputation: 122
It may be your oscillators are restarting. Not very familiar with CoffeeScript (need more coffee...) so can't directly root out the problem but if its a click and drag sliding event perhaps a browser is improperly sending multiple mousedowns with the mousemove event.
Instead why not have the oscillators always running but the gain node at 0, effectively no output. Then when the user goes over the gain ramps and tracks the user input. Generally it is good practice to use ramps, but if you trigger the volume change to occur with mousemove, most users do not move the slidders fast enough for you to get the 'zipping' effect I think you want to avoid.
Upvotes: 0