6.14 Script Showcase
Example 1: General API
--@assign id=1 abbr="C/F" name="Coarse" desc="Coarse Tune" l=0 h=127 d=1 g=1 swap=
--@assign id=2 abbr="C/F" name="Fine" desc="Fine Tune" l=0 h=127 d=2 g=1 accel
--@assign id=3 abbr="Wave" name="Waveform" desc="Waveform Select" l=0 h=3 manual=true
--@assign id=4 abbr="Driv" name="Drive" desc="Drive Amount"
--@assign id=5 abbr="FX" name="FX Swap" desc="FX Turn" l=0 h=127 d=1 g=2
--@assign id=6 abbr="FX" name="FX Push" desc="FX Push Toggle" p=true g=2
local out = 0
local waves = {"sine", "sqre", "tria", "saw"}
local wave_step = 0
local fx_on = false
local my_val = 0
function page.onInit()
page.setTitle("Synth Edit")
slots.update(2, waves[wave_step + 1])
end
function controller.onEncoderTurn(enc)
if enc.id == 3
then local prev = wave_step
wave_step = wave_step + enc.increment
if wave_step < 0 then wave_step = 0 end
if wave_step > 3 then wave_step = 3 end
if wave_step ~= prev
then local iv = wave_step * 16383 // 3
controller.set(enc.id, "v", iv)
slots.update(enc.index, waves[wave_step + 1])
leds.update(enc.id, iv)
midi.sendSysex(out, {0xF0, 0x7D, 0x01, 2, wave_step, 0xF7})
end
elseif enc.id == 4 then
my_val = my_val + enc.increment
if my_val < 0 then my_val = 0 end
if my_val > 127 then my_val = 127 end
leds.update(enc.id, my_val * 16383 // 127)
slots.update(enc.index, string.format("%3d", my_val))
midi.sendSysex(out, {0xF0, 0x7D, 0x09, my_val, 0xF7})
elseif enc.id == 1 then
midi.sendSysex(out, {0xF0, 0x7D, 0x10, enc.scaled, 0xF7})
elseif enc.id == 2 then
midi.sendSysex(out, {0xF0, 0x7D, 0x11, enc.scaled, 0xF7})
elseif enc.id == 5 then
midi.sendSysex(out, {0xF0, 0x7D, 0x20, enc.scaled, 0xF7})
end
end
function controller.onEncoderPress(enc)
if enc.id == 6 then
fx_on = not fx_on
local val = fx_on and 127 or 0
midi.sendSysex(out, {0xF0, 0x7D, 0x01, 5, val, 0xF7})
slots.update(enc.index, fx_on and "FX.Y" or "FX.N")
end
end
function controller.onSysex(bytes)
if bytes[2] ~= 0x7D then return end
local pg = controller.getPage()
if bytes[3] == 0x11 then
local idx = bytes[4] + 1
local value = bytes[5]
local internal = (value * 16383 + 63) // 127
controller.setByIndex(pg, idx, "v", internal)
elseif bytes[3] == 0x12 then
for i = 1, 16 do
if bytes[3 + i] then
local internal = (bytes[3 + i] * 16383 + 63) // 127
controller.setByIndex(pg, i, "v", internal)
end
end
end
end
function page.onPageChange(prev, curr)
for i = 1, 16 do
leds.reset(i)
slots.reset(i)
end
end
Example 2:
This example demonstrates manual value ownership, two turn destinations sharing one Script ID, writing values into another control's destinations, destination swapping, batched LED-ring overrides, dynamic slot labels, and periodic updates.
Place the two SINE assignments on encoder 1, the two TGT assignments on encoder 2, Wr1 on encoder 3, Wr2 on encoder 4, and Rate on encoder 5.
-- Manual mode test script.
--
-- Expected behavior:
--
-- Encoder 1 drives a script-owned sine-pattern LED ring.
-- - With encoder 2 destination 1 active, encoder 3 changes encoder 2's ring;
-- encoder 4 only changes the hidden destination 2 value.
-- - Swap encoder 2. Its ring jumps to destination 2's stored value. Encoder 4
-- now changes the visible ring, while encoder 3 only changes hidden dest 1.
-- - Encoder 2's target destinations stay managed so Swap behaves normally.
-- The sine control and both writer controls are manual; Lua applies their
-- increments and writes their internal values explicitly.
-- - Encoder 5 continuously sweeps up and down from system.update(). Turning
-- it changes the sweep frequency, proving that periodic Lua polling and a
-- manual Script control can work together.
--@assign id=201 abbr="SINE" name="Manual sine action A" l=0 h=127 d=1 g=1 swap=true manual=true
--@assign id=201 abbr="SINE" name="Manual sine action B" l=0 h=127 d=2 g=1 manual=true
--@assign id=202 abbr="TGT" name="Target destination 1" l=0 h=127 d=1 g=2 swap=true
--@assign id=203 abbr="TGT" name="Target destination 2" l=0 h=127 d=2 g=2
--@assign id=204 abbr="Wr1" name="Manual writer destination 1" l=0 h=127 manual=true
--@assign id=205 abbr="Wr2" name="Manual writer destination 2" l=0 h=127 manual=true
--@assign id=209 abbr="Rate" name="Polling LED frequency" l=0 h=127 manual=true
local SINE_ID = 201
local TARGET_DEST_1_ID = 202
local TARGET_DEST_2_ID = 203
local WRITE_DEST_1_ID = 204
local WRITE_DEST_2_ID = 205
local POLL_RATE_ID = 209
local step = 512
local sine_value = 4096
local dest_1_value = 2048
local dest_2_value = 14336
local writer_1_value = dest_1_value
local writer_2_value = dest_2_value
local tau = 6.283185307179586
local led_phase = 0
local led_phase_steps = 32
local POLL_LED_INDEX = 5
local poll_page = 1
local poll_led_value = 0
local poll_led_direction = 1
local poll_rate_value = 4096
local poll_led_step = 0
local function clamp14(value)
if value < 0 then return 0 end
if value > 16383 then return 16383 end
return value
end
local function advance(value, increment)
return clamp14(value + increment * step)
end
local function to7Bit(value)
return math.floor(value * 127 / 16383 + 0.5)
end
local function updateWriterLabel(index, writer, value)
slots.update(index, string.format("%d%03d", writer, to7Bit(value)))
end
local function updateSineLed()
local angle = (led_phase % led_phase_steps) * tau / led_phase_steps
local normalized = math.sin(angle) * 0.45 + 0.5
local ring_value = math.floor(normalized * 16383 + 0.5)
leds.update(SINE_ID, ring_value, 10)
end
local function updatePollRate()
-- 64..2048 per 20 ms update: roughly 0.1 Hz to 3.1 Hz.
poll_led_step = 64 + math.floor(poll_rate_value * 4096 / 16383 + 0.5)
end
function page.onInit()
page.setTitle("All manual")
poll_page = controller.getPage()
system.setUpdateRate(20)
controller.set(SINE_ID, {n="SINE", manual=true, v=sine_value})
controller.set(TARGET_DEST_1_ID, {v=dest_1_value})
controller.set(TARGET_DEST_2_ID, {v=dest_2_value})
controller.set(WRITE_DEST_1_ID, {manual=true, v=writer_1_value})
controller.set(WRITE_DEST_2_ID, {manual=true, v=writer_2_value})
controller.set(POLL_RATE_ID, {manual=true, v=poll_rate_value})
updatePollRate()
updateSineLed()
leds.update({
{WRITE_DEST_1_ID, writer_1_value, 4},
{WRITE_DEST_2_ID, writer_2_value, 8},
{POLL_RATE_ID, poll_led_value, (poll_led_step / 16383) * 100},
})
updateWriterLabel(3, 1, writer_1_value)
updateWriterLabel(4, 2, writer_2_value)
end
function page.onPageChange()
for i = 1, 16 do
slots.reset(i)
leds.reset(i)
end
end
function system.update()
if controller.getPage() ~= poll_page then return end
poll_led_value = poll_led_value + poll_led_direction * poll_led_step
if poll_led_value >= 16383 then
poll_led_value = 16383
poll_led_direction = -1
elseif poll_led_value <= 0 then
poll_led_value = 0
poll_led_direction = 1
end
leds.update(POLL_RATE_ID, poll_led_value, (poll_led_value / 16383) * 100)
end
Example 3: Realistic Sysex Editor
function controller.onEncoderTurn(enc)
if enc.id == SINE_ID then
sine_value = advance(sine_value, enc.increment)
led_phase = (led_phase + enc.increment) % led_phase_steps
controller.set(SINE_ID, "v", sine_value)
updateSineLed()
page.setTitle("Sine " .. sine_value)
elseif enc.id == TARGET_DEST_1_ID then
-- Managed target: firmware has already applied the increment.
dest_1_value = enc.value
page.setTitle("T1 " .. dest_1_value)
elseif enc.id == TARGET_DEST_2_ID then
-- Managed target: firmware has already applied the increment.
dest_2_value = enc.value
page.setTitle("T2 " .. dest_2_value)
elseif enc.id == WRITE_DEST_1_ID then
writer_1_value = advance(writer_1_value, enc.increment)
dest_1_value = writer_1_value
controller.set(WRITE_DEST_1_ID, "v", writer_1_value)
controller.set(TARGET_DEST_1_ID, "v", dest_1_value)
leds.update(WRITE_DEST_1_ID, writer_1_value, 4)
updateWriterLabel(enc.index, 1, writer_1_value)
page.setTitle("D1 " .. dest_1_value)
elseif enc.id == WRITE_DEST_2_ID then
writer_2_value = advance(writer_2_value, enc.increment)
dest_2_value = writer_2_value
controller.set(WRITE_DEST_2_ID, "v", writer_2_value)
controller.set(TARGET_DEST_2_ID, "v", dest_2_value)
leds.update(WRITE_DEST_2_ID, writer_2_value, 8)
updateWriterLabel(enc.index, 2, writer_2_value)
page.setTitle("D2 " .. dest_2_value)
elseif enc.id == POLL_RATE_ID then
poll_rate_value = advance(poll_rate_value, enc.increment)
controller.set(POLL_RATE_ID, "v", poll_rate_value)
updatePollRate()
page.setTitle("Rate " .. to7Bit(poll_rate_value))
end
end
Example 3: Realistic Sysex Editor
-- Yamaha TX81Z complete editor for OXI E16
-- Per-operator params (id 1-64), voice common (65-86), op enable pushes (87-90)
-- OP4 per-operator (id 1-16)
--@assign id=1 abbr="CRS" name="OP4 Coarse" l=0 h=63
--@assign id=2 abbr="Fine" name="OP4 Fine Freq" l=0 h=15
--@assign id=3 abbr="FRng" name="OP4 Freq Range" l=0 h=7
--@assign id=4 abbr="DET" name="OP4 Detune" l=0 h=6
--@assign id=5 abbr="AR" name="OP4 Attack" l=0 h=31
--@assign id=6 abbr="D1R" name="OP4 Decay 1" l=0 h=31
--@assign id=7 abbr="D2R" name="OP4 Decay 2" l=0 h=31
--@assign id=8 abbr="RR" name="OP4 Release" l=1 h=15
--@assign id=9 abbr="D1L" name="OP4 Decay Lvl" l=0 h=15
--@assign id=10 abbr="LS" name="OP4 Level Scale" l=0 h=99
--@assign id=11 abbr="RS" name="OP4 Rate Scale" l=0 h=3
--@assign id=12 abbr="EBS" name="OP4 EG Bias Sens" l=0 h=7
--@assign id=13 abbr="KVS" name="OP4 Key Vel Sens" l=0 h=7
--@assign id=14 abbr="OUT" name="OP4 Output Level" l=0 h=99
--@assign id=15 abbr="SHFT" name="OP4 EG Shift" l=0 h=3
--@assign id=16 abbr="OSW" name="OP4 Osc Wave" l=0 h=7
-- OP3 per-operator (id 17-32)
--@assign id=17 abbr="CRS" name="OP3 Coarse" l=0 h=63
--@assign id=18 abbr="Fine" name="OP3 Fine Freq" l=0 h=15
--@assign id=19 abbr="FRng" name="OP3 Freq Range" l=0 h=7
--@assign id=20 abbr="DET" name="OP3 Detune" l=0 h=6
--@assign id=21 abbr="AR" name="OP3 Attack" l=0 h=31
--@assign id=22 abbr="D1R" name="OP3 Decay 1" l=0 h=31
--@assign id=23 abbr="D2R" name="OP3 Decay 2" l=0 h=31
--@assign id=24 abbr="RR" name="OP3 Release" l=1 h=15
--@assign id=25 abbr="D1L" name="OP3 Decay Lvl" l=0 h=15
--@assign id=26 abbr="LS" name="OP3 Level Scale" l=0 h=99
--@assign id=27 abbr="RS" name="OP3 Rate Scale" l=0 h=3
--@assign id=28 abbr="EBS" name="OP3 EG Bias Sens" l=0 h=7
--@assign id=29 abbr="KVS" name="OP3 Key Vel Sens" l=0 h=7
--@assign id=30 abbr="OUT" name="OP3 Output Level" l=0 h=99
--@assign id=31 abbr="SHFT" name="OP3 EG Shift" l=0 h=3
--@assign id=32 abbr="OSW" name="OP3 Osc Wave" l=0 h=7
-- OP2 per-operator (id 33-48)
--@assign id=33 abbr="CRS" name="OP2 Coarse" l=0 h=63
--@assign id=34 abbr="Fine" name="OP2 Fine Freq" l=0 h=15
--@assign id=35 abbr="FRng" name="OP2 Freq Range" l=0 h=7
--@assign id=36 abbr="DET" name="OP2 Detune" l=0 h=6
--@assign id=37 abbr="AR" name="OP2 Attack" l=0 h=31
--@assign id=38 abbr="D1R" name="OP2 Decay 1" l=0 h=31
--@assign id=39 abbr="D2R" name="OP2 Decay 2" l=0 h=31
--@assign id=40 abbr="RR" name="OP2 Release" l=1 h=15
--@assign id=41 abbr="D1L" name="OP2 Decay Lvl" l=0 h=15
--@assign id=42 abbr="LS" name="OP2 Level Scale" l=0 h=99
--@assign id=43 abbr="RS" name="OP2 Rate Scale" l=0 h=3
--@assign id=44 abbr="EBS" name="OP2 EG Bias Sens" l=0 h=7
--@assign id=45 abbr="KVS" name="OP2 Key Vel Sens" l=0 h=7
--@assign id=46 abbr="OUT" name="OP2 Output Level" l=0 h=99
--@assign id=47 abbr="SHFT" name="OP2 EG Shift" l=0 h=3
--@assign id=48 abbr="OSW" name="OP2 Osc Wave" l=0 h=7
-- OP1 per-operator (id 49-64)
--@assign id=49 abbr="CRS" name="OP1 Coarse" l=0 h=63
--@assign id=50 abbr="Fine" name="OP1 Fine Freq" l=0 h=15
--@assign id=51 abbr="FRng" name="OP1 Freq Range" l=0 h=7
--@assign id=52 abbr="DET" name="OP1 Detune" l=0 h=6
--@assign id=53 abbr="AR" name="OP1 Attack" l=0 h=31
--@assign id=54 abbr="D1R" name="OP1 Decay 1" l=0 h=31
--@assign id=55 abbr="D2R" name="OP1 Decay 2" l=0 h=31
--@assign id=56 abbr="RR" name="OP1 Release" l=1 h=15
--@assign id=57 abbr="D1L" name="OP1 Decay Lvl" l=0 h=15
--@assign id=58 abbr="LS" name="OP1 Level Scale" l=0 h=99
--@assign id=59 abbr="RS" name="OP1 Rate Scale" l=0 h=3
--@assign id=60 abbr="EBS" name="OP1 EG Bias Sens" l=0 h=7
--@assign id=61 abbr="KVS" name="OP1 Key Vel Sens" l=0 h=7
--@assign id=62 abbr="OUT" name="OP1 Output Level" l=0 h=99
--@assign id=63 abbr="SHFT" name="OP1 EG Shift" l=0 h=3
--@assign id=64 abbr="OSW" name="OP1 Osc Wave" l=0 h=7
-- Voice common (id 65-81)
--@assign id=65 abbr="ALG" name="Algorithm" l=0 h=7
--@assign id=66 abbr="FB" name="Feedback" l=0 h=7
--@assign id=67 abbr="LSPD" name="LFO Speed" l=0 h=99
--@assign id=68 abbr="LDLY" name="LFO Delay" l=0 h=99
--@assign id=69 abbr="PMD" name="PM Depth" l=0 h=99
--@assign id=70 abbr="AMD" name="AM Depth" l=0 h=99
--@assign id=71 abbr="LSYN" name="LFO Key Sync" l=0 h=1
--@assign id=72 abbr="LW" name="LFO Wave" l=0 h=3
--@assign id=73 abbr="PMS" name="LFO PM Sens" l=0 h=7
--@assign id=74 abbr="AMS" name="LFO AM Sens" l=0 h=3
--@assign id=75 abbr="MID" name="Middle C" l=0 h=48
--@assign id=76 abbr="PLAY" name="Play Mode" l=0 h=1
--@assign id=77 abbr="PB" name="Pitch Bend Range" l=0 h=12
--@assign id=78 abbr="PMOD" name="Portamento Mode" l=0 h=1
--@assign id=79 abbr="PTIM" name="Portamento Time" l=0 h=99
--@assign id=80 abbr="MWP" name="MW Pitch Range" l=0 h=99
--@assign id=81 abbr="MWA" name="MW Amp Range" l=0 h=99
-- Per-operator ratio/fixed toggle (id 82-85)
--@assign id=82 abbr="R/F" name="OP4 Ratio/Fixed" l=0 h=1
--@assign id=83 abbr="R/F" name="OP3 Ratio/Fixed" l=0 h=1
--@assign id=84 abbr="R/F" name="OP2 Ratio/Fixed" l=0 h=1
--@assign id=85 abbr="R/F" name="OP1 Ratio/Fixed" l=0 h=1
-- Reverb (id 86)
--@assign id=86 abbr="REV" name="Reverb Rate" l=0 h=7
-- Operator enable push toggles (id 87-90)
--@assign id=87 abbr="OP1" name="OP1 Enable" p=true
--@assign id=88 abbr="OP2" name="OP2 Enable" p=true
--@assign id=89 abbr="OP3" name="OP3 Enable" p=true
--@assign id=90 abbr="OP4" name="OP4 Enable" p=true
local out = 0
local channel = 0
local vced = {0xF0, 0x43, 0x10 + channel, 0x12, 0, 0, 0xF7}
local aced = {0xF0, 0x43, 0x10 + channel, 0x13, 0, 0, 0xF7}
-- 0 = VCED, 1 = ACED per script_id (1-86)
local param_type = {
-- OP4 (1-16)
0,1,1,0, 0,0,0,0,0,0,0,0,0,0,1,1,
-- OP3 (17-32)
0,1,1,0, 0,0,0,0,0,0,0,0,0,0,1,1,
-- OP2 (33-48)
0,1,1,0, 0,0,0,0,0,0,0,0,0,0,1,1,
-- OP1 (49-64)
0,1,1,0, 0,0,0,0,0,0,0,0,0,0,1,1,
-- Voice common (65-81): all VCED
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-- Ratio/Fixed (82-85): all ACED
1,1,1,1,
-- Reverb (86): ACED
1,
}
-- Sysex address byte per script_id (1-86)
local param_num = {
-- OP4 (1-16)
11, 2, 1,12, 0, 1, 2, 3, 4, 5, 6, 7, 9,10, 4, 3,
-- OP3 (17-32)
24, 7, 6,25, 13,14,15,16,17,18,19,20,22,23, 9, 8,
-- OP2 (33-48)
37,12,11,38, 26,27,28,29,30,31,32,33,35,36,14,13,
-- OP1 (49-64)
50,17,16,51, 39,40,41,42,43,44,45,46,48,49,19,18,
-- Voice common (65-81): VCED addresses
0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,
0x3C,0x3D,0x3E,0x3F,0x40,0x41,0x42,0x47,0x48,
-- Ratio/Fixed (82-85): ACED addresses Op4,Op3,Op2,Op1
0x00,0x05,0x0A,0x0F,
-- Reverb (86): ACED address
0x14,
}
-- Operator enable state: Op1, Op2, Op3, Op4 (1=on, 0=off)
local op_en = {1, 1, 1, 1}
local function sendOpEnable()
local packed = (op_en[1] * 8) + (op_en[2] * 4) + (op_en[3] * 2) + op_en[4]
vced[5] = 0x5D
vced[6] = packed
midi.sendSysex(out, vced)
end
function page.onInit()
page.setTitle("TX81Z")
end
function controller.onEncoderTurn(enc)
if enc.id >= 1 and enc.id <= 86 then
local msg = param_type[enc.id] == 1 and aced or vced
msg[5] = param_num[enc.id]
msg[6] = enc.scaled
midi.sendSysex(out, msg)
end
end
function controller.onEncoderPress(enc)
-- Op enable toggles (id 87=OP1, 88=OP2, 89=OP3, 90=OP4)
if enc.id >= 87 and enc.id <= 90 then
local op = enc.id - 86 -- 1-4
op_en[op] = 1 - op_en[op]
sendOpEnable()
slots.update(enc.index, op_en[op] == 1 and "ON" or "OFF")
end
end
This page covers OS 1.3.0 (manual 1.3.0a, PDF pages 116 to 126).Download the PDF manualOXI E16 product page



