Last active
January 4, 2021 19:31
-
-
Save d0lfyn/3fd36359a6a9dac49146f74fa0a60558 to your computer and use it in GitHub Desktop.
Patterns 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
##| patterns | |
##| environment | |
use_bpm 480; | |
use_random_seed 0; | |
LOGGING = false; | |
##| domain | |
BASE_NOTE = :c3; | |
NUM_OCTAVES = 3; | |
DOMAIN = (scale BASE_NOTE, :major, num_octaves: NUM_OCTAVES); | |
##| notes | |
POSITION = 0; | |
DURATION = 1; | |
##| patterns | |
PATTERN_START_LENGTH = 4; ##| int [0,) | |
PATTERN_MAX_LENGTH = -1; ##| int [-1,) | -1 for infinitely long pattern | |
PATTERN_EVOLUTION_FACTOR = 60; ##| int [1,) != 0 | lower means more frequent mutations to pattern | |
PATTERN_GENERATION_FACTOR = 60; ##| int [-1,) != 0 | -1 for no new patterns, lower means more frequent pattern generation | |
PATTERN_GROWTH_FACTOR = 2; ##| int [0,) | lower means fewer notes appended per pattern development | |
PATTERN_TIME_TRANSFORM_FACTOR = 2; ##| int (0,) | lower means less transformation applied per pattern time transform | |
PATTERNS_MAX_NUMBER = 64; ##| int [-1,) | -1 for infinitely many patterns | |
DO_TIME_TRANSFORM = false; | |
##| timing | |
set :ticks, 0; | |
EVENT_GAP = 0.5; ##| (0,1] | |
LONGEST_DURATION = 2; ##| int [1,) | |
LONGEST_PAUSE = 8; ##| int [2,) | |
PULSE = 8; ##| (0,) | best if int | |
RHYTHM_AMP = 0.5; ##| [0,) | |
##| performance | |
ECHO_DECAY_MIN = 5; ##| (0,ECHO_DECAY_MAX] | |
ECHO_DECAY_MAX = LONGEST_DURATION * 10; ##| [ECHO_DECAY_MIN,) | |
ECHO_MIX_MIN = 0.1; ##| [0,1] | |
ECHO_MIX_MAX = 0.7; ##| [0,1] | |
ECHO_PHASE = 1; ##| [0,) | |
RELEASE_DURATION = 1; ##| [0,) | |
NUM_VOICES = 3; ##| i think 3 voices is optimal given the lack of constraints | |
##| Functions | |
##| general | |
define :generateDurations do | |
durations = []; | |
for i in 1..LONGEST_DURATION | |
durations.push i; | |
end | |
return durations; | |
end | |
define :generateMelodicIntervals do |pPosition| | |
intervals = []; | |
for i in 0..5 | |
intervals.push i if (pPosition + i) <= getTopPosition; | |
intervals.push -i if (pPosition - i) >= 0; | |
end | |
return intervals; | |
end | |
define :generatePositions do | |
positions = []; | |
for i in 0..getTopPosition | |
positions.push i; | |
end | |
return positions; | |
end | |
define :getTopPosition do | |
return (DOMAIN.length - 1); | |
end | |
##| notes | |
define :getNextNote do |pLastNote| | |
return [(pLastNote[POSITION] + generateMelodicIntervals(pLastNote[POSITION]).choose), | |
generateDurations.choose]; | |
end | |
define :getRandomNote do | |
return [generatePositions.choose, generateDurations.choose]; | |
end | |
define :invertNote do |pNote| | |
return [(getTopPosition - pNote[POSITION]), pNote[DURATION]]; | |
end | |
define :timeTransformNote do |pNote, pTransformation| | |
return [pNote[POSITION], (pNote[DURATION] * (1 + pTransformation))]; | |
end | |
define :transposeNote do |pNote, pSteps| | |
return [(pNote[POSITION] + pSteps), pNote[DURATION]]; | |
end | |
##| patterns | |
define :developPattern do |pPattern, pTimes| | |
pattern = pPattern.take(pPattern.length); | |
times = pTimes; | |
if (PATTERN_MAX_LENGTH >= 0) && (pattern.length >= PATTERN_MAX_LENGTH) | |
return pattern; | |
end | |
if pattern.length == 0 | |
if LOGGING | |
puts "new pattern"; | |
end | |
pattern.push getRandomNote; | |
times -= 1; | |
if times == 0 | |
return pattern; | |
end | |
end | |
if LOGGING | |
puts "developing existing pattern"; | |
end | |
for i in 0...times | |
lastNote = pattern[pattern.length - 1]; | |
pattern.push getNextNote(lastNote); | |
end | |
return pattern; | |
end | |
define :evolvePattern do |pPattern| | |
if LOGGING | |
puts "pattern evolving"; | |
end | |
if pPattern.length == 0 | |
return developPattern(pPattern, dice(PATTERN_GROWTH_FACTOR)); | |
end | |
case dice(4) | |
when 1 | |
return transposePattern(pPattern, [-getPatternTrough(pPattern), | |
(getTopPosition - getPatternPeak(pPattern))].choose); | |
when 2 | |
return developPattern(pPattern, dice(PATTERN_GROWTH_FACTOR)); | |
when 3 | |
return retrogressPattern(pPattern); | |
when 4 | |
return invertPattern(pPattern); | |
##| when 5 | |
##| if DO_TIME_TRANSFORM | |
##| return timeTransformPattern(pPattern); | |
##| else | |
##| return pPattern; | |
##| end | |
end | |
end | |
define :generatePattern do | |
return developPattern([], PATTERN_START_LENGTH); | |
end | |
define :getPatternDurations do |pPattern| | |
return pPattern.map {|n| n[DURATION]}; | |
end | |
define :getPatternPeak do |pPattern| | |
highest = 0; | |
pPattern.each {|n| highest = (n[POSITION] > highest) ? n[POSITION] : highest}; | |
return highest; | |
end | |
define :getPatternPitches do |pPattern| | |
return pPattern.map {|n| DOMAIN[n[POSITION]]}; | |
end | |
define :getPatternTrough do |pPattern| | |
lowest = getTopPosition(); | |
pPattern.each {|n| lowest = (n[POSITION] < lowest) ? n[POSITION] : lowest}; | |
return lowest; | |
end | |
define :invertPattern do |pPattern| | |
if LOGGING | |
puts "inverting"; | |
end | |
return pPattern.map{|n| invertNote n}; | |
end | |
define :isInRange do |pPattern, pLowPos, pHighPos| | |
return pPattern.none? {|n| (n[POSITION] < pLowPos) || (n[POSITION] > pHighPos)}; | |
end | |
define :retrogressPattern do |pPattern| | |
if LOGGING | |
puts "retrogressing"; | |
end | |
result = []; | |
pPattern.each{|n| result.unshift(n)}; | |
return result; | |
end | |
define :timeTransformPattern do |pPattern| | |
if LOGGING | |
puts "time transforming"; | |
end | |
transformation = 0; | |
while transformation == 0 | |
transformation = rrand(-1, PATTERN_TIME_TRANSFORM_FACTOR); | |
end | |
if transformation < -0.9 | |
return []; | |
else | |
return pPattern.map {|n| timeTransformNote(n, transformation)}; | |
end | |
end | |
define :transposePattern do |pPattern, pSteps| | |
if LOGGING | |
puts "transposing"; | |
end | |
return pPattern.map{|n| transposeNote(n, pSteps)}; | |
end | |
##| performance | |
define :calculatePitch do |pPosition| | |
return DOMAIN[pPosition]; | |
end | |
define :performPattern do |pPattern, pInstrument| | |
if pPattern != nil | |
if LOGGING | |
puts "performing " + pPattern.to_s; | |
end | |
sample :elec_tick, amp: RHYTHM_AMP; | |
isInBassRange = (calculateHz(getPatternTrough(pPattern)) <= 140); | |
with_fx :pan, pan: rrand(-1, 1) do | |
with_fx :hpf, cutoff: (calculatePitch(getPatternTrough(pPattern) + 5)) do | |
with_fx :reverb, mix: (isInBassRange ? 0 : 0.2) do | |
with_fx :echo, amp: 0.2, decay: rrand(ECHO_DECAY_MIN, ECHO_DECAY_MAX), | |
phase: ECHO_PHASE, mix: (isInBassRange ? 0 : rrand(ECHO_MIX_MIN, ECHO_MIX_MAX)) do | |
for i in 0...pPattern.length | |
n = pPattern[i]; | |
duration = n[DURATION] - ((i == (pPattern.length - 1)) ? EVENT_GAP : 0); | |
pitch = calculatePitch(n[POSITION]); | |
amplitude = (((i == 0) || isOnDownbeat(get[:ticks])) ? 0.8 : 0.4) + (isInBassRange ? 0.2 : 0); | |
use_synth pInstrument; | |
play pitch, amp: amplitude, sustain: 0, release: RELEASE_DURATION; | |
sleep duration; | |
end | |
end | |
end | |
end | |
end | |
end | |
if LOGGING | |
puts pInstrument.to_s + " resting"; | |
end | |
sleep dice(LONGEST_PAUSE) - EVENT_GAP; | |
end | |
##| timing | |
define :isOnDownbeat do |pTicks| | |
return (pTicks % PULSE) == 0; | |
end | |
##| Live | |
##| variables | |
patterns = [generatePattern]; | |
live_loop :performFullRangeTri do | |
sync :time; | |
if NUM_VOICES > 0 | |
performPattern(patterns.choose, :tri); | |
end | |
end | |
##| live_loop :performLowRangeFM do | |
##| sync :time; | |
##| performPattern((patterns.select {|p| isInRange(p, 0, (DOMAIN.length / 2))}).choose, :fm); | |
##| end | |
##| live_loop :performHighRangePulse do | |
##| sync :time; | |
##| performPattern((patterns.select {|p| isInRange(p, (DOMAIN.length / 2), (DOMAIN.length - 1))}).choose, :pulse); | |
##| end | |
live_loop :performFullRangeFM do | |
sync :time; | |
if NUM_VOICES > 1 | |
performPattern(patterns.choose, :fm); | |
end | |
end | |
live_loop :performFullRangePulse do | |
sync :time; | |
if NUM_VOICES > 2 | |
performPattern(patterns.choose, :pulse); | |
end | |
end | |
live_loop :performFullRangeSine do | |
sync :time; | |
if NUM_VOICES > 3 | |
performPattern(patterns.choose, :sine); | |
end | |
end | |
live_loop :performFullRangeSquare do | |
sync :time; | |
if NUM_VOICES > 4 | |
performPattern(patterns.choose, :square); | |
end | |
end | |
live_loop :changeLife do | |
if ((PATTERNS_MAX_NUMBER == -1) || | |
((PATTERNS_MAX_NUMBER >= 0) && (patterns.length < PATTERNS_MAX_NUMBER))) && | |
(PATTERN_GENERATION_FACTOR != -1) && one_in(PATTERN_GENERATION_FACTOR) | |
patterns.push generatePattern; | |
end | |
deleteIndices = []; | |
for i in 0...patterns.length | |
if patterns[i].length == 0 | |
deleteIndices.push i; | |
end | |
if one_in(PATTERN_EVOLUTION_FACTOR) | |
patterns.push evolvePattern(patterns[i]); | |
if patterns.length > PATTERNS_MAX_NUMBER | |
deleteIndices.push rand_i(PATTERNS_MAX_NUMBER); | |
end | |
end | |
end | |
for i in deleteIndices.reverse | |
patterns.delete_at i; | |
end | |
if LOGGING | |
puts patterns.length.to_s + "/" + PATTERNS_MAX_NUMBER.to_s; | |
end | |
sleep 1; | |
end | |
live_loop :keepTime do | |
cue :time; | |
if isOnDownbeat(get[:ticks]) | |
sample :elec_tick, amp: RHYTHM_AMP; | |
elsif isOnDownbeat(get[:ticks] - (PULSE / 2)) | |
sample :elec_tick, amp: RHYTHM_AMP / 2; | |
end | |
sleep 1; | |
set :ticks, (get[:ticks] + 1); | |
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
patterns | |
v0.0.9 (20210104) | |
by d0lfyn (twitter: @0delphini) | |
a "pattern-oriented" music generator which, over time, | |
creates worlds in which patterns evolve and interweave | |
by chance | |
the complexity of the piece increases quickly up | |
until a carrying capacity for patterns is reached, | |
whereafter existing patterns evolve steadily | |
history: | |
v0.0.1 (20210101) | |
+ initial implementation | |
v0.0.2 (20210101) | |
+ add isInRange calculation | |
+ add stereo FX | |
+ add pattern bank size limit | |
v0.0.3 (20210101) | |
+ reformulate getTopPosition | |
+ add time transformation (buggy and unused) | |
+ add system variable range documentation | |
+ preserve all patterns until carrying capacity reached | |
+ reintroduce third voice | |
v0.0.4 (20210102) | |
+ arrange function groups alphabetically | |
+ make code consistent | |
+ add means not to generate new patterns | |
- remove time transformation evolution case (creates redundant patterns) | |
+ complete documentation of global constants | |
+ add logging mode | |
+ fix pattern deletion mechanism | |
v0.0.5 (20210102) | |
+ add echo phase constant | |
+ optimise performance module | |
v0.0.6 (20210103) | |
+ add 2 more voices and voice number setting | |
v0.0.7 (20210103) | |
+ correct decay range documentation | |
+ reorder variables by order of declaration | |
+ rename PATTERN_CHANGE_INTERVAL to CHANGE_INTERVAL | |
v0.0.8 (20210104) | |
+ stress first note of each pattern | |
+ reduce echo amp | |
+ remove bass echo | |
+ amplify bass | |
+ add pulse and stress downbeats | |
+ add high-pass filter to performance chain | |
v0.0.9 (20210104) | |
+ correctly stress downbeat | |
+ add electric ticks to downbeats and pattern triggers |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment