desc: Interactive Tonnetz MIDI
// version: 5.2 (Full Header)
// author: assistant
// about: Интерактивный Tonnetz. ▲ (Вверх) = Мажор, ▼ (Вниз) = Минор. Три режима клика и секвенсор LPR.
// 
// ==========================================
//             ОПИСАНИЕ РАБОТЫ
// ==========================================
// 1. ГЕНЕРАЦИЯ ЗВУКА: Кликайте по треугольникам для проигрывания мажорных (▲) и минорных (▼) аккордов. 
//    Зажмите мышь для бесконечного сустейна. Ведите мышью с зажатой кнопкой для легато.
// 2. ФИЛЬТР ТОНИКИ (Слева): Нажмите на любую ноту на левой панели, чтобы подсветить 
//    все аккорды, где эта нота является корневой (тоникой). 
// 3. СЕКВЕНСОР LPR (Сверху): Введите строку (например: l p r). 
//    - L (Leading-tone), P (Parallel), R (Relative) — классические преобразования аккордов.
//    - Длительности: заглавные буквы (L, P, R) — половинные; строчные (l, p, r) — четвертные. 
//    - Знаки: L+ (целая), l- (восьмая), l++ (шестнадцатая), O (стартовая нота).
//    - Запятая (l, p, r) означает нон-легато (паузу между аккордами).
// 4. РЕЖИМЫ КЛИКА (Тумблер On Click):
//    - [Interrupt] — клик по сетке мгновенно прерывает секвенсор и играет новый аккорд.
//    - [Wait] — клики встают в очередь (бирюзовый контур) и играют после завершения ВСЕЙ последовательности.
//    - [Last] — клики встают в очередь и играют после завершения ТЕКУЩЕГО звучащего аккорда.
// 5. ТЕМП И ОКТАВЫ: Длительности синхронизированы с BPM хоста. Слайдер "Base Length" 
//    задает длительность в долях. Справа — кнопки смены октавы и PANIC (сброс всех нот).

slider1:beat_mult=1<0.1,4,0.1>Base Length (beats)
slider2:velocity=100<1,127,1>Velocity
slider3:midi_ch=1<1,16,1>MIDI channel
slider4:octave=4<0,8,1>Base octave
slider5:show_labels=1<0,1,1{Off,On}>Show note names
slider6:allow_interrupt=0<0,2,1{Interrupt, Wait, Last}>Seq Click Behavior

@init
// ---------- константы сетки ----------
COLS = 20;
ROWS = 12;

// ---------- состояние ----------
last_cap = 0;
gfx_mouse_down = 0;
trigger_stop_all = 0; 
trigger_panic = 0; 
trigger_play = 0;
trigger_release = 0;
trigger_pc1 = 0;
trigger_pc2 = 0;
trigger_pc3 = 0;
trigger_c = 0;
trigger_r = 0;
trigger_is_lower = 0;
note_off_timer = 0;

// Состояние для drag-legato
hover_c = -1;
hover_r = -1;
hover_is_lower = -1;

// Состояние для Очереди (Queue)
has_queued = 0;
queued_c = -1;
queued_r = -1;
queued_is_lower = -1;

// Состояние секвенсора
input_active = 0;
seq_active = 0;
seq_idx = 0;
seq_waiting = 0;
seq_timer = 0;
seq_c = 0;
seq_r = 0;
seq_is_lower = 0;
seq_length = 0;
has_initial = 0;
initial_dur = 1;
initial_gap = 0;

// Инициализация переменных (сохраняются при REC)
init_done == 0 ? (
  #seq = ""; 
  selected_root_pc = -1;
  init_done = 1;
);

// Массивы для хранения шагов секвенсора
seq_ops = 2000;
seq_durs = 2100;
seq_gaps = 2200;

// Массив активных нот (от 1000 до 1127)
active_notes = 1000; 
i = 0; loop(128, active_notes[i] = 0; i += 1;);

// --- Состояние для подсветки ---
is_playing = 0;
active_c = 0;
active_r = 0;
active_is_lower = 0;

// ---------- функции ----------
function mod12(x)
  local(r)
  (
    r = x % 12;
    r < 0 ? r += 12;
    r;
  );

function node_pc(col, row)
  local(v)
  (
    v = mod12(col * 7 + row * 4);
    v;
  );

function point_in_tri(px, py, x1, y1, x2, y2, x3, y3)
  local(d1, d2, d3, has_neg, has_pos)
  (
    d1 = (px - x2) * (y1 - y2) - (x1 - x2) * (py - y2);
    d2 = (px - x3) * (y2 - y3) - (x2 - x3) * (py - y3);
    d3 = (px - x1) * (y3 - y1) - (x3 - x1) * (py - y1);
    has_neg = (d1 < 0) || (d2 < 0) || (d3 < 0);
    has_pos = (d1 > 0) || (d2 > 0) || (d3 > 0);
    !(has_neg && has_pos);
  );

function stop_active_notes()
  local(i)
  (
    i = 0;
    loop(128,
      active_notes[i] ? (
        midisend(0, $x80 | (midi_ch - 1), i, 0);
        active_notes[i] = 0;
      );
      i += 1;
    );
    is_playing = 0;
  );

function emit_chord()
  local(pc1, pc2, pc3, n)
  (
    seq_is_lower == 0 ? (
      // МАЖОР (Вершина вверх)
      pc1 = node_pc(seq_c, seq_r);       // Корень (нижний левый)
      pc2 = node_pc(seq_c + 1, seq_r);   // Квинта (нижний правый)
      pc3 = node_pc(seq_c, seq_r + 1);   // Терция (вершина)
    ) : (
      // МИНОР (Вершина вниз)
      pc1 = node_pc(seq_c, seq_r + 1);   // Корень (верхний левый)
      pc2 = node_pc(seq_c + 1, seq_r);   // Квинта (верхний правый)
      pc3 = node_pc(seq_c + 1, seq_r + 1); // Терция (нижняя вершина)
    );
    
    n = (octave + 1) * 12 + pc1;
    (n >= 0 && n <= 127) ? ( midisend(0, $x90 | (midi_ch - 1), n, velocity); active_notes[n] = 1; );
    n = (octave + 1) * 12 + pc2;
    (n >= 0 && n <= 127) ? ( midisend(0, $x90 | (midi_ch - 1), n, velocity); active_notes[n] = 1; );
    n = (octave + 1) * 12 + pc3;
    (n >= 0 && n <= 127) ? ( midisend(0, $x90 | (midi_ch - 1), n, velocity); active_notes[n] = 1; );
    
    active_c = seq_c;
    active_r = seq_r;
    active_is_lower = seq_is_lower;
    is_playing = 1;
  );

function apply_lpr(op)
  local(nc, nr, nl)
  (
    nc = seq_c; nr = seq_r; nl = seq_is_lower;
    
    (op == 1) ? ( // L
      nl = 1 - nl;
    ) : (op == 2) ? ( // P
      nl == 0 ? (nr -= 1; nl = 1) : (nr += 1; nl = 0);
    ) : (op == 3) ? ( // R
      nl == 0 ? (nc -= 1; nl = 1) : (nc += 1; nl = 0);
    );
    
    seq_c = nc; seq_r = nr; seq_is_lower = nl;
  );

function parse_seq()
  local(i, len, c, is_upper, dur, op, c2, c3, has_comma)
  (
    len = strlen(#seq);
    i = 0;
    seq_length = 0;
    initial_dur = 1;
    has_comma = 0;
    initial_gap = 0;
    has_initial = 0;

    (len > 0) ? (
      c = str_getchar(#seq, i);
      c2 = (c >= 'A' && c <= 'Z') ? c + 32 : c;
      (c2 == 'o') ? (
        has_initial = 1;
        is_upper = (c >= 'A' && c <= 'Z');
        initial_dur = is_upper ? 2 : 1;
        i += 1;
        (i < len) ? (
          c3 = str_getchar(#seq, i);
          (c3 == '+' && is_upper) ? ( initial_dur = 4; i += 1; )
          : (c3 == '-' && !is_upper) ? (
            initial_dur = 0.5; i += 1;
            (i < len && str_getchar(#seq, i) == '-') ? ( initial_dur = 0.25; i += 1; );
          )
          : (c3 == '+' && !is_upper) ? (
            initial_dur = 0.25; i += 1;
            (i < len && str_getchar(#seq, i) == '+') ? ( i += 1; );
          );
        );
      );
    );

    while(i < len && seq_length < 128) (
      c = str_getchar(#seq, i);
      (c == ',') ? (
        has_comma = 1;
        i += 1;
      ) : (c == ' ' || c == '.') ? (
        i += 1;
      ) : (
        is_upper = (c >= 'A' && c <= 'Z');
        c2 = (c >= 'A' && c <= 'Z') ? c + 32 : c;
        
        (c2 == 'l' || c2 == 'p' || c2 == 'r') ? (
          op = (c2 == 'l') ? 1 : (c2 == 'p') ? 2 : 3;
          dur = is_upper ? 2 : 1;
          
          i += 1;
          (i < len) ? (
            c3 = str_getchar(#seq, i);
            (c3 == '+' && is_upper) ? (
              dur = 4; i += 1;
            ) : (c3 == '-' && !is_upper) ? (
              dur = 0.5; i += 1;
              (i < len && str_getchar(#seq, i) == '-') ? ( dur = 0.25; i += 1; );
            ) : (c3 == '+' && !is_upper) ? (
              dur = 0.25; i += 1;
              (i < len && str_getchar(#seq, i) == '+') ? ( i += 1; );
            );
          );
          
          seq_ops[seq_length] = op;
          seq_durs[seq_length] = dur;
          
          (seq_length == 0) ? (
            initial_gap = has_comma;
          ) : (
            seq_gaps[seq_length - 1] = has_comma;
          );
          
          seq_length += 1;
          has_comma = 0;
        ) : (
          i += 1;
        );
      );
    );
    
    (seq_length > 0) ? (
      seq_gaps[seq_length - 1] = 0;
    );
  );

// Классический горизонтальный Tonnetz
function gx(c, r) ( cx + ((c - off_c) + (r - off_r) * 0.5) * cell_size );
function gy(c, r) ( cy - (r - off_r) * cell_size );

// ---------- СОХРАНЕНИЕ СОСТОЯНИЯ ----------
@serialize
file_var(0, selected_root_pc);
file_string(0, #seq);

@block
// *** СИНХРОНИЗАЦИЯ С BPM ХОСТА ***
current_base_ms = (60000.0 / max(tempo, 1.0)) * beat_mult;

// 0. PANIC (All Notes Off)
trigger_panic ? (
  stop_active_notes();
  midisend(0, $xB0 | (midi_ch - 1), 123, 0); 
  i = 0; loop(128, midisend(0, $x80 | (midi_ch - 1), i, 0); i += 1; ); 
  
  seq_active = 0;
  note_off_timer = 0;
  has_queued = 0;
  trigger_panic = 0;
);

// 1. Принудительная остановка всех нот перед новым аккордом
trigger_stop_all ? (
  stop_active_notes();
  trigger_stop_all = 0;
);

// 2. Логика секвенсора
seq_active ? (
  seq_timer -= (samplesblock / srate) * 1000;
  seq_timer <= 0 ? (
    seq_waiting ? (
      seq_waiting = 0;
      
      (seq_idx == 0) ? (
         dur = initial_dur;
         gap = initial_gap;
      ) : (
         op = seq_ops[seq_idx - 1];
         apply_lpr(op);
         dur = seq_durs[seq_idx - 1];
         gap = seq_gaps[seq_idx - 1];
      );
      
      is_last = (seq_idx == seq_length);
      (!is_last && gap) ? ( seq_timer = current_base_ms * dur * 0.8; ) : ( seq_timer = current_base_ms * dur; );
      
      emit_chord();
    ) : (
      stop_active_notes();
      
      // *** ОБРАБОТКА РЕЖИМА LAST ***
      (allow_interrupt == 2 && has_queued) ? (
        seq_active = 0; // Принудительно завершаем текущую последовательность
      ) : (
        // Нормальное продвижение по секвенсору
        is_last = (seq_idx == seq_length);
        (is_last) ? (
          seq_active = 0;
        ) : (
          gap = (seq_idx == 0) ? initial_gap : seq_gaps[seq_idx - 1];
          
          gap ? (
            dur = (seq_idx == 0) ? initial_dur : seq_durs[seq_idx - 1];
            seq_timer = current_base_ms * dur * 0.2;
            seq_waiting = 1;
          ) : (
            seq_idx += 1;
            seq_waiting = 1;
            seq_timer = 1; 
          );
        );
      );
    );
  );
);

// 3. Логика одиночного нажатия и очереди
!seq_active ? (
  note_off_timer > 0 ? (
    note_off_timer -= (samplesblock / srate) * 1000;
    (note_off_timer <= 0 && !gfx_mouse_down) ? (
      stop_active_notes();
      note_off_timer = 0;
    );
  );
  
  trigger_release ? (
    note_off_timer <= 0 ? stop_active_notes();
    trigger_release = 0;
  );

  has_queued ? (
    has_queued = 0;
    parse_seq(); 
    
    seq_c = queued_c;
    seq_r = queued_r;
    seq_is_lower = queued_is_lower;
    
    (seq_length > 0 || has_initial) ? (
      seq_idx = 0;
      seq_waiting = 1;
      seq_active = 1;
      seq_timer = 1;
    ) : (
      trigger_c = queued_c;
      trigger_r = queued_r;
      trigger_is_lower = queued_is_lower;
      trigger_play = 1;
    );
  ) : (
    trigger_play ? (
      stop_active_notes();
      
      trigger_is_lower == 0 ? (
        trigger_pc1 = node_pc(trigger_c, trigger_r);
        trigger_pc2 = node_pc(trigger_c + 1, trigger_r);
        trigger_pc3 = node_pc(trigger_c, trigger_r + 1);
      ) : (
        trigger_pc1 = node_pc(trigger_c, trigger_r + 1);
        trigger_pc2 = node_pc(trigger_c + 1, trigger_r);
        trigger_pc3 = node_pc(trigger_c + 1, trigger_r + 1);
      );
      
      active_c = trigger_c;
      active_r = trigger_r;
      active_is_lower = trigger_is_lower;
      is_playing = 1;
      
      n = (octave + 1) * 12 + trigger_pc1;
      (n >= 0 && n <= 127) ? ( midisend(0, $x90 | (midi_ch - 1), n, velocity); active_notes[n] = 1; );
      n = (octave + 1) * 12 + trigger_pc2;
      (n >= 0 && n <= 127) ? ( midisend(0, $x90 | (midi_ch - 1), n, velocity); active_notes[n] = 1; );
      n = (octave + 1) * 12 + trigger_pc3;
      (n >= 0 && n <= 127) ? ( midisend(0, $x90 | (midi_ch - 1), n, velocity); active_notes[n] = 1; );
      
      note_off_timer = max(current_base_ms, 50); 
      trigger_play = 0;
    );
  );
);

@gfx 640 480
gfx_clear = 30; 

ui_h = 85; 
left_margin = 60; 
right_margin = 160; 
avail_w = gfx_w - left_margin - right_margin - 20;
avail_h = gfx_h - ui_h - 20;

span_w = (COLS - 1) + (ROWS - 1) * 0.5;
span_h = ROWS - 1;
cell_size = min(avail_w / span_w, avail_h / span_h);
cell_size = min(cell_size, 60); 
cell_size = max(cell_size, 10); 

cx = left_margin + avail_w / 2;
cy = ui_h + avail_h / 2;
off_c = (COLS - 1) / 2.0;
off_r = (ROWS - 1) / 2.0;

// ---------- Панель нот (Слева) ----------
note_x = 10;
note_w = 40;
note_y = ui_h;
note_h = (gfx_h - ui_h - 10) / 12;

pc = 0;
loop(12,
  ny1 = note_y + pc * note_h;
  
  selected_root_pc == pc ? gfx_set(0.3, 0.6, 1, 0.8) : gfx_set(0.2, 0.2, 0.2, 0.8);
  gfx_rect(note_x, ny1, note_w, note_h, 1);
  
  gfx_set(0.1, 0.1, 0.1, 1);
  gfx_rect(note_x, ny1, note_w, note_h, 0);
  
  gfx_setfont(1, "Arial", max(10, floor(note_h * 0.5)));
  strcpy_substr(#name, "C C#D D#E F F#G G#A A#B ", pc * 2, 2);
  
  w_tmp = 0; h_tmp = 0;
  gfx_measurestr(#name, w_tmp, h_tmp);
  
  gfx_set(1, 1, 1, 1);
  gfx_x = note_x + (note_w - w_tmp) / 2;
  gfx_y = ny1 + (note_h - h_tmp) / 2;
  gfx_drawstr(#name);
  
  pc += 1;
);

// ---------- Блок аккорда ----------
chord_x = gfx_w - 160;
chord_y = ui_h + 20;
chord_w = 110;
chord_h = 60;

gfx_set(0.15, 0.15, 0.15, 0.8);
gfx_rect(chord_x, chord_y, chord_w, chord_h, 1);
gfx_set(0.3, 0.3, 0.3, 1);
gfx_rect(chord_x, chord_y, chord_w, chord_h, 0);

is_playing ? (
  active_is_lower == 0 ? (
    root_pc = node_pc(active_c, active_r);
    chord_suffix = "maj";
  ) : (
    root_pc = node_pc(active_c, active_r + 1); 
    chord_suffix = "min";
  );
  
  strcpy_substr(#root_str, "C C#D D#E F F#G G#A A#B ", root_pc * 2, 2);
  sprintf(#chord_name, "%s%s", #root_str, chord_suffix);
  
  gfx_setfont(1, "Arial", 26);
  active_is_lower == 0 ? gfx_set(1, 0.6, 0.2, 1) : gfx_set(0.3, 0.7, 1, 1);
  
  w_tmp = 0; h_tmp = 0;
  gfx_measurestr(#chord_name, w_tmp, h_tmp);
  gfx_x = chord_x + (chord_w - w_tmp) / 2;
  gfx_y = chord_y + (chord_h - h_tmp) / 2;
  gfx_drawstr(#chord_name);
) : (
  gfx_setfont(1, "Arial", 24);
  gfx_set(0.4, 0.4, 0.4, 1);
  gfx_x = chord_x + 38; gfx_y = chord_y + 15;
  gfx_drawstr("---");
);

// ---------- Кнопка PANIC ----------
panic_x = gfx_w - 160;
panic_y = chord_y + chord_h + 10;
panic_w = 110;
panic_h = 30;

gfx_set(0.6, 0.1, 0.1, 1); 
gfx_rect(panic_x, panic_y, panic_w, panic_h, 1);
gfx_set(0.9, 0.3, 0.3, 1);
gfx_rect(panic_x, panic_y, panic_w, panic_h, 0);

gfx_setfont(1, "Arial", 14);
gfx_set(1, 1, 1, 1);
w_tmp = 0; h_tmp = 0;
gfx_measurestr("PANIC", w_tmp, h_tmp);
gfx_x = panic_x + (panic_w - w_tmp) / 2;
gfx_y = panic_y + (panic_h - h_tmp) / 2;
gfx_drawstr("PANIC");

// ---------- Панель октав (Справа) ----------
oct_x = gfx_w - 45;
oct_w = 35;
oct_y = panic_y + panic_h + 10; 
oct_h = gfx_h - oct_y - 20;
btn_h = 30;

(oct_h > btn_h * 2) ? (
  gfx_set(0.15, 0.15, 0.15, 0.8);
  gfx_rect(oct_x, oct_y, oct_w, oct_h, 1);
  gfx_set(0.3, 0.3, 0.3, 1);
  gfx_rect(oct_x, oct_y, oct_w, oct_h, 0);

  gfx_set(0.25, 0.25, 0.25, 1);
  gfx_rect(oct_x, oct_y, oct_w, btn_h, 1);
  gfx_set(0.8, 0.8, 0.8, 1);
  gfx_triangle(oct_x + oct_w/2, oct_y + 10, oct_x + 10, oct_y + 22, oct_x + oct_w - 10, oct_y + 22, 1);

  gfx_set(0.25, 0.25, 0.25, 1);
  gfx_rect(oct_x, oct_y + oct_h - btn_h, oct_w, btn_h, 1);
  gfx_set(0.8, 0.8, 0.8, 1);
  gfx_triangle(oct_x + oct_w/2, oct_y + oct_h - 10, oct_x + 10, oct_y + oct_h - 22, oct_x + oct_w - 10, oct_y + oct_h - 22, 1);

  gfx_setfont(1, "Arial", 18);
  sprintf(#oct_str, "%d", octave);
  w_tmp = 0; h_tmp = 0;
  gfx_measurestr(#oct_str, w_tmp, h_tmp);
  gfx_set(1, 1, 0, 1);
  gfx_x = oct_x + (oct_w - w_tmp) / 2;
  gfx_y = oct_y + (oct_h - h_tmp) / 2;
  gfx_drawstr(#oct_str);

  gfx_setfont(1, "Arial", 10);
  gfx_x = oct_x + 5; gfx_y = oct_y + oct_h - btn_h + 40;
  gfx_set(0.5, 0.5, 0.5, 1);
  gfx_drawstr("OCT");
);

gfx_setfont(1, "Arial", 14);

// ---------- ЛЕГЕНДА ----------
gfx_setfont(1, "Arial", 12);
gfx_set(1, 0.6, 0.2, 1); // Оранжевый
gfx_x = left_margin + 10; gfx_y = ui_h + 5;
gfx_drawstr("▲ = Major");
gfx_set(0.3, 0.7, 1, 1); // Голубой
gfx_x = left_margin + 90; gfx_y = ui_h + 5;
gfx_drawstr("▼ = Minor");

// ---------- Фон треугольников ----------
c = 0;
loop(COLS - 1,
  r = 0;
  loop(ROWS - 1,
    root_maj = node_pc(c, r);
    root_min = node_pc(c, r+1);
    
    // МАЖОР (▲)
    x1 = gx(c, r);   y1 = gy(c, r);       
    x2 = gx(c+1, r); y2 = gy(c+1, r);     
    x3 = gx(c, r+1); y3 = gy(c, r+1);     
    (selected_root_pc >= 0 && root_maj == selected_root_pc) ? gfx_set(1, 1, 0, 0.25) : gfx_set(1, 0.4, 0.1, 0.15);
    gfx_triangle(x1, y1, x2, y2, x3, y3, 1);
    
    // МИНОР (▼)
    x1 = gx(c+1, r);   y1 = gy(c+1, r);   
    x2 = gx(c+1, r+1); y2 = gy(c+1, r+1); 
    x3 = gx(c, r+1);   y3 = gy(c, r+1);   
    (selected_root_pc >= 0 && root_min == selected_root_pc) ? gfx_set(1, 1, 0, 0.25) : gfx_set(0.1, 0.4, 0.9, 0.15);
    gfx_triangle(x1, y1, x2, y2, x3, y3, 1);
    
    r += 1;
  );
  c += 1;
);

// ---------- Линии сетки ----------
gfx_set(0.35, 0.35, 0.45, 1);
c = 0;
loop(COLS,
  r = 0;
  loop(ROWS,
    nx = gx(c, r); ny = gy(c, r);
    c + 1 < COLS ? gfx_line(nx, ny, gx(c+1, r), gy(c+1, r));
    r + 1 < ROWS ? gfx_line(nx, ny, gx(c, r+1), gy(c, r+1));
    (c + 1 < COLS && r + 1 < ROWS) ? gfx_line(gx(c+1, r), gy(c+1, r), gx(c, r+1), gy(c, r+1));
    r += 1;
  );
  c += 1;
);

// ---------- Выделение корневых треугольников ----------
selected_root_pc >= 0 ? (
  c = 0;
  loop(COLS - 1,
    r = 0;
    loop(ROWS - 1,
      root_maj = node_pc(c, r);
      root_min = node_pc(c, r+1);
      gfx_set(1, 1, 0, 0.8);
      
      (root_maj == selected_root_pc) ? (
        x1 = gx(c, r);   y1 = gy(c, r);
        x2 = gx(c+1, r); y2 = gy(c+1, r);
        x3 = gx(c, r+1); y3 = gy(c, r+1);
        gfx_line(x1, y1, x2, y2); gfx_line(x2, y2, x3, y3); gfx_line(x3, y3, x1, y1);
      );
      
      (root_min == selected_root_pc) ? (
        x1 = gx(c+1, r);   y1 = gy(c+1, r);
        x2 = gx(c+1, r+1); y2 = gy(c+1, r+1);
        x3 = gx(c, r+1);   y3 = gy(c, r+1);
        gfx_line(x1, y1, x2, y2); gfx_line(x2, y2, x3, y3); gfx_line(x3, y3, x1, y1);
      );
      r += 1;
    );
    c += 1;
  );
);

// ---------- Активный треугольник ----------
is_playing ? (
  active_is_lower == 0 ? (
    x1 = gx(active_c, active_r);     y1 = gy(active_c, active_r);
    x2 = gx(active_c+1, active_r);   y2 = gy(active_c+1, active_r);
    x3 = gx(active_c, active_r+1);   y3 = gy(active_c, active_r+1);
  ) : (
    x1 = gx(active_c+1, active_r);   y1 = gy(active_c+1, active_r);
    x2 = gx(active_c+1, active_r+1); y2 = gy(active_c+1, active_r+1);
    x3 = gx(active_c, active_r+1);   y3 = gy(active_c, active_r+1);
  );
  
  gfx_set(1, 1, 1, 0.25); 
  gfx_triangle(x1, y1, x2, y2, x3, y3, 1);
  gfx_set(1, 1, 1, 0.9);
  gfx_line(x1, y1, x2, y2); gfx_line(x2, y2, x3, y3); gfx_line(x3, y3, x1, y1);
);

// ---------- Индикация Очереди (Queue) ----------
has_queued ? (
  queued_is_lower == 0 ? (
    x1 = gx(queued_c, queued_r);     y1 = gy(queued_c, queued_r);
    x2 = gx(queued_c+1, queued_r);   y2 = gy(queued_c+1, queued_r);
    x3 = gx(queued_c, queued_r+1);   y3 = gy(queued_c, queued_r+1);
  ) : (
    x1 = gx(queued_c+1, queued_r);   y1 = gy(queued_c+1, queued_r);
    x2 = gx(queued_c+1, queued_r+1); y2 = gy(queued_c+1, queued_r+1);
    x3 = gx(queued_c, queued_r+1);   y3 = gy(queued_c, queued_r+1);
  );
  
  gfx_set(0.2, 0.8, 0.8, 0.4); 
  gfx_triangle(x1, y1, x2, y2, x3, y3, 1);
  gfx_set(0.2, 0.9, 1.0, 1.0); 
  gfx_line(x1, y1, x2, y2); gfx_line(x2, y2, x3, y3); gfx_line(x3, y3, x1, y1);
);

// ---------- Узлы ----------
c = 0;
loop(COLS,
  r = 0;
  loop(ROWS,
    nx = gx(c, r); ny = gy(c, r);
    pc = node_pc(c, r);
    
    gfx_set(0.9, 0.85, 0.7, 1);
    gfx_circle(nx, ny, max(2, cell_size * 0.15), 1);
    
    show_labels ? (
      gfx_setfont(1, "Arial", max(8, floor(cell_size * 0.35))); 
      gfx_set(0.8, 0.8, 0.8, 0.9);
      strcpy_substr(#name, "C C#D D#E F F#G G#A A#B ", pc * 2, 2);
      w_tmp = 0; h_tmp = 0;
      gfx_measurestr(#name, w_tmp, h_tmp); 
      gfx_x = nx - w_tmp / 2;
      gfx_y = ny + cell_size * 0.2; 
      gfx_drawstr(#name);
    );
    r += 1;
  );
  c += 1;
);

gfx_setfont(1, "Arial", 14);

// ---------- UI ----------
input_active ? gfx_set(0.3, 0.6, 1, 1) : gfx_set(0.3, 0.3, 0.3, 1);
gfx_rect(10, 10, 350, 26, 0);
input_active ? gfx_set(0.2, 0.2, 0.25, 1) : gfx_set(0.15, 0.15, 0.15, 1);
gfx_rect(11, 11, 348, 24, 1);

gfx_set(0.8, 0.8, 0.8, 1);
gfx_x = 15; gfx_y = 18; gfx_drawstr("Seq: ");

gfx_set(1, 1, 1, 1);
gfx_x = 60; gfx_y = 18; gfx_drawstr(#seq);
text_end_x = gfx_x; 

input_active ? ( (time_precise() * 2) % 1 > 0.5 ? gfx_rect(text_end_x, 14, 2, 18); );

gfx_set(0.25, 0.25, 0.25, 1);
gfx_rect(370, 10, 50, 26);
gfx_set(0.9, 0.9, 0.9, 1);
gfx_x = 380; gfx_y = 18; gfx_drawstr("Clear");

// ---------- Тумблер поведения клика (3 опции) ----------
sw_x = 115;
sw_y = 42;
sw_w = 180;
sw_h = 18;

gfx_setfont(1, "Arial", 14);
gfx_set(0.8, 0.8, 0.8, 1);
gfx_x = 10; gfx_y = 45;
gfx_drawstr("On Click:");

gfx_set(0.15, 0.15, 0.15, 1);
gfx_rect(sw_x, sw_y, sw_w, sw_h, 1);
gfx_set(0.3, 0.3, 0.3, 1);
gfx_rect(sw_x, sw_y, sw_w, sw_h, 0);

gfx_setfont(1, "Arial", 11);

// 0: Interrupt
(allow_interrupt == 0) ? gfx_set(0.6, 0.2, 0.2, 1) : gfx_set(0.15, 0.15, 0.15, 1);
gfx_rect(sw_x+2, sw_y+2, sw_w/3-2, sw_h-4, 1);
(allow_interrupt == 0) ? gfx_set(1, 1, 1, 1) : gfx_set(0.5, 0.5, 0.5, 1);
gfx_x = sw_x + 6; gfx_y = sw_y + 3;
gfx_drawstr("Interrupt");

// 1: Wait
(allow_interrupt == 1) ? gfx_set(0.2, 0.6, 0.2, 1) : gfx_set(0.15, 0.15, 0.15, 1);
gfx_rect(sw_x+sw_w/3, sw_y+2, sw_w/3-2, sw_h-4, 1);
(allow_interrupt == 1) ? gfx_set(1, 1, 1, 1) : gfx_set(0.5, 0.5, 0.5, 1);
gfx_x = sw_x + sw_w/3 + 20; gfx_y = sw_y + 3;
gfx_drawstr("Wait");

// 2: Last
(allow_interrupt == 2) ? gfx_set(0.2, 0.4, 0.8, 1) : gfx_set(0.15, 0.15, 0.15, 1);
gfx_rect(sw_x+sw_w*2/3, sw_y+2, sw_w/3-2, sw_h-4, 1);
(allow_interrupt == 2) ? gfx_set(1, 1, 1, 1) : gfx_set(0.5, 0.5, 0.5, 1);
gfx_x = sw_x + sw_w*2/3 + 20; gfx_y = sw_y + 3;
gfx_drawstr("Last");

// Подсказка по синтаксису
gfx_setfont(1, "Arial", 12);
gfx_set(0.6, 0.6, 0.6, 1);
gfx_x = 10; gfx_y = 68;
gfx_drawstr("Syntax: O+ (Start) | L+ (Whole) | L (Half) | l (Quarter) | l- (Eighth) | l++ (16th) | , (Non-legato)");

// ---------- Мышь и клавиатура ----------
mouse_down = (mouse_cap & 1) != 0;
clicked = mouse_down && !(last_cap & 1);
released = !mouse_down && (last_cap & 1);

over_input = (mouse_x >= 10 && mouse_x <= 360 && mouse_y >= 10 && mouse_y <= 36);
over_clear = (mouse_x > 370 && mouse_x < 420 && mouse_y > 10 && mouse_y < 36);
over_switch = (mouse_x >= sw_x && mouse_x <= sw_x + sw_w && mouse_y >= sw_y && mouse_y <= sw_y + sw_h);
over_notes = (mouse_x >= note_x && mouse_x <= note_x + note_w && mouse_y >= note_y && mouse_y <= gfx_h);
over_panic = (mouse_x >= panic_x && mouse_x <= panic_x + panic_w && mouse_y >= panic_y && mouse_y <= panic_y + panic_h);

over_oct_up = (oct_h > btn_h * 2) && (mouse_x >= oct_x && mouse_x <= oct_x + oct_w && mouse_y >= oct_y && mouse_y <= oct_y + btn_h);
over_oct_down = (oct_h > btn_h * 2) && (mouse_x >= oct_x && mouse_x <= oct_x + oct_w && mouse_y >= oct_y + oct_h - btn_h && mouse_y <= oct_y + oct_h);

over_grid = !over_input && !over_clear && !over_switch && !over_notes && !over_panic && !over_oct_up && !over_oct_down;

clicked ? (
  over_switch ? ( 
    (mouse_x < sw_x + sw_w/3) ? ( allow_interrupt = 0; )
    : (mouse_x < sw_x + sw_w*2/3) ? ( allow_interrupt = 1; )
    : ( allow_interrupt = 2; );
    input_active = 0; 
  ) :
  over_panic ? ( trigger_panic = 1; input_active = 0; ) :
  over_clear ? ( #seq = ""; input_active = 0; ) : 
  over_input ? ( input_active = 1; ) : 
  over_notes ? ( 
    clicked_pc = floor((mouse_y - note_y) / note_h);
    (clicked_pc >= 0 && clicked_pc <= 11) ? (
      selected_root_pc == clicked_pc ? ( selected_root_pc = -1; ) : ( selected_root_pc = clicked_pc; );
    );
    input_active = 0;
  ) : 
  over_oct_up ? (
    octave = min(8, octave + 1);
    input_active = 0;
  ) :
  over_oct_down ? (
    octave = max(0, octave - 1);
    input_active = 0;
  ) : (
    input_active = 0;
  );
);

mouse_down && over_grid && !input_active ? (
  found = 0;
  c = 0;
  loop(COLS - 1,
    r = 0;
    loop(ROWS - 1,
      x1 = gx(c, r);   y1 = gy(c, r);
      x2 = gx(c+1, r); y2 = gy(c+1, r);
      x3 = gx(c, r+1); y3 = gy(c, r+1);
      point_in_tri(mouse_x, mouse_y, x1, y1, x2, y2, x3, y3) ? (
        best_c = c; best_r = r; best_is_lower = 0; found = 1;
      );
      
      x1 = gx(c+1, r);   y1 = gy(c+1, r);
      x2 = gx(c+1, r+1); y2 = gy(c+1, r+1);
      x3 = gx(c, r+1);   y3 = gy(c, r+1);
      point_in_tri(mouse_x, mouse_y, x1, y1, x2, y2, x3, y3) ? (
        best_c = c; best_r = r; best_is_lower = 1; found = 1;
      );
      r += 1;
    );
    c += 1;
  );
  
  found ? (
    (best_c != hover_c || best_r != hover_r || best_is_lower != hover_is_lower) ? (
      
      hover_c = best_c; hover_r = best_r; hover_is_lower = best_is_lower;
      
      (seq_active && allow_interrupt != 0) ? (
        queued_c = best_c;
        queued_r = best_r;
        queued_is_lower = best_is_lower;
        has_queued = 1;
      ) : (
        parse_seq();
        trigger_stop_all = 1; 
        
        (seq_length > 0 || has_initial) ? (
          seq_c = best_c;
          seq_r = best_r;
          seq_is_lower = best_is_lower;
          seq_idx = 0;
          seq_waiting = 1;
          seq_active = 1;
          seq_timer = 1;
        ) : (
          seq_active = 0;
          trigger_c = best_c;
          trigger_r = best_r;
          trigger_is_lower = best_is_lower;
          trigger_play = 1;
        );
      );
    );
  ) : (
    hover_c = -1; 
  );
);

released ? ( 
  (!seq_active || allow_interrupt == 0) ? trigger_release = 1; 
  hover_c = -1;
);

gfx_mouse_down = mouse_down;
last_cap = mouse_cap;

// *** ВВОД ТЕКСТА ***
c = 0;
while((c = gfx_getchar()) > 0) (
  input_active ? (
    (c == 8) ? ( 
      len = strlen(#seq);
      len > 0 ? ( str_delsub(#seq, len - 1, 1); );
    ) : (c == 13) ? ( 
      input_active = 0; 
    ) : (c == 27) ? ( 
      input_active = 0; 
    ) : (
      c2 = (c >= 'A' && c <= 'Z') ? c + 32 : c; 
      (c2 == 'l' || c2 == 'p' || c2 == 'r' || c2 == 'o' || c == '+' || c == '-' || c == ' ' || c == ',') ? (
        sprintf(#tmp, "%c", c); 
        strcat(#seq, #tmp);
      );
    );
  );
);
