'''

PATTERNS

This short example does the following:

* Defines lists of note and rhythm patterns (user-defined or randomly generated)

* Defines lists of volume (velocity) ranges

* Calls (forks) the play_melody function as many times as you wish. Each of these parallel calls can:
     - send the name of a different instrument (defined at the top with s.new_part()
     - send the "root" note to use for the note lists (called note0)
     - send its own note, rhythm, and velocity lists
     - send a multiplier for all notes (Ex: 0.5 = half speed notes)
     - send the total time for that call to finish
     - send a transportation amount (in semitones, up or down)

* The play_melody function loops through the note, duration, and volume lists, keeping track
  of the total time the notes have played.

* The final note of each call will play exactly the duration to make the total time
  equal what the user requested.

* The total times passed for the different calls (forks) can be the same or different.
  Ex: if you had 4 calls with total_time: 4, 6, 9, 10
  the instruments would progressively stop playing.
  
* You can achieve a LOT of variety by defining different note and duration lists,
  changing octaves, multipliers, and instrumentation
  
* Because the note and duration patterns can be random lists, every time you press PLAY,
  you'll get a different sequence of notes and durations. You can, of course, define these
  lists yourself so that it will sound the same every time.
   
  

DISCLAIMERS:

1. This is a simple example of using fork(), functions, lists, random, etc.
2. This is not intended to be a completed program; it's just an exercise I gave to myself
3. There is no error checking at this point
4. I'm an beginner with Python and SCAMP, and I know there are much cleaner, more efficient ways
   in to do all of this, but at this point, I'm writing the code so I understand it.  :)
   

>> If you've read this far and have pressed PLAY a few time (and perhaps experimented with
>> creating different note and duration lists, I appreciate it and would love to read any
>> comments about it. Thanks.

David Collett
Seattle


'''



from scamp import *
import random

s = Session()

piano       = s.new_part("piano") #
celesta     = s.new_part("celesta") #
agogo       = s.new_part("Agogo") #
echo_drops  = s.new_part("Echo Drops") #
wood_block  = s.new_part("Wood Block") #
taiko_drum  = s.new_part("Taiko Drum") #




'''
FUNCTION PARAMETERS

* instr                  = string; name of instrument as defined above
* note0                  = MIDI number representing note all patterns are based on
* note_pattern           = scale degrees. Ex: [0,4,3,2,10,8,10,7] are semitone offsets from note0
* time_pattern           = list with times in seconds. The elements can match the note_pattern list or be different.
* note_length_mult       = shortens or lengthens every note. Ex: 0.5 will halve the time; 2.0 will double the time.
* total_time             = total seconds you want this to play
* extra_end_time         = total seconds you want to add after the last note plays, to conclude the piece (send 0 if not extra time wanted)
                           these can be the same or different (if you want one or more instruments to fade out longer than others)
* volumes                = list with 2 elements: the lowest and highest volumes you want (0.0 - 1.0)
* transposition          = number of semitones up or down to move notes (Ex: 7 = perfect 5th higher;  -12 = octave lower)

'''



def play_melody(instr, note0, note_pattern, time_pattern, note_length_mult, total_time, extra_end_time, volumes, transposition):
    
    # create call to ".play_note" with instrument name passed
    play_instr = eval(instr + ".play_note")

    # initialize variables needed for looping through the notes
    cur_time = 0
    cur_note_position = 0  # index position in the note_pattern list
    cur_time_position = 0  # index position in the time_pattern list
    note_pattern_len = len(note_pattern)
    time_pattern_len = len(time_pattern) # if this is 0, then the user passed [], and wants to use a random element from random_lengths list

    # set the length (in sec) of first note
    note_len = time_pattern[cur_time_position] * note_length_mult

    # loop and play notes until playing the next note would result in a time longer than user specified
    while cur_time + note_len < total_time:

        play_instr(note0 + note_pattern[cur_note_position] + transposition, random.choice(volumes), note_len)        
        
        # get the next note and time length
        cur_note_position = (cur_note_position + 1) % note_pattern_len

        # choose the next note length and time position
        cur_time_position = (cur_time_position + 1) % time_pattern_len
        note_len = time_pattern[cur_time_position] * note_length_mult

        # advance the total time
        cur_time += note_len

    
    # use all the remaining time to play the final note
    final_note_len = total_time - cur_time
    if final_note_len > 0.0:
        play_instr(note0 + note_pattern[(cur_note_position + 1) % note_pattern_len] + transposition, random.choice(volumes), final_note_len + extra_end_time)

 

  
# ----------------------------------------------------------------------------------------
  
  
  
def random_pattern_generator(num_items, choices):  #choices = list, such as [0,3, 7, 10, 14] or [0.5, 0.125, 0.125, 0.25, 0.25, 0.75, 1.0]
    pattern = []
    for _ in range(0, num_items):
        pattern.append(random.choice(choices))
    return pattern



# ----------------------------------------------------------------------------------------



# Define the note list(s)

# One way is to list the notes (semitone offsets from root note)
# and build note lists of any length
# In the following, note_pattern 1, 2, 3 will each contain a different, random order of 6 notes
# from the note_list below:
note_list = [0, 4, 7, 10, 14, 7, 4]
note_pattern1 = random_pattern_generator(6, note_list)
note_pattern2 = random_pattern_generator(6, note_list)
note_pattern3 = random_pattern_generator(6, note_list)
note_pattern4 = random_pattern_generator(6, note_list)

# If you prefer to specify the exact semitone note_patterns you want,
# you can comment out the lines above, and instead use patterns like these.
# (Or, of course, you can use both methods at the same time.)

# note_pattern1 = [0, 3, 7, 10, 14, 7, 3]
# note_pattern2 = [0, 3, 7, 10, 12]
# note_pattern3 = [0, 7, 0, 7, 14, 12, 7, 0]



# ----------

# Define the time pattern lists.

# You can put any pattern of times (in seconds) in the lists

# time_pattern1  = [0.5, 0.125, 0.125, 0.25, 0.25, 0.75, 1.0]
# time_pattern2  = [0.333333333, 0.666666666, 1.0, 0.5, 0.5]
# time_pattern3  = [0.5, 1.0]
# time_pattern4  = [1.0, 0.5, 0.25, 0.25, 0.5, 0.5,  0.33333333,  0.33333333, 0.33333333, 0.25]

# Or you can generate a random list of times (in seconds)
time_list = [0.125, 0.25, 0.5, 0.75, 1.0, 1.5]
time_pattern1 = random_pattern_generator(4, time_list)
time_pattern2 = random_pattern_generator(6, time_list)
time_pattern3 = random_pattern_generator(8, time_list)
time_pattern4 = random_pattern_generator(4, [1.0, 1.5, 2.0])



# ----------


# Define volume ranges

# You can create as many of these lists as you want
# The volumes of the notes will be selected randomly within this range
# Values must be between 0.0 and 1.0
volumes1 = [.4,  .7]
volumes2 = [.2,  .4]
volumes3 = [.8, 1.0]



# ----------


# Call the play_melody function to play notes

# arguments:              instr,     note0, note_pattern,  time_pattern,    note_length_mult, total_time, extra_end_time,   volumes,  transposition
fork(play_melody, args=["celesta",    60,  note_pattern1, time_pattern1,       1.0,             32,            2.0,         volumes3,      0])
fork(play_melody, args=["echo_drops", 60,  note_pattern1, time_pattern1,       1.0,             32,            1.0,         volumes1,      4])
fork(play_melody, args=["agogo",      48,  note_pattern2, time_pattern2,       1.0,             32,            2.0,         volumes1,      0])
fork(play_melody, args=["wood_block", 48,  note_pattern3, time_pattern3,       1.0,             32,            2.0,         volumes2,     12])
fork(play_melody, args=["piano",      36,  note_pattern3, time_pattern3,       1.0,             32,            3.0,         volumes2,      0])
fork(play_melody, args=["piano",      24,  note_pattern3, time_pattern3,       1.0,             32,            4.0,         volumes3,      0])
fork(play_melody, args=["taiko_drum", 48,  note_pattern4, time_pattern4,       1.0,             32,            2.0,         volumes3,      0])

s.wait_for_children_to_finish()


# ---------- END ----------

