Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Sections for Sonic Pi
## sections
# first functions
define :getMirrorRange do |pNum|
return [-pNum, pNum];
end
## constants ===================================================================
# definitions
RANGE = {
LOWER: 0,
UPPER: 1,
}.freeze;
NOTE = {
POSITION: 0,
DURATION: 1,
}.freeze;
PHRASE = {
POSITION: 0,
MOTIFS: 1,
}.freeze;
THEME = {
MOTIFS: 0,
RHYTHM_RINGS: 1,
CHORD_ROOTS: 2,
}.freeze;
INSTRUMENT = {
SHORT_SWITCHES: 0,
LONG_SWITCHES: 1,
CC_NUMS: 2,
RANGE: 3,
}.freeze;
KEY = {
TONIC: 0,
SCALE: 1,
}.freeze;
# values
DURATIONS = {
BAR: 16,
HALF: 8,
QUARTER: 4,
EIGHTH: 2,
SIXTEENTH: 1,
}.freeze;
NUM_CHANNELS_PER_PORT = 16;
# instruments
ARPS = {
BASSOON: [[4], [4, 5], [1, 11], [:Bb2, :C5]],
BB_CLARINET: [[4], [4, 5], [1, 11], [:D3, :D6]],
CELLO_BOW: [[19], [19], [1, 11], [:C2, :G5]],
CELLO_PLUCK: [[6], [6], [1, 11], [:C2, :G5]],
FLUTE: [[4], [4, 5], [1, 11], [:C4, :G6]],
HARPSICHORD: [[], [], [], [:F1, :F6]],
OBOE: [[4], [4, 5], [1, 11], [:C4, :C6]],
PIANO: [[], [], [], [:C1, :C8]],
SYNTH: [[], [], [], [:C1, :C8]],
VIOLA_BOW: [[19], [19], [1, 11], [:C3, :C6]],
VIOLA_PLUCK: [[6], [6], [1, 11], [:C3, :C6]],
VIOLIN_BOW: [[19], [19], [1, 11], [:G3, :G6]],
VIOLIN_PLUCK: [[6], [6], [1, 11], [:G3, :G6]],
YANGQIN: [[], [nil, note(:E1), note(:Fs1), note(:G1), note(:A1)], [], [:F2, :A6]],
}
MELS = {
BASSOON: [[0], [0], [1, 11], [:Bb2, :C5]],
BB_CLARINET: [[0], [0], [1, 11], [:D3, :D6]],
CELLO: [[0], [0], [1, 11], [:C2, :G5]],
FLUTE: [[0], [0], [1, 11], [:C4, :G6]],
HARPSICHORD: [[], [], [], [:F1, :F6]],
OBOE: [[0], [0], [1, 11], [:C4, :C6]],
PIANO: [[], [], [], [:C1, :C8]],
SYNTH: [[], [], [], [:C1, :C8]],
VIOLA: [[0], [0], [1, 11], [:C3, :C6]],
VIOLIN: [[0], [0], [1, 11], [:G3, :G6]],
YANGQIN: [[], [nil, note(:E1), note(:Fs1), note(:G1), note(:A1)], [], [:F2, :A6]],
}
PADS = {
BASSOON: [[4], [1], [1, 11], [:Bb2, :C5]],
BB_CLARINET: [[4], [1], [1, 11], [:D3, :D6]],
CELLO: [[19], [1, 2, 3, 11, 18], [1, 11], [:C2, :G5]],
DOUBLE_BASS: [[19], [1, 2, 3, 11, 18], [1, 11], [:E1, :G3]],
FLUTE: [[4], [1], [1, 11], [:C4, :G6]],
HARPSICHORD: [[], [], [], [:F1, :F6]],
OBOE: [[4], [1], [1, 11], [:C4, :C6]],
PIANO: [[], [], [], [:C1, :C8]],
SYNTH: [[], [], [], [:C1, :C8]],
TROMBONE: [[], [1], [], [:E2, :F5]],
TRUMPET: [[], [1], [], [:G3, :C6]],
TUBA: [[], [1], [], [:D1, :F4]],
VIBRAPHONE: [[0], [0], [], [:F3, :F6]],
VIOLA: [[19], [1, 2, 3, 11, 18], [1, 11], [:C3, :C6]],
VIOLIN: [[19], [1, 2, 3, 11, 18], [1, 11], [:G3, :G6]],
YANGQIN: [[], [nil, note(:E1), note(:Fs1), note(:G1), note(:A1)], [], [:F2, :A6]],
}
[ARPS, MELS, PADS].each do |instruments|
instruments.each do |key, instrument|
instrument.each do |setting|
setting.freeze;
end
instrument.freeze;
end
instruments.freeze;
end
ENSEMBLES_ARPS = {
HARPSICHORDS: Array.new(NUM_CHANNELS_PER_PORT, ARPS[:HARPSICHORD]),
PIANOS: Array.new(NUM_CHANNELS_PER_PORT, ARPS[:PIANO]),
STRINGS: [
ARPS[:VIOLIN_BOW], ARPS[:VIOLIN_BOW], ARPS[:VIOLA_BOW], ARPS[:CELLO_BOW],
ARPS[:VIOLIN_BOW], ARPS[:VIOLIN_BOW], ARPS[:VIOLA_BOW], ARPS[:CELLO_BOW],
ARPS[:VIOLIN_BOW], ARPS[:VIOLIN_BOW], ARPS[:VIOLA_BOW], ARPS[:CELLO_BOW],
ARPS[:VIOLIN_BOW], ARPS[:VIOLIN_BOW], ARPS[:VIOLA_BOW], ARPS[:CELLO_BOW],
],
STRINGS_AND_WINDS: [
ARPS[:VIOLIN_BOW], ARPS[:VIOLIN_BOW], ARPS[:VIOLA_BOW], ARPS[:CELLO_BOW],
ARPS[:FLUTE], ARPS[:OBOE], ARPS[:BB_CLARINET], ARPS[:BASSOON],
ARPS[:VIOLIN_PLUCK], ARPS[:VIOLIN_PLUCK], ARPS[:VIOLA_PLUCK], ARPS[:CELLO_PLUCK],
ARPS[:FLUTE], ARPS[:OBOE], ARPS[:BB_CLARINET], ARPS[:BASSOON],
],
STRINGS_AND_WINDS_AND_SYNTHS: [
ARPS[:VIOLIN_BOW], ARPS[:VIOLIN_BOW], ARPS[:VIOLA_BOW], ARPS[:CELLO_BOW],
ARPS[:FLUTE], ARPS[:OBOE], ARPS[:BB_CLARINET], ARPS[:BASSOON],
ARPS[:VIOLIN_PLUCK], ARPS[:VIOLIN_PLUCK], ARPS[:VIOLA_PLUCK], ARPS[:CELLO_PLUCK],
ARPS[:FLUTE], ARPS[:OBOE], ARPS[:BB_CLARINET], ARPS[:BASSOON],
ARPS[:SYNTH], ARPS[:SYNTH], ARPS[:SYNTH], ARPS[:SYNTH],
ARPS[:SYNTH], ARPS[:SYNTH], ARPS[:SYNTH], ARPS[:SYNTH],
ARPS[:SYNTH], ARPS[:SYNTH], ARPS[:SYNTH], ARPS[:SYNTH],
ARPS[:SYNTH], ARPS[:SYNTH], ARPS[:SYNTH], ARPS[:SYNTH],
],
SYNTHS: Array.new(NUM_CHANNELS_PER_PORT, ARPS[:SYNTH]),
WINDS: [
ARPS[:FLUTE], ARPS[:OBOE], ARPS[:BB_CLARINET], ARPS[:BASSOON],
ARPS[:FLUTE], ARPS[:OBOE], ARPS[:BB_CLARINET], ARPS[:BASSOON],
ARPS[:FLUTE], ARPS[:OBOE], ARPS[:BB_CLARINET], ARPS[:BASSOON],
ARPS[:FLUTE], ARPS[:OBOE], ARPS[:BB_CLARINET], ARPS[:BASSOON],
],
YANGQINS: Array.new(NUM_CHANNELS_PER_PORT, ARPS[:YANGQIN]),
}
ENSEMBLES_MELS = {
HARPSICHORDS: Array.new(NUM_CHANNELS_PER_PORT, ARPS[:HARPSICHORD]),
PIANOS: Array.new(NUM_CHANNELS_PER_PORT, ARPS[:PIANO]),
STRINGS: [
MELS[:VIOLIN], MELS[:VIOLIN], MELS[:VIOLA], MELS[:CELLO],
MELS[:VIOLIN], MELS[:VIOLIN], MELS[:VIOLA], MELS[:CELLO],
MELS[:VIOLIN], MELS[:VIOLIN], MELS[:VIOLA], MELS[:CELLO],
MELS[:VIOLIN], MELS[:VIOLIN], MELS[:VIOLA], MELS[:CELLO],
],
STRINGS_AND_WINDS: [
MELS[:VIOLIN], MELS[:VIOLIN], MELS[:VIOLA], MELS[:CELLO],
MELS[:FLUTE], MELS[:OBOE], MELS[:BB_CLARINET], MELS[:BASSOON],
MELS[:VIOLIN], MELS[:VIOLIN], MELS[:VIOLA], MELS[:CELLO],
MELS[:FLUTE], MELS[:OBOE], MELS[:BB_CLARINET], MELS[:BASSOON],
],
STRINGS_AND_WINDS_AND_SYNTHS: [
MELS[:VIOLIN], MELS[:VIOLIN], MELS[:VIOLA], MELS[:CELLO],
MELS[:FLUTE], MELS[:OBOE], MELS[:BB_CLARINET], MELS[:BASSOON],
MELS[:VIOLIN], MELS[:VIOLIN], MELS[:VIOLA], MELS[:CELLO],
MELS[:FLUTE], MELS[:OBOE], MELS[:BB_CLARINET], MELS[:BASSOON],
MELS[:SYNTH], MELS[:SYNTH], MELS[:SYNTH], MELS[:SYNTH],
MELS[:SYNTH], MELS[:SYNTH], MELS[:SYNTH], MELS[:SYNTH],
MELS[:SYNTH], MELS[:SYNTH], MELS[:SYNTH], MELS[:SYNTH],
MELS[:SYNTH], MELS[:SYNTH], MELS[:SYNTH], MELS[:SYNTH],
],
SYNTHS: Array.new(NUM_CHANNELS_PER_PORT, MELS[:SYNTH]),
WINDS: [
MELS[:FLUTE], MELS[:OBOE], MELS[:BB_CLARINET], MELS[:BASSOON],
MELS[:FLUTE], MELS[:OBOE], MELS[:BB_CLARINET], MELS[:BASSOON],
MELS[:FLUTE], MELS[:OBOE], MELS[:BB_CLARINET], MELS[:BASSOON],
MELS[:FLUTE], MELS[:OBOE], MELS[:BB_CLARINET], MELS[:BASSOON],
],
YANGQINS: Array.new(NUM_CHANNELS_PER_PORT, MELS[:YANGQIN]),
}
ENSEMBLES_PADS = {
HARPSICHORDS: Array.new(NUM_CHANNELS_PER_PORT, PADS[:HARPSICHORD]),
PIANOS: Array.new(NUM_CHANNELS_PER_PORT, PADS[:PIANO]),
STRINGS: [
PADS[:VIOLIN], PADS[:VIOLIN], PADS[:VIOLA], PADS[:CELLO],
PADS[:VIOLIN], PADS[:VIOLIN], PADS[:VIOLA], PADS[:CELLO],
PADS[:VIOLIN], PADS[:VIOLIN], PADS[:VIOLA], PADS[:CELLO],
PADS[:VIOLIN], PADS[:VIOLIN], PADS[:VIOLA], PADS[:CELLO],
],
STRINGS_AND_WINDS: [
PADS[:VIOLIN], PADS[:VIOLIN], PADS[:VIOLA], PADS[:CELLO],
PADS[:FLUTE], PADS[:OBOE], PADS[:BB_CLARINET], PADS[:BASSOON],
PADS[:VIOLIN], PADS[:VIOLIN], PADS[:VIOLA], PADS[:CELLO],
PADS[:FLUTE], PADS[:OBOE], PADS[:BB_CLARINET], PADS[:BASSOON],
],
STRINGS_AND_WINDS_AND_SYNTHS: [
PADS[:VIOLIN], PADS[:VIOLIN], PADS[:VIOLA], PADS[:CELLO],
PADS[:FLUTE], PADS[:OBOE], PADS[:BB_CLARINET], PADS[:BASSOON],
PADS[:VIOLIN], PADS[:VIOLIN], PADS[:VIOLA], PADS[:CELLO],
PADS[:FLUTE], PADS[:OBOE], PADS[:BB_CLARINET], PADS[:BASSOON],
PADS[:SYNTH], PADS[:SYNTH], PADS[:SYNTH], PADS[:SYNTH],
PADS[:SYNTH], PADS[:SYNTH], PADS[:SYNTH], PADS[:SYNTH],
PADS[:SYNTH], PADS[:SYNTH], PADS[:SYNTH], PADS[:SYNTH],
PADS[:SYNTH], PADS[:SYNTH], PADS[:SYNTH], PADS[:SYNTH],
],
SYNTHS: Array.new(NUM_CHANNELS_PER_PORT, PADS[:SYNTH]),
WINDS: [
PADS[:FLUTE], PADS[:OBOE], PADS[:BB_CLARINET], PADS[:BASSOON],
PADS[:FLUTE], PADS[:OBOE], PADS[:BB_CLARINET], PADS[:BASSOON],
PADS[:FLUTE], PADS[:OBOE], PADS[:BB_CLARINET], PADS[:BASSOON],
PADS[:FLUTE], PADS[:OBOE], PADS[:BB_CLARINET], PADS[:BASSOON],
],
YANGQINS: Array.new(NUM_CHANNELS_PER_PORT, PADS[:YANGQIN]),
}
[ENSEMBLES_ARPS, ENSEMBLES_MELS, ENSEMBLES_PADS].each do |ensembles|
ensembles.each do |key, ensemble|
ensemble.freeze;
end
ensembles.freeze;
end
# rhythm
BLOCKS = ([[].freeze] + (0..16).collect { |i| [true, Array.new(i, false)].flatten.freeze }).freeze;
COMBOS = {
BAR: [
[16],
[8, 8],
[8, 4, 4],
[4, 8, 4],
[4, 4, 8],
[12, 4],
[4, 12],
[6, 6, 4],
[2, 4, 4, 6],
[2, 4, 4, 4, 2],
[3, 3, 3, 3, 4],
[3, 3, 3, 3, 2, 2],
],
HALF: [
[8],
[4, 4],
[4, 2, 2],
[2, 4, 2],
[2, 2, 4],
[6, 2],
[2, 6],
[3, 3, 2],
[1, 2, 2, 3],
[1, 2, 2, 2, 1],
],
QUARTER: [
[4],
[2, 2],
[1, 1, 1, 1],
[2, 1, 1],
[1, 2, 1],
[1, 1, 2],
[3, 1],
[1, 3],
],
}.freeze;
COMBOS.each do |key, combosOfDuration|
combosOfDuration.each do |combo|
combo.freeze;
end
combosOfDuration.freeze;
end
COMBO_SETS = {
LONG: {
BAR: COMBOS[:BAR].select { |c| (c.length < 4) },
HALF: COMBOS[:HALF].select { |c| (c.length < 3) },
FIRST_HALF: COMBOS[:HALF].select { |c| ((c.length < 3) && (c.first > 2)) },
LAST_HALF: COMBOS[:HALF].select { |c| ((c.length < 3) && (c.last > 2)) },
QUARTER: COMBOS[:QUARTER].select { |c| (c.length < 2) },
FIRST_QUARTER: COMBOS[:QUARTER].select { |c| ((c.length < 2) && (c.first > 2)) },
LAST_QUARTER: COMBOS[:QUARTER].select { |c| ((c.length < 2) && (c.last > 2)) },
},
MEDIUM: {
BAR: COMBOS[:BAR].select { |c| c.length.between?(3, 5) },
HALF: COMBOS[:HALF].select { |c| c.length.between?(2, 4) },
FIRST_HALF: COMBOS[:HALF].select { |c| (c.length.between?(2, 4) && (c.first > 2)) },
LAST_HALF: COMBOS[:HALF].select { |c| (c.length.between?(2, 4) && (c.last > 2)) },
QUARTER: COMBOS[:QUARTER].select { |c| (c.length < 4) },
FIRST_QUARTER: COMBOS[:QUARTER].select { |c| ((c.length < 4) && (c.first > 2)) },
LAST_QUARTER: COMBOS[:QUARTER].select { |c| ((c.length < 4) && (c.last > 2)) },
},
SHORT: {
BAR: COMBOS[:BAR].select { |c| (c.length > 3) },
HALF: COMBOS[:HALF].select { |c| (c.length > 2) },
FIRST_HALF: COMBOS[:HALF].select { |c| ((c.length > 2) && (c.first > 2)) },
LAST_HALF: COMBOS[:HALF].select { |c| ((c.length > 2) && (c.last > 2)) },
QUARTER: COMBOS[:QUARTER].select { |c| (c.length > 1) },
FIRST_QUARTER: COMBOS[:QUARTER].select { |c| ((c.length > 1) && (c.first > 2)) },
LAST_QUARTER: COMBOS[:QUARTER].select { |c| ((c.length > 1) && (c.last > 2)) },
},
RHYTHM_LONGEST_BARS: {
BAR: COMBOS[:BAR].select { |c| (c.length < 2) },
},
}.freeze;
COMBO_SETS.each do |comboSetKey, comboSet|
comboSet.each do |combosKey, combos|
combos.freeze;
end
comboSet.freeze;
end
# space
KEYS_HEPTATONIC = {
C2: :ionian,
D2: :dorian,
E2: :phrygian,
F2: :lydian,
G2: :mixolydian,
A2: :aeolian,
}.to_a.map { |key| key.freeze }.freeze;
KEYS_PENTATONIC = {
C2: :major_pentatonic,
A2: :minor_pentatonic,
}.to_a.map { |key| key.freeze }.freeze;
# settings
gSettings = {
domain: {
key: KEYS_HEPTATONIC[5],
numOctaves: 5, # int [1,)
chanceTranspose: 0.35, # [0,1]
},
general: {
chords: {
chanceAvoidTritoneRoot: 1, # [0,1]
chanceChordsSwitch: 0.6, # [0,1]
chanceFavourProgressionToTonic: 0.3, # [0,1]
progressions: (0...7).to_a, # heptatonic
# progressions: (0...5).to_a, # pentatonic
},
motifs: {
chanceFavourIntervalsLarge: 0, # [0,1]
chanceFavourIntervalsSmall: 0.75, # [0,1]
chanceGenerateNoConsecutivelyRepeatedPositions: 0.6, # [0,1]
chanceGenerateSilence: 0, # [0,1]
chanceMotifGenerates: 0.02, # [0,1]
numMotifsToKeep: 4, # int [0,)
numMotifsTotalMax: 8, # int [0,)
rangeNumMotifsInitially: [2,3], # int [0,)
motifCombos: COMBO_SETS[:MEDIUM],
paletteLimit: 7, # int [1,)
},
seed: Time.new.to_i,
tuning: :equal,
},
log: {
shouldLogCues: false,
shouldLogMIDI: false,
shouldLogSeed: true,
shouldLogCustomMessages: true,
},
themes: {
chanceStartTheme: 0.25, # [0,1]
chanceThemeGenerates: 0.005, # [0,1]
numThemesToKeep: 1, # int [0,)
numThemesTotalMax: 2, # int [0,)
rangeNumThemesInitially: [1, 1], # int [0,)
chords: {
chanceAvoidTritoneRoot: 1, # [0,1]
chanceChordsSwitch: 0.8, # [0,1]
chanceFavourProgressionToTonic: 0.15, # [0,1]
progressions: (0...7).to_a, # heptatonic
# progressions: (0...5).to_a, # pentatonic
},
motifs: {
chanceFavourIntervalsLarge: 0, # [0,1]
chanceFavourIntervalsSmall: 0.8, # [0,1]
chanceGenerateNoConsecutivelyRepeatedPositions: 0.75, # [0,1]
chanceGenerateSilence: 0, # [0,1]
chanceGenerateOneTimeMotif: 1, # [0,1]
chanceMotifInverts: 0.15, # [0,1]
chanceMotifPermutes: 0, # [0,1]
chanceMotifRetrogrades: 0.15, # [0,1]
motifCombos: COMBO_SETS[:LONG],
# motifCombos: COMBO_SETS[:LONG].merge(COMBO_SETS[:MEDIUM]) { |key, cs0, cs1| (cs0 + cs1) },
paletteLimit: 7, # int [1,)
},
rangeNumEightBars: [1, 2], # int [1,)
rhythmCombos: COMBO_SETS[:RHYTHM_LONGEST_BARS],
},
time: {
bpm: 380, # int (0,)
finishAfterMinutes: nil, # int [0,) | nil to play forever
eventWindow: 0.5, # (0,1)
numBeatsBufferBeforeTimeStarts: 2, # [0,)
numCyclesBeforeChordChanges: 2, # int [0,)
},
voices: {
arps: {
chanceAddVoices: 0.5, # [0,1]
chanceAddVoicesIfNone: 1, # [0,1]
numActiveMax: 6, # int [0,)
# numActiveMax: 2, # int [0,)
rangeNumToAdd: [1, 1], # int [0,)
enabled: true,
harmony: {
chanceAvoidCrossing: 0, # [0,1] | e.g. A,C crosses B,B
chanceAvoidOverlap: 0, # [0,1] | e.g. A,C overlaps B,D
chanceAvoidOverlapEdges: 0, # [0,1] | e.g. A,C overlaps edge of C,D
chanceAvoidOverlapPosition: 1, # [0,1] | e.g. A,B overlaps position of A,C
chanceChordsAssert: 1, # [0,1]
chanceExtendChordPositions: 1, # [0,1]
chordPositions: [0, 2, 4], # heptatonic
# chordPositions: (0...5).to_a, # pentatonic
chordPositionsExtended: [0, 2, 4, 6], # heptatonic
# chordPositionsExtended: (0...5).to_a, # pentatonic
chanceFavourTranslationsLarge: 0, # [0,1]
chanceFavourTranslationsSmall: 0.75, # [0,1]
translationLimit: 5, # int [0,)
chanceHarmonyConsonant: 1, # [0,1]
chanceExtendConsonance: 1, # [0,1]
chromaticDissonances: [1, 2, 6, 10, 11], # int [0,11]
chromaticDissonancesExtended: [1, 6], # int [0,11]
},
motifs: {
chanceFavourIntervalsLarge: 0, # [0,1]
chanceFavourIntervalsSmall: 0.75, # [0,1]
chanceGenerateNoConsecutivelyRepeatedPositions: 1, # [0,1]
chanceGenerateSilence: 0, # [0,1]
chanceGenerateOneTimeMotif: 0.8, # [0,1]
chanceMotifInverts: 0.15, # [0,1]
chanceMotifPermutes: 0.15, # [0,1]
chanceMotifRetrogrades: 0.15, # [0,1]
motifCombos: COMBO_SETS[:SHORT],
paletteLimit: 9, # int [1,)
},
nameSingular: "arp",
namePlural: "arps",
phrases: {
rangeNumBars: [1, 2], # int [0,)
},
play: {
chanceLegato: 0.5, # [0,1]
chanceMotifRepeats: 0.8, # [0,1]
durationLong: 4, # int [0,)
instruments: ENSEMBLES_ARPS[:STRINGS_AND_WINDS_AND_SYNTHS],
midiCC: {
base: 0.15, # [0,1]
makeup: 0.45, # [0,1]
rangeRandom: getMirrorRange(0.1), # [0,1]
chanceWindUp: 0.8, # [0,1]
durationWindDown: DURATIONS[:BAR], # int [0,16]
durationWindUp: DURATIONS[:HALF], # int [0,16]
},
midiVelocityOff: {
base: 0.75, # [0,1]
makeup: 0, # [0,1]
rangeRandom: getMirrorRange(0.1), # [0,1]
},
midiVelocityOn: {
base: 0.15, # [0,1]
makeup: 0.45, # [0,1]
rangeRandom: getMirrorRange(0.1), # [0,1]
},
midiPorts: ["arps-port-0_2", "arps-port-1_5"],
rangeNumPhraseRepeats: [1, 2], # int [1,)
rhythmCombos: COMBO_SETS[:MEDIUM],
},
performance: {
chancePlayMotif: 0.2, # [0,1]
},
timeToScheduleAhead: 0.5, # [0,1] | default 0.5
},
mels: {
chanceAddVoices: 0.3, # [0,1]
chanceAddVoicesIfNone: 1, # [0,1]
numActiveMax: 4, # int [0,)
# numActiveMax: 2, # int [0,)
rangeNumToAdd: [1, 1], # int [0,)
enabled: true,
harmony: {
chanceAvoidCrossing: 0, # [0,1] | e.g. A,C crosses B,B
chanceAvoidOverlap: 0, # [0,1] | e.g. A,C overlaps B,D
chanceAvoidOverlapEdges: 0, # [0,1] | e.g. A,C overlaps edge of C,D
chanceAvoidOverlapPosition: 1, # [0,1] | e.g. A,B overlaps position of A,C
chanceChordsAssert: 1, # [0,1]
chanceExtendChordPositions: 1, # [0,1]
chordPositions: [0, 2, 4], # heptatonic
# chordPositions: (0...5).to_a, # pentatonic
chordPositionsExtended: [0, 2, 4, 6], # heptatonic
# chordPositionsExtended: (0...5).to_a, # pentatonic
chanceFavourTranslationsLarge: 0, # [0,1]
chanceFavourTranslationsSmall: 0.75, # [0,1]
translationLimit: 5, # int [0,)
chanceHarmonyConsonant: 1, # [0,1]
chanceExtendConsonance: 1, # [0,1]
chromaticDissonances: [1, 2, 6, 10, 11], # int [0,11]
chromaticDissonancesExtended: [1, 6], # int [0,11]
},
nameSingular: "mel",
namePlural: "mels",
play: {
chanceLegato: 1, # [0,1]
chanceMotifRepeats: 0.8, # [0,1]
durationLong: 4, # int [0,)
instruments: ENSEMBLES_MELS[:STRINGS_AND_WINDS_AND_SYNTHS],
midiCC: {
base: 0.2, # [0,1]
makeup: 0.4, # [0,1]
rangeRandom: getMirrorRange(0.1), # [0,1]
chanceWindUp: 1, # [0,1]
durationWindDown: DURATIONS[:BAR], # int [0,16]
durationWindUp: DURATIONS[:HALF], # int [0,16]
},
midiVelocityOff: {
base: 0.75, # [0,1]
makeup: 0, # [0,1]
rangeRandom: getMirrorRange(0.1), # [0,1]
},
midiVelocityOn: {
base: 0.1, # [0,1]
makeup: 0.15, # [0,1]
rangeRandom: getMirrorRange(0.1), # [0,1]
},
midiPorts: ["melodies-port-0_3", "melodies-port-1_6"],
},
performance: {
chancePlayMotif: 0.2, # [0,1]
},
timeToScheduleAhead: 0.5, # [0,1] | default 0.5
},
pads: {
chanceAddVoices: 0.75, # [0,1]
chanceAddVoicesIfNone: 1, # [0,1]
numActiveMax: 8, # int [0,)
# numActiveMax: 3, # int [0,)
rangeNumToAdd: [1, 1], # int [0,)
enabled: true,
harmony: {
chanceAvoidCrossing: 0, # [0,1] | e.g. A,C crosses B,B
chanceAvoidOverlap: 0, # [0,1] | e.g. A,C overlaps B,D
chanceAvoidOverlapEdges: 0, # [0,1] | e.g. A,C overlaps edge of C,D
chanceAvoidOverlapPosition: 1, # [0,1] | e.g. A,B overlaps position of A,C
chanceChordsAssert: 1, # [0,1]
chanceExtendChordPositions: 1, # [0,1]
chordPositions: [0, 2, 4], # heptatonic
# chordPositions: (0...5).to_a, # pentatonic
chordPositionsExtended: [0, 2, 4, 6], # heptatonic
# chordPositionsExtended: (0...5).to_a, # pentatonic
chanceFavourTranslationsLarge: 0, # [0,1]
chanceFavourTranslationsSmall: 0.75, # [0,1]
chanceHarmonyConsonant: 1, # [0,1]
chanceExtendConsonance: 1, # [0,1]
chromaticDissonances: [1, 2, 6, 10, 11], # int [0,11]
chromaticDissonancesExtended: [1, 6], # int [0,11]
},
nameSingular: "pad",
namePlural: "pads",
play: {
instruments: ENSEMBLES_PADS[:STRINGS_AND_WINDS_AND_SYNTHS],
midiCC: {
base: 0.6, # [0,1]
makeup: 0, # [0,1]
rangeRandom: getMirrorRange(0.1), # [0,1]
numBarsToDecay: 16, # int [1,)
durationWindDown: DURATIONS[:QUARTER], # int [0,16]
durationWindUp: DURATIONS[:HALF], # int [0,16]
},
midiVelocityOff: {
base: 0.75, # [0,1]
makeup: 0, # [0,1]
rangeRandom: getMirrorRange(0.1), # [0,1]
},
midiVelocityOn: {
base: 0.6, # [0,1]
makeup: 0, # [0,1]
rangeRandom: getMirrorRange(0.1), # [0,1]
},
midiPorts: ["pads-port-0_4", "pads-port-1_7"],
},
timeToScheduleAhead: 0.5, # [0,1] | default 0.5
},
},
}
# setup
midi_all_notes_off();
use_bpm(gSettings[:time][:bpm]);
use_random_seed(gSettings[:general][:seed]);
use_tuning(gSettings[:general][:tuning]);
use_cue_logging(gSettings[:log][:shouldLogCues]);
use_midi_logging(gSettings[:log][:shouldLogMIDI]);
## functions ===================================================================
## general
define :evalChance? do |pChance|
return (pChance >= 1) || ((pChance > 0) && (rand < pChance));
end
define :getIInRange do |pRange|
if pRange.empty?
return nil;
else
return rrand_i(pRange[RANGE[:LOWER]], pRange[RANGE[:UPPER]]);
end
end
define :getInRange do |pRange|
return rrand(pRange[RANGE[:LOWER]], pRange[RANGE[:UPPER]]);
end
define :getMax do |x, y|
return ((x > y) ? x : y);
end
define :getMin do |x, y|
return ((x < y) ? x : y);
end
define :getRangesInAscArray do |pAscArray|
i = 0;
ranges = [];
loop do
start = i;
while (((i + 1) != pAscArray.length) && ((pAscArray[i + 1] - pAscArray[i]) == 1))
i += 1;
end
ranges.push([pAscArray[start], pAscArray[i]]);
if ((i + 1) == pAscArray.length)
break;
else
i += 1;
end
end
return ranges;
end
define :isIInRange? do |pI, pRange|
return pI.between?(pRange[RANGE[:LOWER]], pRange[RANGE[:UPPER]]);
end
## chords
define :getPositionInChord do |pPosition, pTonicity|
return ((pPosition + pTonicity) % pTonicity);
end
define :getNextChordRoot do |pChordRoot, pChordProgression, pTonicity|
return getPositionInChord((pChordRoot + pChordProgression), pTonicity);
end
define :getNextChordProgressions do |pChordRoot, pChordProgressions, pChordRootsToAvoid, pTonicity|
return pChordProgressions.select { |cp| pChordRootsToAvoid.none? { |r| (r == getPositionInChord((pChordRoot + cp), pTonicity)) } };
end
define :getTritoneChordRoot do |pScale|
case pScale
when :ionian
return 6;
when :dorian
return 5;
when :phrygian
return 4;
when :lydian
return 3;
when :mixolydian
return 2;
when :aeolian
return 1;
when :locrian
return 0;
end
return nil;
end
define :isChordPositionChordDegree? do |pChordPosition, pDegree, pTonicity|
return ((pChordPosition % pTonicity) == pDegree);
end
## domain
define :getDomain do |pBaseNote, pScale, pOctaves|
return (scale pBaseNote, pScale, num_octaves: pOctaves);
end
define :getDomainLength do |pScale, pOctaves|
return getDomain(:C0, pScale, pOctaves).length;
end
define :getDomainPositionAtOrAfterSymbol do |pSymbol, pDomain|
position = (pDomain.index { |p| (p > note(pSymbol)) });
if position.nil?
return nil;
elsif position.zero?
return 0;
elsif (pDomain[position - 1] < note(pSymbol))
return position;
else
return (position - 1);
end
end
define :getDomainPositionAtOrPrecedingSymbol do |pSymbol, pDomain|
domainReverse = pDomain.reverse;
position = (domainReverse.index { |p| (p < note(pSymbol)) });
if position.nil?
return nil;
elsif position.zero?
return (pDomain.length - 1);
elsif (domainReverse[position - 1] > note(pSymbol))
return ((pDomain.length - 1) - position);
else
return ((pDomain.length - 1) - (position - 1));
end
end
define :getScaleLength do |pScale|
return (scale :C0, pScale).length;
end
define :getScaleTonicity do |pScale|
return (getScaleLength(pScale) - 1);
end
define :getTopPosition do |pScale, pOctaves|
return ((scale :C0, pScale, num_octaves: pOctaves).length - 1);
end
define :isScaleNTonic? do |pScale, pN|
return (getScaleTonicity(pScale) == pN);
end
## space
define :areSpotsConsonant? do |pSpot1, pSpot2, pDomain, pSettingsHarmony|
chromaticDissonances = (evalChance?(pSettingsHarmony[:chanceExtendConsonance]) ?
pSettingsHarmony[:chromaticDissonancesExtended] : pSettingsHarmony[:chromaticDissonances]);
return isChromaticIntervalConsonant?((pDomain[pSpot2] - pDomain[pSpot1]), chromaticDissonances);
end
define :chooseInterval do |pIntervals, pChanceFavourIntervalsSmall, pChanceFavourIntervalsLarge|
intervals = [];
if evalChance?(pChanceFavourIntervalsSmall)
if !evalChance?(pChanceFavourIntervalsLarge)
intervals = pIntervals.select { |i| favourIntervalsSmall(i, pIntervals) };
end
elsif evalChance?(pChanceFavourIntervalsLarge)
intervals = pIntervals.select { |i| favourIntervalsLarge(i, pIntervals) };
end
if intervals.empty?
return pIntervals.choose;
else
return intervals.choose;
end
end
define :chooseNextSpotFromSpots do |pSpot, pNextSpots, pChanceFavourIntervalsSmall, pChanceFavourIntervalsLarge|
return (chooseInterval(pNextSpots.map { |ns| (ns - pSpot) }, pChanceFavourIntervalsSmall, pChanceFavourIntervalsLarge) + pSpot);
end
define :favourIntervalsLarge do |pI, pIntervals|
maxAbsI = pIntervals.map { |i| i.abs }.max;
return evalChance?(((pI.abs + 1) / (maxAbsI + 1).to_f).to_f);
end
define :favourIntervalsSmall do |pI, pIntervals|
minAbsI = pIntervals.map { |i| i.abs }.min;
return evalChance?(((minAbsI + 1) / (pI.abs + 1).to_f).to_f);
end
define :filterIntervalsForBoundNotesCompatibility do |pIntervals, pNotes, pBounds, pPaletteLimit|
peak = getNotesPeak(pNotes);
trough = getNotesTrough(pNotes);
leeway = (pPaletteLimit - (peak - trough));
maxPeak = getMin((peak + leeway), pBounds[RANGE[:UPPER]]);
minTrough = getMax((trough - leeway), pBounds[RANGE[:LOWER]]);
prevPos = pNotes.compact.last[NOTE[:POSITION]];
return pIntervals.select { |i| (prevPos + i).between?(minTrough, maxPeak) };
end
define :filterIntervalsForChord do |pIntervals, pLastNote, pTonicity, pPositionsInChord|
return pIntervals.select { |i| pPositionsInChord.any? { |pc| (getPositionInChord((pLastNote + i), pTonicity) == pc) } };
end
define :generatePaletteIntervals do |pPaletteLimit|
return (-pPaletteLimit..pPaletteLimit).to_a;
end
define :isChromaticIntervalConsonant? do |pInterval, pChromaticDissonances|
interval = ((pInterval + 12) % 12);
return pChromaticDissonances.none? {|cd| cd == interval};
end
define :isSpotChordRoot? do |pSpot, pChordRoot, pTonicity|
return (getPositionInChord(pSpot, pTonicity) == pChordRoot);
end
define :isSpotInChord? do |pSpot, pChordRoot, pTonicity, pSettingsHarmony|
positionsInChord = (evalChance?(pSettingsHarmony[:chanceExtendChordPositions]) ?
pSettingsHarmony[:chordPositionsExtended] : pSettingsHarmony[:chordPositions]);
positionInChord = getPositionInChord((pSpot - pChordRoot), pTonicity);
return (positionsInChord.any? {|pc| (pc == positionInChord) });
end
## time
define :buildComboFlat do |pCombos|
case dice()
when 1
return [pCombos[:FIRST_QUARTER].choose, pCombos[:QUARTER].pick(2), pCombos[:LAST_QUARTER].choose].flatten;
when 2
return [pCombos[:FIRST_HALF].choose, pCombos[:QUARTER].choose, pCombos[:LAST_QUARTER].choose].flatten;
when 3
return [pCombos[:FIRST_QUARTER].choose, pCombos[:HALF].choose, pCombos[:LAST_QUARTER].choose].flatten;
when 4
return [pCombos[:FIRST_QUARTER].choose, pCombos[:QUARTER].choose, pCombos[:LAST_HALF].choose].flatten;
when 5
return [pCombos[:FIRST_HALF].choose, pCombos[:LAST_HALF].choose].flatten;
when 6
return pCombos[:BAR].choose;
end
end
define :convertComboToBlocks do |pCombo|
return pCombo.map { |d| BLOCKS[d] };
end
define :generateRhythmRing do |pCombos|
return convertComboToBlocks(pCombos[:BAR].choose).flatten.ring.freeze;
end
define :generateUniqueRhythmRingsForNumBars do |pNumBars, pCombos|
return (0...pNumBars).collect { |x| generateRhythmRing(pCombos) }.freeze;
end
## ideas
# notes — durations must not be nil or zero
define :areNotesAllSilent? do |pNotes|
return pNotes.all? { |n| isNoteSilent?(n) };
end
define :generateInitialNote do |pDuration, pChanceGenerateSilence|
return makeNote((evalChance?(pChanceGenerateSilence) ? nil : 0), pDuration);
end
define :generateNextNoteInBounds do |pNotes, pBounds, pDuration, pSettingsMotifs|
if pNotes.all? { |n| n[NOTE[:POSITION]].nil? }
return generateInitialNote(pDuration, pSettingsMotifs[:chanceGenerateSilence]);
end
prevPos = getSequenceFromNotes(pNotes).compact.last;
intervals = generatePaletteIntervals(pSettingsMotifs[:paletteLimit]);
intervals = filterIntervalsForBoundNotesCompatibility(intervals, pNotes, pBounds, pSettingsMotifs[:paletteLimit]);
if evalChance?(pSettingsMotifs[:chanceGenerateNoConsecutivelyRepeatedPositions])
intervals.delete(0);
end
if (intervals.empty? || evalChance?(pSettingsMotifs[:chanceGenerateSilence]))
return makeNote(nil, pDuration);
elsif pNotes.last[NOTE[:POSITION]].nil?
return makeNote((prevPos + intervals.choose), pDuration);
else
return makeNote((prevPos + chooseInterval(intervals, pSettingsMotifs[:chanceFavourIntervalsSmall], pSettingsMotifs[:chanceFavourIntervalsLarge])), pDuration);
end
end
define :getNotesPeak do |pNotes|
return getSequenceFromNotes(pNotes).compact.max;
end
define :getNotesTrough do |pNotes|
return getSequenceFromNotes(pNotes).compact.min;
end
define :getSequenceFromNotes do |pNotes|
return pNotes.map { |n| n[NOTE[:POSITION]] };
end
define :invertNote do |pNote|
if isNoteSilent?(pNote)
return pNote;
else
return makeNote(-pNote[NOTE[:POSITION]], pNote[NOTE[:DURATION]]);
end
end
define :isNoteSilent? do |pNote|
return pNote[NOTE[:POSITION]].nil?
end
define :makeNote do |pPosition, pDuration|
return [pPosition, pDuration].freeze;
end
define :permuteNotes do |pNotes|
shuffledNotes = pNotes.shuffle;
return pNotes.map { |n| makeNote(shuffledNotes.shift[NOTE[:POSITION]], n[NOTE[:DURATION]]) };
end
define :retrogradeNotes do |pNotes|
reversedNotes = pNotes.reverse;
return pNotes.map { |n| makeNote(reversedNotes.shift[NOTE[:POSITION]], n[NOTE[:DURATION]]) };
end
define :transposeNote do |pNote, pNumSteps|
if (isNoteSilent?(pNote) || pNumSteps.zero?)
return pNote;
else
return makeNote((pNote[NOTE[:POSITION]] + pNumSteps), pNote[NOTE[:DURATION]]);
end
end
define :transposeNotes do |pNotes, pNumSteps|
return (pNumSteps.zero? ? pNotes : pNotes.map { |n| transposeNote(n, pNumSteps) });
end
define :zeroNotes do |pNotes|
return transposeNotes(pNotes, -pNotes.detect { |n| !n[NOTE[:POSITION]].nil? }[NOTE[:POSITION]]);
end
# motifs
define :generateBoundMotif do |pBounds, pSettingsMotifs|
notes = [];
while areNotesAllSilent?(notes) do
combo = buildComboFlat(pSettingsMotifs[:motifCombos]);
combo = combo.take(combo.length);
notes = [generateInitialNote(combo.shift, pSettingsMotifs[:chanceGenerateSilence])];
notes += combo.collect { |duration| generateNextNoteInBounds(notes, pBounds, duration, pSettingsMotifs) };
end
return makeMotif(notes);
end
define :generateMotif do |pSettingsMotifs|
return generateBoundMotif(getMirrorRange(pSettingsMotifs[:paletteLimit]), pSettingsMotifs);
end
define :getAllMotifsPeak do |pMotifs|
return pMotifs.map { |m| getNotesPeak(m) }.max;
end
define :getAllMotifsTrough do |pMotifs|
return pMotifs.map { |m| getNotesTrough(m) }.min;
end
define :invertMotif do |pMotif|
if isAMotif?(pMotif)
return pMotif.map { |n| invertNote(n) };
else
puts(pMotif.to_s + " not a motif! inversion not performed");
end
end
define :isAMotif? do |pMotif|
return pMotif.detect { |n| !n[NOTE[:POSITION]].nil? }[NOTE[:POSITION]].zero?;
end
define :makeMotif do |pNotes|
return zeroNotes(pNotes);
end
# phrases
define :fillGapInPhrases do |pAllPhrases, pBounds, pChordRoot, pDomain, pTonicity, pSettingsVoices|
sh = pSettingsVoices[:harmony];
avoidOverlap = evalChance?(sh[:chanceAvoidOverlap]);
avoidCrossing = (avoidOverlap || evalChance?(sh[:chanceAvoidCrossing]));
spotsTaken = [];
phrasePositions = [];
for p in pAllPhrases
unless p.nil?
spotsTaken.push(evalChance?(sh[:chanceAvoidOverlapEdges]) ? generatePhraseSpots(p) : generatePhraseInnerSpots(p));
phrasePositions.push(p[PHRASE[:POSITION]]);
end
end
spotsTaken.flatten!;
spotsTaken.uniq!;
spots = (0...pDomain.length).to_a;
spots = spots.drop(pBounds[RANGE[:LOWER]]);
boundsRange = (pBounds[RANGE[:UPPER]] - pBounds[RANGE[:LOWER]]);
spots = ((boundsRange < spots.length) ? spots.take(boundsRange) : []);
if avoidOverlap
spots -= spotsTaken;
elsif avoidCrossing
spots -= phrasePositions;
end
spotRanges = getRangesInAscArray(spots);
if evalChance?(sh[:chanceHarmonyConsonant])
spots.select! { |s| phrasePositions.all? { |pp| areSpotsConsonant?(pp, s, pDomain, sh) } };
end
if evalChance?(sh[:chanceChordsAssert])
spots.select! { |s| isSpotInChord?(s, pChordRoot, pTonicity, sh) };
if (phrasePositions.none? { |pp| isSpotChordRoot?(pp, pChordRoot, pTonicity) })
spots.select! { |s| isSpotChordRoot?(s, pChordRoot, pTonicity) };
end
end
if spots.empty?
return nil;
else
if (avoidCrossing || avoidOverlap)
spotRanges.shuffle!;
rangeIndex = spotRanges.index { |r| spots.any? { |s| s.between?(r[RANGE[:LOWER]], r[RANGE[:UPPER]]) } };
return nil if rangeIndex.nil?;
rangeChosen = spotRanges[rangeIndex];
spotChosen = (spots.select { |s| s.between?(rangeChosen[RANGE[:LOWER]], rangeChosen[RANGE[:UPPER]]) }).choose;
return generateBoundPhrase(spotChosen, rangeChosen, pSettingsVoices[:motifs], pSettingsVoices[:phrases]);
else
return nil if spots.empty?;
if evalChance?(sh[:chanceAvoidOverlapPosition])
remainingSpots = (spots - phrasePositions);
return nil? if remainingSpots.empty?;
return generateBoundPhrase(remainingSpots.choose, [spots.first, spots.last], pSettingsVoices[:motifs], pSettingsVoices[:phrases]);
else
return generateBoundPhrase(spots.choose, [spots.first, spots.last], pSettingsVoices[:motifs], pSettingsVoices[:phrases]);
end
end
end
end
define :generateBoundPhrase do |pPosition, pBounds, pSettingsMotifs, pSettingsPhrases|
motifBounds = pBounds.map { |b| (b - pPosition) };
return makePhrase(pPosition,
(0...getIInRange(pSettingsPhrases[:rangeNumBars])).collect { |x| generateBoundMotif(motifBounds, pSettingsMotifs) });
end
define :generatePhraseInnerSpots do |pPhrase|
return ((getPhraseTrough(pPhrase) + 1)...getPhrasePeak(pPhrase)).to_a;
end
define :generatePhraseSpots do |pPhrase|
return (getPhraseTrough(pPhrase)..getPhrasePeak(pPhrase)).to_a;
end
define :getInstantiableSpots do |pMotifs, pAllPhrases, pBounds, pChordRoot, pDomain, pTonicity, pSettingsHarmony|
sh = pSettingsHarmony;
spotsTaken = [];
phrasePositions = [];
for p in pAllPhrases
unless p.nil?
spotsTaken.push(evalChance?(sh[:chanceAvoidOverlapEdges]) ? generatePhraseSpots(p) : generatePhraseInnerSpots(p));
phrasePositions.push(p[PHRASE[:POSITION]]);
end
end
spotsTaken.flatten!;
spotsTaken.uniq!;
motifsPeak = getAllMotifsPeak(pMotifs);
motifsTrough = getAllMotifsTrough(pMotifs);
spots = (0...pDomain.length).to_a;
spots = spots.drop(pBounds[RANGE[:LOWER]]);
boundsRange = (pBounds[RANGE[:UPPER]] - pBounds[RANGE[:LOWER]]);
spots = ((boundsRange < spots.length) ? spots.take(boundsRange) : []);
spots = ((-motifsTrough < spots.length) ? spots.drop(-motifsTrough) : []);
spots = ((motifsPeak < spots.length) ? spots.take(spots.length - motifsPeak) : []);
if evalChance?(sh[:chanceHarmonyConsonant])
spots.select! { |s| phrasePositions.all? { |pp| areSpotsConsonant?(pp, s, pDomain, sh) } };
end
if evalChance?(sh[:chanceChordsAssert])
spots.select! { |s| isSpotInChord?(s, pChordRoot, pTonicity, sh) };
if (phrasePositions.none? { |pp| isSpotChordRoot?(pp, pChordRoot, pTonicity) })
spots.select! { |s| isSpotChordRoot?(s, pChordRoot, pTonicity) };
end
end
if evalChance?(sh[:chanceAvoidOverlap])
(motifsTrough..motifsPeak).each { |mp| spotsTaken.each { |st| (spots -= (st - mp)) } };
elsif evalChance?(sh[:chanceAvoidOverlapPosition])
spots -= phrasePositions;
end
return spots;
end
define :getPhrasePeak do |pPhrase|
return (getAllMotifsPeak(pPhrase[PHRASE[:MOTIFS]]) + pPhrase[PHRASE[:POSITION]]);
end
define :getPhraseTrough do |pPhrase|
return (getAllMotifsTrough(pPhrase[PHRASE[:MOTIFS]]) + pPhrase[PHRASE[:POSITION]]);
end
define :makePhrase do |pPosition, pMotifs|
return [pPosition, pMotifs].freeze;
end
## performance
# instruments
define :generateInstrumentDomainRange do |pInstrument, pDomain|
return [
getDomainPositionAtOrAfterSymbol(pInstrument[INSTRUMENT[:RANGE]][RANGE[:LOWER]], pDomain),
getDomainPositionAtOrPrecedingSymbol(pInstrument[INSTRUMENT[:RANGE]][RANGE[:UPPER]], pDomain),
]
end
# play
define :calculatePitch do |pPosition, pDomain|
return pPosition.nil? ? nil : pDomain[pPosition];
end
define :getTicksToNextAccent do |pRhythm, pTicks|
(1...pRhythm.length).each do |i|
if pRhythm[pTicks + i]
return i;
end
end
return pRhythm.length;
end
define :selectChannel do |pVoiceNum|
return ((pVoiceNum % NUM_CHANNELS_PER_PORT) + 1);
end
define :selectPort do |pVoiceNum, pPorts|
return pPorts[(pVoiceNum / NUM_CHANNELS_PER_PORT).to_i];
end
define :selectKeyswitch do |pInstrument, pDuration, pDurationLong|
if (pDuration < pDurationLong)
return pInstrument[INSTRUMENT[:SHORT_SWITCHES]].choose;
else
return pInstrument[INSTRUMENT[:LONG_SWITCHES]].choose;
end
end
## performance =================================================================
# helper functions
define :calculateMIDIPeakValue do |pSettingsMidi|
return (pSettingsMidi[:base] + pSettingsMidi[:makeup] + getInRange(pSettingsMidi[:rangeRandom]));
end
define :calculateMIDIRestingValue do |pSettingsMidi|
return (pSettingsMidi[:makeup] + getInRange(pSettingsMidi[:rangeRandom]));
end
define :getCurrentDomain do
return getDomain(gSettings[:domain][:key][KEY[:TONIC]], gSettings[:domain][:key][KEY[:SCALE]], gSettings[:domain][:numOctaves]);
end
define :getCurrentTonicity do
return getScaleTonicity(gSettings[:domain][:key][KEY[:SCALE]]);
end
define :getSpotsInCurrentChord do |pSettingsHarmony|
return (0...getCurrentDomain.length).to_a.select { |spot| isSpotInCurrentChord?(spot, pSettingsHarmony) };
end
define :isSpotInCurrentChord? do |pSpot, pSettingsHarmony|
return isSpotInChord?(pSpot, get("chord/root"), getScaleTonicity(gSettings[:domain][:key][KEY[:SCALE]]), pSettingsHarmony);
end
define :isPhrasePlayable? do |pPhrase, pInstrument, pDomain|
phrasePeak = getPhrasePeak(pPhrase);
phraseTrough = getPhraseTrough(pPhrase);
instrumentDomainRange = generateInstrumentDomainRange(pInstrument, pDomain);
return ((instrumentDomainRange[RANGE[:LOWER]] <= phraseTrough) && (phrasePeak <= instrumentDomainRange[RANGE[:UPPER]]));
end
# template functions
define :keepProc do |pProc|
unless isTimeUp?
pProc.call;
else
waitBar;
end
end
define :runProcIfElseWaitBar do |pPredicate, pProc|
if pPredicate.call
pProc.call;
else
waitBar;
end
end
# time functions
define :printLog do
puts(gSettings[:general][:seed]) if gSettings[:log][:shouldLogSeed];
end
# space functions
define :changeSpace do
sync_bpm("time/quarter/3");
currentChordRoot = get("chord/root");
nextChordRoot = nil;
tonicity = getCurrentTonicity();
theme = get("ideas/theme");
unless theme.nil?
nextChordRoot = theme[THEME[:CHORD_ROOTS]][get("ideas/themeIndex")];
else
if (currentChordRoot.zero? && evalChance?(gSettings[:domain][:chanceTranspose]))
nextKey = KEYS_HEPTATONIC.reject { |key| (key[KEY[:TONIC]] == gSettings[:domain][:key][KEY[:TONIC]]) }.choose;
unless nextKey.nil?
gSettings[:domain][:key] = nextKey;
puts("transposing to #{nextKey.to_s}") if gSettings[:log][:shouldLogCustomMessages];
end
nextChordRoot = 0;
else
progressions = gSettings[:general][:chords][:progressions];
rootsToAvoid = [];
rootsToAvoid.push(getTritoneChordRoot(gSettings[:domain][:key][KEY[:SCALE]])) if evalChance?(gSettings[:general][:chords][:chanceAvoidTritoneRoot]);
nextProgressions = getNextChordProgressions(currentChordRoot, progressions, rootsToAvoid, tonicity);
nextProgression = nil;
if evalChance?(gSettings[:general][:chords][:chanceFavourProgressionToTonic])
nextProgression = nextProgressions.detect { |np| isChordPositionChordDegree?((currentChordRoot + np), 0, tonicity) };
end
nextProgression = nextProgressions.choose if nextProgression.nil?;
nextChordRoot = getNextChordRoot(currentChordRoot, nextProgression, tonicity);
end
end
unless (nextChordRoot == currentChordRoot)
puts("next chord #{nextChordRoot.to_s}") if gSettings[:log][:shouldLogCustomMessages];
set("chord/root", nextChordRoot);
if evalChance?(gSettings[:voices][:arps][:harmony][:chanceChordsAssert])
recalculateArps();
end
unless (theme.nil? || !evalChance?(gSettings[:voices][:mels][:harmony][:chanceChordsAssert]))
recalculateMels();
end
end
end
define :generateChordProgressionForNumBars do |pNumBars|
progressions = gSettings[:themes][:chords][:progressions];
progression = [0];
rootsToAvoid = [];
rootsToAvoid.push(getTritoneChordRoot(gSettings[:domain][:key][KEY[:SCALE]])) if evalChance?(gSettings[:themes][:chords][:chanceAvoidTritoneRoot]);
tonicity = getCurrentTonicity();
(0...(pNumBars - 1)). each do |i|
if evalChance?(gSettings[:themes][:chords][:chanceChordsSwitch])
nextProgressions = getNextChordProgressions(progression[i], progressions, rootsToAvoid, tonicity);
nextProgression = nil;
if evalChance?(gSettings[:themes][:chords][:chanceFavourProgressionToTonic])
nextProgression = nextProgressions.detect { |np| isChordPositionChordDegree?((progression[i] + np), 0, tonicity) };
end
nextProgression = nextProgressions.choose if nextProgression.nil?;
progression.push(getNextChordRoot(progression[i], nextProgression, tonicity));
else
progression.push(progression[i]);
end
end
return progression;
end
define :shouldChangeSpace? do
return (!get("ideas/theme").nil? ||
(((get("time/cycle")) > gSettings[:time][:numCyclesBeforeChordChanges]) &&
evalChance?(gSettings[:general][:chords][:chanceChordsSwitch])));
end
# motifs functions
define :addMotifs do
sync_bpm("time/quarter/3");
motifs = get("ideas/motifs");
motifs = motifs.take(motifs.length);
motifs.push(generateMotif(gSettings[:general][:motifs]));
motifs.uniq!;
if (motifs.length > gSettings[:general][:motifs][:numMotifsTotalMax])
motifs.delete(motifs.drop(gSettings[:general][:motifs][:numMotifsToKeep]).choose);
end
set("ideas/motifs", motifs);
if gSettings[:log][:shouldLogCustomMessages]
puts("#{motifs.length.to_s}/#{gSettings[:general][:motifs][:numMotifsTotalMax]} motifs");
end
end
define :initialiseMotifs do
return (0...getIInRange(gSettings[:general][:motifs][:rangeNumMotifsInitially])).collect { |x| generateMotif(gSettings[:general][:motifs]) };
end
define :prepareMotif do |pSettingsMotifs|
prototype = evalChance?(pSettingsMotifs[:chanceGenerateOneTimeMotif]) ?
generateMotif(pSettingsMotifs) : get("ideas/motifs").choose;
if evalChance?(pSettingsMotifs[:chanceMotifInverts])
prototype = invertMotif(prototype);
end
if evalChance?(pSettingsMotifs[:chanceMotifRetrogrades])
prototype = retrogradeNotes(prototype);
end
if evalChance?(pSettingsMotifs[:chanceMotifPermutes])
prototype = permuteNotes(prototype);
end
return makeMotif(prototype);
end
define :shouldAddMotifs? do
numMotifsTotalMax = gSettings[:general][:motifs][:numMotifsTotalMax];
numMotifsToKeep = gSettings[:general][:motifs][:numMotifsToKeep];
return (((get("ideas/motifs").length < numMotifsTotalMax) || (numMotifsToKeep < numMotifsTotalMax)) &&
evalChance?(gSettings[:general][:motifs][:chanceMotifGenerates]));
end
# themes
define :addThemes do
sync_bpm("time/quarter/3");
themes = get("ideas/themes");
themes = themes.take(themes.length);
themes.push(generateTheme());
themes.uniq!;
if (themes.length > gSettings[:themes][:numThemesTotalMax])
themes.delete(themes.drop(gSettings[:themes][:numThemesToKeep]).choose);
end
set("ideas/themes", themes);
if gSettings[:log][:shouldLogCustomMessages]
puts("#{themes.length.to_s}/#{gSettings[:themes][:numThemesTotalMax]} themes");
end
end
define :advanceTheme do
if shouldStartTheme?()
startTheme();
elsif isThemePlaying?()
set("ideas/themeIndex", (get("ideas/themeIndex") + 1));
if isThemeOver?()
finishTheme();
end
end
end
define :finishTheme do
set("ideas/theme", nil);
set("ideas/themeIndex", nil);
clearAllMels();
puts("theme finished") if gSettings[:log][:shouldLogCustomMessages];
end
define :generateTheme do
numEightBars = getIInRange(gSettings[:themes][:rangeNumEightBars]);
motifs = (0..numEightBars).collect { |x| prepareMotif(gSettings[:themes][:motifs]) };
motifs = motifs.pick(numEightBars * 8);
motifs += motifs.take(8);
rhythmRings = generateUniqueRhythmRingsForNumBars(((numEightBars * 8) + 8), gSettings[:themes][:rhythmCombos]);
chords = generateChordProgressionForNumBars(numEightBars * 8);
chords += chords.take(8);
return makeTheme(motifs, rhythmRings, chords);
end
define :initialiseThemes do
return (0...getIInRange(gSettings[:themes][:rangeNumThemesInitially])).collect { |x| generateTheme() };
end
define :isThemeOver? do
return (get("ideas/themeIndex") >= get("ideas/theme")[THEME[:CHORD_ROOTS]].length);
end
define :isThemePlaying? do
return !get("ideas/themeIndex").nil?;
end
define :makeTheme do |pMotifs, pRhythmRings, pChords|
return [pMotifs, pRhythmRings, pChords].freeze;
end
define :shouldAddThemes? do
numThemesTotalMax = gSettings[:themes][:numThemesTotalMax];
numThemesToKeep = gSettings[:themes][:numThemesToKeep];
return (((get("ideas/themes").length < numThemesTotalMax) || (numThemesToKeep < numThemesTotalMax)) &&
evalChance?(gSettings[:themes][:chanceThemeGenerates]));
end
define :shouldStartTheme? do
return (get("ideas/theme").nil? && get("chord/root").zero? && evalChance?(gSettings[:themes][:chanceStartTheme]));
end
define :startTheme do
set("ideas/theme", get("ideas/themes").choose);
set("ideas/themeIndex", 0);
puts("theme starting") if gSettings[:log][:shouldLogCustomMessages];
end
# play functions
define :finishVoice do |pVoiceNum, pInstrument, pKeyswitch, pVelOff, pCCConcludingValue|
midi_note_off(pKeyswitch, vel_f: pVelOff) unless pKeyswitch.nil?;
restVoiceCC(pVoiceNum, pInstrument, pCCConcludingValue);
sync_bpm("time/quarterTick");
end
define :initialiseVoice do |pVoiceNum, pInstrument, pKeyswitch, pVelOn, pCCInitialValue|
midi_note_on(pKeyswitch, vel_f: pVelOn) unless pKeyswitch.nil?;
restVoiceCC(pVoiceNum, pInstrument, pCCInitialValue);
sync_bpm("time/quarterTick");
end
define :restVoiceCC do |pVoiceNum, pInstrument, pCCRestingValue|
setVoiceCCValue(pVoiceNum, pInstrument, pCCRestingValue);
end
define :setVoiceCCValue do |pVoiceNum, pInstrument, pCCValue|
for ccn in pInstrument[INSTRUMENT[:CC_NUMS]]
midi_cc(ccn, val_f: pCCValue);
end
end
define :sustainVoiceCC do |pVoiceNum, pInstrument, pCCInitial, pCCConcluding, pNumBarsToDecay, pSettingsHarmony|
startingChordRoot = get("chord/root");
currentChordRoot = startingChordRoot;
decrements = (pNumBarsToDecay * DURATIONS[:BAR] * 4);
decrement = (((pCCInitial - pCCConcluding) / decrements.to_f).to_f);
ccValue = pCCInitial;
decrements.times do
if (currentChordRoot == get("chord/root"))
ccValue -= decrement;
setVoiceCCValue(pVoiceNum, pInstrument, ccValue);
sync_bpm("time/quarterTick");
else
if isSpotInCurrentChord?(startingChordRoot, pSettingsHarmony)
currentChordRoot = get("chord/root");
else
break;
end
end
end
return ccValue;
end
define :windDownVoiceCC do |pVoiceNum, pInstrument, pCCValue, pCCConcluding, pDurationWindDown|
ccValue = pCCValue;
decrements = (pDurationWindDown * 4);
decrement = ((pCCValue - pCCConcluding) / decrements.to_f).to_f;
decrements.times do
ccValue -= decrement;
setVoiceCCValue(pVoiceNum, pInstrument, ccValue);
sync_bpm("time/quarterTick");
end
end
define :windUpVoiceCC do |pVoiceNum, pInstrument, pCCInitial, pCCPeak, pDurationWindUp|
ccValue = pCCInitial;
increments = (pDurationWindUp * 4);
increment = ((pCCPeak - pCCInitial) / increments.to_f).to_f;
increments.times do
ccValue += increment;
setVoiceCCValue(pVoiceNum, pInstrument, ccValue);
sync_bpm("time/quarterTick");
end
end
# voice functions
define :addVoices do |pSettingsVoices|
sync_bpm("time/quarter/1");
rangeNumVoicesToAdd = pSettingsVoices[:rangeNumToAdd];
rangeNumVoicesToAdd[RANGE[:UPPER]] = getMin(
rangeNumVoicesToAdd[RANGE[:UPPER]],
(pSettingsVoices[:numActiveMax] - send("count#{pSettingsVoices[:namePlural].capitalize}Taken"))
)
numVoicesToAdd = getIInRange(rangeNumVoicesToAdd);
unless numVoicesToAdd.zero?
getAllVoiceNums(pSettingsVoices).shuffle.each do |vn|
if (send("is#{pSettingsVoices[:nameSingular].capitalize}Free?", vn) &&
send("decide#{pSettingsVoices[:nameSingular].capitalize}?", vn))
generateVoice(vn, pSettingsVoices);
numVoicesToAdd -= 1;
break if numVoicesToAdd.zero?;
end
end
end
sync_bpm("time/tick"); # required for stability
end
define :decideVoice? do |pVoicing, pSetVoiceProc|
unless pVoicing.nil?
pSetVoiceProc.call();
return true;
else
return false;
end
end
define :determineVoiceSpotsAvailable do |pVoiceNum, pVoicesSymbol|
settingsVoices = gSettings[:voices][pVoicesSymbol];
instrument = settingsVoices[:play][:instruments][pVoiceNum];
instrumentDomainRange = generateInstrumentDomainRange(instrument, getCurrentDomain());
voices = send("getAll#{pVoicesSymbol.to_s.capitalize}");
spotsAvailable = getSpotsInCurrentChord(settingsVoices[:harmony]);
(0...voices.length).each do |i|
unless (voices[i].nil? || (settingsVoices[:play][:instruments][i] != instrument))
spotsAvailable.delete(voices[i]);
end
end
spotsAvailable.select! { |spot| isIInRange?(spot, instrumentDomainRange) };
return spotsAvailable;
end
define :generateVoice do |pVoiceNum, pSettingsVoices|
with_sched_ahead_time(pSettingsVoices[:timeToScheduleAhead]) do
in_thread(name: "#{pSettingsVoices[:nameSingular]}#{pVoiceNum.to_s}".to_sym) do
sync_bpm("time/quarter/0");
if gSettings[:log][:shouldLogCustomMessages]
puts("#{pSettingsVoices[:nameSingular]} #{pVoiceNum} playing #{get("#{pSettingsVoices[:namePlural]}/#{pVoiceNum.to_s}")}");
end
send("play#{pSettingsVoices[:nameSingular].capitalize}", pVoiceNum);
send("clear#{pSettingsVoices[:nameSingular].capitalize}", pVoiceNum);
end
end
end
define :getAllVoiceNums do |pSettingsVoices|
return (0...pSettingsVoices[:play][:instruments].length).to_a;
end
define :shouldAddVoices? do |pSettingsVoices|
numVoicesTaken = send("count#{pSettingsVoices[:namePlural].capitalize}Taken");
return (pSettingsVoices[:enabled] &&
(evalChance?(pSettingsVoices[:chanceAddVoices]) || (numVoicesTaken.zero? &&
evalChance?(pSettingsVoices[:chanceAddVoicesIfNone]))) &&
(numVoicesTaken < pSettingsVoices[:numActiveMax]));
end
# arps functions
define :createArp do |pArpNum|
settingsArps = gSettings[:voices][:arps];
arps = getAllArps();
instrumentDomainRange = generateInstrumentDomainRange(settingsArps[:play][:instruments][pArpNum], getCurrentDomain());
arp = nil;
if evalChance?(settingsArps[:performance][:chancePlayMotif])
motifs = (0...getIInRange(settingsArps[:phrases][:rangeNumBars])).collect { |x| prepareMotif(settingsArps[:motifs]) };
instantiableSpots = getInstantiableSpots(motifs, arps, instrumentDomainRange,
get("chord/root"), getCurrentDomain(), getCurrentTonicity(), settingsArps[:harmony]);
spot = instantiableSpots.choose;
arp = makePhrase(spot, motifs) unless spot.nil?;
end
if arp.nil?
arp = fillGapInPhrases(arps, instrumentDomainRange,
get("chord/root"), getCurrentDomain(), getCurrentTonicity(), settingsArps);
end
return arp;
end
define :decideArp? do |pArpNum|
arp = createArp(pArpNum);
return decideVoice?(arp, (proc { setArp(pArpNum, arp) }));
end
define :generateArp do |pArpNum|
in_thread(name: "arp#{pArpNum.to_s}".to_sym) do
sync_bpm("time/quarter/0");
playArp(pArpNum);
end
end
define :playArp do |pArpNum|
settingsPlay = gSettings[:voices][:arps][:play];
settingsCC = settingsPlay[:midiCC];
settingsVelOn = settingsPlay[:midiVelocityOn];
settingsVelOff = settingsPlay[:midiVelocityOff];
instrument = settingsPlay[:instruments][pArpNum];
arp = getArp(pArpNum);
break if arp.nil?;
motifs = arp[PHRASE[:MOTIFS]];
domain = getCurrentDomain();
ccInitial = calculateMIDIRestingValue(settingsCC);
ccPeak = calculateMIDIPeakValue(settingsCC);
ccFinal = calculateMIDIRestingValue(settingsCC);
ccChange = ((ccPeak - ccFinal) / (DURATIONS[:BAR] * 4).to_f).to_f;
ccValue = ccPeak;
velMax = calculateMIDIPeakValue(settingsVelOn);
velMin = calculateMIDIRestingValue(settingsVelOn);
velDecayPerTick = ((velMax - velMin) / (DURATIONS[:BAR]).to_f).to_f;
ticksElapsed = 0;
ticksIntoBar = 0;
ticksLeft = (getIInRange(settingsPlay[:rangeNumPhraseRepeats]) * DURATIONS[:BAR] * motifs.length);
isOnFinalBar = false;
shouldWaitForWindup = true;
lastPitch = nil;
shouldLegato = false;
noteIndex = 0;
barIndex = 0;
rhythmRings = generateUniqueRhythmRingsForNumBars(motifs.length, settingsPlay[:rhythmCombos]);
with_midi_defaults(port: selectPort(pArpNum, settingsPlay[:midiPorts]), channel: selectChannel(pArpNum)) do
if evalChance?(settingsCC[:chanceWindUp])
in_thread do
windUpVoiceCC(pArpNum, instrument, ccInitial, ccPeak, settingsCC[:durationWindUp]);
end
else
setVoiceCCValue(pArpNum, instrument, ccPeak);
shouldWaitForWindup = false;
end
until ticksLeft.zero?
currentDomain = getCurrentDomain();
unless (domain == currentDomain)
domain = currentDomain;
unless isPhrasePlayable?(arp, instrument, currentDomain)
midi_note_off(lastPitch, vel_f: calculateMIDIPeakValue(settingsVelOff)) unless lastPitch.nil?;
break;
end
end
isAccented = rhythmRings[barIndex][ticksElapsed];
if isAccented
noteIndex = 0;
ticksIntoBar = 0;
ccValue = ccPeak;
setVoiceCCValue(pArpNum, instrument, ccValue) unless (shouldWaitForWindup || isOnFinalBar);
end
n = motifs[barIndex][noteIndex];
pitch = nil;
pitch = calculatePitch((arp[PHRASE[:POSITION]] + n[NOTE[:POSITION]]), domain) unless n[NOTE[:POSITION]].nil?;
velOn = (velMax - (ticksIntoBar * velDecayPerTick));
velOff = calculateMIDIPeakValue(settingsVelOff);
ticksToNextAccent = getTicksToNextAccent(rhythmRings[barIndex], ticksElapsed);
duration = n[NOTE[:DURATION]];
duration = getMin(duration, ticksToNextAccent);
duration = getMin(duration, ticksLeft);
isOnFinalNoteOfFinalBar = (isOnFinalBar && (duration == ticksLeft));
keyswitch = selectKeyswitch(instrument, duration, settingsPlay[:durationLong]);
midi_note_on(keyswitch, vel_f: velOn) unless keyswitch.nil?;
midi_note_off(lastPitch, vel_f: velOff) if ((!shouldLegato || (lastPitch == pitch)) && !lastPitch.nil?);
sync_bpm("time/quarterTick");
unless (shouldWaitForWindup || isOnFinalBar)
if (ticksIntoBar < DURATIONS[:HALF])
ccValue -= ccChange;
else
ccValue += ccChange;
end
setVoiceCCValue(pArpNum, instrument, ccValue);
end
midi_note_on(pitch, vel_f: velOn) unless pitch.nil?;
sync_bpm("time/quarterTick");
unless (shouldWaitForWindup || isOnFinalBar)
if (ticksIntoBar < DURATIONS[:HALF])
ccValue -= ccChange;
else
ccValue += ccChange;
end
setVoiceCCValue(pArpNum, instrument, ccValue);
end
unless (duration == 1)
tempTicksIntoBar = ticksIntoBar;
(duration - 1).times do
4.times do
unless (shouldWaitForWindup || isOnFinalBar)
if (ticksIntoBar < DURATIONS[:HALF])
ccValue -= ccChange;
else
ccValue += ccChange;
end
setVoiceCCValue(pArpNum, instrument, ccValue);
end
sync_bpm("time/quarterTick");
end
tempTicksIntoBar += 1;
end
end
sync_bpm("time/quarterTick");
unless (shouldWaitForWindup || isOnFinalBar)
if (ticksIntoBar < DURATIONS[:HALF])
ccValue -= ccChange;
else
ccValue += ccChange;
end
setVoiceCCValue(pArpNum, instrument, ccValue);
end
midi_note_off(lastPitch, vel_f: velOff) if (shouldLegato && (lastPitch != pitch) && !lastPitch.nil?);
lastPitch = pitch;
shouldLegato = (!isOnFinalNoteOfFinalBar && (ticksToNextAccent > duration) && evalChance?(settingsPlay[:chanceLegato]));
midi_note_off(keyswitch, vel_f: velOff) unless keyswitch.nil?;
ticksElapsed += duration;
ticksIntoBar += duration;
ticksLeft -= duration;
if isOnFinalNoteOfFinalBar
midi_note_off(pitch, vel_f: velOff) unless pitch.nil?;
break;
end
unless isOnFinalBar
hasBarElapsed = (ticksElapsed % DURATIONS[:BAR]).zero?;
arpNow = getArp(pArpNum);
shouldRepeat = false;
shouldContinue = false;
shouldAdvance = false;
if (arp != arpNow)
if hasBarElapsed
arp = arpNow;
if arpNow.nil?
midi_note_off(pitch, vel_f: velOff) unless pitch.nil?;
break;
else
shouldRepeat = true;
end
else
shouldContinue = true;
end
elsif hasBarElapsed
if evalChance?(settingsPlay[:chanceMotifRepeats])
shouldRepeat = true;
elsif ((barIndex + 1) < motifs.length)
shouldAdvance = true;
end
else
shouldContinue = true;
end
if shouldRepeat
ticksLeft += DURATIONS[:BAR];
shouldWaitForWindup = false if shouldWaitForWindup;
elsif shouldContinue
noteIndex += 1;
elsif shouldAdvance
barIndex += 1;
shouldWaitForWindup = false if shouldWaitForWindup;
else
in_thread do
windDownVoiceCC(pArpNum, instrument, ccValue, ccFinal, settingsCC[:durationWindDown]);
end
isOnFinalBar = true;
ticksLeft += DURATIONS[:BAR];
shouldWaitForWindup = false if shouldWaitForWindup;
end
else
noteIndex += 1;
end
sync_bpm("time/quarterTick");
end
end
end
define :recalculateArps do
settingsArps = gSettings[:voices][:arps];
arps = getAllArps();
newArps = Array.new(arps.length, nil);
chordRoot = get("chord/root");
domain = getCurrentDomain();
tonicity = getCurrentTonicity();
(0...arps.length).to_a.shuffle.each do |i|
unless arps[i].nil?
oldSpot = arps[i][PHRASE[:POSITION]];
motifs = arps[i][PHRASE[:MOTIFS]];
newSpots = getInstantiableSpots(motifs, newArps,
generateInstrumentDomainRange(settingsArps[:play][:instruments][i], getCurrentDomain()),
chordRoot, domain, tonicity, settingsArps[:harmony]);
newSpots.select! { |is| ((is - oldSpot).abs < settingsArps[:harmony][:translationLimit]) };
newSpot = chooseNextSpotFromSpots(oldSpot, newSpots,
settingsArps[:harmony][:chanceFavourTranslationsSmall], settingsArps[:harmony][:chanceFavourTranslationsLarge]);
newArps[i] = makePhrase(newSpot, motifs) unless newSpot.nil?;
puts("arp #{i.to_s} switching from #{oldSpot.to_s} to #{newSpot.to_s}") if gSettings[:log][:shouldLogCustomMessages];
end
end
setAllArps(newArps);
end
# mels functions
define :decideMel? do |pMelNum|
mel = determineMelSpotsAvailable(pMelNum, getAllMels()).choose;
return decideVoice?(mel, (proc { setMel(pMelNum, mel) }));
end
define :determineMelSpotsAvailable do |pMelNum, pMels|
settingsMels = gSettings[:voices][:mels];
instrument = settingsMels[:play][:instruments][pMelNum];
instrumentDomainRange = generateInstrumentDomainRange(instrument, getCurrentDomain());
theme = get("ideas/theme");
return [] if theme.nil?;
motifs = theme[THEME[:MOTIFS]];
instantiableSpots = getInstantiableSpots(motifs, pMels.reject { |m| m.nil? }.map { |m| makePhrase(m, motifs) }, instrumentDomainRange,
get("chord/root"), getCurrentDomain(), getCurrentTonicity(), settingsMels[:harmony]);
return instantiableSpots;
end
define :playMel do |pMelNum|
settingsPlay = gSettings[:voices][:mels][:play];
settingsCC = settingsPlay[:midiCC];
settingsVelOn = settingsPlay[:midiVelocityOn];
settingsVelOff = settingsPlay[:midiVelocityOff];
instrument = settingsPlay[:instruments][pMelNum];
domain = getCurrentDomain();
mel = getMel(pMelNum);
break if mel.nil?;
theme = get("ideas/theme");
break if theme.nil?;
motifs = theme[THEME[:MOTIFS]];
ccInitial = calculateMIDIRestingValue(settingsCC);
ccPeak = calculateMIDIPeakValue(settingsCC);
ccFinal = calculateMIDIRestingValue(settingsCC);
ccChange = (((ccPeak - ccFinal) / (DURATIONS[:BAR] * 4).to_f).to_f);
ccValue = ccPeak;
velMax = calculateMIDIPeakValue(settingsVelOn);
velMin = calculateMIDIRestingValue(settingsVelOn);
velDecayPerTick = ((velMax - velMin) / (DURATIONS[:BAR]).to_f).to_f;
barIndex = get("ideas/themeIndex");
ticksElapsed = 0;
ticksIntoBar = 0;
ticksLeft = ((motifs.length - barIndex) * DURATIONS[:BAR]);
isOnFinalBar = (ticksLeft == DURATIONS[:BAR]);
shouldWaitForWindup = true
lastPitch = nil;
shouldLegato = false;
noteIndex = 0;
rhythmRings = theme[THEME[:RHYTHM_RINGS]];
with_midi_defaults(port: selectPort(pMelNum, settingsPlay[:midiPorts]), channel: selectChannel(pMelNum)) do
unless (isOnFinalBar || !evalChance?(settingsCC[:chanceWindUp]))
in_thread do
windUpVoiceCC(pMelNum, instrument, ccInitial, ccPeak, settingsCC[:durationWindUp]);
end
else
setVoiceCCValue(pMelNum, instrument, ccPeak);
shouldWaitForWindup = false;
end
until ticksLeft.zero?
currentDomain = getCurrentDomain();
unless (domain == currentDomain)
domain = currentDomain;
unless isPhrasePlayable?(makePhrase(mel, motifs), instrument, currentDomain)
midi_note_off(lastPitch, vel_f: calculateMIDIPeakValue(settingsVelOff)) unless lastPitch.nil?;
break;
end
end
isAccented = rhythmRings[barIndex][ticksElapsed];
if isAccented
noteIndex = 0;
ticksIntoBar = 0;
ccValue = ccPeak;
setVoiceCCValue(pMelNum, instrument, ccValue) unless (shouldWaitForWindup || isOnFinalBar);
end
n = motifs[barIndex][noteIndex];
pitch = nil;
pitch = calculatePitch((mel + n[NOTE[:POSITION]]), domain) unless n[NOTE[:POSITION]].nil?;
velOn = (velMax - (ticksIntoBar * velDecayPerTick));
velOff = calculateMIDIPeakValue(settingsVelOff);
ticksToNextAccent = getTicksToNextAccent(rhythmRings[barIndex], ticksElapsed);
duration = n[NOTE[:DURATION]];
duration = getMin(duration, ticksToNextAccent);
duration = getMin(duration, ticksLeft);
isOnFinalNote = (duration == ticksLeft);
keyswitch = selectKeyswitch(instrument, duration, settingsPlay[:durationLong]);
midi_note_on(keyswitch, vel_f: velOn) unless keyswitch.nil?;
midi_note_off(lastPitch, vel_f: velOff) if ((!shouldLegato || (lastPitch == pitch)) && !lastPitch.nil?);
sync_bpm("time/quarterTick");
unless (shouldWaitForWindup || isOnFinalBar)
if (ticksIntoBar < DURATIONS[:HALF])
ccValue -= ccChange;
else
ccValue += ccChange;
end
setVoiceCCValue(pMelNum, instrument, ccValue);
end
midi_note_on(pitch, vel_f: velOn) unless pitch.nil?;
sync_bpm("time/quarterTick");
unless (shouldWaitForWindup || isOnFinalBar)
if (ticksIntoBar < DURATIONS[:HALF])
ccValue -= ccChange;
else
ccValue += ccChange;
end
setVoiceCCValue(pMelNum, instrument, ccValue);
end
unless (duration == 1)
tempTicksIntoBar = ticksIntoBar;
(duration - 1).times do
4.times do
unless (shouldWaitForWindup || isOnFinalBar)
if (ticksIntoBar < DURATIONS[:HALF])
ccValue -= ccChange;
else
ccValue += ccChange;
end
setVoiceCCValue(pMelNum, instrument, ccValue);
end
sync_bpm("time/quarterTick");
end
tempTicksIntoBar += 1;
end
end
sync_bpm("time/quarterTick");
unless (shouldWaitForWindup || isOnFinalBar)
if (ticksIntoBar < DURATIONS[:HALF])
ccValue -= ccChange;
else
ccValue += ccChange;
end
setVoiceCCValue(pMelNum, instrument, ccValue);
end
midi_note_off(lastPitch, vel_f: velOff) if (shouldLegato && (lastPitch != pitch) && !lastPitch.nil?);
lastPitch = pitch;
shouldLegato = (!isOnFinalNote && (ticksToNextAccent > duration) && evalChance?(settingsPlay[:chanceLegato]));
midi_note_off(keyswitch, vel_f: velOff) unless keyswitch.nil?;
ticksElapsed += duration;
ticksIntoBar += duration;
ticksLeft -= duration;
if ticksLeft.zero?
midi_note_off(pitch, vel_f: velOff);
break;
end
unless isOnFinalBar
hasBarElapsed = (ticksElapsed % DURATIONS[:BAR]).zero?;
melNow = getMel(pMelNum);
shouldContinue = false;
shouldAdvance = false;
if (melNow.nil? || (mel != melNow))
if hasBarElapsed
mel = melNow;
if melNow.nil?
midi_note_off(pitch, vel_f: velOff);
break;
else
shouldAdvance = true;
end
else
shouldContinue = true;
end
elsif hasBarElapsed
if (ticksLeft == DURATIONS[:BAR])
in_thread do
windDownVoiceCC(pMelNum, instrument, ccValue, ccFinal, settingsCC[:durationWindDown]);
end
isOnFinalBar = true;
end
shouldAdvance = true;
else
shouldContinue = true;
end
if shouldContinue
noteIndex += 1;
elsif shouldAdvance
barIndex += 1;
shouldWaitForWindup = false if shouldWaitForWindup;
end
else
noteIndex += 1;
end
sync_bpm("time/quarterTick");
end
end
end
define :recalculateMels do
settingsMels = gSettings[:voices][:mels];
mels = getAllMels();
newMels = Array.new(mels.length, nil);
(0...mels.length).to_a.shuffle.each do |i|
oldSpot = mels[i];
unless oldSpot.nil?
newSpots = determineMelSpotsAvailable(i, newMels);
newSpots.select! { |is| ((is - oldSpot).abs < settingsMels[:harmony][:translationLimit]) };
newSpot = chooseNextSpotFromSpots(oldSpot, newSpots,
settingsMels[:harmony][:chanceFavourTranslationsSmall], settingsMels[:harmony][:chanceFavourTranslationsLarge]);
newMels[i] = newSpot unless newSpot.nil?;
puts("mel #{i.to_s} switching from #{oldSpot.to_s} to #{newSpot.to_s}") if gSettings[:log][:shouldLogCustomMessages];
end
end
setAllMels(newMels);
end
define :shouldAddMels? do
return (isThemePlaying?() && shouldAddVoices?(gSettings[:voices][:mels]));
end
# pads functions
define :decidePad? do |pPadNum|
pad = determineVoiceSpotsAvailable(pPadNum, :pads).choose;
return decideVoice?(pad, (proc { setPad(pPadNum, pad) }));
end
define :playPad do |pPadNum|
settingsPads = gSettings[:voices][:pads];
settingsHarmony = settingsPads[:harmony];
settingsPlay = settingsPads[:play];
settingsCC = settingsPlay[:midiCC];
settingsVelOn = settingsPlay[:midiVelocityOn];
settingsVelOff = settingsPlay[:midiVelocityOff];
ccInitial = calculateMIDIRestingValue(settingsCC);
ccPeak = calculateMIDIPeakValue(settingsCC);
ccFinal = calculateMIDIRestingValue(settingsCC);
velOn = calculateMIDIPeakValue(settingsVelOn);
velOff = calculateMIDIPeakValue(settingsVelOff);
instrument = settingsPlay[:instruments][pPadNum];
keyswitch = instrument[INSTRUMENT[:LONG_SWITCHES]].choose;
pitch = calculatePitch(getPad(pPadNum), getCurrentDomain());
with_midi_defaults(port: selectPort(pPadNum, settingsPlay[:midiPorts]), channel: selectChannel(pPadNum)) do
initialiseVoice(pPadNum, instrument, keyswitch, velOn, ccInitial);
midi_note_on(pitch, vel_f: velOn);
windUpVoiceCC(pPadNum, instrument, ccInitial, ccPeak, settingsCC[:durationWindUp]);
ccValue = sustainVoiceCC(pPadNum, instrument, ccPeak, ccFinal, settingsCC[:numBarsToDecay], settingsHarmony);
windDownVoiceCC(pPadNum, instrument, ccValue, ccFinal, settingsCC[:durationWindDown]);
midi_note_off(pitch, vel_f: velOff);
finishVoice(pPadNum, instrument, keyswitch, velOff, ccFinal);
end
end
# performance functions
define :finishPiece do
[:arps, :mels, :pads].each do |voices|
gSettings[:voices][voices][:play][:midiPorts].each do |port|
midi_all_notes_off(port: port);
end
end
end
define :waitBar do
wait(DURATIONS[:BAR]);
end
define :isTimeUp? do
if gSettings[:time][:finishAfterMinutes].nil?
return false;
end
return ((get("time/tick") / gSettings[:time][:bpm]).to_i >= gSettings[:time][:finishAfterMinutes]);
end
define :minusWindow do |pDuration|
return (pDuration - gSettings[:time][:eventWindow]);
end
# time functions
define :setup do
printLog();
end
define :wrapup do
advanceTheme();
set("time/cycle", ((get("time/cycle")) + 1));
end
# run functions
# space
define :runSpace do
runProcIfElseWaitBar((proc { shouldChangeSpace?() }), (proc { changeSpace() }));
end
# motifs
define :runMotifs do
runProcIfElseWaitBar((proc { shouldAddMotifs?() }), (proc { addMotifs() }));
end
# themes
define :runThemes do
runProcIfElseWaitBar((proc { shouldAddThemes?() }), (proc { addThemes() }));
end
# voices
define :runMels do
runProcIfElseWaitBar((proc { shouldAddMels?() }), (proc { addVoices(gSettings[:voices][:mels]) }));
end
define :runVoices do |pSettingsVoices|
runProcIfElseWaitBar((proc { shouldAddVoices?(pSettingsVoices) }), (proc { addVoices(pSettingsVoices) }));
end
define :runTime do
setup();
(0...4).each do |quarter|
cue("time/quarter/#{quarter.to_s}");
4.times do
4.times do
wait(0.25);
cue("time/quarterTick");
end
set("time/tick", (tick + 1));
end
end
wrapup();
end
## live ========================================================================
# time state
set("chord/root", 0);
set("ideas/motifs", initialiseMotifs());
set("ideas/theme", nil);
set("ideas/themeIndex", nil);
set("ideas/themes", initialiseThemes());
set("time/cycle", 0);
set("time/tick", 0);
# time state functions
[:arps, :mels, :pads].each do |pVoiceType|
settings = gSettings[:voices][pVoiceType];
(0...settings[:play][:instruments].length).each do |i|
set("#{settings[:namePlural]}/#{i.to_s}", nil);
end
define "clear#{settings[:nameSingular].capitalize}".to_sym do |pVoiceNum|
set("#{settings[:namePlural]}/#{pVoiceNum.to_s}", nil);
end
define "clearAll#{settings[:namePlural].capitalize}".to_sym do
(0...settings[:play][:instruments].length).each do |i|
send("set#{settings[:nameSingular].capitalize}", i, nil);
end
end
define "count#{settings[:namePlural].capitalize}Taken".to_sym do
count = 0;
(0...settings[:play][:instruments].length).each do |i|
count += 1 if send("is#{settings[:nameSingular].capitalize}Taken?", i);
end
return count;
end
define "getAll#{settings[:namePlural].capitalize}".to_sym do
allVoices = [];
(0...settings[:play][:instruments].length).each do |i|
allVoices.push(send("get#{settings[:nameSingular].capitalize}", i));
end
return allVoices;
end
define "get#{settings[:nameSingular].capitalize}".to_sym do |pVoiceNum|
return get("#{settings[:namePlural]}/#{pVoiceNum.to_s}");
end
define "is#{settings[:nameSingular].capitalize}Free?".to_sym do |pVoiceNum|
return get("#{settings[:namePlural]}/#{pVoiceNum.to_s}").nil?;
end
define "is#{settings[:nameSingular].capitalize}Taken?".to_sym do |pVoiceNum|
return !get("#{settings[:namePlural]}/#{pVoiceNum.to_s}").nil?;
end
define "set#{settings[:nameSingular].capitalize}".to_sym do |pVoiceNum, pVoicing|
set("#{settings[:namePlural]}/#{pVoiceNum.to_s}", pVoicing);
end
define "setAll#{settings[:namePlural].capitalize}".to_sym do |pAllVoicing|
(0...settings[:play][:instruments].length).each do |i|
send("set#{settings[:nameSingular].capitalize}", i, pAllVoicing[i]);
end
end
end
# chords
live_loop :keepSpace do
keepProc(proc { runSpace() });
end
# motifs
live_loop :keepMotifs do
keepProc(proc { runMotifs() });
end
# themes