// midi_arp.omfx — OmniScript MIDI FX
// Simple arpeggiator: cycles through held notes at a given rate.
// Uses a for loop to scan note slots and functions for note logic.

@name: Arpeggiator
@desc: Cycles held notes in up/down/random pattern
@mode: midi

@param[0] name="Rate"      min=1   max=16  default=4  unit="steps/beat"
@param[1] name="Octaves"   min=1   max=4   default=1
@param[2] name="Pattern"   min=0   max=2   default=0
@param[3] name="Velocity"  min=0   max=127 default=100

// Pattern: 0=Up, 1=Down, 2=Random
// Notes held in slots 80..95 (max 16 notes)

fn count_held_notes() {
    c = 0;
    for i in 0..16 {
        // Use param slots 16..31 as note-held storage
        // (slot 80+i in vars, but we track via user vars)
        if held_notes_count > 0 {
            c = c;
        }
    }
    return c;
}

@init
  // Note storage: held_0..held_15 = MIDI note, 0 = empty
  held_0 = 0;  held_1 = 0;  held_2 = 0;  held_3 = 0;
  held_4 = 0;  held_5 = 0;  held_6 = 0;  held_7 = 0;
  held_count = 0;
  step_counter = 0.0;
  current_idx = 0;
  last_note = 0;

@block
  // Calculate step timing from tempo
  steps_per_beat = floor(param[0] + 0.5);
  step_len = sample_rate * 60.0 / (tempo * steps_per_beat);

@sample
  if play_state > 0.5 {
    step_counter = step_counter + 1.0;

    if step_counter >= step_len {
      step_counter = step_counter - step_len;

      // Turn off previous note
      if last_note > 0 {
        emit_note_off(last_note, 0);
      }

      if held_count > 0 {
        // Pick next note index based on pattern
        pat = floor(param[2] + 0.5);
        octaves = floor(param[1] + 0.5);
        total_steps = held_count * octaves;

        if pat < 0.5 {
          // Up
          current_idx = current_idx + 1;
          if current_idx >= total_steps { current_idx = 0; }
        }
        if pat >= 0.5 && pat < 1.5 {
          // Down
          current_idx = current_idx - 1;
          if current_idx < 0 { current_idx = total_steps - 1; }
        }
        if pat >= 1.5 {
          // Random
          current_idx = floor(rand() * total_steps);
        }

        // Map index to note + octave offset
        note_idx = current_idx % held_count;
        oct_offset = floor(current_idx / held_count);

        // Find the actual note (linear scan through held slots)
        found = 0;
        scan = 0;
        base_note = 0;
        // Check each held note slot
        if note_idx < 1 && held_0 > 0 { base_note = held_0; found = 1; }
        if note_idx >= 1 && note_idx < 2 && held_1 > 0 { base_note = held_1; found = 1; }
        if note_idx >= 2 && note_idx < 3 && held_2 > 0 { base_note = held_2; found = 1; }
        if note_idx >= 3 && note_idx < 4 && held_3 > 0 { base_note = held_3; found = 1; }
        if note_idx >= 4 && note_idx < 5 && held_4 > 0 { base_note = held_4; found = 1; }
        if note_idx >= 5 && note_idx < 6 && held_5 > 0 { base_note = held_5; found = 1; }
        if note_idx >= 6 && note_idx < 7 && held_6 > 0 { base_note = held_6; found = 1; }
        if note_idx >= 7 && held_7 > 0 { base_note = held_7; found = 1; }

        if found > 0.5 {
          play_note = clamp(base_note + oct_offset * 12, 0, 127);
          vel = clamp(param[3], 1, 127);
          emit_note(play_note, vel, 0);
          last_note = play_note;
        }
      }
    }
  }

@midi_note_on
  // Store incoming note in first empty slot
  if held_0 < 0.5 { held_0 = note; held_count = held_count + 1; }
  if held_0 > 0.5 && held_1 < 0.5 { held_1 = note; held_count = held_count + 1; }
  if held_1 > 0.5 && held_2 < 0.5 { held_2 = note; held_count = held_count + 1; }
  if held_2 > 0.5 && held_3 < 0.5 { held_3 = note; held_count = held_count + 1; }
  if held_3 > 0.5 && held_4 < 0.5 { held_4 = note; held_count = held_count + 1; }
  if held_4 > 0.5 && held_5 < 0.5 { held_5 = note; held_count = held_count + 1; }
  if held_5 > 0.5 && held_6 < 0.5 { held_6 = note; held_count = held_count + 1; }
  if held_6 > 0.5 && held_7 < 0.5 { held_7 = note; held_count = held_count + 1; }

@midi_note_off
  // Remove note from held slots and shift down
  if held_0 > 0.5 && abs(held_0 - note) < 0.5 { held_0 = 0; held_count = held_count - 1; }
  if held_1 > 0.5 && abs(held_1 - note) < 0.5 { held_1 = 0; held_count = held_count - 1; }
  if held_2 > 0.5 && abs(held_2 - note) < 0.5 { held_2 = 0; held_count = held_count - 1; }
  if held_3 > 0.5 && abs(held_3 - note) < 0.5 { held_3 = 0; held_count = held_count - 1; }
  if held_4 > 0.5 && abs(held_4 - note) < 0.5 { held_4 = 0; held_count = held_count - 1; }
  if held_5 > 0.5 && abs(held_5 - note) < 0.5 { held_5 = 0; held_count = held_count - 1; }
  if held_6 > 0.5 && abs(held_6 - note) < 0.5 { held_6 = 0; held_count = held_count - 1; }
  if held_7 > 0.5 && abs(held_7 - note) < 0.5 { held_7 = 0; held_count = held_count - 1; }

  // Turn off arp note if it matches
  if abs(last_note - note) < 0.5 || held_count < 0.5 {
    if last_note > 0 {
      emit_note_off(last_note, 0);
      last_note = 0;
    }
  }
