This is a simple way of sending mod wheel, expression and breath to plugins that receive them, but don't have visible controls.
desc: Simple 3-Bar MIDI CC Drag Pad (with Send Buttons)
author: Assistant
version: 3.1
in_pin:none
out_pin:none
@init
// ==========================================
// CONFIGURATION: Edit CC Numbers & Channel Here
// ==========================================
MIDI_CHAN = 1; // MIDI Channel (1 - 16)
CC_1 = 1; // Modulation Wheel (CC 1)
CC_2 = 11; // Expression (CC 11)
CC_3 = 2; // Breath Controller (CC 2)
// Initial Values (0 - 127)
val_1 = 0;
val_2 = 0;
val_3 = 0;
// ==========================================
// THREAD-SAFE RING BUFFER (UI -> Audio Thread)
// ==========================================
BUF_WRITE = 1000;
BUF_READ = 1001;
BUF_START = 1010;
BUF_SIZE = 256;
BUF_WRITE[0] = 0;
BUF_READ[0] = 0;
function queue_cc(num, val) (
val = max(0, min(127, val));
w_idx = BUF_WRITE[0];
slot_ptr = BUF_START + ((w_idx % BUF_SIZE) * 2);
slot_ptr[0] = num;
slot_ptr[1] = val;
BUF_WRITE[0] = w_idx + 1;
);
function queue_send_all() (
queue_cc(CC_1, val_1);
queue_cc(CC_2, val_2);
queue_cc(CC_3, val_3);
);
// Debug Display State
debug_msg_status = "Status: Initialized. Drag a bar or click Send.";
debug_msg_cc = 0;
debug_msg_val = 0;
debug_flash = 0;
@block
// 1. Pass through external incoming MIDI
while (midirecv(offset, msg1, msg23) ? (
midisend(offset, msg1, msg23);
));
// 2. Drain queued UI events inside the audio block thread
r_idx = BUF_READ[0];
w_idx = BUF_WRITE[0];
while (r_idx < w_idx) (
slot_ptr = BUF_START + ((r_idx % BUF_SIZE) * 2);
cc_num = slot_ptr[0];
cc_val = slot_ptr[1];
midisend(0, 176 + (MIDI_CHAN - 1), cc_num, cc_val);
debug_msg_status = "SENT MIDI CC";
debug_msg_cc = cc_num;
debug_msg_val = cc_val;
debug_flash = 1.0;
r_idx += 1;
);
BUF_READ[0] = r_idx;
@gfx 400 370
// Dark Background
gfx_r = 0.10; gfx_g = 0.10; gfx_b = 0.12; gfx_a = 1.0;
gfx_rect(0, 0, gfx_w, gfx_h);
// Mouse Click Triggering (Single Click Detection)
is_click = (mouse_cap & 1) && !(last_mouse_cap & 1);
// ==========================================
// 1. DEBUG CONSOLE & "SEND ALL" HEADER
// ==========================================
header_h = 34;
send_all_w = 85;
// Debug Box Bounds
dbg_w = gfx_w - send_all_w - 30;
gfx_r = 0.05 + (debug_flash * 0.1);
gfx_g = 0.08 + (debug_flash * 0.3);
gfx_b = 0.10 + (debug_flash * 0.2);
gfx_rect(10, 10, dbg_w, header_h, 1);
gfx_r = 0.3; gfx_g = 0.5; gfx_b = 0.6;
gfx_rect(10, 10, dbg_w, header_h, 0);
// Print Debug Output Text
gfx_r = 0.4 + (debug_flash * 0.6);
gfx_g = 0.9;
gfx_b = 0.4 + (debug_flash * 0.6);
gfx_x = 18; gfx_y = 21;
gfx_drawstr(debug_msg_status);
debug_msg_cc > 0 ? (
gfx_drawstr(" -> CC: ");
gfx_drawnumber(debug_msg_cc, 0);
gfx_drawstr(" | Val: ");
gfx_drawnumber(debug_msg_val, 0);
);
debug_flash = max(0, debug_flash - 0.05);
// "SEND ALL" Button
send_all_x = gfx_w - send_all_w - 10;
send_all_y = 10;
hover_send_all = (mouse_x >= send_all_x && mouse_x <= send_all_x + send_all_w && mouse_y >= send_all_y && mouse_y <= send_all_y + header_h);
hover_send_all ? (
gfx_r = 0.25; gfx_g = 0.55; gfx_b = 0.85;
is_click ? queue_send_all();
) : (
gfx_r = 0.18; gfx_g = 0.28; gfx_b = 0.38;
);
gfx_rect(send_all_x, send_all_y, send_all_w, header_h, 1);
gfx_r = 0.3; gfx_g = 0.5; gfx_b = 0.6;
gfx_rect(send_all_x, send_all_y, send_all_w, header_h, 0);
gfx_r = 0.95; gfx_g = 0.95; gfx_b = 0.95;
gfx_x = send_all_x + 12; gfx_y = send_all_y + 11;
gfx_drawstr("SEND ALL");
// ==========================================
// 2. VERTICAL BARS LAYOUT WITH SEND BUTTONS
// ==========================================
btn_bar_h = 24;
margin_top = 90;
margin_bottom = 35;
pad = 15;
num_bars = 3;
bar_w = (gfx_w - (pad * (num_bars + 1))) / num_bars;
bar_h = gfx_h - margin_top - margin_bottom;
i = 0;
loop(num_bars,
cc_num = (i == 0 ? CC_1 : i == 1 ? CC_2 : CC_3);
cc_val = (i == 0 ? val_1 : i == 1 ? val_2 : val_3);
bx = pad + i * (bar_w + pad);
by = margin_top;
// --- Draw Per-Bar "SEND" Button ---
btn_y = margin_top - btn_bar_h - 10;
hover_bar_btn = (mouse_x >= bx && mouse_x <= bx + bar_w && mouse_y >= btn_y && mouse_y <= btn_y + btn_bar_h);
hover_bar_btn ? (
gfx_r = 0.35; gfx_g = 0.55; gfx_b = 0.45;
is_click ? queue_cc(cc_num, cc_val);
) : (
gfx_r = 0.18; gfx_g = 0.26; gfx_b = 0.22;
);
gfx_rect(bx, btn_y, bar_w, btn_bar_h, 1);
gfx_r = 0.3; gfx_g = 0.45; gfx_b = 0.35;
gfx_rect(bx, btn_y, bar_w, btn_bar_h, 0);
gfx_r = 0.9; gfx_g = 0.9; gfx_b = 0.9;
gfx_x = bx + (bar_w / 2) - 16;
gfx_y = btn_y + 6;
gfx_drawstr("SEND");
// --- Mouse Drag Logic for Bar Tracks ---
(mouse_cap & 1) && (mouse_x >= bx && mouse_x <= bx + bar_w && mouse_y >= by && mouse_y <= by + bar_h) ? (
norm_val = 1 - ((mouse_y - by) / bar_h);
new_val = max(0, min(127, floor(norm_val * 127 + 0.5)));
// Push event to queue when value changes during drag
new_val != cc_val ? (
i == 0 ? val_1 = new_val :
i == 1 ? val_2 = new_val :
val_3 = new_val;
queue_cc(cc_num, new_val);
);
);
// Read current value
cc_val = (i == 0 ? val_1 : i == 1 ? val_2 : val_3);
// Bar Background Track
gfx_r = 0.04; gfx_g = 0.04; gfx_b = 0.05;
gfx_rect(bx, by, bar_w, bar_h, 1);
// Filled Value Bar
fill_h = (cc_val / 127) * bar_h;
fill_y = by + (bar_h - fill_h);
i == 0 ? ( gfx_r = 0.2; gfx_g = 0.6; gfx_b = 0.9; ) :
( gfx_r = 0.25; gfx_g = 0.75; gfx_b = 0.45; );
gfx_rect(bx, fill_y, bar_w, fill_h, 1);
// Box Border
gfx_r = 0.25; gfx_g = 0.25; gfx_b = 0.3;
gfx_rect(bx, by, bar_w, bar_h, 0);
// Value Number Inside Bar
gfx_r = 1.0; gfx_g = 1.0; gfx_b = 1.0;
gfx_x = bx + (bar_w / 2) - 8;
gfx_y = by + bar_h - 22;
gfx_drawnumber(cc_val, 0);
// Label Below Bar
gfx_r = 0.75; gfx_g = 0.75; gfx_b = 0.75;
gfx_x = bx + (bar_w / 2) - 24;
gfx_y = by + bar_h + 8;
i == 0 ? gfx_drawstr("Mod (1)") :
i == 1 ? gfx_drawstr("Exp (11)") :
gfx_drawstr("Breath (2)");
i += 1;
);
last_mouse_cap = mouse_cap;