This file is part of the Mixco framework.
- View me on a static web
- View me on GitHub
Defines different hardware controls.
{multi} = require 'heterarchy'
{indent, hexStr, assert, factory, xmlTag, joinLn} = require './util'
behaviour = require './behaviour'
{some, extend} = require 'underscore'
exports.MIDI_NOTE_ON = MIDI_NOTE_ON = 0x9
exports.MIDI_NOTE_OFF = MIDI_NOTE_OFF = 0x8
exports.MIDI_CC = MIDI_CC = 0xB
exports.MIDI_PITCHBEND = MIDI_PITCHBEND = 0xE
The midi function returns an object representing a MIDI identifier for a control.
midiId = (message = MIDI_CC, midino = 0, channel = 0) ->
message: message
midino: midino
channel: channel
status: -> (@message << 4) | @channel
configMidi: (depth) ->
"""
#{indent depth}<status>#{hexStr @status()}</status>
#{indent depth}<midino>#{hexStr @midino}</midino>
"""
exports.midiId = midiId
The noteIds and ccIds returns a list with the MIDI messages needed to identify a control based on notes or control signals.
pbIds = -> [ midiId(MIDI_PITCHBEND, 0, arguments...) ]
noteOnIds = -> [ midiId(MIDI_NOTE_ON, arguments...) ]
noteIds = -> [ midiId(MIDI_NOTE_ON, arguments...)
, midiId(MIDI_NOTE_OFF, arguments...) ]
ccIds = -> [ midiId(MIDI_CC, arguments...) ]
exports.pbIds = pbIds
exports.noteOnIds = noteOnIds
exports.noteIds = noteIds
exports.ccIds = ccIds
The event function returns an object representing an script event coming from Mixxx.
exports.event = event = (channel, control, value, status, group) ->
channel: channel
control: control
value: switch status >> 4
when MIDI_PITCHBEND then (value * 128.0 + control) / 128.0
else value
status: status
group: group
message: -> @status >> 4
pressed: status >> 4 != MIDI_NOTE_OFF && value
Base class for all control types.
class exports.Control extends behaviour.Actor
constructor: (@ids = [midiId()], args...) ->
@else = => @_else arguments...
@else.when = => @_elseWhen arguments...
@else_ = @else
super()
if not (@ids instanceof Array)
@ids = ccIds @ids, args...
@_behaviours = []
@_controlRegistry?(@)
The following set of methods define the behaviour of the control. A
control can have several behaviours at the same time. Note that when
passing behaviours to these methods (which is always the last
parameter) you can either pass a Behaviour object or a key and
group strings that will be puto directly into a behaviour.map
.
Thera are three kinds of behaviours we can associate to the control:
-
With does we associate behaviours that are always active, unconditionally.
-
With when we associate behaviours that are only active when some condition is met. This
condition
is a booleanbehaviour.Value
object. -
With else we associate behaviours that are only active when no other when behaviour is active.
does: (args...) -> assert not @_isInit @_behaviours.push @registerBehaviour behaviour.toBehaviour args... this when: (args...) -> assert not @_isInit @_lastWhen = behaviour.when args... @_behaviours.push @registerBehaviour @_lastWhen this _elseWhen: (args...) -> assert @_lastWhen?, "'elseWhen' must be preceded by 'when' or 'elseWhen'" @_lastWhen = @_lastWhen.else.when args... @_behaviours.push @registerBehaviour @_lastWhen this _else: (args...) -> assert @_lastWhen?, "'else' must be preceded by 'when' or 'elseWhen'" @_lastWhen = @_lastWhen.else args... @_behaviours.push @registerBehaviour @_lastWhen @_lastWhen = undefined this init: (script) -> @script = script assert not @_isInit for b in @_behaviours b.enable script, this @_isInit = true shutdown: (script) -> assert script == @script assert @_isInit for b in @_behaviours b.disable script, this @_isInit = false delete @script registerBehaviour: (b) -> b configInputs: (depth, script) -> configOutputs: (depth, script) ->
The setRegistry is a class method that is called by the scripts to automate the registration of the controls. It is called directly by the scripts base class.
setRegistry: (registry) ->
assert not @_controlRegistry? or not registry?
@_controlRegistry = registry
An input control can process inputs from the hardware.
class exports.InControl extends exports.Control
init: (script) ->
super
if @needsHandler()
script.registerHandler \
((args...) => @emit 'event', event args...),
@handlerId()
A input control can be configured with the same type of options that
behaviours can. These are documented in the mixco.behaviour
module. An options chooser syntax is also available.
option: (options...) ->
(@_options ?= []).push options...
for beh in @_behaviours
beh.option options...
this
@property 'options', -> behaviour.makeOptionsChooser @
registerBehaviour: (beh) ->
if @_options?
beh.option @_options...
beh
The control will listen to the --via a handler-- only when the
behaviours need it. If there is only one behaviour in the control and
this can be directly mapped, the midi messages will be connected
directly in the XML file. Otherwise, the control will request to
process the MIDI messages via the script, and it will emit a event
signal when they are received.
needsHandler: ->
@_behaviours.length != 1 or
not @_behaviours[0].directInMapping() or
some @_behaviours[0]._options, (opt) -> not opt.name
handlerId: ->
"x#{@ids[0].status().toString(16)}_x#{@ids[0].midino.toString(16)}"
configInputs: (depth, script) ->
if @needsHandler()
mapping =
group: "[Master]"
key: script.handlerKey @handlerId()
else
mapping = @_behaviours[0].directInMapping()
joinLn(@configInMapping depth, mapping, id for id in @ids)
configInMapping: (depth, mapping, id) ->
"""
#{indent depth}<control>
#{indent depth+1}<group>#{mapping.group}</group>
#{indent depth+1}<key>#{mapping.key}</key>
#{id.configMidi depth+1}
#{indent depth+1}<options>
#{@configOptions depth+2}
#{indent depth+1}</options>
#{indent depth}</control>
"""
configOptions: (depth) ->
if @needsHandler()
"#{indent depth}<script-binding/>"
else if @_behaviours[0]._options?.length > 0
joinLn(
for opt in @_behaviours[0]._options
if opt.name?
"#{indent depth}<#{opt.name}/>")
else
"#{indent depth}<normal/>"
An output control can send data to the hardware.
class exports.OutControl extends exports.Control
constructor: ->
super
@_states =
on: 0x7f
off: 0x00
disable: 0x00
send: (state) ->
@doSend state
states: (states) ->
extend @_states, states
@
doSend: (state) ->
for id in @ids
if id.message != MIDI_NOTE_OFF
if state of @_states
@script.mixxx.midi.sendShortMsg \
id.status(), id.midino, @_states[state]
else
@script.mixxx.midi.sendShortMsg \
id.status(), id.midino, state
init: ->
We should remove the send function before enabling behaviours.
if not @needsSend()
@send = undefined
super
shutdown: ->
@doSend 'disable'
super
needsSend: ->
@_behaviours.length != 1 or
not @_behaviours[0].directOutMapping()
configOutputs: (depth, script) ->
mapping = not @needsSend() and @_behaviours[0].directOutMapping()
if mapping
joinLn(@configOutMapping depth, mapping, id for id in @ids)
configOutMapping: (depth, mapping, id) ->
if id.message != MIDI_NOTE_OFF
options = joinLn [
xmlTag 'minimum', mapping.minimum, depth+1
xmlTag 'maximum', mapping.maximum, depth+1
]
"""
#{indent depth}<output>
#{indent depth+1}<group>#{mapping.group}</group>
#{indent depth+1}<key>#{mapping.key}</key>
#{id.configMidi depth+1}
#{indent depth+1}<on>#{hexStr @_states['on']}</on>
#{indent depth+1}<off>#{hexStr @_states['off']}</off>
#{options}
#{indent depth}</output>
"""
Lets provide a series of factories to make scripts read more declarative.
exports.input = factory exports.InControl
exports.output = factory exports.OutControl
Represents a hardware control that can do both input and output. This is often the case for buttons that have a LED.
class exports.InOutControl extends multi exports.InControl,
exports.OutControl
exports.control = factory exports.InOutControl
Copyright (C) 2013 Juan Pedro Bolívar Puente
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/.