module main
author unknown
version 1 0 
description ''

script 560 43 {
whenStarted
'MIDI_select MIDI instrument' (MIDI_instrument 'piano' 1) 1
forever {
  'rhy_wait until next' (('rhy_whole duration') * 2)
  repeat (random 2 6) {
    'MIDI_play MIDI note' (scales_chord 'major' 60) ('rhy_eighth duration') 1 75
  }
  'rhy_wait until next' ('rhy_whole duration')
  repeat (random 2 6) {
    'MIDI_play MIDI note' (scales_chord 'major' 65) ('rhy_eighth duration') 1 75
  }
}
}

script 48 47 {
whenStarted
forever {
  'rhy_wait until next' ('rhy_half duration')
  'MIDI_play MIDI drum' 'Closed Hi-Hat' ('rhy_eighth duration') 127
  'MIDI_play MIDI drum' 'Pedal Hi-Hat' ('rhy_eighth duration') 127
  'MIDI_play MIDI drum' 'Open Hi-Hat' ('rhy_quarter duration') 127
}
}

script 49 295 {
whenStarted
forever {
  'rhy_wait until next' (('rhy_whole duration') * 2)
  'MIDI_play MIDI drum' 'Acoustic Bass Drum' ('rhy_quarter duration') 127
  'rhy_wait until next' ('rhy_half duration')
  'MIDI_play MIDI drum' 'Acoustic Snare' ('rhy_quarter duration') 127
  'rhy_wait until next' ('rhy_half duration')
  'MIDI_play MIDI drum' 'Acoustic Bass Drum' ('rhy_quarter duration') 80
  'MIDI_play MIDI drum' 'Acoustic Bass Drum' ('rhy_quarter duration') 127
  'MIDI_play MIDI drum' 'Acoustic Snare' ('rhy_quarter duration') 127
}
}

script 560 447 {
whenStarted
'MIDI_select MIDI instrument' (MIDI_instrument 'chromatic percussion' 1) 3
forever {
  'rhy_wait until next' (('rhy_whole duration') * 4)
  scales_improv (scales_scale 'ionian' 60) 2 '4,4,8' 'MIDI channel' 3 127
}
}

script 51 677 {
whenStarted
'MIDI_select MIDI instrument' (MIDI_instrument 'bass' 1) 2
forever {
  'rhy_wait until next' (('rhy_whole duration') * 2)
  scales_arpeggiate (scales_chord 'major' 36) '1,3,4,2,1' '4,4,4,8,8' 127 'MIDI channel' 2
  scales_arpeggiate (scales_chord 'major' 41) '2,4,3,2,2' '4,4,4,8,8' 127 'MIDI channel' 2
}
}


module MIDI Output
author MicroBlocks
version 1 7 
choices druminst 'Acoustic Bass Drum' 'Bass Drum 1' 'Side Stick' 'Acoustic Snare' 'Hand Clap' 'Electric Snare' 'Low Floor Tom' 'Closed Hi-Hat' 'High Floor Tom' 'Pedal Hi-Hat' 'Low Tom' 'Open Hi-Hat' 'Low-Mid Tom' 'Hi-Mid Tom' 'Crash Cymbal 1' 'High Tom' 'Ride Cymbal 1' 'Chinese Cymbal' 'Ride Bell' Tambourine 'Splash Cymbal' Cowbell 'Crash Cymbal 2' Vibraslap 'Ride Cymbal 2' 'Hi Bongo' 'Low Bongo' 'Mute Hi Conga' 'Open Hi Conga' 'Low Conga' 'High Timbale' 'Low Timbale' 'High Agogo' 'Low Agogo' Cabasa Maracas 'Short Whistle' 'Long Whistle' 'Short Guiro' 'Long Guiro' Claves 'Hi Wood Block' 'Low Wood Block' 'Mute Cuica' 'Open Cuica' 'Mute Triangle' 'Open Triangle' 
choices _midiPin USB 
choices instrument piano 'chromatic percussion' organ guitar bass strings ensemble brass reed pipe 'synth lead' 'synth pad' 'synth SFX' ethnic percussive SFX 
choices drumkits standard room power electronic 'TR-808' jazz brush orchestra SFX 'CM-64/CM-32L' 
choices controls 'bank select 1' 'bank select 2' modulation 'portamento time' volume pan expression hold portamento sostenuto soft resonance 'release time' 'attack time' cutoff 'decay time' 'vibrato rate' 'vibrato depth' 'vibrato delay' 'portamento control' 'effect 1' 'effect 2' 
description 'Control MIDI devices. By default, it interfaces the hardware serial port, but it also supports software serial and USB MIDI.

Software Serial Ports
---------------------
You can specify a software serial port using the `set MIDI pin` block. On most boards, however, the default serial pin is going to be pin 1, so there is no need to use the `set MIDI pin` block. Connect pin 5 of the MIDI DIN connector to the serial pin, and pin 4 of the MIDI DIN connector to 3.3v or 5v.

USB MIDI
--------
Alternatively, if you want to use the MIDI library to control a USB MIDI device -or a virtual synth running in your PC, even while MicroBlocks is running- you can do so by setting the `use USB MIDI` flag to true in the `set MIDI pin` block.
'
variables _MIDI_initialized _MIDI_pin _MIDI_drums 

  spec ' ' 'MIDI_play MIDI note' 'play MIDI note _ for _ ms on channel _ volume _' 'auto auto auto auto' 60 500 1 127
  spec ' ' 'MIDI_send note' 'set MIDI note _ to _ on channel _ volume _' 'auto bool auto auto' 60 true 1 127
  space
  spec 'r' 'MIDI_note to MIDI' 'note _ octave _ to MIDI key' 'auto auto' 'c' 0
  space
  spec ' ' 'MIDI_select MIDI instrument' 'select MIDI instrument _ for channel _' 'auto auto' '10' 2
  spec 'r' 'MIDI_instrument' 'instrument _ number _' 'menu.instrument menu.range:1-8' 'piano' 1
  spec ' ' 'MIDI_select MIDI drum kit' 'select MIDI drum kit _' 'menu.drumkits' 'brush'
  space
  spec ' ' 'MIDI_play MIDI drum' 'play MIDI drum _ for _ ms volume _' 'menu.druminst num num' 'Acoustic Snare' 100 80
  spec 'r' 'MIDI_drum note' 'drum _' 'menu.druminst' 'Ride Cymbal 2'
  space
  spec ' ' 'MIDI_change MIDI control' 'change MIDI control _ on channel _ to _' 'menu.controls auto auto' 'modulation' 1 127
  spec ' ' 'MIDI_pitch bend channel' 'pitch bend MIDI channel _ to _ %' 'auto auto' 1 -50
  space
  spec ' ' 'MIDI_send MIDI reset' 'send MIDI reset'
  space
  spec ' ' 'MIDI_send MIDI start playing' 'send MIDI start playing'
  spec ' ' 'MIDI_send MIDI stop playing' 'send MIDI stop playing'
  spec ' ' 'MIDI_send MIDI continue playing' 'send MIDI continue playing'
  space
  spec ' ' 'MIDI_set MIDI pin' 'set MIDI signal to pin _' 'auto._midiPin' 8
  space
  spec ' ' '_MIDI init' '_MIDI init'
  space
  spec ' ' '_MIDI command' '_MIDI command _ channel _ arg1 _ : arg2 _' 'num num num num' 9 1 60 127
  spec ' ' '_MIDI send bytes' '_MIDI send bytes _' 'auto' ''
  space
  spec 'r' '_trimmedLowercase' '_trimmedLowercase _' 'str' 'A. b C...'

to 'MIDI_change MIDI control' control channel value {
  local 'controlChangeCmd' 11
  local 'controller' (at ('[data:find]' control ('[data:makeList]' 'bank select 1' 'bank select 2' 'modulation' 'portamento time' 'volume' 'pan' 'expression' 'hold' 'portamento' 'sostenuto' 'soft' 'resonance' 'release time' 'attack time' 'cutoff' 'decay time' 'vibrato rate' 'vibrato depth' 'vibrato delay' 'portamento control' 'effect 1' 'effect 2')) ('[data:makeList]' 0 32 1 5 7 10 11 64 65 66 67 71 72 73 74 75 76 77 78 84 91 93))
  '_MIDI command' controlChangeCmd channel controller value
}

to 'MIDI_drum note' instrument {
  if (not (isType _MIDI_drums 'list')) {_MIDI_drums = ('[data:split]' 'Acoustic Bass Drum,Bass Drum 1,Side Stick,Acoustic Snare,Hand Clap,Electric Snare,Low Floor Tom,Closed Hi-Hat,High Floor Tom,Pedal Hi-Hat,Low Tom,Open Hi-Hat,Low-Mid Tom,Hi-Mid Tom,Crash Cymbal 1,High Tom,Ride Cymbal 1,Chinese Cymbal,Ride Bell,Tambourine,Splash Cymbal,Cowbell,Crash Cymbal 2,Vibraslap,Ride Cymbal 2,Hi Bongo,Low Bongo,Mute Hi Conga,Open Hi Conga,Low Conga,High Timbale,Low Timbale,High Agogo,Low Agogo,Cabasa,Maracas,Short Whistle,Long Whistle,Short Guiro,Long Guiro,Claves,Hi Wood Block,Low Wood Block,Mute Cuica,Open Cuica,Mute Triangle,Open Triangle' ',')}
  return (('[data:find]' instrument _MIDI_drums) + 34)
}

to MIDI_instrument instrument number {
  local 'categories' ('[data:split]' 'piano,chromatic percussion,organ,guitar,bass,strings,ensemble,brass,reed,pipe,synth lead,synth pad,synth SFX,ethnic,percussive,SFX' ',')
  return (((('[data:find]' instrument categories) - 1) * 8) + number)
}

to 'MIDI_note to MIDI' note octave {
  local 'noteNames' ('[data:makeList]' 'c' 'c#' 'd' 'd#' 'e' 'f' 'f#' 'g' 'g#' 'a' 'a#' 'b' 'c_' 'db' 'd_' 'eb' 'e_' 'e#' 'f_' 'gb' 'g_' 'ab' 'a_' 'bb' 'b_' 'b#')
  local 'midiKeys' ('[data:makeList]' 60 61 62 63 64 65 66 67 68 69 70 71 59 61 61 63 63 65 64 66 66 68 68 70 70 72)
  return ((at ('[data:find]' ('_trimmedLowercase' note) noteNames) midiKeys) + (octave * 12))
}

to 'MIDI_pitch bend channel' channel percent {
  local 'pitchBendCmd' 14
  local 'bendMSB' (maximum 0 (minimum (((64 * percent) / 100) + 64) 127))
  '_MIDI command' pitchBendCmd channel 0 bendMSB
}

to 'MIDI_play MIDI drum' drumName duration volume {
  if (isType drumName 'number') {
    'MIDI_send note' drumName true 10 volume
  } else {
    'MIDI_send note' ('MIDI_drum note' drumName) true 10 volume
  }
  waitMillis (duration - 3)
}

to 'MIDI_play MIDI note' 'midi note' duration channel volume {
  'MIDI_send note' (v 'midi note') true channel volume
  waitMillis (duration - 3)
  'MIDI_send note' (v 'midi note') false channel 0
}

to 'MIDI_select MIDI drum kit' drumkit {
  local 'programChangeCmd' 12
  '_MIDI command' programChangeCmd 10 ((at ('[data:find]' drumkit ('[data:split]' 'standard,room,power,electronic,TR-808,jazz,brush,orchestra,SFX,CM-64/CM-32L' ',')) ('[data:split]' '1,9,17,25,26,33,41,49,57,128' ',')) - 1)
}

to 'MIDI_select MIDI instrument' 'instrument number' channel {
  local 'programChangeCmd' 12
  '_MIDI command' programChangeCmd channel ((v 'instrument number') - 1)
}

to 'MIDI_send MIDI continue playing' {
  '_MIDI send bytes' (hexToInt 'FB')
}

to 'MIDI_send MIDI reset' {
  '_MIDI send bytes' (hexToInt 'FF')
  '_MIDI send bytes' ('[data:makeList]' (hexToInt 'F0') (hexToInt '7E') (hexToInt '7F') (hexToInt '09') (hexToInt '01') (hexToInt 'F7'))
  comment 'Turn off all notes on all channels'
  for chan 16 {
    for key 128 {
      'MIDI_send note' (key - 1) false chan 0
    }
  }
}

to 'MIDI_send MIDI start playing' {
  '_MIDI send bytes' (hexToInt 'FA')
}

to 'MIDI_send MIDI stop playing' {
  '_MIDI send bytes' (hexToInt 'FC')
}

to 'MIDI_send note' 'midi note' on/off channel volume {
  if (isType (v 'midi note') 'list') {
    for note (v 'midi note') {
      'MIDI_send note' note on/off channel volume
    }
  } else {
    local 'noteOnMsg' 9
    if on/off {
      '_MIDI command' noteOnMsg channel (v 'midi note') volume
    } else {
      '_MIDI command' noteOnMsg channel (v 'midi note') 0
    }
  }
}

to 'MIDI_set MIDI pin' pin {
  '[serial:close]'
  if ('USB' == pin) {
    _MIDI_pin = 'USB'
  } else {
    if (isType pin 'list') {
      _MIDI_pin = pin
    } else {
      _MIDI_pin = ('[data:makeList]' ('[data:makeList]' pin ('[data:range]' 1 16)))
    }
  }
  _MIDI_initialized = (booleanConstant false)
  '_MIDI init'
}

to '_MIDI command' cmd channel arg1 arg2 {
  '_MIDI init'
  local 'cmdByte' ((cmd << 4) | ((channel - 1) & 15))
  local 'oneArgByte' ((pushArgCount) < 4)
  if (_MIDI_pin == 0) {
    if oneArgByte {
      '[serial:write]' ('[data:makeList]' cmdByte arg1)
    } else {
      '[serial:write]' ('[data:makeList]' cmdByte arg1 arg2)
    }
  } (_MIDI_pin == 'USB') {
    if oneArgByte {
      '[serial:midiSend]' cmdByte arg1
    } else {
      '[serial:midiSend]' cmdByte arg1 arg2
    }
  } else {
    for map _MIDI_pin {
      if (('[data:find]' channel (at 2 map)) > 0) {
        '[io:softWriteByte]' cmdByte (at 1 map) 31250
        '[io:softWriteByte]' arg1 (at 1 map) 31250
        if (not oneArgByte) {
          '[io:softWriteByte]' arg2 (at 1 map) 31250
        }
      }
    }
  }
}

to '_MIDI init' {
  if (not _MIDI_initialized) {
    if (_MIDI_pin == 0) {
      '[serial:open]' 31250
    } else {
      '[serial:close]'
    }
    _MIDI_initialized = (booleanConstant true)
  }
}

to '_MIDI send bytes' byteList {
  '_MIDI init'
  if (isType byteList 'number') {
    byteList = ('[data:makeList]' byteList)
  }
  for byte byteList {
    if (_MIDI_pin == 0) {
      '[serial:write]' byte
    } (_MIDI_pin == 'USB') {
      '[serial:midiSend]' byte
    } else {
      for map _MIDI_pin {
        '[io:softWriteByte]' byte (at 1 map) 31250
      }
    }
  }
}

to '_trimmedLowercase' s {
  comment 'Return a copy of the given string without whitespace
or periods and all lowercase.'
  local 'result' (newList (size s))
  '[data:delete]' 'all' result
  for i (size s) {
    local 'ch' ('[data:unicodeAt]' i s)
    if (and (ch > 32) (ch != 46)) {
      if (and (65 <= ch) (ch <= 90)) {ch = (ch + 32)}
      '[data:addLast]' ch result
    }
  }
  return ('[data:unicodeString]' result)
}


module Rhythm Control
author MicroBlocks
version 2 0 
description 'Keeps time according to a tempo and time signature. Useful to create musical patterns.

Includes abstractions for note durations, bars and beats.
'
variables _rhy_tempo _rhy_bars _rhy_looping _rhy_beat_duration _rhy_measures _rhy_tick 

  spec ' ' 'rhy_set tempo' 'set tempo to _ bpm at _ / _ signature' 'auto auto auto' 250 4 4
  space
  spec ' ' 'rhy_start counting' 'start counting _ bars' 'auto' 16
  spec ' ' 'rhy_stop counting' 'stop counting'
  spec ' ' 'rhy_wait until next' 'wait until next _' 'auto' ''
  space
  spec 'r' 'rhy_current beat' 'current beat'
  spec 'r' 'rhy_current bar' 'current bar'
  space
  spec 'r' 'rhy_whole duration' '¹⁄₁'
  spec 'r' 'rhy_half duration' '½'
  spec 'r' 'rhy_quarter duration' '¼'
  spec 'r' 'rhy_eighth duration' '⅛'
  spec 'r' 'rhy_sixteenth duration' '¹⁄₁₆'
  space
  spec 'r' 'rhy_dotted' '_ .' 'auto' ''
  spec 'r' 'rhy_triplet' '‿³ _' 'auto' ''
  space
  spec ' ' '_rhy loop' '_rhy loop'
  spec 'r' '_rhy duration of note value' '_rhy duration of note value _' 'auto' 4
  spec 'r' '_rhy 2 ^' '_rhy 2 ^ _' 'auto' '10'
  spec 'r' '_rhy between' '_rhy _ between _ and _' 'auto auto auto' '10' '10' '10'

to '_rhy 2 ^' n {
  local 'result' 1
  repeat n {
    result = (result * 2)
  }
  return result
}

to '_rhy between' foo a b {
  return (and (foo >= a) (foo <= b))
}

to '_rhy duration of note value' 'note value' {
  for i (v 'note value') {
    if (('_rhy 2 ^' i) == (v 'note value')) {return (at i ('[data:makeList]' ('rhy_half duration') ('rhy_quarter duration') ('rhy_eighth duration') ('rhy_sixteenth duration')))}
  }
  return ('rhy_whole duration')
}

to '_rhy loop' {
  repeatUntil (not _rhy_looping) {
    _rhy_tick = 0
    for bar _rhy_bars {
      for beat _rhy_measures {
        if (not _rhy_looping) {stopTask}
        'rhy_wait until next' _rhy_beat_duration
        _rhy_tick += 1
        sayIt _rhy_tick
      }
    }
  }
}

to 'rhy_current bar' {
  return ((_rhy_tick / _rhy_measures) + 1)
}

to 'rhy_current beat' {
  return ((_rhy_tick % _rhy_measures) + 1)
}

to rhy_dotted duration {
  return (duration + (duration / 2))
}

to 'rhy_eighth duration' {
  if (_rhy_tempo == 0) {_rhy_tempo = 240}
  return (30000 / _rhy_tempo)
}

to 'rhy_half duration' {
  if (_rhy_tempo == 0) {_rhy_tempo = 240}
  return (120000 / _rhy_tempo)
}

to 'rhy_quarter duration' {
  if (_rhy_tempo == 0) {_rhy_tempo = 240}
  return (60000 / _rhy_tempo)
}

to 'rhy_set tempo' tempo measures 'note value' {
  _rhy_tempo = tempo
  _rhy_beat_duration = ('_rhy duration of note value' (v 'note value'))
  _rhy_measures = measures
}

to 'rhy_sixteenth duration' {
  if (_rhy_tempo == 0) {_rhy_tempo = 240}
  return (15000 / _rhy_tempo)
}

to 'rhy_start counting' bars {
  if (_rhy_measures == 0) {'rhy_set tempo' 240 4 4}
  _rhy_bars = bars
  _rhy_looping = (booleanConstant true)
  sendBroadcast '_rhy loop'
}

to 'rhy_stop counting' {
  _rhy_looping = (booleanConstant false)
}

to rhy_triplet duration {
  return ((duration * 2) / 3)
}

to 'rhy_wait until next' duration {
  waitUntil ('_rhy between' ((timer) % duration) 0 3)
}

to 'rhy_whole duration' {
  if (_rhy_tempo == 0) {_rhy_tempo = 240}
  return (240000 / _rhy_tempo)
}


module 'Scales & Chords' Data
author MicroBlocks
version 2 1 
depends Rhythm 
choices sound_dst 'MIDI channel' 'tone pin' 
choices scales ionian dorian phrygian lydian mixolydian aeolian locrian 
choices pentatonics major minor 
choices chords major minor diminished 'major 7th' 'minor 7th' Maj7 
description 'Lists of relative semitone intervals for all Western diatonic scales plus the major and minor pentatonic scales.

It also includes intervals for the most common chords and progressions, and an abstraction of the circle of fifths.
'
variables _scales_degrees _scales_circle5ths 

  spec 'r' 'scales_scale' '_ scale root _ : steps _' 'menu.scales num num' 'ionian' 60 7
  spec 'r' 'scales_pentatonic scale' 'pentatonic _ scale root _ : steps _' 'menu.pentatonics num num' 'major' 60 7
  space
  spec 'r' 'scales_chord' '_ chord root _ : steps _' 'menu.chords num num' 'major' 60 4
  spec 'r' 'scales_chord with roman numeral' '_ chord for root _ : steps _' 'auto auto auto' 'IV' 60 4
  space
  spec ' ' 'scales_arpeggiate' 'arpeggiate _ in order _ duration _ and volume _ on _ _' 'auto str str num menu.sound_dst auto' 'aListOfNotes' '1,3,4,2' '4,8' 100 'MIDI channel' 1
  spec ' ' 'scales_improv' 'improvise over _ for _ bars with durations _ on _ _ volume _' 'auto auto str menu.sound_dst auto auto' 'aListOfNotes' '10' '4,4,8' 'MIDI channel' 1 100
  space
  spec 'r' 'scales_transposed' '_ transposed _ semitones' 'auto auto' 60 12
  space
  spec 'r' 'scales_progression' 'chord progression _ with root _ : and _ steps per chord' 'auto auto auto' 'I,IV,viiº,iii,vi,ii,V,I' 60 3
  spec 'r' 'scales_circle rotation' 'rotation _ of circle of 5ths _ M _ m _ º' 'auto str str str' 0 '-1,0,1' '-1,0,1' '0'
  space
  spec 'r' '_scales intervals' '_scales intervals _  with root _ and _ steps' 'auto auto auto' 'aList' 60 10
  spec 'r' '_scales degrees' '_scales degrees'
  spec ' ' '_scales_init_circle' '_scales_init_circle'
  spec 'r' '_scales circular item' '_scales circular item _ of _' 'auto auto' '10' ''
  spec 'r' '_scales parsed durations' '_scales parsed durations _' 'str' '4,4,8'

to '_scales circular item' n list {
  if (n < 1) {
    return (at ((size list) - ((absoluteValue n) % (size list))) list)
  } else {
    return (at (((n - 1) % (size list)) + 1) list)
  }
}

to '_scales degrees' {
  if (_scales_degrees == 0) {_scales_degrees = ('[data:makeList]' 'I' 'II' 'III' 'IV' 'V' 'VI' 'VII' 'i' 'ii' 'iii' 'iv' 'v' 'vi' 'vii')}
  return _scales_degrees
}

to '_scales intervals' intervals root steps {
  local 'notes' ('[data:makeList]')
  for step steps {
    '[data:addLast]' ((at (((step - 1) % (size intervals)) + 1) intervals) + (root + (12 * ((step - 1) / (size intervals))))) notes
  }
  return notes
}

to '_scales parsed durations' 'duration string' {
  local 'duration' ('[data:split]' (v 'duration string') ',')
  for idx (size duration) {
    if ((at idx duration) < 33) {atPut idx duration ('_rhy duration of note value' ('[data:convertType]' (at idx duration) 'number'))}
  }
  return duration
}

to '_scales_init_circle' {
  if (_scales_circle5ths == 0) {_scales_circle5ths = ('[data:makeList]' ('[data:makeList]' 'I' 'V' 'II' 'VI' 'III' 'VII' 'bV' 'bII' 'bVI' 'bIII' 'bVII' 'IV') ('[data:makeList]' 'vi' 'iii' 'vii' 'bv' 'bii' 'bvi' 'biii' 'bvii' 'iv' 'i' 'v' 'ii') ('[data:makeList]' 'VIIº' 'bVº' 'bIIº' 'bVIº' 'bIIIº' 'bVIIº' 'IVº' 'Iº' 'Vº' 'IIº' 'VIº' 'IIIº'))}
}

to scales_arpeggiate 'note list' order duration volume destination channel/pin {
  if (order == '') {
    order = ('[data:range]' 1 (size (v 'note list')))
  } (isType order 'string') {
    order = ('[data:split]' order ',')
  }
  if (isType duration 'string') {
    duration = ('_scales parsed durations' duration)
  }
  for i (size order) {
    local 'start' (millisOp)
    if (isType duration 'list') {
      local 'dur' ('_scales circular item' i duration)
    } else {
      local 'dur' duration
    }
    if (('[data:convertType]' (at i order) 'number') == 0) {
      waitMillis ((dur - ((millisOp) - start)) - 2)
    } else {
      if (destination == 'MIDI channel') {
        'MIDI_play MIDI note' (at (at i order) (v 'note list')) ((dur - ((millisOp) - start)) - 2) channel/pin volume
      } else {
        if (channel/pin != '') {_tonePin = channel/pin}
        playMIDIKey (at (at i order) (v 'note list')) ((dur - ((millisOp) - start)) - 1)
      }
    }
  }
}

to scales_chord which root 'optional steps' {
  local 'steps' (argOrDefault 3 4)
  return ('_scales intervals' (at ('[data:find]' which ('[data:makeList]' 'major' 'minor' 'diminished' 'major 7th' 'minor 7th' 'Maj7')) ('[data:makeList]' ('[data:makeList]' 0 4 7) ('[data:makeList]' 0 3 7) ('[data:makeList]' 0 3 6) ('[data:makeList]' 0 3 7 10) ('[data:makeList]' 0 3 7 10) ('[data:makeList]' 0 4 7 11))) root steps)
}

to 'scales_chord with roman numeral' degree root 'optional steps' {
  local 'steps' (argOrDefault 3 3)
  local 'add 7th' (('[data:find]' '7' degree) > 0)
  local 'diminished' ((('[data:find]' 'o' degree) + ('[data:find]' 'º' degree)) > 0)
  local 'chord name' ''
  local 'flat' (('[data:find]' 'b' degree) == 1)
  if flat {
    degree = ('[data:copyFromTo]' degree 2 (size degree))
    root += -1
  }
  if (v 'add 7th') {
    degree = ('[data:copyFromTo]' degree 1 ((size degree) - 1))
  }
  if diminished {
    degree = ('[data:copyFromTo]' degree 1 ((size degree) - 1))
  }
  local 'index' ('[data:find]' degree ('_scales degrees'))
  if (index > 7) {
    'chord name' = 'minor'
  } else {
    'chord name' = 'major'
  }
  if (v 'add 7th') {
    'chord name' = ('[data:join]' (v 'chord name') ' 7th')
  }
  if diminished {
    'chord name' = 'diminished'
  }
  return (scales_chord (v 'chord name') (at (((index - 1) % 7) + 1) ('[data:makeList]' (root + 0) (root + 2) (root + 4) (root + 5) (root + 7) (root + 9) (root + 11))) steps)
}

to 'scales_circle rotation' n majors minors dims {
  '_scales_init_circle'
  local 'degrees' ('[data:makeList]')
  if (majors != '') {
    for index ('[data:split]' majors ',') {
      '[data:addLast]' ('_scales circular item' ((index + n) + 1) (at 1 _scales_circle5ths)) degrees
    }
  }
  if (minors != '') {
    for index ('[data:split]' minors ',') {
      '[data:addLast]' ('_scales circular item' ((index + n) + 1) (at 2 _scales_circle5ths)) degrees
    }
  }
  if (dims != '') {
    for index ('[data:split]' dims ',') {
      '[data:addLast]' ('_scales circular item' ((index + n) + 1) (at 3 _scales_circle5ths)) degrees
    }
  }
  return ('[data:joinStrings]' degrees ',')
}

to scales_improv scale 'bar count' 'possible durations' destination channel/pin volume {
  if (_rhy_beat_duration == 0) {'rhy_set tempo' 250 4 4}
  local 'next note' (at 'random' scale)
  local 'max jump' 4
  if (isType (v 'possible durations') 'string') {
    'possible durations' = ('_scales parsed durations' (v 'possible durations'))
  }
  local 'next duration' (at 'random' (v 'possible durations'))
  local 'total duration' ((v 'bar count') * (_rhy_beat_duration * _rhy_measures))
  local 'next vol' volume
  repeatUntil ((v 'total duration') <= 0) {
    'total duration' += (0 - (v 'next duration'))
    if (((v 'total duration') - (v 'next duration')) >= 0) {
      local 'index' (('[data:find]' (v 'next note') scale) + (random (random 0 1) (v 'max jump')))
      if (or (index > (size scale)) (index < 1)) {
        'max jump' = (0 - (v 'max jump'))
        'next note' = ('_scales circular item' (index + (v 'max jump')) scale)
      } else {
        'next note' = (at index scale)
      }
      if ((random 1 3) < 3) {
        'next duration' = (at 'random' (v 'possible durations'))
      }
      if (destination == 'MIDI channel') {
        if (isType volume 'list') {'next vol' = (random (at 1 volume) (at 2 volume))}
        'MIDI_play MIDI note' (v 'next note') (v 'next duration') channel/pin (v 'next vol')
      } else {
        if (channel/pin != '') {_tonePin = channel/pin}
        playMIDIKey (v 'next note') (v 'next duration')
      }
      if ((random 1 3) == 3) {
        waitMillis (v 'next duration')
      }
    }
  }
}

to 'scales_pentatonic scale' which root 'optional steps' {
  local 'steps' (argOrDefault 3 5)
  return ('_scales intervals' (at ('[data:find]' which ('[data:makeList]' 'major' 'minor')) ('[data:makeList]' ('[data:makeList]' 0 2 4 7 9) ('[data:makeList]' 0 3 5 7 10))) root steps)
}

to scales_progression degrees root 'optional steps' {
  degrees = ('[data:split]' degrees ',')
  local 'steps' (argOrDefault 3 3)
  local 'chords' ('[data:makeList]')
  for i (size degrees) {
    if (isType steps 'list') {
      local 'chord steps' (at i steps)
    } else {
      local 'chord steps' steps
    }
    '[data:addLast]' ('scales_chord with roman numeral' (at i degrees) root (v 'chord steps')) chords
  }
  return chords
}

to scales_scale which root 'optional steps' {
  local 'steps' (argOrDefault 3 7)
  return ('_scales intervals' (at ('[data:find]' which ('[data:makeList]' 'ionian' 'dorian' 'phrygian' 'lydian' 'mixolydian' 'aeolian' 'locrian')) ('[data:makeList]' ('[data:makeList]' 0 2 4 5 7 9 11) ('[data:makeList]' 0 2 3 5 7 9 10) ('[data:makeList]' 0 1 3 5 7 8 10) ('[data:makeList]' 0 2 4 6 7 9 11) ('[data:makeList]' 0 2 4 5 7 9 10) ('[data:makeList]' 0 2 3 5 7 8 10) ('[data:makeList]' 0 1 3 5 6 8 10))) root steps)
}

to scales_transposed 'note or chord' semitones {
  if (isType (v 'note or chord') 'list') {
    local 'notes' (newList (size (v 'note or chord')))
    for i (size (v 'note or chord')) {
      atPut i notes ((at i (v 'note or chord')) + semitones)
    }
    return notes
  } else {
    return ((v 'note or chord') + semitones)
  }
}

