// midi_probability.omfx — OmniScript MIDI FX
// Probability gate: every incoming note has a chance of being played.
// Perfect on drum loops to create evolving, semi-random variations
// while keeping the core groove.
//
// Velocity-weighted mode: louder notes are more likely to pass through,
// so accents survive and ghost notes dance.

@name: Probability Gate
@desc: Randomly pass or drop notes; keeps the groove evolving
@mode: midi

@param[0] name="Probability" min=0.0 max=1.0  default=0.75 unit="%"
@param[1] name="Vel-Weight"  min=0.0 max=1.0  default=0.5  unit="%"
@param[2] name="Low Key"     min=0   max=127  default=0
@param[3] name="High Key"    min=0   max=127  default=127
@param[4] name="Vel Scale"   min=0.0 max=2.0  default=1.0  unit="x"

// Keep track of which notes we let through so matching note-offs
// don't leak (or worse — stick).
// NOTE: `fn` defs must be at the TOP of the file (before any @section)
// so they're hoisted into the global scope and visible from every section.

// Returns 1 if we should remember `n` as a held note.
fn mark_held(n) {
    if held_0 < 0.5  { held_0 = n; return 1; }
    if held_1 < 0.5  { held_1 = n; return 1; }
    if held_2 < 0.5  { held_2 = n; return 1; }
    if held_3 < 0.5  { held_3 = n; return 1; }
    if held_4 < 0.5  { held_4 = n; return 1; }
    if held_5 < 0.5  { held_5 = n; return 1; }
    if held_6 < 0.5  { held_6 = n; return 1; }
    if held_7 < 0.5  { held_7 = n; return 1; }
    if held_8 < 0.5  { held_8 = n; return 1; }
    if held_9 < 0.5  { held_9 = n; return 1; }
    if held_10 < 0.5 { held_10 = n; return 1; }
    if held_11 < 0.5 { held_11 = n; return 1; }
    if held_12 < 0.5 { held_12 = n; return 1; }
    if held_13 < 0.5 { held_13 = n; return 1; }
    if held_14 < 0.5 { held_14 = n; return 1; }
    if held_15 < 0.5 { held_15 = n; return 1; }
    return 0;
}

fn clear_held(n) {
    if held_0 == n  { held_0 = 0; return 1; }
    if held_1 == n  { held_1 = 0; return 1; }
    if held_2 == n  { held_2 = 0; return 1; }
    if held_3 == n  { held_3 = 0; return 1; }
    if held_4 == n  { held_4 = 0; return 1; }
    if held_5 == n  { held_5 = 0; return 1; }
    if held_6 == n  { held_6 = 0; return 1; }
    if held_7 == n  { held_7 = 0; return 1; }
    if held_8 == n  { held_8 = 0; return 1; }
    if held_9 == n  { held_9 = 0; return 1; }
    if held_10 == n { held_10 = 0; return 1; }
    if held_11 == n { held_11 = 0; return 1; }
    if held_12 == n { held_12 = 0; return 1; }
    if held_13 == n { held_13 = 0; return 1; }
    if held_14 == n { held_14 = 0; return 1; }
    if held_15 == n { held_15 = 0; return 1; }
    return 0;
}

@init
  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_8=0; held_9=0; held_10=0; held_11=0;
  held_12=0; held_13=0; held_14=0; held_15=0;

@midi_note_on
  // Range gate first — notes outside the zone pass through untouched
  if note < param[2] || note > param[3] {
    mark_held(note);
    new_vel = clamp(velocity * param[4], 1, 127);
    emit_note(note, new_vel, sample_offset);
  } else {
    // Compute effective probability: base + velocity-weighted boost
    base_p = param[0];
    vel_norm = velocity / 127.0;
    p = base_p * (1.0 - param[1]) + vel_norm * param[1];
    if rand() < p {
      mark_held(note);
      new_vel = clamp(velocity * param[4], 1, 127);
      emit_note(note, new_vel, sample_offset);
    }
  }

@midi_note_off
  // Only emit note-off if we originally let the note through
  if clear_held(note) > 0.5 {
    emit_note_off(note, sample_offset);
  }
