top of page

IN MEMORIAM UMILK (2020)

It's really all due to a 25 cent piece of plastic - a lever in my Nespresso machine that broke a hinge and stopped functioning. Its role was to direct the flow of liquid from the machine. When a capsule was pressed against it, it would flip and pour the coffee out the front nozzle; when the capsule was released after brewing, it would flip back and the remaining liquid would fall into the spent capsule basket.  I asked Nespresso if I could buy a replacement. They said no, but that I could fix it for $150. So, I cut out the plastic grill on the front of the capsule basket and for a year and a half just stuck my coffee cup into the basket to catch my brew as it dripped out. But after an 18-month fight with planned obsolescence, I caved. It's the holidays. "Honey, we should gift ourselves a new Nespresso machine for Christmas, right?," I proposed to my wife. She was fully supportive. But old Nelly wasn't going to be sent off to the glue factory without a photo. So, I recorded 3 takes of her in my studio before bringing her over to Nespresso to trade in and get a new machine. In Memoriam UMilk is a Ligeti-esque Requiem for a faithful machine. It involves 1,872 brewed coffee playbacks, layered and transposed based on the overtone series. A Nespresso coffee maker is in the end a simple machine, and pitch shifting a recording of it evokes other machines. At its slowest, it is a creaking ship; at its thickest, a horde of hornets. Still, I think we can hear the original throughout, placed in multiple, yet simultaneous time layers, and for some reason this projects a sense of memory for me.

The video below offers a glimpse of the recording process. Apologies for the poor audio quality. Below that is the recording of the composition.

Description
Recording
Score

SCORE

set_recording_bit_depth! 24

nespresso = "/Desktop/NespressoMachine/Final"

bits = "/Desktop/NespressoMachine/Bits"

 

=begin

The parameters of this work consist of arrays of values and indices. For each array of values, there is a corresponding array of index values that are used to retrieve array values. The parameters are sample, start, attack, decay, rate, and pan.

 

The sample_vals array is used to selected 1 of the 3 available samples of the Nespresso machine making coffee.

The start_vals, attack_vals, decay_vals, rate_vals, and pan_vals all influence sample playback. Start values are offset values for playback of the sample. Attack and decay values influence the envelop of the sample playback. The rate value influences the playback speed. The pan values (obviously) determine the panning of the sample playback.

=end

 

sample_vals = [0,1,2]

start_vals = [0.2,0.3,0.5]

attack_vals = [1.5,2.5,4,6.5]

decay_vals = [8,13,21]

rate_vals = [0.03125, 0.0625, 0.09375, 0.125, 0.15625, 0.1875, 0.21875,

             0.25, 0.28125, 0.3125, 0.34375, 0.375, 0.40625, 0.4375, 0.46875,

             0.5, 0.53125, 0.5625, 0.59375, 0.625, 0.65625, 0.6875, 0.71875,

             0.75, 0.78125, 0.8125, 0.84375, 0.875, 0.90625, 0.9375, 0.96875, 1.0]             

pan_vals = [-0.6,-0.5,-0.4,-0.3,-0.2,-0.1,0.0,0.1,0.2,0.3,0.4,0.5,0.6]

 

# Determining rate of playback is the most complex aspect of this work. The minimum playback speed is

# 0.03125 and the maximum is 3.1. This is explained below in more detail. Here simply note that

# rate_base and rate_transpose are components in determining the rate value.

rate_base = [16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]

rate_transpose = [0.0,0.1,0.2,0.3,0.5,0.8,1.3,2.1,1.3,0.8,0.5,0.3,0.2,0.1]

 

# A "shift" value is applied to both start offset and attack during sample playback. Note it is 13

# values long and like bpm_values below it is ticked through at the outermost .times block, which is

# repeated 13 times.

start_vals_shift = [0.0,0.01,0.02,0.03,0.04,0.05,0.06,0.07,0.08,0.09,0.1,0.11,0.12]

 

# The BPM updates through the course of the piece. Note that the values here are logarithmically

# related, using the 12th root of 2, the same value used for the equal tempered scale (and also tempo

# in Stockhausen's Gruppen). They have been rounded off.With the "octave" (144) added, this array is

# 13 values long. 

bpm_vals = [72, 76, 80, 85, 90, 96, 101, 107, 114, 121, 128, 135, 144]

 

# The sample_index array is all permutations of 3 values, [0,1,2].

sample_index = [0, 1, 2, 0, 2, 1, 1, 0, 2, 1, 2, 0, 2, 0, 1, 2, 1, 0]

# start_index is just a rotation of the values in sample_index. It is rotated by 1 value.

start_index = [1, 2, 0, 2, 1, 1, 0, 2, 1, 2, 0, 2, 0, 1, 2, 1, 0, 0] 

# attack index contains all permutations of 4 values [0,1,2,3].

attack_index = [0, 1, 2, 3, 0, 1, 3, 2, 0, 2, 1, 3, 0, 2, 3, 1, 0, 3,

                2, 1, 0, 3, 1, 2, 1, 0, 2, 3, 1, 0, 3, 2, 1, 2, 0, 3,

                1, 2, 3, 0, 1, 3, 0, 2, 1, 3, 2, 0, 2, 0, 1, 3, 2, 0,

                3, 1, 2, 1, 0, 3, 2, 1, 3, 0, 2, 3, 0, 1, 2, 3, 1, 0,

                3, 0, 1, 2, 3, 0, 2, 1, 3, 1, 0, 2, 3, 1, 2, 0, 3, 2,

                1, 0, 3, 2, 0, 1]

# decay_index is just a rotation of the values in sample_index as well. It is rotated by 3 values.

decay_index = [0, 2, 1, 1, 0, 2, 1, 2, 0, 2, 0, 1, 2, 1, 0, 0, 1, 2]

 

# rate index is an LCG ordering of mod 16.

rate_index = [11, 10, 5, 12, 15, 14, 9, 0, 3, 2, 13, 4, 7, 6, 1, 8]

 

# pan_index is an LCG ordering of mod 13. a = 1; Xn = 5; c = 3; m = 13

pan_index = [5, 8, 11, 1, 4, 7, 10, 0, 3, 6, 9, 12, 2]

 

##### THE SCORE #####

 

=begin

I mention in the description of this work that the Nespresso machine serves up 1,872 cups of coffee over the course of the piece. You can see this by multiplying all of the .times values below. They sum to this value. To understand the process of this work, we need to start with the most nested .times block, which is named Times Block 1. I've named each block below for ease of reference.

 

Times Block 1 creates 4-note "chords", each with a unique offset, envelop, rate, and pan.

 

Times Block 2 simply controls the speed of Times Block 1 repetition.

 

Times Block 3 updates the rate offset every 16 playbacks (4*4). What is rate offset? Note that the rate_vals array is 32 values in length, but the rate_index is only 16 values in length. The rate_base value shifts the base index value for rate_index. For instance, if rate_base is 16, then the rate_index effective range is 16-31. If rate_base is 0, then the rate_index effective range is 0-15. This has substantial formal influence. Lower range values produce slower playback. Higher range values, faster. This is quite audible in the work.

 

Times Block 4 also influences the flow of the music by controlling the speed at which Times Block 3 repeats. Additionally, Times Block 4 updates the rate_transposition value, set to the variable trans. Rate transposition is a value added to each sample playback. It is also used to adjust the wait length for Times Block 2 and 3. At its place in the hierarchy, each rate_transposition value is applied to 48 playbacks (4*4*3). The rate_transposition array is 14 values and is cycled almost 3 times in the work. Like Times Block 3 it has a noticeable influence on the form of the work.

 

Times Block 5, like Times Block 4, is used to update parameters. Here the updates occur with every 144 sample playbacks. As mentioned above, the bpm_vals is 13 values long; hence, the reason that this .times block is repeated 13 times. The sv_shift (shift to the start value) is also updated here. Its source array (start_vals_shift) is also 13 values. Again, this time block has substantial influence on the formal experience of the work. All values for wait, start, and attack are given in BPM. Hence, the gradual doubling the BPM over the course of the piece via exponential increase is very perceptible. 

=end

 

# Times Block 5 

13.times do

  

  cur_bpm = bpm_vals.tick(:bpm)

  use_bpm cur_bpm

  sv_shift = start_vals_shift.tick(:svs)

  

  # Times Block 4

  3.times do

    

    trans = rate_transpose.tick(:rt)

    

    # Times Block 3

    3.times do

      

      rate_offset = rate_base.tick(:rb)

      

      # Times Block 2

      4.times do

        

        # Times Block 1

        4.times do

          sample nespresso, sample_vals[sample_index.tick(:sample)],

            start: start_vals[start_index.tick(:start)]-sv_shift,

            attack: attack_vals[attack_index.tick(:attack)]-sv_shift,

            decay: decay_vals[decay_index.tick(:decay)],

            decay_level: 0.0, sustain: 0, sustain_level: 0.0, release: 0,

            rate: rate_vals[rate_index.tick(:rate)+rate_offset]+trans,

            pan: pan_vals[pan_index.tick(:pan)]

        end

        

        wait 3 - trans

        

      end

      

      wait 5 - trans

      

    end

    

  end

  

end

 

=begin

This is the second part that comes at the end. It is overlapped with the first part. Aligning them in the score via code would have been incredibly time consuming since I would have to run the work repeatedly to find the exact spot to place the entrance of the second part, which it only enters after 15 minutes of music. Hence, for practical purposes, I rendered this second part separately and combined the two parts in post-production.

 

This part uses samples of opening and closing the hatch used to place the capsule in the machine. It is in three parts. Look closely at the second one. It is just a version of the first, but the array values are reversed and there is an additional divisor operation applied to the wait value. This additional divisor causes smaller r values to increase the rate of playback. Note that the value 5.4 in (5.4 - r) yields 1.0 when given the first r value (5.4 - 4.4). Hence, this second part starts off at the same speed that the first one ended.

=end

 

[1.1,1.2,1.3,1.5,1.8,2.3,3.1,4.4].each do |r|

  

  32.times do

    

    temp_samp = rand_i(9)

    sample bits, temp_samp, pan: rrand(-0.5, 0.5), rate: rrand(0.9, r)

    wait choose([0.33,0.22,0.11]) / r

    

  end

  

end

 

[1.1,1.2,1.3,1.5,1.8,2.3,3.1,4.4].reverse.each do |r|

  

  32.times do

    

    temp_samp = rand_i(9)

    sample bits, temp_samp, pan: rrand(-0.5, 0.5), rate: rrand(0.9, r)

    wait (choose([0.33,0.22,0.11]) / r) / (5.4 - r)

    

  end

  

end

 

[1.1,1.2,1.3,1.5,1.8,2.3,3.1,4.4].each do |r|

  

  32.times do

    

    temp_samp = [1,2,4,5,7,8].choose

    sample bits, temp_samp, pan: rrand(-0.5, 0.5), rate: rrand(0.9 * r, r)

    wait 0.03 / r

    

  end

  

end

bottom of page