describe 'mixco.behaviour', ->
util = require '../../mixco/util'
value = require '../../mixco/value'
behaviour = require '../../mixco/behaviour'
transform = require '../../mixco/transform'
mock = require '../mock'
mockActor = -> createSpyObj 'actor', [
'doSend',
'send',
'on',
'addListener',
'removeListener' ]
mockBehaviour = ->
mocked = new behaviour.Behaviour arguments...
spyOn(mocked, 'enable').andCallThrough()
spyOn(mocked, 'disable').andCallThrough()
spyOn(mocked, 'onMidiEvent').andCallThrough()
mocked
describe 'option', ->
option = behaviour.option
it 'has some linear transforms', ->
expect(option.invert.transform 32).toBe 95
expect(option.spread64.transform 32).toBe -32
it 'has some non-linear transforms', ->
expect(option.diff.transform 32, 8).toBe 40
expect(option.hercjog.transform 32, 8).toBe 40
it 'enables soft takeover on input mappings', ->
beh = behaviour.mapIn "[Test]", "test"
beh.script = mock.testScript()
option.softTakeover.enable beh
expect(beh.script.mixxx.engine.softTakeover)
.toHaveBeenCalledWith "[Test]", "test", true
option.softTakeover.disable beh
expect(beh.script.mixxx.engine.softTakeover)
.toHaveBeenCalledWith "[Test]", "test", false
it 'replaces dashes from option names', ->
expect(option.invert.name).toBe 'invert'
expect(option.softTakeover.name).toBe 'soft-takeover'
describe 'Behaviour', ->
behav = null
beforeEach ->
behav = mockBehaviour initial: 32
it 'returns the same MIDI value as normal value', ->
expect(behav.value).toBe behav.midiValue
expect(behav.value).toBe 32
behav.value = 64
expect(behav.value).toBe behav.midiValue
expect(behav.value).toBe 64
it 'transforms MIDI input events with given options', ->
behav.option transform: (x) -> x * 2
actor = new behaviour.Actor
behav.enable {}, actor
actor.emit 'event', value: 3
expect(behav.onMidiEvent).toHaveBeenCalledWith value: 6
behav.option transform: (x) -> x - 1
actor.emit 'event', value: 3
expect(behav.onMidiEvent).toHaveBeenCalledWith value: 5
it 'processes MIDI input events with given options', ->
behav.option process: (ev, b) -> ev.value = ev.value * 2
actor = new behaviour.Actor
behav.enable {}, actor
actor.emit 'event', value: 3
expect(behav.onMidiEvent).toHaveBeenCalledWith value: 6
behav.option process: (ev, b) -> ev.value = ev.value - b.midiValue
actor.emit 'event', value: 3
expect(behav.onMidiEvent).toHaveBeenCalledWith value: -26
it 'transforms can use the previous value', ->
opt = createSpyObj 'option', ['transform']
behav.option opt
actor = new behaviour.Actor
behav.enable {}, actor
actor.emit 'event', value: 3
expect(opt.transform).toHaveBeenCalledWith 3, 32
it 'options are enabled and disabled', ->
opt = createSpyObj 'option', ['enable', 'disable']
behav.option opt
script = {}
actor = new behaviour.Actor
behav.enable script, actor
expect(opt.enable).toHaveBeenCalledWith behav
behav.disable script, actor
expect(opt.disable).toHaveBeenCalledWith behav
it 'can take options with an option chooser syntax', ->
behav.options.spread64
behav.options.softTakeover
expect(behav._options).toEqual [
behaviour.option.spread64
behaviour.option.softTakeover
]
describe 'Output', ->
output = null
actor = null
beforeEach ->
output = new behaviour.Output
actor = mockActor()
it 'can accept actor without "send"', ->
actor.send = undefined
output.enable {}, actor
output.value = 5
output.value = 0
it 'initializes the actor depending on pre-enable value', ->
output.output.value = 1
output.enable {}, actor
expect(actor.doSend).toHaveBeenCalledWith 'on'
it 'initializes the actor even if it denies output', ->
actor.send = undefined
output.output.value = 1
output.enable {}, actor
expect(actor.doSend).toHaveBeenCalledWith 'on'
it 'sends "on" value when value is above or equal minimum', ->
output.enable {}, actor
output.output.value = 1
expect(actor.send).toHaveBeenCalledWith 'on'
it 'sends "on" value when value is bellow minimum', ->
output.enable {}, actor
output.output.value = 1
output.output.value = 0
expect(actor.send).toHaveBeenCalledWith 'off'
describe 'Transform', ->
it 'can take an initial value as second parameter', ->
t = behaviour.transform (->), 42
expect(t.value).toBe 42
it 'sets its value and output to the transformed MIDI input', ->
t = behaviour.transform (v) -> v * 2
t.onMidiEvent value: 3
expect(t.value).toBe 6
expect(t.output.value).toBe 6
expect(t.midiValue).toBe 6
t.onMidiEvent value: 6
expect(t.value).toBe 12
expect(t.output.value).toBe 12
expect(t.midiValue).toBe 12
it 'can inverse the transform to reconstruct the midi values', ->
f = (v) -> v * 2
f.inverse = (v) -> v / 2
t = behaviour.transform f
t.onMidiEvent value: 3
expect(t.value).toBe 6
expect(t.midiValue).toBe 3
it 'does not set its value when the transform gives a nully value', ->
t = behaviour.transform (v) -> if v != 42 then v * 2
t.onMidiEvent value: 3
expect(t.value).toBe 6
t.onMidiEvent value: 42
expect(t.value).toBe 6
t.onMidiEvent value: 0
expect(t.value).toBe 0
it 'can take non-linear transforms', ->
t = behaviour.transform transform.binaryT
t.onMidiEvent value: 3
expect(t.value).toBe true
t.onMidiEvent value: 3
expect(t.value).toBe false
describe 'InMap', ->
map = null
actor = null
script = null
beforeEach ->
map = behaviour.map
group: "[test]"
key: "test"
initial: 42
actor = mockActor()
script = mock.testScript()
it 'returns the value as midi value when not inversible transform', ->
expect(map.value).toBe map.midiValue
expect(map.midiValue).toBe 42
map.value = 32
expect(map.value).toBe map.midiValue
expect(map.midiValue).toBe 32
it 'uses the inverse of the transform to produce back the MIDI values', ->
map.transform inverse: (x) -> x - 10
expect(map.value).toBe 42
expect(map.midiValue).toBe 32
map.value = 32
expect(map.value).toBe 32
expect(map.midiValue).toBe 22
describe 'Map', ->
map2 = null
map = null
actor = null
script = null
beforeEach ->
map = behaviour.map "[Test]", "test"
map2 = behaviour.map "[Test]", "test", "[Test2]", "test2"
actor = mockActor()
script = mock.testScript()
it 'does not listen to the Mixxx control unnecesarily', ->
actor.send = undefined
map.enable script, actor
expect(script.mixxx.engine.connectControl)
.not.toHaveBeenCalled()
it 'connects to the Mixxx control when actor has send', ->
map.enable script, actor
expect(script.mixxx.engine.connectControl)
.toHaveBeenCalledWith "[Test]", "test", script.handlerKey()
it 'connects to output control when different from input', ->
map2.enable script, actor
expect(script.mixxx.engine.connectControl)
.toHaveBeenCalledWith "[Test2]", "test2", script.handlerKey()
it 'direct maps output to the right parameter', ->
expect(map.directOutMapping()).toEqual {
group: "[Test]", key: "test", minimum: 1 }
expect(map2.directOutMapping()).toEqual {
group: "[Test2]", key: "test2", minimum: 1 }
it 'direct maps input to the right parameter', ->
expect(map.directInMapping()).toEqual {
group: "[Test]", key: "test" }
expect(map2.directInMapping()).toEqual {
group: "[Test]", key: "test" }
it 'connects to the Mixxx control when someone is obsrving "value"', ->
map.on "value", ->
map.enable script, actor
expect(script.mixxx.engine.connectControl)
.toHaveBeenCalledWith "[Test]", "test", script.handlerKey()
it 'initializes the value and output with the current engine status', ->
script.mixxx.engine.getValue = (group, key) ->
if group == "[Test]" and key == "test"
1
else if group == "[Test2]" and key == "test2"
2
else
null
map2.enable script, actor
expect(map2.value).toBe(1)
expect(map2.output.value).toBe(2)
it 'sets the values in the engine using the default transform', ->
xfader = behaviour.map "[Master]", "crossfader"
xfader.enable script, actor
xfader.onMidiEvent value: 63.5
expect(script.mixxx.engine.setValue)
.toHaveBeenCalledWith "[Master]", "crossfader", 0.0
xfader.onMidiEvent value: 127
expect(script.mixxx.engine.setValue)
.toHaveBeenCalledWith "[Master]", "crossfader", 1
xfader.onMidiEvent value: 0
expect(script.mixxx.engine.setValue)
.toHaveBeenCalledWith "[Master]", "crossfader", 1
it 'sets the values in the engine using custom transformation', ->
xfader = behaviour.map("[Master]", "crossfader").transform (v) -> v
xfader.enable script, actor
xfader.onMidiEvent value: 64
expect(script.mixxx.engine.setValue)
.toHaveBeenCalledWith "[Master]", "crossfader", 64
xfader.onMidiEvent value: 127
expect(script.mixxx.engine.setValue)
.toHaveBeenCalledWith "[Master]", "crossfader", 127
xfader.onMidiEvent value: 0
expect(script.mixxx.engine.setValue)
.toHaveBeenCalledWith "[Master]", "crossfader", 0
it 'does not direct map output when a custom transform is set', ->
xfader = behaviour.map("[Master]", "crossfader").transform (v) -> v
expect(xfader.directInMapping()).toBe undefined
it 'does not direct map input when a custom transform is set', ->
xfader = behaviour.map("[Master]", "crossfader").meter (v) -> v
expect(xfader.directOutMapping()).toBe undefined
it 'does nothing when the transform return null', ->
xfader = behaviour.map("[Master]", "crossfader").transform (v) ->
if v == 64 then 64 else null
xfader.enable script, actor
xfader.onMidiEvent value: 32
expect(script.mixxx.engine.setValue)
.not.toHaveBeenCalled()
xfader.onMidiEvent value: 64
expect(script.mixxx.engine.setValue)
.toHaveBeenCalledWith "[Master]", "crossfader", 64
it 'does toggle from previous state when binary transform', ->
lock = behaviour.map "[Channel1]", "keylock"
lock.enable script, actor
lock.onMidiEvent value: 32
expect(script.mixxx.engine.setValue)
.toHaveBeenCalledWith "[Channel1]", "keylock", true
lock.onMidiEvent value: 32
expect(script.mixxx.engine.setValue)
.toHaveBeenCalledWith "[Channel1]", "keylock", false
describe 'Chooser', ->
actor = null
script = null
chooser = null
engine = null
beforeEach ->
chooser = behaviour.chooser()
actor = mockActor()
script = mock.testScript()
engine = script.mixxx.engine
chooser.add "[Channel1]", "pfl"
chooser.add "[Channel2]", "pfl"
chooser.add "[Channel3]", "pfl"
chooser.add "[Channel4]", "pfl"
it "activates the right option, when chooser is enabled", ->
chooser.enable script, actor
chooser.activate 0
expect(engine.getValue "[Channel1]", "pfl").toBe true
expect(engine.getValue "[Channel2]", "pfl").toBe false
chooser.activate 1
expect(engine.getValue "[Channel1]", "pfl").toBe false
expect(engine.getValue "[Channel2]", "pfl").toBe true
it "activates the right option, when first activator is enabled", ->
chooser.activator(0).enable script, actor
chooser.activate 0
expect(engine.getValue "[Channel1]", "pfl").toBe true
expect(engine.getValue "[Channel2]", "pfl").toBe false
it "activates the right option, when second activator is enabled", ->
chooser.activator(1).enable script, actor
chooser.activate 1
expect(engine.getValue "[Channel1]", "pfl").toBe false
expect(engine.getValue "[Channel2]", "pfl").toBe true
it "activators activate when they receive non-zero value", ->
chooser.activator(0).enable script, actor
chooser.activator(1).enable script, actor
chooser.activator(1).onMidiEvent value: 1
expect(engine.getValue "[Channel1]", "pfl").toBe false
expect(engine.getValue "[Channel2]", "pfl").toBe true
chooser.activator(0).onMidiEvent value: 1
expect(engine.getValue "[Channel1]", "pfl").toBe true
expect(engine.getValue "[Channel2]", "pfl").toBe false
chooser.activator(1).onMidiEvent value: 0
expect(engine.getValue "[Channel1]", "pfl").toBe true
expect(engine.getValue "[Channel2]", "pfl").toBe false
it "toggles the selected option on or off", ->
chooser.enable script, actor
chooser.activate 1
expect(engine.getValue "[Channel1]", "pfl").toBe false
expect(engine.getValue "[Channel2]", "pfl").toBe true
chooser._updateValue()
chooser.onMidiEvent value: 1
expect(engine.getValue "[Channel1]", "pfl").toBe false
expect(engine.getValue "[Channel2]", "pfl").toBe false
chooser._updateValue()
chooser.onMidiEvent value: 1
expect(engine.getValue "[Channel1]", "pfl").toBe false
expect(engine.getValue "[Channel2]", "pfl").toBe true
it "connects and disconnects from controls", ->
chooser.enable script, actor
expect(script.mixxx.engine.connectControl)
.toHaveBeenCalledWith "[Channel1]", "pfl", jasmine.any(String)
expect(script.mixxx.engine.connectControl)
.toHaveBeenCalledWith "[Channel2]", "pfl", jasmine.any(String)
chooser.disable script, actor
expect(script.mixxx.engine.connectControl)
.toHaveBeenCalledWith "[Channel1]", "pfl",
jasmine.any(String), true
expect(script.mixxx.engine.connectControl)
.toHaveBeenCalledWith "[Channel2]", "pfl",
jasmine.any(String), true
it "initialzies its value to true", ->
engine.setValue "[Channel1]", "pfl", true
chooser.enable script, actor
expect(chooser.value).toBe true
it "initialzies its value to false", ->
chooser.enable script, actor
expect(chooser.value).toBe false
it "can select with a selector knob", ->
selector = chooser.selector()
chooser.enable script, actor
selector.enable script, actor
chooser.activate 0
chooser._updateValue()
selector.onMidiEvent value: 32
expect(engine.getValue "[Channel1]", "pfl").toBe false
expect(engine.getValue "[Channel2]", "pfl").toBe true
expect(engine.getValue "[Channel3]", "pfl").toBe false
expect(engine.getValue "[Channel4]", "pfl").toBe false
selector.onMidiEvent value: 80
expect(engine.getValue "[Channel1]", "pfl").toBe false
expect(engine.getValue "[Channel2]", "pfl").toBe false
expect(engine.getValue "[Channel3]", "pfl").toBe true
expect(engine.getValue "[Channel4]", "pfl").toBe false
selector.onMidiEvent value: 120
expect(engine.getValue "[Channel1]", "pfl").toBe false
expect(engine.getValue "[Channel2]", "pfl").toBe false
expect(engine.getValue "[Channel3]", "pfl").toBe false
expect(engine.getValue "[Channel4]", "pfl").toBe true
it "selector value is transformed back to MIDI", ->
selector = chooser.selector()
chooser.enable script, actor
selector.enable script, actor
chooser.activate 0
chooser._updateValue()
chooser.select 0
chooser._updateValue()
expect(selector.midiValue).toBe 0
chooser.select 1
chooser._updateValue()
expect(selector.midiValue).toBe 32
chooser.select 2
chooser._updateValue()
expect(selector.midiValue).toBe 64
chooser.select 3
chooser._updateValue()
expect(selector.midiValue).toBe 96
it "selector value keeps MIDI offset", ->
selector = chooser.selector()
chooser.enable script, actor
selector.enable script, actor
chooser.activate 0
chooser._updateValue()
selector.onMidiEvent value: 36
expect(selector.midiValue).toBe 36
selector._updateValue 1
expect(selector.midiValue).toBe 36
it "can select before or after being enabled", ->
chooser.select 2
chooser.enable script, actor
chooser.onMidiEvent value: 1
expect(engine.getValue "[Channel1]", "pfl").toBe false
expect(engine.getValue "[Channel2]", "pfl").toBe false
expect(engine.getValue "[Channel3]", "pfl").toBe true
expect(engine.getValue "[Channel4]", "pfl").toBe false
chooser.select 1
expect(engine.getValue "[Channel1]", "pfl").toBe false
expect(engine.getValue "[Channel2]", "pfl").toBe true
expect(engine.getValue "[Channel3]", "pfl").toBe false
expect(engine.getValue "[Channel4]", "pfl").toBe false
it "assumes engine keeps exclusivity with auto-exclusive", ->
chooser = behaviour.chooser autoExclusive: true
chooser.add "[Channel1]", "pfl"
chooser.add "[Channel2]", "pfl"
chooser.enable script, actor
chooser.activate 1
expect(engine.getValue "[Channel1]", "pfl").toBe 0
expect(engine.getValue "[Channel2]", "pfl").toBe true
chooser.activate 0
expect(engine.getValue "[Channel1]", "pfl").toBe true
expect(engine.getValue "[Channel2]", "pfl").toBe true
it "calls onDisable to materialize disabling", ->
spy = createSpyObj "disabler", ['onDisable']
chooser = behaviour.chooser onDisable: spy.onDisable
chooser.add "[Channel1]", "pfl"
chooser.add "[Channel2]", "pfl"
chooser.enable script, actor
chooser.onMidiEvent value: 1
expect(spy.onDisable).not.toHaveBeenCalled()
chooser.value = true
chooser.onMidiEvent value: 1
expect(spy.onDisable).toHaveBeenCalled()
it "reads value from optional second key", ->
spy = createSpyObj "disabler", ['onDisable']
chooser = behaviour.chooser()
chooser.add "[Channel1]", "pfl", "listen"
chooser.add "[Channel2]", "pfl", "listen"
chooser.enable script, actor
expect(script.mixxx.engine.connectControl)
.not.toHaveBeenCalledWith "[Channel1]", "pfl", jasmine.any(String)
expect(script.mixxx.engine.connectControl)
.toHaveBeenCalledWith "[Channel1]", "listen", jasmine.any(String)
engine.setValue "[Channel1]", "pfl", true
chooser._updateValue()
expect(chooser.value).toBe false
engine.setValue "[Channel1]", "pfl", false
engine.setValue "[Channel1]", "listen", true
chooser._updateValue()
expect(chooser.value).toBe true
describe 'When', ->
condition = null
wrapped = null
when_ = null
actor = null
script = null
beforeEach ->
condition = value.value initial: false
wrapped = mockBehaviour()
actor = mockActor()
script = mock.testScript()
when_ = behaviour.when condition, wrapped
it "does nothing when enabled and condition not satisifed", ->
when_.enable script, actor
expect(wrapped.enable).
not.toHaveBeenCalled()
it "enables wrapped when condition is satisfied", ->
condition.value = true
when_.enable script, actor
expect(wrapped.enable).
toHaveBeenCalledWith script, actor
it "disables wrapped when it is disabled", ->
condition.value = true
when_.enable script, actor
when_.disable script, actor
expect(wrapped.disable).
toHaveBeenCalledWith script, actor
it "enables or disables wrapped when condition changes", ->
when_.enable script, actor
condition.value = true
expect(wrapped.enable).
toHaveBeenCalledWith script, actor
condition.value = false
expect(wrapped.disable).
toHaveBeenCalledWith script, actor
it "generates a new negated version on 'else", ->
wrapped2 = mockBehaviour()
else_ = when_.else wrapped2
condition.value = true
else_.enable script, actor
expect(wrapped2.enable).
not.toHaveBeenCalledWith script, actor
condition.value = false
expect(wrapped2.enable).
toHaveBeenCalledWith script, actor
it "else-when chains enable one branch exclusively", ->
condition2 = value.value false
wrapped2 = mockBehaviour()
elseWhen_ = when_.else.when condition2, wrapped2
wrapped3 = mockBehaviour()
else_ = when_.else wrapped3
when_.enable script, actor
elseWhen_.enable script, actor
else_.enable script, actor
expect(wrapped.actor).not.toBeDefined()
expect(wrapped2.actor).not.toBeDefined()
expect(wrapped3.actor).toBeDefined()
condition.value = true
expect(wrapped.actor).toBeDefined()
expect(wrapped2.actor).not.toBeDefined()
expect(wrapped3.actor).not.toBeDefined()
condition2.value = true
expect(wrapped.actor).toBeDefined()
expect(wrapped2.actor).not.toBeDefined()
expect(wrapped3.actor).not.toBeDefined()
condition.value = false
expect(wrapped.actor).not.toBeDefined()
expect(wrapped2.actor).toBeDefined()
expect(wrapped3.actor).not.toBeDefined()
condition2.value = false
expect(wrapped.actor).not.toBeDefined()
expect(wrapped2.actor).not.toBeDefined()
expect(wrapped3.actor).toBeDefined()
it "exposes wether it meets the condition on its 'value'", ->
when_.enable script, actor
condition.value = true
expect(when_.value).toBe true
condition.value = false
expect(when_.value).toBe false
it "propagates options to the wrapped behaviour", ->
when_.option behaviour.option.softTakeover
expect(wrapped._options).toEqual [
behaviour.option.softTakeover
]
when_.option behaviour.option.invert
expect(wrapped._options).toEqual [
behaviour.option.softTakeover
behaviour.option.invert
]
describe 'PunchIn', ->
rightPunchIn = null
leftPunchIn = null
actor = null
script = null
xfader = 0.0
beforeEach ->
leftPunchIn = behaviour.punchIn 0.5
rightPunchIn = behaviour.punchIn -0.5
actor = mockActor()
script = mock.testScript()
script.mixxx.engine.getValue = (group, control) ->
expect(group).toBe "[Master]"
expect(control).toBe "crossfader"
xfader
leftPunchIn.enable script, actor
rightPunchIn.enable script, actor
it "does nothing when the crossfader is to the requested side", ->
xfader = -0.75
leftPunchIn.onMidiEvent value: 1
expect(script.mixxx.engine.setValue).not.toHaveBeenCalled()
leftPunchIn.onMidiEvent value: 0
expect(script.mixxx.engine.setValue).not.toHaveBeenCalled()
xfader = 0.75
rightPunchIn.onMidiEvent value: 1
expect(script.mixxx.engine.setValue).not.toHaveBeenCalled()
rightPunchIn.onMidiEvent value: 0
expect(script.mixxx.engine.setValue).not.toHaveBeenCalled()
it "sets the crossfader to the middle and restores otherwise", ->
xfader = 0.75
leftPunchIn.onMidiEvent value: 1
expect(script.mixxx.engine.setValue)
.toHaveBeenCalledWith "[Master]", "crossfader", 0.0
leftPunchIn.onMidiEvent value: 0
expect(script.mixxx.engine.setValue)
.toHaveBeenCalledWith "[Master]", "crossfader", 0.75
xfader = -0.75
rightPunchIn.onMidiEvent value: 1
expect(script.mixxx.engine.setValue)
.toHaveBeenCalledWith "[Master]", "crossfader", 0.0
rightPunchIn.onMidiEvent value: 0
expect(script.mixxx.engine.setValue)
.toHaveBeenCalledWith "[Master]", "crossfader", -0.75
describe 'scratchEnable', ->
actor = null
script = null
scratch = null
beforeEach ->
actor = mockActor()
script = mock.testScript()
scratch = behaviour.scratchEnable 1, 32, 33, 1, 0.4, false
scratch.enable script, actor
it 'enables scratch on button press', ->
expect(script.mixxx.engine.scratchEnable)
.not.toHaveBeenCalled()
scratch.onMidiEvent value: 1
expect(script.mixxx.engine.scratchEnable)
.toHaveBeenCalledWith 1, 32, 33, 1, 0.4, false
it 'disables scratch on button release', ->
expect(script.mixxx.engine.scratchDisable)
.not.toHaveBeenCalled()
scratch.onMidiEvent value: 0
expect(script.mixxx.engine.scratchEnable)
.not.toHaveBeenCalled()
expect(script.mixxx.engine.scratchDisable)
.toHaveBeenCalledWith 1, false
describe 'scratchTick', ->
actor = null
script = null
scratch = null
beforeEach ->
actor = mockActor()
script = mock.testScript()
scratch = behaviour.scratchTick 1, (v) -> v / 2
scratch.enable script, actor
it 'ticks the given deck scratch with the current transform', ->
expect(script.mixxx.engine.scratchTick)
.not.toHaveBeenCalled()
scratch.onMidiEvent value: 64
expect(script.mixxx.engine.scratchTick)
.toHaveBeenCalledWith 1, 32
scratch.onMidiEvent value: 32
expect(script.mixxx.engine.scratchTick)
.toHaveBeenCalledWith 1, 16