Skip to content

monome/iii

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

22 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

iii

note: This documentation is transitional as we work this all out. It's going to change.

An evolution of capabilities for monome grids, where an interactive scripting environment runs on the device itself.

  • scripting is in Lua (which is familiar to norns and crow)
  • scripts can be uploaded and stored, to be executed on startup
  • in addition to USB-TTY access to the scripting REPL, the device enumerates as USB-MIDI and provides scripting control over all MIDI communication
  • Lua libraries are provided for time-based operations (metros, measurement) and writing arbitrary data to the internal flash (ie: presets or stored sequences)

why do this?

The grid was originally conceived of as "doing nothing by itself" without being connected to a computer running a program. Now, the tiny computer inside the grid (RP2040) is capable enough of doing some interesting things. We're hoping this means in some cases simply requiring less complexity (as in, a specialized eurorack module or DAW plugin or tricky command line framework). It also provides the possibility to connect to less-general-purpose computers (like phones) who prefer MIDI.

That said! The original method of interfacing via norns or serialosc (and any of the various languages and environments) is a fundamentally excellent approach. iii fills a small gap.

The new firmware is capable of toggling between iii and monome/serial modes.

why NOT do this

This is alpha software and such:

  • there are bugs, almost certainly
  • there are limitations we know about
  • there are limitations we haven't yet identified
  • initial documentation and examples are sparse

Furthermore we're not looking at this (yet) as part of the product: as such it has no promised functionality, a warranty, or a timeline to become official.

Practically speaking: the grid is a very constrained device when it comes to user interface. In a typical norns script or Max patch, there is helpful text and interface details that assist with the navigation of a blank grid. An iii script running on the device itself will require some clever design to accommodate the minimal interface.

note: we're working on extending diii (the text interface) to include some amount of interface display and OSC exchange, which may serve some of these needs.

compatibility

editions

  • yes: 2022 and later grids (includes grids one and zero)
  • no: 2020-2021 grids use different microcontroller, hence cannot use this firmware. (they are, however, mechanically compatible so we are considering a PCB upgrade. TBA.)
  • no: all other grids use an FTDI USB Serial chip which means they can't do USB-MIDI.

firmwares

  • Download the most recent firmware for your specific device (Note that grids from 2022 have a different LED driver and hence require a different firmware. Identify the PCB revision by checking the date on the corner).
  • Remove the bottom screws.
  • Locate the golden pushbutton near the USB port. Hold it down while connecting the grid to a computer. This will start the device in bootloader mode.
  • A USB drive will enumerate. Download the appropriate firmware listed below and copy the file to this drive. The drive will unmount immediately upon copying the file (on macOS this may cause a benign alert).
  • Disconnect and put the screws back on (make sure to place the spacers first).

For firmware updates you can use the diii command ^^b to reboot the device into bootloader mode without opening the unit again.

undo

To go back to the original firmware, see these instructions.

modes

The "mode" is indicated by the startup light pattern.

  • particles: this is standard monome/serial mode, compatible with norns, serialosc, ansible, etc.
  • plasma: iii mode with a blank script.
  • something else: previously uploaded iii script.

To change the mode, hold down key 1,1 (top left) while powering up the device.

To force-clear a script, switch into iii mode while holding down the key for two full seconds. (This may be helpful for debugging a locked-up script).

diii

A terminal text interface for iii devices, based on druid. Send commands, get text feedback, and upload scripts. See the source here.

install

Requires python and is installable via pip:

sudo pip3 install monome-diii

(For extended install instructions see druid's install guide and remember to replace monome-druid with monome-diii).

run

ALERT - things get really weird/broken when serialosc (the service / daemon) and diii are running at the same time, or when multiple copies of diii are running. Be aware!

For ease of managing scripts, navigate to the folder with your iii scripts ie:

cd ~/iii

Type diii in a terminal.

If things are working, it will report your device connected.
A few quick commands:

^^p         print script
^^c         clear script
^^z         reboot script
^^r         reboot device
^^b         reboot into bootloader mode

To upload a script:

u step.lua

To re-upload the same script, you can just execute u.

All other commands will be passed to the device's Lua environment and any results will be printed.

To quit, execute q or CTRL-C.

writing scripts

Note that there is no init() or similar. The script is simply run from the start to the end. You'll need to design/plan for whatever initialization you need.

Lua library

help() will display a built-in quick reference which is stored on the device itself (and should match capabilities and syntax).

-- grid
(event) grid(x,y,z)
grid_led_all(z)
grid_led(x,y,z)
grid_led_rel(x,y,z,zmin,zmax)
grid_led_get(x,y)
grid_refresh()
grid_size_x()
grid_size_y()

-- midi
(event) midi_rx(ch,status,data1,data2)
midi_note_on(note,vel,ch)
midi_note_off(note,vel,ch)
midi_cc(cc,val,ch)
midi_tx(ch,status,data1,data2)

-- metro
id = metro.new(callback, time_ms, count_optional)
metro.stop(id)
-- slew
id = slew.new(callback, start_val, end_val, time_ms, quant)
slew.stop(id)

-- pset
table = pset_read(index)
pset_write(index, table)

-- utils
dostring()
get_time()
ps(formatted_string,...)
pt(table_to_print)
clamp(n,min,max)
round(number,quant)
linlin(slo,shi,dlo,dhi,f)
wrap(n,min,max)

grid

function grid(x,y,z) -- callback for grid keypresses. example to print key data:
  ps("grid %d %d %d",x,y,z)
end

LEDs

Drawing queues LED state changes which will be seen with the next grid_refresh(). Coordinates are all 1-indexed (meaning (1,1) is the top-left corner):

  • grid_led_all(z): set all LEDs to z
  • grid_led(x,y,z): set the LED at (x, y) to z
  • grid_led_rel(x,y,z,zmin,zmax): move relative to the last LED command

Additionally, grid_led_get(x,y) returns the level of the LED at (x,y).

midi

midi_rx is the callback for raw bytes sent to the USB-MIDI port. Note that the first byte has the channel and status separated out in advance. Maybe this is weird and we'll change it. We will also add some message type lookups similar to norns.

midi_tx sends bytes over USB-MIDI. midi_note_on, midi_note_off, and midi_cc are helper functions so you don't need to remember the MIDI protocol. We'll add more of these helpers for other MIDI messages.

metro

The system supports fifteen timed metronome objects.

id = metro.new(callback, time_ms, count_optional)

  • id: an alias for our metronome
  • callback: a function called on every metronome tick, passing the current stage with each execution
  • time_ms: the interval (in milliseconds) to execute each tick
  • count: (optional) the number of ticks to execute
    • if count is omitted or set to -1, the metro will repeat indefinitely

For example:

example_metro = metro.new(
  function(stage)
    print(stage)
  end,
  1000, -- interval of 1000ms
  2 -- execute the callback twice
)
print(example_metro)

Running the code above will result in:

1
2

Note that when a metro is created, it is automatically started.

Stop a running metro with metro.stop(id).

slew

Use slew to smoothly count between two values over a specified time.

id = slew.new(callback, start_val, end_val, time_ms, quant)

  • id: an alias for our slew
  • callback: a function called on every increment, passing the current value with each execution
  • start_val: our starting value
  • end_val: our destination value
  • time_ms: how many milliseconds the journey from our starting value to the destination value will take
  • quant: (optional) the granularity between each interim value
    • if quant is omitted, values will change by 1

For backwards movement, make start_val greater than end_val.

Stop a running slew with slew.stop(id).
Stop all running slews with slew.stopall().

See slew.lua for an example.

flash

4k sized blocks can be read, written, and cleared. Each block is referenced with an index. We haven't defined an upper limit as the user flash occupies the end of the memory map. (We'll clarify this later).

The flash can be used arbitrarily, though we expect it to be most useful for presets or similar.

cmd = "print('hello world i am in the flash!')"
flash_write(0,cmd) -- this will now survive power cycling
(...)
cmd = flash_read(0)
dostring(cmd) -- prints the message!

TODO: we need to add a table serializer. Because flash is just blocks of bytes (text), ie:

x = {20,22,26,29}
preset = table_serialize(x) -- preset now is the string "{20,22,26,29}" 
flash_write(0,preset)
(...)
x = dostring(flash_read(0)) -- recall a preset

presets

Scripts can store and recall tables of data into/from the RP2040's flash storage, using a simple 'preset' mechanism:

  • pset_write(n,table): writes table to flash position n
  • pset_read(n): returns the table stored at flash position n

as of release 250201

The pset functions need a bit more design work -- these slots have no awareness of which script wrote them or if the tables will be valid for the current script.

Each flash slot (there are 256 of them) can be cleared with flash_clear(id).

To clear every flash slot, run this command:

for i=1,256 do flash_clear(i) end

utils

device_id() returns the string name of the connected device.

dostring(cmd) executes the string cmd. Be careful.

get_time() returns time since boot in milliseconds. Helpful for measuring intervals.

ps(formatted_string,...) is a helper to give printf capabilities, For example:

ps("i am %s and i like the number %d", "awake", 3) -- "i am awake and i like the number 3"

pt(table) attempts to print a table nicely.

clamp(n,min,max) clamps value n between a min and max.

round(number,quant) rounds to a multiple of a number with quant precision.

linlin(slo,shi,dlo,dhi,f) linearly maps value f from one range to another range.

wrap(n,min,max) wraps integer n to a positive min/max range.

notes

  • Lua is 1-indexed, so grid coordinates start at 1,1, and metro indices also start at 1.
  • Script size is currently limited to 32k. (This could change if needed).

TODO

contributing

Small Lua tests and docs fixes welcome. Also suggestions for inclusion in the core scripting library (which is compiled into the firmware).

Discussion happens at the repository.

note: this repository is not for the firmware itself, which we have not yet determined how / if to license.