top of page

JOJO'S RICE BOWLS (2020)

A former student wrote a piece using a cutting board from Ikea, beer (mostly) bottles, and a set of rice bowls. I borrowed the rice bowls and sampled them to create a virtual instrument. The work is one of my little machines. It uses multiple number series derived from the linear congruential generator formula, each running at their own pace. The program is a (fairly) complex process that creates a kind of musical behavior. It is like music from an unfamiliar culture - it seems logically coherent but its logic is too distant to understand.

Description
Recording
Score

SCORE

# 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.

 

=begin

This is the score to Jojo's Rice Bowls. The samples used are from 4 rice bowls that a former student of mine used in her work for solo percussion. I sampled them to create a virtual instrument. All samples are individual hits. There are 4 takes at 4 dynamic levels for the 4 instruments. Hence, 64 samples in total. For example and to help visualize this, these are the samples recorded for rice bowl 1: 

 

p take 1

p take 2

p take 3

p take 4

mf take 1

mf take 2

mf take 3

mf take 4

f take 1

f take 2

f take 3

f take 4

ff take 1

ff take 2

ff take 3

ff take 4

=end

 

RiceBowls = "/Desktop/JojosRiceBowls/Samples/"

set_recording_bit_depth! 24

 

use_random_seed 123

 

use_bpm 63

 

=begin

Much of the following code sets constants for the work.

 

The 4 values in an array assigned to pan will serve as panning values for each sample played back. The panning is determined by the instrument. The first instrument will call up the first value and pan it slightly to the left (-0.15), the fourth instrument will call up the fourth value to pan it slightly to the right (0.15). The second and third get panned even more slightly. Since all of the instruments were recorded centered in front of a microphone, this technique creates the illusion that the instruments are spaced within a stereo field as they would be if recording a percussionist playing them live. 

=end

pan = [-0.15, -0.05, 0.05, 0.15]

 

=begin

The work is built from a series of events. Each event is a series of samples called up in a certain rhythm. The event_lengths constant determines those lengths. Note that the values are 7, 8, 9 multiplied by 1, 2, and 4. This ensures that there are short events, average length events, and long events. This is one of a number of ways to ensure this. I've tried other ways in other works. I thought I'd try this one here. I wanted equal probability of short and long events and not too many choices from the pool of short and long number of events.

=end

event_lengths = [7, 8, 9, 14, 16, 18, 28, 32, 36]

 

# The event_nth array will be iterated through to call up the event_lengths values.

event_nth = [3, 2, 7, 0, 8, 4, 6, 5, 1, 6, 4, 5, 0, 7, 8, 3, 1, 2, 0, 6, 3, 0, 6,

             3, 0, 6, 3, 3, 8, 1, 0, 5, 7, 6, 2, 4, 6, 1, 8, 0, 4, 2, 3, 7, 5]

 

=begin

The fours_permutations constant is an array with all of the possible permutations of the array [0,1,2,3]. Note that they are arranged so that there are no adjacently repeated values, that is, the last value of a permutation isn't the same as the first value of the succeeding permutation. This array has a few roles in the work. In particular, it is used to select the take of a given instrument at a given dynamic. Due to the design of the array, I am ensured that even at a repeated dynamic level no sample gets repeated consecutively. This enhances the realism of the virtual instrument. It avoids what is called the "machine gun" effect.

=end

fours_permutations = [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, 0, 1, 3, 2, 1, 0]

 

=begin

The cycle_series_choice is the heart of the algorithm. It determines whether an event will be a series event or a cycle event. It is the first decision made in the algorithm. If it's 0, then a series event will occur, and if it's 1, then a cycle series will occur. Note that the array was generated so that the number of 0s in it is equal to the number values in the series_selection array, ensuring that all values in the series_selection array are called.

=end

cycle_series_choice = [0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1,

                       1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1,

                       0, 1, 0, 1, 1, 1, 1, 0]

 

=begin

There are 3 types of series in this work, when a value in the series_selection array is called up, then it determines which type is applied. Note that there are 3 values here and the array is all permutations of three values - [0,1,2]. Related to this, note that cycle_series_choice has only 2 values, fours_permutations has 4 values. Coming up, we'll see that dyns_selection has 6 values, rhythm_selection has 5 values. That's not a coincidence; it's a design decision that helps create the kind of musical behavior I'm seeking in the work. This piece is like a little machine. The cogs join but also rotate at their own speeds. There is no completion of a process in this work. That would only happen when the LCM of all array lengths was reached. I've not figured out what that number is, but it's very large. The LCM for just cycle_series_choice and rhythm_selection is 12,980, which means that cycle_series_choice would need to be repeated 295 times, making the process about 17 hours long. And certainly the LCM of all of these array lengths is more than 12,980. In the end, I just chose to go with about 3.5 minutes of this process, determined by the cycle_series_choice and its exhaustion of the series_selection array.

=end

series_selection = [1, 2, 0, 2, 1, 0, 2, 0, 1, 0, 2, 1, 0, 1, 2, 1, 0, 2]

 

=begin

There are 4 cycles in the work. These are just combinations of the four instruments. So, there can be 1, 2, 3, or 4 instruments placed into a pattern. Okay, yeah, I know, one instrument isn't a pattern, two instruments is really just alternation, etc. So, maybe it's not the best name. The cycle_selection array is used to choose the number of instruments in the cycle.

=end

cycle_selection = fours_permutations.reverse

 

=begin

dyns_selection is used to choose a dynamic behavior, of which there are 6.

 

1. loud

2. quiet

3. crescendo

4. diminueno

5. sometimes loud

6. sometimes accent

 

The difference between the last two is that for "sometimes loud" the p/mf dynamic is generally used and occasionally there is a ff, whereas for "sometimes accent", the p dynamic is used and occasionally there is a mf or f.

=end

dyns_selection = [1, 4, 1, 4, 1, 4, 1, 5, 3, 1, 5, 3, 1, 0, 5, 4, 3, 2, 2, 3, 4, 5,

                  0, 1, 2, 4, 0, 2, 4, 0]

=begin

rhythm_selection is used to choose a rhythmic behavior, of which there are 5.

 

1. even

2. random

3. cyclical

4. accelerando

5. ritardando

=end

rhythm_selection = [0, 1, 2, 4, 3, 0, 1, 3, 2, 4, 0, 1, 3, 4, 2, 0, 1, 4, 2, 3, 0,

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

                    0, 1]

 

=begin

The following 3 series are arrays generated using the linear congruential generator. This is the algorithm used to create pseudo-random numbers, but the creation of random numbers is only simulated if the formula is given the correct values, which are very large and prime or values to the power of 2. If you use it with smaller collections of values, you get sometimes complete, sometimes incomplete arrangements of all values. There is no real randomness when generating values with an LCG, so you can determine if the result is a complete rearrangement of the values involved or incomplete, repetitive, etc. Series1 and Series2 use different input values for the components of the formula and hence their outcomes are different. More specifically, series2 is generated by recursively calling the formula with the multiplier value changing (likely from 1 to 3 but I don't completely remember). Series1 is generated by recursively calling the formula with the increment value changing (again likely from 1 to 4, but I can't remember).

=end

series1 = [2, 3, 0, 1, 3, 1, 3, 1, 0, 3, 2, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1,

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

           0, 1, 1, 1, 1, 1]

 

series2 = [3, 1, 3, 1, 0, 2, 0, 2, 1, 3, 1, 3, 2, 0, 2, 0, 0, 2, 0, 2, 2,

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

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

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

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

 

series3 = fours_permutations

 

=begin

These following constants (quartet_vals, trio_vals, etc.) are helpful for the main algorithm of the work. They allow the program to jump by 1, 2, 3, or 4 values in the fours_permutations array. More details are below.

=end

quartet_vals = range(1, 12, inclusive: true)

trio_vals = range(1, 18, inclusive: true)

pairs_vals = range(1, 24, inclusive: true)

singles_vals = range(0,47,inclusive: true)

 

count = 0

 

# This is the foundation of the whole piece. For each value in cycle_series_choice

cycle_series_choice.each do |n| # do the following

  # The count variable is there so that I can keep track of where things are in the process     

  # as the code executes.

  count  = count  + 1 # Increase count by 1.

  

  print "This is event:  #{count}" # Print to the console which event number it is.

  

  # Get the next value in the event_length array and set it to temp_len.

  temp_len = event_lengths[event_nth.tick(:evt_n)]

  

  # Figure out what the pitches are. To do this, first we need to see if cycle_series_choice

  # value on this iteration is 0 or 1. Then, we know if we will use a series or a cycle.

  event_pitches = if n == 0 then # if it's 0, then do the following stuff.

    # Set the next series_selection value to temp_series_select

    temp_series_select = series_selection.tick(:ser_sel)

    

    # Now we test for which series to use. 

    case temp_series_select

    when 0

      # Collect temp_len values in series1. If temp_len is 7, then collect the next

      # 7 values in the series1 array, for example. These values will then be set to

      # event_pitch.

      temp_len.times.collect { series1.tick(:ser1) } 

    when 1

      temp_len.times.collect { series2.tick(:ser2) }

    when 2

      temp_len.times.collect { series3.tick(:ser3) }

    end

  else # But if temp_series_select is something else (and it could only be 1 but we don't

       # have to test for that since we know that's the only option)

    # Set the next cycle_selection value to temp_cycle_select

    temp_cycle_select = cycle_selection.tick(:cyc_sel)

    # Now we test for which cycle to use.

    case temp_cycle_select

    when 0

      # if it's 0, then tick to the next array value in singles_vals and set to temp_single

      temp_single = singles_vals.tick(:s_val).to_i

      # Use temp_single as an index value and get that index in fours_permutations

      # and repeat temp_len times.

      temp_len.times.collect { fours_permutations[temp_single] }

    when 1

      # if it's 1, then tick to the next array value in pairs_vals, multiply by 2 and

      # take that number of values from fours_permutations starting at the beginning of

      # the array. Then, take the last two values in that array.

      # This allows us to "tick" through pairs of values in fours_permutations.

      temp_pair = fours_permutations.take(2 * pairs_vals.tick(:p_val)).last(2)

      # Divide temp_len by 2 and then round up (that's what ceil as in ceiling does).

      # Then repeat that many times temp_pair, flatten that array into a single array,

      # and take the first temp_len values. This process ensures that you get enough

      # values. For instance, if temp_len is 7, then divided by two is 3.5. If rounded

      # down, then you would get temp_pair * 3, which is only 6 values. Instead, with ceil

      # you get 8, and then you just take the first 7.

      (temp_len / 2.0).ceil.times.collect { temp_pair }.flatten.take(temp_len)

    when 2

      temp_trio = fours_permutations.take(3 * trio_vals.tick(:t_val)).last(3)

      (temp_len / 3.0).ceil.times.collect { temp_trio }.flatten.take(temp_len)

    when 3

      temp_quartet = fours_permutations.take(4 * quartet_vals.tick(:q_val)).last(4)

      (temp_len / 4.0).ceil.times.collect { temp_quartet }.flatten.take(temp_len)

    end

  end

  

  # Tick to the next value in rhythm_selection and set it to temp_rhy_select

  temp_rhy_select = rhythm_selection.tick(:rhy_sel)

  

  # Now, we generate some rhythmic values.

  event_rhythm = case temp_rhy_select

  when 0 # If 0, then create an even rhythm

    # Randomly choose a durational value. Here, these values are between 32nd notes and 

    # 1/8 notes.

    temp_single_dur = choose([0.125, 0.14, 0.167, 0.2, 0.25, 0.33, 0.4, 0.5])

    # Repeat that value temp_len times.

    temp_len.times.collect { temp_single_dur }

  when 1 # This is random rhythm.

    # Choose randomly from this array of value temp_len times.

    temp_len.times.collect { choose([0.125, 0.14, 0.167, 0.2, 0.25, 0.33, 0.4, 0.5]) }

  when 2 # Generate a cycle of 2 to 5 values, randomly choosing from the array of values.

    (temp_len / rrand_i(2,5)).round.times.collect { choose([0.125, 0.14, 0.167, 0.2, 0.25, 0.33, 0.4, 0.5]) }

  when 3

    # Since there are 8 notes values from shortest to longest, make an array with a series

    # of values from 7 to 0 (which will serve as index values to the rhythmic values array).

    # These are rounded to turn them into integers.

    temp_accel = line(7,0,steps: temp_len,inclusive:true).to_a.collect {|n| n.round}

    temp_accel.collect { |n| [0.125, 0.14, 0.167, 0.2, 0.25, 0.33, 0.4, 0.5][n] }

  when 4 # Just the reverse of accelerando above.

    temp_rit = line(0,7,steps: temp_len,inclusive:true).to_a.collect {|n| n.round}

    temp_rit.collect { |n| [0.125, 0.14, 0.167, 0.2, 0.25, 0.33, 0.4, 0.5][n] }

  end

  

  # Now, we generate dynamics.

  temp_dyns_select = dyns_selection.tick(:dyns_sel)

  

  event_dynamics = case temp_dyns_select

  when 0

    temp_len.times.collect { 2 } # This is loud. It's the 3rd of 4, not the loudest.

  when 1

    temp_len.times.collect { 0 } # Quietest dynamic.

  when 2

    # Compare the next two to accel/rit from the previous component. It's the same with 

    # some minor adjustments to scale for 4 dynamics rather than 7 note values.

    line(0, 3, steps: temp_len, inclusive:true).to_a.collect {|n| n.round}

  when 3

    line(3, 0, steps: temp_len, inclusive:true).to_a.collect {|n| n.round}

  when 4

    # Here we use the built-in function of Sonic Pi called one_in, as in a "one in 8" chance.

    # If it's true, then play the loudest (3) dynamic. Otherwise, randomly choose from 

    # the quietest two (0, 1). This randomness gives the music a bit more life.

    temp_len.times.collect {

      if one_in(8)

        3

      else

        choose([0,1])

      end

    }

  when 5

    # Here we seek an accent, so randomly choose between the 2nd and 3rd loudest dynamic.

    # Otherwise, always play the quietest.

    temp_len.times.collect {

      if one_in(8)

        choose([1,2])

      else

        0

      end

    }

  end

  

  # This is where the music happens! We read through each value of event_pitch,

  # event_dynamic, and event_rhythm and combine them to choose samples and play them

  # back in a particular rhythm.

  temp_len.times do

    cur_pitch = event_pitches.tick(:pitch)

    # cur_samp is determined by cur_pitch (0 to 3) being multiplied by 16. This gets us

    # into the correct range of samples for that particular instrument. Then, event_dynamic 

    # is multiplied by 4 (since there are 4 takes for each dynamic), again to get us into 

    # the correct range of that dynamic on that instrument. Then, we are left with choosing 

    # the take which is done by ticking through fours_permutations.

    cur_samp = (cur_pitch * 16) + (event_dynamics.tick(:dyn) * 4) + fours_permutations.tick(:take)

    sample RiceBowls, cur_samp, pan: pan[cur_pitch]

    wait event_rhythm.tick(:rhy)

  end

  # Once this is finished, we go back to the beginning of the algorithm, take the next 

  # cycle_series_choice value and run the process again to get a new event.

end

bottom of page