class SongFactory(object):

    __slots__ = [ 'defaults', 'fx_buses', 'scenes', 'instruments', 'patterns' ]

    def __init__(self, name=None, author=None):
        self.defaults = dict()
        # by name, a dict of instrument objects
        self.instruments = dict()
        # by name, a dict of pattern objects
        self.patterns = dict()
        # by name, a  list of instantiated camp.band.member objects
        self.fx_buses = dict()
        # by name, a dict of scene objects
        # scene objects contain chain management code
        self.scenes = dict()

    def pattern(self, name):
        return self.patterns[name]

    def to_data(self):
        """
        Convert the composition to a simple datastructure.
        """

        data = dict(
            defaults = dict(),
            instruments = dict(),
            patterns = dict(),
            fx_buses = dict(),
            scenes = dict()
        )

        for (k, v) in self.instruments.items():
            data['instruments'][k] = v.to_data()

        for (k, v) in self.patterns.items():
            if type(v) == list:
                data['patterns'][k] = v
            else:
                data['patterns'][k] = v.to_data()

        for (k, v) in self.fx_buses.items():
            data['fx_buses'][k] = v.to_data()

        for (k,v) in self.scenes.items():
            data['scenes'][k] = v.to_data()
        return data

    def to_json(self):
        """
        Convert the composition to JSON
        """
        data = self.to_data()
        return json.dumps(data, indent=4)

    def save(self, filename):
        """
        Save the composition to disk.
        """
        fh = open(filename, "w")
        fh.write(self.to_json())
        fh.close()

    def set_defaults(self, **data):
        """
        Configures/reconfigures a series of global settings using kwargs.  Some of these values may later be overriden at the scene
        or other level.
        """
        assert type(data) == dict
        # example:
        # song.set_defaults(bpm=120)
        # (each of these set_* methods temporarily creates a validation object but the internal storage is a dictionary in SongFactory)
        self.defaults.update(Defaults(self, **data).as_dict())

    def set_fx_buses(self, **data):
        """
        Defines one or more FxBus objects using kwargs.  Each value should be a FxBus object, which in turn is constructed by a list of 1 or more
        module specifications that correspond to camp.band.member objects.
        """
        # example
        # song.set_fx_busses(
        #   arpeggiate_lead = FxBus(
        #      [module='arp', params=dict(splits=[4], octaves='transpose_pt1', mode='locked')])
        # )
        self.fx_buses.update(FxBuses(self, **data).as_dict())

    def set_instruments(self, **data):
        """
        Defines/redefines one or more instruments using kwargs.  Each value is an Instrument object.
        """
        assert type(data) == dict
        # example
        # song.set_instruments(
        #    strings = Instrument().set(channel=1),
        #    lead    = Instrument().set(channel=2),
        #)
        self.instruments.update(Instruments(self, **data).as_dict())

    def set_scenes(self, **data):
        """
        Defines/redefines one or more scenes using kwargs.  Each value is a Scene object.
        """
        assert type(data) == dict
        # example
        # song.set_scenes(
        #    overture = Scene(
        #        scale = "C4 major",
        #        bar_count = 12,
        #        # pre_fx = dict(strings='random_velocity_and_duration'),
        #        # post_fx = dict(strings='arpeggiate_lead'),
        #        patterns = dict(strings='basic_chords', lead=[ 'some_jam_pt2', 'some_jam_pt1' ])
        #    ),
        #    llama_theme = Scene(
        #        scale = "C4 major",
        #        bar_count = 12,
        #        # pre_fx = dict(strings = 'random_velocity_and_duration'),
        #        # post_fx = dict(strings = 'arpeggiate_strings', lead = 'transpose_lead'),
        #        post_fx = dict(strings='arpeggiate_lead'),
        #        patterns = dict(strings='basic_chords', lead=[ 'some_jam_pt1', 'some_jam_pt2' ])
        #    )
        #)
        self.scenes.update(Scenes(self, **data).as_dict())

    def set_patterns(self, typ='basic', mode=None, patterns=None):
        """
        Defines one or more patterns using kwargs.  Can specify endless or random patterns by changing 'typ'.
        Each typ may also respond to submodifier 'mode'.
        """
        assert type(patterns) == dict
        #Example:
        # song.set_patterns(typ='random', patterns=dict(
        #    scale  =[1,2,3,4,5,6,7],
        #    melody ="1 4 2 3 4 1",
        #    chords ="I IV V I"
        #))

        which = None
        if typ == 'basic':
            print("A")
            which = BasicPatterns(self, patterns=patterns)
        elif typ == 'random':
            print("B")
            which = RandomPatterns(self, patterns=patterns, mode=mode)
        elif typ == 'endless':
            print("C")
            which = EndlessPatterns(self, patterns=patterns)
        else:
            # TODO: support RepeatedPatterns here?
            raise Exception("unknown pattern type: %s" % typ)
        self.patterns.update(which.as_dict())


    def handle_scene(self, scene_name, play=False):

        scene = self.scenes.get(scene_name)
        if scene is None:
            raise Exception("no such scene: %s" % scene_name)
        conductor = Conductor(signal=scene.get_signals(), performance=scene.get_output())
        if play:
            conductor.start()

    def play(self, scene_names):

        # first validate
        for scene_name in scene_names:
            self.handle_scene(scene_name, play=False)

        # then play
        for scene_name in scene_names:
            self.handle_scene(scene_name, play=True) class Defaults(object):

    __slots__ = [ '_defaults', '_factory']

    def __init__(self, song, **kwargs):

        # the initial defaults object, if no defaults are set, has some global defaults
        self._defaults = CODED_DEFAULTS
        self._factory = song

        # now apply whatever default overrides from the user
        self.set(**kwargs)

    def set(self, **kwargs):

        # it is not valid to set a default that the system doesn't know about, so for instance
        # the system will yell at you if you misspell "scene_bar_count" as "scene_bar_ct".  This is
        # exclusively for typo prevention.

        keys = CODED_DEFAULTS.keys()
        for (k,v) in kwargs.items():
            if k not in keys:
                raise Exception("invalid default setting: %s" % k)
            self._defaults[k] = v
        return self

    def as_dict(self):
        return self._defaults class Instruments(object):

    __slots__ = [ "_instruments", "_factory" ]

    def __init__(self, song, **instruments):

        self._factory = song
        self._instruments = dict()

        for (instrument_name, instrument) in instruments.items():

            print("CREATING INSTRUMENT: %s" % instrument)

            instrument._factory = song
            
            print(instrument.channel)
            if instrument.channel is None:
                raise Exception("DEBUG: channel is None")


            if type(instrument) != Instrument:
                raise Exception("instruments collection requires an instrument")

            print("AIGHT")
            self._instruments[instrument_name] = instrument

    def as_dict(self):
        return self._instruments

    def to_data(self):
        results = dict(cls="camp.tracker.instruments.Instruments")
        for (k,v) in self._instruments():
            results['data'][k] = v.to_data() class Instrument(object):

    def __init__(self, channel=None, notation='roman'):

        self.channel = channel
        self.notation = notation

        if type(self.channel) != int:
            raise Exception("channel must be set, as an integer, got: %s" % self.channel)
        if notation not in VALID_NOTATIONS:
            raise Exception("invalid notation type: %s" % notation)

    def to_data(self):
        return dict(cls="camp.band.tracker.instrument.Instrument", data=dict(channel=self.channel, notation=self.notation)) class RandomPatterns(Patterns):

    def __init__(self, song, mode=None, patterns=None):
        self.mode = mode
        super().__init__(song, patterns=patterns)

    def create(self, pattern):
        return Randomly(pattern, mode=self.mode) class BasicPatterns(Patterns):

    def create(self, pattern):
        return pattern class EndlessPatterns(Patterns):

    def create(self, pattern):
        return Endlessly(pattern) class FxBuses(object):

    __slots__ = [ '_buses', '_factory' ]

    def __init__(self, song, **buses):

        self._factory = song
        self._buses = dict()

        for (bus_name, bus) in buses.items():
            if getattr(bus, '__call__', None) is not None:
                bus = bus(song)
            if not isinstance(bus, FxBus):
                raise Exception("only a FxBus is allowed inside of FxBusses set method, got: %s" % bus)
            self._buses[bus_name] = bus
            bus._factory = song

    def as_dict(self):
        return self._buses

    def to_data(self):
        result = dict(cls='camp.tracker.fx_bus.FxBuses')
        for (k,v) in self._buses:
            result['data'][k] = v.to_data()
        return result class FxBus(object):

    def __init__(self, member_list):
        self._nodes = [ member.copy() for member in member_list ]

    def nodes(self):
        return [ node.copy() for node in self._nodes ]

    def to_data(self):
        return [ x.to_data() for x in self._nodes ] class Scenes(object):

    __slots__ = [ '_scenes', '_factory' ]

    def __init__(self, song, **scenes):

        self._factory = song
        self._scenes = dict()

        for (scene_name, scene) in scenes.items():

            if getattr(scene, '__call__', None) is not None:
                scene = scene(song)
            if not isinstance(scene, Scene):
                raise Exception("only a Scene is allowed inside of Scenes set method, got %s" % scene)

            self._scenes[scene_name] = scene
            scene._factory = song
            scene.build()

    def as_dict(self):
        return self._scenes class Scene(object):

    def __init__(self, bpm=None, scale=None, pre_fx=None, post_fx=None, patterns=None, bar_count=None):

        self.scale = scale
        self.bpm = None
        self.pre_fx = pre_fx
        self.post_fx = post_fx
        self.patterns = patterns
        self.bar_count = bar_count
        self._factory = None # set up by scenes.py
        self._scale_source = ScaleSource(scales=core_scale(self.scale))
        self._output = None
        self._players = dict()


    def to_data(self):
        result = dict(
            scale = self.scale,
            bpm = self.bpm,
            pre_fx = self.pre_fx,
            post_fx = self.post_fx,
            patterns = self.patterns,
            bar_count = self.bar_count
        )
        #if self.pre_fx:
        #    for (k,v) in self.pre_fx.items():
        #        result['pre_fx'][k] = v.to_data()
        #if self.post_fx:
        #    for (k,v) in self.post_fx.items():
        #        result['post_fx'][k] = v.to_data()
        #if self.patterns:
        #    for (k,v) in self.patterns.items():
        #        result['patterns'][k] = v.to_data()
        return result

    def build(self):

        self._build_output()
        self._build_fx_chains()
        self._build_players()
        self._build_interconnects()

    def _build_output(self):

        # FIXME: the system uses stop_seconds to cap a scene at a given number of seconds
        # but we are really more interested in beats.  We probably want to attach a new timer
        # instance to the output to make this work instead, until then stop_seconds is hard coded
        # for DEBUG only and should be removed.  This should use defaults/scene_max_bars and bars on
        # the scene object.

        if self.bpm is None:
            self.bpm = self._factory.defaults['bpm']
        self._output = Performance(bpm=self.bpm, stop_seconds = 10)

    def _build_fx_chains(self):
        for (chain_name, bus) in self._factory.fx_buses.items():
            previous = None
            nodes = bus.nodes()
            for item in nodes:
                if previous is not None:
                    previous.sends = []
                    previous.send_to(item)
                previous = item

    def _build_players(self):

        for (instrument_name, pattern_list) in self.patterns.items():

            instrument = self._factory.instruments[instrument_name]
            channel = instrument.channel
            notation = instrument.notation
            sources = []

            print("PATTERN LIST= %s" % pattern_list)
            if isinstance(pattern_list, str):
                pattern_list = [ pattern_list ]

            for pattern_name in pattern_list:

                pattern = self._factory.patterns.get(pattern_name, None)
                if pattern is None:
                    raise Exception("pattern not found: %s" % pattern_name)

                real_pattern = None

                if notation == 'roman':
                    real_pattern = Roman(symbols=pattern)
                    print("RP=%s" % pattern)
                elif notation == 'literal':
                    real_pattern = Literal(symbols=pattern)
                else:
                    raise Exception("unknown notation type for instrument: %s" % instrument_name)

                sources.append(real_pattern)

            self._players[instrument_name] = Ordered(sources=sources)

    def _stitch_fx_chain(self, assignments, instrument_name, from_node, to_node):

        # SHOULD BE REMOVABLE
        #if assignments is None or instrument_name not in assignments:
        #    return

        fx_chain_name = assignments[instrument_name]
        if fx_chain_name not in self._factory.fx_buses:
            # FIXME: typed exceptions everywhere to make it easier for higher level apps
            raise Exception("fx bus not found: %s" % fx_chain_name)

        fx_chain = self._factory.fx_buses[fx_chain_name].nodes()

        # FIXME: BOOKMARK: I think need to implement COPY methods here as reuse of the same chain may cause
        # some interestingness... maybe.  Nodes should return copies of the objects (?)

        head = fx_chain[0]
        tail = fx_chain[-1]
        print("FROM %s to %s" % (from_node, head))
        print("AND FROM %s to %s" % (tail, to_node))
        from_node.send_to(head)
        tail.send_to(to_node)

    def _build_interconnects(self):

        for (instrument_name, player) in self._players.items():
            # FIXME: all of this stuff should use python standard logging
            print("---")
            print("CONFIGURING INSTRUMENT CHAIN: %s" % instrument_name)
            print(dir(player))


            player = self._players[instrument_name]
            #import pdb; pdb.set_trace()

            # TODO: consider whether it makes sense for a FxBus to connect to another FxBus
            # ignoring for now.

            if self.pre_fx is None or instrument_name not in self.pre_fx:
                # connect directly to source
                print("PRE DIRECT CONNECT")
                self._scale_source.send_to(self._players[instrument_name])
            else:
                # ensure prefx is coupled to source and tail is coupled to output
                print("PRE CONNECT")
                self._stitch_fx_chain(self.pre_fx, instrument_name, self._scale_source, player)

            instrument = self._factory.instruments[instrument_name]
            channel_assign = Channel(channel=instrument.channel)
            player.send_to(channel_assign)

            if self.post_fx is None or instrument_name not in self.post_fx:
                # connect directly to output
                print("POST DIRECT CONNECT")
                channel_assign.send_to(self._output)
            else:
                # ensure player is coupled to head of post fx and tail is coupled to output
                print("POST CONNECT")
                self._stitch_fx_chain(self.post_fx, instrument_name, channel_assign, self._output)

    def get_signals(self):
        return [ self._scale_source ]

    def get_output(self):
        return self._output 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 Arp(Member):

    """
    A powerful arpeggiator.

    When done, will be able to move by scale notes, octaves, and semitones,
    and also have both a free and locked mode.

    The various transpositions can have different lengths, for instance, a 5 note
    semitone arpeggiation pattern can transpose every second note by +2 octaves.
    As such, this arp can be pretty chaotic.

    Locked will reset with each incoming note signal, free will run regardless
    of the incoming note signal.

    This is, therefore, somewhat of a mashup of subdivide.py and transpose.py,
    with some extra features.
    """

    def __init__(self, channel=None, splits=None, semitones=None, octaves=None, scale_notes=None, rests=None, mode='locked', when=True):

        """
        Usage:

        - channel - MIDI channel to override, if any
        - semitones - offset by this many semitones for every arp note
        - octaves - offset by this many octaves for every arp note
        - scale_notes - offset by this many scale notes for every arp note (scale dependent)
        - rests - where this is true, do not play any notes
        - splits - decides how many arp notes to play inside each beat.  length=1 with no shifts is a no-op.
        - mode - if 'free', doesn't reset the arpeggiator with each beat.  Default 'locked', does.

        sd = Arp(
            splits=Endlessly[4],
            semitones=Endlessly[1,4,9],
            mode='free',
            rests=Endlessly[0,0,0,0,1]
            lengths=
        )

        # beat Fires
        # arp plays note+1, note+4, note+9, then note+1 semitones in sequence
        # beat Fires
        # arp plays REST, note+9, note+1, then note+4 semitones in sequence
        # beat Fires
        # arp plays note+9, note+1, then, note+9 semitones in sequence

        """

        super().__init__(channel=channel, when=when)

        print("ARP CH=%s" % self.channel)


        assert mode in [ 'free', 'locked']
        self._mode = mode

        # save the inputs (which might be selectors or just arrays) since if we are in 'locked'
        # mode we want to reset them at the beginning of each new beat.
        self._splits = splits
        self._semitones = semitones
        self._octaves = octaves
        self._scale_notes = scale_notes
        self._rests = rests

        self.current_scale = None
        self.previous_scale = None
        self.working_scale = None
        self._subdivide_amounts = self.draw_from(splits)
        self._run_once = True

    def to_data(self):
        return dict(cls="camp.band.members.arp.Arp", data=dict(
            channel = self.datafy(self.channel),
            mode = self.datafy(self._mode),
            splits = self.datafy(self._splits),
            semitones = self.datafy(self._semitones),
            octaves = self.datafy(self._octaves),
            rests = self.datafy(self._rests),
            when = self.datafy(self._when)
        ))

    def copy(self):
        return Arp(channel=self.channel, splits=self._splits, semitones=self._semitones, octaves=self._octaves, scale_notes=self._scale_notes,
          rests=self._rests, mode=self._mode, when=self._when)

    def reset(self):
        # for now, I don't believe we need this, because of the way free and locked
        # are already coded.
        pass

    def _scale_note_shift(self, scale_note_shift=None, note=None):
        raise NotImplementedError()

    def _handle_arp_note(self, event):

        octave_shift = next(self._octave_amounts)
        semitone_shift = next(self._semitone_amounts)
        scale_shift = next(self._scale_amounts)

        # FIXME: this copy may not be needed because of event.copy
        #notes = [ n.copy() for n in event.notes ]
        # if so, also remove from subdivide
        rest = next(self._rests_cond)

        if rest:
            print("ARP RESTING because %s" % rest)
            event.notes = []
        else:
            print("ARP GOT NOTES: %s" % event.notes)
            if len(event.notes) == 0:
                return
                #raise Exception("ow?")
            for (i,note) in enumerate(event.notes):
                if octave_shift:
                    print("ARP:OS")
                    event.notes[i] = note.transpose(octaves=octave_shift)
                if semitone_shift:
                    print("ARP:OS")
                    event.notes[i] = note.transpose(semitones=semitone_shift)
                if scale_shift:
                    print("ARP:OS")
                    event.notes[i] = self._scale_note_shift(scale_note_shift=scale_shift, note=note)

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

        if event.notes is None or len(event.notes) == 0:
            # FIXME: should have a function for this boilerplate pattern and use it in all instances
            for send in self.sends:
                send.signal(event, start_time, end_time)
            return event

        produced = []

        """
        Callback for the plugin.  This one is a bit complicated.
        """

        if self._scale_notes:
            raise NotImplementedError()

            #self.current_scale = event.get('scale', None)
            #if self.current_scale is None:
            #   raise Exception("requires scale data to arpeggiate on scale")
            # aside:
            # this new scale isn't SUPER huge for calculations so if you need to do something crazy like
            # jump around 5 octaves and also shift by scale notes, do it like this:
            # Arp(scale_notes=Endlessly([-2,0,2]), octaves=Endlessly([-5,5]))
            # NOT:
            # Arp(scale_notes=Endlessly([100,0,80,-36)])
            # as that will probably make stuff blow up and I can't be bothered with the error handling ATM.
            # self.working_scale_notes = [ note.transpose(octaves=-3) for note in scale.copy.generate(length=12*6) ]

        if (self.current_scale != self.previous_scale) or self._mode == 'locked' or self._run_once is False:
            print("ARP RESET")
            self._run_once = True
            self._octave_amounts = self.draw_from(self._octaves)
            self._semitone_amounts = self.draw_from(self._semitones)
            self._scale_amounts = self.draw_from(self._scale_notes)
            self._rests_cond = self.draw_from(self._rests)
            self.previous_scale = self.current_scale

        # FIXME: lots of repeated code with subdivide, let's make this share code!!!

        # calculate the length of the beat cycle
        delta = end_time - start_time
        # find out how many subdivisions there are for this beat
        slices = next(self._subdivide_amounts)
        # how long is each subdivision in seconds?
        each_slice_width = delta / slices
        # when does the first subdivision start?
        new_start_time = start_time

        # here we flag the note. This is because certain plugins like
        # ScalePlayer need to know what to do such that they shorten
        # the durations of the notes coming out, otherwise things won't work right.

        event.add_flags(subdivide=slices)

        # for however many times we are going to subdivide

        for slice_num in range(0,slices):

            new_event = event.copy()

            # calculate the end time of the new beat we are going to trigger

            new_end_time = new_start_time + each_slice_width
            new_event.time = new_start_time

            self._handle_arp_note(new_event)

            for send in self.sends:
                send.signal(new_event, new_start_time, new_end_time)

            # move on to the next beat in the subdivision.
            new_start_time = new_start_time + each_slice_width

            produced.append(new_event)

        return produced 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 Subdivide(Member):

    """
    Repeats a signal a given number of amounts.

    For instance, if we have a quarter note pulse from the conductor:
    Q Q Q Q

    This will produce in each "Q" beat four signals, rather than one.
    SSSS SSSS SSSS SSSS

    It does this by slicing up the beat into smaller beats.
    """

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

        """
        Usage:

        sd = Subdivide(amounts=[1,2,3,4])

        Notice the amounts array.

        This means that for the first beat, there is only one subdivision (no subdivision).
        For the next "beat", the notes will be subdivided twice.
        Then three times, then four.

        After that, we'll cycle back and do 1 subdivision again. Ad nauseum.
        """

        super().__init__(channel=channel, when=when)
        self._splits = splits
        self.reset()

    def to_data(self):
        return dict(cls="camp.band.members.subdivide.Subdivide", data=dict(
            splits = self.datafy(self._splits),
            channel = self.datafy(self.channel),
            when = self.datafy(self._when)
        ))

    def copy(self):
        return Subdivide(channel=self.channel, when=self._when, splits=self._splits)

    def reset(self):

        self._subdivide_amounts = self.draw_from(self._splits)

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

        """
        Callback for the plugin.  This one is a bit complicated.
        """

        produced = []

        # calculate the length of the beat cycle
        delta = end_time - start_time
        # find out how many subdivisions there are for this beat
        slices = next(self._subdivide_amounts)
        # how long is each subdivision in seconds?
        each_slice_width = delta / slices
        # when does the first subdivision start?
        new_start_time = start_time

        # here we flag the note. This is because certain plugins like
        # ScalePlayer need to know what to do such that they shorten
        # the durations of the notes coming out, otherwise things won't work right.

        event.add_flags(subdivide=slices)

        # for however many times we are going to subdivide

        for slice_num in range(0,slices):

            event = event.copy()

            # calculate the end time of the new beat we are going to trigger

            new_end_time = new_start_time + each_slice_width

            event.time = new_start_time

            print("SUBDIVIDE SENDS: %s" % self.sends)
            for send in self.sends:
                send.signal(event, new_start_time, new_end_time)

            # move on to the next beat in the subdivision.
            new_start_time = new_start_time + each_slice_width

            produced.append(event)

        return produced 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.tracker.song_factory import SongFactory
from camp.tracker.defaults import Defaults
from camp.tracker.instruments import Instruments
from camp.tracker.instrument import Instrument
from camp.tracker.patterns import RandomPatterns, BasicPatterns, EndlessPatterns
from camp.tracker.fx_buses import FxBuses
from camp.tracker.fx_bus import FxBus
from camp.tracker.scenes import Scenes
from camp.tracker.scene import Scene
from camp.band.members.chordify import Chordify
from camp.band.members.arp import Arp
from camp.band.members.permit import Permit
from camp.band.members.subdivide import Subdivide
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.
"""


# ---------------------------------------------------------------------------------
# WARNING -- THIS EXAMPLE IS UNDER DEVELOPMENT CURRENTLY
# AND IS NOT FINAL.  What this shows is how to describe a composition more
# conceptually.  The sequencer (interface for people who do not want to write code)
# will build off of this.
# ----------------------------------------------------------------------------------


def play():

    # We are building a house of fire, baby.

    song = SongFactory(
        name='Foo',
        author='Your Name'
    )

    song.set_defaults(
        bpm = 120,
        scene_bar_count = 8
    )

    song.set_instruments(
        strings = Instrument(channel=1),

        lead    = Instrument(channel=2),