Last active
November 27, 2021 16:27
-
-
Save d0lfyn/4d57234a948f9219dda0856c79853745 to your computer and use it in GitHub Desktop.
Sections for Sonic Pi
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
## 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 | |
live_loop :keepThemes do | |
keepProc(proc { runThemes() }); | |
end | |
# voices | |
live_loop :keepArps do | |
keepProc(proc { runVoices(gSettings[:voices][:arps]) }); | |
end | |
live_loop :keepMels do | |
keepProc(proc { runMels() }); | |
end | |
live_loop :keepPads do | |
keepProc(proc { runVoices(gSettings[:voices][:pads]) }); | |
end | |
# time | |
wait(gSettings[:time][:numBeatsBufferBeforeTimeStarts]); | |
live_loop :keepTime do | |
keepProc(proc { runTime() }); | |
finishPiece() if isTimeUp?(); | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
sections | |
v0.2.0 (20210217) | |
by d0lfyn (twitter: @0delphini) | |
this program generates polyphonic music. | |
history: | |
v0.2.0 (20210217) | |
+ implement arps, mels, and pads sections | |
+ implement themes | |
+ implement support for unlimited number of midi ports | |
+ implement transposition | |
+ re-organise settings | |
+ revamp CC controls |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hello your advanced Sonic Pi code interests me quite a lot.
Do you explain somwhere how to use it?
run_file '..' does not seem to be the right usage.
Or at least .. it crahes on my
AMD A8-7100 Radeon R5, 8 Compute Cores 4C+4G 1.80 GHz
Windows 10 - system
with:
Timing Exception: thread got too far behind time
C:/Program Files/Sonic Pi/app/server/ruby/lib/sonicpi/lang/core.rb:4164:in
sleep' C:/Program Files/Sonic Pi/app/server/ruby/lib/sonicpi/lang/core.rb:4252:in
wait'eval:2409:in
block (2 levels) in __spider_eval' C:/Program Files/Sonic Pi/app/server/ruby/lib/sonicpi/runtime.rb:830:in
eval'C:/Program Files/Sonic Pi/app/server/ruby/lib/sonicpi/runtime.rb:830:in
block (2 levels) in __spider_eval' C:/Program Files/Sonic Pi/app/server/ruby/lib/sonicpi/runtime.rb:1093:in
block (2 levels) in __in_thread'Thank you very much!