Hey all! I haven’t posted in a while. Been keeping myself busy with teaching and coding tunes. I’ve been spending a lot of time on MIDI, I’m really enjoying the generative abilities of SPi in conjunction with synth sound design. Since I can’t share that code with y’all, I decided to make a little electro tune with General MIDI to show some of the techniques I’ve been using in my creations. I’m happier with the results than I expected, considering it’s the generic midi sounds. If you have Windows this should work immediately. Otherwise you’re going to need to find a MIDI softsynth that will play GM/GS or play it over a keyboard that does GM and change the port variable.
port = "microsoft_gs_wavetable_synth"
#set up the channels and make sure everything is switched before continuing.
midi_pc 38, port: port, channel: 1
midi_pc 94, port: port, channel: 2
midi_pc 3, port: port, channel: 3
midi_pc 80, port: port, channel: 4
sleep 0.1
use_bpm 65
with_midi_defaults port: port, channel: 10 do
live_loop :drums, delay: 8 do
stop if tick > 165
#First kick is set, next 3 are random
midi 35
sleep 0.125
3.times do
midi 36 if one_in(7)
sleep 0.125
end
#Snare is set and alternates, claps are random
midi [40, 38].ring.tick(:sn)
sleep 0.125
3.times do
midi 39 if one_in(4)
sleep 0.125
end
end
live_loop :hats do
stop if tick > 63
#hats do their own thing in time. the strong beat is steady, the secondary beat is random.
(range, 2, 15).choose.times do
midi 44, vel: rrand_i(107, 127)
sleep 0.125
midi 42, vel: rrand_i(64, 96) if one_in(3)
sleep 0.125
end
#play an open hat at the end of the line half the time. There is always a little bit of
#space after a hat run to let them breathe for a second.
midi 46, vel: rrand_i(64, 96) if one_in(2)
sleep [0.25, 0.5, 0.75].choose
end
end
with_midi_defaults port: port, channel: 1 do
live_loop :bass, delay: 64 do
#an end for the basslne
if tick == 67
midi 32, sustain: 0.25
sleep 0.25
midi 27, sustain: 0.125
sleep 0.125
midi 30, sustain: 0.125
sleep 0.125
midi 27, sustain: 0.25
stop
end
#The bass does its thing in a relative pentatonic, no chord progression necessary.
#sync if allows the bass to wander around for a while and then come back on key beats
sync :drums if one_in(3)
6.times do
#I use nlen (note length) a lot to keep sustain/release relative to the sleep duration.
#In this case we jump between a 75% and 100% gate randomly relative to the sleep.
nlen = [0.125, 0.25, 0.375].choose
midi (scale, 27, :minor_pentatonic).choose, sustain: nlen * [0.75, 1].choose
sleep nlen
end
end
end
with_midi_defaults port: port, channel: 2 do
live_loop :pads do
stop if tick > 63
#notes is the root of the stack.
notes = [51, 49, 47, 46, 51, 49, 47, 49, 51, 49, 47, 46, 51, 49, 47, 53].ring
midi notes.look , sustain: 2, vel: 90
#note2 is a fifth up except on the final chord where it's raised a half step
note2 = notes.look + 7
note2 += 1 if look % 16 == 15
midi note2 , sustain: 2, vel: 84
#the last note is an octave higher
midi notes.look + 12 , sustain: 2, vel: 78
sleep 2
end
end
with_midi_defaults port: port, channel: 3 do
live_loop :chords, delay: 32 do
stop if tick > 63
#setting up the chord degrees and inversions for anything folowing the progression.
deg = [1, 3, 4, 5].ring.look
inv = [0, -1, -2, -2].ring.look
2.times do
#broadcast the degree and inversion to anything syncing
cue :cho, deg: deg, invert: inv
#this alternates between the set inversion and 2 inversions up
notes = (chord_invert,(chord_degree, deg, 51, :minor, 3 ) , inv + [0, 2].ring.tick(:inv))
notes.each do |n|
midi n , sustain: 1, vel: 85
end
sleep 1
end
end
end
with_midi_defaults port: port, channel: 4 do
live_loop :mel, delay: 96 do
#end on the root of the tonic
if tick > 17
midi 63, sustain: 2, vel: 112
stop
end
cho = sync :cho
#never start right in time with the kick, always a slight delay
sleep [ 0.25, 0.5].choose
#long enough to make a phrase, short enough so that the line doesn't run into the wrong chord
5.times do
nlen = [0.25, 0.5, 0.75].choose
notes = (chord_invert,(chord_degree, cho[:deg], 63, :minor, 4 ) , cho[:invert] + [0, 1, 2].choose)
midi notes.choose , sustain: nlen * 1, vel: rrand_i(96, 112)
sleep nlen
end
end
end