Last active
January 18, 2021 02:21
-
-
Save d0lfyn/7a4da954b13e2548ad98639e40c9daf9 to your computer and use it in GitHub Desktop.
Prototypes 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
##| prototypes | |
##| environment | |
BPM = 300; | |
use_bpm BPM; | |
RANDOM_SEED = Time.new.to_i; | |
PRINT_RANDOM_SEED_REMINDER = true; | |
use_random_seed RANDOM_SEED; | |
LOGGING = false; | |
##| domain | |
BASE_NOTE = :E2; | |
NUM_OCTAVES = 4; | |
DOMAIN = (scale BASE_NOTE, :aeolian, num_octaves: NUM_OCTAVES); | |
##| general | |
DISSONANT_CHROMATIC_INTERVALS = [1, 2, 6, 10, 11]; ##| e.g. [1, 2, 6, 10, 11] | |
FAVOUR_CONSONANT_MELODY_CHANCE = 0.05; ##| [0,1] | |
FAVOUR_MELODIC_INTERVALS_CHANCE = 1; ##| [0,1] | |
FAVOUR_INTERVALS_IN_RANGE_CHANCE = 1; ##| [0,1] | |
FAVOUR_SMALL_INTERVALS_CHANCE = 0.05; ##| [0,1] | |
FAVOUR_LARGE_INTERVALS_CHANCE = 0; ##| [0,1] | |
FAVOUR_BRIEF_DURATIONS_CHANCE = 0; ##| [0,1] | |
FAVOUR_LONG_DURATIONS_CHANCE = 0; ##| [0,1] | |
##| notes | |
POSITION = 0; DURATION = 1; | |
PP = 0.1; P = 0.2; MP = 0.4; MF = 0.6; | |
##| prototypes | instantiable sequences of notes | |
PROTOTYPE_MIN_START_LENGTH = 10; ##| int [1,) | |
PROTOTYPE_MAX_START_LENGTH = 20; ##| int [PROTOTYPE_MIN_START_LENGTH,) | |
PROTOTYPE_MIN_LENGTH = 1; ##| int [0,) | |
PROTOTYPE_MAX_LENGTH = -1; ##| int [0,) | -1 for infinitely long prototype | |
PROTOTYPE_MAX_GROWTH = 16; ##| int [0,) | |
PROTOTYPE_MAX_CHOP = 16; ##| int [0,) | |
PROTOTYPE_EVOLUTION_CHANCE = 0.01; ##| [0,1] | |
PROTOTYPE_GENERATION_CHANCE = 0.01; ##| [0,1] | |
PROTOTYPES_MAX_TOTAL = 32; ##| int [1,) | -1 for infinitely many | |
PROTOTYPES_MAX_ACTIVE = 1; ##| int [0,PROTOTYPES_MAX_TOTAL] | |
PROTOTYPES_TO_KEEP = 1; ##| int [0,PROTOTYPES_MAX_TOTAL] | |
PROTOTYPE_MAX_RANGE = 5; ##| int [0,) | -1 for unlimited range | |
FAVOUR_CONSONANT_HARMONY_CHANCE = 1; ##| [0,1] | |
FAVOUR_FREQUENCY_COMPATIBILITY_CHANCE = 1; ##| [0,1] | |
FAVOUR_TONIC_AND_DOMINANT_CHANCE = 0.5; ##| [0,1] | |
FAVOUR_BRIEF_PROTOTYPES_CHANCE = 0; ##| [0,1] | |
FAVOUR_LONG_PROTOTYPES_CHANCE = 0; ##| [0,1] | |
##| performance | |
ECHO_DECAY_MIN = 5; ##| (0,ECHO_DECAY_MAX] | |
ECHO_DECAY_MAX = 2 * 10; ##| [ECHO_DECAY_MIN,) | |
ECHO_MIX_MIN = 0.1; ##| [0,1] | |
ECHO_MIX_MAX = 0.7; ##| [0,1] | |
ECHO_PHASE = 1; ##| [0,) | |
RELEASE_DURATION = 0.25; ##| (0,1] | -1 for full note duration | |
REPETITION_CHANCE = 1; ##| [0,1] | |
MAX_REPETITIONS = 16; ##| int [1,) | |
VELOCITY_MAKEUP = 0.1; ##| (,) | |
NUM_VOICES = 4; ##| [0,5] | |
set :voice0Playing, nil; | |
set :voice1Playing, nil; | |
set :voice2Playing, nil; | |
set :voice3Playing, nil; | |
set :voice4Playing, nil; | |
VOICE_0_PLAY_CHANCE = 0.75; ##| [0,1] | |
VOICE_1_PLAY_CHANCE = 0.75; ##| [0,1] | |
VOICE_2_PLAY_CHANCE = 0.75; ##| [0,1] | |
VOICE_3_PLAY_CHANCE = 0.75; ##| [0,1] | |
VOICE_4_PLAY_CHANCE = 0.75; ##| [0,1] | |
MIDI_MODE = true; ##| voices on channels [1-NUM_VOICES], rhythm on [NUM_VOICES+1,(NUM_VOICES*2)+2] | |
MIDI_PORT = "loopmidi_port"; | |
##| timing | |
set :ticks, 0; | |
DECISION_BUFFER_TIME = 5; ##| [0,) | gives machine time to keep up with decision calculations | |
LONGEST_DURATION = 2; ##| int [1,) | |
PAUSE = (DECISION_BUFFER_TIME * NUM_VOICES); | |
PULSE = 16; ##| (0,) | best if even int | |
END_AFTER_MINUTES = -1; ##| [0,) | -1 for never-ending music | |
##| Functions | |
##| general | |
define :chooseDuration do |pDurations| | |
durations = []; | |
if (FAVOUR_BRIEF_DURATIONS_CHANCE > 0) | |
if (!evaluateChance?(FAVOUR_LONG_DURATIONS_CHANCE)) | |
durations = pDurations.select {|d| evaluateChance?((FAVOUR_BRIEF_DURATIONS_CHANCE / d).to_f)}; | |
end | |
elsif (FAVOUR_LONG_DURATIONS_CHANCE > 0) | |
durations = pDurations.select {|d| !evaluateChance?((FAVOUR_LONG_DURATIONS_CHANCE / d).to_f)}; | |
end | |
if durations.empty? | |
return pDurations.choose; | |
end | |
return durations.choose; | |
end | |
define :chooseInterval do |pIntervals| | |
intervals = []; | |
if (FAVOUR_SMALL_INTERVALS_CHANCE > 0) | |
if (!evaluateChance?(FAVOUR_LARGE_INTERVALS_CHANCE)) | |
intervals = pIntervals.select {|i| evaluateChance?((FAVOUR_SMALL_INTERVALS_CHANCE / (i.abs + 1)).to_f)}; | |
end | |
elsif (FAVOUR_LARGE_INTERVALS_CHANCE > 0) | |
intervals = pIntervals.select {|i| !evaluateChance?((FAVOUR_LARGE_INTERVALS_CHANCE / (i.abs + 1)).to_f)}; | |
end | |
if intervals.empty? | |
return pIntervals.choose; | |
end | |
return intervals.choose; | |
end | |
define :evaluateChance? do |pChance| | |
return (pChance == 1) || ((pChance > 0) && (rand < pChance)); | |
end | |
define :filterAbsIntervalsForConsonance do |pAbsIntervals| | |
return pAbsIntervals.select {|i| isDiatonicIntervalConsonant?(i)}; | |
end | |
define :filterIntervalsForContour do |pIntervals| | |
return pIntervals.select {|i| (i >= -5) && (i <= 5)}; | |
end | |
define :filterIntervalsForRange do |pSequence, pIntervals| | |
leeway = PROTOTYPE_MAX_RANGE - getSequenceRange(pSequence); | |
maxPeak = getSequencePeak(pSequence) + leeway; | |
minTrough = getSequenceTrough(pSequence) - leeway; | |
lastPosition = pSequence[pSequence.length - 1]; | |
return pIntervals.select {|i| ((i + lastPosition) <= maxPeak) && ((i + lastPosition) >= minTrough)}; | |
end | |
define :generateDurations do | |
durations = []; | |
for i in 1..LONGEST_DURATION | |
durations.push i; | |
end | |
return durations; | |
end | |
define :generateInnerPositionsCoveredBySequence do |pSequence| | |
return generatePositionsInRange(getSequenceTrough(pSequence) + 1, getSequencePeak(pSequence) - 1); | |
end | |
define :generateOctaveIntervals do | |
intervals = []; | |
for i in -7..7 | |
intervals.push i; | |
end | |
return intervals; | |
end | |
define :generatePositions do | |
positions = []; | |
for i in 0..getTopPosition | |
positions.push i; | |
end | |
return positions; | |
end | |
define :generatePositionsCoveredBySequence do |pSequence| | |
return generatePositionsInRange(getSequenceTrough(pSequence), getSequencePeak(pSequence)); | |
end | |
define :generatePositionsInRange do |pLower, pUpper| | |
positions = []; | |
if pLower < pUpper | |
for i in pLower..pUpper | |
positions.push i; | |
end | |
end | |
return positions; | |
end | |
define :getAbsInterval do |pAbsPosition, pInterval| | |
return pInterval + pAbsPosition; | |
end | |
define :getAbsPosition do |pZeroPosition, pPosition| | |
return pPosition - pZeroPosition; | |
end | |
define :getRelativeInterval do |pAbsPosition, pAbsInterval| | |
return pAbsInterval - pAbsPosition; | |
end | |
define :getSequencePeak do |pSequence| | |
highestPitch = -9999; | |
pSequence.each {|pitch| highestPitch = ((pitch > highestPitch) ? pitch : highestPitch)}; | |
return highestPitch; | |
end | |
define :getSequenceRange do |pSequence| | |
if pSequence.empty? | |
return 0; | |
end | |
return getSequencePeak(pSequence) - getSequenceTrough(pSequence); | |
end | |
define :getSequenceTrough do |pSequence| | |
lowestPitch = 9999; | |
pSequence.each {|pitch| lowestPitch = ((pitch < lowestPitch) ? pitch : lowestPitch)}; | |
return lowestPitch; | |
end | |
define :getTopPosition do | |
return (DOMAIN.length - 1); | |
end | |
define :isChromaticIntervalConsonant? do |pInterval| | |
interval = pInterval % 12; | |
return DISSONANT_CHROMATIC_INTERVALS.none? {|di| di == interval}; | |
end | |
define :isDiatonicIntervalConsonant? do |pInterval| | |
return (((pInterval % 2) == 0) && ((pInterval % 6) != 0)) || ((pInterval % 5) == 0) || ((pInterval % 7) == 0); | |
end | |
define :isPositionDominant? do |pPosition| | |
return (pPosition % 7) == 4; | |
end | |
define :isPositionTonic? do |pPosition| | |
return (pPosition % 7) == 0; | |
end | |
##| notes | |
define :areNotesConsonant? do |pNote1, pNote2| | |
if pNote1.nil? || pNote2.nil? | |
return true; | |
end | |
return arePositionsConsonant?(pNote1[POSITION], pNote2[POSITION]); | |
end | |
define :arePositionsConsonant? do |pPosition1, pPosition2| | |
if pPosition1.nil? || pPosition2.nil? | |
return true; | |
end | |
return isChromaticIntervalConsonant?(note(DOMAIN[pPosition1]) - note(DOMAIN[pPosition2])); | |
end | |
define :generateNextNote do |pSequence, pLastNote| | |
intervals = generateOctaveIntervals; | |
if evaluateChance?(FAVOUR_CONSONANT_MELODY_CHANCE) | |
absPosition = getAbsPosition(pSequence[0][POSITION], pLastNote[POSITION]); | |
intervals = filterAbsIntervalsForConsonance(intervals.map {|i| getAbsInterval(absPosition, i)}). | |
map{|ai| getRelativeInterval(absPosition, ai)}; | |
end | |
if evaluateChance?(FAVOUR_MELODIC_INTERVALS_CHANCE) | |
intervals = filterIntervalsForContour(intervals); | |
end | |
if evaluateChance?(FAVOUR_INTERVALS_IN_RANGE_CHANCE) | |
intervals = filterIntervalsForRange(pSequence, intervals); | |
end | |
durations = generateDurations; | |
if intervals.empty? | |
return nil; | |
end | |
return [(pLastNote[POSITION] + chooseInterval(intervals)), chooseDuration(durations)]; | |
end | |
define :generateProtoNote do | |
return [0, generateDurations.choose] | |
end | |
define :generateRandomNote do | |
return [generatePositions.choose, generateDurations.choose]; | |
end | |
define :invertNote do |pNote| | |
return [(7 - pNote[POSITION]), pNote[DURATION]]; | |
end | |
define :transposeNote do |pNote, pSteps| | |
return [(pNote[POSITION] + pSteps), pNote[DURATION]]; | |
end | |
##| prototypes | |
define :chopPrototype do |pPrototype, pNumberToChop| | |
if LOGGING | |
puts "chopping" | |
end | |
if pNumberToChop >= pPrototype.length | |
return []; | |
end | |
if one_in(2) | |
return pPrototype.drop(pNumberToChop); | |
else | |
return pPrototype.take(pPrototype.length - pNumberToChop); | |
end | |
end | |
define :developPrototype do |pPrototype, pTimes| | |
prototype = pPrototype.take(pPrototype.length); | |
times = pTimes; | |
if (times == 0) || ((PROTOTYPE_MAX_LENGTH != -1) && (prototype.length >= PROTOTYPE_MAX_LENGTH)) | |
return prototype; | |
end | |
if prototype.empty? | |
if LOGGING | |
puts "new prototype"; | |
end | |
prototype.push generateProtoNote; | |
times -= 1; | |
end | |
if LOGGING | |
puts "developing " + times.to_s + " times"; | |
end | |
for i in 0...times | |
lastNote = prototype[prototype.length - 1]; | |
nextNote = generateNextNote(getSequenceFromPrototype(prototype), lastNote); | |
if nextNote.nil? | |
return prototype; | |
else | |
prototype.push nextNote; | |
end | |
end | |
return prototype; | |
end | |
define :evolvePrototype do |pPrototype| | |
if LOGGING | |
puts "evolving"; | |
end | |
case dice(4) | |
when 1 | |
if PROTOTYPE_MAX_GROWTH == 0 | |
return pPrototype; | |
end | |
growthAmount = rand_i(PROTOTYPE_MAX_GROWTH + 1); | |
if growthAmount == 0 | |
return pPrototype; | |
end | |
return developPrototype(pPrototype, growthAmount); | |
when 2 | |
return retrogradePrototype(pPrototype); | |
when 3 | |
return invertPrototype(pPrototype); | |
when 4 | |
if PROTOTYPE_MAX_CHOP == 0 | |
return pPrototype; | |
end | |
chopAmount = rand_i(PROTOTYPE_MAX_CHOP + 1); | |
if chopAmount == 0 | |
return pPrototype; | |
end | |
return chopPrototype(pPrototype, chopAmount); | |
when 5 | |
return permutePrototype(pPrototype); | |
end | |
end | |
define :favourPrototypes do |pPrototypes| | |
pool = pPrototypes.take(pPrototypes.length); | |
if evaluateChance?(FAVOUR_BRIEF_PROTOTYPES_CHANCE) | |
if !evaluateChance?(FAVOUR_LONG_PROTOTYPES_CHANCE) | |
pool = filterPrototypesBrief(pool); | |
end | |
elsif evaluateChance?(FAVOUR_LONG_PROTOTYPES_CHANCE) | |
pool = filterPrototypesLong(pool); | |
end | |
return pool; | |
end | |
define :filterPrototypesBrief do |pPrototypes| | |
pool = pPrototypes.take(pPrototypes.length); | |
minLength = (PROTOTYPE_MAX_LENGTH != -1) ? (PROTOTYPE_MAX_LENGTH * LONGEST_DURATION) : 999999999; | |
pool.each do |p| | |
totalDuration = getPrototypeTotalDuration(p); | |
minLength = (totalDuration < minLength) ? totalDuration : minLength; | |
end | |
pool.select! {|p| getPrototypeTotalDuration(p) == minLength}; | |
return pool; | |
end | |
define :filterPrototypesLong do |pPrototypes| | |
pool = pPrototypes; | |
maxLength = 0; | |
pool.each do |p| | |
totalDuration = getPrototypeTotalDuration(p); | |
maxLength = (totalDuration > maxLength) ? totalDuration : maxLength; | |
end | |
pool.select! {|p| getPrototypeTotalDuration(p) == maxLength}; | |
return pool; | |
end | |
define :filterPrototypesRange do |pPrototypes, pLower, pUpper| | |
return pPrototypes.select{|p| (getPrototypeTrough(p) >= pLower) && (getPrototypePeak(p) <= pUpper)}; | |
end | |
define :generateInnerPositionsCoveredByPrototype do |pPrototype| | |
return generateInnerPositionsCoveredBySequence(getSequenceFromPrototype(pPrototype)); | |
end | |
define :generatePositionsCoveredByPrototype do |pPrototype| | |
return generatePositionsCoveredBySequence(getSequenceFromPrototype(pPrototype)); | |
end | |
define :generatePrototype do | |
return developPrototype([], rrand_i(PROTOTYPE_MIN_START_LENGTH, (PROTOTYPE_MAX_START_LENGTH + 1))); | |
end | |
define :getPrototypeRange do |pPrototype| | |
return getSequenceRange(getSequenceFromPrototype(pPrototype)); | |
end | |
define :getPrototypesToDelete do |pPrototypes| | |
pool = pPrototypes.take(pPrototypes.length); | |
if evaluateChance?(FAVOUR_BRIEF_PROTOTYPES_CHANCE) | |
if !evaluateChance?(FAVOUR_LONG_PROTOTYPES_CHANCE) | |
pool = filterPrototypesLong(pool); | |
end | |
elsif evaluateChance?(FAVOUR_LONG_PROTOTYPES_CHANCE) | |
pool = filterPrototypesBrief(pool); | |
end | |
return pool; | |
end | |
define :getPrototypeDurations do |pPrototype| | |
return pPrototype.map {|n| n[DURATION]}; | |
end | |
define :getPrototypePeak do |pPrototype| | |
return getSequencePeak(getSequenceFromPrototype(pPrototype)); | |
end | |
define :getPrototypePitches do |pPrototype| | |
return pPrototype.map {|n| DOMAIN[n[POSITION]]}; | |
end | |
define :getPrototypeTotalDuration do |pPrototype| | |
sum = 0; | |
pPrototype.each {|n| sum += n[DURATION]}; | |
return sum; | |
end | |
define :getPrototypeTrough do |pPrototype| | |
return getSequenceTrough(getSequenceFromPrototype(pPrototype)); | |
end | |
define :getSequenceFromPrototype do |pPrototype| | |
return pPrototype.map {|n| n[POSITION]}; | |
end | |
define :instantiatePrototype do |pPrototype, pLowerLimit, pUpperLimit| | |
if pPrototype.nil? | |
return nil; | |
end | |
inPlay = []; | |
firstNotes = []; | |
for p in [get[:voice0Playing], get[:voice1Playing], get[:voice2Playing], get[:voice3Playing], get[:voice4Playing]] | |
if !p.nil? | |
inPlay.push generateInnerPositionsCoveredByPrototype(p); | |
firstNotes.push p[0][POSITION]; | |
end | |
end | |
inPlay.flatten!; | |
inPlay.uniq!; | |
rangePositions = generatePositionsInRange(0, getPrototypeRange(pPrototype)); | |
spots = generatePositions.drop(pLowerLimit); | |
spots = ((pUpperLimit - pLowerLimit) < spots.length) ? spots.take(pUpperLimit - pLowerLimit) : []; | |
spots = (rangePositions.length < spots.length) ? spots.take(spots.length - rangePositions.length) : []; | |
spots.shuffle!; | |
if evaluateChance?(FAVOUR_CONSONANT_HARMONY_CHANCE) | |
spots.select! {|s| firstNotes.all? {|fn| arePositionsConsonant?(fn, s)}}; | |
end | |
if firstNotes.empty? || evaluateChance?(FAVOUR_TONIC_AND_DOMINANT_CHANCE) | |
spots.select! {|s| isPositionTonic?(s) || isPositionDominant?(s)}; | |
end | |
if spots.length > 0 | |
if evaluateChance?(FAVOUR_FREQUENCY_COMPATIBILITY_CHANCE) | |
for spot in spots | |
spotFound = true; | |
for pos in rangePositions | |
if inPlay.any? {|playing| (playing == (spot + pos))} | |
spotFound = false; | |
break; | |
end | |
end | |
if (spotFound == true) | |
return transposePrototype(pPrototype, (spot - getPrototypeTrough(pPrototype))); | |
end | |
end | |
else | |
spot = spots.take(1)[0]; | |
return transposePrototype(pPrototype, (spot - getPrototypeTrough(pPrototype))); | |
end | |
end | |
return nil; | |
end | |
define :invertPrototype do |pPrototype| | |
if LOGGING | |
puts "inverting"; | |
end | |
return pPrototype.map{|n| invertNote n}; | |
end | |
define :isInRange? do |pPrototype, pLowPos, pHighPos| | |
return pPrototype.none? {|n| (n[POSITION] < pLowPos) || (n[POSITION] > pHighPos)}; | |
end | |
define :permutePrototype do |pPrototype| | |
if LOGGING | |
puts "permuting"; | |
end | |
return pPrototype.shuffle; | |
end | |
define :retrogradePrototype do |pPrototype| | |
if LOGGING | |
puts "retrograding"; | |
end | |
result = []; | |
pPrototype.each{|n| result.unshift(n)}; | |
return result; | |
end | |
define :rootPrototype do |pPrototype| | |
if pPrototype.empty? | |
return pPrototype; | |
end | |
offset = pPrototype[0][POSITION]; | |
if offset == 0 | |
return pPrototype; | |
end | |
return transposePrototype(pPrototype, -offset); | |
end | |
define :transposePrototype do |pPrototype, pSteps| | |
if LOGGING | |
puts "transposing"; | |
end | |
return pPrototype.map{|n| transposeNote(n, pSteps)}; | |
end | |
##| performance | |
define :calculatePitch do |pPosition| | |
return DOMAIN[pPosition]; | |
end | |
define :calculateHz do |pPosition| | |
return midi_to_hz(calculatePitch(pPosition)); | |
end | |
##| timing | |
define :hasPieceEnded? do |pTicks| | |
return (END_AFTER_MINUTES != -1) && (pTicks > (END_AFTER_MINUTES * BPM)); | |
end | |
define :isOnDownbeat? do |pTicks| | |
return (pTicks % PULSE) == 0; | |
end | |
##| Live | |
if MIDI_MODE | |
midi_all_notes_off port: MIDI_PORT; | |
end | |
##| variables | |
set :prototypes, [generatePrototype]; | |
set :activePrototypes, get[:prototypes].pick(PROTOTYPES_MAX_ACTIVE); | |
##| functions | |
define :choosePrototype do | |
numActivePrototypes = get[:activePrototypes].length; | |
nextPrototype = nil; | |
if (numActivePrototypes > 0) | |
activePrototypes = get[:activePrototypes].take(numActivePrototypes); | |
nextPrototype = activePrototypes.delete_at rand_i(numActivePrototypes); | |
set :activePrototypes, activePrototypes; | |
end | |
return nextPrototype; | |
end | |
define :decideNextInstance do |pVoice, pInstance| | |
case pVoice | |
when 0 | |
set :voice0Playing, pInstance; | |
when 1 | |
set :voice1Playing, pInstance; | |
when 2 | |
set :voice2Playing, pInstance; | |
when 3 | |
set :voice3Playing, pInstance; | |
when 4 | |
set :voice4Playing, pInstance; | |
end | |
if LOGGING | |
puts "voice " + pVoice.to_s + " decides to play " + pInstance.to_s; | |
end | |
end | |
define :perform do |pVoice, pInstance, pInstrument| | |
if !pInstance.nil? | |
if LOGGING | |
puts "voice " + pVoice.to_s + " playing " + pInstance.to_s; | |
end | |
repetitions = 1; | |
if evaluateChance?(REPETITION_CHANCE) | |
repetitions = dice(MAX_REPETITIONS); | |
end | |
repetitions.times do | |
if MIDI_MODE | |
midi_note_on :c4, vel_f: P, port: MIDI_PORT, channel: (NUM_VOICES * 2) + pVoice + 2; | |
else | |
sample :elec_tick, amp: P; | |
end | |
isInBassRange = (calculateHz(getPrototypeTrough(pInstance)) <= 140); | |
with_fx :pan, pan: rrand(-1, 1) do | |
with_fx :hpf, cutoff: (calculatePitch(getPrototypeTrough(pInstance) + 5)) do | |
with_fx :reverb, pre_mix: (isInBassRange ? 0 : 0.2) do | |
with_fx :echo, amp: 0.2, decay: rrand(ECHO_DECAY_MIN, ECHO_DECAY_MAX), phase: ECHO_PHASE, pre_mix: (isInBassRange ? 0 : rrand(ECHO_MIX_MIN, ECHO_MIX_MAX)) do | |
for i in 0...pInstance.length | |
n = pInstance[i]; | |
pitch = calculatePitch(n[POSITION]); | |
amplitude = (((i == 0) || isOnDownbeat?(get[:ticks] + 1)) ? P : PP) + | |
(!MIDI_MODE && isInBassRange ? PP : 0) + | |
VELOCITY_MAKEUP; | |
use_synth pInstrument; | |
if MIDI_MODE | |
midi_note_on pitch, vel_f: amplitude, port: MIDI_PORT, channel: pVoice + 1; | |
midi_note_on :c4, vel_f: PP, port: MIDI_PORT, channel: NUM_VOICES + pVoice + 2; | |
else | |
play pitch, amp: amplitude, sustain: 0, release: ((RELEASE_DURATION == -1) ? n[DURATION] : RELEASE_DURATION); | |
sample :elec_tick, amp: PP; | |
end | |
sleep ((RELEASE_DURATION == -1) ? n[DURATION] : RELEASE_DURATION); | |
if MIDI_MODE | |
midi_note_off pitch, vel_f: 96, port: MIDI_PORT, channel: pVoice + 1; | |
midi_note_off :c4, vel_f: 96, port: MIDI_PORT, channel: NUM_VOICES + pVoice + 2; | |
end | |
sleep n[DURATION] - ((RELEASE_DURATION == -1) ? n[DURATION] : RELEASE_DURATION); | |
end | |
end | |
end | |
end | |
end | |
if MIDI_MODE | |
midi_note_off :c4, port: MIDI_PORT, channel: (NUM_VOICES * 2) + pVoice + 2; | |
end | |
end | |
case pVoice | |
when 0 | |
set :voice0Playing, nil; | |
when 1 | |
set :voice1Playing, nil; | |
when 2 | |
set :voice2Playing, nil; | |
when 3 | |
set :voice3Playing, nil; | |
when 4 | |
set :voice4Playing, nil; | |
end | |
end | |
if LOGGING | |
puts "voice " + pVoice.to_s + " resting"; | |
end | |
sleep PAUSE - (pVoice * 0.2 * DECISION_BUFFER_TIME); | |
end | |
##| loops | |
live_loop :performVoice0 do | |
if (NUM_VOICES > 0) && !hasPieceEnded?(get[:ticks]) | |
sync :decide0; | |
nextInstance = nil; | |
if evaluateChance?(VOICE_0_PLAY_CHANCE) | |
nextInstance = instantiatePrototype(choosePrototype, 0, getTopPosition); | |
end | |
decideNextInstance(0, nextInstance); | |
sync :perform; | |
perform(0, nextInstance, :tri); | |
else | |
sleep 999999999; | |
end | |
end | |
live_loop :performVoice1 do | |
if (NUM_VOICES > 1) && !hasPieceEnded?(get[:ticks]) | |
sleep (0.5 * 0.2 * DECISION_BUFFER_TIME); | |
sync :decide1; | |
nextInstance = nil; | |
if evaluateChance?(VOICE_1_PLAY_CHANCE) | |
nextInstance = instantiatePrototype(choosePrototype, 0, getTopPosition); | |
end | |
decideNextInstance(1, nextInstance); | |
sleep (0.5 * 0.2 * DECISION_BUFFER_TIME); | |
sync :perform; | |
perform(1, nextInstance, :fm); | |
else | |
sleep 999999999; | |
end | |
end | |
live_loop :performVoice2 do | |
if (NUM_VOICES > 2) && !hasPieceEnded?(get[:ticks]) | |
sleep 0.2 * DECISION_BUFFER_TIME; | |
sync :decide2; | |
nextInstance = nil; | |
if evaluateChance?(VOICE_2_PLAY_CHANCE) | |
nextInstance = instantiatePrototype(choosePrototype, 0, getTopPosition); | |
end | |
decideNextInstance(2, nextInstance); | |
sleep 0.2 * DECISION_BUFFER_TIME; | |
sync :perform; | |
perform(2, nextInstance, :pulse); | |
else | |
sleep 999999999; | |
end | |
end | |
live_loop :performVoice3 do | |
if (NUM_VOICES > 3) && !hasPieceEnded?(get[:ticks]) | |
sleep (1.5 * 0.2 * DECISION_BUFFER_TIME); | |
sync :decide3; | |
nextInstance = nil; | |
if evaluateChance?(VOICE_3_PLAY_CHANCE) | |
nextInstance = instantiatePrototype(choosePrototype, 0, getTopPosition); | |
end | |
decideNextInstance(3, nextInstance); | |
sleep (1.5 * 0.2 * DECISION_BUFFER_TIME); | |
sync :perform; | |
perform(3, nextInstance, :sine); | |
else | |
sleep 999999999; | |
end | |
end | |
live_loop :performVoice4 do | |
if (NUM_VOICES > 4) && !hasPieceEnded?(get[:ticks]) | |
sleep (2 * 0.2 * DECISION_BUFFER_TIME); | |
sync :decide4; | |
nextInstance = nil; | |
if evaluateChance?(VOICE_4_PLAY_CHANCE) | |
nextInstance = instantiatePrototype(choosePrototype, 0, getTopPosition); | |
end | |
decideNextInstance(4, nextInstance); | |
sleep (2 * 0.2 * DECISION_BUFFER_TIME); | |
sync :perform; | |
perform(4, nextInstance, :square); | |
else | |
sleep 999999999; | |
end | |
end | |
live_loop :changePrototypes do | |
sync :change; | |
prototypes = get[:prototypes].take(get[:prototypes].length); | |
changed = false; | |
if evaluateChance?(PROTOTYPE_GENERATION_CHANCE) | |
prototypes.push generatePrototype; | |
changed = true; | |
end | |
prototypes.pick(PROTOTYPES_MAX_ACTIVE).each do |p| | |
if evaluateChance?(PROTOTYPE_EVOLUTION_CHANCE) | |
prototypes.push rootPrototype(evolvePrototype(p)); | |
changed = true; | |
end | |
end | |
if changed | |
prototypes.select! {|p| (p.length >= PROTOTYPE_MIN_LENGTH)}; | |
prototypes.uniq!; | |
(((prototypes.length - PROTOTYPES_MAX_TOTAL) > 0) ? (prototypes.length - PROTOTYPES_MAX_TOTAL) : 0).times do | |
prototypes.delete_at rrand_i(PROTOTYPES_TO_KEEP, prototypes.length); | |
end | |
set :prototypes, prototypes; | |
if LOGGING | |
puts prototypes.length.to_s + "/" + PROTOTYPES_MAX_TOTAL.to_s; | |
end | |
end | |
sleep DECISION_BUFFER_TIME; | |
end | |
live_loop :keepBeat do | |
if !hasPieceEnded?(get[:ticks]) | |
if isOnDownbeat?(get[:ticks]) | |
if MIDI_MODE | |
midi_note_on :c4, vel_f: MP, port: MIDI_PORT, channel: NUM_VOICES + 1; | |
else | |
sample :elec_tick, amp: MP; | |
end | |
elsif isOnDownbeat?(get[:ticks] - (PULSE / 2)) | |
if MIDI_MODE | |
midi_note_on :c4, vel_f: P, port: MIDI_PORT, channel: NUM_VOICES + 1; | |
else | |
sample :elec_tick, amp: P; | |
end | |
end | |
end | |
sleep 1; | |
set :ticks, (get[:ticks] + 1); | |
end | |
live_loop :keepTime do | |
cue :decide0; | |
sleep (0.2 * DECISION_BUFFER_TIME); | |
cue :decide1; | |
sleep (0.2 * DECISION_BUFFER_TIME); | |
cue :decide2; | |
sleep (0.2 * DECISION_BUFFER_TIME); | |
cue :decide3; | |
sleep (0.2 * DECISION_BUFFER_TIME); | |
cue :decide4; | |
sleep (0.2 * DECISION_BUFFER_TIME); | |
cue :perform; | |
cue :change; | |
if PRINT_RANDOM_SEED_REMINDER | |
puts RANDOM_SEED; | |
end | |
sleep 0.5; | |
set :activePrototypes, get[:prototypes].pick(PROTOTYPES_MAX_ACTIVE); | |
sleep 0.5; | |
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
prototypes | |
v0.0.10 (20210117) | |
by d0lfyn (twitter: @0delphini) | |
a program that generates "pattern-oriented" music in a style i call "essentialism", which focuses on the process of bringing ideas into reality, and which further reflects on the processes of living and being. this program embodies a simple process of composing and performing music. | |
history: | |
v0.0.1 (20210107) | |
+ initial implementation | |
v0.0.2 (20210108) | |
+ replace Array #keep_if call with #select | |
v0.0.3 (20210108) | |
+ rename VELOCITY_ADJUSTMENT to VELOCITY_MAKEUP | |
+ replace former constant CHANGE_BUFFER_TIME | |
v0.0.4 (20210109) | |
+ introduce active prototypes | |
+ optimise change loop | |
+ add chop evolution | |
+ add ending mechanism | |
+ add random seed reminder | |
v0.0.5 (20210110) | |
+ add choose interval function with adjustable small interval favouring | |
+ revise consonance function to determine consonance relative to first note of prototype | |
+ add choose duration function with brief and long duration favouring | |
+ preserve n first prototypes with PROTOTYPES_TO_KEEP | |
v0.0.6 (20210112) | |
+ transpose evolved prototypes back to 0 | |
+ add PROTOTYPE_MIN_LENGTH | |
+ remove filterPrototypesBrief and filterPrototypesLong "organism" calls | |
v0.0.7 (20210113) | |
+ add voice play chance | |
+ add evaluateChance and refactor all chance calculations to use it | |
+ rename function calls for clarity and conciseness | |
+ add FAVOUR_LARGE_INTERVALS_CHANCE | |
+ fix MIDI tick channels | |
v0.0.8 (20210114) | |
+ introduce DISSONANT_INTERVALS | |
+ create and distinguish chromatic and diatonic interval calculations | |
v0.0.9 (20210114) | |
+ make instantiatePrototype operate on one prototype only | |
+ introduce activePrototypes time-state variable | |
+ replace comparisons with nil with concise equivalent | |
v0.0.10 (20210117) | |
+ add tonic and dominant spot favouring (always choose tonic or dominant if nothing is playing) | |
+ add permutation evolution |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment