PACHINKO MOBILE (2018)
Pachinko mobile is written for 2-channel playback. It is inspired by Japanese pachinko halls that the composer heard on recordings. The music references EDM but never achieves to emulate it. This is not music to dance to. It constantly shifts riffs and tempo. It is an experiment in hyperactive use of beat and groove in what is otherwise an experimental sound world. The mobile in the title references not just the Italian term for motion, but also mobile phones. Aside from the familiar drum sounds in the piece (kick drum) all of the sounds derive from electromagnetic frequency activity on an iPhone recorded with a coil pickup while using various apps on the device.
SCORE
=begin
Note that this score is best viewed on a tablet, laptop, or desktop.
The line breaks on a mobile device may likely make reading the score confusing at times.
Comments are in black. Code and, sometimes, references to code elements within the comments are
given in blue.
=end
iPhone = "/Sonic Pi/Samples/ElectricDreamsSamplesAll/"
=begin
The variable is called iPhone because all of the audio files within this folder are recordings of
the electro-magnetic frequencies being emitted by my iPhone while in use.
It was an iPhone 6. Its battery went kablooey after 2 years and I didn't have AppleCare.
I was bitter at the time, but I'm over it. I'm left with these lovely sounds, like
picture postcards from a European holiday where all the photos were taken in the dead of winter.
=end
define :start_end do |cur_start, cur_length, max|
if cur_start <= (max / 2)
temp_start = cur_start + 1
elsif cur_start == max
temp_start = max - [0, 1].choose
else
temp_start = cur_start + [-1, 1].choose
end
if cur_length + temp_start >= max
temp_length = (max - temp_start) + rrand_i(2, 5)
elsif cur_length <= 3
temp_length = [3, 4].choose
else
temp_length = cur_length + [-1, 1].choose
end
return [temp_start, temp_length]
end
=begin
This piece is designed around a function called :main_groove. The function :main_groove takes 3 arguments: n_times (as in number of times), bpm (as in beats per minute), and seed. The :main_groove function is designed to generate a series of values and return a subset of them using a start value and a length value. For example, if the array [1,2,3,4,5,6,7,8] is generated, and the values for the frame variable are [2,3], then the subset would be [3,4,5]. The first frame value of 2 tells the computer the index with which to begin the subset. The second frame value tells the computer how many values to return, that is, the length of the array to return. With each repetition within the main function (:main_groove) of this piece, this frame will shift slightly. The :start_end function defined above determines this shift.
=end
define :main_groove do |n_times, bpm, seed|
use_random_seed seed
use_bpm bpm
rate_range = [[1.5, 5.3], [2.0, 6.0], [5.0, 8.0], [1.8, 3.8]].choose
# The rate_range variable determines the max and min values that will
# influence the rhythm of the work.
notes = (0..45).to_a.shuffle
# For the notes variable, I make use of the range feature in Ruby.
# It generates an array of integers from 0 to 45, then shuffle (randomly arrange) them.
# Running this function with a different seed value will return a new order via shuffle.
reps = stretch(1, notes.length).to_a
=begin
The built-in function stretch takes a value and repeats it to form an array. The reps variable will be an array with the value of 1 repeated 46 times, the length of notes. The variable serves as an initialization. It will be used to determine the number of repetitions of a sound. But with each repetition of a frame (discussed below), the values of reps gradually adjust to values greater than 1. We can hear this pretty clearly in the music. A riff will be repeated but certain elements will be repeated 2, 3, 4, etc. times, wherein the first time we hear the riff, each element is play only once.
=end
rates = line(rate_range[0], rate_range[1], steps: notes.length, inclusive: true).to_a.shuffle
=begin
To create an array of rate values for the variable rates the built-in function line is used. Line will take the first and second values in the rate_range array and return an array that gradual grows from the first to the second value over a given number of iterations. In this case, the length of the notes variable determines the length of this array. The result is then randomly shuffled.
=end
frame = [rrand_i((notes.length - 10), notes.length), rrand_i(5, 10)]
=begin
The frame variable is an array of 2 values. The first will be used as a starting point, the second as a length. This was addressed earlier with the :start_end function. Basically, all of these variables give us arrays of data that will be used for different components of the music. Below frame is used to extract a subset of adjacent values from the data above.
=end
n_times.times do
cur_frame = frame
# Over repetitions cur_frame will be updated. Hence, the frame values are a kind of initialization.
# In retrospect, I think that this could be left out and frame could just be used instead.
notes_frame = notes.last(frame[0]).take(frame[1])
reps_frame = reps.last(frame[0]).take(frame[1])
rates_frame = rates.last(frame[0]).take(frame[1])
# In the 3 lines of code above, the values of frame are used to return subsets of the notes, reps,
# and rates variables.
# Now we're cooking with grease. It's time to make some noise!
sample :drum_bass_soft # play the built-in sample called :drum_bass_soft.
# The kick drum sound will be played once while all of the following code runs "simultaneously".
frame[1].times do # Since the second value in the frame array indicates the length of the subarray.
# it can be used as the value for the number of repetitions of the following code.
# We complement the kick drum sound with a iPhone sample that is processed and played back
# very quickly - a rate of 1 is normal playback, a rate or 20 is 20x faster, and anything played
# back fast is also played back higher - to increase its pitch. We add echo and filtering to it.
# It sounds like an electronic hi-hat.
with_fx :echo, mix: 0.2, phase: 0.25, max_phase: 2 do
with_fx :hpf, mix: 1.0, cutoff: 60 do
sample iPhone, 35, rate: 20, amp: 1.5
end
end
cur_samp = notes_frame.tick(:notes)
cur_rate = rates_frame.tick(:rates)
with_fx :distortion, mix: 0.4, distort: 0.6 do # add distortion...of course.
reps_frame.tick(:reps).times do
# Use the values from reps to determine how many times the following sample is repeated.
sample iPhone, cur_samp, rate: cur_rate, amp: 0.5, pan: rrand(-0.25, 0.25)
=begin
cur_samp calls up a file within the iPhone folder. The value 16 would call up the 17th file. Note that panning of the sound is determined randomly. The rrand function chooses a floating point value between -0.25 and 0.25.
=end
sleep ((sample_duration(iPhone, cur_samp) / cur_rate) * 4.0).ceil / 4.0
# The sleep function controls the speed of program execution in Sonic Pi.
# It is equivalent to the wait function.
end # This end ends the reps_frame repetitions.
end # This end ends the with_fx processing.
end # This end ends the frame[1] repetitions.
# The following code is what adjusts the number of repetitions for each sound in future iterations
# of the n.times.times.
repsBeginning = reps.take(reps.length - frame[0])
# repsBeginning returns the reps values that are outside the current frame because they are too
# early in the array.
repsEnding = reps.last(frame[0] - frame[1])
# repsEnding returns the reps values that are outside the current frame because they are too late
# in the array.
# With the reps values within the current frame assign to the variable newFrameBit a new array
# in which...
newFrameBit = reps_frame.collect do |x| # For each value in reps_frame collect the following results
if x == 1
x + 1
else
x + [1, -1].choose
end
end
# So, if reps_frame = [1,2,1,3,3,2], newFrameBit could yield something like [2,1,2,2,4,1].
# By the way, the style of capitalization in newFrameBit is called camel case.
# 'Cuz of the humps. You get it?
# reps is updated to combine the three arrays into one. This is called concatenation, by the way.
reps = repsBeginning + newFrameBit + repsEnding
# Yay, we finally run :start_end to get a new frame! (Took a while to get around to using it.)
next_frame = start_end(cur_frame[0], cur_frame[1], notes.length)
frame = next_frame
# Assign next_frame to frame. This will, then, be assigned to cur_frame when the code is repeated.
# and then all of the frames for notes, reps, and rates will be adjusted accordingly.
tick_reset_all
# Reset all of the ticks so that the arrays are read from the beginning again with the repetition.
end
end
=begin
Now, we are ready to make the piece. Actually, this isn't the most efficient way to write the code for the piece. Do you see how many times the same function (:main_groove) is called? We could just tick through arrays of values for the arguments to :main_groove and then just have one instance of :main_groove.
For example, the first section (Groove 1) in the more efficient coding style:
args = [[1,168,10], [2,168,20], [1,168,10], [2,168,20]]
This array of arrays could be extended for the whole piece.
4.times do
cur_args = args.tick(:args)
main_groove(cur_args[0], cur_args[1], cur_args[2])
end
But with this coding style we won't be able to see the form of the piece, and that's really important in a score: hence, the lugubriousness.
=end
###############
## THE PIECE ##
###############
# The score is quite obvious. You can probably follow along when listening to the playback.
2.times do # Groove 1
main_groove(1, 168, 10)
main_groove(2, 168, 20)
end
3.times do # Groove 2
main_groove(2, 168, 48)
main_groove(2, 120, 52)
end
main_groove(1, 72, 72) # Fill 1
main_groove(4, 96, 60) # Groove 3
main_groove(1, 168, 1341) # Groove Transition 1
main_groove(2, 172, 1500)
main_groove(2, 132, 1341)
main_groove(2, 172, 1500)
main_groove(1, 112, 1341)
main_groove(2, 172, 1500)
main_groove(4, 84, 1341)
main_groove(2, 172, 1500)
# If you get lost when listening to the playback then know that this is the moment when all of the
# reverb comes in. That's a pretty obvious event to help you get back on track.
with_fx :reverb, mix: 0.25, damp: 0.85, room: 0.7 do # Space Out
with_fx :panslicer, mix: 0.7, phase: 0.125 do
with_fx :echo, mix: 0.25, decay: 4, phase: 0.4 do
main_groove(8, 58, 1341)
end
end
end
main_groove(2, 172, 1500)
main_groove(1, 110, 72) # Fill 2
main_groove(3, 240, 101)
main_groove(1, 48, 72)
main_groove(1, 96, 60) # Groove 3/Transition 2
main_groove(8, 240, 101)
main_groove(2, 96, 60)
main_groove(5, 240, 101)
main_groove(3, 96, 60)
main_groove(3, 240, 101)
main_groove(1, 110, 72)
main_groove(5, 96, 60)
main_groove(2, 240, 101)
2.times do # Groove 1
main_groove(1, 168, 10)
main_groove(2, 168, 20)
end
=begin
Following this, 3 recordings of heavily distorted random selections of iPhone files play for the last minute of the composition. It sounded like a pachinko hall to me: hence, the title of the work. This ends the piece. I don't remember how I did this and I can't find the code for it. So, I can't share it here. I tried to make the end as "hard core" as electro-magnetic blips from an iPhone can be.
=end