Link search Menu Expand Document

midi

control

Syntax Description
my_midi = midi.connect(n) Assign the connected MIDI device at this port to a script, defaults to port 1 unless n is specified
my_midi.event = function(data) User script callback when this connected device receives MIDI message data
my_midi:send(data) Send MIDI data to this connected device, eg. {144,60,127} = note on event for note 60 with velocity 127
my_midi:note_on(note, vel, ch) Send note on event to this connected device, unspecified velocity defaults to 100 and unspecified channel defaults to 1
my_midi:note_off(note, vel, ch) Send note off event to this connected device, unspecified velocity defaults to 100 and unspecified channel defaults to 1
my_midi:cc(cc, val, ch) Send cc event to this connected device, unspecified channel defaults to 1
my_midi:pitchbend(val, ch) Send pitchbend message to this connected device, unspecified channel defaults to 1
my_midi:key_pressure(note, val, ch) Send key pressure event to this connected device, unspecified channel defaults to 1
my_midi:channel_pressure(val, ch) Send channel pressure event to this connected device, unspecified channel defaults to 1
my_midi:program_change(val, ch) Send program change event to this connected device, unspecified channel defaults to 1
my_midi:start() Send start event to this connected device
my_midi:stop() Send stop event to this connected device
my_midi:continue() Send continue event to this connected device
my_midi:clock() Send clock event to this connected device (nb. norns global clock can transmit MIDI clock without scripting)
my_midi:song_position(lsb, msb) Send song position event to this connected device
my_midi:song_select(val) Send song select event to this connected device
midi.remove(id) User script callback when any midi device is removed, passes ID of the removed device
midi.to_msg Convert MIDI data (bytes) to specific messages, eg. channel, velocity, note, type
midi.to_data Convert MIDI messages to data (bytes)

query

Syntax Description
midi.devices Returns any presently-connected MIDI devices : table
midi.devices[x] Returns information about this presently-connected MIDI device (.name, .dev, .id) : table
midi.vports Returns all remembered MIDI devices : table
midi.vports[x] Returns information about this remembered MIDI device (.name and control functions) : table

example: targeting a selectable device

function init()
  midi_device = {} -- container for connected midi devices
  midi_device_names = {}
  target = 1
  key3_hold = false
  random_note = math.random(48,72)

  for i = 1,#midi.vports do -- query all ports
    midi_device[i] = midi.connect(i) -- connect each device
    table.insert( -- register its name:
      midi_device_names, -- table to insert to
      "port "..i..": "..util.trim_string_to_width(midi_device[i].name,80) -- value to insert
    )
  end

  params:add_option("midi target", "midi target",midi_device_names,1)
  params:set_action("midi target", function(x) target = x end)
end

function enc(n,d)
  if n == 2 then
    if #midi_device > 0 then
      params:delta("midi target",d)
      redraw()
    end
  end
end

function key(n,z)
  if n == 3 then
    if z == 1 then
      midi_device[target]:note_on(random_note) -- defaults to velocity 100 on ch 1
      key3_hold = true
      redraw()
    elseif z == 0 then
      midi_device[target]:note_off(random_note)
      random_note = math.random(50,70)
      key3_hold = false
      redraw()
    end
  end
end

function redraw()
  screen.clear()
  screen.move(0,10)
  screen.text(params:string("midi target"))
  screen.move(0,30)
  if not key3_hold then
    screen.text("press K3 to send note "..random_note)
  else
    screen.text("release K3 to end note "..random_note)
  end
  screen.update()
end

example: targeting multiple devices

s = require 'sequins'
MU = require 'musicutil'

engine.name = 'PolySub' -- just to auralize sequences

function init()
  -- set some engine params:
  engine.ampRel(0.1)
  engine.ampAtk(0.005)
  engine.hzLag(0)

  target_device_count = 3 -- target 3 connected devices, feel free to change!

  scale = "Major Pentatonic" -- for scale generation

  midi_device = {} -- container for connected midi devices
  midi_device_names = {} -- container for their names

  -- container for individual sequence parameters
  sequence = {
    target = {}, -- which MIDI port to target
    notes = {}, -- the note pool for the sequence
    sync_val = {}, -- the clock sync value
    clock = {}, -- the iterating clock
    selected = 1
  }

  for i = 1,target_device_count do
    sequence.target[i] = i -- target device slot (i)
    -- MU.generate_scale(base_note, scale_name, octaves)
    local this_scale = MU.generate_scale(50 - (math.random(-2,1) * 12), scale, 2)
    sequence.notes[i] = s.new(this_scale) -- build a sequins of generated notes
    sequence.sync_val[i] = 3/math.random(11) -- randomly assign tick value per sequence
  end

  refresh_midi_devices()

  params:add_separator("multiple midi device example")

  for i = 1,target_device_count do -- for each MIDI target...
    params:add_group("output "..i,4)

    -- create a parameter to change its target:
    params:add_option("target "..i, "device", midi_device_names, i)
    params:set_action("target "..i, function(x) sequence.target[i] = x end)
    -- and channel and velocity value
    params:add_number("channel "..i, "channel", 1, 16, 1)
    params:add_number("velocity "..i, "velocity", 0, 127, 63)

    -- sequins step size allows skipping within sequence
    params:add_number("sequins step size "..i, "sequins step size", -10, 10, 1)
    params:set_action("sequins step size "..i, function(x) sequence.notes[i]:step(x) end)
  end

  -- sequences start off
  transport_state = "off"

  -- common redraw mechanism
  redraw_timer = metro.init(draw_screen,1/15,-1)
  screen_dirty = true
  redraw_timer:start()

end

function start_sequences()
  if transport_state == "off" then
    transport_state = "on"
    for i = 1,#sequence.target do
      -- for each sequence, create a clock:
      sequence.clock[i] = clock.run(iterate_sequence, i)
    end
  end
end

function stop_sequences()
  if transport_state == "on" then
    transport_state = "off"
    for i = 1,#sequence.target do
      -- for each sequence, cancel its clock:
      clock.cancel(sequence.clock[i])
      -- reset the sequins index:
      sequence.notes[i].ix = 1
      -- stop the engine:
      engine.stop(i)
    end
  end
end

function iterate_sequence(i)
  while true do
    clock.sync(sequence.sync_val[i])
    local played_note = sequence.notes[i]()
    local velocity = params:get("velocity "..i)
    local channel = params:get("channel "..i)

    midi_device[i]:note_on(played_note, velocity, channel)
    engine.start(i,MU.note_num_to_freq(played_note))

    clock.sleep(0.1) -- after 100 ms, perform note off:
    midi_device[i]:note_off(played_note, 0, channel)
    engine.stop(i)

    screen_dirty = true
  end
end

function refresh_midi_devices()
  for i = 1,#midi.vports do -- query all ports
    midi_device[i] = midi.connect(i) -- connect each device
    table.insert( -- register its name:
      midi_device_names, -- table to insert to
      "port "..i..": "..util.trim_string_to_width(midi_device[i].name,40) -- value to insert
    )
  end
end

function enc(n,d)
  if n == 2 then
    -- change selected sequence
    sequence.selected = util.clamp(sequence.selected+d, 1, target_device_count)
  elseif n == 3 then
    -- change selected sequence's step size
    params:delta("sequins step size "..sequence.selected, d)
  end
  screen_dirty = true
end

function key(n,z)
  if n == 3 and z == 1 then
    -- toggle sequence on/off
    if transport_state == "off" then
      start_sequences()
    else
      stop_sequences()
    end
    screen_dirty = true
  end
end

function draw_screen()
  if screen_dirty then
    redraw()
    screen_dirty = false
  end
end

function redraw()
  screen.clear()
  for i = 1,target_device_count do
    screen.level(sequence.selected == i and 15 or 5)
    screen.move(0,10*i)
    screen.text("sequence "..i..": "..sequence.notes[i].data[sequence.notes[i].ix])
    screen.move(128,10*i)
    screen.text_right("step size: "..params:get("sequins step size "..i))
  end
  screen.move(128,58)
  screen.level(15)
  screen.text_right("K3: turn all "..(transport_state == "on" and "off" or "on"))
  screen.update()
end

description

Connect a script to MIDI hardware. Provides MIDI message and event communication between norns and external MIDI boxes, dongles, and hubs. When a MIDI device is connected to norns, its identity is stored in two tables: .vports and .devices

The .vports table reflects the MIDI devices norns has stored under SYSTEM > DEVICES > MIDI. It can therefore reflect devices not currently connected.

The .devices table reflects presently-connected MIDI devices. The general order is reflected under SYSTEM > DEVICES > MIDI > port – my_midi.devices[1].name will always return virtual, as its the first MIDI connection established by norns on boot. Subsequent device connections will consume sequential indices. However, if you remove and connect a device many times, each re-connect will increment the index. So, it is possible for a device to show up as the third item selectable under SYSTEM > DEVICES > MIDI > port, but it might occupy the fifth .device index.