Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
with
97 additions
and 1 deletion.
- +2 −1 framer/Framer.coffee
- +53 −0 framer/MIDIControl.coffee
- +42 −0 framer/MIDIInput.coffee
@@ -0,0 +1,53 @@ | ||
{_} = require "./Underscore" | ||
{BaseClass} = require "./BaseClass" | ||
{Events} = require "./Events" | ||
{MIDIInput} = require "./MIDIInput" | ||
|
||
Events.MIDIControlValueChange = "MIDIControlValueChange" | ||
|
||
class MIDIControl extends BaseClass | ||
|
||
@define "min", @simpleProperty("min", 0) | ||
@define "max", @simpleProperty("max", 127) | ||
@define "control", @simpleProperty("control", null) | ||
@define "channel", @simpleProperty("channel", null) | ||
# Not supported yet, needs MIDIInput that opens all inputs | ||
# @define "source", @simpleProperty("source", null) | ||
|
||
constructor: (options={}) -> | ||
super options | ||
|
||
MIDIInput.enabled = true | ||
MIDIInput.onCommand (timeStamp, data) => | ||
[b1, b2, b3] = data | ||
|
||
# Mask the bytes to get the info we want | ||
command = b1 & 0xf0 | ||
channel = (b1 & 0x0f) + 1 # 1-16 | ||
data1 = b2 & 0x7f | ||
data2 = b3 & 0x7f | ||
|
||
# 0xb0 control change | ||
# 0x90 note on | ||
# 0x80 note off | ||
|
||
return unless command in [0xb0, 0x90, 0x80] | ||
return if @channel? and @channel isnt channel | ||
return if @control? and @control isnt data1 | ||
|
||
info = | ||
channel: channel | ||
control: data1 | ||
|
||
if command in [0x90, 0x80] | ||
info = _.defaults info | ||
type: "note" | ||
|
||
@emit(Events.MIDIControlValueChange, @_modulate(data2), info) | ||
|
||
_modulate: (value) -> | ||
Utils.modulate(value, [0, 127], [@min, @max]) | ||
|
||
onValueChange: (cb) -> @on(Events.MIDIControlValueChange, cb) | ||
|
||
exports.MIDIControl = MIDIControl |
@@ -0,0 +1,42 @@ | ||
{BaseClass} = require "./BaseClass" | ||
{Events} = require "./Events" | ||
|
||
Events.MIDICommand = "midiCommand" | ||
|
||
class MIDIInput extends BaseClass | ||
|
||
@define "enabled", | ||
get: -> @_input or @_request | ||
set: (value) -> | ||
return unless value != @enabled | ||
return @_requestRejected() if not navigator.requestMIDIAccess | ||
if value | ||
@_request = navigator.requestMIDIAccess().then @_requestResolved, @_requestRejected | ||
else | ||
@_input?.close() | ||
@_request = null | ||
@_input = null | ||
|
||
# Success handlers | ||
|
||
_requestResolved: (access) => | ||
# Pick the last one | ||
access.inputs.forEach (input) => | ||
@_input = input | ||
@_input.onmidimessage = @_onmidimessage | ||
|
||
# Failure handlers | ||
|
||
_requestRejected: (error) => | ||
throw Error "Requesting MIDI access failed: #{error ? "not supported by browser"}" | ||
|
||
# Event handlers | ||
|
||
_onmidimessage: (message) => | ||
@emit(Events.MIDICommand, message.timeStamp, message.data) | ||
|
||
# Event shortcuts | ||
|
||
onCommand: (cb) -> @on(Events.MIDICommand, cb) | ||
|
||
exports.MIDIInput = new MIDIInput |