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 hidden or 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 hidden or 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:inwait'eval:2409:in
block (2 levels) in __spider_eval' C:/Program Files/Sonic Pi/app/server/ruby/lib/sonicpi/runtime.rb:830:ineval'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:inblock (2 levels) in __in_thread'Thank you very much!