arpeggiator

MIDI arpeggiator. Receives MIDI notes on the channel set with the attribute MIDIchannel (0=all channels). Arpeggio directions: Up, down, up/down, random and play order. Configurable note length and velocity (velocity setting 0 uses the velocity of the played note). "Hold mode " to latch played notes (MIDI sustain pedal also triggers hold). Needs to be clocked from an LFO or MIDI clock input to produce notes. Semitone transpose input. Can add up to 3 octaves to the arpeggiated notes. "Retrigger " checkbox: If the notelength is longer than the clock interval, retriggers the gate every time a new arpeggio note is played. "Ignore " inlet: incoming MIDI notes are ignored as long as input is "high". Special feature: If "ignore" is on and "hold" is re-enabled, the last used latched notes are loaded again.
Author: Peter Witzel
License: CC0
Github: cpwitz/midi/arpeggiator.axo

Inlets

bool32.rising clock

bool32 hold arpeggio

bool32 double stroke each arpeggiated note

bool32 don't receive midi notes!

int32.positive arp direction (0-4)

int32.positive number of addition octaves (0-3)

int32.bipolar transpose semitones

frac32.bipolar modulate note length

Outlets

bool32 gate

int32.positive number of notes in arpeggio

frac32.bipolar pitch

frac32.positive velocity

Parameters

bool32.tgl retrigger gate if notelength is longer than clock input

frac32.u.map 0=as played

int32.hradio ↑, ↓, ↕, rand, order

int32.hradio octaves

frac32.s.map.klineartime.exp notelength

Attributes

spinner arpeggio input midi channel (0=all channels)

Displays

bool32 holding

int32.label notes

Declaration
int8_t notes[128];
int8_t notes_ord[128];
int8_t released_on_hold[128];
int8_t latched_notes[256]; // holdes last latched and ordered notes
int notecount;
int ntrigclock;
int ngate;
int highest_note;
int lowest_note;
int order_pos;
int octave;
int moveup;
int holding;
int sustainpedal;
int dehold_lock;
int ignored;
int32_t lastnote;
int32_t gateval;
int32_t outpitch;
uint32_t outvelo;
int secondclock;
Init
outpitch = 0;
outvelo = 0;
notecount = 0;
ntrigclock = 0;
lastnote = 0;
gateval = 0;
ngate = 0;
highest_note = 0;
lowest_note = 0;
order_pos = 0;
octave = 0;
moveup = 1;
holding = 0;
sustainpedal = 0;
dehold_lock = 0;
secondclock = 0;
ignored = 0;

for (int i = 0; i < 128; i++) {
  notes[i] = 0;
  notes_ord[i] = 0;
  released_on_hold[i] = 0;
  latched_notes[i] = latched_notes[i + 128] = 0;
}
Control Rate
if ((inlet_clock > 0) && !ntrigclock) {
  // clock received
  if (notecount > 0) {
    if (inlet_double > 0 && secondclock > 0) {
      ngate = 1;
    } else {
      int32_t playnote = 0;
      int32_t nextnote;
      int nextoctave = octave;
      int direction = (param_direction + inlet_direction) % 5;
      int number_octaves = (param_octaves + inlet_octaves) % 4;
      if (direction == 0) {
        // up
        nextnote = lastnote;
        do {
          nextnote = (nextnote + 1) % 128;
          if (notes[nextnote] != 0) {
            playnote = nextnote;
            if (playnote == highest_note) {
              nextoctave = (octave + 1) % (number_octaves + 1);
            }
            break;
          }
        } while (nextnote != lastnote);
      }
      if (direction == 1) {
        // down
        nextnote = lastnote;
        do {
          nextnote = nextnote - 1;
          if (nextnote < 0)
            nextnote = 127;
          if (notes[nextnote] != 0) {
            playnote = nextnote;
            if (playnote == lowest_note) {
              nextoctave = octave - 1;
              if (nextoctave < 0) {
                nextoctave = number_octaves;
              };
            }
            break;
          }
        } while (nextnote != lastnote);
      }
      if (direction == 2) {
        // up/down
        nextnote = lastnote;
        if (moveup) {
          do {
            nextnote = (nextnote + 1) % 128;
            if (notes[nextnote] != 0) {
              playnote = nextnote;
              if (playnote == highest_note) {
                nextoctave = (octave + 1) % (number_octaves + 1);
                if (nextoctave == 0) {
                  nextoctave = (number_octaves > 0 && notecount == 1)
                                   ? octave - 1
                                   : octave;
                  moveup = 0;
                }
              }
              break;
            }
          } while (nextnote != lastnote);

        } else {
          do {
            nextnote = nextnote - 1;
            if (nextnote < 0)
              nextnote = 127;
            if (notes[nextnote] != 0) {
              playnote = nextnote;
              if (playnote == lowest_note) {
                nextoctave = octave - 1;
                if (nextoctave < 0) {
                  nextoctave = (number_octaves > 0 && notecount == 1) ? 1 : 0;
                  moveup = 1;
                };
              }
              break;
            }
          } while (nextnote != lastnote);
        }
      }
      if (direction == 3) {
        // rand
        int rand = GenerateRandomNumber();
        int randpos = ((rand < 0 ? -rand + 1 : rand) % (notecount));
        playnote = notes_ord[randpos];
        rand = GenerateRandomNumber();
        nextoctave = ((rand < 0 ? -rand + 1 : rand) % (number_octaves + 1));
      }
      if (direction == 4) {
        // order
        playnote = notes_ord[order_pos];
        if (order_pos == notecount - 1) {
          nextoctave = (octave + 1) % (number_octaves + 1);
        }
        order_pos = (order_pos + 1) % notecount;
      }
      if (playnote > 0) {
        outpitch = (playnote + inlet_transpose + 12 * octave - 64) << 21;
        // LogTextMessage("Triggering #%i %i", playnote, outpitch);
        outvelo = param_velocity > 0 ? param_velocity : (notes[playnote] << 20);
        ngate = 1;
        lastnote = playnote;
        octave = nextoctave;
      }
    }
    secondclock = secondclock == 0 ? 1 : 0;
  }
  ntrigclock = 1;
}
if (!(inlet_clock > 0) && ntrigclock) {
  ntrigclock = 0;
}

// create gate pulse
if (ngate > 0) {
  // check if gate is closed
  if (gateval <= 0 || param_retrigger == 0) {
    gateval = 1 << 30;
    ngate = 0;
  } else {
    // retrigger
    gateval = 0;
  }
}
if (gateval > 0) {
  int32_t t;
  MTOF(-(param_notelength + inlet_notelength), t);
  gateval -= t >> 3;
  if (gateval <= 0)
    outlet_gate = 0;
  else
    outlet_gate = 1;
} else
  outlet_gate = 0;

outlet_pitch = outpitch;
outlet_velocity = outvelo;
disp_notes = notecount;
outlet_notes = notecount;
ignored = inlet_ignore;
int new_holding = sustainpedal | inlet_hold;
if (holding == 0 && new_holding > 0 && ignored > 0 && notecount == 0) {
  dehold_lock = 1;
  // restore last latch if no notes are pressed and ignore is on
  order_pos = 0;
  for (int i = 0; i < 128; i++) {
    notes[i] = latched_notes[i];
    notes_ord[i] = latched_notes[i + 128];
    if (notes_ord[i] > 0) {
      order_pos++;
    }
    if (notes[i] > 0) {
      notecount++;
      released_on_hold[i] = 1;
    }
  }
  octave = 0;
  moveup = 1;
  lastnote = notecount > 0 ? notecount - 1 : 0;
  secondclock = 0;
  dehold_lock = 0;
}
if (holding == 1 && new_holding == 0) {
  dehold_lock = 1;
  int store_hold = notecount;
  // here comes nasty copy&paste code from the midi section :(
  for (int r = 0; r < 128; r++) {
    // store last latched
    if (store_hold > 0) {
      latched_notes[r] = notes[r];
      latched_notes[r + 128] = notes_ord[r];
    }
    if (released_on_hold[r] == 1) {
      released_on_hold[r] = 0;
      notes[r] = 0;
      notecount = notecount > 0 ? notecount - 1 : 0;
      // find in play order array and remove it
      int orderpos = -1;
      for (int i = 0; i < 128; i++) {
        if (notes_ord[i] == r) {
          orderpos = i;
          break;
        }
      }
      if (orderpos > -1) {
        for (int i = 0; i < notecount - orderpos; i++) {
          notes_ord[i + orderpos] = notes_ord[i + orderpos + 1];
        }
        notes_ord[notecount + 1] = 0;
      }
    }
  }
  for (int i = 0; i < 128; i++) {
    if (notes[i] > 0) {
      lowest_note = i;
      break;
    }
  }
  for (int i = 127; i >= 0; i--) {
    if (notes[i] > 0) {
      highest_note = i;
      break;
    }
  }
  dehold_lock = 0;
}
holding = new_holding;
disp_holding = holding;
Midi Handler
int channel = (status & 0xf) + 1;
int cmd = (status & 0x70) >> 4;

if ((channel != attr_MIDIchannel && attr_MIDIchannel > 0) || dehold_lock) {
  return;
}

bool resetStartNote = false;

// some midi devices send note on with velocity 0 instead of note off
if (cmd == 1 && data2 == 0) {
  cmd = 0;
}

if (cmd == 1 && ignored == 0) {
  // note on
  // LogTextMessage("Pressed: %i (%i)", data1, data2);
  if (notecount == 0) {
    resetStartNote = true;
  }
  if (notes[data1] == 0) {
    notecount++;
    notes_ord[notecount - 1] = data1;
  }
  notes[data1] = data2;
  released_on_hold[data1] = 0;
} else if (cmd == 0 && ignored == 0) {
  // note off
  // LogTextMessage("Release: %i", data1);
  if (holding) {
    released_on_hold[data1] = 1;
  } else {
    if (notes[data1] != 0) {
      notecount = notecount > 0 ? notecount - 1 : 0;
      notes[data1] = 0;
      // find in play order array and remove it
      int orderpos = -1;
      for (int i = 0; i < 128; i++) {
        if (notes_ord[i] == data1) {
          orderpos = i;
          break;
        }
      }
      if (orderpos > -1) {
        for (int i = 0; i < notecount - orderpos; i++) {
          notes_ord[i + orderpos] = notes_ord[i + orderpos + 1];
        }
        notes_ord[notecount + 1] = 0;
      }
    }
  }
} else if (cmd == 3 && data1 == 64 && ignored == 0) {
  sustainpedal = data2 > 0 ? 1 : 0;
}

if (ignored == 0 && (cmd == 0 || cmd == 1)) {
  // find highest and lowest note
  for (int i = 0; i < 128; i++) {
    if (notes[i] > 0) {
      lowest_note = i;
      break;
    }
  }
  for (int i = 127; i >= 0; i--) {
    if (notes[i] > 0) {
      highest_note = i;
      break;
    }
  }
  if (resetStartNote) {
    order_pos = 0;
    octave = 0;
    moveup = 1;
    lastnote = 0;
  }
  /*// debug out
  for (int i=0;i<notecount;i++) {
          LogTextMessage("%i = #%i", i+1, notes_ord[i]);
  } */
}

Privacy

© 2024 Zrna Research