Пример #1
0
def play():

    # now modifying our previous example, instead of playing the same two
    # scales in a loop, let's play a given set of notes in each scale.
    # We'll use roman notation to play the 1st, 4th, and 5th note in the scale
    # followed by the 1st, 4th, and 5th major chord
    # finally, we'll play the 2nd, 3rd, and 6th minor chord.


    output = Performance(bpm=120, stop_seconds=10)

    # this is just as before, playing one scale then the other in a loop of 7
    # notes each
    scale1 = scale("c6 major")
    scale2 = scale("c6 minor")
    scale_choices = [ dict(scale=scale1, beats=7), dict(scale=scale2, beats=7) ]
    source = ScaleSource(scales=Endlessly(scale_choices))

    # the scale follower will play the first 7 notes in each scale, whatever the current
    # scale is.  Note that a scale change that doesn't quite line up with the length
    # of the roman pattern rolling over might sound a bit weird.  That's ok.
    roman = Roman(symbols=Endlessly("1 4 5 I IV V ii iii vi".split()), channel=1)
    source.chain([roman, output])

    conductor = Conductor(signal=[source], performance=output)
    conductor.start()
Пример #2
0
def play():

    # Let's show how to play a sequence of one length but omit every Nth note

    output = Performance(bpm=120, stop_seconds=15)

    source = ScaleSource(scales=scale("D minor"))

    melody = Roman(symbols=Endlessly("1 2 3 4 5 6 7".split()), channel=1)
    silence = Permit(when=Endlessly([1, 1, 0]))

    # so we're playing a pattern of 1 2 3 4 5 6 7 of scale degree notes in a giant loop
    # the first time through what really happens is:
    #    1 2 <REST> 4 5 <REST> 7
    # and the second time:
    #    1 <REST> 3 4 <REST> 6 7
    # and the third time:
    #    <REST> 2 3 <REST> 5 6 <REST>
    # all of this is because the pattern and the 'silence' pattern are of unequal lengths

    # this is not ALWAYS relevant in a composition, but if you want to do something
    # polyrhytmic and unorthodox (not just with 'Silence') patterns of  unequal length
    # can be interesting.

    # HOMEWORK: experiment with changing the melody patterns and the silence patterns

    # HOMEWORK: change endlessly to randomly, and see what happens

    source.chain([melody, silence, output])
    conductor = Conductor(signal=[source], performance=output)
    conductor.start()
Пример #3
0
def play():

    # examples/06_subdivide showed how to chop up the incoming quarter note signal
    # to create faster signals.  However, sometimes in a given bar we want a mix of note durations.
    # how do we do that?

    # For this example, lets play whole note chords underneath a quarter note melody.

    # implementation caveat:
    # Because the "beat" signal from the conductor comes in every quarter note, we have to take
    # care and rest in the right places of the input signal to avoid funness.  This may get easier
    # later.

    # this is pretty close to what we did in 09_harmony.py

    output = Performance(bpm=120, stop_seconds=15)

    # this performance will actually complete so stop_seconds is mostly ignorable.

    scale_choices = Endlessly([scale("D major")])
    source = ScaleSource(scales=scale_choices)

    # the underlying chords - note the rests
    chords = Roman(symbols=Endlessly(
        "I - - - I - - - IV - - - V - - - ".split()),
                   channel=1)
    # every beat is a quarter note, but on the first beat in a cycle of 4
    # play a WHOLE note
    chords_len = Duration(lengths=Endlessly([1, 0, 0, 0]))
    source.chain([chords, chords_len, output])

    # the melody
    # TODO: Roman should figure out if input is a string here and auto-split.
    pattern2 = Roman("1 2 3 1 2 2 1 3 3 1 3 2".split(), channel=2)
    pattern3 = Roman("1 3 3 1 4 4 1 3 3 1 5 -".split(), channel=2)
    pattern4 = Roman("1 5 5 1 4 4 1 3 3 1 2 2".split(), channel=2)
    pattern5 = Roman("1 2 4 6 4 2 1 6 4 2 1 -".split(), channel=2)
    melody_sequence = [pattern2, pattern3, pattern4, pattern5]
    melody = Ordered(sources=Endlessly(melody_sequence))
    # we could just do: source.chain(source, ordered) here
    # but it would mean later we would have less flexibility
    # to make the patterns feel different.
    source.chain([melody])
    for pattern in melody_sequence:
        source.chain([pattern, output])

    conductor = Conductor(signal=[source], performance=output)
    conductor.start()
Пример #4
0
def play():

    # this really isn't endless, because we've engaged a global stop timer.
    # but if you change 10 to 100, it will play for 100 seconds.  Make note
    # of the use of Endlessly here to see that data sources are not exhausted.

    output = Performance(bpm=120, stop_seconds=10)

    # play the two scales for 7 beats each, and then keep using those scales
    scale1 = scale("c6 major")
    scale2 = scale("c6 minor")
    scale_choices = [dict(scale=scale1, beats=7), dict(scale=scale2, beats=7)]

    source = ScaleSource(scales=Endlessly(scale_choices))

    # note: the syntax is simpler to just play ONE scale repeatedly, and you'll see this
    # done more in future examples.  Because we don't need to stay in one scale for N beats
    # before moving on to another, we don't need all the extra info.
    # source = ScaleSource(scales=scale1)

    # the scale follower will play the first 7 notes in each scale, whatever the current
    # scale is.
    follower = ScaleFollower(lengths=7, channel=1)
    source.chain([follower, output])

    conductor = Conductor(signal=[source], performance=output)
    conductor.start()
Пример #5
0
def play():

    # in example 14 we showed how to nest patterns in ways that are easier
    # to conceptualize.  For instance, these patterns play in order, and we
    # call that a movement.  However, it's often tedious to specify things
    # that replay again and again.  To do make this easy, we have a new
    # selector to introduce - Repeatedly!

    output = Performance(bpm=240, stop_seconds=100)

    scale_choices = Endlessly([scale("D minor")])
    source = ScaleSource(scales=scale_choices)

    pattern1 = Roman(symbols=Repeatedly("1 2 3 4".split(), cycles=2))
    pattern2 = Roman(symbols="4 3 2 1".split())
    movement1 = Ordered(sources=[pattern1, pattern2])

    pattern3 = Roman(symbols=Repeatedly("1 4 1 4 1 5 1 5".split(), cycles=2))
    pattern4 = Roman(symbols="I IV V I".split())
    movement2 = Ordered(sources=[pattern3, pattern4])

    suite = Ordered(sources=[movement1, movement2], channel=1)

    source.send_to(suite)

    # for this example, we won't do anything crazy with transpositions or anything.

    suite.send_to(output)

    conductor = Conductor(signal=[source], performance=output)
    conductor.start()
Пример #6
0
def play():

    # in example 13 we showed the use of Ordered to trigger
    # one pattern after another.  As programs grow, it might be nice
    # to group them conceptually in a larger composition.

    # This is a demo that shows how Ordered can nest to not work only
    # with basic patterns, but also larger movements.

    # The idea is this: Generators stack!

    # Like before, we're setting stop seconds to 100 seconds but this composition
    # will complete due to the sources being exhausted first, which is the point.

    output = Performance(bpm=240, stop_seconds=100)

    for scale_name in ["C major", "D minor"]:

        scale_choices = Endlessly([scale(scale_name)])
        source = ScaleSource(scales=scale_choices)

        pattern1 = Roman(symbols="1 2 3 4 5 6 7".split())
        pattern2 = Roman(symbols="i ii iii iv v v vii".split())
        movement1 = Ordered(sources=[pattern1, pattern2])

        pattern3 = Roman(symbols="7 6 5 4 3 2 1".split())
        pattern4 = Roman(symbols="vii vi v iv iii ii i".split())
        movement2 = Ordered(sources=[pattern3, pattern4])
        movement2_transposer = Transpose(octaves=Endlessly([-2]))
        movement2.send_to(movement2_transposer)

        suite = Ordered(sources=[movement1, movement2], channel=1)

        source.send_to(suite)
        output.listens_to([movement1, movement2_transposer])

        # BONUS TIP: we technically don't have to have just one conductor invocation, if it
        # keeps it simple.

        conductor = Conductor(signal=[source], performance=output)
        conductor.start()
Пример #7
0
def play():

    # 21 examples an no mention of velocity (loudness) yet?  Wow.
    # velocity is easy.  in MIDI, velocity is a loudness level from 127 (maximum)
    # to 0 (silent).

    # because the human ear percieves different pitches at different loudnesses,
    # the following is a silly demo of repeating a middle C at different loudness
    # levels.

    # (IDEA: this gives me the idea for a math-equation type iterator so I can do sine
    # curves for various automation, but... later)

    output = Performance(bpm=120, stop_seconds=15)

    melody = Literal(symbols=Endlessly(["C4"]), channel=1)
    velocity = Velocity(
        levels=Endlessly([10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120]))

    melody.chain([velocity, output])
    conductor = Conductor(signal=[melody], performance=output)
    conductor.start()
Пример #8
0
def play():

    # here's an example that might *START* to approximate a song.
    # here we have two instruments playing on two different tracks.

    output = Performance(bpm=60, stop_seconds=10)

    # both instruments will use the same scale at the same time, but there is a scale
    # change as we cycle between scales every 24 beats

    scale1 = scale("c5 major_pentatonic")
    scale2 = scale("e5 mixolydian")
    scale_choices = [
        dict(scale=scale1, beats=24),
        dict(scale=scale2, beats=24)
    ]
    source = ScaleSource(scales=Endlessly(scale_choices))

    # the first instrument plays a series of chords, transposed down an octave
    # from the original scale carrier signal.

    roman1 = Roman(symbols=Endlessly("I IV V IV III:dim ii".split()),
                   channel=1)
    transpose1 = Transpose(octaves=-1)
    source.chain([roman1, transpose1, output])

    # the second instrument plays a series of notes, but is responding to sixteenth
    # notes, not quarter notes, because of the subdivide.

    subdivide2 = Subdivide(splits=4)
    roman2 = Roman(symbols=Endlessly("1 4 3 4 4 3 2 1".split()), channel=2)
    source.chain([subdivide2, roman2, output])

    # homework assignment:
    # change subdivide2 so it's chained BEFORE roman2
    # what happens and why?

    conductor = Conductor(signal=[source], performance=output)
    conductor.start()
Пример #9
0
def play():

    # example 05 was "playing all the power chords in a chromatic scale".
    # example 06 used subdivide to chop up alternating measures with faster notes
    # now lets alternately transpose every 2nd measure up an octave
    # and every 3rd measure down an octave

    output = Performance(bpm=120, stop_seconds=10)

    source = ScaleSource(scales=scale("c4 chromatic"))

    follower = ScaleFollower(lengths=Endlessly([12]))

    subdivide = Subdivide(splits=Endlessly([1,4]))

    # pay attention to this part - nothing else has changed
    transpose = Transpose(octaves=Endlessly([0,1,-1]))

    chordify = Chordify(types="power", channel=1)
    source.chain([follower, subdivide, transpose, chordify, output])

    conductor = Conductor(signal=[source], performance=output)
    conductor.start()
Пример #10
0
def play():

    # we've focussed a lot on randomness now.  So far, we've shown how to pick random values
    # for notes and how to conditionally do something or not with "when=".

    # What's interesting though is that, due to the way some effects plugins work, we can choose
    # to intermix effects.  Here, we show that there are really two patterns going on, but only one
    # is going to win and actually get to play a note.

    # we're relying one 'musician' object stomping on another, but only sometimes.

    output = Performance(bpm=120, stop_seconds=15)

    source = ScaleSource(scales=scale("Ab blues"))

    pattern1 = Endlessly("1 2 3 4 5 6 7".split())
    pattern2 = Randomly(
        "VII:power VI:power V:power IV:power III:power II:power I:power".split(
        ))
    cut_over = Randomly([0, 0, 0, 1, 1, 1], mode='probability')

    melody1 = Roman(symbols=pattern1, channel=1)
    melody2 = Roman(symbols=pattern2, channel=2, when=cut_over)
    transpose = Transpose(octaves=Endlessly([-2, -1, 0, 1, 2]))

    # there is no sane reason to want to do this, but what happens is that for the first three notes we are guaranteed
    # to play pattern1.  Then three notes of pattern 2.  The last note is acutally a toss up between pattern1 and pattern2.
    # since they live in the same chain, pattern1 is going to overwrite pattern2 even though they are expressed in a chain.
    # we can also even choose to overwrite the MIDI channel, which we do, all while sharing a transposition cycle.

    # again, this isn't realistic for most compositions, we'd likely just set up a bunch of patterns with RESTS, but
    # I wanted to show all the possibilities before leaving the theme of randomness for a while.  Hopefully the
    # idea of "when=" is now drummed in.

    source.chain([melody1, melody2, transpose, output])
    conductor = Conductor(signal=[source], performance=output)
    conductor.start()
Пример #11
0
def play():

    # this is sort of a variation of example 08.  Except now, rather than
    # playing two different patterns that might clash, we're selecting chord
    # patterns for instrument 2 to harmonize with what instrument 1 is playing.

    output = Performance(bpm=120, stop_seconds=10)

    # both instruments will use the same scale at the same time, but there is a scale
    # change as we cycle between scales every 24 beats

    scale1 = scale("c5 major_pentatonic")
    scale2 = scale("e5 mixolydian")
    scale_choices = [ dict(scale=scale1, beats=24), dict(scale=scale2, beats=24) ]
    source = ScaleSource(scales=Endlessly(scale_choices))

    # the first instrument plays a series of notes

    roman1 = Roman(symbols=Endlessly("1 2 4 1 2 3 1 2 4 3 3".split()), channel=1)
    source.chain([roman1, output])

    # the second instrument transposes that note down two octaves and plays a power
    # chord.  We could pass in an array of chords to vary the chord type, but this is
    # probably going to sound less prone to clashing.

    chordify = Chordify(types=Endlessly(["power"]), channel=2)
    transpose = Transpose(octaves=Endlessly([-2]))
    source.chain([roman1, chordify, transpose, output])

    # note that roman 1 is part of each chain, but the channel number is overridden
    # in the second set.  This can be done because the event objects are copied as they
    # are passed between each layer.  Technically the channel can be overriden at any
    # time.  Ideas for future chaos, perhaps?

    conductor = Conductor(signal=[source], performance=output)
    conductor.start()
Пример #12
0
def play():

    # Permit, introduced in the example 18_randomness2.py allows us to
    # silence an entire chain when certain conditions are met.

    # However, sometimes, we may want to decide to apply an affect only
    # when something is true.  Because this could be useful ANYWHERE
    # the same type of "when" logic can actually be attached to ANYTHING.

    # here is a probabilistic way to use random, that combines the concepts.

    output = Performance(bpm=120, stop_seconds=15)

    source = ScaleSource(scales=scale("Ab blues"))

    melody = Roman(symbols=Endlessly("1 2 3 4 5 6 7".split()), channel=1)
    chordify = Chordify(types=Endlessly(['major', 'minor']),
                        when=Randomly([0, 0.45], mode='probability'))
    transpose = Transpose(octaves=Endlessly([2, -2]),
                          when=Randomly([0, 0, 0.75], mode='probability'))

    # the result is every other note has a 40% chance of becoming a chord,  which is always alternating
    # major and minor when it happens

    # every THIRD note has a 75 percent chance of being transposed, which will be alternating +2/-2 octaves
    # when it happens

    # so now, we have easily inserted CONDITIONAL effects.  The use of when=Randomly wasn't required.
    # we could also have used Endlessly or Repeatedly.  Though keep in mind if using Repeatedly, when
    # the event chain is exhausted, that particular part of the performance will stop.  And when everything stops,
    # the performance is done.  Because this is likely being applied to an effect chain, Repeatedly probably
    # doesn't make the most sense with "when".  But Endlessly?  Sure!

    source.chain([melody, chordify, transpose, output])
    conductor = Conductor(signal=[source], performance=output)
    conductor.start()
Пример #13
0
def play():

    # in example 11 we showed off some random functionality
    # in example 17 we showed how to silence notes based on that
    # knowledge of 'random'.

    # here is a probabilistic way to use random, that combines the concepts.

    output = Performance(bpm=120, stop_seconds=15)

    source = ScaleSource(scales=scale("Ab blues"))

    melody = Roman(symbols=Endlessly("1 2 3 4 5 6 7".split()), channel=1)
    silence = Permit(when=Randomly([1, 0.5], mode='probability'))

    # so we're playing a scale pattern, but every other note has a 50% chance
    # of not being played.

    source.chain([melody, silence, output])
    conductor = Conductor(signal=[source], performance=output)
    conductor.start()
Пример #14
0
def play():

    # example 05 was "playing all the power chords in a chromatic scale".
    # let's modify the example very slightly using Subdivide.
    # normally the conductor sends down quarter notes.  Let's alternate
    # playing quarter notes and sixteenth notes.

    output = Performance(bpm=120, stop_seconds=10)

    # play the two scales for 7 beats each, and then keep using those scales
    source = ScaleSource(scales=scale("c4 chromatic"))

    follower = ScaleFollower(lengths=12)

    # pay attention to this part - nothing else has changed
    subdivide = Subdivide(splits=Endlessly([1, 4]))

    chordify = Chordify(types="power", channel=1)
    source.chain([follower, subdivide, chordify, output])

    conductor = Conductor(signal=[source], performance=output)
    conductor.start()
Пример #15
0
def play():

    # here we aren't introducing any new concepts, but we're arranging some
    # concepts a little differently.  This is somewhat of a review, as we're going
    # to start exploring some more (hopefully) musical demos in our examples now.

    # This one sounds just a bit Perrey and Kingsley with the right VST.

    output = Performance(bpm=60, stop_seconds=10)

    # let's play in the A major scale

    scale_choices = Endlessly([dict(scale=scale("a4 major"))])

    source = ScaleSource(scales=scale_choices)

    # we're going to alternate between the 1st, 4th, and 5th notes
    # of the scale, but we didn't just jump to chords because of what
    # we are about to do with the arp.

    roman = Roman(symbols=Endlessly("1 4 5".split()), channel=1)

    # and now for a clever use of the arp, to machine gun 4 repititions of
    # each chord, with rests in between.  Subdivide alone couldn't do this.
    # Also notice this is running with NO tranpositions.

    arp = Arp(
        # no transpositions
        semitones=Endlessly([0, 0, 0, 0, 0, 0, 0, 0]),
        # every beat gets sliced up 8 times, no exceptions
        splits=Endlessly([8]),
        # try uncommenting this next line:
        #octaves=Endlessly([0,0,1,0,2,0,3,0]),
        # play every other note on the arp
        # TODO: IDEA: seems like we should also allow arp velocity!
        rests=Endlessly([0, 1, 0, 1, 0, 1, 0, 1]))

    # now the output here is just machine gunned notes.  Turn it into major chords.

    chordify = Chordify(types=Endlessly(['major']))

    source.chain([roman, arp, chordify, output])

    conductor = Conductor(signal=[source], performance=output)
    conductor.start()
Пример #16
0
 def create(self, pattern):
     return Endlessly(pattern)
Пример #17
0
def play():

    # in example 15 we show how to drift between different patterns.

    # so far, we've mostly been showing patterns inside a scale, because
    # that sounds good for melodic instruments.  Not everything lives
    # inside a scale though - accidentals etc.  Transpose and the arp
    # can move by semitones to overcome that.

    # however, when dealing with drum kits, the scale is pretty much never
    # relevant

    # Usually these things trigger with something like C3 for a kick drum, D4
    # for a Tom, and so on -- but it varies by drumkit.

    # Having a transpose or scale change in there would
    # mess it all up.

    # Here's an example of switching through some drum patterns, and it also
    # fires multiple types of drum hits on a single channel.

    # when setting this example up, put any synth you want on MIDI channel 2,
    # but on MIDI channel 1 put some kind of drumkit.  You may need to change
    # the MIDI note names to make it sound right

    K = "F4"
    H = "A4"
    C = "Bb4"

    output = Performance(bpm=120, stop_seconds=15)

    melody_trigger = ScaleSource(scales=scale("D minor"))
    melody = Roman(symbols=Endlessly("1 - - - - 2 - - - -".split()), channel=1)

    drum_trigger = Subdivide(splits=4,
                             channel=2)  # gimme 16th notes for the drum tracks

    # kick drum on the quarter notes
    kicks = Literal(symbols=Endlessly([
        K, None, None, None, K, None, None, None, K, None, None, None, K, None,
        None, None
    ]))

    # hihat on the 8th notes every other bar
    # but wait one bar before starting that up.
    hihat = Ordered(sources=Endlessly([
        Literal(symbols="- - - - - - - - - - - - - - - -".split()),
        Literal(symbols=Endlessly([
            H, None, H, None, H, None, H, None, H, None, H, None, H, None, H,
            None
        ]))
    ]))

    # cymbals on the half notes
    cymbals = Literal(symbols=Endlessly([
        C, None, None, None, None, None, None, C, None, None, None, None, None,
        None
    ]))

    melody_trigger.send_to(melody)
    drum_trigger.send_to([kicks, hihat, cymbals])
    output.listens_to([melody, kicks, hihat, cymbals])

    conductor = Conductor(signal=[melody_trigger, drum_trigger],
                          performance=output)
    conductor.start()
Пример #18
0
def play():

    # so far what we've done has been mostly theoretical.
    # to construct larger songs, obviously, one thing has to occur
    # after another.  To do this, we could just pass in GIGANTIC
    # arrays to everything but that would suck!

    # now, everything in CAMP is built off the idea of generators.
    # this means the system can pretty well know if one thing is done
    # playing and more on to the next.

    # while we could just pass in huge note arrays, that kind of sucks.
    # What if we want a flute to play
    # it's part and then have the trombone come in?  To do that,
    # we need a facility for ordering - to say "this comes next"

    # NOTE: we're setting stop seconds to 100 seconds but this composition
    # will complete due to the sources being exhausted first, which is the point.
    # we're not writing an endlessly generative piece here, but moving towards
    # more explicit songwriting tools.  The point of this example is to show
    # we are writing a finite song in PIECES.

    output = Performance(bpm=240, stop_seconds=100)
    scale_choices = Endlessly([scale("a3 major")])

    source = ScaleSource(scales=scale_choices)

    pattern1 = Roman(symbols="1 2 3 4".split())
    pattern2 = Roman(symbols="I I I I".split())
    pattern2_transpose = Transpose(octaves=Endlessly([2]))
    pattern2.send_to(pattern2_transpose)

    # in this example, we're going to play pattern1 until it is exhausted
    # then pattern2, and when that's done, we're done.

    ordered = Ordered(sources=[pattern1, pattern2], channel=1)

    # HOMEWORK ASSIGNMENT:
    # uncomment this line to randomly pick a pattern and play it until
    # it is exhausted, but keep doing that endlessly.  Patterns might
    # repeat, see notes about advanced usages of Randomly in examples/11_randomness.py
    #
    # ordered = Ordered(sources=Randomly([pattern1,pattern2]), channel=1)

    # HOMEWORK ASSIGNMENT:
    #
    # uncomment this next line to play pattern1, pattern2 in an endless sequence
    # ordered = Ordered(Endlessly([pattern1,pattern2]))

    # TODO: IDEA: it might be nice to have a "beats" flag on ordered where it can stop
    # producing after a certain number of beats
    # ordered = Ordered(Randomly([pattern1,pattern2,pattern3]), beats=24)
    # this would allow for patterns of patterns, and better nesting.

    # TODO: IDEA: anything that can take a "beats" should really take a bars=, it's just easier
    # to think about

    # HOMEWORK: using a pattern in Ordered that uses Endlessly will cause the pattern to NOT switch.
    # Try this out and understand why pattern 2 will never play.
    # pattern1 = Roman(symbols=Endlessly("3 6 3 6 5 1 2".split()))
    # ordered = Ordered([pattern1,pattern2,pattern3])

    # TODO: IDEA, make the above homework example actually work, with something like:
    # pattern1 = Roman(symbols=Endlessly("3 6 3 6 5 1 2".split()))
    # ordered = Ordered([ dict(pattern=pattern1, beats=8), dict(pattern=pattern2, beats=8) ])

    # NOTE: in simple cases we could connect ordered to the output, but we'd miss the transposition,
    # and I wanted to show off a more complete example.  This also underscores the idea that source.chain
    # is just syntactic sugar and you don't have to use it.

    source.send_to(ordered)
    pattern1.send_to(output)
    pattern2_transpose.send_to(output)

    conductor = Conductor(signal=[source], performance=output)
    conductor.start()
Пример #19
0
def play():

    # previous examples showed a couple of instruments, lets go back to one
    # instrument to showcase just the arpeggiator.

    # WARNING - may not make sense if you haven't used any hardware or
    # software arpeggiators.

    output = Performance(bpm=120, stop_seconds=10)

    scale1 = scale("c4 major")
    scale_choices = [dict(scale=scale1, beats=7)]

    # refresher - CAMP is generator based, and performances stop when there is
    # nothing left to do OR stop_seconds is reached.
    # by not wrapping the scale choices in "Endlessly", we're allowing this example
    # to terminate when the scale is done.  This example will play 24 quarter note
    # beats and STOP.  But the way the arpeggiator is coded, there will really be
    # 24*4 = 96 sixteenth notes, of which 3/4s of them will be played with audible notes
    # and the rests will be, ahem, rests.

    # refresher - the MIDI channel number can be set anywhere in the graph, as well as overrided
    # anywhere in the graph.  Leaving it off here on ScaleSource and setting it on the
    # Arp would do exactly the same thing.  Leaving off the channel will cause an exception.

    source = ScaleSource(scales=(scale_choices), channel=1)

    # refresher - the scale follower will take note of the current scale and
    # play the scale for a given number of notes in order.  We could be more
    # creative by using Roman() instead to select particular notes (or chords)
    # in the scale.

    follower = ScaleFollower(lengths=Endlessly([7]))

    # refresher, the roman usage looks very similar:
    # roman = Roman(symbols="1 2 3 4 5 6 7".split())
    # really, there's little use for ScaleFollower in the real world, but it was
    # getting lonely and wanted to be featured here.  It's a bit more efficient
    # but I doubt that matters.

    # NOW FOR THE FUN PART.

    # ok, here's the arpeggiator.

    # in this example, we take the currently selected note from the scale
    # play that note, then the notes 4 and 9 semitones up - a major chord.
    # every single beat will be divided into 4 parts by the arp due to 'splits'
    # however every 4th note is also a REST.
    #
    # see this table:
    #
    # TRIGGER: | C4 ~~~~~~~~~~~~ | D4 ~~~~~~~~~ | ...
    # ---------+-----------------+--------------+
    # RESTS:   | N   N   N  Y    | N   N   N  Y |
    # SEMI:    | +0  +3  +8  -   | +0  +3  +8   |
    # OCTAVES: | +2  -2  0   0   | +2  -2  0  0 |
    # ---------+-----------------+--------------+
    # PLAY:    | C6  E2  G4  -   | D6  Gb2 A4 - |
    #
    # more creative things can be done by having the semitones
    # and rest patterns be different lengths, and so on, as well as using
    # the 'free' mode with those uneven lengths.

    arp = Arp(semitones=Endlessly([0, 3, 8, 0]),
              octaves=Endlessly([2, -2, 0, 0]),
              splits=Endlessly([4]),
              rests=Endlessly([0, 0, 0, 1]))

    # HOMEWORK: add "mode=free" to the Arp constructor above and experiment
    # with octaves, splits, rests, and semitones of different values and array
    # lengths.

    # POP QUIZ:  what happens if we remove Endlessly from some of the expressions above?
    # WHY?

    # the chain here is simple - scale source feeds note source, which in turn
    # feeds the arp.

    source.chain([follower, arp, output])

    # kick out the jams

    conductor = Conductor(signal=[source], performance=output)
    conductor.start()