tags: jsfx This is a simple way of sending mod wheel, expression and breath to plugins that receive them, but don't have visible controls. ```plaintext 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; ```