From 186fa728036709dd1ab9c60d888d3a9468f8769e Mon Sep 17 00:00:00 2001 From: ezra buchla Date: Wed, 27 Sep 2023 14:55:37 -0700 Subject: [PATCH] Norns converged update (#1706) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add PSET number to PSET actions + add delete action (#1544) * add PSET number to callbacks + add delete callback * fix passing pset_number in delete action * Revert "fix passing pset_number in delete action" This reverts commit 7f98bb29afc67449e8f1c3471bdc80e0878c8a61. * fix pset_number in read action * clean up formatting * remove unnecessary nil checks * update paramset docs * fix parameter name passing to params:delete * add pset_number to docs * releases.txt * remove nonexistent post_filter_fc_mod command from softcut param factory * Update pmap.lua * typo fix (#1560) * fix registration for screen_display_image_region * Update readme.md * add note to readme regarding release flag * use abl_link C API for link clock * init session state * add param separators to lookup table (#1539) * add param separators to lookup table for hide/show * add unnamed failsafe * add visibility lookup for strings * fix name forcing * cleanup DSP 'lab work' folder (#1580) * add script-definable action for when clock tempo changes (#1575) * add clock.temo_changed callback * clean up naming * reset handler when clocks are cleaned up * fold in artem's feedback * remove 'source' pass, remove redundant nils * params: align 'add_separator' and 'add_group' flow with other paramtypes (#1584) * protect against casual param naming * protect groups, too * add error message * change separator and group addition * specify overwrite conditions * overwrite flag allows for continuity for param count * allow hidden param to be registered * add parameter-based lfo scripting library (#1585) * initial upload + unload lfo's after use * default = 'param action' and track fn mapping * add function type lookup table * add ldoc notes * bars -> clocked * no spaces in param IDs * remove 'frm', not used * rename library to 'param_lfo' * rename to 'param-lfo' * p_lfo -> plfo just cosmetic, but looks way better * apply menu password entry to SMB + hotspot (#1570) * unify smb + hotspot password change * Update link * Revert "Update link" This reverts commit 55fff78e3b66220258e30b9d45a8752b23d85982. * Revert "Merge branch 'main' into password-helper" This reverts commit 66fe7983aad172134a1409259d612a6f35976bb2, reversing changes made to 651d7c08ce8fe96f1a300c9756318a1004ceab15. * Revert "Revert "Merge branch 'main' into password-helper"" This reverts commit b797f04ccac2f95bbee8db9498c866ba09fa6d55. * maiden inaccessible * robust message for hotspot password change * fix to add midi data when device is removed (issue #1557) (#1562) * typo fix * fix to add midi data when device is removed (issue #1557) code fixed and doc info updated for issue #1557: https://github.com/monome/norns/issues/1557 * Place downbeats correctly. Without this change, a swing of 66 will shift some events early and some events late from where they would otherwise be. With this change, a swing of 66 leaves some beats alone, and shifts other beats late from where they would otherwise be. Example to evaluate change, run as a norns script: ``` engine.name = "PolyPerc" local lattice = require("lattice") function init() l = lattice:new() p1 = l:new_pattern{ enabled = true, division = 1/4, action = function(t) print("q", t) engine.hz(440) end, } p2 = l:new_pattern{ enabled = true, division = 1/8, swing = 66, action = function(t) print("e", t) engine.hz(660) end, } l:start() end ``` In this example, the expected behavior is that two events (440 and 660) fall on the "down" beat, and then one (660) falls on the "up" beat, but with a "triplet feel". Without the change we get the "eighth notes" falling before and after the quarter note, but not on it. * Revert "add parameter-based lfo scripting library (#1585)" (#1588) This reverts commit 221531aca2cc9191620d4cdba565ca97b1485cbe. * Revert "fix to add midi data when device is removed (issue #1557) (#1562)" (#1589) This reverts commit a808394ff63648af4a607d8bec04b9cde9c4328a. * add 'lfo' (#1591) * add lib/lfospec a general-purpose scripting library for establishing LFOs, with optional parameter UI control * add lfospec attributions * protect against nil IDs * add note about clocks + start/stop with parameter menu * update params per entry * Use lattice for LFOs, so they share clocks, and allow setting ppqn * attribution and unused var cleanup * change name and fold in feedback * register 'norns.lfo' table, manage from script.lua * Update script.lua * check for nil norns.lfo * remove note about clocks since using lattice, there's no worry about counting clocks * add API text to LFO:add okay, this is the last touch! sorry for all the final countdown updates :grimacing: Co-authored-by: Naomi Seyfer * execute global Midi.remove callback (#1590) this runs the global, customizable midi device removal callback when a device is unplugged (if the device is registered by the norns midi system), in addition to and without affecting the per-device removal callback (if defined.) alternative to PR #1562, addressing issue #1557 * 220802 * releases.txt * hotfix: lfo `:add` (#1593) * hotfix: lfo `:add` gah! the `:add` method wasn't properly invoking the `.new` function * Update lfo.lua * rest of the lfo hotfix (#1594) * hotfix: lfo `:add` gah! the `:add` method wasn't properly invoking the `.new` function * Update lfo.lua * Update lfo.lua * releases.txt * gamepad support (#1439) * basic gamepad support w/ whitelist * basic support for gamepad in global menu * fix missing clear callback * handling of analog joysticks & numeric dpads * optim: faster lookup of event code 2 axis * (cont.) * fix bad name for axis properties * bug fixes, catchall axis callback vs dpad/apad * rename `apad` into `astick` (analog stick) * mark dpad as naalog for bufalo model, fix typo * do not debug log * do not add separators to PSET file (#1598) * fixed screen curve param descriptions (#1603) * Update paramset.lua (#1605) * expand acceptable accum CC values (#1606) a tiny change that allows controllers to send any CC value above 64 for "up" and any below for "down". previously it had to just be 65 and 63 which isn't supported by all controllers. * add TAPE previewing to fileselect (#1607) * add previewing to fileselect * working * stop previewing on key and always on left scroll * Tweaks to the keyboard (#1611) * handle osc messages for the keyboard * change parameters with +/- keys * allow holding key to scroll menu * toggle menu with F5 key * goto menu if F1-4 are pressed * add version error (#1613) * add previewing to fileselect * working * stop previewing on key and always on left scroll * add version error message * working * address issue #1612 (#1617) adds a call to the script-defined `clock.tempo_change_handler` function whenever the `clock_tempo` parameter is changed in the parameters UI from an external source * work on lattice v2 (#1616) * work on v2 * preserve 'new_pattern' for backwards-compatibility * quarter note is good actually lol * incorporate tyler's comments * docblock, comments, and semantic changes Co-authored-by: Tyler Etters * fix `_menu.keychar` not getting called (#1619) * prevent global menu shortcut messing up w/ sub-menu's (#1620) * fix undefined `gamepad` when `script.clear` gets called (#1621) * fix: document sprocket ordering (#1625) * update lfo lib to use lattice sprockets (#1626) * add password length failsafes for WPA-PSK (#1627) WPA-PSK requires a sequence between 8 and 63 ASCII characters, so if a user uses this prompt to change their password to something shorter, then they won't be able to access hotspot (due to the conveniences added by https://github.com/monome/norns/pull/1570). to help guardrail, this commit adds: - an on-screen `textentry` check which shows a countdown to 8 characters and a warning if the password goes beyond 63 - a character count check to `m.passdone`, which will only change all passwords if the string length is >= 8 and < 64, otherwise it prints warnings to maiden that the password has not been changed * 221214 * releases.txt * update.sh fix * update docs * releases.txt * `new_pattern` was missing return keyword (#1629) * lfo fixes + improvements (#1630) fixed locally global variables for `scaled_min` / `scaled_max`, `mid`, and `rand_value`, which interfered with square and random waves when multiple LFOs were running with different min/max values. added `mid: rising` and `mid: falling` as options for the `reset_target`, which allows more control over how the reset affects the starting value * add osc.cleanup() to script.lua (#1643) * allow sending midi clock to all devices simultaneously (#1642) * Add files via upload * toggles for targeting device clock out builds out @tehn's feedback from #1642 : - any currently-ported midi device will populate in the 'midi clock out' section of 'PARAMS > CLOCK' - each visible entry has a toggle to receive norns clock - toggles get saved/restored as part of `system.state` during clean reboots follow the approach brian outlined actually saves us from doing any if's during each clock tick! * longer short name cleaned up formatting to match the SYSTEM > DEVICES > MIDI syntax, which allows for up to 20 characters to display comfortably before aliasing Co-authored-by: dan derks * fix lattice transport value getting increased in steps of 5 (#1638) * fix lattice ppqn resolution divded by 5 * order sprockets once per pulse * Make maiden-repl compile on macOS 13.1 Ventura (#1645) * Change wrap to use math instead of iteration, and happen in constant time (#1577) * clean up device callbacks (#1646) * remove unnecessary _menu.rebuild_params() (#1647) this gets called elsewhere throughout the stack (including MIDI device add/remove), which makes this unnecessary -- it also seems to cause race-condition conflicts as the rebuild attempts to index parameters which don't yet exist * Update device_midi.c blind fix attempt for dropped sysex bytes * fix typo (want stop byte not start byte) * Add UK keyboard option. (#1651) * Remove error from unknown key press. (#1654) * fix argument indices for buffer_clear_region_channel (#1656) this is a blind fix attempt for issue #1652 * lua NRT processing function (#1634) * not working * still not working * first draft * fix typo * refactor: attempt to pass buffer data rather than function * catch silly typo * process_chunk should match OscInterface.cpp * working! refactor to allow longer sections to be processed * fix errors * catch typos * refactor to use shared memory * attempt to not cause stack overflow, fix wscript so norns ./wafs correctly * catch typo, add debug prints * attempt to prevent poke failing to open shared memory * strange not-working place * catch small typo, add debug print * attempt to prevent error on second call to process * working * refactor to use clocks to chunk up softcut_process * fix weaver bugs, change lua API * fix: catch typo * Update docs for screen.fill() and screen.stroke(). (#1660) * new crow4 features on norns (#1662) * run ldoc * regenerate docs * better separator labeling in core params (#1665) * better separator labeling * Update audio.lua * reduce 'send_midi_clock' var scope (#1666) during release testing, i realized that after my ~40th script load, `clock.sync` calls were off by a tenth of a beat, compounding as i continued to load scripts. when i verified #b932b0d sparked the issue's emergence, i realized that the `send_midi_clock` table was being added to each time `clock.add_params()` was called, but never emptied out. so, eventually, the clock was doing 24ppqn calls on a table with *tons* of entries. i guess nice to accidentally stress-test that a lot of devices could receive MIDI clock before timing issues emerged? anyway, fixed now! * Implement character/key conversion (#1659) * Implement character/key conversion. * Remove char_to_code(). * [mergeable] rework `gamepad` to handle more edge-cases (#1624) * rework gamepad to handle more edge-cases * woops * SDL-format GUID generation for HID devices * (cont., missed this one binding) * use this new GUID as id in known controller models table * add guid in controller conf, some 8bitdo masquerade as xbox 360 * log guid * actually use guid in `gamepad.process` callback * analog btn support, fix std order X axis, no meta btn state * fix value of sign in gamepad.axis() when analog button * remove dead code * re-introduce denoize for analog buttons, comments * call gamepad.analog after computing state * on sensors, negative half is generally 1+ bigger than positive one * tweak submenu integration * disable verbose mode * prevent trigger script cb if in menu, add _menu.analog * prefix all new `menu` callbacks w/ `gamepad_` * rounding the half-reso may be counter-intuitive, document instead * sometimes lower half bigger, sometimes upper one... * fix actual resolution for this controller * be resilient against "lean" config files (w/ empty maps ommited) * add system/settings menu, move items, add battery_warning (#1668) * add system/settings menu, move items, add battery_warning * yikes booleans * fixes * export png (#1669) * changelog * norns.expand_filesystem() (#1670) * Update norns.lua * Update norns.lua * releases.txt * allow user to add gamepad profiles without tempering w/ core (#1671) * allow user to add gamepad models without tempering w/ core * more explicit var names * Revert "lua NRT processing function (#1634)" (#1673) This reverts commit 0c06b09726a052ced8fdc8e9611b6e1d114285f5. * changelog * releases.txt * Update settings.lua (#1675) * revert export_png and rename export_screenshot (#1676) * changelog, version, releases.txt * releases.txt * fix: softcut.voice_sync order documentation softcut's `voice_sync` command has its arguments reversed in the API docs * add fade_time correction, as well! * fix fade_time parameter names * add filters to fileselect (#1678) * add filters to fileselect * Update fileselect.lua * remove whitespace * fix midi clock double-tap (#1680) * changelog, version * update.sh ancient typo * Update releases.txt * fix #1681 (#1682) ah! i had totally spaced on the auto-shortening of long filenames by appending '...', which confused the "is this a file or is this a folder?" mechanism! this is now fixed + and i will ask for community testing! * 230614 * changelog * releases.txt beta * releases.txt stable * Return correct 7th chords from musicutil.generate_chord_scale_degree (#1688) * Support half-diminished chords and correct scale degrees * Add remaining missing 7th chords to roman notation: dominant, minor major, augmented major * Use M decorator for Augmented Major 7, expand docs * More docstring tweaks * Update scale chord tables for Augmented Major 7 (27) * Add new glyphs with FontForge instead of PixelForge * Add alt_names Min7b5 and Maj7#5 for consistency w/teletype * Ensure that disk field can represent size of larger sd cards (#1690) Co-authored-by: Chris Aquino * lfo library: v2 (#1692) * 'saw' -> 'tri', add 'up' and 'down', fix init * build params from lfo spec * add phase * tune 'RESTART' (#1695) * midi mapping touchups (#1696) * fix user gamepad profile lookup + new model (#1697) * fix: user-defined gamepad profile lookup * new gamepad model: Retrolink B00GWKL3Y4 * more info * disable hciuart in update (#1700) * make variable y in util.wrap local (#1704) * fix: debounce tape preview in fileselect (closes #1628) (#1703) --------- Co-authored-by: dan derks Co-authored-by: brian crabtree Co-authored-by: zbs <86270534+zjb-s@users.noreply.github.com> Co-authored-by: Jonathan Snyder <52048666+jaseknighter@users.noreply.github.com> Co-authored-by: Greg Wuller Co-authored-by: Tyler Etters Co-authored-by: Artem Popov Co-authored-by: Naomi Seyfer Co-authored-by: Jordan Besly <11557146+p3r7@users.noreply.github.com> Co-authored-by: Tom Waters Co-authored-by: Rylee Alanza Lyman <46907231+ryleelyman@users.noreply.github.com> Co-authored-by: Zack Co-authored-by: Tyler Etters Co-authored-by: kasperbauer Co-authored-by: Anders Östlin Co-authored-by: Nik Silver Co-authored-by: trent Co-authored-by: brian crabtree Co-authored-by: Michael Dewberry Co-authored-by: Chris Aquino Co-authored-by: Chris Aquino --- .gitmodules | 3 - crone/dsp/workbench/dbfs_meter.py | 48 -- crone/dsp/workbench/requirements.txt | 8 - crone/src/OscInterface.cpp | 6 +- doc/index.html | 26 +- doc/modules/Lattice.html | 213 ++++--- doc/modules/arc.html | 9 +- doc/modules/audio.html | 11 +- doc/modules/clock.html | 80 ++- doc/modules/controlspec.html | 9 +- doc/modules/core.crow.public.html | 5 +- doc/modules/core.crow.quote.html | 5 +- doc/modules/crow.html | 5 +- doc/modules/encoders.html | 5 +- doc/modules/engine.html | 5 +- doc/modules/gamepad.html | 315 +++++++++++ doc/modules/grid.html | 59 +- doc/modules/hid.html | 5 +- doc/modules/keyboard.html | 5 +- doc/modules/lib.BeatClock.html | 7 +- doc/modules/lib.EnvGraph.html | 23 +- doc/modules/lib.FilterGraph.html | 11 +- doc/modules/lib.Graph.html | 29 +- doc/modules/lib.MusicUtil.html | 19 +- doc/modules/lib.UI.html | 23 +- doc/modules/lib.container.defaulttable.html | 5 +- doc/modules/lib.container.deque.html | 5 +- doc/modules/lib.container.observable.html | 5 +- doc/modules/lib.container.watchtable.html | 5 +- doc/modules/lib.container.weaktable.html | 5 +- doc/modules/lib.elca.html | 7 +- doc/modules/lib.er.html | 7 +- doc/modules/lib.fileselect.html | 5 +- doc/modules/lib.filters.html | 5 +- doc/modules/lib.formatters.html | 5 +- doc/modules/lib.intonation.html | 21 +- doc/modules/lib.lfo.html | 320 +++++++++++ doc/modules/lib.listselect.html | 5 +- doc/modules/lib.pattern.html | 5 +- doc/modules/lib.sequins.html | 124 +---- doc/modules/lib.tabutil.html | 49 +- doc/modules/lib.test.luaunit.html | 5 +- doc/modules/lib.textentry.html | 5 +- doc/modules/lib.textentry_kbd.html | 5 +- doc/modules/lib.timeline.html | 270 +++++++++ doc/modules/lib.util.html | 88 +-- doc/modules/lib.voice.html | 5 +- doc/modules/metro.html | 5 +- doc/modules/midi.html | 15 +- doc/modules/norns.html | 11 +- doc/modules/osc.html | 35 +- doc/modules/params.control.html | 5 +- doc/modules/paramset.html | 192 ++++--- doc/modules/poll.html | 5 +- doc/modules/screen.html | 215 ++++++- doc/modules/script.html | 11 +- doc/modules/softcut.html | 108 +++- lua/core/arc.lua | 6 + lua/core/audio.lua | 2 +- lua/core/clock.lua | 67 ++- lua/core/gamepad.lua | 494 ++++++++++++++++ .../gamepad_model/ibufalo_classic_usb.lua | 37 ++ lua/core/gamepad_model/index.lua | 24 + lua/core/gamepad_model/retro_controller.lua | 32 ++ lua/core/gamepad_model/xbox_360.lua | 55 ++ lua/core/grid.lua | 10 +- lua/core/hid.lua | 33 +- lua/core/hid_device_class.lua | 24 +- lua/core/keyboard.lua | 70 ++- lua/core/keymap/uk.lua | 116 ++++ lua/core/menu.lua | 97 +++- lua/core/menu/home.lua | 4 + lua/core/menu/mix.lua | 20 + lua/core/menu/params.lua | 97 +++- lua/core/menu/reset.lua | 2 +- lua/core/menu/restart.lua | 3 +- lua/core/menu/settings.lua | 93 ++++ lua/core/menu/system.lua | 12 +- lua/core/menu/tape.lua | 14 +- lua/core/midi.lua | 15 + lua/core/norns.lua | 21 +- lua/core/osc.lua | 7 + lua/core/params/control.lua | 8 + lua/core/params/group.lua | 7 +- lua/core/params/number.lua | 8 + lua/core/params/option.lua | 8 + lua/core/params/separator.lua | 5 +- lua/core/paramset.lua | 189 +++++-- lua/core/pmap.lua | 8 +- lua/core/screen.lua | 34 +- lua/core/script.lua | 14 +- lua/core/softcut.lua | 9 +- lua/core/startup.lua | 1 + lua/core/state.lua | 15 +- lua/core/wifi.lua | 14 +- lua/lib/fileselect.lua | 133 +++-- lua/lib/lattice.lua | 190 ++++--- lua/lib/lfo.lua | 525 ++++++++++++++++++ lua/lib/musicutil.lua | 167 ++++-- lua/lib/sequins.lua | 313 +++++++---- lua/lib/timeline.lua | 234 ++++++++ lua/lib/util.lua | 46 +- maiden-repl/CMakeLists.txt | 18 +- maiden-repl/README.md | 2 +- matron/src/clocks/clock_link.cc | 42 +- matron/src/device/device_hid.cc | 31 ++ matron/src/device/device_hid.h | 3 + matron/src/device/device_midi.cc | 2 +- matron/src/event_types.h | 2 +- matron/src/hardware/screen.cc | 40 +- matron/src/hardware/screen.h | 1 + matron/src/weaver.cc | 39 +- readme-setup.md | 2 +- readme.md | 6 +- releases.txt | 12 +- resources/04B_03__.TTF | Bin 21028 -> 21228 bytes third-party/link | 2 +- third-party/link-c | 1 - third-party/wscript | 3 +- update/changelog.txt | 74 +++ update/update.sh | 7 + update/version.txt | 2 +- 122 files changed, 4982 insertions(+), 1082 deletions(-) delete mode 100644 crone/dsp/workbench/dbfs_meter.py delete mode 100644 crone/dsp/workbench/requirements.txt create mode 100644 doc/modules/gamepad.html create mode 100644 doc/modules/lib.lfo.html create mode 100644 doc/modules/lib.timeline.html mode change 100644 => 100755 lua/core/clock.lua create mode 100644 lua/core/gamepad.lua create mode 100644 lua/core/gamepad_model/ibufalo_classic_usb.lua create mode 100644 lua/core/gamepad_model/index.lua create mode 100644 lua/core/gamepad_model/retro_controller.lua create mode 100644 lua/core/gamepad_model/xbox_360.lua mode change 100644 => 100755 lua/core/grid.lua mode change 100644 => 100755 lua/core/hid.lua create mode 100644 lua/core/keymap/uk.lua mode change 100644 => 100755 lua/core/menu/params.lua create mode 100644 lua/core/menu/settings.lua mode change 100644 => 100755 lua/core/midi.lua mode change 100644 => 100755 lua/core/osc.lua mode change 100644 => 100755 lua/core/params/control.lua mode change 100644 => 100755 lua/core/params/number.lua mode change 100644 => 100755 lua/core/params/option.lua mode change 100644 => 100755 lua/core/paramset.lua mode change 100644 => 100755 lua/core/pmap.lua mode change 100644 => 100755 lua/core/script.lua mode change 100644 => 100755 lua/core/state.lua mode change 100644 => 100755 lua/lib/fileselect.lua create mode 100755 lua/lib/lfo.lua create mode 100644 lua/lib/timeline.lua delete mode 160000 third-party/link-c diff --git a/.gitmodules b/.gitmodules index a87363c76..f53a7295a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ -[submodule "third-party/link-c"] - path = third-party/link-c - url = https://github.com/artfwo/link-c.git [submodule "third-party/link"] path = third-party/link url = https://github.com/Ableton/link.git diff --git a/crone/dsp/workbench/dbfs_meter.py b/crone/dsp/workbench/dbfs_meter.py deleted file mode 100644 index 2149fc3e4..000000000 --- a/crone/dsp/workbench/dbfs_meter.py +++ /dev/null @@ -1,48 +0,0 @@ -# dbFS metering scale following IEC-60268-18 - -from scipy import interpolate -import numpy as np -import matplotlib.pyplot as plt - -# this dictionary associates dbFS values with deflection percentage -db_deflect = { - 0: 100, - -10: 75, - -20: 50, - -30: 30, - -40: 15, - -50: 7.5, - -60: 5 -} - -def dbamp(db): - return pow(10, db * 0.05) - -# we want to take linear amplitude and return deflection. -# we'll take the IEC table, convert dbFS to amp, and interpolate equal points. - -db = list(db_deflect.keys()) -db.sort() -pos = list(map(lambda x: db_deflect[x], db)) -amp = list(map(lambda x: dbamp(x), db)) - -# we want zero amp == zero pos. -# insert an additional point for interpolation source. -amp.insert(0, 0) -pos.insert(0, 0) - -interpf = interpolate.interp1d(amp, pos) -n = 32 # table doesn't have to be huge. -x = np.linspace(0, 1, n) -y = interpf(x) - -plt.plot(x, y, '-o') -plt.grid(True) -plt.show() - -# export the table, scaled to unit range -print("const float amp_meter_table[] = { ") -print("\t", end='') -for p in y: - print('{}, '.format(p*0.01), end='') -print("\n}") \ No newline at end of file diff --git a/crone/dsp/workbench/requirements.txt b/crone/dsp/workbench/requirements.txt deleted file mode 100644 index c5e564b1e..000000000 --- a/crone/dsp/workbench/requirements.txt +++ /dev/null @@ -1,8 +0,0 @@ -cycler==0.10.0 -kiwisolver==1.1.0 -matplotlib==3.1.1 -numpy==1.17.2 -pyparsing==2.4.2 -python-dateutil==2.8.0 -scipy==1.3.1 -six==1.12.0 diff --git a/crone/src/OscInterface.cpp b/crone/src/OscInterface.cpp index 147ab43ef..1c0dabd95 100644 --- a/crone/src/OscInterface.cpp +++ b/crone/src/OscInterface.cpp @@ -724,13 +724,13 @@ void OscInterface::addServerMethods() { if (argc < 2) { return; } - if (argc > 3) { + if (argc > 2) { dur = argv[2]->f; } - if (argc > 4) { + if (argc > 3) { fadeTime = argv[3]->f; } - if (argc > 5) { + if (argc > 4) { preserve = argv[4]->f; } softCutClient->clearBufferWithFade(argv[0]->i, argv[1]->f, dur, fadeTime, preserve); diff --git a/doc/index.html b/doc/index.html index eb72450fd..66922d0d0 100644 --- a/doc/index.html +++ b/doc/index.html @@ -40,6 +40,7 @@

Modules

  • core.crow.quote
  • encoders
  • engine
  • +
  • gamepad
  • grid
  • hid
  • keyboard
  • @@ -69,6 +70,7 @@

    Modules

  • lib.Graph
  • lib.intonation
  • Lattice
  • +
  • lib.lfo
  • lib.listselect
  • lib.MusicUtil
  • lib.pattern
  • @@ -77,6 +79,7 @@

    Modules

  • lib.test.luaunit
  • lib.textentry
  • lib.textentry_kbd
  • +
  • lib.timeline
  • lib.UI
  • lib.util
  • lib.voice
  • @@ -128,6 +131,10 @@

    Modules

    engine Engine class + + gamepad + gamepad + grid Grid class @@ -249,7 +256,11 @@

    Modules

    Lattice - module for creating a lattice of patterns based on a single fast "superclock" + module for creating a lattice of sprockets based on a single fast "superclock" + + + lib.lfo + construct an LFO lib.listselect @@ -265,9 +276,7 @@

    Modules

    lib.sequins - sequins - nestable tables with sequencing behaviours & control flow - TODO i think ASL can be defined in terms of a sequins... + sequins lib.tabutil @@ -285,6 +294,11 @@

    Modules

    lib.textentry_kbd just a separate module to store textentry keyboard callbacks prevents having to deal w/ a circular dependency + + + lib.timeline + timeline sequencer + hotrod some clock & sequins structures for rapid playability lib.UI @@ -292,7 +306,7 @@

    Modules

    lib.util - Utility module + db to amp. lib.voice @@ -304,7 +318,7 @@

    Modules

    generated by LDoc 1.4.6 -Last updated 2022-02-01 08:10:05 +Last updated 2023-03-17 18:54:24
    diff --git a/doc/modules/Lattice.html b/doc/modules/Lattice.html index 7161924c1..d132d75de 100644 --- a/doc/modules/Lattice.html +++ b/doc/modules/Lattice.html @@ -47,6 +47,7 @@

    Modules

  • core.crow.quote
  • encoders
  • engine
  • +
  • gamepad
  • grid
  • hid
  • keyboard
  • @@ -76,6 +77,7 @@

    Modules

  • lib.Graph
  • lib.intonation
  • Lattice
  • +
  • lib.lfo
  • lib.listselect
  • lib.MusicUtil
  • lib.pattern
  • @@ -84,6 +86,7 @@

    Modules

  • lib.test.luaunit
  • lib.textentry
  • lib.textentry_kbd
  • +
  • lib.timeline
  • lib.UI
  • lib.util
  • lib.voice
  • @@ -94,12 +97,12 @@

    Modules

    Module Lattice

    -

    module for creating a lattice of patterns based on a single fast "superclock"

    +

    module for creating a lattice of sprockets based on a single fast "superclock"

    Info:

      -
    • Release: v1.2.1
    • -
    • Author: tyleretters & ezra & zack
    • +
    • Release: v2.0
    • +
    • Author: tyleretters & ezra & zack & rylee
    @@ -134,8 +137,8 @@

    Functions

    destroy the lattice - set_meter (meter) - set the meter of the lattice + set_meter (_) + set_meter is deprecated auto_pulse (s) @@ -143,43 +146,56 @@

    Functions

    pulse () - advance all patterns in this lattice a single by pulse, call this manually if lattice.auto = false + advance all sprockets in this lattice a single by pulse, call this manually if lattice.auto = false - new_pattern ([args]) - factory method to add a new pattern to this lattice + new_sprocket ([args]) + factory method to add a new sprocket to this lattice - Pattern:new (args) - "private" method to instantiate a new pattern, only called by Lattice:new_pattern() + new_pattern (args) + new_pattern is deprecated - Pattern:start () - start the pattern + order_sprockets () + "private" method to keep numerical order of the sprocket ids + for use when pulsing - Pattern:stop () - stop the pattern + Sprocket:new (args) + "private" method to instantiate a new sprocket, only called by Lattice:new_sprocket() - Pattern:toggle () - toggle the pattern + Sprocket:start () + start the sprocket - Pattern:destroy () - flag the pattern to be destroyed + Sprocket:stop () + stop the sprocket - Pattern:set_division (n) - set the division of the pattern + Sprocket:toggle () + toggle the sprocket - Pattern:set_action (the) - set the action for this pattern + Sprocket:destroy () + flag the sprocket to be destroyed - Pattern:set_swing (the) - set the swing of the pattern + Sprocket:set_division (n) + set the division of the sprocket + + + Sprocket:set_action (the) + set the action for this sprocket + + + Sprocket:set_swing (the) + set the swing of the sprocket + + + Sprocket:set_delay (of) + set the delay for this sprocket @@ -201,11 +217,10 @@

    Functions

    Parameters:

    • args - table + table optional named attributes are: - "auto" (boolean) turn off "auto" pulses from the norns clock, defaults to true - - "meter" (number) of quarter notes per measure, defaults to 4 - - "ppqn" (number) the number of pulses per quarter note of this superclock, defaults to 96 + - "ppqn" (number) the number of pulses per quarter cycle of this superclock, defaults to 96 (optional)
    @@ -213,7 +228,7 @@

    Parameters:

    Returns:

      - table + table a new lattice
    @@ -307,17 +322,16 @@

    Returns:

    - set_meter (meter) + set_meter (_)
    - set the meter of the lattice + set_meter is deprecated

    Parameters:

      -
    • meter - number - the meter the lattice counts +
    • _ +
    @@ -337,7 +351,7 @@

    Parameters:

    Parameters:

    @@ -352,7 +366,7 @@

    Parameters:

    pulse ()
    - advance all patterns in this lattice a single by pulse, call this manually if lattice.auto = false + advance all sprockets in this lattice a single by pulse, call this manually if lattice.auto = false @@ -362,23 +376,24 @@

    Parameters:

    - - new_pattern ([args]) + + new_sprocket ([args])
    - factory method to add a new pattern to this lattice + factory method to add a new sprocket to this lattice

    Parameters:

    • args - table + table optional named attributes are: - "action" (function) called on each step of this division (lattice.transport is passed as the argument), defaults to a no-op - - "division" (number) the division of the pattern, defaults to 1/4 - - "enabled" (boolean) is this pattern enabled, defaults to true + - "division" (number) the division of the sprocket, defaults to 1/4 + - "enabled" (boolean) is this sprocket enabled, defaults to true - "swing" (number) is the percentage of swing (0 - 100%), defaults to 50 - "delay" (number) specifies amount of delay, as fraction of division (0.0 - 1.0), defaults to 0 + - "order" (number) specifies the place in line this lattice occupies from 1 to 5, lower first, defaults to 3 (optional)
    @@ -386,8 +401,8 @@

    Parameters:

    Returns:

      - table - a new pattern + table + a new sprocket
    @@ -395,11 +410,46 @@

    Returns:

    - - Pattern:new (args) + + new_pattern (args)
    - "private" method to instantiate a new pattern, only called by Lattice:new_pattern() + new_pattern is deprecated + + +

    Parameters:

    +
      +
    • args + +
    • +
    + + + + + +
    +
    + + order_sprockets () +
    +
    + "private" method to keep numerical order of the sprocket ids + for use when pulsing + + + + + + + +
    +
    + + Sprocket:new (args) +
    +
    + "private" method to instantiate a new sprocket, only called by Lattice:new_sprocket()

    Parameters:

    @@ -412,8 +462,8 @@

    Parameters:

    Returns:

      - table - a new pattern + table + a new sprocket
    @@ -421,11 +471,11 @@

    Returns:

    - - Pattern:start () + + Sprocket:start ()
    - start the pattern + start the sprocket @@ -435,11 +485,11 @@

    Returns:

    - - Pattern:stop () + + Sprocket:stop ()
    - stop the pattern + stop the sprocket @@ -449,11 +499,11 @@

    Returns:

    - - Pattern:toggle () + + Sprocket:toggle ()
    - toggle the pattern + toggle the sprocket @@ -463,11 +513,11 @@

    Returns:

    - - Pattern:destroy () + + Sprocket:destroy ()
    - flag the pattern to be destroyed + flag the sprocket to be destroyed @@ -477,18 +527,18 @@

    Returns:

    - - Pattern:set_division (n) + + Sprocket:set_division (n)
    - set the division of the pattern + set the division of the sprocket

    Parameters:

    • n number - the division of the pattern + the division of the sprocket
    @@ -498,11 +548,11 @@

    Parameters:

    - - Pattern:set_action (the) + + Sprocket:set_action (the)
    - set the action for this pattern + set the action for this sprocket

    Parameters:

    @@ -519,11 +569,11 @@

    Parameters:

    - - Pattern:set_swing (the) + + Sprocket:set_swing (the)
    - set the swing of the pattern + set the swing of the sprocket

    Parameters:

    @@ -538,6 +588,27 @@

    Parameters:

    +
    +
    + + Sprocket:set_delay (of) +
    +
    + set the delay for this sprocket + + +

    Parameters:

    +
      +
    • of + fraction + the time between beats to delay (0-1) +
    • +
    + + + + +
    @@ -546,7 +617,7 @@

    Parameters:

    generated by LDoc 1.4.6 -Last updated 2022-02-01 08:10:05 +Last updated 2023-03-17 18:54:24
    diff --git a/doc/modules/arc.html b/doc/modules/arc.html index a2ca82aed..93bbd11e1 100644 --- a/doc/modules/arc.html +++ b/doc/modules/arc.html @@ -47,6 +47,7 @@

    Modules

  • core.crow.quote
  • encoders
  • engine
  • +
  • gamepad
  • grid
  • hid
  • keyboard
  • @@ -76,6 +77,7 @@

    Modules

  • lib.Graph
  • lib.intonation
  • Lattice
  • +
  • lib.lfo
  • lib.listselect
  • lib.MusicUtil
  • lib.pattern
  • @@ -84,6 +86,7 @@

    Modules

  • lib.test.luaunit
  • lib.textentry
  • lib.textentry_kbd
  • +
  • lib.timeline
  • lib.UI
  • lib.util
  • lib.voice
  • @@ -161,11 +164,11 @@

    Parameters:

    : arbitrary numeric identifier
  • serial - string + string : serial
  • name - string + string : name
  • dev @@ -361,7 +364,7 @@

    Parameters:

    generated by LDoc 1.4.6 -Last updated 2022-02-01 08:10:05 +Last updated 2023-03-17 18:54:24
    diff --git a/doc/modules/audio.html b/doc/modules/audio.html index 6f4fb8b69..57cfa0aff 100644 --- a/doc/modules/audio.html +++ b/doc/modules/audio.html @@ -52,6 +52,7 @@

    Modules

  • core.crow.quote
  • encoders
  • engine
  • +
  • gamepad
  • grid
  • hid
  • keyboard
  • @@ -81,6 +82,7 @@

    Modules

  • lib.Graph
  • lib.intonation
  • Lattice
  • +
  • lib.lfo
  • lib.listselect
  • lib.MusicUtil
  • lib.pattern
  • @@ -89,6 +91,7 @@

    Modules

  • lib.test.luaunit
  • lib.textentry
  • lib.textentry_kbd
  • +
  • lib.timeline
  • lib.UI
  • lib.util
  • lib.voice
  • @@ -593,7 +596,7 @@

    Parameters:

    Parameters:

    @@ -338,7 +341,7 @@

    Parameters:

    Level value, accepts y_min to y_max, defaults to 1.
  • curve - string or number + string or number Curve of envelope, accepts "lin", "exp" or a number where 0 is linear and positive and negative numbers curve the envelope up and down, defaults to -4.
  • @@ -386,7 +389,7 @@

    Parameters:

    Level value, accepts y_min to y_max.
  • curve - string or number + string or number Curve of envelope, accepts "lin", "exp" or a number where 0 is linear and positive and negative numbers curve the envelope up and down, defaults to -4.
  • @@ -436,7 +439,7 @@

    Parameters:

    Level value, accepts y_min to y_max, defaults to 1.
  • curve - string or number + string or number Curve of envelope, accepts "lin", "exp" or a number where 0 is linear and positive and negative numbers curve the envelope up and down, defaults to -4.
  • @@ -476,7 +479,7 @@

    Parameters:

    Level value, accepts y_min to y_max.
  • curve - string or number + string or number Curve of envelope, accepts "lin", "exp" or a number where 0 is linear and positive and negative numbers curve the envelope up and down, defaults to -4.
  • @@ -526,7 +529,7 @@

    Parameters:

    Level value, accepts y_min to y_max, defaults to 1.
  • curve - string or number + string or number Curve of envelope, accepts "lin", "exp" or a number where 0 is linear and positive and negative numbers curve the envelope up and down, defaults to -4.
  • @@ -566,7 +569,7 @@

    Parameters:

    Level value, accepts y_min to y_max.
  • curve - string or number + string or number Curve of envelope, accepts "lin", "exp" or a number where 0 is linear and positive and negative numbers curve the envelope up and down, defaults to -4.
  • @@ -708,7 +711,7 @@

    Returns:

    Returns:

      - string or number + string or number Curve value.
    @@ -723,7 +726,7 @@

    Returns:

    generated by LDoc 1.4.6 -Last updated 2022-02-01 08:10:05 +Last updated 2023-03-17 18:54:24
    diff --git a/doc/modules/lib.FilterGraph.html b/doc/modules/lib.FilterGraph.html index 9b1207455..83e9c0e5f 100644 --- a/doc/modules/lib.FilterGraph.html +++ b/doc/modules/lib.FilterGraph.html @@ -47,6 +47,7 @@

    Modules

  • core.crow.quote
  • encoders
  • engine
  • +
  • gamepad
  • grid
  • hid
  • keyboard
  • @@ -76,6 +77,7 @@

    Modules

  • lib.Graph
  • lib.intonation
  • Lattice
  • +
  • lib.lfo
  • lib.listselect
  • lib.MusicUtil
  • lib.pattern
  • @@ -84,6 +86,7 @@

    Modules

  • lib.test.luaunit
  • lib.textentry
  • lib.textentry_kbd
  • +
  • lib.timeline
  • lib.UI
  • lib.util
  • lib.voice
  • @@ -168,7 +171,7 @@

    Parameters:

    Maximum amplitude value in dB for y axis, defaults to 30.
  • filter_type - string + string Type of filter, accepts "lowpass", "bandpass", "notch" or "highpass", defaults to "lowpass".
  • slope @@ -208,7 +211,7 @@

    Returns:

    Parameters:

    • filter_type - string + string Type of filter, accepts "lowpass", "bandpass", "notch" or "highpass", defaults to "lowpass".
    • slope @@ -242,7 +245,7 @@

      Parameters:

      Returns:

        - string + string Filter type string.
      @@ -317,7 +320,7 @@

      Returns:

      generated by LDoc 1.4.6 -Last updated 2022-02-01 08:10:05 +Last updated 2023-03-17 18:54:24
      diff --git a/doc/modules/lib.Graph.html b/doc/modules/lib.Graph.html index 65bfb4ace..e715abe04 100644 --- a/doc/modules/lib.Graph.html +++ b/doc/modules/lib.Graph.html @@ -47,6 +47,7 @@

      Modules

    • core.crow.quote
    • encoders
    • engine
    • +
    • gamepad
    • grid
    • hid
    • keyboard
    • @@ -76,6 +77,7 @@

      Modules

    • lib.Graph
    • lib.intonation
    • Lattice
    • +
    • lib.lfo
    • lib.listselect
    • lib.MusicUtil
    • lib.pattern
    • @@ -84,6 +86,7 @@

      Modules

    • lib.test.luaunit
    • lib.textentry
    • lib.textentry_kbd
    • +
    • lib.timeline
    • lib.UI
    • lib.util
    • lib.voice
    • @@ -320,7 +323,7 @@

      Parameters:

      Maximum value for x axis, defaults to 1.
    • x_warp - string + string defines warping for x axis, accepts "lin" or "exp", defaults to "lin".
    • y_min @@ -332,11 +335,11 @@

      Parameters:

      Maximum value for y axis, defaults to 1.
    • y_warp - string + string defines warping for y axis, accepts "lin" or "exp", defaults to "lin".
    • style - string + string defines visual style, accepts "line", "point", "spline", "line_and_point", "spline_and_point" or "bar", defaults to "line".
    • show_x_axis @@ -652,7 +655,7 @@

      Parameters:

      Returns:

        - string + string x warp string.
      @@ -671,7 +674,7 @@

      Returns:

      Parameters:

      • warp - string + string Warp string, accepts "lin" or "exp".
      @@ -775,7 +778,7 @@

      Parameters:

      Returns:

        - string + string y warp string.
      @@ -794,7 +797,7 @@

      Returns:

      Parameters:

      • warp - string + string Warp string, accepts "lin" or "exp".
      @@ -816,7 +819,7 @@

      Parameters:

      Returns:

        - string + string Style string.
      @@ -835,7 +838,7 @@

      Returns:

      Parameters:

      • style - string + string Style string, accepts "line", "point", "spline", "line_and_point", "spline_and_point" or "bar".
      @@ -985,7 +988,7 @@

      Parameters:

      Returns:

        - table + table Point table.
      @@ -1012,7 +1015,7 @@

      Parameters:

      Point's y position.
    • curve - string or number + string or number Curve of previous line segment, accepts "lin", "exp" or a number where 0 is linear and positive and negative numbers curve the envelope up and down, defaults to "lin". (optional)
    • @@ -1058,7 +1061,7 @@

      Parameters:

      (optional)
    • curve - string or number + string or number Curve of previous line segment, accepts "lin", "exp" or a number where 0 is linear and positive and negative numbers curve the envelope up and down. (optional)
    • @@ -1342,7 +1345,7 @@

      Parameters:

      generated by LDoc 1.4.6 -Last updated 2022-02-01 08:10:05 +Last updated 2023-03-17 18:54:24
      diff --git a/doc/modules/lib.MusicUtil.html b/doc/modules/lib.MusicUtil.html index 4d5422b6c..b6f3f9921 100644 --- a/doc/modules/lib.MusicUtil.html +++ b/doc/modules/lib.MusicUtil.html @@ -47,6 +47,7 @@

      Modules

    • core.crow.quote
    • encoders
    • engine
    • +
    • gamepad
    • grid
    • hid
    • keyboard
    • @@ -76,6 +77,7 @@

      Modules

    • lib.Graph
    • lib.intonation
    • Lattice
    • +
    • lib.lfo
    • lib.listselect
    • lib.MusicUtil
    • lib.pattern
    • @@ -84,6 +86,7 @@

      Modules

    • lib.test.luaunit
    • lib.textentry
    • lib.textentry_kbd
    • +
    • lib.timeline
    • lib.UI
    • lib.util
    • lib.voice
    • @@ -203,7 +206,7 @@

      Parameters:

      MIDI note number (0-127) where scale will begin.
    • scale_type - string + string String defining scale type (eg, "major", "aeolian" or "neapolitan major"), see class for full list.
    • octaves @@ -274,7 +277,7 @@

      Parameters:

      MIDI note number (0-127) for chord.
    • chord_type - string + string String defining chord type (eg, "major", "minor 7" or "sus4"), see class for full list.
    • inversion @@ -310,11 +313,11 @@

      Parameters:

      MIDI note number (0-127) defining the key.
    • scale_type - string + string String defining scale type (eg, "major", "dorian"), see class for full list.
    • roman_chord_type - string + string Roman-numeral-style string defining chord type (eg, "V", "iv7" or "III+") including limited bass notes (e.g. "iv6-9") and lowercase-letter inversion notation (e.g. "IIb" for first inversion) Will only return chords defined in MusicUtil.CHORDS. @@ -347,7 +350,7 @@

      Parameters:

      MIDI note number (0-127) defining the key.
    • scale_type - string + string String defining scale type (eg, "major", "dorian"), see class for full list.
    • degree @@ -393,7 +396,7 @@

      Parameters:

      MIDI note number (0-127) for root of key.
    • key_type - string + string String defining key type (eg, "major", "aeolian" or "neapolitan major"), see class for full list.
    @@ -495,7 +498,7 @@

    Parameters:

    Returns:

      - string + string Name string (eg, "C#3").
    @@ -758,7 +761,7 @@

    Returns:

    generated by LDoc 1.4.6 -Last updated 2022-02-01 08:10:05 +Last updated 2023-03-17 18:54:24
    diff --git a/doc/modules/lib.UI.html b/doc/modules/lib.UI.html index 3c1d59e25..850f8fc42 100644 --- a/doc/modules/lib.UI.html +++ b/doc/modules/lib.UI.html @@ -54,6 +54,7 @@

    Modules

  • core.crow.quote
  • encoders
  • engine
  • +
  • gamepad
  • grid
  • hid
  • keyboard
  • @@ -83,6 +84,7 @@

    Modules

  • lib.Graph
  • lib.intonation
  • Lattice
  • +
  • lib.lfo
  • lib.listselect
  • lib.MusicUtil
  • lib.pattern
  • @@ -91,6 +93,7 @@

    Modules

  • lib.test.luaunit
  • lib.textentry
  • lib.textentry_kbd
  • +
  • lib.timeline
  • lib.UI
  • lib.util
  • lib.voice
  • @@ -403,7 +406,7 @@

    Parameters:

    Selected tab, defaults to 1.
  • titles - {string,...} + {string,...} Table of strings for tab titles.
  • @@ -507,7 +510,7 @@

    Parameters:

    Selected entry, defaults to 1.
  • entries - {string,...} + {string,...} Table of strings for list entries.
  • @@ -632,7 +635,7 @@

    Parameters:

    Selected entry, defaults to 1.
  • entries - {string,...} + {string,...} Table of strings for list entries.
  • @@ -745,7 +748,7 @@

    Message

    Parameters:

    @@ -840,11 +843,11 @@

    Parameters:

    Maximum value, defaults to 1.
  • markers - table + table Array of marker positions.
  • the - string + string direction of the slider "up" (defult), down, left, right
  • @@ -1010,15 +1013,15 @@

    Parameters:

    Sets where fill line is drawn from, defaults to 0.
  • markers - table + table Array of marker positions.
  • units - string + string String to display after value text.
  • title - string + string String to be displayed instead of value text.
  • @@ -1244,7 +1247,7 @@

    Parameters:

    generated by LDoc 1.4.6 -Last updated 2022-02-01 08:10:05 +Last updated 2023-03-17 18:54:24
    diff --git a/doc/modules/lib.container.defaulttable.html b/doc/modules/lib.container.defaulttable.html index 0cb389688..c1d7f56b7 100644 --- a/doc/modules/lib.container.defaulttable.html +++ b/doc/modules/lib.container.defaulttable.html @@ -43,6 +43,7 @@

    Modules

  • core.crow.quote
  • encoders
  • engine
  • +
  • gamepad
  • grid
  • hid
  • keyboard
  • @@ -72,6 +73,7 @@

    Modules

  • lib.Graph
  • lib.intonation
  • Lattice
  • +
  • lib.lfo
  • lib.listselect
  • lib.MusicUtil
  • lib.pattern
  • @@ -80,6 +82,7 @@

    Modules

  • lib.test.luaunit
  • lib.textentry
  • lib.textentry_kbd
  • +
  • lib.timeline
  • lib.UI
  • lib.util
  • lib.voice
  • @@ -113,7 +116,7 @@

    Module lib.container.defaulttable

    generated by LDoc 1.4.6 -Last updated 2022-02-01 08:10:05 +Last updated 2023-03-17 18:54:24
    diff --git a/doc/modules/lib.container.deque.html b/doc/modules/lib.container.deque.html index 8a1c75de4..402a6d69f 100644 --- a/doc/modules/lib.container.deque.html +++ b/doc/modules/lib.container.deque.html @@ -47,6 +47,7 @@

    Modules

  • core.crow.quote
  • encoders
  • engine
  • +
  • gamepad
  • grid
  • hid
  • keyboard
  • @@ -76,6 +77,7 @@

    Modules

  • lib.Graph
  • lib.intonation
  • Lattice
  • +
  • lib.lfo
  • lib.listselect
  • lib.MusicUtil
  • lib.pattern
  • @@ -84,6 +86,7 @@

    Modules

  • lib.test.luaunit
  • lib.textentry
  • lib.textentry_kbd
  • +
  • lib.timeline
  • lib.UI
  • lib.util
  • lib.voice
  • @@ -441,7 +444,7 @@

    Returns:

    generated by LDoc 1.4.6 -Last updated 2022-02-01 08:10:05 +Last updated 2023-03-17 18:54:24
    diff --git a/doc/modules/lib.container.observable.html b/doc/modules/lib.container.observable.html index 98a7c543d..1d07bf1b9 100644 --- a/doc/modules/lib.container.observable.html +++ b/doc/modules/lib.container.observable.html @@ -47,6 +47,7 @@

    Modules

  • core.crow.quote
  • encoders
  • engine
  • +
  • gamepad
  • grid
  • hid
  • keyboard
  • @@ -76,6 +77,7 @@

    Modules

  • lib.Graph
  • lib.intonation
  • Lattice
  • +
  • lib.lfo
  • lib.listselect
  • lib.MusicUtil
  • lib.pattern
  • @@ -84,6 +86,7 @@

    Modules

  • lib.test.luaunit
  • lib.textentry
  • lib.textentry_kbd
  • +
  • lib.timeline
  • lib.UI
  • lib.util
  • lib.voice
  • @@ -282,7 +285,7 @@

    Returns:

    generated by LDoc 1.4.6 -Last updated 2022-02-01 08:10:05 +Last updated 2023-03-17 18:54:24
    diff --git a/doc/modules/lib.container.watchtable.html b/doc/modules/lib.container.watchtable.html index f1d5f5dcd..b43f1326f 100644 --- a/doc/modules/lib.container.watchtable.html +++ b/doc/modules/lib.container.watchtable.html @@ -43,6 +43,7 @@

    Modules

  • core.crow.quote
  • encoders
  • engine
  • +
  • gamepad
  • grid
  • hid
  • keyboard
  • @@ -72,6 +73,7 @@

    Modules

  • lib.Graph
  • lib.intonation
  • Lattice
  • +
  • lib.lfo
  • lib.listselect
  • lib.MusicUtil
  • lib.pattern
  • @@ -80,6 +82,7 @@

    Modules

  • lib.test.luaunit
  • lib.textentry
  • lib.textentry_kbd
  • +
  • lib.timeline
  • lib.UI
  • lib.util
  • lib.voice
  • @@ -108,7 +111,7 @@

    Module lib.container.watchtable

    generated by LDoc 1.4.6 -Last updated 2022-02-01 08:10:05 +Last updated 2023-03-17 18:54:24
    diff --git a/doc/modules/lib.container.weaktable.html b/doc/modules/lib.container.weaktable.html index b0bec2cc2..604e59d39 100644 --- a/doc/modules/lib.container.weaktable.html +++ b/doc/modules/lib.container.weaktable.html @@ -43,6 +43,7 @@

    Modules

  • core.crow.quote
  • encoders
  • engine
  • +
  • gamepad
  • grid
  • hid
  • keyboard
  • @@ -72,6 +73,7 @@

    Modules

  • lib.Graph
  • lib.intonation
  • Lattice
  • +
  • lib.lfo
  • lib.listselect
  • lib.MusicUtil
  • lib.pattern
  • @@ -80,6 +82,7 @@

    Modules

  • lib.test.luaunit
  • lib.textentry
  • lib.textentry_kbd
  • +
  • lib.timeline
  • lib.UI
  • lib.util
  • lib.voice
  • @@ -107,7 +110,7 @@

    Module lib.container.weaktable

    generated by LDoc 1.4.6 -Last updated 2022-02-01 08:10:05 +Last updated 2023-03-17 18:54:24
    diff --git a/doc/modules/lib.elca.html b/doc/modules/lib.elca.html index 46b7ee7ae..56b80819b 100644 --- a/doc/modules/lib.elca.html +++ b/doc/modules/lib.elca.html @@ -47,6 +47,7 @@

    Modules

  • core.crow.quote
  • encoders
  • engine
  • +
  • gamepad
  • grid
  • hid
  • keyboard
  • @@ -76,6 +77,7 @@

    Modules

  • lib.Graph
  • lib.intonation
  • Lattice
  • +
  • lib.lfo
  • lib.listselect
  • lib.MusicUtil
  • lib.pattern
  • @@ -84,6 +86,7 @@

    Modules

  • lib.test.luaunit
  • lib.textentry
  • lib.textentry_kbd
  • +
  • lib.timeline
  • lib.UI
  • lib.util
  • lib.voice
  • @@ -246,7 +249,7 @@

    Parameters:

    Returns:

      - table + table table with 8 binary values
    @@ -305,7 +308,7 @@

    Parameters:

    generated by LDoc 1.4.6 -Last updated 2022-02-01 08:10:05 +Last updated 2023-03-17 18:54:24
    diff --git a/doc/modules/lib.er.html b/doc/modules/lib.er.html index 62f8ba204..d75c993db 100644 --- a/doc/modules/lib.er.html +++ b/doc/modules/lib.er.html @@ -47,6 +47,7 @@

    Modules

  • core.crow.quote
  • encoders
  • engine
  • +
  • gamepad
  • grid
  • hid
  • keyboard
  • @@ -76,6 +77,7 @@

    Modules

  • lib.Graph
  • lib.intonation
  • Lattice
  • +
  • lib.lfo
  • lib.listselect
  • lib.MusicUtil
  • lib.pattern
  • @@ -84,6 +86,7 @@

    Modules

  • lib.test.luaunit
  • lib.textentry
  • lib.textentry_kbd
  • +
  • lib.timeline
  • lib.UI
  • lib.util
  • lib.voice
  • @@ -140,7 +143,7 @@

    Parameters:

    Returns:

      - table + table
    @@ -155,7 +158,7 @@

    Returns:

    generated by LDoc 1.4.6 -Last updated 2022-02-01 08:10:05 +Last updated 2023-03-17 18:54:24
    diff --git a/doc/modules/lib.fileselect.html b/doc/modules/lib.fileselect.html index 88dd014e4..91555586f 100644 --- a/doc/modules/lib.fileselect.html +++ b/doc/modules/lib.fileselect.html @@ -43,6 +43,7 @@

    Modules

  • core.crow.quote
  • encoders
  • engine
  • +
  • gamepad
  • grid
  • hid
  • keyboard
  • @@ -72,6 +73,7 @@

    Modules

  • lib.Graph
  • lib.intonation
  • Lattice
  • +
  • lib.lfo
  • lib.listselect
  • lib.MusicUtil
  • lib.pattern
  • @@ -80,6 +82,7 @@

    Modules

  • lib.test.luaunit
  • lib.textentry
  • lib.textentry_kbd
  • +
  • lib.timeline
  • lib.UI
  • lib.util
  • lib.voice
  • @@ -105,7 +108,7 @@

    Module lib.fileselect

    generated by LDoc 1.4.6 -Last updated 2022-02-01 08:10:05 +Last updated 2023-03-17 18:54:24
    diff --git a/doc/modules/lib.filters.html b/doc/modules/lib.filters.html index c987630ff..d772a40f1 100644 --- a/doc/modules/lib.filters.html +++ b/doc/modules/lib.filters.html @@ -50,6 +50,7 @@

    Modules

  • core.crow.quote
  • encoders
  • engine
  • +
  • gamepad
  • grid
  • hid
  • keyboard
  • @@ -79,6 +80,7 @@

    Modules

  • lib.Graph
  • lib.intonation
  • Lattice
  • +
  • lib.lfo
  • lib.listselect
  • lib.MusicUtil
  • lib.pattern
  • @@ -87,6 +89,7 @@

    Modules

  • lib.test.luaunit
  • lib.textentry
  • lib.textentry_kbd
  • +
  • lib.timeline
  • lib.UI
  • lib.util
  • lib.voice
  • @@ -312,7 +315,7 @@

    Parameters:

    generated by LDoc 1.4.6 -Last updated 2022-02-01 08:10:05 +Last updated 2023-03-17 18:54:24
    diff --git a/doc/modules/lib.formatters.html b/doc/modules/lib.formatters.html index d1f936bd8..9a509f6ec 100644 --- a/doc/modules/lib.formatters.html +++ b/doc/modules/lib.formatters.html @@ -47,6 +47,7 @@

    Modules

  • core.crow.quote
  • encoders
  • engine
  • +
  • gamepad
  • grid
  • hid
  • keyboard
  • @@ -76,6 +77,7 @@

    Modules

  • lib.Graph
  • lib.intonation
  • Lattice
  • +
  • lib.lfo
  • lib.listselect
  • lib.MusicUtil
  • lib.pattern
  • @@ -84,6 +86,7 @@

    Modules

  • lib.test.luaunit
  • lib.textentry
  • lib.textentry_kbd
  • +
  • lib.timeline
  • lib.UI
  • lib.util
  • lib.voice
  • @@ -452,7 +455,7 @@

    Parameters:

    generated by LDoc 1.4.6 -Last updated 2022-02-01 08:10:05 +Last updated 2023-03-17 18:54:24
    diff --git a/doc/modules/lib.intonation.html b/doc/modules/lib.intonation.html index 73f66d652..c5d4722bd 100644 --- a/doc/modules/lib.intonation.html +++ b/doc/modules/lib.intonation.html @@ -48,6 +48,7 @@

    Modules

  • core.crow.quote
  • encoders
  • engine
  • +
  • gamepad
  • grid
  • hid
  • keyboard
  • @@ -77,6 +78,7 @@

    Modules

  • lib.Graph
  • lib.intonation
  • Lattice
  • +
  • lib.lfo
  • lib.listselect
  • lib.MusicUtil
  • lib.pattern
  • @@ -85,6 +87,7 @@

    Modules

  • lib.test.luaunit
  • lib.textentry
  • lib.textentry_kbd
  • +
  • lib.timeline
  • lib.UI
  • lib.util
  • lib.voice
  • @@ -163,7 +166,7 @@

    12-tone scales

    Returns:

      - table + table
    @@ -183,7 +186,7 @@

    Returns:

    Returns:

      - table + table
    @@ -204,7 +207,7 @@

    Returns:

    Returns:

      - table + table
    @@ -225,7 +228,7 @@

    Returns:

    Returns:

      - table + table
    @@ -245,7 +248,7 @@

    Returns:

    Returns:

      - table + table
    @@ -269,7 +272,7 @@

    higher-ton

    Returns:

      - table + table
    @@ -289,7 +292,7 @@

    Returns:

    Returns:

      - table + table
    @@ -311,7 +314,7 @@

    Returns:

    Returns:

      - table + table
    @@ -326,7 +329,7 @@

    Returns:

    generated by LDoc 1.4.6 -Last updated 2022-02-01 08:10:05 +Last updated 2023-03-17 18:54:24
    diff --git a/doc/modules/lib.lfo.html b/doc/modules/lib.lfo.html new file mode 100644 index 000000000..98a1f135f --- /dev/null +++ b/doc/modules/lib.lfo.html @@ -0,0 +1,320 @@ + + + + + norns/docs + + + + +
    + +
    + +
    +
    +
    + + +
    + + + + + + +
    + +

    Module lib.lfo

    +

    construct an LFO

    +

    + + +

    Functions

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    LFO:add (shape, min, max, depth, mode, period, action)construct an LFO via table arguments + eg.
    LFO:start ()start LFO
    LFO:stop ()stop LFO
    LFO:set (var, arg)set LFO variable state
    LFO:get (var)get LFO variable state
    LFO:reset_phase ()reset the LFO's phase
    LFO:add_params (id[, separator[, group]])Build parameter menu UI for an already-constructed LFO.
    + +
    +
    + + +

    Functions

    + +
    +
    + + LFO:add (shape, min, max, depth, mode, period, action) +
    +
    + construct an LFO via table arguments + eg. my_lfo:add{shape = 'sine', min = 200, max = 12000} + + +

    Parameters:

    +
      +
    • shape + string + The shape for this LFO (options: 'sine','saw','square','random'; default: 'sine') +
    • +
    • min + number + The minimum bound for this LFO (default: 0) +
    • +
    • max + number + The maximum bound for this LFO (default: 1) +
    • +
    • depth + number + The depth of modulation between min/max (range: 0.0 to 1.0; default: 0.0) +
    • +
    • mode + string + How to advance the LFO (options: 'clocked', 'free'; default: 'clocked') +
    • +
    • period + number + The timing of this LFO's advancement. If mode is 'clocked', argument is in beats. If mode is 'free', argument is in seconds. +
    • +
    • action + function + A callback function to perform as the LFO advances. This library passes both the scaled and the raw value to the callback function. +
    • +
    + + + + + +
    +
    + + LFO:start () +
    +
    + start LFO + + + + + + + +
    +
    + + LFO:stop () +
    +
    + stop LFO + + + + + + + +
    +
    + + LFO:set (var, arg) +
    +
    + set LFO variable state + + +

    Parameters:

    +
      +
    • var + string + The variable to target (options: 'shape', 'min', 'max', 'depth', 'offset', 'mode', 'period', 'reset_target', 'baseline', 'action', 'ppqn') +
    • +
    • arg + various + The argument to pass to the target (often numbers + strings, but 'action' expects a function) +
    • +
    + + + + + +
    +
    + + LFO:get (var) +
    +
    + get LFO variable state + + +

    Parameters:

    +
      +
    • var + string + The variable to query (options: 'shape', 'min', 'max', 'depth', 'offset', 'mode', 'period', 'reset_target', 'baseline', 'action', 'enabled', 'controlspec') +
    • +
    + + + + + +
    +
    + + LFO:reset_phase () +
    +
    + reset the LFO's phase + + + + + + + +
    +
    + + LFO:add_params (id[, separator[, group]]) +
    +
    + Build parameter menu UI for an already-constructed LFO. + + +

    Parameters:

    +
      +
    • id + string + The parameter ID to use for this LFO. +
    • +
    • separator + string + A separator name for the LFO parameters. + (optional) +
    • +
    • group + string + A group name for the LFO parameters. + (optional) +
    • +
    + + + + + +
    +
    + + +
    +
    +
    +generated by LDoc 1.4.6 +Last updated 2023-03-17 18:54:24 +
    +
    + + diff --git a/doc/modules/lib.listselect.html b/doc/modules/lib.listselect.html index 7d59d054b..42504f10c 100644 --- a/doc/modules/lib.listselect.html +++ b/doc/modules/lib.listselect.html @@ -43,6 +43,7 @@

    Modules

  • core.crow.quote
  • encoders
  • engine
  • +
  • gamepad
  • grid
  • hid
  • keyboard
  • @@ -72,6 +73,7 @@

    Modules

  • lib.Graph
  • lib.intonation
  • Lattice
  • +
  • lib.lfo
  • lib.listselect
  • lib.MusicUtil
  • lib.pattern
  • @@ -80,6 +82,7 @@

    Modules

  • lib.test.luaunit
  • lib.textentry
  • lib.textentry_kbd
  • +
  • lib.timeline
  • lib.UI
  • lib.util
  • lib.voice
  • @@ -105,7 +108,7 @@

    Module lib.listselect

    generated by LDoc 1.4.6 -Last updated 2022-02-01 08:10:05 +Last updated 2023-03-17 18:54:24
    diff --git a/doc/modules/lib.pattern.html b/doc/modules/lib.pattern.html index 0fedbca21..4fc4432f2 100644 --- a/doc/modules/lib.pattern.html +++ b/doc/modules/lib.pattern.html @@ -47,6 +47,7 @@

    Modules

  • core.crow.quote
  • encoders
  • engine
  • +
  • gamepad
  • grid
  • hid
  • keyboard
  • @@ -76,6 +77,7 @@

    Modules

  • lib.Graph
  • lib.intonation
  • Lattice
  • +
  • lib.lfo
  • lib.listselect
  • lib.MusicUtil
  • lib.pattern
  • @@ -84,6 +86,7 @@

    Modules

  • lib.test.luaunit
  • lib.textentry
  • lib.textentry_kbd
  • +
  • lib.timeline
  • lib.UI
  • lib.util
  • lib.voice
  • @@ -363,7 +366,7 @@

    Parameters:

    generated by LDoc 1.4.6 -Last updated 2022-02-01 08:10:05 +Last updated 2023-03-17 18:54:24
    diff --git a/doc/modules/lib.sequins.html b/doc/modules/lib.sequins.html index 031c2115a..79a7d8aa1 100644 --- a/doc/modules/lib.sequins.html +++ b/doc/modules/lib.sequins.html @@ -47,6 +47,7 @@

    Modules

  • core.crow.quote
  • encoders
  • engine
  • +
  • gamepad
  • grid
  • hid
  • keyboard
  • @@ -76,6 +77,7 @@

    Modules

  • lib.Graph
  • lib.intonation
  • Lattice
  • +
  • lib.lfo
  • lib.listselect
  • lib.MusicUtil
  • lib.pattern
  • @@ -84,6 +86,7 @@

    Modules

  • lib.test.luaunit
  • lib.textentry
  • lib.textentry_kbd
  • +
  • lib.timeline
  • lib.UI
  • lib.util
  • lib.voice
  • @@ -94,35 +97,15 @@

    Modules

    Module lib.sequins

    -

    sequins - nestable tables with sequencing behaviours & control flow - TODO i think ASL can be defined in terms of a sequins...

    -

    -

    for the norns port, @tyleretters copy-pasta'd with no changes from: - https://github.com/monome/crow/blob/34ce1e455f01fdef65a0d37aa97163b4cd14a115/lua/sequins.lua

    +

    sequins

    +

    Functions

    - - - - - - - - - - - - - - - - - - + +
    S.next (self)control flow execution
    S.do_ctrl (act)control flow manipulation
    S.step (self, s)behaviour modifiers
    S.all (self)helpers in terms of core
    S.__call (self, ...)metamethodsS:func (fn, ...)transformers
    @@ -134,99 +117,16 @@

    Functions

    - - S.next (self) -
    -
    - control flow execution - - -

    Parameters:

    -
      -
    • self - -
    • -
    - - - - - -
    -
    - - S.do_ctrl (act) -
    -
    - control flow manipulation - - -

    Parameters:

    -
      -
    • act - -
    • -
    - - - - - -
    -
    - - S.step (self, s) -
    -
    - behaviour modifiers - - -

    Parameters:

    -
      -
    • self - -
    • -
    • s - -
    • -
    - - - - - -
    -
    - - S.all (self) -
    -
    - helpers in terms of core - - -

    Parameters:

    -
      -
    • self - -
    • -
    - - - - - -
    -
    - - S.__call (self, ...) + + S:func (fn, ...)
    - metamethods + transformers

    Parameters:

      -
    • self +
    • fn
    • ... @@ -246,7 +146,7 @@

      Parameters:

    generated by LDoc 1.4.6 -Last updated 2022-02-01 08:10:05 +Last updated 2023-03-17 18:54:24
    diff --git a/doc/modules/lib.tabutil.html b/doc/modules/lib.tabutil.html index 3c083db1e..bf3dae26b 100644 --- a/doc/modules/lib.tabutil.html +++ b/doc/modules/lib.tabutil.html @@ -47,6 +47,7 @@

    Modules

  • core.crow.quote
  • encoders
  • engine
  • +
  • gamepad
  • grid
  • hid
  • keyboard
  • @@ -76,6 +77,7 @@

    Modules

  • lib.Graph
  • lib.intonation
  • Lattice
  • +
  • lib.lfo
  • lib.listselect
  • lib.MusicUtil
  • lib.pattern
  • @@ -84,6 +86,7 @@

    Modules

  • lib.test.luaunit
  • lib.textentry
  • lib.textentry_kbd
  • +
  • lib.timeline
  • lib.UI
  • lib.util
  • lib.voice
  • @@ -180,7 +183,7 @@

    Functions

    Parameters:

    @@ -201,7 +204,7 @@

    Parameters:

    Parameters:

    @@ -209,7 +212,7 @@

    Parameters:

    Returns:

      - table + table sorted table
    @@ -229,7 +232,7 @@

    Returns:

    Parameters:

    @@ -256,7 +259,7 @@

    Returns:

    Parameters:

    • t - table + table table to check
    • e @@ -308,7 +311,7 @@

      Parameters:

      Parameters:

      • t - table + table table to check
      • e @@ -337,7 +340,7 @@

        Returns:

        Parameters:

        @@ -345,7 +348,7 @@

        Parameters:

        Returns:

          - table + table table with entries for each line
        @@ -364,11 +367,11 @@

        Returns:

        Parameters:

        @@ -393,11 +396,11 @@

        Parameters:

        Parameters:

        @@ -423,7 +426,7 @@

        Returns:

        Parameters:

        @@ -456,7 +459,7 @@

        Parameters:

        Returns:

          - table + table the proxied read-only table
        @@ -478,11 +481,11 @@

        Returns:

        Parameters:

        • default_values - table + table base values (provides keys & fallback values)
        • custom_values - table + table override values (take precedence)
        @@ -490,7 +493,7 @@

        Parameters:

        Returns:

          - table + table composite table
        @@ -511,11 +514,11 @@

        Returns:

        Parameters:

        • table_to_mutate - table + table table to mutate
        • updated_values - table + table override values (take precedence)
        @@ -523,7 +526,7 @@

        Parameters:

        Returns:

          - table + table composite table
        @@ -542,7 +545,7 @@

        Returns:

        Parameters:

        • t - table + table table to check
        • condition @@ -553,7 +556,7 @@

          Parameters:

          Returns:

            - table + table table with values that pass the test
          @@ -568,7 +571,7 @@

          Returns:

          generated by LDoc 1.4.6 -Last updated 2022-02-01 08:10:05 +Last updated 2023-03-17 18:54:24
          diff --git a/doc/modules/lib.test.luaunit.html b/doc/modules/lib.test.luaunit.html index 7866a6033..5bb5bc522 100644 --- a/doc/modules/lib.test.luaunit.html +++ b/doc/modules/lib.test.luaunit.html @@ -48,6 +48,7 @@

          Modules

        • core.crow.quote
        • encoders
        • engine
        • +
        • gamepad
        • grid
        • hid
        • keyboard
        • @@ -77,6 +78,7 @@

          Modules

        • lib.Graph
        • lib.intonation
        • Lattice
        • +
        • lib.lfo
        • lib.listselect
        • lib.MusicUtil
        • lib.pattern
        • @@ -85,6 +87,7 @@

          Modules

        • lib.test.luaunit
        • lib.textentry
        • lib.textentry_kbd
        • +
        • lib.timeline
        • lib.UI
        • lib.util
        • lib.voice
        • @@ -274,7 +277,7 @@

          Fields:

          generated by LDoc 1.4.6 -Last updated 2022-02-01 08:10:05 +Last updated 2023-03-17 18:54:24
          diff --git a/doc/modules/lib.textentry.html b/doc/modules/lib.textentry.html index 897698b2a..9efd3c2e5 100644 --- a/doc/modules/lib.textentry.html +++ b/doc/modules/lib.textentry.html @@ -43,6 +43,7 @@

          Modules

        • core.crow.quote
        • encoders
        • engine
        • +
        • gamepad
        • grid
        • hid
        • keyboard
        • @@ -72,6 +73,7 @@

          Modules

        • lib.Graph
        • lib.intonation
        • Lattice
        • +
        • lib.lfo
        • lib.listselect
        • lib.MusicUtil
        • lib.pattern
        • @@ -80,6 +82,7 @@

          Modules

        • lib.test.luaunit
        • lib.textentry
        • lib.textentry_kbd
        • +
        • lib.timeline
        • lib.UI
        • lib.util
        • lib.voice
        • @@ -105,7 +108,7 @@

          Module lib.textentry

          generated by LDoc 1.4.6 -Last updated 2022-02-01 08:10:05 +Last updated 2023-03-17 18:54:24
          diff --git a/doc/modules/lib.textentry_kbd.html b/doc/modules/lib.textentry_kbd.html index b36a59f8a..edec9c487 100644 --- a/doc/modules/lib.textentry_kbd.html +++ b/doc/modules/lib.textentry_kbd.html @@ -43,6 +43,7 @@

          Modules

        • core.crow.quote
        • encoders
        • engine
        • +
        • gamepad
        • grid
        • hid
        • keyboard
        • @@ -72,6 +73,7 @@

          Modules

        • lib.Graph
        • lib.intonation
        • Lattice
        • +
        • lib.lfo
        • lib.listselect
        • lib.MusicUtil
        • lib.pattern
        • @@ -80,6 +82,7 @@

          Modules

        • lib.test.luaunit
        • lib.textentry
        • lib.textentry_kbd
        • +
        • lib.timeline
        • lib.UI
        • lib.util
        • lib.voice
        • @@ -106,7 +109,7 @@

          Module lib.textentry_kbd

          generated by LDoc 1.4.6 -Last updated 2022-02-01 08:10:05 +Last updated 2023-03-17 18:54:24
          diff --git a/doc/modules/lib.timeline.html b/doc/modules/lib.timeline.html new file mode 100644 index 000000000..b71039179 --- /dev/null +++ b/doc/modules/lib.timeline.html @@ -0,0 +1,270 @@ + + + + + norns/docs + + + + +
          + +
          + +
          +
          +
          + + +
          + + + + + + +
          + +

          Module lib.timeline

          +

          timeline sequencer + hotrod some clock & sequins structures for rapid playability

          +

          + + +

          Functions

          + + + + + + + + + + + + + +
          TL.loop (t)loop + standalone
          TL.score (t)score + standalone
          TL.real (t)real + standalone
          +

          Tables

          + + + + + +
          TL.mmsmetamethods
          + +
          +
          + + +

          Functions

          + +
          +
          + + TL.loop (t) +
          +
          + loop + standalone + + +

          Parameters:

          +
            +
          • t + +
          • +
          + + + + + +
          +
          + + TL.score (t) +
          +
          + score + standalone + + +

          Parameters:

          +
            +
          • t + +
          • +
          + + + + + +
          +
          + + TL.real (t) +
          +
          + real + standalone + + +

          Parameters:

          +
            +
          • t + +
          • +
          + + + + + +
          +
          +

          Tables

          + +
          +
          + + TL.mms +
          +
          + metamethods + + +

          Fields:

          +
            +
          • stop + +
          • +
          • unless + +
          • +
          • times + +
          • +
          • once + +
          • +
          • loop + +
          • +
          • score + +
          • +
          • real + +
          • +
          • play + +
          • +
          • iter + +
          • +
          • hotswap + +
          • +
          • launch + +
          • +
          • queue + +
          • +
          + + + + + +
          +
          + + +
          +
          +
          +generated by LDoc 1.4.6 +Last updated 2023-03-17 18:54:24 +
          +
          + + diff --git a/doc/modules/lib.util.html b/doc/modules/lib.util.html index 600a8f472..38507cd82 100644 --- a/doc/modules/lib.util.html +++ b/doc/modules/lib.util.html @@ -47,6 +47,7 @@

          Modules

        • core.crow.quote
        • encoders
        • engine
        • +
        • gamepad
        • grid
        • hid
        • keyboard
        • @@ -76,6 +77,7 @@

          Modules

        • lib.Graph
        • lib.intonation
        • Lattice
        • +
        • lib.lfo
        • lib.listselect
        • lib.MusicUtil
        • lib.pattern
        • @@ -84,6 +86,7 @@

          Modules

        • lib.test.luaunit
        • lib.textentry
        • lib.textentry_kbd
        • +
        • lib.timeline
        • lib.UI
        • lib.util
        • lib.voice
        • @@ -94,16 +97,12 @@

          Modules

          Module lib.util

          -

          Utility module

          +

          db to amp.

          Functions

          - - - - @@ -182,11 +181,11 @@

          Functions

          - + - +
          dbamp (db)db to amp.
          time () get system time in fractional seconds
          wrap (n, min, max)wrap a number to a positive min/max rangewrap a integer to a positive min/max range
          wrap_max (n, min, max)wrap a number to a positive min/max range but clamp the minwrap an integer to a positive min/max range but clamp the min
          @@ -197,33 +196,6 @@

          Functions

          Functions

          -
          - - dbamp (db) -
          -
          - db to amp. - - -

          Parameters:

          -
            -
          • db - number - -
          • -
          - -

          Returns:

          -
            - - number - amp -
          - - - - -
          time () @@ -254,7 +226,7 @@

          Returns:

          Parameters:

          @@ -262,7 +234,7 @@

          Parameters:

          Returns:

            - table + table
          @@ -281,7 +253,7 @@

          Returns:

          Parameters:

          @@ -308,7 +280,7 @@

          Returns:

          Parameters:

          @@ -335,7 +307,7 @@

          Returns:

          Parameters:

          @@ -356,7 +328,7 @@

          Parameters:

          Parameters:

          • cmd - string + string command
          • raw @@ -385,11 +357,11 @@

            Returns:

            Parameters:

            @@ -416,7 +388,7 @@

            Returns:

            Parameters:

            • s - string + string string to trim
            • width @@ -428,7 +400,7 @@

              Parameters:

              Returns:

                - string + string trimmed string
              @@ -712,7 +684,7 @@

              Parameters:

              Returns:

                - string + string seconds : seconds in h:m:s
              @@ -785,7 +757,7 @@

              Returns:

              Parameters:

              @@ -793,7 +765,7 @@

              Parameters:

              Returns:

                - string + string acronym
              @@ -806,21 +778,21 @@

              Returns:

              wrap (n, min, max)
          - wrap a number to a positive min/max range + wrap a integer to a positive min/max range

          Parameters:

          • n - number + integer
          • min - number + integer
          • max - number + integer
          @@ -828,7 +800,7 @@

          Parameters:

          Returns:

            - number + integer cycled value
          @@ -841,21 +813,21 @@

          Returns:

          wrap_max (n, min, max)
          - wrap a number to a positive min/max range but clamp the min + wrap an integer to a positive min/max range but clamp the min

          Parameters:

          • n - number + integer
          • min - number + integer
          • max - number + integer
          @@ -863,7 +835,7 @@

          Parameters:

          Returns:

            - number + integer cycled value
          @@ -878,7 +850,7 @@

          Returns:

          generated by LDoc 1.4.6 -Last updated 2022-02-01 08:10:05 +Last updated 2023-03-17 18:54:24
          diff --git a/doc/modules/lib.voice.html b/doc/modules/lib.voice.html index 0cca604d1..a6489a905 100644 --- a/doc/modules/lib.voice.html +++ b/doc/modules/lib.voice.html @@ -47,6 +47,7 @@

          Modules

        • core.crow.quote
        • encoders
        • engine
        • +
        • gamepad
        • grid
        • hid
        • keyboard
        • @@ -76,6 +77,7 @@

          Modules

        • lib.Graph
        • lib.intonation
        • Lattice
        • +
        • lib.lfo
        • lib.listselect
        • lib.MusicUtil
        • lib.pattern
        • @@ -84,6 +86,7 @@

          Modules

        • lib.test.luaunit
        • lib.textentry
        • lib.textentry_kbd
        • +
        • lib.timeline
        • lib.UI
        • lib.util
        • lib.voice
        • @@ -244,7 +247,7 @@

          Parameters:

          generated by LDoc 1.4.6 -Last updated 2022-02-01 08:10:05 +Last updated 2023-03-17 18:54:24
          diff --git a/doc/modules/metro.html b/doc/modules/metro.html index 2baba9815..1c0f8e882 100644 --- a/doc/modules/metro.html +++ b/doc/modules/metro.html @@ -47,6 +47,7 @@

          Modules

        • core.crow.quote
        • encoders
        • engine
        • +
        • gamepad
        • grid
        • hid
        • keyboard
        • @@ -76,6 +77,7 @@

          Modules

        • lib.Graph
        • lib.intonation
        • Lattice
        • +
        • lib.lfo
        • lib.listselect
        • lib.MusicUtil
        • lib.pattern
        • @@ -84,6 +86,7 @@

          Modules

        • lib.test.luaunit
        • lib.textentry
        • lib.textentry_kbd
        • +
        • lib.timeline
        • lib.UI
        • lib.util
        • lib.voice
        • @@ -278,7 +281,7 @@

          Parameters:

          generated by LDoc 1.4.6 -Last updated 2022-02-01 08:10:05 +Last updated 2023-03-17 18:54:24
          diff --git a/doc/modules/midi.html b/doc/modules/midi.html index dabd27a08..16f60c12a 100644 --- a/doc/modules/midi.html +++ b/doc/modules/midi.html @@ -47,6 +47,7 @@

          Modules

        • core.crow.quote
        • encoders
        • engine
        • +
        • gamepad
        • grid
        • hid
        • keyboard
        • @@ -76,6 +77,7 @@

          Modules

        • lib.Graph
        • lib.intonation
        • Lattice
        • +
        • lib.lfo
        • lib.listselect
        • lib.MusicUtil
        • lib.pattern
        • @@ -84,6 +86,7 @@

          Modules

        • lib.test.luaunit
        • lib.textentry
        • lib.textentry_kbd
        • +
        • lib.timeline
        • lib.UI
        • lib.util
        • lib.voice
        • @@ -208,7 +211,7 @@

          Parameters:

          : arbitrary numeric identifier
        • name - string + string : name
        • dev @@ -623,7 +626,7 @@

          Parameters:

          Parameters:

          @@ -631,7 +634,7 @@

          Parameters:

          Returns:

            - table + table data : table of midi status and data bytes
          @@ -650,7 +653,7 @@

          Returns:

          Parameters:

          @@ -658,7 +661,7 @@

          Parameters:

          Returns:

            - table + table msg : midi message table, contents vary depending on message
          @@ -673,7 +676,7 @@

          Returns:

          generated by LDoc 1.4.6 -Last updated 2022-02-01 08:10:05 +Last updated 2023-03-17 18:54:24
          diff --git a/doc/modules/norns.html b/doc/modules/norns.html index cce13680d..ba758989c 100644 --- a/doc/modules/norns.html +++ b/doc/modules/norns.html @@ -48,6 +48,7 @@

          Modules

        • core.crow.quote
        • encoders
        • engine
        • +
        • gamepad
        • grid
        • hid
        • keyboard
        • @@ -77,6 +78,7 @@

          Modules

        • lib.Graph
        • lib.intonation
        • Lattice
        • +
        • lib.lfo
        • lib.listselect
        • lib.MusicUtil
        • lib.pattern
        • @@ -85,6 +87,7 @@

          Modules

        • lib.test.luaunit
        • lib.textentry
        • lib.textentry_kbd
        • +
        • lib.timeline
        • lib.UI
        • lib.util
        • lib.voice
        • @@ -206,7 +209,7 @@

          Functions

          Parameters:

          • cmd - string + string shell command to execute
          • callback @@ -233,7 +236,7 @@

            Parameters:

            Parameters:

            @@ -241,7 +244,7 @@

            Parameters:

            Returns:

              - {string,...} + {string,...} a table of matching pathnames
            @@ -320,7 +323,7 @@

            Fields

            generated by LDoc 1.4.6 -Last updated 2022-02-01 08:10:05 +Last updated 2023-03-17 18:54:24
            diff --git a/doc/modules/osc.html b/doc/modules/osc.html index b42282c48..621c2ac3b 100644 --- a/doc/modules/osc.html +++ b/doc/modules/osc.html @@ -47,6 +47,7 @@

            Modules

          • core.crow.quote
          • encoders
          • engine
          • +
          • gamepad
          • grid
          • hid
          • keyboard
          • @@ -76,6 +77,7 @@

            Modules

          • lib.Graph
          • lib.intonation
          • Lattice
          • +
          • lib.lfo
          • lib.listselect
          • lib.MusicUtil
          • lib.pattern
          • @@ -84,6 +86,7 @@

            Modules

          • lib.test.luaunit
          • lib.textentry
          • lib.textentry_kbd
          • +
          • lib.timeline
          • lib.UI
          • lib.util
          • lib.voice
          • @@ -108,6 +111,10 @@

            Functions

            OSC.send (to, path, args) static method to send osc event. + + OSC.cleanup () + clear handlers. +
            @@ -129,15 +136,15 @@

            Functions

            Parameters:

            @@ -158,15 +165,15 @@

            Parameters:

            Parameters:

            @@ -175,6 +182,20 @@

            Parameters:

            + +
            + + OSC.cleanup () +
            +
            + clear handlers. + + + + + + +
            @@ -183,7 +204,7 @@

            Parameters:

            generated by LDoc 1.4.6 -Last updated 2022-02-01 08:10:05 +Last updated 2023-03-17 18:54:24
            diff --git a/doc/modules/params.control.html b/doc/modules/params.control.html index cf94c3b2a..a5c35ca84 100644 --- a/doc/modules/params.control.html +++ b/doc/modules/params.control.html @@ -47,6 +47,7 @@

            Modules

          • core.crow.quote
          • encoders
          • engine
          • +
          • gamepad
          • grid
          • hid
          • keyboard
          • @@ -76,6 +77,7 @@

            Modules

          • lib.Graph
          • lib.intonation
          • Lattice
          • +
          • lib.lfo
          • lib.listselect
          • lib.MusicUtil
          • lib.pattern
          • @@ -84,6 +86,7 @@

            Modules

          • lib.test.luaunit
          • lib.textentry
          • lib.textentry_kbd
          • +
          • lib.timeline
          • lib.UI
          • lib.util
          • lib.voice
          • @@ -423,7 +426,7 @@

            Returns:

            generated by LDoc 1.4.6 -Last updated 2022-02-01 08:10:05 +Last updated 2023-03-17 18:54:24
            diff --git a/doc/modules/paramset.html b/doc/modules/paramset.html index de81af43b..df9ff08e1 100644 --- a/doc/modules/paramset.html +++ b/doc/modules/paramset.html @@ -47,6 +47,7 @@

            Modules

          • core.crow.quote
          • encoders
          • engine
          • +
          • gamepad
          • grid
          • hid
          • keyboard
          • @@ -76,6 +77,7 @@

            Modules

          • lib.Graph
          • lib.intonation
          • Lattice
          • +
          • lib.lfo
          • lib.listselect
          • lib.MusicUtil
          • lib.pattern
          • @@ -84,6 +86,7 @@

            Modules

          • lib.test.luaunit
          • lib.textentry
          • lib.textentry_kbd
          • +
          • lib.timeline
          • lib.UI
          • lib.util
          • lib.voice
          • @@ -105,14 +108,6 @@

            Functions

            constructor. - ParamSet:add_separator (name) - add separator. - - - ParamSet:add_group (name, n) - add parameter group. - - ParamSet:add (args) add generic parameter. @@ -149,6 +144,14 @@

            Functions

            add binary + ParamSet:add_separator (id, name) + add separator. + + + ParamSet:add_group (id, name, n) + add parameter group. + + ParamSet:print () print. @@ -225,6 +228,10 @@

            Functions

            read from disk. + ParamSet:delete (filename, name) + delete from disk. + + ParamSet:default () read default pset if present. @@ -269,57 +276,6 @@

            Parameters:

            - -
            - - ParamSet:add_separator (name) -
            -
            - add separator. - name is optional. - separators have their own parameter index and - can be hidden or added to a paremeter group. - - -

            Parameters:

            - - - - - - -
            -
            - - ParamSet:add_group (name, n) -
            -
            - add parameter group. - groups cannot be nested, - i.e. a group cannot be made within a group. - - -

            Parameters:

            -
              -
            • name - string - -
            • -
            • n - int - -
            • -
            - - - - -
            @@ -402,11 +358,11 @@

            Parameters:

            • id string - + (no spaces)
            • name string - + (can contain spaces)
            • options @@ -433,11 +389,11 @@

              Parameters:

              • id string - + (no spaces)
              • name string - + (can contain spaces)
              • controlspec controlspec @@ -465,11 +421,11 @@

                Parameters:

                • id string - + (no spaces)
                • name string - + (can contain spaces)
                • path string @@ -520,11 +476,11 @@

                  Parameters:

                  • id string - + (no spaces)
                  • name string - + (can contain spaces)
                  • min number @@ -564,11 +520,11 @@

                    Parameters:

                    • id string - + (no spaces)
                    • name string - + (can contain spaces)
                    @@ -589,11 +545,11 @@

                    Parameters:

                    • id string - + (no spaces)
                    • name string - + (can contain spaces)
                    • behavior string @@ -609,6 +565,74 @@

                      Parameters:

                      + +
                      + + ParamSet:add_separator (id, name) +
                      +
                      + add separator. + id and name are optional. + if neither id or name are provided, + separator will be named 'separator' + and will not have a unique parameter index. + separators which have their own parameter index + can be hidden / shown. + + +

                      Parameters:

                      +
                        +
                      • id + string + (no spaces) +
                      • +
                      • name + string + (can contain spaces) +
                      • +
                      + + + + + +
                      +
                      + + ParamSet:add_group (id, name, n) +
                      +
                      + add parameter group. + groups cannot be nested, + i.e. a group cannot be made within a group. + id and name are optional. + if neither id or name are provided, + group will be named 'group' + and will not have a unique parameter index. + groups which have their own parameter index + can be hidden / shown. + + +

                      Parameters:

                      +
                        +
                      • id + string + (no spaces) +
                      • +
                      • name + string + (can contain spaces) +
                      • +
                      • n + int + +
                      • +
                      + + + + +
                      @@ -1014,6 +1038,30 @@

                      Parameters:

                      + +
                      + + ParamSet:delete (filename, name) +
                      +
                      + delete from disk. + + +

                      Parameters:

                      +
                        +
                      • filename + either an absolute path, a number (for [scriptname]-[number].pset in local data folder) or nil (for default [scriptname].pset in local data folder) +
                      • +
                      • name + string + +
                      • +
                      + + + + +
                      @@ -1064,7 +1112,7 @@

                      Parameters:

                      generated by LDoc 1.4.6 -Last updated 2022-02-01 08:10:05 +Last updated 2023-03-17 18:54:24
                      diff --git a/doc/modules/poll.html b/doc/modules/poll.html index e8f26adfd..5bca4c18b 100644 --- a/doc/modules/poll.html +++ b/doc/modules/poll.html @@ -47,6 +47,7 @@

                      Modules

                    • core.crow.quote
                    • encoders
                    • engine
                    • +
                    • gamepad
                    • grid
                    • hid
                    • keyboard
                    • @@ -76,6 +77,7 @@

                      Modules

                    • lib.Graph
                    • lib.intonation
                    • Lattice
                    • +
                    • lib.lfo
                    • lib.listselect
                    • lib.MusicUtil
                    • lib.pattern
                    • @@ -84,6 +86,7 @@

                      Modules

                    • lib.test.luaunit
                    • lib.textentry
                    • lib.textentry_kbd
                    • +
                    • lib.timeline
                    • lib.UI
                    • lib.util
                    • lib.voice
                    • @@ -230,7 +233,7 @@

                      Parameters:

                      generated by LDoc 1.4.6 -Last updated 2022-02-01 08:10:05 +Last updated 2023-03-17 18:54:24
                      diff --git a/doc/modules/screen.html b/doc/modules/screen.html index d4539dcae..78c214027 100644 --- a/doc/modules/screen.html +++ b/doc/modules/screen.html @@ -47,6 +47,7 @@

                      Modules

                    • core.crow.quote
                    • encoders
                    • engine
                    • +
                    • gamepad
                    • grid
                    • hid
                    • keyboard
                    • @@ -76,6 +77,7 @@

                      Modules

                    • lib.Graph
                    • lib.intonation
                    • Lattice
                    • +
                    • lib.lfo
                    • lib.listselect
                    • lib.MusicUtil
                    • lib.pattern
                    • @@ -84,6 +86,7 @@

                      Modules

                    • lib.test.luaunit
                    • lib.textentry
                    • lib.textentry_kbd
                    • +
                    • lib.timeline
                    • lib.UI
                    • lib.util
                    • lib.voice
                    • @@ -227,7 +230,27 @@

                      Functions

                      Screen.display_png (filename, x, y) - display png. + display png + + + Screen.load_png (filename) + load png into an image buffer + + + Screen.create_image (width, height) + create an image buffer + + + Screen.display_image (image, x, y) + display image buffer + + + Screen.display_image_region (image, left, top, width, height, x, y) + display sub-region image buffer + + + Screen.draw_to (image, func) + direct screen drawing within the provide function into the image instead of the screen Screen.peek (x, y, w, h) @@ -651,27 +674,27 @@

                      Parameters:

                      • x1 number - destination x + handle 1 x
                      • y1 number - destination y + handle 1 y
                      • x2 number - handle 1 x + handle 2 x
                      • y2 number - handle 1 y + handle 2 y
                      • x3 number - handle 2 x + destination x
                      • y3 number - handle 2 y + destination y
                      @@ -692,27 +715,27 @@

                      Parameters:

                      • x1 number - relative destination x + handle 1 x
                      • y1 number - relative destination y + handle 1 y
                      • x2 number - handle 1 x + handle 2 x
                      • y2 number - handle 1 y + handle 2 y
                      • x3 number - handle 2 x + relative destination x
                      • y3 number - handle 2 y + relative destination y
                      @@ -742,6 +765,9 @@

                      Parameters:

                      stroke current path. uses currently selected color. + after this call the current path will be cleared, so the 'relative' functions + (`move_rel`, `line_rel` and `curve_rel`) won't work - use their absolute + alternatives instead. @@ -757,6 +783,9 @@

                      Parameters:

                      fill current path. uses currently selected color. + after this call the current path will be cleared, so the 'relative' functions + (`move_rel`, `line_rel` and `curve_rel`) won't work - use their absolute + alternatives instead. @@ -777,7 +806,7 @@

                      Parameters:

                      Parameters:

                      @@ -807,7 +836,7 @@

                      Parameters:

                      y position
                    • str - string + string : text to write
                    • degrees @@ -833,7 +862,7 @@

                      Parameters:

                      Parameters:

                      @@ -855,7 +884,7 @@

                      Parameters:

                      Parameters:

                      @@ -885,7 +914,7 @@

                      Parameters:

                      y position
                    • str - string + string : text to write
                    • degrees @@ -911,7 +940,7 @@

                      Parameters:

                      Parameters:

                      @@ -1059,7 +1088,7 @@

                      Parameters:

                      Screen.display_png (filename, x, y)
            - display png. + display png

            Parameters:

            @@ -1081,6 +1110,148 @@

            Parameters:

            +
            +
            + + Screen.load_png (filename) +
            +
            + load png into an image buffer + + +

            Parameters:

            +
              +
            • filename + +
            • +
            + + + + + +
            +
            + + Screen.create_image (width, height) +
            +
            + create an image buffer + + +

            Parameters:

            +
              +
            • width + number + image witdth +
            • +
            • height + number + image height +
            • +
            + + + + + +
            +
            + + Screen.display_image (image, x, y) +
            +
            + display image buffer + + +

            Parameters:

            +
              +
            • image + +
            • +
            • x + number + x position +
            • +
            • y + number + y position +
            • +
            + + + + + +
            +
            + + Screen.display_image_region (image, left, top, width, height, x, y) +
            +
            + display sub-region image buffer + + +

            Parameters:

            +
              +
            • image + +
            • +
            • left + number + left inset within image +
            • +
            • top + number + top inset within image +
            • +
            • width + number + width from right within image +
            • +
            • height + number + height from top within image +
            • +
            • x + number + x position +
            • +
            • y + number + y position +
            • +
            + + + + + +
            +
            + + Screen.draw_to (image, func) +
            +
            + direct screen drawing within the provide function into the image instead of the screen + + +

            Parameters:

            +
              +
            • image + image + the image to draw into +
            • +
            • func + function + function called to perform drawing +
            • +
            + + + + +
            @@ -1142,7 +1313,7 @@

            Parameters:

            height
          • s - string + string screen content to set
          @@ -1266,7 +1437,7 @@

          Usage:

          generated by LDoc 1.4.6 -Last updated 2022-02-01 08:10:05 +Last updated 2023-03-17 18:54:24
          diff --git a/doc/modules/script.html b/doc/modules/script.html index f7f0776d0..cd4e80f7d 100644 --- a/doc/modules/script.html +++ b/doc/modules/script.html @@ -47,6 +47,7 @@

          Modules

        • core.crow.quote
        • encoders
        • engine
        • +
        • gamepad
        • grid
        • hid
        • keyboard
        • @@ -76,6 +77,7 @@

          Modules

        • lib.Graph
        • lib.intonation
        • Lattice
        • +
        • lib.lfo
        • lib.listselect
        • lib.MusicUtil
        • lib.pattern
        • @@ -84,6 +86,7 @@

          Modules

        • lib.test.luaunit
        • lib.textentry
        • lib.textentry_kbd
        • +
        • lib.timeline
        • lib.UI
        • lib.util
        • lib.voice
        • @@ -151,7 +154,7 @@

          Functions

          Parameters:

          • filename - string + string file to load. leave blank to reload current file.
          @@ -186,7 +189,7 @@

          Parameters:

          Parameters:

          @@ -194,7 +197,7 @@

          Parameters:

          Returns:

            - table + table meta table with metadata
          @@ -209,7 +212,7 @@

          Returns:

          generated by LDoc 1.4.6 -Last updated 2022-02-01 08:10:05 +Last updated 2023-03-17 18:54:24
          diff --git a/doc/modules/softcut.html b/doc/modules/softcut.html index b89d85d0d..92a02b6de 100644 --- a/doc/modules/softcut.html +++ b/doc/modules/softcut.html @@ -47,6 +47,7 @@

          Modules

        • core.crow.quote
        • encoders
        • engine
        • +
        • gamepad
        • grid
        • hid
        • keyboard
        • @@ -76,6 +77,7 @@

          Modules

        • lib.Graph
        • lib.intonation
        • Lattice
        • +
        • lib.lfo
        • lib.listselect
        • lib.MusicUtil
        • lib.pattern
        • @@ -84,6 +86,7 @@

          Modules

        • lib.test.luaunit
        • lib.textentry
        • lib.textentry_kbd
        • +
        • lib.timeline
        • lib.UI
        • lib.util
        • lib.voice
        • @@ -330,12 +333,25 @@

          render_buffer (ch, start, dur, samples) request snapshot of buffer content for region. + + + process_buffer (ch, start, dur, sleep_time, block_size) + request that softcut process buffer with user-defined process function + usage: clock.run(softcut.process_buffer(ch, start, dur, sleep_time, block_size)) event_render (func) set function for render callback. + process_func (func) + set function for processing of buffer. + + + event_done (func) + set function for job callback. + + query_position (i) query playback position @@ -1636,7 +1652,7 @@

          Parameters:

          Parameters:

          • file - string + string : input file path
          • start_src @@ -1685,7 +1701,7 @@

            Parameters:

            Parameters:

            • file - string + string : input file path
            • start_src @@ -1726,7 +1742,7 @@

              Parameters:

              Parameters:

              • file - string + string : output file path
              • start @@ -1759,7 +1775,7 @@

                Parameters:

                Parameters:

                • file - string + string : output file path
                • start @@ -1830,6 +1846,44 @@

                  Parameters:

                  + +
                  + + process_buffer (ch, start, dur, sleep_time, block_size) +
                  +
                  + request that softcut process buffer with user-defined process function + usage: clock.run(softcut.process_buffer(ch, start, dur, sleep_time, block_size)) + + +

                  Parameters:

                  +
                    +
                  • ch + integer + : buffer channel index (1-based) +
                  • +
                  • start + number + : beginning of region in seconds +
                  • +
                  • dur + number + : length of region in seconds +
                  • +
                  • sleep_time + number + : amount of time to wait between blocks in seconds; default 0.2 +
                  • +
                  • block_size + integer + : number of samples per block; default 1024 +
                  • +
                  + + + + +
                  @@ -1851,6 +1905,48 @@

                  Parameters:

                  + +
                  + + process_func (func) +
                  +
                  + set function for processing of buffer. use process_buffer to apply. + + +

                  Parameters:

                  +
                    +
                  • func + function + : called when buffer content is processed. args: (sample_index, current_value) +
                  • +
                  + + + + + +
                  +
                  + + event_done (func) +
                  +
                  + set function for job callback. called when process_buffer is complete. + + +

                  Parameters:

                  +
                    +
                  • func + function + : called when buffer job is complete. args: (ch, job_type, num_to_expect) +
                  • +
                  + + + + +
                  @@ -1926,7 +2022,7 @@

                  Parameters:

                  Returns:

                    - table + table table of parameter states for each voice
                  @@ -1962,7 +2058,7 @@

                  Returns:

                  generated by LDoc 1.4.6 -Last updated 2022-02-01 08:10:05 +Last updated 2023-03-17 18:54:24
                  diff --git a/lua/core/arc.lua b/lua/core/arc.lua index fb2304009..5ec137ab0 100644 --- a/lua/core/arc.lua +++ b/lua/core/arc.lua @@ -157,6 +157,12 @@ function Arc.cleanup() dev.delta = nil dev.key = nil end + + Arc.add = function(dev) + print("arc added:", dev.id, dev.name, dev.serial) + end + + Arc.remove = function(dev) end end -- @static diff --git a/lua/core/audio.lua b/lua/core/audio.lua index 61d2049b6..864213e3e 100644 --- a/lua/core/audio.lua +++ b/lua/core/audio.lua @@ -496,7 +496,7 @@ function Audio.add_params() norns.state.mix.tape = x end) params:set_save("tape_level", false) - params:add_separator() + params:add_separator("monitoring_separator", "monitoring") params:add_option("monitor_mode", "monitor mode", {"STEREO", "MONO"}, norns.state.mix.monitor_mode) params:set_action("monitor_mode", diff --git a/lua/core/clock.lua b/lua/core/clock.lua old mode 100644 new mode 100755 index ae14dfb26..5ddf8dd0e --- a/lua/core/clock.lua +++ b/lua/core/clock.lua @@ -51,7 +51,6 @@ clock.sync = function(...) return coroutine.yield(SCHEDULE_SYNC, ...) end - -- todo: use c api instead clock.resume = function(coro_id, ...) local coro = clock.threads[coro_id] @@ -88,6 +87,7 @@ clock.cleanup = function() clock.transport.start = nil clock.transport.stop = nil + clock.tempo_change_handler = nil end --- select the sync source. @@ -124,9 +124,21 @@ end clock.transport = {} +--- static callback when clock transport is started; +-- user scripts can redefine +-- @static clock.transport.start = nil + +--- static callback when clock transport is stopped; +-- user scripts can redefine +-- @static clock.transport.stop = nil +--- static callback when clock tempo is adjusted via PARAMETERS > CLOCK > tempo; +-- user scripts can redefine +-- @static +-- @param bpm : the new tempo +clock.tempo_change_handler = nil clock.internal = {} @@ -188,7 +200,8 @@ end function clock.add_params() - params:add_group("CLOCK", 9) + local send_midi_clock = {} + params:add_group("CLOCK", 27) params:add_option("clock_source", "source", {"internal", "midi", "link", "crow"}, norns.state.clock.source) @@ -210,6 +223,9 @@ function clock.add_params() if source == "internal" then clock.internal.set_tempo(bpm) elseif source == "link" then clock.link.set_tempo(bpm) end norns.state.clock.tempo = bpm + if clock.tempo_change_handler ~= nil then + clock.tempo_change_handler(bpm) + end end) params:set_save("clock_tempo", false) params:add_trigger("clock_reset", "reset") @@ -219,6 +235,7 @@ function clock.add_params() if source == "internal" then clock.internal.start() elseif source == "link" then print("link reset not supported") end end) + params:add_separator("link_separator", "link") params:add_number("link_quantum", "link quantum", 1, 32, norns.state.clock.link_quantum) params:set_action("link_quantum", function(x) @@ -233,15 +250,32 @@ function clock.add_params() norns.state.clock.link_start_stop_sync = x end) params:set_save("link_start_stop_sync", false) - local clock_table = {"off"} + params:add_separator("midi_clock_out_separator", "midi clock out") for i = 1,16 do - local short_name = string.len(midi.vports[i].name) < 12 and midi.vports[i].name or util.acronym(midi.vports[i].name) - clock_table[i+1] = "port "..(i)..""..(midi.vports[i].name ~= "none" and (": "..short_name) or "") + local short_name = string.len(midi.vports[i].name) <= 20 and midi.vports[i].name or util.acronym(midi.vports[i].name) + params:add_binary("clock_midi_out_"..i, i..". "..short_name, "toggle", norns.state.clock.midi_out[i]) + params:set_action("clock_midi_out_"..i, + function(x) + if x == 1 then + if not tab.contains(send_midi_clock,i) then + table.insert(send_midi_clock,i) + end + else + if tab.contains(send_midi_clock,i) then + table.remove(send_midi_clock,tab.key(send_midi_clock, i)) + end + end + norns.state.clock.midi_out[i] = x + end + ) + if short_name ~= "none" and midi.vports[i].connected then + params:show("clock_midi_out_"..i) + else + params:hide("clock_midi_out_"..i) + end + params:set_save("clock_midi_out_"..i, false) end - params:add_option("clock_midi_out", "midi out", - clock_table, norns.state.clock.midi_out) - params:set_action("clock_midi_out", function(x) norns.state.clock.midi_out = x end) - params:set_save("clock_midi_out", false) + params:add_separator("crow_clock_separator", "crow") params:add_option("clock_crow_out", "crow out", {"off", "output 1", "output 2", "output 3", "output 4"}, norns.state.clock.crow_out) params:set_action("clock_crow_out", function(x) @@ -282,15 +316,12 @@ function clock.add_params() end) -- executes midi out (needs a subtick) - -- FIXME: lots of if's every tick blah clock.run(function() while true do clock.sync(1/24) - local midi_out = params:get("clock_midi_out")-1 - if midi_out > 0 then - if midi.vports[midi_out].name ~= "none" then - midi.vports[midi_out]:clock() - end + for i = 1,#send_midi_clock do + local port = send_midi_clock[i] + midi.vports[port]:clock() end end end) @@ -300,7 +331,11 @@ function clock.add_params() while true do if params:get("clock_source") ~= 1 then local external_tempo = math.floor(clock.get_tempo() + 0.5) + local previous_val = params:get("clock_tempo") params:set("clock_tempo", external_tempo, true) + if clock.tempo_change_handler ~= nil and previous_val ~= external_tempo then + clock.tempo_change_handler(external_tempo) + end end clock.sleep(1) @@ -340,4 +375,4 @@ end -------------------------------------------------------------------------------- ]] -return clock +return clock \ No newline at end of file diff --git a/lua/core/gamepad.lua b/lua/core/gamepad.lua new file mode 100644 index 000000000..4a27dfec5 --- /dev/null +++ b/lua/core/gamepad.lua @@ -0,0 +1,494 @@ +--- gamepad +-- @module gamepad + + +-- ------------------------------------------------------------------------ +-- deps + +local hid_events = require "hid_events" + + +-- ------------------------------------------------------------------------ +-- debugging + +local debug_level = 0 + + +-- ------------------------------------------------------------------------ +-- state + +gamepad = {} + +-- NB: lots of gamepads like to use their own codes, different from what appears in core/hid_events.lua +gamepad.model = require 'gamepad_model/index' + +--- button states +gamepad.state = { + DPDOWN = false, + DPUP = false, + LDOWN = false, + LUP = false, + LLEFT = false, + LRIGHT = false, + RDOWN = false, + RUP = false, + RLEFT = false, + RRIGHT = false, + TLEFT = false, + TRIGHT = false, + -- aggregated + DOWN = false, + UP = false, + LEFT = false, + RIGHT = false, +} + +-- cache to prevent spamming when val=0 (origin) +local prev_dir = { + ABS_X = 0, + ABS_Y = 0, + ABS_RX = 0, + ABS_RY = 0, + ABS_Z = 0, + ABS_RZ = 0, +} +local prev_dir_v = { + ABS_X = 0, + ABS_Y = 0, + ABS_RX = 0, + ABS_RY = 0, + ABS_Z = 0, + ABS_RZ = 0, +} + + +-- ------------------------------------------------------------------------ +-- script lifecycle + +-- clear callbacks +function gamepad.clear() + -- axis callbacks + -- - directional pad, axis either X or Y + gamepad.dpad = function(axis, sign) end + -- - analog pads, sensor_axis either dpady, dpadx, lefty, leftx, righty, rightx, triggerleft, triggerright + gamepad.analog = function(sensor_axis, val, half_reso) end + -- - all axis input (both digital & analog), value (sign) converted to digital (-1,0,1) + gamepad.axis = function(sensor_axis, sign) end + + -- button press callback + gamepad.button = function(button_name, state) end +end + +--- macro state shortcuts +function gamepad.up() + return gamepad.state.DPUP or gamepad.state.LUP end +function gamepad.down() + return gamepad.state.DPDOWN or gamepad.state.LDOWN end +function gamepad.left() + return gamepad.state.DPLEFT or gamepad.state.LLEFT end +function gamepad.right() + return gamepad.state.DPRIGHT or gamepad.state.LRIGHT end + + +-- ------------------------------------------------------------------------ +-- axis lookup / mapping + +-- lookup table for keycode -> axis keycode +-- prevents (expensive) call to `gamepad.code_2_keycode` +function gamepad.axis_code_2_keycode(code) + local mapping = { + [0x00] = 'ABS_X', + [0x01] = 'ABS_Y', + [0x02] = 'ABS_Z', + [0x03] = 'ABS_RX', + [0x04] = 'ABS_RY', + [0x05] = 'ABS_RZ', + [0x10] = 'ABS_HAT0X', + [0x11] = 'ABS_HAT0Y', + } + return mapping[code] +end + +function gamepad.is_axis_keycode_analog(axis_keycode) + return tab.contains({'ABS_Y', 'ABS_X', + 'ABS_RY', 'ABS_RX', + 'ABS_Z', 'ABS_RZ',}, axis_keycode) +end + +-- axis keycode -> normalized sensor axis +-- possible return values +-- - dpady / dpadx (dpad) +-- - lefty / leftx (left analog stick) +-- - righty / rightx (right stick) +-- - triggerleft / triggerright (analog shoulder buttons) +function gamepad.axis_keycode_to_sensor_axis(gamepad_conf, axis_keycode) + return gamepad_conf.axis_mapping[axis_keycode] +end + +-- normalized sensor axis -> normalized states +function gamepad.sensor_axis_to_states(sensor_axis) + local mapping = { + dpady = {'DPDOWN', 'DPUP'}, + dpadx = {'DPRIGHT', 'DPLEFT'}, + lefty = {'LDOWN', 'LUP'}, + leftx = {'LRIGHT', 'LLEFT'}, + righty = {'RDOWN', 'RUP'}, + rightx = {'RRIGHT', 'RLEFT'}, + triggerleft = {nil, 'TLEFT'}, + triggerright = {nil, 'TRIGHT'}, + } + return mapping[sensor_axis] +end + +-- normalized sensor axis -> generic axis +function gamepad.sensor_axis_to_axis(sensor_axis) + local mapping = { + dpady = 'Y', + dpadx = 'X', + -- lefty = 'Y', + -- leftx = 'X', + -- righty = 'RY', + -- rightx = 'RX', + } + return mapping[sensor_axis] +end + + +-- ------------------------------------------------------------------------ +-- state modifiers + +function gamepad.register_analog_button_state(sensor_axis, state, inverted, do_log_event) + local states = gamepad.sensor_axis_to_states(sensor_axis) + + if states == nil then + return + end + + local down_state = states[2] + + if state or inverted then + gamepad.state[down_state] = true + else + gamepad.state[down_state] = false + end +end + +function gamepad.register_direction_state(sensor_axis, sign, inverted, do_log_event) + local states = gamepad.sensor_axis_to_states(sensor_axis) + + if states == nil then + return + end + + local s1 = states[1] + local s2 = states[2] + + if sign == 0 then + gamepad.state[s1] = false + gamepad.state[s2] = false + else + if inverted then + sign = sign * - 1 + end + if sign > 0 then + gamepad.state[s1] = true + gamepad.state[s2] = false + if do_log_event and debug_level >= 1 then print("SENSOR STATE: "..s1) end + else + gamepad.state[s1] = false + gamepad.state[s2] = true + if do_log_event and debug_level >= 1 then print("SENSOR STATE: "..s2) end + end + end +end + +function gamepad.register_button_state(button_name, val) + gamepad.state[button_name] = val +end + +function gamepad.trigger_button(button_name, val) + + -- menu button + if _menu.mode then + if _menu.gamepad_button then _menu.gamepad_button(button_name, val) end + -- script button + elseif gamepad.button then gamepad.button(button_name, val) end +end + +function gamepad.trigger_axis(sensor_axis, sign) + -- menu axis + if _menu.mode then + if _menu.gamepad_axis then _menu.gamepad_axis(sensor_axis, sign) end + -- script axis + elseif gamepad.axis then gamepad.axis(sensor_axis, sign) end +end + +function gamepad.trigger_dpad(axis, sign) + -- menu dpad + if _menu.mode then + if _menu.gamepad_dpad then _menu.gamepad_dpad(axis, sign) end + -- script dpad + elseif gamepad.dpad then gamepad.dpad(axis, sign) end +end + + +-- ------------------------------------------------------------------------ +-- analog sensor 2 states + +--- Returns true if value for axis is around origin +-- i.e. when joystick / d-pad is not actioned +function gamepad.is_analog_origin(gamepad_conf, axis_keycode, value) + local origin = gamepad_conf.analog_axis_o[axis_keycode] + if origin == nil then + origin = 0 + end + local noize_margin = gamepad_conf.analog_axis_o_margin[axis_keycode] + if noize_margin == nil then + noize_margin = 0 + end + return ( value >= (origin - noize_margin) and value <= (origin + noize_margin)) +end + +local function normalized_analog_button_val(gamepad_conf, axis_keycode, val) + local reso = gamepad_conf.analog_axis_resolution[axis_keycode] + + if gamepad.is_analog_origin(gamepad_conf, axis_keycode, val) then + val = 0 + end + + local state = false + local sign = 0 + if val > reso * 2/3 then + state = true + end + + if gamepad_conf.axis_invert[axis_keycode] then + state = not state + end + + if state then + sign = 1 + end + + return {val, state, sign} +end + +local function normalized_analog_direction_val(gamepad_conf, axis_keycode, val) + local origin = gamepad_conf.analog_axis_o[axis_keycode] + local reso = gamepad_conf.analog_axis_resolution[axis_keycode] + local half_reso = reso / 2 + + if gamepad.is_analog_origin(gamepad_conf, axis_keycode, val) then + val = 0 + else + val = val - origin + end + + if val <= half_reso * 2/3 and val >= - half_reso * 2/3 then + sign = 0 + else + sign = val < 0 and -1 or 1 + end + + return {val, sign} +end + + +-- ------------------------------------------------------------------------ +-- incoming events + +function gamepad.process(guid, typ, code, val, do_log_event) + + local event_code_type + for k, v in pairs(hid_events.types) do + if tonumber(v) == typ then + event_code_type = k + break + end + end + + local gamepad_conf = gamepad.model[guid] + local gamepad_alias = gamepad_conf.alias + + local do_log_event = gamepad.is_loggable_event(gamepad_conf, event_code_type, code, val) + + if do_log_event and debug_level >= 2 then + local keycode = gamepad.code_2_keycode(event_code_type, code) + local msg = "hid.event" .."\t".. gamepad_alias .."\t".. " type: " .. typ .."\t".. " code: " .. code .."\t".. " value: "..val + if keycode then + msg = msg .."\t".. " keycode: "..keycode + end + print(msg) + end + + local button_name + + if event_code_type == "EV_ABS" then + local axis_keycode = gamepad.axis_code_2_keycode(code) + local sensor_axis = gamepad.axis_keycode_to_sensor_axis(gamepad_conf, axis_keycode) + local axis = gamepad.sensor_axis_to_axis(sensor_axis) + + local sign + + -- if not recognized axis then + if not sensor_axis then + return + end + + -- TODO: handle relative axes? + + local is_analog = gamepad.is_axis_keycode_analog(axis_keycode) + local is_dpad = true + if is_analog and (not gamepad_conf.dpad_is_analog) then + is_dpad = false + end + + local is_button = false + local btn_state = false + button_name = gamepad.analog_axis_keycode_2_button(gamepad_conf, axis_keycode) + if button_name then + is_button = true + end + + if is_analog then + + if is_button then + local normalized = normalized_analog_button_val(gamepad_conf, axis_keycode, val) + val = normalized[1] + btn_state = normalized[2] + sign = normalized[3] + else + local normalized = normalized_analog_direction_val(gamepad_conf, axis_keycode, val) + val = normalized[1] + sign = normalized[2] + end + + -- first callback -> TODO: kinda wrong to do it before btn states? + + else -- digital + sign = val + if sign ~= 0 then + sign = val < 0 and -1 or 1 + end + end + + -- register state + local btn_val = 0 + if is_button then + gamepad.register_analog_button_state(sensor_axis, btn_state, gamepad_conf.axis_invert[axis_keycode]) + if do_log_event and debug_level >= 1 then print("BUTTON: " .. button_name .. " " .. tostring(btn_state)) end + gamepad.register_button_state(button_name, btn_state) + else + gamepad.register_direction_state(sensor_axis, sign, gamepad_conf.axis_invert[axis_keycode], do_log_event) + end + + -- callbacks + -- - gamepad.analog() + if is_analog and val ~= prev_dir_v[axis_keycode] then + local reso = gamepad_conf.analog_axis_resolution[axis_keycode] + local half_reso = reso / 2 + local reported_reso = is_button and reso or half_reso + local dbg_reso = (val >= 0) and reported_reso or -reported_reso + if debug_level >= 2 then print("ANALOG: " .. sensor_axis .. " " .. val .. "/" .. dbg_reso) end + prev_dir_v[axis_keycode] = val + if _menu.mode then + if _menu.gamepad_analog then _menu.gamepad_analog(sensor_axis, val, reported_reso) end + elseif gamepad.analog then gamepad.analog(sensor_axis, val, reported_reso) end + end + + -- - gamepad.axis() + gamepad.axis() / gamepad.button() + if sign ~= prev_dir[axis_keycode] then + prev_dir[axis_keycode] = sign + + if do_log_event and debug_level >= 1 then print("AXIS: " .. sensor_axis .. " " .. sign) end + gamepad.trigger_axis(sensor_axis, sign) + + if is_button then + gamepad.trigger_button(button_name, btn_val) + end + + if is_dpad and axis then + -- REVIEW: should call again `gamepad.register_direction_state` in that case, right? + -- for edge case when `gamepad.trigger_dpad` is called by script to simulate user input + gamepad.trigger_dpad(axis, sign) + end + end + end + + + if event_code_type == "EV_KEY" then + button_name = gamepad.code_2_button(gamepad_conf, code) + if button_name then + + local btn_state = false + if val > 0 then + btn_state = true + end + + if do_log_event and debug_level >= 1 then print("BUTTON: " .. button_name .. " " .. tostring(btn_state)) end + + gamepad.register_button_state(button_name, btn_state) + gamepad.trigger_button(button_name, val) + + end + end +end + + +-- ------------------------------------------------------------------------ +-- debug + +--- Predicate that returns true only on non-reset values (i.e. on key/joystick presses) +function gamepad.is_loggable_event(gamepad_conf,event_code_type,code,val) + if (event_code_type == "EV_KEY") then + return true + end + if event_code_type == "EV_ABS" then + local axis_keycode = gamepad.code_2_keycode(event_code_type, code) + if gamepad.is_axis_keycode_analog(axis_keycode) then + return not gamepad.is_analog_origin(gamepad_conf,axis_keycode,val) + else + return (val ~= 0) + end + end +end + + +-- ------------------------------------------------------------------------ +-- hid event parsing + + +--- Returns button name associated w/ (non-axis) key code +function gamepad.code_2_button(gamepad_conf, code) + if gamepad_conf.button == nil then return end + local code_2_button = tab.invert(gamepad_conf.button) + return code_2_button[code] +end + +--- Returns button name associated w/ analog axis key code +function gamepad.analog_axis_keycode_2_button(gamepad_conf, axis_keycode) + if gamepad_conf.analog_button == nil then return end + local code_2_button = tab.invert(gamepad_conf.analog_button) + return code_2_button[axis_keycode] +end + +--- Returns event key name associated w/ key code +-- this is not lightweight so should only be used in debug statements +function gamepad.code_2_keycode(event_code_type, code) + for k, v in pairs(hid_events.codes) do + if tonumber(v) == code then + if util.string_starts(k, gamepad.event_code_type_2_key_prfx(event_code_type)) then + return k + end + end + end +end + +function gamepad.event_code_type_2_key_prfx(event_code_type) + return string.sub(event_code_type, -3) +end + + +-- ------------------------------------------------------------------------ + +return gamepad diff --git a/lua/core/gamepad_model/ibufalo_classic_usb.lua b/lua/core/gamepad_model/ibufalo_classic_usb.lua new file mode 100644 index 000000000..a1829dc0d --- /dev/null +++ b/lua/core/gamepad_model/ibufalo_classic_usb.lua @@ -0,0 +1,37 @@ +return { + alias = "iBUFFALO Classic USB Gamepad", + guid = "03000000830500006020000010010000", + analog_axis_o = { + ABS_X = 127, + ABS_Y = 127 + }, + analog_axis_o_margin = { + ABS_X = 3, + ABS_Y = 3 + }, + analog_axis_resolution = { + ABS_X = 255, + ABS_Y = 255 + }, + axis_invert = { + ABS_X = false, + ABS_Y = false + }, + axis_mapping = { + ABS_X = "dpadx", + ABS_Y = "dpady" + }, + button = { + A = 288, + B = 289, + L1 = 292, + R1 = 293, + SELECT = 294, + START = 295, + X = 290, + Y = 291 + }, + analog_button = {}, + dpad_is_analog = true, + hid_name = "USB,2-axis 8-button gamepad " +} diff --git a/lua/core/gamepad_model/index.lua b/lua/core/gamepad_model/index.lua new file mode 100644 index 000000000..f68353a45 --- /dev/null +++ b/lua/core/gamepad_model/index.lua @@ -0,0 +1,24 @@ + +local models = {} + +-- bundled w/ core +models['03000000830500006020000010010000'] = require 'gamepad_model/ibufalo_classic_usb' +models['030000005e0400008e02000014010000'] = require 'gamepad_model/xbox_360' +models['03000000790000001100000010010000'] = require 'gamepad_model/retro_controller' + +-- user-local +local user_conf_dir = _path.data .. "gamepads/" +if util.file_exists(user_conf_dir) then + local user_confs = util.scandir(user_conf_dir) + for _, conf_file in pairs(user_confs) do + if conf_file:sub(-#'.lua') == '.lua' then + local conf_file_sans_ext = conf_file:gsub("%.lua", "") + print("loading user gamepad conf: "..user_conf_dir..conf_file) + local module_path = user_conf_dir .. conf_file_sans_ext + local user_conf = require(module_path) + models[user_conf.guid] = user_conf + end + end +end + +return models diff --git a/lua/core/gamepad_model/retro_controller.lua b/lua/core/gamepad_model/retro_controller.lua new file mode 100644 index 000000000..a82270b31 --- /dev/null +++ b/lua/core/gamepad_model/retro_controller.lua @@ -0,0 +1,32 @@ +-- Retrolink B00GWKL3Y4 ((S)NES-style) +-- also sold by under other brands: iNNext, Elecom... + +return { + alias = "Retro Controller", + guid = "03000000790000001100000010010000", + analog_axis_o = { + ABS_X = 127, + ABS_Y = 127 + }, + analog_axis_o_margin = {}, + analog_axis_resolution = { + ABS_X = 256, + ABS_Y = 256 + }, + axis_invert = { + ABS_X = false, + ABS_Y = false + }, + axis_mapping = { + ABS_X = "dpadx", + ABS_Y = "dpady" + }, + button = { + A = 289, + B = 290, + SELECT = 296, + START = 297 + }, + dpad_is_analog = true, + hid_name = "USB Gamepad " +} diff --git a/lua/core/gamepad_model/xbox_360.lua b/lua/core/gamepad_model/xbox_360.lua new file mode 100644 index 000000000..a62b2acaa --- /dev/null +++ b/lua/core/gamepad_model/xbox_360.lua @@ -0,0 +1,55 @@ +return { + alias = "Xbox 360 Controller", + guid = "030000005e0400008e02000014010000", + analog_axis_o = { + ABS_RX = 0, + ABS_RY = 0, + ABS_X = 0, + ABS_Y = 0, + ABS_Z = 0, + ABS_RZ = 0 + }, + analog_axis_o_margin = {}, + analog_axis_resolution = { + ABS_RX = 65535, + ABS_RY = 65535, + ABS_X = 65535, + ABS_Y = 65535, + ABS_Z = 255, + ABS_RZ = 255 + }, + axis_invert = { + ABS_HAT0X = false, + ABS_HAT0Y = false, + ABS_RX = false, + ABS_RY = false, + ABS_X = false, + ABS_Y = false + }, + axis_mapping = { + ABS_HAT0X = "dpadx", + ABS_HAT0Y = "dpady", + ABS_RX = "rightx", + ABS_RY = "righty", + ABS_X = "leftx", + ABS_Y = "lefty", + ABS_Z = "triggerleft", + ABS_RZ = "triggerright" + }, + button = { + A = 305, + B = 304, + L1 = 310, + R1 = 311, + SELECT = 314, + START = 315, + X = 308, + Y = 307 + }, + analog_button = { + L2 = "ABS_Z", + R2 = "ABS_RZ", + }, + dpad_is_analog = false, + hid_name = "Controller" +} diff --git a/lua/core/grid.lua b/lua/core/grid.lua old mode 100644 new mode 100755 index 3efa8567c..a9aed0cc4 --- a/lua/core/grid.lua +++ b/lua/core/grid.lua @@ -79,14 +79,14 @@ end -- @param dev : a Grid table function Grid.remove(dev) end --- set grid rotation. +--- set grid rotation. -- @tparam integer val : rotation 0,90,180,270 as [0, 3] function Grid:rotation(val) _norns.grid_set_rotation(self.dev, val) end --- enable/disable grid tilt. +--- enable/disable grid tilt. -- @tparam integer id : sensor -- @tparam integer val : off/on [0, 1] function Grid:tilt_enable(id, val) @@ -144,6 +144,12 @@ function Grid.cleanup() dev.key = nil dev.tilt = nil end + + Grid.add = function(dev) + print("grid added:", dev.id, dev.name, dev.serial) + end + + Grid.remove = function(dev) end end -- update devices. diff --git a/lua/core/hid.lua b/lua/core/hid.lua old mode 100644 new mode 100755 index f620afc2e..46d04ce52 --- a/lua/core/hid.lua +++ b/lua/core/hid.lua @@ -6,6 +6,8 @@ local hid_events = require 'hid_events' local hid_device_class = require 'hid_device_class' local tab = require 'tabutil' +local gamepad = require 'gamepad' + local Hid = {} Hid.__index = Hid @@ -30,12 +32,13 @@ end -- @tparam table types : array of supported event types. keys are type codes, values are strings -- @tparam table codes : array of supported codes. each entry is a table of codes of a given type. subtables are indexed by supported code numbers; values are code names -- @tparam userdata dev : opaque pointer to device -function Hid.new(id, name, types, codes, dev) +function Hid.new(id, name, types, codes, dev, guid) local device = setmetatable({}, Hid) device.id = id device.name = vport.get_unique_device_name(name, Hid.devices) device.dev = dev -- opaque pointer + device.guid = guid -- SDL format GUID device.event = nil -- event callback device.remove = nil -- device unplug callback device.port = nil @@ -44,8 +47,8 @@ function Hid.new(id, name, types, codes, dev) device.types = {} device.codes = {} -- types table shall be a simple array with default indexing - for k,v in pairs(types) do - device.types[k] = v + for k,v in pairs(types) do + device.types[k] = v end -- codes table shall be an associate array indexed by type for k,v in pairs(codes) do @@ -57,7 +60,7 @@ function Hid.new(id, name, types, codes, dev) device.is_ascii_keyboard = hid_device_class.is_ascii_keyboard(device) device.is_mouse = hid_device_class.is_mouse(device) - + device.is_gamepad = hid_device_class.is_gamepad(device) -- autofill next postiion local connected = {} @@ -81,10 +84,10 @@ end -- @static -- @param dev : a Hid table function Hid.add(dev) - print("HID device was added:", dev.id, dev.name) + print("HID device was added:", dev.id, dev.name, dev.guid) if dev.is_ascii_keyboard then print("this appears to be an ASCII keyboard!") end if dev.is_mouse then print("this appears to be a mouse!") end - + if dev.is_gamepad then print("this appears to be a gamepad!") end end --- static callback when any hid device is removed; @@ -93,7 +96,6 @@ end -- @param dev : a Hid table function Hid.remove(dev) end - --- create device, returns object with handler and send -- @static function Hid.connect(n) @@ -112,6 +114,15 @@ function Hid.cleanup() for _, dev in pairs(Hid.devices) do dev.event = nil end + + Hid.add = function(dev) + print("HID device was added:", dev.id, dev.name) + if dev.is_ascii_keyboard then print("this appears to be an ASCII keyboard!") end + if dev.is_mouse then print("this appears to be a mouse!") end + if dev.is_gamepad then print("this appears to be a gamepad!") end + end + + Hid.remove = function(dev) end end function Hid.update_devices() @@ -136,8 +147,8 @@ end _norns.hid = {} -- hid devices -_norns.hid.add = function(id, name, types, codes, dev) - local g = Hid.new(id, name, types, codes, dev) +_norns.hid.add = function(id, name, types, codes, dev, guid) + local g = Hid.new(id, name, types, codes, dev, guid) Hid.devices[id] = g Hid.update_devices() if Hid.add ~= nil then Hid.add(g) end @@ -171,7 +182,9 @@ _norns.hid.event = function(id, type, code, value) end if device.is_ascii_keyboard then - keyboard.process(type,code,value) + keyboard.process(type, code, value) + elseif device.is_gamepad then + gamepad.process(device.guid, type, code, value) end else error('no entry for hid '..id) diff --git a/lua/core/hid_device_class.lua b/lua/core/hid_device_class.lua index 691a5f3fd..942da953d 100644 --- a/lua/core/hid_device_class.lua +++ b/lua/core/hid_device_class.lua @@ -1,13 +1,16 @@ local hid_events = require 'hid_events' local tab = require 'tabutil' +local gamepad_models = require 'gamepad_model/index' + + local HidDeviceClass = {} HidDeviceClass.is_ascii_keyboard = function(device) local types_inv = tab.invert(device.types) local type_key = hid_events.types.EV_KEY --- an ascii keyboard should support key events - if types_inv[type_key] == nil then + if types_inv[type_key] == nil then return false end local key_codes_inv = tab.invert(device.codes[type_key]) @@ -25,12 +28,12 @@ HidDeviceClass.is_mouse = function(device) local types_inv = tab.invert(device.types) local type_key = hid_events.types.EV_KEY local type_rel = hid_events.types.EV_REL - -- a mouse should support relative X/Y movement events, + -- a mouse should support relative X/Y movement events, -- and at least 1 button - if types_inv[type_key] == nil then + if types_inv[type_key] == nil then return false end - if types_inv[type_rel] == nil then + if types_inv[type_rel] == nil then return false end local key_codes_inv = tab.invert(device.codes[type_key]) @@ -50,12 +53,17 @@ HidDeviceClass.is_mouse = function(device) return true end -HidDeviceClass.isGamePad = function(device) - -- TODO +HidDeviceClass.is_gamepad = function(device) + -- NB: we could test for support for REL_ABS & REL_KEY + -- BUT each controller brand likes to reinvent the keycode they send + -- (+ other ) + -- hence it's safer to have a whitelist of models + -- + a wizard script for people to register new ones + return (device.guid ~= nil and gamepad_models[device.guid] ~= nil) end -HidDeviceClass.isTablet = function(device) - -- TODO +HidDeviceClass.is_tablet = function(device) + -- TODO end --- ... other TODO? diff --git a/lua/core/keyboard.lua b/lua/core/keyboard.lua index d224b133a..0894abee9 100644 --- a/lua/core/keyboard.lua +++ b/lua/core/keyboard.lua @@ -36,6 +36,7 @@ keyboard.keymap.sk = require 'core/keymap/sk' keyboard.keymap.si = require 'core/keymap/si' keyboard.keymap.es = require 'core/keymap/es' keyboard.keymap.se = require 'core/keymap/se' +keyboard.keymap.uk = require 'core/keymap/uk' keyboard.selected_map = "us" local km = keyboard.keymap[keyboard.selected_map] @@ -88,9 +89,24 @@ function keyboard.clear() keyboard.char = function() end end ---- key code callback, script should redefine +--- key code callback, script should redefine. +-- use this primarily for responding to physical key presses ('LEFTSHIFT', +-- 'ENTER', 'F7', etc) not printable characters, as it does not adjust to +-- the setting of the user's keyboard layout, including national variant. +-- if you want to detect the character, use @{char} or @{code_to_char}. +-- +-- @tparam string key a code representing the physical key pressed. +-- @tparam number value 0 = released, 1 = pressed, 2 = pressed and held. +-- function keyboard.code(key, value) end ---- key character callback, script should redefine + +--- key character callback, script should redefine. +-- will only be called for printable characters such as letters, numbers +-- and punctuation - and space. will take account of the setting of the user's keyboard +-- layout, including national variant. +-- +-- @tparam string ch the printable character intended to be generated. +-- function keyboard.char(ch) end @@ -112,10 +128,20 @@ function keyboard.meta() function keyboard.process(type,code,value) local c = keyboard.codes[code] + if c == nil then + return + end -- textentry keycode if te_kbd_cb.code then te_kbd_cb.code(c,value) + -- menu screen goto + elseif (c=="F1" or c=="F2" or c=="F3" or c=="F4") and value==1 then + _menu.set_mode(true) + _menu.keycode(c,value) + -- toggle menu with F5 + elseif (c=="F5" and value==1) then + _menu.set_mode(not _menu.mode) -- menu keycode elseif _menu.mode then _menu.keycode(c,value) -- script keycode @@ -123,23 +149,37 @@ function keyboard.process(type,code,value) keyboard.state[c] = value>0 + local a = keyboard.code_to_char(c) + + if value>0 and a then + --print("char: "..a) + -- menu keychar + if te_kbd_cb.char then te_kbd_cb.char(a) + -- script keychar + elseif _menu.mode then _menu.keychar(a) + -- textentry keycode + elseif keyboard.char then keyboard.char(a) end + end + --print("kb",code,value,keyboard.codes[code]) +end + +--- convert a keyboard code to a printable character. +-- the output will be based on the setting of they user's keyboard +-- layout and the state of the modifier keys currently pressed +-- (eg a 'SHIFT' key). +-- +-- @tparam string code a code representing a physical key, +-- as passed into @{code}. +-- @treturn string a string, or nil if no conversion is possible. +-- +function keyboard.code_to_char(code) + if code == nil then return nil end + local c_mods = char_modifier.NONE if keyboard.shift() then c_mods = c_mods | char_modifier.SHIFT end if keyboard.altgr() then c_mods = c_mods | char_modifier.ALTGR end - if value>0 then - local a = km[c_mods][c] - if a then - --print("char: "..a) - -- menu keychar - if te_kbd_cb.char then te_kbd_cb.char(a) - -- script keychar - elseif _menu.mode then _menu.keychar(a) - -- textentry keycode - elseif keyboard.char then keyboard.char(a) end - end - end - --print("kb",code,value,keyboard.codes[code]) + return km[c_mods][code] end keyboard.codes = {} diff --git a/lua/core/keymap/uk.lua b/lua/core/keymap/uk.lua new file mode 100644 index 000000000..d9094b89c --- /dev/null +++ b/lua/core/keymap/uk.lua @@ -0,0 +1,116 @@ + +local char_modifier = require 'core/keymap/char_modifier' + +local k = {} + +k[char_modifier.NONE] = {} +k[char_modifier.SHIFT] = {} + +k[char_modifier.NONE].SPACE = ' ' +k[char_modifier.SHIFT].SPACE = ' ' + +k[char_modifier.NONE].A = 'a' +k[char_modifier.NONE].B = 'b' +k[char_modifier.NONE].C = 'c' +k[char_modifier.NONE].D = 'd' +k[char_modifier.NONE].E = 'e' +k[char_modifier.NONE].F = 'f' +k[char_modifier.NONE].G = 'g' +k[char_modifier.NONE].H = 'h' +k[char_modifier.NONE].I = 'i' +k[char_modifier.NONE].J = 'j' +k[char_modifier.NONE].K = 'k' +k[char_modifier.NONE].L = 'l' +k[char_modifier.NONE].M = 'm' +k[char_modifier.NONE].N = 'n' +k[char_modifier.NONE].O = 'o' +k[char_modifier.NONE].P = 'p' +k[char_modifier.NONE].Q = 'q' +k[char_modifier.NONE].R = 'r' +k[char_modifier.NONE].S = 's' +k[char_modifier.NONE].T = 't' +k[char_modifier.NONE].U = 'u' +k[char_modifier.NONE].V = 'v' +k[char_modifier.NONE].W = 'w' +k[char_modifier.NONE].X = 'x' +k[char_modifier.NONE].Y = 'y' +k[char_modifier.NONE].Z = 'z' + +k[char_modifier.SHIFT].A = 'A' +k[char_modifier.SHIFT].B = 'B' +k[char_modifier.SHIFT].C = 'C' +k[char_modifier.SHIFT].D = 'D' +k[char_modifier.SHIFT].E = 'E' +k[char_modifier.SHIFT].F = 'F' +k[char_modifier.SHIFT].G = 'G' +k[char_modifier.SHIFT].H = 'H' +k[char_modifier.SHIFT].I = 'I' +k[char_modifier.SHIFT].J = 'J' +k[char_modifier.SHIFT].K = 'K' +k[char_modifier.SHIFT].L = 'L' +k[char_modifier.SHIFT].M = 'M' +k[char_modifier.SHIFT].N = 'N' +k[char_modifier.SHIFT].O = 'O' +k[char_modifier.SHIFT].P = 'P' +k[char_modifier.SHIFT].Q = 'Q' +k[char_modifier.SHIFT].R = 'R' +k[char_modifier.SHIFT].S = 'S' +k[char_modifier.SHIFT].T = 'T' +k[char_modifier.SHIFT].U = 'U' +k[char_modifier.SHIFT].V = 'V' +k[char_modifier.SHIFT].W = 'W' +k[char_modifier.SHIFT].X = 'X' +k[char_modifier.SHIFT].Y = 'Y' +k[char_modifier.SHIFT].Z = 'Z' + +k[char_modifier.NONE]['0'] = '0' +k[char_modifier.NONE]['1'] = '1' +k[char_modifier.NONE]['2'] = '2' +k[char_modifier.NONE]['3'] = '3' +k[char_modifier.NONE]['4'] = '4' +k[char_modifier.NONE]['5'] = '5' +k[char_modifier.NONE]['6'] = '6' +k[char_modifier.NONE]['7'] = '7' +k[char_modifier.NONE]['8'] = '8' +k[char_modifier.NONE]['9'] = '9' + +k[char_modifier.SHIFT]['0'] = ')' +k[char_modifier.SHIFT]['1'] = '!' +k[char_modifier.SHIFT]['2'] = '"' +k[char_modifier.SHIFT]['3'] = '£' +k[char_modifier.SHIFT]['4'] = '$' +k[char_modifier.SHIFT]['5'] = '%' +k[char_modifier.SHIFT]['6'] = '^' +k[char_modifier.SHIFT]['7'] = '&' +k[char_modifier.SHIFT]['8'] = '*' +k[char_modifier.SHIFT]['9'] = '(' + +k[char_modifier.NONE].GRAVE = '`' +k[char_modifier.NONE].MINUS = '-' +k[char_modifier.NONE].EQUAL = '=' +k[char_modifier.NONE].LEFTBRACE = '[' +k[char_modifier.NONE].RIGHTBRACE = ']' +k[char_modifier.NONE].BACKSLASH = '#' +k[char_modifier.NONE].SEMICOLON = ';' +k[char_modifier.NONE].APOSTROPHE = '\'' +k[char_modifier.NONE].COMMA = ',' +k[char_modifier.NONE].DOT = '.' +k[char_modifier.NONE].SLASH = '/' + +k[char_modifier.SHIFT].GRAVE = '¬' +k[char_modifier.SHIFT].MINUS = '_' +k[char_modifier.SHIFT].EQUAL = '+' +k[char_modifier.SHIFT].LEFTBRACE = '{' +k[char_modifier.SHIFT].RIGHTBRACE = '}' +k[char_modifier.SHIFT].BACKSLASH = '~' +k[char_modifier.SHIFT].SEMICOLON = ':' +k[char_modifier.SHIFT].APOSTROPHE = '@' +k[char_modifier.SHIFT].COMMA = '<' +k[char_modifier.SHIFT].DOT = '>' +k[char_modifier.SHIFT].SLASH = '?' + +k[char_modifier.NONE]['102ND'] = '\\' +k[char_modifier.SHIFT]['102ND'] = '|' + + +return k diff --git a/lua/core/menu.lua b/lua/core/menu.lua index 4c8e4bcaf..01b5b9e96 100644 --- a/lua/core/menu.lua +++ b/lua/core/menu.lua @@ -6,6 +6,7 @@ local util = require 'util' local fileselect = require 'fileselect' local listselect = require 'listselect' local textentry = require 'textentry' +local gamepad = require 'gamepad' _menu = {} @@ -78,6 +79,9 @@ norns.scripterror = function(msg) print("### SCRIPT ERROR: "..msg) if util.string_starts(msg,"missing") then print("### try 'SYSTEM > RESTART'") + elseif util.string_starts(msg,"version") then + print("### try 'SYSTEM > UPDATE'") + print("### or check for new disk image") end _menu.errormsg = msg _menu.scripterror = true @@ -186,6 +190,10 @@ _menu.set_page = function(page) _menu.redraw = m[page].redraw _menu.keyboardcode = m[page].keycode _menu.keyboardchar = m[page].keychar + _menu.custom_gamepad_axis = m[page].gamepad_axis + _menu.custom_gamepad_dpad = m[page].gamepad_dpad + _menu.custom_gamepad_button = m[page].gamepad_button + _menu.custom_gamepad_analog = m[page].gamepad_analog m[page].init() _menu.redraw() end @@ -206,16 +214,33 @@ end -- global menu keys function _menu.keycode(c,value) - if value==1 then - if c=="F1" then _menu.set_page("MIX") - elseif c=="F2" then _menu.set_page("TAPE") - elseif c=="F3" then _menu.set_page("HOME") - elseif c=="F4" then _menu.set_page("PARAMS") + -- those are globals and can't be overriden by a sub-menu + if value>0 then + if c=="F1" then + _menu.set_page("MIX") + return + elseif c=="F2" then + _menu.set_page("TAPE") + return + elseif c=="F3" then + _menu.set_page("HOME") + return + elseif c=="F4" then + _menu.set_page("PARAMS") + return end end + -- if a sub-menu defines its own handler, it takes precedence... + if _menu.keyboardcode then + _menu.keyboardcode(c,value) + return + end + + -- ... otherwise we use those default bindings in most places + -- E2 emu (scolling) - if value==1 then + if value>0 then if c=="DOWN" then _menu.penc(2,1) elseif c=="UP" then @@ -227,7 +252,7 @@ function _menu.keycode(c,value) end end - -- key emu + -- K2/K3 emu if value==1 or value==0 then if c=="LEFT" then _menu.key(2,value) @@ -236,11 +261,64 @@ function _menu.keycode(c,value) end end - if _menu.keyboardcode then _menu.keyboardcode(c,value) end + -- parameter change with +/- + if c=="MINUS" then + _menu.penc(3,value*-1) + elseif c=="EQUAL" then + _menu.penc(3,value) + end +end + +function _menu.keychar(c) + if _menu.keyboardchar then _menu.keyboardchar(c) end end -function _menu.keychar(c) end +function _menu.gamepad_axis(_sensor_axis,_value) + + -- if a sub-menu defines its own handler, it takes precedence... + if _menu.custom_gamepad_axis then + _menu.custom_gamepad_axis(_sensor_axis,_value) + return + end + if gamepad.down() then + _menu.penc(2,1) + elseif gamepad.up() then + _menu.penc(2,-1) + elseif gamepad.left() then + _menu.key(2,1) + elseif gamepad.right() then + _menu.key(3,1) + end +end + +function _menu.gamepad_button(b,value) + + if value == 1 and (b == "L1" or b == "R1") then + local delta = b == "R1" and 1 or -1 + local c = util.clamp(_menu.panel+delta,1,4) + if c ~= _menu.panel then + _menu.shownav = true + _menu.panel = c + _menu.set_page(_menu.panels[_menu.panel]) + nav_vanish:start() + end + end + + -- if a sub-menu defines its own handler, it takes precedence... + if _menu.custom_gamepad_button then + _menu.custom_gamepad_button(b,value) + return + end + + if value==1 or value==0 then + if b == "B" then + _menu.key(2,value) + elseif b == "A" then + _menu.key(3,value) + end + end +end -- interfaces @@ -251,6 +329,7 @@ m["PARAMS"] = require 'core/menu/params' m["SYSTEM"] = require 'core/menu/system' m["DEVICES"] = require 'core/menu/devices' m["WIFI"] = require 'core/menu/wifi' +m["SETTINGS"] = require 'core/menu/settings' m["RESTART"] = require 'core/menu/restart' m["RESET"] = require 'core/menu/reset' m["UPDATE"] = require 'core/menu/update' diff --git a/lua/core/menu/home.lua b/lua/core/menu/home.lua index e64aa64cb..efd661b8e 100644 --- a/lua/core/menu/home.lua +++ b/lua/core/menu/home.lua @@ -105,6 +105,10 @@ m.redraw = function() screen.level(8) screen.move(0,25) screen.text("try 'SYSTEM > RESTART'") + elseif util.string_starts(_menu.errormsg,"version") then + screen.level(8) + screen.move(0,25) + screen.text("try 'SYSTEM > UPDATE'") end end screen.level(15) diff --git a/lua/core/menu/mix.lua b/lua/core/menu/mix.lua index b57bcd22c..54f2f5589 100644 --- a/lua/core/menu/mix.lua +++ b/lua/core/menu/mix.lua @@ -21,6 +21,26 @@ m.enc = function(n,d) end end +m.gamepad_axis = function (_sensor_axis,_value) + if gamepad.left() then + _menu.key(2,1) + elseif gamepad.right() then + _menu.key(3,1) + elseif gamepad.down() then + _menu.penc(2,-1) + elseif gamepad.up() then + _menu.penc(2,1) + end +end + +m.gamepad_button = function(b,value) + if b == "B" then + _menu.penc(3,-1) + elseif b == "A" then + _menu.penc(3,1) + end +end + m.redraw = function() local n screen.clear() diff --git a/lua/core/menu/params.lua b/lua/core/menu/params.lua old mode 100644 new mode 100755 index 1a359a4d1..2464c3ffa --- a/lua/core/menu/params.lua +++ b/lua/core/menu/params.lua @@ -192,9 +192,9 @@ m.key = function(n,z) params:set(i) m.triggered[i] = 2 end - elseif t == params.tBINARY and m.mode == mEDIT then + elseif t == params.tBINARY and m.mode == mEDIT then params:delta(i,1) - if params:lookup_param(i).behavior == 'trigger' then + if params:lookup_param(i).behavior == 'trigger' then m.triggered[i] = 2 else m.on[i] = params:get(i) end elseif m.mode == mMAP and params:get_allow_pmap(i) then @@ -225,7 +225,7 @@ m.key = function(n,z) if m.mode == mEDIT then params:delta(i, 0) if params:lookup_param(i).behavior ~= 'trigger' then - m.on[i] = params:get(i) + m.on[i] = params:get(i) end end end @@ -280,7 +280,7 @@ m.key = function(n,z) if n==2 and z==1 then m.mode = mPSET elseif n==3 and z==1 then - os.execute("rm "..pset[m.ps_pos+1].file) + params:delete(pset[m.ps_pos+1].file,pset[m.ps_pos+1].name,string.format("%02d",m.ps_pos+1)) init_pset() m.mode = mPSET end @@ -336,7 +336,7 @@ m.enc = function(n,d) -- MAPEDIT elseif m.mode == mMAPEDIT then if n==2 then - m.mpos = (m.mpos+d) % 11 + m.mpos = (m.mpos+d) % 12 elseif n==3 then local p = page[m.pos+1] local n = params:get_id(p) @@ -377,6 +377,8 @@ m.enc = function(n,d) end elseif m.mpos==10 then if d>0 then pm.accum = true else pm.accum = false end + elseif m.mpos == 11 then + if d>0 then pm.echo = true else pm.echo = false end end end _menu.redraw() @@ -391,6 +393,39 @@ m.enc = function(n,d) end end +m.gamepad_axis = function (_sensor_axis,_value) + + if gamepad.down() then + _menu.penc(2,1) + elseif gamepad.up() then + _menu.penc(2,-1) + end + + if m.mode == mSELECT then + if gamepad.left() then + _menu.key(2,1) + elseif gamepad.right() then + _menu.key(3,1) + end + elseif m.mode == mEDIT or m.mode == mMAP then + local i = page[m.pos+1] + local t = params:t(i) + if t == params.tGROUP then + if gamepad.left() then + _menu.key(2,1) + elseif gamepad.right() then + _menu.key(3,1) + end + else + if gamepad.left() then + _menu.penc(3,-1) + elseif gamepad.right() then + _menu.penc(3,1) + end + end + end +end + m.redraw = function() screen.clear() _menu.draw_panel() @@ -518,29 +553,29 @@ m.redraw = function() screen.text(n) screen.move(127,10) screen.text_right(params:string(p)) - screen.move(0,25) + screen.move(0,22) hl(1) if m.midilearn then screen.text("LEARNING") else screen.text("LEARN") end - screen.move(127,25) + screen.move(127,22) hl(2) screen.text_right("CLEAR") screen.level(4) - screen.move(0,40) + screen.move(0,32) screen.text("cc") - screen.move(55,40) + screen.move(55,32) hl(3) screen.text_right(m.cc) screen.level(4) - screen.move(0,50) + screen.move(0,42) screen.text("ch") - screen.move(55,50) + screen.move(55,42) hl(4) screen.text_right(m.ch) screen.level(4) - screen.move(0,60) + screen.move(0,52) screen.text("dev") - screen.move(55,60) + screen.move(55,52) hl(5) local long_name = midi.vports[m.dev].name @@ -549,30 +584,36 @@ m.redraw = function() screen.text_right(tostring(m.dev)..": "..short_name) screen.level(4) - screen.move(63,40) + screen.move(63,32) screen.text("in") - screen.move(103,40) + screen.move(103,32) hl(6) screen.text_right(pm.in_lo) screen.level(4) - screen.move(127,40) + screen.move(127,32) hl(7) screen.text_right(pm.in_hi) screen.level(4) - screen.move(63,50) + screen.move(63,42) screen.text("out") - screen.move(103,50) + screen.move(103,42) hl(8) screen.text_right(out_lo) - screen.move(127,50) + screen.move(127,42) hl(9) screen.text_right(out_hi) screen.level(4) - screen.move(63,60) + screen.move(63,52) screen.text("accum") - screen.move(127,60) + screen.move(127,52) hl(10) screen.text_right(pm.accum and "yes" or "no") + screen.level(4) + screen.move(63,62) + screen.text("echo") + screen.move(127,62) + hl(11) + screen.text_right(pm.echo and "yes" or "no") -- PSET elseif m.mode == mPSET then screen.level(4) @@ -629,7 +670,7 @@ m.init = function() m.on = {} for i,param in ipairs(params.params) do if param.t == params.tBINARY then - if params:lookup_param(i).behavior == 'trigger' then + if params:lookup_param(i).behavior == 'trigger' then m.triggered[i] = 2 else m.on[i] = params:get(i) end end @@ -644,7 +685,7 @@ m.deinit = function() end _menu.rebuild_params = function() - if m.mode == mEDIT or m.mode == mMAP then + if m.mode == mEDIT or m.mode == mMAP then if m.group then build_sub(m.groupid) else @@ -677,7 +718,7 @@ norns.menu_midi_event = function(data, dev) local d = norns.pmap.data[r] local t = params:t(r) if d.accum then - v = v - 64 + v = (v > 64) and 1 or -1 d.value = util.clamp(d.value + v, d.in_lo, d.in_hi) v = d.value end @@ -688,12 +729,12 @@ norns.menu_midi_event = function(data, dev) elseif t == params.tNUMBER or t == params.tOPTION then s = util.round(s) params:set(r,s) - elseif t == params.tBINARY then + elseif t == params.tBINARY then params:delta(r,s) - if _menu.mode then + if _menu.mode then for i,param in ipairs(params.params) do - if params:lookup_param(i).behavior == params:lookup_param(r).behavior then - if params:lookup_param(i).behavior == 'trigger' then + if params:lookup_param(i).behavior == params:lookup_param(r).behavior then + if params:lookup_param(i).behavior == 'trigger' then m.triggered[i] = 2 else m.on[i] = params:get(i) end end diff --git a/lua/core/menu/reset.lua b/lua/core/menu/reset.lua index 5d8317bb0..3d14c4311 100644 --- a/lua/core/menu/reset.lua +++ b/lua/core/menu/reset.lua @@ -4,7 +4,7 @@ local m = { m.key = function(n,z) if n==2 and z==1 then - _menu.set_page("SYSTEM") + _menu.set_page("SETTINGS") elseif n==3 and z==1 then m.confirmed = true _menu.redraw() diff --git a/lua/core/menu/restart.lua b/lua/core/menu/restart.lua index 2ddd4b8dd..2517d54aa 100644 --- a/lua/core/menu/restart.lua +++ b/lua/core/menu/restart.lua @@ -8,11 +8,10 @@ m.key = function(n,z) elseif n==3 and z==1 then m.confirmed = true _menu.redraw() - _norns.reset() + _norns.restart() end end - m.enc = function(n,delta) end m.redraw = function() diff --git a/lua/core/menu/settings.lua b/lua/core/menu/settings.lua new file mode 100644 index 000000000..329de3c7c --- /dev/null +++ b/lua/core/menu/settings.lua @@ -0,0 +1,93 @@ +local textentry= require 'textentry' + +local m = { + pos = 1, + list = {"RESET", "PASSWORD >", "BATTERY WARNING"}, + pages = {"RESET", "PASSWORD"} +} + +m.key = function(n,z) + if n==2 and z==1 then + _menu.set_page("SYSTEM") + elseif n==3 and z==1 then + if m.pages[m.pos]=="RESET" then + _menu.set_page("RESET") + elseif m.pages[m.pos]=="PASSWORD" then + textentry.enter(m.passdone, "", "new password:", m.passcheck) + end + end +end + +m.enc = function(n,delta) + if n==2 then + m.pos = util.clamp(m.pos + delta, 1, #m.list) + _menu.redraw() + elseif n==3 and m.list[m.pos]=="BATTERY WARNING" then + norns.state.battery_warning = (delta>0) and 1 or 0 + screen.update = screen.update_default + _menu.redraw() + end +end + +m.redraw = function() + screen.clear() + for i=1,6 do + if (i > 3 - m.pos) and (i < #m.list - m.pos + 4) then + screen.move(0,10*i) + local line = m.list[i+m.pos-3] + if(i==3) then + screen.level(15) + else + screen.level(4) + end + screen.text(line) + if m.list[i+m.pos-3]=="BATTERY WARNING" then + screen.move(128,10*i) + screen.text_right(norns.state.battery_warning==1 and "on" or "off") + end + end + end + screen.update() +end + +m.init = norns.none +m.deinit = norns.none + +m.passcheck = function(txt) + if txt ~= nil then + if string.len(txt) < 8 then + return ("remaining: "..8 - string.len(txt)) + elseif string.len(txt) > 63 then + return ("too long") + end + end +end + +m.passdone = function(txt) + if txt ~= nil then + if string.len(txt) >= 8 and string.len(txt) < 64 then + local chpasswd_status = os.execute("echo 'we:"..txt.."' | sudo chpasswd") + local smbpasswd_status = os.execute("printf '"..txt.."\n"..txt.."\n' | sudo smbpasswd -a we") + local hotspotpasswd_status; + local fd = io.open("home/we/norns/.system.hotspot_password", "w+") + if fd then + io.output(fd) + io.write(txt) + io.close(fd) + hotspotpasswd_status = true + end + if chpasswd_status then print("ssh password changed") end + if smbpasswd_status then print("samba password changed") end + if hotspotpasswd_status then print("hotspot password changed, toggle WIFI off/on to take effect") end + elseif string.len(txt) <= 8 then + print("!! password must be at least 8 characters !!") + print("!! password has not been changed !!") + elseif string.len(txt) > 64 then + print("!! password cannot be longer than 63 characters !!") + print("!! password has not been changed !!") + end + end + _menu.set_page("SETTINGS") +end + +return m \ No newline at end of file diff --git a/lua/core/menu/system.lua b/lua/core/menu/system.lua index 1735ee14f..3a382276f 100644 --- a/lua/core/menu/system.lua +++ b/lua/core/menu/system.lua @@ -1,20 +1,14 @@ -local textentry= require 'textentry' - local m = { pos = 1, - list = {"DEVICES > ", "WIFI >", "MODS >", "RESTART", "RESET", "UPDATE", "PASSWORD >", "LOG"}, - pages = {"DEVICES", "WIFI", "MODS", "RESTART", "RESET", "UPDATE", "PASSWORD", "LOG"} + list = {"DEVICES > ", "WIFI >", "MODS >", "SETTINGS >", "RESTART", "UPDATE", "LOG"}, + pages = {"DEVICES", "WIFI", "MODS", "SETTINGS", "RESTART", "UPDATE", "LOG"} } m.key = function(n,z) if n==2 and z==1 then _menu.set_page("HOME") elseif n==3 and z==1 then - if m.pages[m.pos]=="PASSWORD" then - textentry.enter(m.passdone, "", "new password:") - else - _menu.set_page(m.pages[m.pos]) - end + _menu.set_page(m.pages[m.pos]) end end diff --git a/lua/core/menu/tape.lua b/lua/core/menu/tape.lua index 43565f857..3b6e8ba98 100644 --- a/lua/core/menu/tape.lua +++ b/lua/core/menu/tape.lua @@ -56,10 +56,10 @@ local function read_tape_index() for f in tape:gmatch("([^\n]+)") do fs = string.sub(f,1,4) if tonumber(fs) then - table.insert(t,tonumber(fs)) + table.insert(t,tonumber(fs)) end end - + if #t == 0 then m.fileindex = 0 else @@ -187,6 +187,16 @@ end m.enc = norns.none +m.gamepad_axis = function (_sensor_axis,_value) + if gamepad.down() then + m.mode = TAPE_MODE_REC + _menu.redraw() + elseif gamepad.up() then + m.mode = TAPE_MODE_PLAY + _menu.redraw() + end +end + m.redraw = function() screen.clear() diff --git a/lua/core/midi.lua b/lua/core/midi.lua old mode 100644 new mode 100755 index 1e501fdee..8b82cbdd5 --- a/lua/core/midi.lua +++ b/lua/core/midi.lua @@ -191,6 +191,9 @@ function Midi.cleanup() for _, dev in pairs(Midi.devices) do dev.event = nil end + + Midi.add = function(dev) end + Midi.remove = function(dev) end end -- utility @@ -384,7 +387,18 @@ function Midi.update_connected_state() else Midi.vports[i].connected = false end + if params.lookup["clock_midi_out_"..i] ~= nil then + local short_name = string.len(midi.vports[i].name) <= 20 and midi.vports[i].name or util.acronym(midi.vports[i].name) + params:lookup_param("clock_midi_out_"..i).name = i..". "..short_name + if short_name ~= "none" and midi.vports[i].connected then + params:show("clock_midi_out_"..i) + else + params:set("clock_midi_out_"..i,0) + params:hide("clock_midi_out_"..i) + end + end end + _menu.rebuild_params() end -- add a device. @@ -399,6 +413,7 @@ end -- remove a device. _norns.midi.remove = function(id) if Midi.devices[id] then + Midi.remove(Midi.devices[id]) if Midi.devices[id].remove then Midi.devices[id].remove() end diff --git a/lua/core/norns.lua b/lua/core/norns.lua index 967496e04..5478ee5ea 100644 --- a/lua/core/norns.lua +++ b/lua/core/norns.lua @@ -96,7 +96,7 @@ norns.battery_current = 0 -- battery percent handler _norns.battery = function(percent, current) - if current < 0 and percent < 5 then + if current < 0 and percent < 5 and norns.state.battery_warning==1 then screen.update = screen.update_low_battery elseif current > 0 and norns.battery_current < 0 then screen.update = screen.update_default @@ -221,6 +221,20 @@ _norns.reset = function() os.execute("sudo systemctl restart norns-main.service") end +-- restart device +_norns.restart = function() + hook.system_pre_shutdown() + print("RESTARTING") + norns.script.clear() + _norns.free_engine() + norns.state.clean_shutdown = true + norns.state.save() + pcall(cleanup) + audio.level_dac(0) + audio.headphone_gain(0) + _norns.reset() +end + -- startup function will be run after I/O subsystems are initialized, -- but before I/O event loop starts ticking (see readme-script.md) _startup = function() @@ -236,3 +250,8 @@ end norns.rerun = function() norns.script.load(norns.state.script) end + +-- expand the filesystem after a fresh installation +norns.expand_filesystem = function() + os.execute('sudo raspi-config --expand-rootfs') +end \ No newline at end of file diff --git a/lua/core/osc.lua b/lua/core/osc.lua old mode 100644 new mode 100755 index 65ff4fcac..53c8e0805 --- a/lua/core/osc.lua +++ b/lua/core/osc.lua @@ -42,6 +42,11 @@ function OSC.send_crone(path, args) end end +--- clear handlers. +function OSC.cleanup() + OSC.event = nil +end + local function param_handler(path, args) local address_parts = {} local osc_pset_id = "" @@ -92,6 +97,8 @@ local function remote_handler(path, args) _norns.key(n, val) elseif cmd=="/remote/enc" then _norns.enc(n, val) + elseif cmd=="/remote/brd" then + keyboard.process(1,n,val) end end diff --git a/lua/core/params/control.lua b/lua/core/params/control.lua old mode 100644 new mode 100755 index 17432d949..0a5537595 --- a/lua/core/params/control.lua +++ b/lua/core/params/control.lua @@ -80,6 +80,14 @@ function Control:set_raw(value, silent) self.raw = clamped_value if silent==false then self:bang() end end + if norns.pmap.data[self.id] ~= nil then + local midi_prm = norns.pmap.data[self.id] + midi_prm.value = util.round(util.linlin(midi_prm.out_lo, midi_prm.out_hi, midi_prm.in_lo, midi_prm.in_hi, self.raw)) + if midi_prm.echo then + local port = norns.pmap.data[self.id].dev + midi.vports[port]:cc(midi_prm.cc, midi_prm.value, midi_prm.ch) + end + end end --- get_delta. diff --git a/lua/core/params/group.lua b/lua/core/params/group.lua index d2fbdda13..39ac404ec 100644 --- a/lua/core/params/group.lua +++ b/lua/core/params/group.lua @@ -6,10 +6,11 @@ Group.__index = Group local tGROUP = 7 -function Group.new(name, n) +function Group.new(id, name, n) local g = setmetatable({}, Group) - g.name = name or "group" - g.n = n or 1 + g.name = type(name) ~= "number" and name or (id or "group") + g.id = id or g.name + g.n = type(name) == "number" and name or (n or 1) g.t = tGROUP g.action = function() end return g diff --git a/lua/core/params/number.lua b/lua/core/params/number.lua old mode 100644 new mode 100755 index bc0d79802..ac83cf234 --- a/lua/core/params/number.lua +++ b/lua/core/params/number.lua @@ -42,6 +42,14 @@ function Number:set(v, silent) self.value = c if silent==false then self:bang() end end + if norns.pmap.data[self.id] ~= nil then + local midi_prm = norns.pmap.data[self.id] + midi_prm.value = util.round(util.linlin(midi_prm.out_lo, midi_prm.out_hi, midi_prm.in_lo, midi_prm.in_hi, self.value)) + if midi_prm.echo then + local port = norns.pmap.data[self.id].dev + midi.vports[port]:cc(midi_prm.cc, midi_prm.value, midi_prm.ch) + end + end end function Number:delta(d) diff --git a/lua/core/params/option.lua b/lua/core/params/option.lua old mode 100644 new mode 100755 index 807b5af4c..97d1fb666 --- a/lua/core/params/option.lua +++ b/lua/core/params/option.lua @@ -36,6 +36,14 @@ function Option:set(v, silent) self.selected = c if silent==false then self:bang() end end + if norns.pmap.data[self.id] ~= nil then + local midi_prm = norns.pmap.data[self.id] + midi_prm.value = util.round(util.linlin(midi_prm.out_lo, midi_prm.out_hi, midi_prm.in_lo, midi_prm.in_hi, self.selected)) + if midi_prm.echo then + local port = norns.pmap.data[self.id].dev + midi.vports[port]:cc(midi_prm.cc, midi_prm.value, midi_prm.ch) + end + end end function Option:delta(d) diff --git a/lua/core/params/separator.lua b/lua/core/params/separator.lua index 7ae004050..70cc03bf0 100644 --- a/lua/core/params/separator.lua +++ b/lua/core/params/separator.lua @@ -6,9 +6,10 @@ Separator.__index = Separator local tSEPARATOR = 0 -function Separator.new(name) +function Separator.new(id,name) local s = setmetatable({}, Separator) - s.name = name or "" + s.name = name or (id or "separator") + s.id = id or s.name s.t = tSEPARATOR s.action = function() end return s diff --git a/lua/core/paramset.lua b/lua/core/paramset.lua old mode 100644 new mode 100755 index 1012156b0..44d8bba3c --- a/lua/core/paramset.lua +++ b/lua/core/paramset.lua @@ -92,41 +92,11 @@ function ParamSet.new(id, name) ps.group = 0 ps.action_write = nil ps.action_read = nil + ps.action_delete = nil ParamSet.sets[ps.id] = ps return ps end ---- add separator. --- name is optional. --- separators have their own parameter index and --- can be hidden or added to a paremeter group. --- @tparam string name -function ParamSet:add_separator(name) - local param = separator.new(name) - table.insert(self.params, param) - self.count = self.count + 1 - self.group = self.group - 1 - self.hidden[self.count] = false -end - ---- add parameter group. --- groups cannot be nested, --- i.e. a group cannot be made within a group. --- @tparam string name --- @tparam int n -function ParamSet:add_group(name,n) - if self.group < 1 then - local param = group.new(name,n) - table.insert(self.params, param) - self.count = self.count + 1 - self.group = n - self.hidden[self.count] = false - self.lookup[name] = self.count - else - print("ERROR: paramset cannot nest GROUPs") - end -end - --- add generic parameter. -- helper function to add param to paramset -- two uses: @@ -162,13 +132,18 @@ function ParamSet:add(args) param = binary.new(id, name, args.behavior, args.default, args.allow_pmap) elseif args.type == "text" then param = text.new(id, name, args.text) + elseif args.type == "separator" then + param = separator.new(id, name) + elseif args.type == "group" then + param = group.new(id, name, args.n) else print("paramset.add() error: unknown type") return nil end end - if self.lookup[param.id] ~= nil then + local overwrite = true + if self.lookup[param.id] ~= nil and param.t ~= 0 and param.t ~= 7 then print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") print("!!!!! ERROR: parameter ID collision: ".. param.id) print("! please contact the script maintainer - this will cause a load failure in future updates") @@ -178,6 +153,22 @@ function ParamSet:add(args) else print("! BEWARE! clobbering a script or mod param") end + elseif self.lookup[param.id] ~= nil and param.t == 0 then + if params:lookup_param(param.id).t ~= 0 then + print("! separator ID <"..param.id.."> collides with a non-separator parameter, will not overwrite") + overwrite = false + elseif param.id ~= "separator" then + print("! stealing separator ID <"..param.id.."> from earlier separator") + overwrite = true + end + elseif self.lookup[param.id] ~= nil and param.t == 7 then + if params:lookup_param(param.id).t ~= 7 then + print("! group ID <"..param.id.."> collides with a non-group parameter, will not overwrite") + overwrite = false + elseif param.id ~= "group" then + print("! stealing group ID <"..param.id.."> from earlier group") + overwrite = true + end end param.save = true @@ -185,11 +176,29 @@ function ParamSet:add(args) table.insert(self.params, param) self.count = self.count + 1 self.group = self.group - 1 - self.lookup[param.id] = self.count + if overwrite == true then + self.lookup[param.id] = self.count + end self.hidden[self.count] = false if args.action then param.action = args.action end + + local midi_prm = norns.pmap.data[param.id] + if midi_prm then + local val + if param.t == 3 then + val = params:get_raw(param.id) + else + val = params:get(param.id) + end + midi_prm.value = util.round(util.linlin(midi_prm.out_lo, midi_prm.out_hi, midi_prm.in_lo, midi_prm.in_hi, val)) + if midi_prm.echo then + local port = norns.pmap.data[param.id].dev + midi.vports[port]:cc(midi_prm.cc, midi_prm.value, midi_prm.ch) + end + end + end --- add number. @@ -205,8 +214,8 @@ function ParamSet:add_number(id, name, min, max, default, formatter, wrap) end --- add option. --- @tparam string id --- @tparam string name +-- @tparam string id (no spaces) +-- @tparam string name (can contain spaces) -- @param options -- @param default function ParamSet:add_option(id, name, options, default) @@ -214,8 +223,8 @@ function ParamSet:add_option(id, name, options, default) end --- add control. --- @tparam string id --- @tparam string name +-- @tparam string id (no spaces) +-- @tparam string name (can contain spaces) -- @tparam controlspec controlspec -- @param formatter function ParamSet:add_control(id, name, controlspec, formatter) @@ -223,8 +232,8 @@ function ParamSet:add_control(id, name, controlspec, formatter) end --- add file. --- @tparam string id --- @tparam string name +-- @tparam string id (no spaces) +-- @tparam string name (can contain spaces) -- @tparam string path function ParamSet:add_file(id, name, path) self:add { param=file.new(id, name, path) } @@ -236,8 +245,8 @@ function ParamSet:add_text(id, name, txt) end --- add taper. --- @tparam string id --- @tparam string name +-- @tparam string id (no spaces) +-- @tparam string name (can contain spaces) -- @tparam number min -- @tparam number max -- @param default @@ -248,21 +257,57 @@ function ParamSet:add_taper(id, name, min, max, default, k, units) end --- add trigger. --- @tparam string id --- @tparam string name +-- @tparam string id (no spaces) +-- @tparam string name (can contain spaces) function ParamSet:add_trigger(id, name) self:add { param=trigger.new(id, name) } end --- add binary --- @tparam string id --- @tparam string name +-- @tparam string id (no spaces) +-- @tparam string name (can contain spaces) -- @tparam string behavior -- @tparam number default function ParamSet:add_binary(id, name, behavior, default) self:add { param=binary.new(id, name, behavior, default) } end +--- add separator. +-- id and name are optional. +-- if neither id or name are provided, +-- separator will be named 'separator' +-- and will not have a unique parameter index. +-- separators which have their own parameter index +-- can be hidden / shown. +-- @tparam string id (no spaces) +-- @tparam string name (can contain spaces) +function ParamSet:add_separator(id, name) + self:add { param=separator.new(id, name) } +end + +--- add parameter group. +-- groups cannot be nested, +-- i.e. a group cannot be made within a group. +-- id and name are optional. +-- if neither id or name are provided, +-- group will be named 'group' +-- and will not have a unique parameter index. +-- groups which have their own parameter index +-- can be hidden / shown. +-- @tparam string id (no spaces) +-- @tparam string name (can contain spaces) +-- @tparam int n +function ParamSet:add_group(id, name, n) + if id == nil then id = "group" end + n = type(name) == "number" and name or (n or 1) + if self.group < 1 then + self:add { param=group.new(id, name, n) } + self.group = type(name) == "number" and name or n + else + print("ERROR: paramset cannot nest GROUPs") + end +end + --- print. function ParamSet:print() print("paramset ["..self.name.."]") @@ -341,6 +386,20 @@ end function ParamSet:delta(index, d) local param = self:lookup_param(index) param:delta(d) + if norns.pmap.data[param.id] ~= nil then + local midi_prm = norns.pmap.data[param.id] + local val + if param.t == 3 then + val = param:get_raw() + else + val = param:get() + end + midi_prm.value = util.round(util.linlin(midi_prm.out_lo, midi_prm.out_hi, midi_prm.in_lo, midi_prm.in_hi, val)) + if midi_prm.echo then + local port = norns.pmap.data[param.id].dev + midi.vports[port]:cc(midi_prm.cc, midi_prm.value, midi_prm.ch) + end + end end --- set action. @@ -402,6 +461,7 @@ end -- parameters are visible by default. -- @param index function ParamSet:visible(index) + if type(index)=="string" then index = self.lookup[index] end return not self.hidden[index] end @@ -414,6 +474,8 @@ local function unquote(s) return s:gsub('^"', ''):gsub('"$', ''):gsub('\\"', '"') end +-- get param object at index; useful for meta-programming tasks like changing a param once it's been created. +-- @param index function ParamSet:lookup_param(index) if type(index) == "string" and self.lookup[index] then return self.params[self.lookup[index]] @@ -429,10 +491,12 @@ end -- @tparam string name function ParamSet:write(filename, name) filename = filename or 1 + local pset_number; if type(filename) == "number" then local n = filename filename = norns.state.data .. norns.state.shortname - filename = filename .. "-" .. string.format("%02d",n) .. ".pset" + pset_number = string.format("%02d",n) + filename = filename .. "-" .. pset_number .. ".pset" end print("pset >> write: "..filename) local fd = io.open(filename, "w+") @@ -440,13 +504,13 @@ function ParamSet:write(filename, name) io.output(fd) if name then io.write("-- "..name.."\n") end for _,param in ipairs(self.params) do - if param.id and param.save and param.t ~= self.tTRIGGER then + if param.id and param.save and param.t ~= self.tTRIGGER and param.t ~= self.tSEPARATOR then io.write(string.format("%s: %s\n", quote(param.id), param:get())) end end io.close(fd) - if self.action_write ~= nil then - self.action_write(filename,name) + if self.action_write ~= nil then + self.action_write(filename,name,pset_number) end else print("pset: BAD FILENAME") end end @@ -456,15 +520,18 @@ end -- @tparam boolean silent if true, do not trigger parameter actions function ParamSet:read(filename, silent) filename = filename or norns.state.pset_last + local pset_number; if type(filename) == "number" then local n = filename filename = norns.state.data .. norns.state.shortname - filename = filename .. "-" .. string.format("%02d",n) .. ".pset" + pset_number = string.format("%02d",n) + filename = filename .. "-" .. pset_number .. ".pset" end print("pset >> read: " .. filename) local fd = io.open(filename, "r") if fd then io.close(fd) + local param_already_set = {} for line in io.lines(filename) do if util.string_starts(line, "--") then params.name = string.sub(line, 4, -1) @@ -475,7 +542,7 @@ function ParamSet:read(filename, silent) id = unquote(id) local index = self.lookup[id] - if index and self.params[index] then + if index and self.params[index] and not param_already_set[index] then if tonumber(value) ~= nil then self.params[index]:set(tonumber(value), silent) elseif value == "-inf" then @@ -485,18 +552,35 @@ function ParamSet:read(filename, silent) elseif value then self.params[index]:set(value, silent) end + param_already_set[index] = true end end end end if self.action_read ~= nil then - self.action_read(filename,silent) + self.action_read(filename,silent,pset_number) end else print("pset :: "..filename.." not read.") end end +--- delete from disk. +-- @param filename either an absolute path, a number (for [scriptname]-[number].pset in local data folder) or nil (for default [scriptname].pset in local data folder) +-- @tparam string name +function ParamSet:delete(filename, name, pset_number) + if type(filename) == "number" then + local n = filename + filename = norns.state.data .. norns.state.shortname + filename = filename .. "-" .. string.format("%02d",n) .. ".pset" + end + print("pset >> delete: "..filename, name, pset_number) + norns.system_cmd("rm "..filename) + if self.action_delete ~= nil then + self.action_delete(filename, name, pset_number) + end +end + --- read default pset if present. function ParamSet:default() self:read() @@ -519,6 +603,7 @@ function ParamSet:clear() self.count = 0 self.action_read = nil self.action_write = nil + self.action_delete = nil self.lookup = {} end diff --git a/lua/core/pmap.lua b/lua/core/pmap.lua old mode 100644 new mode 100755 index aae6c90a8..c4988aca5 --- a/lua/core/pmap.lua +++ b/lua/core/pmap.lua @@ -17,6 +17,7 @@ function pmap.new(id) p.out_lo = 0 p.out_hi = 1 p.accum = false + p.echo = false p.value = 0 pmap.data[id] = p end @@ -89,17 +90,20 @@ function pmap.read() if fd then io.close(fd) for line in io.lines(filename) do - --local name, value = string.match(line, "(\".-\")%s*:%s*(.*)") local name, value = string.match(line, "(\".-\")%s*:%s*(.*)") if name and value and tonumber(value)==nil then --print(unquote(name) .. " : " .. unquote(value)) local x = load("return "..unquote(value)) pmap.data[unquote(name)] = x() + -- 230816: new 'echo' field added to the pmap constructor + if pmap.data[unquote(name)].echo == nil then + pmap.data[unquote(name)].echo = false + end end end pmap.refresh() else - print("m.read: "..filename.." not read.") + print("m.read: "..filename.." not read, using defaults.") end end diff --git a/lua/core/screen.lua b/lua/core/screen.lua index 327b6cb0a..b2633a21e 100644 --- a/lua/core/screen.lua +++ b/lua/core/screen.lua @@ -119,21 +119,21 @@ Screen.circle = function(x, y, r) _norns.screen_circle(x, y, r) end Screen.rect = function(x, y, w, h) _norns.screen_rect(x, y, w, h) end --- draw curve (cubic Bézier spline). --- @tparam number x1 destination x --- @tparam number y1 destination y --- @tparam number x2 handle 1 x --- @tparam number y2 handle 1 y --- @tparam number x3 handle 2 x --- @tparam number y3 handle 2 y +-- @tparam number x1 handle 1 x +-- @tparam number y1 handle 1 y +-- @tparam number x2 handle 2 x +-- @tparam number y2 handle 2 y +-- @tparam number x3 destination x +-- @tparam number y3 destination y Screen.curve = function(x1, y1, x2, y2, x3, y3) _norns.screen_curve(x1, y1, x2, y2, x3, y3) end --- draw curve (cubic Bézier spline) relative coordinates. --- @tparam number x1 relative destination x --- @tparam number y1 relative destination y --- @tparam number x2 handle 1 x --- @tparam number y2 handle 1 y --- @tparam number x3 handle 2 x --- @tparam number y3 handle 2 y +-- @tparam number x1 handle 1 x +-- @tparam number y1 handle 1 y +-- @tparam number x2 handle 2 x +-- @tparam number y2 handle 2 y +-- @tparam number x3 relative destination x +-- @tparam number y3 relative destination y Screen.curve_rel = function(x1, y1, x2, y2, x3, y3) _norns.screen_curve_rel(x1, y1, x2, y2, x3, y3) end --- close current path. @@ -141,10 +141,16 @@ Screen.close = function() _norns.screen_close() end --- stroke current path. -- uses currently selected color. +-- after this call the current path will be cleared, so the 'relative' functions +-- (`move_rel`, `line_rel` and `curve_rel`) won't work - use their absolute +-- alternatives instead. Screen.stroke = function() _norns.screen_stroke() end --- fill current path. -- uses currently selected color. +-- after this call the current path will be cleared, so the 'relative' functions +-- (`move_rel`, `line_rel` and `curve_rel`) won't work - use their absolute +-- alternatives instead. Screen.fill = function() _norns.screen_fill() end --- draw text (left aligned). @@ -303,6 +309,10 @@ _norns.screen_circle = function(x, y, r) _norns.screen_arc(x, y, r, 0, math.pi*2) end +--- export screenshot +-- @param filename: saved to dust/data/(script)/(filename).png +Screen.export_screenshot = function(filename) _norns.screen_export_screenshot(norns.state.data..filename..'.png') end + --- display png -- @param filename -- @tparam number x x position diff --git a/lua/core/script.lua b/lua/core/script.lua old mode 100644 new mode 100755 index d146f4e6f..0f51e62a1 --- a/lua/core/script.lua +++ b/lua/core/script.lua @@ -29,6 +29,10 @@ Script.clear = function() -- reset embedded modules json = _json + if norns.lfo ~= nil then + norns.lfo.lattice:destroy() + norns.lfo = nil + end -- script local state local state = { } @@ -61,6 +65,7 @@ Script.clear = function() arc.cleanup() midi.cleanup() hid.cleanup() + osc.cleanup() -- stop all timers metro.free_all() @@ -89,8 +94,9 @@ Script.clear = function() -- clear crow functions norns.crow.init() - -- clear keyboard handlers + -- clear HID device handlers keyboard.clear() + gamepad.clear() -- clear last run norns.state.script = '' @@ -99,6 +105,7 @@ Script.clear = function() norns.state.path = _path["dust"] norns.state.data = _path.data norns.state.lib = norns.state.path + norns.version.required = nil -- clear params params:clear() @@ -213,6 +220,11 @@ end --- load engine, execute script-specified init (if present). Script.run = function() + if tonumber(norns.version.required) and tonumber(norns.version.required) > tonumber(norns.version.update) then + norns.scripterror("version " .. norns.version.required .. " required") + Script.clear() + return + end -- allow mods to do initialization hook.script_pre_init() diff --git a/lua/core/softcut.lua b/lua/core/softcut.lua index 21b048c82..749497c84 100644 --- a/lua/core/softcut.lua +++ b/lua/core/softcut.lua @@ -85,8 +85,8 @@ SC.loop = function(voice,state) _norns.cut_param("loop_flag",voice,state) end --- set fade time. -- @tparam int voice : voice index --- @tparam number pos : loop start position in seconds -SC.fade_time = function(voice,pos) _norns.cut_param("fade_time",voice,pos) end +-- @tparam number fade_time : crossfade time in seconds +SC.fade_time = function(voice,fade_time) _norns.cut_param("fade_time",voice,fade_time) end --- set record level. -- this sets the realtime-modulated record level, @@ -120,10 +120,10 @@ SC.buffer = function(i,b) _norns.cut_param_ii("buffer",i,b) end --- synchronize two voices. --- position of "dst" will be immediately set to that of "source" --- @tparam int src : source voice index -- @tparam int dst : destination voice index +-- @tparam int src : source voice index -- @tparam number offset : additional offset in seconds -SC.voice_sync = function(src, dst, offset) _norns.cut_param_iif("voice_sync",src,dst,offset) end +SC.voice_sync = function(dst, src, offset) _norns.cut_param_iif("voice_sync",dst,src,offset) end --- set pre_filter cutoff frequency. --- @tparam int voice : voice index @@ -516,7 +516,6 @@ function SC.params() pre_filter_dry = { type="control", controlspec=controlspec.new(0, 1, 'lin', 0, 0, "") }, -- post filter post_filter_fc = { type="control", controlspec=controlspec.new(10, 12000, 'exp', 1, 12000, "Hz") }, - post_filter_fc_mod = { type="control", controlspec=controlspec.new(0, 1, 'lin', 0, 1, "") }, post_filter_rq = { type="control", controlspec=controlspec.new(0.0005, 8.0, 'exp', 0, 2.0, "") }, -- @fixme use dB / taper? post_filter_lp = { type="control", controlspec=controlspec.new(0, 1, 'lin', 0, 1, "") }, diff --git a/lua/core/startup.lua b/lua/core/startup.lua index daabbcd3e..de18ffc8b 100644 --- a/lua/core/startup.lua +++ b/lua/core/startup.lua @@ -20,6 +20,7 @@ clock = require "core/clock" midi = require 'core/midi' osc = require 'core/osc' keyboard = require 'core/keyboard' +gamepad = require 'core/gamepad' poll = require 'core/poll' engine = tab.readonly{table = require 'core/engine', except = {'name'}} softcut = require 'core/softcut' diff --git a/lua/core/state.lua b/lua/core/state.lua old mode 100644 new mode 100755 index 633a1809a..506fac95b --- a/lua/core/state.lua +++ b/lua/core/state.lua @@ -8,6 +8,8 @@ state.data = _path.data state.name = '' state.shortname = '' state.clean_shutdown = false +state.battery_warning = 1 + state.mix = {} state.mix.output = 0 state.mix.input = 0 @@ -48,7 +50,7 @@ state.clock.source = 1 state.clock.tempo = 90 state.clock.link_quantum = 4 state.clock.link_start_stop_sync = 1 -state.clock.midi_out = 1 +state.clock.midi_out = {} state.clock.crow_out = 1 state.clock.crow_out_div = 4 state.clock.crow_in_div = 4 @@ -62,6 +64,12 @@ state.resume = function() dofile(_path.data..'system.state') end + -- if previously-saved state.clock.midi_out is a number, + -- make it a table + if type(state.clock.midi_out) == 'number' then + state.clock.midi_out = {} + end + -- update vports midi.update_devices() grid.update_devices() @@ -105,6 +113,7 @@ state.save_state = function() io.write("norns.state.shortname = '" .. state.shortname .. "'\n") io.write("norns.state.path = '" .. state.path .. "'\n") io.write("norns.state.data = '" .. state.data .. "'\n") + io.write("norns.state.battery_warning = " .. state.battery_warning .. "\n") io.write("norns.state.mix.output = " .. norns.state.mix.output .. "\n") io.write("norns.state.mix.input = " .. norns.state.mix.input .. "\n") io.write("norns.state.mix.monitor = " .. norns.state.mix.monitor .. "\n") @@ -139,7 +148,9 @@ state.save_state = function() io.write("norns.state.clock.tempo = " .. norns.state.clock.tempo .. "\n") io.write("norns.state.clock.link_quantum = " .. norns.state.clock.link_quantum .. "\n") io.write("norns.state.clock.link_start_stop_sync = " .. norns.state.clock.link_start_stop_sync .. "\n") - io.write("norns.state.clock.midi_out = " .. norns.state.clock.midi_out .. "\n") + for i = 1,16 do + io.write("norns.state.clock.midi_out["..i.."] = " .. norns.state.clock.midi_out[i] .. "\n") + end io.write("norns.state.clock.crow_out = " .. norns.state.clock.crow_out .. "\n") io.write("norns.state.clock.crow_out_div = " .. norns.state.clock.crow_out_div .. "\n") io.write("norns.state.clock.crow_in_div = " .. norns.state.clock.crow_in_div .. "\n") diff --git a/lua/core/wifi.lua b/lua/core/wifi.lua index 6052bb77d..128963079 100644 --- a/lua/core/wifi.lua +++ b/lua/core/wifi.lua @@ -6,12 +6,24 @@ local util = require "util" -- local HOTSPOT = "Hotspot" -local hotspot_password = "nnnnnnnn" -- -- common functions -- +local function get_hotspot_password() + local hotspot_password; + local fd = io.open("home/we/norns/.system.hotspot_password", "r") + if fd then + io.input(fd) + hotspot_password = io.read() + io.close(fd) + else + hotspot_password = "nnnnnnnn" + end + return hotspot_password +end + local function collect_info(cmd) local info = {} local output = util.os_capture(cmd, true) diff --git a/lua/lib/fileselect.lua b/lua/lib/fileselect.lua old mode 100644 new mode 100755 index 79d473a7f..0cc58558c --- a/lua/lib/fileselect.lua +++ b/lua/lib/fileselect.lua @@ -4,7 +4,7 @@ local fs = {} -function fs.enter(folder, callback) +function fs.enter(folder, callback, filter_string) fs.folders = {} fs.list = {} fs.display_list = {} @@ -15,8 +15,11 @@ function fs.enter(folder, callback) fs.callback = callback fs.done = false fs.path = nil + fs.filter = filter_string and filter_string or "all" + fs.previewing = nil + fs.previewing_timeout_counter = nil - if fs.folder:sub(-1,-1) ~= "/" then + if fs.folder:sub(-1, -1) ~= "/" then fs.folder = fs.folder .. "/" end @@ -48,8 +51,11 @@ function fs.exit() else norns.menu.set(fs.enc_restore, fs.key_restore, fs.redraw_restore) end - if fs.path then fs.callback(fs.path) - else fs.callback("cancel") end + if fs.path then + fs.callback(fs.path) + else + fs.callback("cancel") + end end function fs.pushd(dir) @@ -62,10 +68,9 @@ function fs.pushd(dir) fs.redraw() end - fs.getdir = function() local path = fs.folder - for k,v in pairs(fs.folders) do + for k, v in pairs(fs.folders) do path = path .. v end --print("path: "..path) @@ -77,6 +82,7 @@ fs.getlist = function() fs.list = util.scandir(dir) fs.display_list = {} fs.lengths = {} + fs.visible = {} fs.pos = 0 if fs.depth > 0 then @@ -88,42 +94,96 @@ fs.getlist = function() for k, v in ipairs(fs.list) do local line = v local max_line_length = 128 + local display_length = ""; + local fulldir = dir .. line + + fs.visible[k] = true if string.sub(line, -1) ~= "/" then - local _, samples, rate = audio.file_info(dir .. line) + local _, samples, rate = audio.file_info(fulldir) + -- if file is audio: if samples > 0 and rate > 0 then - fs.lengths[k] = util.s_to_hms(math.floor(samples / rate)) - max_line_length = 97 + -- if there's no filter or we specify an "audio" or format filter: + if fs.filter == "all" or fs.filter == "audio" or fs.filter == fulldir:match("^.+(%..+)$") then + display_length = util.s_to_hms(math.floor(samples / rate)) + max_line_length = 97 + else -- otherwise, do not display audio file: + fs.visible[k] = false + display_length = nil + end + -- if file is NOT audio: + elseif fs.filter ~= "all" then + if fs.filter == "audio" or fs.filter ~= fulldir:match("^.+(%..+)$") then + fs.visible[k] = false + display_length = nil + end end end - line = util.trim_string_to_width(line, max_line_length) - fs.display_list[k] = line + if fs.visible[k] then + line = util.trim_string_to_width(line, max_line_length) + table.insert(fs.display_list, line) + table.insert(fs.lengths, display_length) + end + end +end + +local function stop() + if fs.previewing then + fs.previewing = nil + audio.tape_play_stop() + fs.redraw() + end +end + +local function timeout() + if fs.previewing_timeout_counter == nil then + fs.previewing_timeout_counter = clock.run(function() + clock.sleep(1) + fs.previewing_timeout_counter = nil + end) end end -fs.key = function(n,z) +local function start() + if fs.previewing_timeout_counter ~= nil then return end + timeout() + stop() + fs.previewing = fs.pos + audio.tape_play_open(fs.getdir() .. fs.file) + audio.tape_play_start() + fs.redraw() +end + +fs.key = function(n, z) -- back - if n==2 and z==1 then + if n == 2 and z == 1 then fs.done = true - -- select - elseif n==3 and z==1 then + stop() + -- select + elseif n == 3 and z == 1 then + stop() if #fs.list > 0 then - fs.file = fs.list[fs.pos+1] + if string.sub(fs.display_list[fs.pos + 1], -3) == '...' then + fs.file = fs.list[fs.pos + 1] + else + fs.file = fs.display_list[fs.pos + 1] + end if fs.file == "../" then fs.folders[fs.depth] = nil fs.depth = fs.depth - 1 fs.getlist() fs.redraw() - elseif string.find(fs.file,'/') then - --print("folder") + elseif string.find(fs.file, '/') then + --print("folder selected") fs.depth = fs.depth + 1 fs.folders[fs.depth] = fs.file fs.getlist() fs.redraw() else + -- print("file selected") local path = fs.folder - for k,v in pairs(fs.folders) do + for k, v in pairs(fs.folders) do path = path .. v end fs.path = path .. fs.file @@ -135,35 +195,46 @@ fs.key = function(n,z) end end -fs.enc = function(n,d) - if n==2 then - fs.pos = util.clamp(fs.pos + d, 0, fs.len - 1) +fs.enc = function(n, d) + if n == 2 then + fs.pos = util.clamp(fs.pos + d, 0, #fs.display_list - 1) fs.redraw() + elseif n == 3 and d > 0 then + fs.file = fs.display_list[fs.pos + 1] + if fs.lengths[fs.pos + 1] ~= "" then + start() + end + elseif n == 3 and d < 0 then + -- always stop with left scroll + stop() end end - fs.redraw = function() screen.clear() screen.font_face(1) screen.font_size(8) if #fs.list == 0 then screen.level(4) - screen.move(0,20) + screen.move(0, 20) screen.text("(no files)") else - for i=1,6 do - if (i > 2 - fs.pos) and (i < fs.len - fs.pos + 3) then - local list_index = i+fs.pos-2 - screen.move(0,10*i) - if(i==3) then + for i = 1, 6 do + if (i > 2 - fs.pos) and (i < #fs.display_list - fs.pos + 3) then + local list_index = i + fs.pos - 2 + screen.move(0, 10 * i) + if (i == 3) then screen.level(15) else screen.level(4) end - screen.text(fs.display_list[list_index]) + local text = fs.display_list[list_index] + if list_index - 1 == fs.previewing then + text = util.trim_string_to_width('* ' .. text, 97) + end + screen.text(text) if fs.lengths[list_index] then - screen.move(128,10*i) + screen.move(128, 10 * i) screen.text_right(fs.lengths[list_index]) end end diff --git a/lua/lib/lattice.lua b/lua/lib/lattice.lua index da89ef38c..e32a390c0 100644 --- a/lua/lib/lattice.lua +++ b/lua/lib/lattice.lua @@ -1,29 +1,27 @@ ---- module for creating a lattice of patterns based on a single fast "superclock" +---- module for creating a lattice of sprockets based on a single fast "superclock" -- -- @module Lattice --- @release v1.2.1 --- @author tyleretters & ezra & zack +-- @release v2.0 +-- @author tyleretters & ezra & zack & rylee -local Lattice, Pattern = {}, {} +local Lattice, Sprocket = {}, {} --- instantiate a new lattice -- @tparam[opt] table args optional named attributes are: -- - "auto" (boolean) turn off "auto" pulses from the norns clock, defaults to true --- - "meter" (number) of quarter notes per measure, defaults to 4 --- - "ppqn" (number) the number of pulses per quarter note of this superclock, defaults to 96 +-- - "ppqn" (number) the number of pulses per quarter cycle of this superclock, defaults to 96 -- @treturn table a new lattice function Lattice:new(args) local l = setmetatable({}, { __index = Lattice }) - local args = args == nil and {} or args + args = args == nil and {} or args l.auto = args.auto == nil and true or args.auto - l.meter = args.meter == nil and 4 or args.meter l.ppqn = args.ppqn == nil and 96 or args.ppqn l.enabled = false l.transport = 0 l.superclock_id = nil - l.pattern_id_counter = 100 - l.patterns = {} - l.pattern_ordering={} + l.sprocket_id_counter = 100 + l.sprockets = {} + l.sprocket_ordering = {{}, {}, {}, {}, {}} return l end @@ -37,18 +35,18 @@ end --- reset the norns clock without restarting lattice function Lattice:reset() - -- destroy clock, but not the patterns + -- destroy clock, but not the sprockets self:stop() - if self.superclock_id ~= nil then + if self.superclock_id ~= nil then clock.cancel(self.superclock_id) - self.superclock_id = nil + self.superclock_id = nil end - for i, pattern in pairs(self.patterns) do - pattern.phase = pattern.division * self.ppqn * self.meter * (1-pattern.delay) - pattern.downbeat = false + for i, sprocket in pairs(self.sprockets) do + sprocket.phase = sprocket.division * self.ppqn * 4 * (1 - sprocket.delay) -- "4" because in music a "quarter note" == "1/4" + sprocket.downbeat = false end self.transport = 0 - params:set("clock_reset",1) + params:set("clock_reset", 1) end --- reset the norns clock and restart lattice @@ -73,14 +71,13 @@ function Lattice:destroy() if self.superclock_id ~= nil then clock.cancel(self.superclock_id) end - self.patterns = {} - self.pattern_ordering={} + self.sprockets = {} + self.sprocket_ordering = {} end ---- set the meter of the lattice --- @tparam number meter the meter the lattice counts -function Lattice:set_meter(meter) - self.meter = meter +--- set_meter is deprecated +function Lattice:set_meter(_) + print("meter is deprecated") end --- use the norns clock to pulse @@ -90,82 +87,95 @@ function Lattice.auto_pulse(s) s:pulse() clock.sync(1/s.ppqn) end -end +end ---- advance all patterns in this lattice a single by pulse, call this manually if lattice.auto = false +--- advance all sprockets in this lattice a single by pulse, call this manually if lattice.auto = false function Lattice:pulse() if self.enabled then - local ppm = self.ppqn * self.meter + local ppc = self.ppqn * 4 -- pulses per cycle; "4" because in music a "quarter note" == "1/4" local flagged=false - for _, id in ipairs(self.pattern_ordering) do - local pattern=self.patterns[id] - if pattern.enabled then - pattern.phase = pattern.phase + 1 - local swing_val = (2*pattern.swing/100) - if not pattern.downbeat then - swing_val = (2*(100-pattern.swing)/100) - end - if pattern.phase > (pattern.division * ppm)*swing_val then - pattern.phase = pattern.phase - (pattern.division * ppm) - if pattern.delay_new ~= nil then - pattern.phase = pattern.phase - (pattern.division*ppm)*(1-(pattern.delay-pattern.delay_new)) - pattern.delay = pattern.delay_new - pattern.delay_new = nil + for i = 1, 5 do + for _, id in ipairs(self.sprocket_ordering[i]) do + local sprocket = self.sprockets[id] + if sprocket.enabled then + sprocket.phase = sprocket.phase + 1 + local swing_val = 2 * sprocket.swing / 100 + if not sprocket.downbeat then + swing_val = 1 end - pattern.action(self.transport) - pattern.downbeat = not pattern.downbeat + if sprocket.phase > sprocket.division * ppc * swing_val then + sprocket.phase = sprocket.phase - (sprocket.division * ppc) + if sprocket.delay_new ~= nil then + sprocket.phase = sprocket.phase - (sprocket.division * ppc) * (1 - (sprocket.delay - sprocket.delay_new)) + sprocket.delay = sprocket.delay_new + sprocket.delay_new = nil + end + sprocket.action(self.transport) + sprocket.downbeat = not sprocket.downbeat + end + elseif sprocket.flag then + self.sprockets[sprocket.id] = nil + flagged = true end - elseif pattern.flag then - self.patterns[pattern.id] = nil - flagged=true end end if flagged then - self:order_patterns() + self:order_sprockets() end self.transport = self.transport + 1 end end ---- factory method to add a new pattern to this lattice +--- factory method to add a new sprocket to this lattice -- @tparam[opt] table args optional named attributes are: -- - "action" (function) called on each step of this division (lattice.transport is passed as the argument), defaults to a no-op --- - "division" (number) the division of the pattern, defaults to 1/4 --- - "enabled" (boolean) is this pattern enabled, defaults to true +-- - "division" (number) the division of the sprocket, defaults to 1/4 +-- - "enabled" (boolean) is this sprocket enabled, defaults to true -- - "swing" (number) is the percentage of swing (0 - 100%), defaults to 50 -- - "delay" (number) specifies amount of delay, as fraction of division (0.0 - 1.0), defaults to 0 --- @treturn table a new pattern -function Lattice:new_pattern(args) - self.pattern_id_counter = self.pattern_id_counter + 1 - local args = args == nil and {} or args - args.id = self.pattern_id_counter +-- - "order" (number) specifies the place in line this lattice occupies from 1 to 5, lower first, defaults to 3 +-- @treturn table a new sprocket +function Lattice:new_sprocket(args) + self.sprocket_id_counter = self.sprocket_id_counter + 1 + args = args == nil and {} or args + args.id = self.sprocket_id_counter + args.order = args.order == nil and 3 or util.clamp(args.order, 1, 5) args.action = args.action == nil and function(t) return end or args.action args.division = args.division == nil and 1/4 or args.division args.enabled = args.enabled == nil and true or args.enabled - args.phase = args.division * self.ppqn * self.meter - args.swing = args.swing == nil and 50 or args.swing - args.delay = args.delay == nil and 0 or args.delay - local pattern = Pattern:new(args) - self.patterns[self.pattern_id_counter] = pattern - self:order_patterns() - return pattern + args.phase = args.division * self.ppqn * 4 -- "4" because in music a "quarter note" == "1/4" + args.swing = args.swing == nil and 50 or util.clamp(args.swing,0,100) + args.delay = args.delay == nil and 0 or util.clamp(args.delay,0,1) + local sprocket = Sprocket:new(args) + self.sprockets[self.sprocket_id_counter] = sprocket + self:order_sprockets() + return sprocket end --- "private" method to keep numerical order of the pattern ids +--- new_pattern is deprecated +function Lattice:new_pattern(args) + print("'new_pattern' is deprecated; use 'new_sprocket' instead.") + return self:new_sprocket(args) +end + +--- "private" method to keep numerical order of the sprocket ids -- for use when pulsing -function Lattice:order_patterns() - self.pattern_ordering={} - for id, pattern in pairs(self.patterns) do - table.insert(self.pattern_ordering,id) +function Lattice:order_sprockets() + self.sprocket_ordering = {{}, {}, {}, {}, {}} + for id, sprocket in pairs(self.sprockets) do + table.insert(self.sprocket_ordering[sprocket.order],id) + end + for i = 1, 5 do + table.sort(self.sprocket_ordering[i]) end - table.sort(self.pattern_ordering) end ---- "private" method to instantiate a new pattern, only called by Lattice:new_pattern() --- @treturn table a new pattern -function Pattern:new(args) - local p = setmetatable({}, { __index = Pattern }) +--- "private" method to instantiate a new sprocket, only called by Lattice:new_sprocket() +-- @treturn table a new sprocket +function Sprocket:new(args) + local p = setmetatable({}, { __index = Sprocket }) p.id = args.id + p.order = args.order p.division = args.division p.action = args.action p.enabled = args.enabled @@ -177,48 +187,48 @@ function Pattern:new(args) return p end ---- start the pattern -function Pattern:start() +--- start the sprocket +function Sprocket:start() self.enabled = true end ---- stop the pattern -function Pattern:stop() +--- stop the sprocket +function Sprocket:stop() self.enabled = false end ---- toggle the pattern -function Pattern:toggle() +--- toggle the sprocket +function Sprocket:toggle() self.enabled = not self.enabled end ---- flag the pattern to be destroyed -function Pattern:destroy() +--- flag the sprocket to be destroyed +function Sprocket:destroy() self.enabled = false self.flag = true end ---- set the division of the pattern --- @tparam number n the division of the pattern -function Pattern:set_division(n) +--- set the division of the sprocket +-- @tparam number n the division of the sprocket +function Sprocket:set_division(n) self.division = n end ---- set the action for this pattern +--- set the action for this sprocket -- @tparam function the action -function Pattern:set_action(fn) +function Sprocket:set_action(fn) self.action = fn end ---- set the swing of the pattern +--- set the swing of the sprocket -- @tparam number the swing value 0-100% -function Pattern:set_swing(swing) - self.swing=util.clamp(swing,0,100) +function Sprocket:set_swing(swing) + self.swing = util.clamp(swing,0,100) end --- set the delay for this pattern +--- set the delay for this sprocket -- @tparam fraction of the time between beats to delay (0-1) -function Pattern:set_delay(delay) +function Sprocket:set_delay(delay) self.delay_new = util.clamp(delay,0,1) end diff --git a/lua/lib/lfo.lua b/lua/lib/lfo.lua new file mode 100755 index 000000000..e0c92cb06 --- /dev/null +++ b/lua/lib/lfo.lua @@ -0,0 +1,525 @@ +-- LFOs for general-purpose scripting +-- @module lib.lfo +-- inspired by contributions from @markwheeler (changes), @justmat (hnds), and @sixolet (toolkit) +-- added by @dndrks + @sixolet, with improvements by @Dewb + +local lattice = require 'lattice' + +local LFO = {} +LFO.__index = LFO + +local lfo_rates = {1/16,1/8,1/4,5/16,1/3,3/8,1/2,3/4,1,1.5,2,3,4,6,8,16,32,64,128,256,512,1024} +local lfo_rates_as_strings = {"1/16","1/8","1/4","5/16","1/3","3/8","1/2","3/4","1","1.5","2","3","4","6","8","16","32","64","128","256","512","1024"} +local lfo_shapes = {'sine','tri','square','random','up','down'} + +local params_per_entry = 14 + +function LFO.init() + if norns.lfo == nil then + norns.lfo = {lattice = lattice:new()} + end +end + +local function scale_lfo(target) + if target.baseline == 'min' then + target.scaled_min = target.min + target.scaled_max = target.min + target.percentage + target.mid = util.linlin(target.min,target.max,target.scaled_min,target.scaled_max,(target.min+target.max)/2) + elseif target.baseline == 'center' then + target.mid = (target.min+target.max)/2 + local centroid_mid = math.abs(target.min-target.max) * (target.depth/2) + target.scaled_min = util.clamp(target.mid - centroid_mid,target.min,target.max) + target.scaled_max = util.clamp(target.mid + centroid_mid,target.min,target.max) + elseif target.baseline == 'max' then + target.mid = (target.min+target.max)/2 + target.scaled_min = target.max * (1-(target.depth)) + target.scaled_max = target.max + target.mid = math.abs(util.linlin(target.min,target.max,target.scaled_min,target.scaled_max,target.mid)) + end +end + +--- construct an LFO +-- @param string shape The shape for this LFO (options: 'sine', 'tri', 'up', 'down', 'square', 'random'; default: 'sine') +-- @param number min The minimum bound for this LFO (default: 0) +-- @param number max The maximum bound for this LFO (default: 1) +-- @param number depth The depth of modulation between min/max (range: 0.0 to 1.0; default: 0.0) +-- @param string mode How to advance the LFO (options: 'clocked', 'free'; default: 'clocked') +-- @param number period The timing of this LFO's advancement. If mode is 'clocked', argument is in beats. If mode is 'free', argument is in seconds. +-- @param function action A callback function to perform as the LFO advances. This library passes both the scaled and the raw value to the callback function. +-- @param number phase The phase shift amount for this LFO (range: 0.0 to 1.0,; default: 0) +-- @param string baseline From where the LFO should start (options: 'min', 'center', 'max'; default: 'min') +function LFO.new(shape, min, max, depth, mode, period, action, phase, baseline) + local i = {} + setmetatable(i, LFO) + i.init() + i.scaled = 0 + i.raw = 0 + i.phase_counter = 0 + if shape == 'saw' then + shape = 'up' + end + i.shape = shape == nil and 'sine' or shape + i.min = min == nil and 0 or min + i.max = max == nil and 1 or max + i.depth = depth == nil and 0 or depth + i.enabled = 0 + i.mode = mode == nil and 'clocked' or mode + i.period = period == nil and 4 or period + i.reset_target = 'floor' + i.baseline = baseline == nil and 'min' or baseline + i.offset = 0 + i.ppqn = 96 + i.controlspec = { + warp = 'linear', + step = 0.01, + units = '', + quantum = 0.01, + wrap = false, + formatter = nil + } + i.action = action == nil and (function(scaled, raw) end) or action + i.percentage = math.abs(i.min-i.max) * i.depth + i.scaled_min = i.min + i.scaled_max = i.max + i.mid = 0 + i.rand_value = 0 + i.phase = phase == nil and 0 or phase + scale_lfo(i) + return i +end + +--- construct an LFO via table arguments +-- eg. my_lfo:add{shape = 'sine', min = 200, max = 12000} +-- @tparam string shape The shape for this LFO (options: 'sine', 'tri', 'up', 'down', 'square', 'random'; default: 'sine') +-- @tparam number min The minimum bound for this LFO (default: 0) +-- @tparam number max The maximum bound for this LFO (default: 1) +-- @tparam number depth The depth of modulation between min/max (range: 0.0 to 1.0; default: 0.0) +-- @tparam string mode How to advance the LFO (options: 'clocked', 'free'; default: 'clocked') +-- @tparam number period The timing of this LFO's advancement. If mode is 'clocked', argument is in beats. If mode is 'free', argument is in seconds. +-- @tparam function action A callback function to perform as the LFO advances. This library passes both the scaled and the raw value to the callback function. +-- @param number phase The phase shift amount for this LFO (range: 0.0 to 1.0,; default: 0) +-- @param string baseline From where the LFO should start (options: 'min', 'center', 'max'; default: 'min') +function LFO:add(args) + return self.new(args.shape, args.min, args.max, args.depth, args.mode, args.period, args.action, args.phase, args.baseline) +end + +-- PARAMETERS UI / +local function lfo_params_visibility(state, i) + params[state](params, "lfo_baseline_"..i) + params[state](params, "lfo_offset_"..i) + params[state](params, "lfo_depth_"..i) + params[state](params, "lfo_scaled_"..i) + params[state](params, "lfo_raw_"..i) + params[state](params, "lfo_mode_"..i) + if state == "show" then + if params:get("lfo_mode_"..i) == 1 then + params:hide("lfo_free_"..i) + params:show("lfo_clocked_"..i) + elseif params:get("lfo_mode_"..i) == 2 then + params:hide("lfo_clocked_"..i) + params:show("lfo_free_"..i) + end + else + params:hide("lfo_clocked_"..i) + params:hide("lfo_free_"..i) + end + params[state](params, "lfo_shape_"..i) + params[state](params, "lfo_min_"..i) + params[state](params, "lfo_max_"..i) + params[state](params, "lfo_reset_"..i) + params[state](params, "lfo_reset_target_"..i) + _menu.rebuild_params() +end + +local function build_lfo_spec(lfo,i,bound) + params:add{ + type = 'control', + id = 'lfo_'..bound..'_'..i, + name = 'lfo '..bound, + controlspec = controlspec.new( + lfo.min, + lfo.max, + lfo.controlspec.warp, + lfo.controlspec.step, + bound == 'min' and lfo.min or lfo.max, + lfo.controlspec.units, + lfo.controlspec.quantum, + lfo.controlspec.wrap + ), + formatter = lfo.controlspec.formatter + } + params:set_action('lfo_'..bound..'_'..i, function(x) + lfo:set(bound,x) + end) +end + +local function lfo_bang(i) + local function lb(prm) + params:lookup_param("lfo_"..prm.."_"..i):bang() + end + lb('depth') + lb('min') + lb('max') + lb('baseline') + lb('offset') + lb('mode') + lb('clocked') + lb('free') + lb('shape') + lb('reset') + lb('reset_target') +end + +-- / PARAMETERS UI + +-- SCRIPTING / + +local function change_bound(target, which, value) + target[which] = value + target.percentage = math.abs(target.min-target.max) * target.depth + scale_lfo(target) +end + +local function change_baseline(target, value) + target.baseline = value + scale_lfo(target) +end + +local function change_depth(target, value) + target.depth = value + target.percentage = math.abs(target.min-target.max) * value + scale_lfo(target) +end + +local function change_ppqn(target, ppqn) + target.ppqn = ppqn + if target.sprocket then + target.sprocket:set_division(1/(4*ppqn)) + end +end + +local function change_period(target, new_period) + local new_phase_counter = target.phase_counter + (1/target.ppqn) + local new_phase + local adjusted_phase_counter + if target.mode == "clocked" then + new_phase = new_phase_counter / target.period + adjusted_phase_counter = (new_phase * new_period) - 1/target.ppqn + else + new_phase = new_phase_counter * clock.get_beat_sec() / target.period + adjusted_phase_counter = (new_phase * new_period / clock.get_beat_sec()) - 1/target.ppqn + end + + target.period = new_period + target.phase_counter = adjusted_phase_counter +end + +local function process_lfo(id) + local _lfo = id + local phase + + _lfo.phase_counter = _lfo.phase_counter + (1/_lfo.ppqn) + if _lfo.mode == "clocked" then + phase = _lfo.phase_counter / _lfo.period + else + phase = _lfo.phase_counter * clock.get_beat_sec() / _lfo.period + end + phase = (phase + _lfo.phase) % 1 + + if _lfo.enabled == 1 then + + local current_val; + if _lfo.shape == 'sine' then + current_val = (math.sin(2*math.pi*phase) + 1)/2 + elseif _lfo.shape == 'tri' then + current_val = phase < 0.5 and phase/0.5 or 1-(phase-0.5)/(0.5) + elseif _lfo.shape == 'up' then + current_val = phase + elseif _lfo.shape == 'down' then + current_val = 1-phase + elseif _lfo.shape == 'square' then + current_val = phase < 0.5 and 1 or 0 + elseif _lfo.shape == 'random' then + current_val = (math.sin(2*math.pi*phase) + 1)/2 + end + + local min = _lfo.min + local max = _lfo.max + + if _lfo.shape ~= 'random' then + _lfo.raw = current_val + end + current_val = current_val + _lfo.offset + local value = util.linlin(0,1,min,min + _lfo.percentage,current_val) + + if _lfo.depth > 0 then + + if _lfo.baseline == 'center' then + value = util.linlin(0,1,_lfo.scaled_min,_lfo.scaled_max,current_val) + elseif _lfo.baseline == 'max' then + value = util.linlin(0,1,_lfo.scaled_max,_lfo.scaled_min,current_val) + end + + if _lfo.shape == 'sine' or _lfo.shape == 'tri' or _lfo.shape == 'up' or _lfo.shape == 'down' then + value = util.clamp(value,min,max) + _lfo.scaled = value + elseif _lfo.shape == 'square' then + local square_value = value >= _lfo.mid and max or min + square_value = util.linlin(min,max,_lfo.scaled_min,_lfo.scaled_max,square_value) + square_value = util.clamp(square_value,_lfo.scaled_min,_lfo.scaled_max) + _lfo.scaled = square_value + _lfo.raw = util.linlin(_lfo.scaled_min,_lfo.scaled_max,0,1,square_value) + elseif _lfo.shape == 'random' then + local prev_value = _lfo.rand_value + _lfo.rand_value = value >= _lfo.mid and max or min + local rand_value; + if prev_value ~= _lfo.rand_value then + rand_value = util.linlin(min,max,_lfo.scaled_min,_lfo.scaled_max,math.random(math.floor(min*100),math.floor(max*100))/100) + rand_value = util.clamp(rand_value,min,max) + _lfo.scaled = rand_value + _lfo.raw = util.linlin(_lfo.scaled_min,_lfo.scaled_max,0,1,rand_value) + end + end + + _lfo.action(_lfo.scaled, _lfo.raw) + + if _lfo.parameter_id ~= nil then + params:set("lfo_scaled_".._lfo.parameter_id,util.round(_lfo.scaled,0.01)) + params:set("lfo_raw_".._lfo.parameter_id,util.round(_lfo.raw,0.01)) + end + + end + end +end + +--- start LFO +function LFO:start(from_parameter) + if self.sprocket == nil then + self:reset_phase() + self.sprocket = norns.lfo.lattice:new_sprocket{ + action=function() process_lfo(self) end, + division=1/(4*self.ppqn), + enabled=true, + } + if not norns.lfo.lattice.enabled then + norns.lfo.lattice:start() + end + self.enabled = 1 + if not from_parameter and self.parameter_id ~= nil then + params:set('lfo_'..self.parameter_id,2) + end + end +end + +--- stop LFO +function LFO:stop() + if self.sprocket ~= nil then + self.sprocket:destroy() + self.sprocket = nil + self.enabled = 0 + if next(norns.lfo.lattice.sprockets) == nil then + norns.lfo.lattice:stop() + end + end +end + +--- set LFO variable state +-- @tparam string var The variable to target (options: 'shape', 'min', 'max', 'depth', 'offset', 'phase', 'mode', 'period', 'reset_target', 'baseline', 'action', 'ppqn') +-- @tparam various arg The argument to pass to the target (often numbers + strings, but 'action' expects a function) +function LFO:set(var, arg) + if var == nil then + error('scripted LFO variable required') + elseif var == 'min' or var == 'max' then + change_bound(self, var, arg) + elseif var == 'depth' then + change_depth(self, arg) + elseif var == 'baseline' then + change_baseline(self, arg) + elseif var == 'ppqn' then + change_ppqn(self, arg) + elseif var == 'period' then + change_period(self, arg) + elseif arg == nil then + error('scripted LFO argument required') + elseif not self[var] then + error("scripted LFO variable '"..var.."' not valid") + else + self[var] = arg + end +end + +--- get LFO variable state +-- @tparam string var The variable to query (options: 'shape', 'min', 'max', 'depth', 'offset', 'phase', 'mode', 'period', 'reset_target', 'baseline', 'action', 'enabled', 'controlspec') +function LFO:get(var) + if var == nil then + error('scripted LFO variable required') + elseif not self[var] then + error("scripted LFO variable '"..var.."' not valid") + else + return self[var] + end +end + +--- reset the LFO's phase +function LFO:reset_phase() + if self.mode == "free" then + local baseline = clock.get_beat_sec()/self.period + if self.reset_target == "floor" then + self.phase_counter = 0.75/baseline + elseif self.reset_target == "ceiling" then + self.phase_counter = 0.25/baseline + elseif self.reset_target == "mid: falling" then + self.phase_counter = 0.5/baseline + elseif self.reset_target == "mid: rising" then + self.phase_counter = baseline + end + else + if self.reset_target == "floor" then + self.phase_counter = 0.75 * (self.period) + elseif self.reset_target == "ceiling" then + self.phase_counter = 0.25 * (self.period) + elseif self.reset_target == "mid: falling" then + self.phase_counter = 0.5 * (self.period) + elseif self.reset_target == "mid: rising" then + self.phase_counter = self.period + end + end +end + +-- / SCRIPT LFOS + +--- Build parameter menu UI for an already-constructed LFO. +-- @tparam string id The parameter ID to use for this LFO. +-- @tparam[opt] string separator A separator name for the LFO parameters. +-- @tparam[opt] string group A group name for the LFO parameters. +function LFO:add_params(id,sep,group) + if id ~= nil then + if params.lookup["lfo_"..id] == nil then + + if group then + if sep ~= nil then + params:add_group("lfo_group_"..id,group,params_per_entry+1) + else + params:add_group("lfo_group_"..id,group,params_per_entry) + end + end + + if sep then + params:add_separator("lfo_sep_"..sep,sep) + end + + params:add_option("lfo_"..id,"lfo state",{"off","on"},self:get('enabled')+1) + params:set_action("lfo_"..id,function(x) + if x == 1 then + lfo_params_visibility("hide", id) + params:set("lfo_scaled_"..id,"") + params:set("lfo_raw_"..id,"") + self:stop() + elseif x == 2 then + lfo_params_visibility("show", id) + self:start(true) + end + self:set('enabled',x-1) + lfo_bang(id) + end) + + params:add_option("lfo_shape_"..id, "lfo shape", lfo_shapes, tab.key(lfo_shapes,self:get('shape'))) + params:set_action("lfo_shape_"..id, function(x) self:set('shape', params:lookup_param("lfo_shape_"..id).options[x]) end) + + params:add_number("lfo_depth_"..id,"lfo depth",0,100,self:get('depth')*100,function(param) return (param:get().."%") end) + params:set_action("lfo_depth_"..id, function(x) + if x == 0 then + params:set("lfo_scaled_"..id,"") + params:set("lfo_raw_"..id,"") + end + self:set('depth',x/100) + end) + + params:add_number('lfo_phase_'..id, 'lfo phase', 0, 100, self:get('phase') * 100, function(param) return (param:get()..'%') end) + params:set_action('lfo_phase_'..id, function(x) + self:set('phase',x/100) + end) + + params:add_number('lfo_offset_'..id, 'lfo offset', -100, 100, self:get('offset')*100, function(param) return (param:get().."%") end) + params:set_action("lfo_offset_"..id, function(x) + self:set('offset',x/100) + end) + + params:add_text("lfo_scaled_"..id," scaled value","") + params:add_text("lfo_raw_"..id," raw value","") + + build_lfo_spec(self,id,"min") + build_lfo_spec(self,id,"max") + + local baseline_options = {"from min", "from center", "from max"} + params:add_option("lfo_baseline_"..id, "lfo baseline", baseline_options, tab.key(baseline_options,'from '..self:get('baseline'))) + params:set_action("lfo_baseline_"..id, function(x) + self:set('baseline',string.gsub(params:lookup_param("lfo_baseline_"..id).options[x],"from ","")) + _menu.rebuild_params() + end) + + local mode_options = {'clocked', 'free'} + params:add_option("lfo_mode_"..id, "lfo mode", mode_options, mode_options[self:get('mode')]) + params:set_action("lfo_mode_"..id, + function(x) + self:set('mode',params:lookup_param("lfo_mode_"..id).options[x]) + self:set( + 'period', + x == 1 and (lfo_rates[params:get('lfo_clocked_'..id)] * 4) or params:get('lfo_free_'..id) + ) + if x == 1 and params:string("lfo_"..id) == "on" then + params:hide("lfo_free_"..id) + params:show("lfo_clocked_"..id) + elseif x == 2 and params:string("lfo_"..id) == "on" then + params:hide("lfo_clocked_"..id) + params:show("lfo_free_"..id) + end + _menu.rebuild_params() + end + ) + + local current_period_as_rate = self:get('mode') == 'clocked' and lfo_rates[lfo_rates_as_strings[self:get('period')]] or lfo_rates[self:get('period')] + local rate_index = tab.key(lfo_rates,self:get('period')) + params:add_option("lfo_clocked_"..id, "lfo rate", lfo_rates_as_strings, rate_index) + params:set_action("lfo_clocked_"..id, + function(x) + if params:string("lfo_mode_"..id) == "clocked" then + self:set('period',lfo_rates[x] * 4) + end + end + ) + + params:add{ + type='control', + id="lfo_free_"..id, + name="lfo rate", + controlspec=controlspec.new(0.1,300,'exp',0.1,current_period_as_rate,'sec') + } + params:set_action("lfo_free_"..id, function(x) + if params:string("lfo_mode_"..id) == "free" then + self:set('period', x) + end + end) + + params:add_trigger("lfo_reset_"..id, "reset lfo") + params:set_action("lfo_reset_"..id, function(x) self:reset_phase() end) + + local reset_destinations = {"floor","ceiling","mid: rising","mid: falling"} + params:add_option("lfo_reset_target_"..id, "reset lfo to", reset_destinations, reset_destinations[self:get('reset_target')]) + params:set_action("lfo_reset_target_"..id, function(x) + self:set('reset_target', params:lookup_param("lfo_reset_target_"..id).options[x]) + end) + + params:hide("lfo_free_"..id) + + params:lookup_param("lfo_"..id):bang() + self.parameter_id = id + else + print('! params for LFO '..id..' already added !') + end + else + print('! parameter id is required to add LFO parameters') + end +end + +return LFO \ No newline at end of file diff --git a/lua/lib/musicutil.lua b/lua/lib/musicutil.lua index 247de2d9a..1f8816d4b 100644 --- a/lua/lib/musicutil.lua +++ b/lua/lib/musicutil.lua @@ -11,8 +11,8 @@ MusicUtil.NOTE_NAMES = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A MusicUtil.SCALES = { {name = "Major", alt_names = {"Ionian"}, intervals = {0, 2, 4, 5, 7, 9, 11, 12}, chords = {{1, 2, 3, 4, 5, 6, 7, 14}, {14, 15, 17, 18, 19, 20, 21, 22, 23}, {14, 15, 17, 19}, {1, 2, 3, 4, 5}, {1, 2, 4, 8, 9, 10, 11, 14, 15}, {14, 15, 17, 19, 21, 22}, {24, 26}, {1, 2, 3, 4, 5, 6, 7, 14}}}, {name = "Natural Minor", alt_names = {"Minor", "Aeolian"}, intervals = {0, 2, 3, 5, 7, 8, 10, 12}, chords = {{14, 15, 17, 19, 21, 22}, {24, 26}, {1, 2, 3, 4, 5, 6, 7, 14}, {14, 15, 17, 18, 19, 20, 21, 22, 23}, {14, 15, 17, 19}, {1, 2, 3, 4, 5}, {1, 2, 4, 8, 9, 10, 11, 14, 15}, {14, 15, 17, 19, 21, 22}}}, - {name = "Harmonic Minor", intervals = {0, 2, 3, 5, 7, 8, 11, 12}, chords = {{14, 16, 17}, {24, 25, 26}, {12}, {17, 18, 19, 20, 21, 24, 25, 26}, {1, 8, 12, 13, 14, 15}, {1, 2, 3, 16, 17, 18, 24, 25}, {12, 24, 25}, {14, 16, 17}}}, - {name = "Melodic Minor", intervals = {0, 2, 3, 5, 7, 9, 11, 12}, chords = {{14, 16, 17, 18, 20}, {14, 15, 17, 18, 19}, {12}, {1, 2, 4, 8, 9}, {1, 8, 9, 10, 12, 13, 14, 15}, {24, 26}, {12, 13, 24, 26}, {14, 16, 17, 18, 20}}}, + {name = "Harmonic Minor", intervals = {0, 2, 3, 5, 7, 8, 11, 12}, chords = {{14, 16, 17}, {24, 25, 26}, {12, 27}, {17, 18, 19, 20, 21, 24, 25, 26}, {1, 8, 12, 13, 14, 15}, {1, 2, 3, 16, 17, 18, 24, 25}, {12, 24, 25}, {14, 16, 17}}}, + {name = "Melodic Minor", intervals = {0, 2, 3, 5, 7, 9, 11, 12}, chords = {{14, 16, 17, 18, 20}, {14, 15, 17, 18, 19}, {12, 27}, {1, 2, 4, 8, 9}, {1, 8, 9, 10, 12, 13, 14, 15}, {24, 26}, {12, 13, 24, 26}, {14, 16, 17, 18, 20}}}, {name = "Dorian", intervals = {0, 2, 3, 5, 7, 9, 10, 12}, chords = {{14, 15, 17, 18, 19, 20, 21, 22, 23}, {14, 15, 17, 19}, {1, 2, 3, 4, 5}, {1, 2, 4, 8, 9, 10, 11, 14, 15}, {14, 15, 17, 19, 21, 22}, {24, 26}, {1, 2, 3, 4, 5, 6, 7, 14}, {14, 15, 17, 18, 19, 20, 21, 22, 23}}}, {name = "Phrygian", intervals = {0, 1, 3, 5, 7, 8, 10, 12}, chords = {{14, 15, 17, 19}, {1, 2, 3, 4, 5}, {1, 2, 4, 8, 9, 10, 11, 14, 15}, {14, 15, 17, 19, 21, 22}, {24, 26}, {1, 2, 3, 4, 5, 6, 7, 14}, {14, 15, 17, 18, 19, 20, 21, 22, 23}, {14, 15, 17, 19}}}, {name = "Lydian", intervals = {0, 2, 4, 6, 7, 9, 11, 12}, chords = {{1, 2, 3, 4, 5}, {1, 2, 4, 8, 9, 10, 11, 14, 15}, {14, 15, 17, 19, 21, 22}, {24, 26}, {1, 2, 3, 4, 5, 6, 7, 14}, {14, 15, 17, 18, 19, 20, 21, 22, 23}, {14, 15, 17, 19}, {1, 2, 3, 4, 5}}}, @@ -21,35 +21,35 @@ MusicUtil.SCALES = { {name = "Whole Tone", intervals = {0, 2, 4, 6, 8, 10, 12}, chords = {{12, 13}, {12, 13}, {12, 13}, {12, 13}, {12, 13}, {12, 13}, {12, 13}}}, {name = "Major Pentatonic", alt_names = {"Gagaku Ryo Sen Pou"}, intervals = {0, 2, 4, 7, 9, 12}, chords = {{1, 2, 4}, {14, 15}, {}, {14}, {14, 15, 17, 19}, {1, 2, 4}}}, {name = "Minor Pentatonic", alt_names = {"Zokugaku Yo Sen Pou"}, intervals = {0, 3, 5, 7, 10, 12}, chords = {{14, 15, 17, 19}, {1, 2, 4}, {14, 15}, {}, {14}, {14, 15, 17, 19}}}, - {name = "Major Bebop", intervals = {0, 2, 4, 5, 7, 8, 9, 11, 12}, chords = {{1, 2, 3, 4, 5, 6, 7, 12, 14}, {14, 15, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26}, {1, 8, 12, 13, 14, 15, 17, 19}, {1, 2, 3, 4, 5, 16, 17, 18, 20, 24, 25}, {1, 2, 4, 8, 9, 10, 11, 14, 15}, {12, 24, 25}, {14, 15, 16, 17, 19, 21, 22}, {24, 25, 26}, {1, 2, 3, 4, 5, 6, 7, 12, 14}}}, - {name = "Altered Scale", intervals = {0, 1, 3, 4, 6, 8, 10, 12}, chords = {{12, 13, 24, 26}, {14, 16, 17, 18, 20}, {14, 15, 17, 18, 19}, {12}, {1, 2, 4, 8, 9}, {1, 8, 9, 10, 12, 13, 14, 15}, {24, 26}, {12, 13, 24, 26}}}, + {name = "Major Bebop", intervals = {0, 2, 4, 5, 7, 8, 9, 11, 12}, chords = {{1, 2, 3, 4, 5, 6, 7, 12, 14, 27}, {14, 15, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26}, {1, 8, 12, 13, 14, 15, 17, 19}, {1, 2, 3, 4, 5, 16, 17, 18, 20, 24, 25}, {1, 2, 4, 8, 9, 10, 11, 14, 15}, {12, 24, 25, 27}, {14, 15, 16, 17, 19, 21, 22}, {24, 25, 26}, {1, 2, 3, 4, 5, 6, 7, 12, 14, 27}}}, + {name = "Altered Scale", intervals = {0, 1, 3, 4, 6, 8, 10, 12}, chords = {{12, 13, 24, 26}, {14, 16, 17, 18, 20}, {14, 15, 17, 18, 19}, {12, 27}, {1, 2, 4, 8, 9}, {1, 8, 9, 10, 12, 13, 14, 15}, {24, 26}, {12, 13, 24, 26}}}, {name = "Dorian Bebop", intervals = {0, 2, 3, 4, 5, 7, 9, 10, 12}, chords = {{1, 2, 4, 8, 9, 10, 11, 14, 15, 17, 18, 19, 20, 21, 22, 23}, {14, 15, 17, 19, 21, 22}, {1, 2, 3, 4, 5}, {24, 26}, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 14, 15}, {14, 15, 17, 18, 19, 20, 21, 22, 23}, {14, 15, 17, 19, 24, 26}, {1, 2, 3, 4, 5, 6, 7, 14}, {1, 2, 4, 8, 9, 10, 11, 14, 15, 17, 18, 19, 20, 21, 22, 23}}}, {name = "Mixolydian Bebop", intervals = {0, 2, 4, 5, 7, 9, 10, 11, 12}, chords = {{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 14, 15}, {14, 15, 17, 18, 19, 20, 21, 22, 23}, {14, 15, 17, 19, 24, 26}, {1, 2, 3, 4, 5, 6, 7, 14}, {1, 2, 4, 8, 9, 10, 11, 14, 15, 17, 18, 19, 20, 21, 22, 23}, {14, 15, 17, 19, 21, 22}, {1, 2, 3, 4, 5}, {24, 26}, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 14, 15}}}, {name = "Blues Scale", alt_names = {"Blues"}, intervals = {0, 3, 5, 6, 7, 10, 12}, chords = {{14, 15, 17, 19, 24, 26}, {1, 2, 4, 17, 18, 20}, {14, 15}, {}, {}, {14}, {14, 15, 17, 19, 24, 26}}}, {name = "Diminished Whole Half", intervals = {0, 2, 3, 5, 6, 8, 9, 11, 12}, chords = {{24, 25}, {1, 2, 8, 17, 18, 19, 24, 25, 26}, {24, 25}, {1, 2, 8, 17, 18, 19, 24, 25, 26}, {24, 25}, {1, 2, 8, 17, 18, 19, 24, 25, 26}, {24, 25}, {1, 2, 8, 17, 18, 19, 24, 25, 26}, {24, 25}}}, {name = "Diminished Half Whole", intervals = {0, 1, 3, 4, 6, 7, 9, 10, 12}, chords = {{1, 2, 8, 17, 18, 19, 24, 25, 26}, {24, 25}, {1, 2, 8, 17, 18, 19, 24, 25, 26}, {24, 25}, {1, 2, 8, 17, 18, 19, 24, 25, 26}, {24, 25}, {1, 2, 8, 17, 18, 19, 24, 25, 26}, {24, 25}, {1, 2, 8, 17, 18, 19, 24, 25, 26}}}, - {name = "Neapolitan Major", intervals = {0, 1, 3, 5, 7, 9, 11, 12}, chords = {{14, 16, 17, 18}, {12, 13}, {12, 13}, {1, 8, 9, 12, 13}, {12, 13}, {12, 13, 24, 26}, {12, 13}, {14, 16, 17, 18}}}, + {name = "Neapolitan Major", intervals = {0, 1, 3, 5, 7, 9, 11, 12}, chords = {{14, 16, 17, 18}, {12, 13, 27}, {12, 13}, {1, 8, 9, 12, 13}, {12, 13}, {12, 13, 24, 26}, {12, 13}, {14, 16, 17, 18}}}, {name = "Hungarian Major", intervals = {0, 3, 4, 6, 7, 9, 10, 12}, chords = {{1, 2, 8, 17, 18, 19, 24, 25, 26}, {1, 2, 17, 18, 24, 25}, {24}, {24, 25, 26}, {}, {17, 18, 19, 24, 25, 26}, {}, {1, 2, 8, 17, 18, 19, 24, 25, 26}}}, - {name = "Harmonic Major", intervals = {0, 2, 4, 5, 7, 8, 11, 12}, chords = {{1, 3, 5, 6, 12, 14}, {24, 25, 26}, {1, 8, 12, 13, 17, 19}, {16, 17, 18, 20, 24, 25}, {1, 2, 8, 14, 15}, {12, 24, 25}, {24, 25}, {1, 3, 5, 6, 12, 14}}}, - {name = "Hungarian Minor", intervals = {0, 2, 3, 6, 7, 8, 11, 12}, chords = {{16, 17, 24}, {}, {12}, {}, {1, 3, 12, 14}, {1, 3, 8, 16, 17, 19, 24, 26}, {1, 2, 12, 17, 18}, {16, 17, 24}}}, - {name = "Lydian Minor", intervals = {0, 2, 4, 6, 7, 8, 10, 12}, chords = {{1, 8, 9, 12, 13}, {12, 13}, {12, 13, 24, 26}, {12, 13}, {14, 16, 17, 18}, {12, 13}, {12, 13}, {1, 8, 9, 12, 13}}}, + {name = "Harmonic Major", intervals = {0, 2, 4, 5, 7, 8, 11, 12}, chords = {{1, 3, 5, 6, 12, 14, 27}, {24, 25, 26}, {1, 8, 12, 13, 17, 19}, {16, 17, 18, 20, 24, 25}, {1, 2, 8, 14, 15}, {12, 24, 25, 27}, {24, 25}, {1, 3, 5, 6, 12, 14, 27}}}, + {name = "Hungarian Minor", intervals = {0, 2, 3, 6, 7, 8, 11, 12}, chords = {{16, 17, 24}, {}, {12, 27}, {}, {1, 3, 12, 14, 27}, {1, 3, 8, 16, 17, 19, 24, 26}, {1, 2, 12, 17, 18}, {16, 17, 24}}}, + {name = "Lydian Minor", intervals = {0, 2, 4, 6, 7, 8, 10, 12}, chords = {{1, 8, 9, 12, 13}, {12, 13}, {12, 13, 24, 26}, {12, 13}, {14, 16, 17, 18}, {12, 13, 27}, {12, 13}, {1, 8, 9, 12, 13}}}, {name = "Neapolitan Minor", alt_names = {"Byzantine"}, intervals = {0, 1, 3, 5, 7, 8, 11, 12}, chords = {{14, 16, 17}, {1, 3, 5, 8, 9}, {12, 13}, {17, 19, 21, 24, 26}, {12, 13}, {1, 2, 3, 14, 16, 17, 18}, {12}, {14, 16, 17}}}, - {name = "Major Locrian", intervals = {0, 2, 4, 5, 6, 8, 10, 12}, chords = {{12, 13}, {12, 13, 24, 26}, {12, 13}, {14, 16, 17, 18}, {12, 13}, {12, 13}, {1, 8, 9, 12, 13}, {12, 13}}}, - {name = "Leading Whole Tone", intervals = {0, 2, 4, 6, 8, 10, 11, 12}, chords = {{12, 13}, {12, 13}, {1, 8, 9, 12, 13}, {12, 13}, {12, 13, 24, 26}, {12, 13}, {14, 16, 17, 18}, {12, 13}}}, - {name = "Six Tone Symmetrical", intervals = {0, 1, 4, 5, 8, 9, 11, 12}, chords = {{12}, {1, 3, 8, 12, 13, 16, 17, 19}, {1, 2, 12, 14}, {1, 3, 12, 16, 17, 24}, {12}, {1, 3, 5, 12, 16, 17}, {}, {12}}}, + {name = "Major Locrian", intervals = {0, 2, 4, 5, 6, 8, 10, 12}, chords = {{12, 13}, {12, 13, 24, 26}, {12, 13}, {14, 16, 17, 18}, {12, 13, 27}, {12, 13}, {1, 8, 9, 12, 13}, {12, 13}}}, + {name = "Leading Whole Tone", intervals = {0, 2, 4, 6, 8, 10, 11, 12}, chords = {{12, 13, 27}, {12, 13}, {1, 8, 9, 12, 13}, {12, 13}, {12, 13, 24, 26}, {12, 13}, {14, 16, 17, 18}, {12, 13, 27}}}, + {name = "Six Tone Symmetrical", intervals = {0, 1, 4, 5, 8, 9, 11, 12}, chords = {{12, 27}, {1, 3, 8, 12, 13, 16, 17, 19, 27}, {1, 2, 12, 14}, {1, 3, 12, 16, 17, 24, 27}, {12}, {1, 3, 5, 12, 16, 17, 27}, {}, {12, 27}}}, {name = "Balinese", intervals = {0, 1, 3, 7, 8, 12}, chords = {{17}, {}, {}, {}, {1, 3, 14}, {17}}}, - {name = "Persian", intervals = {0, 1, 4, 5, 6, 8, 11, 12}, chords = {{12}, {1, 3, 8, 14, 15, 16, 17, 19}, {1, 2, 4, 12}, {16, 17, 24}, {14, 15}, {12, 13}, {14}, {12}}}, - {name = "East Indian Purvi", intervals = {0, 1, 4, 6, 7, 8, 11, 12}, chords = {{1, 3, 12}, {14, 15, 16, 17, 19, 24, 26}, {1, 2, 4, 12, 17, 18, 20}, {14, 15}, {}, {12, 13}, {14}, {1, 3, 12}}}, - {name = "Oriental", intervals = {0, 1, 4, 5, 6, 9, 10, 12}, chords = {{}, {12}, {}, {1, 3, 12, 14}, {1, 3, 8, 16, 17, 19, 24, 26}, {1, 2, 12, 17, 18}, {16, 17, 24}, {}}}, - {name = "Double Harmonic", intervals = {0, 1, 4, 5, 7, 8, 11, 12}, chords = {{1, 3, 12, 14}, {1, 3, 8, 16, 17, 19, 24, 26}, {1, 2, 12, 17, 18}, {16, 17, 24}, {}, {12}, {}, {1, 3, 12, 14}}}, - {name = "Enigmatic", intervals = {0, 1, 4, 6, 8, 10, 11, 12}, chords = {{12, 13}, {14, 15, 16, 17, 18, 19}, {1, 2, 4, 12}, {1, 8, 9, 10, 14, 15}, {12, 13}, {24, 26}, {14}, {12, 13}}}, - {name = "Overtone", intervals = {0, 2, 4, 6, 7, 9, 10, 12}, chords = {{1, 2, 4, 8, 9}, {1, 8, 9, 10, 12, 13, 14, 15}, {24, 26}, {12, 13, 24, 26}, {14, 16, 17, 18, 20}, {14, 15, 17, 18, 19}, {12}, {1, 2, 4, 8, 9}}}, - {name = "Eight Tone Spanish", intervals = {0, 1, 3, 4, 5, 6, 8, 10, 12}, chords = {{12, 13, 24, 26}, {1, 2, 3, 4, 5, 6, 7, 14, 16, 17, 18, 20}, {14, 15, 17, 18, 19, 20, 21, 22, 23}, {12}, {14, 15, 16, 17, 19}, {1, 2, 3, 4, 5, 8, 9}, {1, 2, 4, 8, 9, 10, 11, 12, 13, 14, 15}, {14, 15, 17, 19, 21, 22, 24, 26}, {12, 13, 24, 26}}}, - {name = "Prometheus", intervals = {0, 2, 4, 6, 9, 10, 12}, chords = {{}, {1, 8, 9, 12, 13}, {}, {12, 13, 24, 26}, {14, 17, 18}, {12}, {}}}, + {name = "Persian", intervals = {0, 1, 4, 5, 6, 8, 11, 12}, chords = {{12, 27}, {1, 3, 8, 14, 15, 16, 17, 19}, {1, 2, 4, 12}, {16, 17, 24}, {14, 15}, {12, 13}, {14}, {12, 27}}}, + {name = "East Indian Purvi", intervals = {0, 1, 4, 6, 7, 8, 11, 12}, chords = {{1, 3, 12, 27}, {14, 15, 16, 17, 19, 24, 26}, {1, 2, 4, 12, 17, 18, 20}, {14, 15}, {}, {12, 13, 27}, {14}, {1, 3, 12, 27}}}, + {name = "Oriental", intervals = {0, 1, 4, 5, 6, 9, 10, 12}, chords = {{}, {12, 27}, {}, {1, 3, 12, 14, 27}, {1, 3, 8, 16, 17, 19, 24, 26}, {1, 2, 12, 17, 18}, {16, 17, 24}, {}}}, + {name = "Double Harmonic", intervals = {0, 1, 4, 5, 7, 8, 11, 12}, chords = {{1, 3, 12, 14, 27}, {1, 3, 8, 16, 17, 19, 24, 26}, {1, 2, 12, 17, 18}, {16, 17, 24}, {}, {12, 27}, {}, {1, 3, 12, 14, 27}}}, + {name = "Enigmatic", intervals = {0, 1, 4, 6, 8, 10, 11, 12}, chords = {{12, 13, 27}, {14, 15, 16, 17, 18, 19}, {1, 2, 4, 12}, {1, 8, 9, 10, 14, 15}, {12, 13}, {24, 26}, {14}, {12, 13, 27}}}, + {name = "Overtone", intervals = {0, 2, 4, 6, 7, 9, 10, 12}, chords = {{1, 2, 4, 8, 9}, {1, 8, 9, 10, 12, 13, 14, 15}, {24, 26}, {12, 13, 24, 26}, {14, 16, 17, 18, 20}, {14, 15, 17, 18, 19}, {12, 27}, {1, 2, 4, 8, 9}}}, + {name = "Eight Tone Spanish", intervals = {0, 1, 3, 4, 5, 6, 8, 10, 12}, chords = {{12, 13, 24, 26}, {1, 2, 3, 4, 5, 6, 7, 14, 16, 17, 18, 20}, {14, 15, 17, 18, 19, 20, 21, 22, 23}, {12, 27}, {14, 15, 16, 17, 19}, {1, 2, 3, 4, 5, 8, 9}, {1, 2, 4, 8, 9, 10, 11, 12, 13, 14, 15}, {14, 15, 17, 19, 21, 22, 24, 26}, {12, 13, 24, 26}}}, + {name = "Prometheus", intervals = {0, 2, 4, 6, 9, 10, 12}, chords = {{}, {1, 8, 9, 12, 13}, {}, {12, 13, 24, 26}, {14, 17, 18}, {12, 27}, {}}}, {name = "Gagaku Rittsu Sen Pou", intervals = {0, 2, 5, 7, 9, 10, 12}, chords = {{14, 15}, {14, 15, 17, 19}, {1, 2, 4, 14}, {14, 15, 17, 19, 21, 22}, {}, {1, 2, 3, 4, 5}, {14, 15}}}, {name = "In Sen Pou", intervals = {0, 1, 5, 2, 8, 12}, chords = {{}, {1, 3}, {17, 18}, {24, 26}, {}, {}}}, {name = "Okinawa", intervals = {0, 4, 5, 7, 11, 12}, chords = {{1, 3, 14}, {17}, {}, {}, {}, {1, 3, 14}}}, - {name = "Chromatic", intervals = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}, chords = {{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26}, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26}, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26}, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26}, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26}, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26}, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26}, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26}, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26}, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26}, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26}, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26}, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26}}} + {name = "Chromatic", intervals = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}, chords = {{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27}, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27}, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27}, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27}, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27}, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27}, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27}, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27}, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27}, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27}, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27}, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27}, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27}}} } MusicUtil.CHORDS = { {name = "Major", alt_names = {"Maj"}, intervals = {0, 4, 7}}, @@ -77,7 +77,8 @@ MusicUtil.CHORDS = { {name = "Minor 13", alt_names = {"Min13"}, intervals = {0, 3, 7, 10, 14, 17, 21}}, {name = "Diminished", alt_names = {"Dim"}, intervals = {0, 3, 6}}, {name = "Diminished 7", alt_names = {"Dim7"}, intervals = {0, 3, 6, 9}}, - {name = "Half Diminished 7", intervals = {0, 3, 6, 10}} + {name = "Half Diminished 7", alt_names = {"Min7b5"}, intervals = {0, 3, 6, 10}}, + {name = "Augmented Major 7", alt_names = {"Maj7#5"}, intervals = {0, 4, 8, 11}} } -- Data from https://github.com/fredericcormier/WesternMusicElements @@ -86,63 +87,63 @@ MusicUtil.SCALE_CHORD_DEGREES = { name = "Major", chords = { "I", "ii", "iii", "IV", "V", "vi", "vii\u{B0}", - "I7", "ii7", "iii7", "IV7", "V7", "vi7", "vii\u{B0}7" + "IM7", "ii7", "iii7", "IVM7", "V7", "vi7", "vii\u{F8}7" } }, { name = "Natural Minor", chords = { "i", "ii\u{B0}", "III", "iv", "v", "VI", "VII", - "i7", "ii\u{B0}7", "III7", "iv7", "v7", "VI7", "VII7" + "i7", "ii\u{F8}7", "IIIM7", "iv7", "v7", "VIM7", "VII7" } }, { name = "Harmonic Minor", chords = { "i", "ii\u{B0}", "III+", "iv", "V", "VI", "vii\u{B0}", - "i7", "ii\u{B0}7", "III+7", "iv7", "V7", "VI7", "vii\u{B0}7" + "i\u{266e}7", "ii\u{F8}7", "III+M7", "iv7", "V7", "VIM7", "vii\u{B0}7" } }, { name = "Melodic Minor", chords = { "i", "ii", "III+", "IV", "V", "vi\u{B0}", "vii\u{B0}", - "i7", "ii7", "III+7", "IV7", "V7", "vi\u{B0}7", "vii\u{B0}7" + "i\u{266e}7", "ii7", "III+M7", "IV7", "V7", "vi\u{F8}7", "vii\u{F8}7" } }, { name = "Dorian", chords = { "i", "ii", "III", "IV", "v", "vi\u{B0}", "VII", - "i7", "ii7", "III7", "IV7", "v7", "vi\u{B0}7", "VII7" + "i7", "ii7", "IIIM7", "IV7", "v7", "vi\u{F8}7", "VIIM7" } }, { name = "Phrygian", chords = { "i", "II", "III", "iv", "v\u{B0}", "VI", "vii", - "i7", "II7", "III7", "iv7", "v\u{B0}7", "VI7", "vii7" + "i7", "IIM7", "III7", "iv7", "v\u{F8}7", "VIM7", "vii7" } }, { name = "Lydian", chords = { "I", "II", "iii", "iv\u{B0}", "V", "vi", "vii", - "I7", "II7", "iii7", "iv\u{B0}7", "V7", "vi7", "vii7" + "IM7", "II7", "iii7", "iv\u{F8}7", "VM7", "vi7", "vii7" } }, { name = "Mixolydian", chords = { "I", "ii", "iii\u{B0}", "IV", "v", "vi", "VII", - "I7", "ii7", "iii\u{B0}7", "IV7", "v7", "vi7", "VII7" + "I7", "ii7", "iii\u{F8}7", "IVM7", "v7", "vi7", "VIIM7" } }, { name = "Locrian", chords = { "i\u{B0}", "II", "iii", "iv", "V", "VI", "vii", - "i\u{B0}7", "II7", "iii7", "iv7", "V7", "VI7", "vii7" + "i\u{F8}7", "IIM7", "iii7", "iv7", "VM7", "VI7", "vii7" } }, } @@ -330,13 +331,15 @@ function MusicUtil.generate_chord(root_num, chord_type, inversion) return out_array end ---- Generate chord from a root note. +--- Generate a chord using Roman chord notation for a given root note and scale. -- @tparam integer root_num MIDI note number (0-127) defining the key. --- @tparam string scale_type String defining scale type (eg, "major", "dorian"), see class for full list. +-- @tparam string scale_type String defining scale type (eg, "Major", "Dorian".) -- @tparam string roman_chord_type Roman-numeral-style string defining chord type (eg, "V", "iv7" or "III+") -- including limited bass notes (e.g. "iv6-9") and lowercase-letter inversion notation (e.g. "IIb" for first inversion) --- Will only return chords defined in MusicUtil.CHORDS. -- @treturn {integer...} Array of MIDI note numbers. +-- @see See MusicUtil.SCALES for the supported scale types and MusicUtil.CHORDS for the chords that can be returned. +-- @see This function *can* return notes that are outside the scale and will not try to resolve ambiguous notation with +-- context. See chord_type_for_note or generate_chord_scale_degree if you want to constrain chords to in-scale pitches. function MusicUtil.generate_chord_roman(root_num, scale_type, roman_chord_type) if type(root_num) ~= "number" or root_num < 0 or root_num > 127 then return nil end @@ -345,21 +348,35 @@ function MusicUtil.generate_chord_roman(root_num, scale_type, roman_chord_type) local scale_data = lookup_data(MusicUtil.SCALES, scale_type) if not scale_data then return nil end - -- treat extended ascii degree symbols as asterisks - rct = string.gsub(rct, "\u{B0}", "*") - rct = string.gsub(rct, "\u{BA}", "*") + -- normalize special chars to plain ASCII using MuseScore-compatible characters + -- lua does not correctly process utf8 in set character classes, so substitute these + -- prior to the string.match + -- treat degree symbols or asterisks as 'o' + rct = string.gsub(rct, "\u{B0}", "o") + rct = string.gsub(rct, "\u{BA}", "o") + rct = string.gsub(rct, "*", "o") + -- treat upper and lowercase o-stroke as 0 + rct = string.gsub(rct, "\u{D8}", "0") + rct = string.gsub(rct, "\u{F8}", "0") + -- treat natural sign as h + rct = string.gsub(rct, "\u{266E}", "h") local degree_string, augdim_string, added_string, bass_string, inv_string = - string.match(rct, "([ivxIVX]+)([+*]?)([0-9]*)-?([0-9]?)([bcdefg]?)") + string.match(rct, "([ivxIVX]+)([+o0hM]*)([1-9]*)-?([1-9]?)([bcdefg]?)") local d = string.lower(degree_string) - local is_major = degree_string ~= d + local is_capitalized = degree_string ~= d local is_augmented = augdim_string == "+" - local is_diminished = augdim_string == "*" + local is_diminished = augdim_string == "o" local is_seventh = added_string == "7" + local is_half_diminished = augdim_string == "0" and is_seventh + local is_major_seventh = augdim_string == "M" and is_seventh + local is_augmented_major_seventh = augdim_string == "+M" and is_seventh + local is_minormajor_seventh = augdim_string == "h" and is_seventh + local chord_type = nil - if is_major then + if is_capitalized then -- uppercase, assume major in most circumstances if is_augmented then if is_seventh then chord_type = "Augmented 7" @@ -372,14 +389,22 @@ function MusicUtil.generate_chord_roman(root_num, scale_type, roman_chord_type) else chord_type = "Diminished" end + elseif is_half_diminished then + chord_type = "Half Diminished 7" + elseif is_minormajor_seventh then + chord_type = "Minor Major 7" + elseif is_augmented_major_seventh then + chord_type = "Augmented Major 7" + elseif is_major_seventh then + chord_type = "Major 7" + elseif is_seventh then + chord_type = "Dominant 7" elseif added_string == "6" then if bass_string == "9" then chord_type = "Major 69" else chord_type = "Major 6" end - elseif is_seventh then - chord_type = "Major 7" elseif added_string == "9" then chord_type = "Major 9" elseif added_string == "11" then @@ -389,7 +414,7 @@ function MusicUtil.generate_chord_roman(root_num, scale_type, roman_chord_type) else chord_type = "Major" end - else -- minor + else -- lowercase degree, assume minor in most circumstances if is_augmented then if is_seventh then chord_type = "Augmented 7" @@ -402,14 +427,22 @@ function MusicUtil.generate_chord_roman(root_num, scale_type, roman_chord_type) else chord_type = "Diminished" end + elseif is_half_diminished then + chord_type = "Half Diminished 7" + elseif is_minormajor_seventh then + chord_type = "Minor Major 7" + elseif is_augmented_major_seventh then + chord_type = "Augmented Major 7" + elseif is_major_seventh then + chord_type = "Major 7" + elseif is_seventh then + chord_type = "Minor 7" elseif added_string == "6" then if bass_string == "9" then chord_type = "Minor 69" else chord_type = "Minor 6" end - elseif is_seventh then - chord_type = "Minor 7" elseif added_string == "9" then chord_type = "Minor 9" elseif added_string == "11" then @@ -446,20 +479,52 @@ function MusicUtil.generate_chord_roman(root_num, scale_type, roman_chord_type) return MusicUtil.generate_chord(degree_note, chord_type, inversion) end ---- Generate chord from a root note. +--- Generate a chord from a scale degree, for a given root note and key, using the +--- system of tonal harmony from the European common-practice period. -- @tparam integer root_num MIDI note number (0-127) defining the key. --- @tparam string scale_type String defining scale type (eg, "major", "dorian"), see class for full list. +-- @tparam string scale_type String defining scale type. Not all scales are supported; valid values +-- are "Major" (or "Ionian"), "Natural Minor" (or "Minor" or "Aeolian"), "Harmonic Minor", +-- "Melodic Minor", "Dorian", "Phrygian", "Lydian", "Mixolydian", or "Locrian". -- @tparam integer degree Number between 1-7 selecting the degree of the chord. --- See MusicUtil.SCALE_CHORD_DEGREES for chords assigned to each degree. --- Will only return chords defined in MusicUtil.CHORDS. -- @tparam[opt] boolean seventh Return the 7th chord if set to true (optional) -- @treturn {integer...} Array of MIDI note numbers. +-- @see See MusicUtil.SCALE_CHORD_DEGREES for the specific chords assigned to each degree. function MusicUtil.generate_chord_scale_degree(root_num, scale_type, degree, seventh) local d = util.clamp(degree, 1, 7) if seventh then d = d + 7 end - local scale_data = lookup_data(MusicUtil.SCALE_CHORD_DEGREES, scale_type) - return MusicUtil.generate_chord_roman(root_num, scale_type, scale_data.chords[d]) + + -- look up record in SCALES first so we can support alt_names + local scale_data = lookup_data(MusicUtil.SCALES, scale_type) + if not scale_data then return nil end + local scale_degree_data = lookup_data(MusicUtil.SCALE_CHORD_DEGREES, scale_data.name) + if not scale_degree_data then return nil end + + return MusicUtil.generate_chord_roman(root_num, scale_type, scale_degree_data.chords[d]) +end + +-- Offline test function to confirm that SCALE_CHORD_DEGREES table generates in-scale notes only +--[[ +local function test_scale_degrees() + for i = 1, #MusicUtil.SCALE_CHORD_DEGREES do + local scale_type = MusicUtil.SCALE_CHORD_DEGREES[i].name + local scale_data = lookup_data(MusicUtil.SCALES, scale_type) + for d = 1,7 do + for pass = 1,2 do + local seventh = pass == 2 + local chord = MusicUtil.generate_chord_scale_degree(0, scale_type, d, seventh) + for n = 1, #chord do + local note = chord[n] % 12 + if tab.contains(scale_data.intervals, note) == false then + print("Note " .. note .. " not in scale for " .. scale_type .. " degree " .. d .. (seventh and " [seventh]" or "")) + tab.print(chord) + end + end + end + end + end end +test_scale_degrees() +--]] --- List chord types for a given root note and key. -- @tparam integer note_num MIDI note number (0-127) for root of chord. diff --git a/lua/lib/sequins.lua b/lua/lib/sequins.lua index 7cba2d1cc..a574bd5fa 100644 --- a/lua/lib/sequins.lua +++ b/lua/lib/sequins.lua @@ -1,164 +1,252 @@ --- sequins --- nestable tables with sequencing behaviours & control flow --- TODO i think ASL can be defined in terms of a sequins... --- --- for the norns port, @tyleretters copy-pasta'd with no changes from: --- https://github.com/monome/crow/blob/34ce1e455f01fdef65a0d37aa97163b4cd14a115/lua/sequins.lua local S = {} +-- convert a string to a table of chars +local function totable(t) + if type(t) == 'string' then + local tmp = {} + t:gsub('.', function(c) table.insert(tmp,c) end) + return tmp + end + return t +end + function S.new(t) + t = totable(t) -- convert a string to a table of chars -- wrap a table in a sequins with defaults local s = { data = t - , length = #t -- memoize table length for speed - , set_ix = 1 -- force first stage to start at elem 1 - , ix = 1 -- current val - , n = 1 -- can be a sequin + , length = #t -- memoize for efficiency + , ix = 1 + , qix = 1 -- force 1st value to 1st step + , n = 1 -- store the step val (can be a sequins) + , flw = {} -- store any applied flow modifiers + , fun = {} -- store a transformer function & any additional arguments } - s.action = {up = s} - setmetatable(s, S) - return s + return setmetatable(s, S) end local function wrap_index(s, ix) return ((ix - 1) % s.length) + 1 end --- can this be generalized to cover every/count/times etc -function S.setdata(self, t) - self.data = t +function S:is_sequins() return getmetatable(self) == S end + +function S:setdata(t) + if S.is_sequins(t) then + do -- flow modifiers + local src = t.flw + local dst = self.flw + + -- update / remove existing + for k,v in pairs(dst) do + if src[k] then -- already exists + dst[k].n = src[k].n -- update value + else dst[k] = nil end -- removed + end + + -- add any new + for k,v in pairs(src) do + if not dst[k] then -- newly added + dst[k] = {ix = v.ix, n = v.n} -- manual copy + end + end + end + + do -- update data of transformers + local src = t.fun + local dst = self.fun + + if dst[1] then -- already have a transformer + dst[1] = src[1] -- copy function + if src[2] then + for k,v in ipairs(src[2]) do + if S.is_sequins(v) and dst[2] and S.is_sequins(dst[2][k]) then + dst[2][k]:settable(v) -- recurse nested sequins + else + dst[2][k] = v -- copy piecemeal + end + end + end + elseif src[1] then -- no existing transformer, so we just pull in the new one + print'new transformer' + self.fun = t.fun + end + end + + -- finally, place data over top of input sequins + t = t.data -- handle sequins data as input table + end + + t = totable(t) -- convert a string to a table of chars + + -- data swap + for i=1,#t do + if S.is_sequins(t[i]) and S.is_sequins(self.data[i]) then + self.data[i]:settable(t[i]) -- recurse nested sequins + else + self.data[i] = t[i] -- copy table piecemeal + end + end + self.data[#t+1] = nil -- disregard any surplus data + self.length = #t self.ix = wrap_index(self, self.ix) end -function S.is_sequins(t) return getmetatable(t) == S end +function S:copy() + if type(self) == 'table' then + local cp = {} + for k,v in pairs(self) do + cp[k] = S.copy(v) + end + return setmetatable(cp, getmetatable(self)) + else return self end +end + +function S:peek() return self.data[self.ix] end -local function turtle(t, fn) - -- apply fn to all nested sequins. default to 'next' - if S.is_sequins(t) then - if fn then - return fn(t) - else return S.next(t) end +function S:bake(n) + n = n or #self -- void call creates a table of length == the parent sequins + local t = {} + for i=1,n do + t[i] = self() end - return t + return S.new(t) end +local function turtle(t, fn) + -- apply fn to all nested sequins + fn = fn or S.next -- default to S.next + if S.is_sequins(t) then return fn(t) end -- unwrap + return t -- literal value +end ------------------------------ ---- control flow execution +--- transformers -function S.next(self) - local act = self.action - if act.action then - return S.do_ctrl(act) - else return S.do_step(act) end +function S:func(fn, ...) + self.fun = {fn, {...}} -- capture function & any args (sequins) + return self end -function S.select(self, ix) - rawset(self, 'set_ix', ix) - return self +local function do_transform(s, v) + if s.fun[1] then -- implicitly handles a cleared function + if #s.fun[2] > 0 then + return s.fun[1](v, table.unpack(s.fun[2])) + else return s.fun[1](v) end + else return v end end -function S.do_step(act) - local s = act.up - -- if .set_ix is set, it will be used, rather than incrementing by s.n - local newix = wrap_index(s, s.set_ix or s.ix + turtle(s.n)) +-- support arithmetic via math operators +S._fns = { + add = function(n,b) return n+turtle(b) end, + sub = function(n,b) return n-turtle(b) end, + mul = function(n,b) return n*turtle(b) end, + div = function(n,b) return n/turtle(b) end, + mod = function(n,b) return n%turtle(b) end, +} + +-- assume 'a' is self of a sequins +S.__add = function(a,b) return S.func(a, S._fns.add, b) end +S.__sub = function(a,b) return S.func(a, S._fns.sub, b) end +S.__mul = function(a,b) return S.func(a, S._fns.mul, b) end +S.__div = function(a,b) return S.func(a, S._fns.div, b) end +S.__mod = function(a,b) return S.func(a, S._fns.mod, b) end + +------------------------------ +--- control flow execution + +local function do_step(s) + -- if .qix is set, it will be used, rather than incrementing by s.n + local newix = wrap_index(s, s.qix or s.ix + turtle(s.n)) + -- pull data from new index (unwrap if it's a sequins) local retval, exec = turtle(s.data[newix]) - if exec ~= 'again' then s.ix = newix; s.set_ix = nil end + -- handle messaging from child sequins + if exec ~= 'again' then s.ix = newix; s.qix = nil end -- FIXME add protection for list of dead sequins. for now we just recur, hoping for a live sequin in nest - if exec == 'skip' then return S.next(s) end + if retval == 'skip' or retval == 'dead' then return S.next(s) end return retval, exec end - ------------------------------- ---- control flow manipulation - -function S.do_ctrl(act) - act.ix = act.ix + 1 - if not act.cond or act.cond(act) then - retval, exec = S.next(act) - if exec then act.ix = act.ix - 1 end -- undo increment - else - retval, exec = {}, 'skip' +S.flows = { + every = function(f,n) return (f.ix % n) ~= 0 end, + times = function(f,n) return f.ix > n end, + count = function(f,n) + if f.ix < n then return 'again' + else f.ix = 0 end end - if act.rcond then - if act.rcond(act) then - if exec == 'skip' then retval, exec = S.next(act) - else exec = 'again' end - end +} + +local function do_flow(s, k) + local f = s.flw[k] -- check if flow-mod exists + if f then + f.ix = f.ix + 1 + return S.flows[k](f, turtle(f.n)) end - return retval, exec end -function S.reset(self) - self.ix = self.length - for _,v in ipairs(self.data) do turtle(v, S.reset) end - local a = self.action - while a.ix do - a.ix = 0 - turtle(a.n, S.reset) - a = a.action +function S.next(s) + if do_flow(s, 'every') then return 'skip' end + if do_flow(s, 'times') then return 'dead' end + local again = do_flow(s, 'count') + if again then + local e = s.flw.every + if e then e.ix = e.ix - 1 end -- undo every advance end + return do_transform(s,do_step(s)), again end ---- behaviour modifiers -function S.step(self, s) self.n = s; return self end +function S:step(n) self.n = n; return self end +function S.flow(s, k, n) s.flw[k] = {n=n, ix=0}; return s end +function S:every(n) return self:flow('every',n) end +function S:count(n) return self:flow('count',n) end +function S:times(n) return self:flow('times',n) end +function S:all() return self:flow('count',#self) end -function S.extend(self, t) - self.action = { up = self -- containing sequins - , action = self.action -- wrap nested actions - , ix = 0 - } - for k,v in pairs(t) do self.action[k] = v end +function S:select(ix) + rawset(self, 'qix', ix) -- qix may be nil, hence rawset return self end -function S._every(self) - return (self.ix % turtle(self.n)) == 0 -end - -function S._times(self) - return self.ix <= turtle(self.n) -end - -function S._count(self) - if self.ix < turtle(self.n) then return true - else self.ix = 0 end -- reset +function S:reset() + self:select(1) + for _,v in ipairs(self.data) do turtle(v, S.reset) end + for _,v in pairs(self.flw) do + v.ix = 0 + turtle(v.n, S.reset) + end + if self.fun[1] and #self.fun[2] > 0 then + for _,v in pairs(self.fun[2]) do + turtle(v, S.reset) + end + end end -function S.cond(self, p) return S.extend(self, {cond = p}) end -function S.condr(self, p) return S.extend(self, {cond = p, rcond = p}) end -function S.every(self, n) return S.extend(self, {cond = S._every, n = n}) end -function S.times(self, n) return S.extend(self, {cond = S._times, n = n}) end -function S.count(self, n) return S.extend(self, {rcond = S._count, n = n}) end - ---- helpers in terms of core -function S.all(self) return self:count(self.length) end -function S.once(self) return self:times(1) end -function S.peek(self) return self.data[self.ix] end - +------------------------------ --- metamethods +-- calling the sequins library will create a new sequins object (S:new) +-- calling a sequins object will produce a new value (S:next) S.__call = function(self, ...) return (self == S) and S.new(...) or S.next(self) end S.metaix = { settable = S.setdata , step = S.step - , cond = S.cond - , condr = S.condr + , flow = S.flow , every = S.every , times = S.times , count = S.count , all = S.all - , once = S.once , reset = S.reset , select = S.select , peek = S.peek + , copy = S.copy + , map = S.func + , bake = S.bake } S.__index = function(self, ix) - -- runtime calls to step() and select() should return values, not functions if type(ix) == 'number' then return self.data[ix] else return S.metaix[ix] @@ -171,7 +259,34 @@ S.__newindex = function(self, ix, v) end end +S.__tostring = function(t) + -- data + local st = {} + for i=1,t.length do + st[i] = tostring(t.data[i]) + end + local s = string.format('s[%i]{%s}', t.qix or t.ix, table.concat(st,',')) + + -- modifiers + for k,v in pairs(t.flw) do + -- TODO do we need to print current counters? + s = string.format('%s:%s[%i](%s)',s, k:sub(1,1), v.ix, tostring(v.n)) + end + + -- transformer + if #t.fun > 0 then + local fns = {'fn'} + for i=1,#t.fun[2] do + fns[i+1] = tostring(t.fun[2][i]) + end + s = string.format('%s:map(%s)',s, table.concat(fns, ',')) + end + + return s +end + +-- use memoized data length, aka #(t.data) +S.__len = function(t) return t.length end -setmetatable(S, S) -return S +return setmetatable(S, S) diff --git a/lua/lib/timeline.lua b/lua/lib/timeline.lua new file mode 100644 index 000000000..4e36210a2 --- /dev/null +++ b/lua/lib/timeline.lua @@ -0,0 +1,234 @@ +--- timeline sequencer +-- hotrod some clock & sequins structures for rapid playability + +--- globals are available on crow, otherwise require for norns +local s = sequins or require 'lib/sequins' +local clk = clock or require 'clock' + +local TL = {launch_default = 1} + +-- create a timeline object from a table +function TL.new(t) + return setmetatable({ lq = t.lq or TL.launch_default + , qd = t.qd or false + }, TL) +end + +function TL.is_timeline(t) return getmetatable(t) == TL end + +function TL.hotswap(old, new) + if type(old) == 'table' then + if TL.is_timeline(old) then + if TL.is_timeline(new) then -- tl for tl + TL.hotswap(old.t, new.t) + new:stop() -- ensure a new timeline doesn't run! + -- TODO hotswap other elements of the timeline + else -- put the new data table in existing tl + TL.hotswap(old.t, new) + end + elseif s.is_sequins(old) then old:settable(new) -- sequins + else -- regular table + for k,v in pairs(old) do + old[k] = TL.hotswap(v, new[k]) + end + end + else return new end -- return updated value + -- TODO nested timelines + return old +end + + +-- helper fns +local realize = function(q) + if s.is_sequins(q) then return q() else return q end +end +local apply = function(fn, ...) fn(...) end +local bsleep = function(v) clk.sleep(clk.get_beat_sec()*v) end +local isfn = function(f) return (type(f) == 'function') end + +-- abstract fns that handle value realizeization & fn application +local doact = function(fn) + fn = realize(fn) + if type(fn) == 'string' then return fn -- strings are keywords + elseif isfn(fn) then return fn() -- call it directly + else -- table of fn & args + local t = {} -- make a copy to avoid changing sequins + for i=1,#fn do t[i] = realize(fn[i]) end + return apply(table.unpack(t)) + end +end + +local dowait = function(d, z) -- duration, zero + local z = z+realize(d) + clk.sync(z) + return z +end + +local doalign = function(b, z) + local now = clk.get_beats() + local ct = now - z -- zero-ref'd current time + b = realize(b) + if ct < b then bsleep(b-ct) end +end + +-- like doalign, but in seconds rather than beats +local doaligns = function(b, z) + b = realize(b) -- timestamp to wait until + if b > z then + clk.sleep(b-z) -- wait until the perfect moment + return b -- return current time + else return z end -- otherwise return time now +end + +local dopred = function(p) + p = realize(p) -- realize sequins value + if isfn(p) then return p() else return p end +end + + +--- pre-methods (chain into :loop etc) +-- works in all tl modes + +-- launch quantization to lock to a clock.sync +function TL.launch(q) return TL.new{lq = q} end +function TL:_launch(q) self.lq = q; return self end + +-- stops auto-play +function TL.queue() return TL.new{qd = true} end +function TL:_queue() self.qd = true; return self end + + +--- loop +-- standalone +function TL.loop(t) return TL.new{}:loop(t) end + +-- method version +function TL:_loop(t) + self.mode = 'loop' + self.t = t -- capture table + self.fn = function() + self.i = 0 -- iteration 0 before quant finished + clk.sync(self.lq) -- launch quantization + self.z = math.floor(clk.get_beats()) -- reference beat to stop drift + repeat + self.i = self.i + 1 + for i=1,#self.t,2 do + doact(self.t[i+1]) + self.z = dowait(self.t[i], self.z) + end + until(dopred(self.p or false)) + end + if not self.qd then TL.play(self) end + return self +end + +-- shortcut for a loop that begins stopped +function TL.qloop(t) return TL.queue():loop(t) end + +-- loop predicate methods +function TL:unless(pred) + self.p = pred + return self -- method chain +end +function TL:times(n) + self._times = n + self.p = function() + n = n - 1 + return (n <= 0) -- true at minimum count + end + return self -- method chain +end + + +--- score +-- standalone +function TL.score(t) return TL.new{}:score(t) end + +-- method version +function TL:_score(t) + self.mode = 'score' + self.t = t -- capture table + self.fn = function() + local now = clk.get_beats() + local lq = self.lq + self.z = now + (lq - (now % lq)) -- calculate beat-zero + ::_R:: -- NOTE: self.z must already be updated! + for i=1,#self.t,2 do + doalign(self.t[i], self.z) + if doact(self.t[i+1]) == 'reset' then + self.z = self.z + self.t[i] -- increment beat-zero by 'reset' marker + goto _R + end + end + end + if not self.qd then TL.play(self) end + return self +end + + +---real +-- standalone +function TL.real(t) return TL.new{}:real(t) end + +-- method version +function TL:_real(t) + self.mode = 'real' + self.t = t -- capture table + self.fn = function() + clk.sync(self.lq) -- launch quantization + ::_R:: + self.z = 0 -- tracks elapsed time as 0 is arbitrary + for i=1,#self.t,2 do + self.z = doaligns(self.t[i], self.z) -- track current loop time + if doact(self.t[i+1]) == 'reset' then goto _R end + end + end + if not self.qd then TL.play(self) end + return self +end + + +--- post methods for operating on any tl object + +-- stop a playing timeline +-- NOTE: doesn't destroy the timeline. allows it to be restarted with :play +function TL:stop() + if self.coro then -- check the coroutine exists (won't if not yet :play'd) + clk.cancel(self.coro) + end +end + +-- play a queued timeline +function TL:play() + self:stop() -- stop the timeline, and restart + if self._times then -- if has :times method, refresh the self.p pred-fn + self:times(self._times) + end + self.coro = clk.run(self.fn) +end + +-- return count of loop repetitions inclusive +function TL:iter() return self.i end + +-- alias clock cleanup to stop all running timelines +TL.cleanup = clk.cleanup + +--- metamethods +TL.mms = { stop = TL.stop + , unless = TL.unless + , times = TL.times + , once = TL.once + , loop = TL._loop + , score = TL._score + , real = TL._real + , play = TL.play + , iter = TL.iter + , hotswap= TL.hotswap + , launch = TL._launch + , queue = TL._queue + } +TL.__index = TL.mms + +setmetatable(TL, TL) + +return TL diff --git a/lua/lib/util.lua b/lua/lib/util.lua index 00f2fe62f..bb42f59bc 100644 --- a/lua/lib/util.lua +++ b/lua/lib/util.lua @@ -1,4 +1,4 @@ ---- Utility module +-- Utility module -- @module lib.util util = {} @@ -243,48 +243,40 @@ function util.acronym(name) return (name:gsub("%s+", "")) end ---- wrap a number to a positive min/max range --- @tparam number n --- @tparam number min --- @tparam number max --- @treturn number cycled value +--- wrap a integer to a positive min/max range +-- @tparam integer n +-- @tparam integer min +-- @tparam integer max +-- @treturn integer cycled value function util.wrap(n, min, max) if max < min then local temp = min min = max max = temp end - local y = n - local d = max - min + 1 - while y > max do - y = y - d - end - while y < min do - y = y + d + if n >= min and n <= max then + return n end - return y + local d = max - min + 1 + local y = (n - min) % d + return y + min end ---- wrap a number to a positive min/max range but clamp the min --- @tparam number n --- @tparam number min --- @tparam number max --- @treturn number cycled value +--- wrap an integer to a positive min/max range but clamp the min +-- @tparam integer n +-- @tparam integer min +-- @tparam integer max +-- @treturn integer cycled value function util.wrap_max(n, min, max) if max < min then local temp = min min = max max = temp end - local y = n - local d = max - min + 1 - while y > max do - y = y - d - end - if y < min then - y = min + if n < min then + return min end - return y + return util.wrap(n, min, max) end return util diff --git a/maiden-repl/CMakeLists.txt b/maiden-repl/CMakeLists.txt index 1982943db..51b2a12a2 100644 --- a/maiden-repl/CMakeLists.txt +++ b/maiden-repl/CMakeLists.txt @@ -15,22 +15,22 @@ add_executable(maiden-repl ${SRC}) if(UNIX) if(APPLE) - find_library(pthread libpthread.dylib REQUIRED) find_library(readline libreadline.dylib NO_DEFAULT_PATH HINTS - /usr/local/opt/readline/lib + /opt/homebrew/opt/readline/lib + REQUIRED) + find_library(nanomsg libnanomsg.dylib + NO_DEFAULT_PATH + HINTS + /opt/homebrew/opt/nanomsg/lib REQUIRED) - find_library(nanomsg libnanomsg.dylib REQUIRED) - find_library(ncurses libcurses.dylib REQUIRED) - find_library(panel libpanel.dylib REQUIRED) target_link_libraries(maiden-repl - ${pthread} ${readline} - ${ncurses} - ${panel} + ncurses + panel ${nanomsg}) - include_directories(/usr/local/include /usr/local/opt/readline/include) + include_directories(/usr/local/include /opt/homebrew/opt/nanomsg/include /opt/homebrew/opt/readline/include) else() set(CURSES_NEED_NCURSES TRUE) set(CURSES_NEED_WIDE TRUE) # Required for unicode diff --git a/maiden-repl/README.md b/maiden-repl/README.md index b4fec0851..6380b397f 100644 --- a/maiden-repl/README.md +++ b/maiden-repl/README.md @@ -31,7 +31,7 @@ building `maiden-repl` is known to build on: * monome norns, norns shield os images * linux (debian buster) -* macOS (10.14 Mojave) +* macOS (13.1 Ventura) when building directly on a norns device `maiden-repl` should be built by default as part of the `waf` based build system for the full norns software diff --git a/matron/src/clocks/clock_link.cc b/matron/src/clocks/clock_link.cc index fa69bb21d..7ce4021d6 100644 --- a/matron/src/clocks/clock_link.cc +++ b/matron/src/clocks/clock_link.cc @@ -6,7 +6,7 @@ #include #include -#include +#include #include "clock.h" @@ -28,61 +28,59 @@ static clock_reference_t clock_link_reference; static void *clock_link_run(void *p) { (void)p; - AbletonLink *link; - AbletonLinkClock *clock; - AbletonLinkSessionState *state; + abl_link link; + abl_link_session_state state; - link = ableton_link_new(120); - clock = ableton_link_clock(link); + link = abl_link_create(120); + state = abl_link_create_session_state(); while (true) { if (pthread_mutex_trylock(&clock_link_shared_data.lock) == 0) { - state = ableton_link_capture_app_session_state(link); + abl_link_capture_app_session_state(link, state); - uint64_t micros = ableton_link_clock_micros(clock); - double link_tempo = ableton_link_session_state_tempo(state); - bool link_playing = ableton_link_session_state_is_playing(state); + uint64_t micros = abl_link_clock_micros(link); + double link_tempo = abl_link_tempo(state); + bool link_playing = abl_link_is_playing(state); if (clock_link_shared_data.transport_start) { - ableton_link_session_state_set_is_playing(state, true, 0); - ableton_link_commit_app_session_state(link, state); + abl_link_set_is_playing(state, true, 0); + abl_link_commit_app_session_state(link, state); clock_link_shared_data.transport_start = false; } if (clock_link_shared_data.transport_stop) { - ableton_link_session_state_set_is_playing(state, false, 0); - ableton_link_commit_app_session_state(link, state); + abl_link_set_is_playing(state, false, 0); + abl_link_commit_app_session_state(link, state); clock_link_shared_data.transport_stop = false; } if (clock_link_shared_data.start_stop_sync) { if (!clock_link_shared_data.playing && link_playing) { - ableton_link_session_state_request_beat_at_start_playing_time(state, 0, clock_link_shared_data.quantum); + abl_link_request_beat_at_start_playing_time(state, 0, clock_link_shared_data.quantum); clock_link_shared_data.playing = true; // this will also reschedule pending sync events to beat 0 clock_start_from_source(CLOCK_SOURCE_LINK); - ableton_link_commit_app_session_state(link, state); + abl_link_commit_app_session_state(link, state); } else if (clock_link_shared_data.playing && !link_playing) { clock_link_shared_data.playing = false; clock_stop_from_source(CLOCK_SOURCE_LINK); } } - double link_beat = ableton_link_session_state_beat_at_time(state, micros, clock_link_shared_data.quantum); + double link_beat = abl_link_beat_at_time(state, micros, clock_link_shared_data.quantum); clock_update_source_reference(&clock_link_reference, link_beat, 60.0f / link_tempo); if (clock_link_shared_data.requested_tempo > 0) { - ableton_link_session_state_set_tempo(state, clock_link_shared_data.requested_tempo, micros); - ableton_link_commit_app_session_state(link, state); + abl_link_set_tempo(state, clock_link_shared_data.requested_tempo, micros); + abl_link_commit_app_session_state(link, state); clock_link_shared_data.requested_tempo = 0; } - ableton_link_enable(link, clock_link_shared_data.enabled); - ableton_link_enable_start_stop_sync(link, clock_link_shared_data.start_stop_sync); + abl_link_enable(link, clock_link_shared_data.enabled); + abl_link_enable_start_stop_sync(link, clock_link_shared_data.start_stop_sync); - ableton_link_session_state_destroy(state); pthread_mutex_unlock(&clock_link_shared_data.lock); } diff --git a/matron/src/device/device_hid.cc b/matron/src/device/device_hid.cc index 96b121f7c..0a0ac9ad4 100644 --- a/matron/src/device/device_hid.cc +++ b/matron/src/device/device_hid.cc @@ -1,6 +1,7 @@ #include #include +#include #include #include #include @@ -55,11 +56,39 @@ static void add_codes(struct dev_hid *d) { } } +void get_guid(struct libevdev * dev, guint16 * guid) { + guid[0] = GINT16_TO_LE(libevdev_get_id_bustype(dev)); + guid[1] = 0; + guid[2] = GINT16_TO_LE(libevdev_get_id_vendor(dev)); + guid[3] = 0; + guid[4] = GINT16_TO_LE(libevdev_get_id_product(dev)); + guid[5] = 0; + guid[6] = GINT16_TO_LE(libevdev_get_id_version(dev)); + guid[7] = 0; +} + +void guid_to_string(guint16 * guid, char * guidstr) { + static const char k_rgchHexToASCII[] = "0123456789abcdef"; + int i; + for (i = 0; i < 8; i++) { + unsigned char c = guid[i]; + + *guidstr++ = k_rgchHexToASCII[c >> 4]; + *guidstr++ = k_rgchHexToASCII[c & 0x0F]; + + c = guid[i] >> 8; + *guidstr++ = k_rgchHexToASCII[c >> 4]; + *guidstr++ = k_rgchHexToASCII[c & 0x0F]; + } + *guidstr = '\0'; +} + int dev_hid_init(void *self) { struct dev_hid *d = (struct dev_hid *)self; struct dev_common *base = (struct dev_common *)self; struct libevdev *dev = NULL; int ret = 1; + guint16 raw_guid[16]; int fd = open(d->base.path, O_RDONLY); if (fd < 0) { @@ -80,6 +109,8 @@ int dev_hid_init(void *self) { d->vid = libevdev_get_id_vendor(dev); d->pid = libevdev_get_id_product(dev); + get_guid(dev, raw_guid); + guid_to_string(raw_guid, d->guid); base->start = &dev_hid_start; base->deinit = &dev_hid_deinit; diff --git a/matron/src/device/device_hid.h b/matron/src/device/device_hid.h index b9c138895..ad200b1a4 100644 --- a/matron/src/device/device_hid.h +++ b/matron/src/device/device_hid.h @@ -6,6 +6,8 @@ #include "device_common.h" #include +#define DEV_GUID_LEN 33 + typedef uint8_t dev_vid_t; typedef uint8_t dev_pid_t; typedef uint16_t dev_code_t; @@ -16,6 +18,7 @@ struct dev_hid { // identifiers dev_vid_t vid; dev_pid_t pid; + char guid[DEV_GUID_LEN]; // count of supported event types int num_types; // array of supported event types diff --git a/matron/src/device/device_midi.cc b/matron/src/device/device_midi.cc index e5e2657fa..ed63088f4 100644 --- a/matron/src/device/device_midi.cc +++ b/matron/src/device/device_midi.cc @@ -253,7 +253,7 @@ static inline ssize_t dev_midi_consume_buffer(midi_input_state_t *state, ssize_t clock_midi_handle_message(byte); } - if (is_status_byte(byte)) { + if (is_status_byte(byte) && (byte != 0xf7)) { midi_input_msg_start(state, byte); } else { midi_input_msg_acc(state, byte); diff --git a/matron/src/event_types.h b/matron/src/event_types.h index 4c1a85e45..5473514f9 100644 --- a/matron/src/event_types.h +++ b/matron/src/event_types.h @@ -238,7 +238,7 @@ struct event_power { struct event_stat { struct event_common common; - uint16_t disk; + uint32_t disk; uint8_t temp; uint8_t cpu; uint8_t cpu1; diff --git a/matron/src/hardware/screen.cc b/matron/src/hardware/screen.cc index e24bf5ae7..3f6d6422c 100644 --- a/matron/src/hardware/screen.cc +++ b/matron/src/hardware/screen.cc @@ -395,8 +395,44 @@ double *screen_text_extents(const char *s) { } extern void screen_export_png(const char *s) { - CHECK_CR - cairo_surface_write_to_png(surface, s); + CHECK_CR + cairo_surface_write_to_png(surface, s); +} + +extern void screen_export_screenshot(const char *s) { + CHECK_CR + static cairo_surface_t *png; + static cairo_t *temp; // for bg fill + // width = 640 (128*4 pixels with 64 pixel black border) + // hieght = 384 (64*4 pixels plus border) + png = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 640, 384); + temp = cairo_create(png); + // fill + cairo_set_source_rgb(temp, 0, 0, 0); + cairo_rectangle(temp, 0, 0, 640, 384); + cairo_fill(temp); + // copy big pixles + uint32_t *src = (uint32_t *)cairo_image_surface_get_data(surface); + uint32_t *dst = (uint32_t *)cairo_image_surface_get_data(png); + if (!src || !dst) return; + dst += 64 + 640*64; + for(int y=0;y<64;y++) { + for(int x=0;x<128;x++) { + // FIXME: needs some sort of gamma correction? + uint32_t p = *src++ | 0xFF000000; // FF for alpha + for(int xx=0;xx<4;xx++) { + *(dst+1920) = p; + *(dst+1280) = p; + *(dst+640) = p; + *dst++ = p; + } + } + dst += 640*4 - 128*4; + } + // cleanup + cairo_destroy(temp); + cairo_surface_write_to_png(png, s); + cairo_surface_destroy((cairo_surface_t *)png); } void screen_display_png(const char *filename, double x, double y) { diff --git a/matron/src/hardware/screen.h b/matron/src/hardware/screen.h index cdc91fed6..623c37bc6 100644 --- a/matron/src/hardware/screen.h +++ b/matron/src/hardware/screen.h @@ -32,6 +32,7 @@ extern void screen_clear(void); extern void screen_close_path(void); extern double *screen_text_extents(const char *s); extern void screen_export_png(const char *s); +extern void screen_export_screenshot(const char *s); extern void screen_display_png(const char *filename, double x, double y); extern char *screen_peek(int x, int y, int *w, int *h); extern void screen_poke(int x, int y, int w, int h, unsigned char *buf); diff --git a/matron/src/weaver.cc b/matron/src/weaver.cc index a559624cd..76dac8f36 100644 --- a/matron/src/weaver.cc +++ b/matron/src/weaver.cc @@ -122,6 +122,7 @@ static int _screen_clear(lua_State *l); static int _screen_close(lua_State *l); static int _screen_text_extents(lua_State *l); static int _screen_export_png(lua_State *l); +static int _screen_export_screenshot(lua_State *l); static int _screen_display_png(lua_State *l); static int _screen_peek(lua_State *l); static int _screen_poke(lua_State *l); @@ -496,6 +497,7 @@ void w_init(void) { lua_register_norns("screen_close", &_screen_close); lua_register_norns("screen_text_extents", &_screen_text_extents); lua_register_norns("screen_export_png", &_screen_export_png); + lua_register_norns("screen_export_screenshot", &_screen_export_screenshot); lua_register_norns("screen_display_png", &_screen_display_png); lua_register_norns("screen_peek", &_screen_peek); lua_register_norns("screen_poke", &_screen_poke); @@ -1015,6 +1017,19 @@ int _screen_export_png(lua_State *l) { return 0; } +/*** + * screen: export_screenshot + * @function s_export_screenshot + * @tparam string filename + */ +int _screen_export_screenshot(lua_State *l) { + lua_check_num_args(1); + const char *s = luaL_checkstring(l, 1); + screen_export_screenshot(s); + lua_settop(l, 0); + return 0; +} + /*** * screen: display_png * @function s_display_png @@ -1134,6 +1149,27 @@ int _screen_set_operator(lua_State *l) { return 0; } +// clang-format off +static luaL_Reg _image_methods[] = { + {"__gc", _image_free}, + {"__tostring", _image_tostring}, + {"__eq", _image_equals}, + {"_context_focus", _image_context_focus}, + {"_context_defocus", _image_context_defocus}, + {"extents", _image_extents}, + {"name", _image_name}, + {NULL, NULL} +}; + +static luaL_Reg _image_functions[] = { + {"screen_load_png", _screen_load_png}, + {"screen_create_image", _screen_create_image}, + {"screen_display_image", _screen_display_image}, + {"screen_display_image_region", _screen_display_image_region}, + {NULL, NULL} +}; +// clang-format on + int _image_new(lua_State *l, screen_surface_t *surface, const char *name) { _image_t *ud = (_image_t *)lua_newuserdata(l, sizeof(_image_t)); ud->surface = surface; @@ -2129,7 +2165,8 @@ void w_handle_hid_add(void *p) { } lua_pushlightuserdata(lvm, dev); - l_report(lvm, l_docall(lvm, 5, 0)); + lua_pushstring(lvm, dev->guid); + l_report(lvm, l_docall(lvm, 6, 0)); } void w_handle_hid_remove(int id) { diff --git a/readme-setup.md b/readme-setup.md index 53d7c71fd..eda311fb0 100644 --- a/readme-setup.md +++ b/readme-setup.md @@ -27,7 +27,7 @@ for other platforms (x86, amd64), these packages can again use the standard repo sudo apt install libnanomsg-dev supercollider-language supercollider-server supercollider-dev ``` -and `libmonome` must be build and installed from source: +and `libmonome` must be built and installed from source: - clone, build, and install: ``` diff --git a/readme.md b/readme.md index f37906e08..61e494506 100644 --- a/readme.md +++ b/readme.md @@ -11,10 +11,12 @@ norns is many sound instruments. it connects to grids, MIDI, and other objects. git clone https://github.com/monome/norns.git cd norns git submodule update --init --recursive -./waf configure -./waf +./waf configure --release +./waf build --release ``` +(NB: the `--release` flag creates builds specifically for armv8/cortex-a53 instruction set, meaning optimized for rpi3 and compatible with rpi4. It also enables aggressive compiler optimizations. Omit flag if you need debug symbols or to build for a different architecture. It does need to supplied to both configuration and build steps.) + ## documentation - [user docs](https://monome.org/docs/norns) - [API docs](https://monome.org/docs/norns/api) diff --git a/releases.txt b/releases.txt index a1569a87e..27ced7a9a 100644 --- a/releases.txt +++ b/releases.txt @@ -1,12 +1,12 @@ releases = { stable = { - version="220306", - url="https://github.com/monome/norns/releases/download/v2.7.0/norns220306.tgz", - sha="https://github.com/monome/norns/releases/download/v2.7.0/norns220306.sha256" + version="230614", + url="https://github.com/monome/norns/releases/download/v2.7.7/norns230614.tgz", + sha="https://github.com/monome/norns/releases/download/v2.7.7/norns230614.sha256" }, beta = { - version="220321", - url="https://github.com/monome/norns/releases/download/v2.7.1/norns220321.tgz", - sha="https://github.com/monome/norns/releases/download/v2.7.1/norns220321.sha256" + version="230614", + url="https://github.com/monome/norns/releases/download/v2.7.7/norns230614.tgz", + sha="https://github.com/monome/norns/releases/download/v2.7.7/norns230614.sha256" } } diff --git a/resources/04B_03__.TTF b/resources/04B_03__.TTF index 5ad788e322f0b947820f49d75acc37e3b266d871..1deb3b8ac24c07ecf620010edc918cc9aaf5d676 100644 GIT binary patch delta 734 zcmZ3ogz?Q%#(D-u1_lOxh6V;^h5|RY5Z{t#%TyQ`f-V3>W!zm{-5AsvW&-&efP6Xs zV11*es?sM642%^(epqsDV!^^Cd{Y@1SY&`=pOVW;6e17*Jj%epmI9R5ODjmvZ8kpS z59A8~`6B5#m1%!tx+XI)u8tu-;~70UBlks}QEEZi0a>70HlQaN>=+o8fD|wU?$=nqjOVxc${^4D z0w}_;Ygxr#5dGl#jpWG+jOOfB=Mni;>j%jG{8SU`~m0I^2ZT>t<8 delta 524 zcmaE}lyS)t#(D-u1_lOxh6V;^h5|RY5Z~OVrKt=IK^#C)8Fv>~HwJZv=?n~kFMxbG z|6qNirmE5>3=E7FKz>+qZeqdw{XDG<3@jo*u}{fmB?^&;e;#FEV2c6D>!lT>=QbN3 z@(1!k=82@|RHj|=?@ni6VBf&N;OdZ(npmImZFX`MQ1TBDn`Hn+SZ^~k0Sz+&@>Mc& zODa5B8LWW(3k(cQPC5C>iR)z#L;(%#0g9>QCRP+MaxroO*%3hX3VDgSsTWubiy0Vv zJ^{Z{U+fLrnmjFTU-XiomX!a13Xb+aHtAuxOy(m