Skip to content

Instantly share code, notes, and snippets.

@d0lfyn
Last active January 18, 2021 02:21
Show Gist options
  • Save d0lfyn/7a4da954b13e2548ad98639e40c9daf9 to your computer and use it in GitHub Desktop.
Save d0lfyn/7a4da954b13e2548ad98639e40c9daf9 to your computer and use it in GitHub Desktop.
Prototypes for Sonic Pi
##| 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
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