def scale(input):
    """
    Shortcut: scale(['C major') -> Scale object
    """
    (root, typ) = input.split()
    return Scale(root=note(root), typ=typ) class Conductor(object):

    """
    The conductor oversees a performance.

    Don't think of this metaphor too literally.  This isn't a real world model, exactly.

    With each wave of the baton (every quarter note, a beat in "bpm"), the conductor sends a beat event down toward listening band members.
    This is just an event without data in it.  No notes. No velocity.  But it does mark the start of the quarter note with the time field.

    Each member (who in turn can talk to other members) copy the event, and modify it.  They could add or change the notes, set a velocity,
    or set a duration.

    If the conductor is not conducting fast enough, a subdivide plugin can speed it up.

    The performance is, counter-intuitively, also a band-member, taking high level note objects and cleaning them  up, making
    sure time divisions are appropriate, and note events are in the pipeline.  The conductor knows about the performance object
    and asks for it's timeline.

    The timeline object is responsible for the "clock" of the system and is the key to playback.
    """

    def __init__(self, signal=None, performance=None):

        """
        Constructor.

        signal - a list of band members that are keyed into 'beats'.  If not listed here the band
        member needs to be keyed into other band members or they won't play.

        performance - the last note in the event chains that both owns the timeline and makes sure
        band members feeding into the performance impact the timeline.
        """

        assert type(signal) == list
        print("PERF=%s" % performance)

        assert type(performance) == Performance

        self.realtime = Realtime()
        self.signal = signal
        #self.timeline = timeline
        self.performance = performance
        self.timeline = self.performance.timeline
        self.bpm = self.performance.bpm
        self.quarter_note_length = 60 / self.bpm

    def _band_event_to_midi_events(self, event):
        """
        Given a camp.band.playback.event Event, return the list of MIDI events associated with it.
        """
        midi_events = []
        if event.notes is not None:
            if event.channel is None:
                raise Exception("missing channel assignment somewhere in the pipeline: %s" % event.channel)
            for note in event.notes:
                if event.off == True:
                    midi_events.append(self.realtime.note_off(event.channel, note.note_number(), event.velocity))
                else:
                    midi_events.append(self.realtime.note_on(event.channel, note.note_number(), event.velocity))
            return midi_events
        else:
            raise Exception("do not understand non-note events just yet: %s" % event)

    def handle_band_event(self, event):
        """
        Given a camp.band.playback.event Event, play the interpretation of it as MIDI notes.
        """

        midi_events = self._band_event_to_midi_events(event)
        for midi_event in midi_events:
            self.realtime.play_event(midi_event)

    def start(self):

        """
        The start method begins a performance.  The performance will end on it's own
        or when the user Control-C's it.
        """

        running = True
        now_time = 0

        while running:

            print("-- BEAT")
            # we're just going to send a beat signal, the members of the band
            # decide whether to play any notes.  A beat is an inaudible event.
            beat = Event(time=now_time, duration=0.25)

            # if the output ever does not produce events in a time slice, the
            # playback will stop.  We may implement a 'silent' event to keep it
            # going, though this is based on the idea of generators - keep playing
            # until the data runs out
            self.performance.got_events = False

            # the beat happens every quarter note.  Here we calculate the time
            # for the current beat cycle to end
            until_time = now_time + self.quarter_note_length

            # for musicians paying attention to the conductor, tell them the
            # next beat cycle has started now and when it will end
            for item in self.signal:
                item.signal(beat, now_time, until_time)

            # the timeline has recorded any musicians that are being listened
            # to by the RealtimeOutput (aka Audience).  Get all these events
            # off the timeline with appropriate timing delays so they play back
            # exactly when they are supposed to.  Events outside the beat cycle
            # will stay on the timeline for later.  For instance, a whole note may
            # have an 'off' event well in the future.
            for event in self.timeline.process_due_events(now_time, until_time):
                self.handle_band_event(event)

            # if no events were recorded, we're done, so bail out
            if not self.performance.got_events:
                print("OUT OF EVENTS")
                running = False

            # count the beat cycle as concluded
            now_time = until_time

        # the performance is done, make sure we don't leave any notes stuck on
        # FIXME: TODO: hook SIGINT so this also happens on Control-C.
        for event in self.timeline.process_off_events():
            self.handle_band_event(event) class Performance(Member):

    """
    Somewhat of a misnomer, the realtime output doesn't really output anything.
    However, it does throw away events that aren't musically relevant, and records
    whether the performance is done or not by way of the special 'got_events' flag.

    The actual processing of the realtime timeline and actual realtime work is mainly
    in the conductor class.  This then, could technically be called "Audience" and may
    be renamed.
    """


    def __init__(self, bpm=None, stop_seconds=60):

        # TODO: stop_seconds is nice, but we don't we really want stop_beats or stop_bars
        # or something?


        assert bpm is not None

        self.timeline = Timeline()

        self.got_events = False # if we don't get any musical events, the performance will stop
        self.stop_seconds = stop_seconds
        self.bpm = bpm

        # FIXME: this needs to be calculated off of tempo, change to BPM shortly.

        self.whole_note_length = ( 60 / 120) * 4

        super().__init__()

    def on_signal(self, event, start_time, end_time):

        print(event)

        if event.duration is None:
            event.duration = 0.25
        if event.velocity is None:
            event.velocity = 127

        if (event.notes is None or len(event.notes) == 0) and event.keep_alive:
            # an empty set of notes is ok under certain conditions
            print("FIXED!")
            self.got_events = True
            return

        if end_time >= self.stop_seconds:
            self.got_events = False
            return

        event = event.copy()
        self.timeline.add_event(event)
        event_off = event.copy()

        event_off.off = True

        # if we passed through the subdivide middleware shorten the length
        # before we play it.
        subdivide = event_off.flags.get('subdivide', 1)
        event_off.duration = event_off.duration / subdivide

        offset = event.duration * self.whole_note_length
        event_off.time = event.time + offset
        #print("OFF EVENT is %s in the future" % (offset))
        self.timeline.add_event(event_off)
        self.got_events = True class ScaleSource(Member):

    """
    The scale source uses event flags to specify the current scale to be used on each beat.
    Typically, but not always, there will be one scale source at the top of the mesh.

    To play one scale for 7 beats, then another scale for 7:

    source1 = ScaleSource(scales=[ dict(scale=scale1, beats=7), dict(scale=scale2, beats=7) ])

    To keep it simple and just select a permanent scale:

    source1 = ScaleSource(scales=[ dict(scale=scale1) ])
    """

    def __init__(self, scales=None, beats=8, channel=None, when=True):

        super().__init__(channel=channel, when=when)
        self.beats = beats
        self._scales = scales
        self.reset()

    def to_data(self):
        return dict(cls="camp.band.members.scale_source.ScaleSource", data=dict(
            scales = self.datafy(self._scales),
            channel = self.datafy(self.channel),
            when = self.datafy(self._when),
            beats = self.datafy(self.beats)
        ))

    def copy(self):
        return ScaleSource(scales=self._scales, beats=self.beats, channel=self.channel, when=self._when)

    def reset(self):
        self.scale_spec_looper = self.draw_from(self._scales)
        self.scale_gen = self.scale_generator()


    def scale_generator(self):

        for scale_spec in self.scale_spec_looper:

            # if the long form scale spec is not used, assume the scale
            # will be used for a default number of beats
            repeat_beats = self.beats

            if isinstance(scale_spec, Scale):
                scale = scale_spec
            else:
                scale = scale_spec.get('scale', None)
                if scale is None:
                    raise Exception("invalid scale spec")
                repeat_beats = scale_spec.get('beats', 1)

            self.current_scale = scale

            for beat in range(0, repeat_beats):
                yield scale


    def on_signal(self, event, start_time, end_time):

        try:
            scale = next(self.scale_gen)
        except StopIteration:
            return

        event.add_flags(scale=scale)

        for send in self.sends:
            send.signal(event, start_time, end_time)

        return [ event ] class Roman(Member):

    """
    The roman player listens to an incoming note signal and then only really pays attention to the velocity, duration, and scale data.
    The actual note played depends on a symbol off the scale, whether a note or a chord.

    As such, a ScaleSource makes the most sense to have higher up in the chain.

    For instance:

    r = RomanPlayer(symbols="I 2 3 4 IV 2 3 4 iii".split())
    """

    def __init__(self, symbols=None, channel=None, when=True):

        super().__init__(channel=channel, when=when)
        self._symbols = symbols
        print("ROMAN CONSTRUCTED WITH SYMBOLS=%s" % symbols)
        self.reset()

    def to_data(self):
        return dict(cls="camp.band.members.roman.Roman", data=dict(
            symbols = self.datafy(self._symbols),
            channel = self.datafy(self.channel),
            when = self.datafy(self._when)
        ))

    def copy(self):
        return Roman(symbols=self._symbols, channel=self.channel, when=self._when)

    def reset(self):

        self.symbol_looper = self.draw_from(self._symbols)

    def on_signal(self, event, start_time, end_time):


        scale = event.flags.get('scale',None)
        if scale is None:
            raise Exception("missing scale data from note signal, something wrong here")

        try:
            symbol = next(self.symbol_looper)
            print("ROMAN DRAW: %s" % symbol)
        except StopIteration:
            return []

        event.notes = RomanNotation(scale=scale).do_notes(symbol)
        print("ROMAN NOTES: %s" % event.notes)

        for send in self.sends:
            send.signal(event, start_time, end_time)

        return [ event ] class Endlessly(Selector):

    def __init__(self, alist, mode=None):
        self.data = alist
        self.my_generator = endlessly_generate(alist)

    def draw(self):
        result = next(self.my_generator)
        return result

    def to_data(self):
        return dict(cls="camp.band.selectors.selector.Endlessly", data=dict(alist=self.data)) class Randomly(Selector):

    # see examples/11_randomness.py for usage

    # TODO: implement serialism by allowing a serialism=True flag which will pop
    # the item off the list once consumed.  When done, update comments in 11_randomness.py
    # to show the example.

    def __init__(self, alist, mode='choice'):
        self.data = alist
        self.mode = mode
        self.my_generator = endlessly_generate(alist, mode)

        random.seed()


    def draw(self):
        result = next(self.my_generator)
        return result

    def to_data(self):
        return dict(cls="camp.band.selectors.selector.Randomly", data=dict(alist=self.data, mode=self.mode)) class Chordify(Member):

    """
    Chordify takes a note producing member and turns it into chords of
    the given type.

    cf = Chordify(types=["major","major","minor"])

    In the above example, if connected to a scale player, or other note
    generating BandMember, we'd turn the input into a major chord for the first two beats
    and every third beat we'd use a minor chord.

    To do things consistently:

    cf = Chordify(types=["major"])

    There you go, all major chords.
    """

    # TODO: allow "None" as a type to just emit the note.

    def __init__(self, types=None, channel=None, when=True):

        """
        Constructor.  Read class docs.
        """

        super().__init__(channel=channel, when=when)
        self._types = types
        self.reset()

    def copy(self):
        return Chordify(types=self._types, channel=self.channel, when=self._when)

    def to_data(self):
        return dict(cls="camp.band.members.chordify.Chordify", data=dict(
            types = self.datafy(self._types),
            channel = self.datafy(self.channel),
            when = self.datafy(self._when)
        ))

    def reset(self):
        self._which_chord = self.draw_from(self._types)

    def on_signal(self, event, start_time, end_time):

        chord_typ = next(self._which_chord)

        if event.notes and len(event.notes) > 0:
            chord = Chord(root=event.notes[0], typ=chord_typ)
            event.notes = chord.notes

        for send in self.sends:
            send.signal(event, start_time, end_time)

        return [ event ] class Permit(Member):

    """
    Permit will only let some notes to pass through

    The following example will not play every 4th note.

    silence = Permit(when=Repeatedly([1,1,1,0]))

    The following example omits about 1/4 of the notes at random

    silence = Permit(when=Randomly([1,1,1,0]))

    The following example says every 4th note has a 50% chance of not playing

    silence = Permit(when=Randomly([1,1,1,0.5], mode='probability'))

    The following example is an absolute mute and may be of limited value.

    silence = Permit(when=0)
    """

    def __init__(self, when=None, channel=None):
        assert when is not None
        # NOTE: looks odd, but important that when is not passed to constructor here!
        super().__init__(channel=channel, when=1)
        self._when = when
        self.reset()

    def to_data(self):
        return dict(cls="camp.band.members.permit.Permit", data=dict(
            channel = self.datafy(self.channel),
            when = self.datafy(self._when)
        ))

    def copy(self):
        return Permit(when=self._when, channel=self.channel)

    def reset(self):

        self._should_silence = self.draw_from(self._when)

    def on_signal(self, event, start_time, end_time):

        permitted = next(self._should_silence)

        if not permitted:
            event.notes = []
            event.keep_alive = True

        for send in self.sends:
            send.signal(event, start_time, end_time)

        return [ event ] class Transpose(Member):


    def __init__(self, channel=None, octaves=None, when=True):

        # TODO: later also support semitone and step transpositions in higher level API
        # (already possible in lower levels)
        # or even grabbing the next note in the scale

        super().__init__(channel=channel, when=when)
        self._octaves = octaves
        self.reset()

    def to_data(self):
        return dict(cls="camp.band.members.transpose.Transpose", data=dict(
            octaves = self.datafy(self._octaves),
            channel = self.datafy(self.channel),
            when = self.datafy(self._when)
        ))

    def copy(self):
        return Transpose(channel=self.channel, octaves=self._octaves, when=self._when)

    def reset(self):
        self.octaves_looper = self.draw_from(self._octaves)


    def on_signal(self, event, start_time, end_time):

        amount = next(self.octaves_looper)

        if event.notes is not None and len(event.notes) > 0:
            notes = [ n.transpose(octaves=amount) for n in event.notes ]
            event.notes = notes

        for send in self.sends:
            send.signal(event, start_time, end_time)

        return [ event ]
from camp.core.scale import scale
from camp.band.conductor import Conductor
from camp.band.members.performance import Performance
from camp.band.members.scale_source import ScaleSource
from camp.band.members.roman import Roman
from camp.band.selectors.endlessly import Endlessly
from camp.band.selectors.randomly import Randomly
from camp.band.members.chordify import Chordify
from camp.band.members.permit import Permit
from camp.band.members.transpose import Transpose
"""
Copyright 2016, Michael DeHaan <michael.dehaan@gmail.com>

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""



def play():

    # Previously, in 11_randomness we introduced the default 'choice' random mode, which just endlessly
    # picks something from a list.

    # in 18_randomness2.py we introduced 'probability', which returns True or False depending on the chances
    # given.  It's used with the "when" statement, shown in both 18_randomness2.py and 19_skipover.py for doing
    # some rather fun things.

    # Some more types of randomness are "exhaust" and "human_random".

    # just to prove this performance will terminate by reaching the END of the performance, we're setting the stop
    # seconds to a crazy long amount of time.  This one won't loop endlessly, because of the way 'exhaust' mode works.
    output = Performance(bpm=120, stop_seconds=9999)

    # exhaust takes a list and feeds it in random order.  It would be the same as shuffle sorting the list, basically, and then
    # popping off the first element repeatedly.  The result is all items will be used, in a random order, and then the sequence
    # will stop.
    #
    # We normally write this in a one liner, but we'll break it up in this example to make things a bit more clear

    scale_choices = [scale("Ab blues"), scale("C major"), scale("Eb mixolydian")]
    scale_chooser = Randomly(scale_choices, mode='exhaust')
    source = ScaleSource(scales=Randomly(scale_choices, mode='exhaust'))

    # human_random is inspired by the Apple iTunes problem.  In a party shuffle mode, people may get suprised to learn
    # that the system played three Van Halen songs in a row.  Apple changed the system to switch between artists more frequently.
    # in CAMP, human random will play each item in a list once, before going back and starting over with a new random selection.

    note_choices = "1 2 3 4 5 6 7".split()
    note_chooser = Randomly(note_choices, mode='human_random')

    melody = Roman(symbols=note_chooser, channel=1)