class View(object):
    """
    A View is made up of two components:
    - A configuration: How the buttons and dials on the input device should behave
    - A Map: Connects the input device's controls (by their ID) to Targets like
             Parameters, Commands etc. from which their current values can also
             be infered.
    When triggerivating a View, the input Device needs to be reconfigured and the
    values of each mapped target transmitted to that device for it to show those
    values on the hardware.
    """
    def __init__(self, device, name="Unnamed View"):
        # WARNING! The device might be in a threadshell, therefore do not use any of its
        #          methods. Just access attributes. Methods on those attributes are fine though.
        self.name = name
        # Map IDs of a device's controls to configurations:
        # - Buttons: toggle vs momentary
        self.configuration = {}
        for ID, control in device.controls.items():
            self.configuration[ID] = copy(control.default_conf)

        # Map IDs of a device's controls to Targets
        self.map = ddict(list)

    def find_IDs_by_target(self, vtarget):
        """
        Make a list of IDs on this view mapped to the provided target.
        """
        IDs = []
        for ID, vtargets in self.map.items():
            for target in vtargets:
                if target == vtarget or target.is_connected_to(vtarget):
                    IDs.append(ID)
        return IDs

    def map_this(self, ID, t):
        """
        Add a mapping from the ID to the target
        """
        self.map[ID].append(t)

    def unmap(self, ID):
        """
        Remove all mappings of the provided ID
        """
        self.map[ID] = []

    def unmap_target(self, target):
        """
        Remove all mappings to the provided target
        """
        for targets in self.map.values():
            if target in targets: targets.remove(target) 

    def __eq__(self, other):
        return self.name == other.name

    def __repr__(self):
        return self.name

    def __str__(self):
        return self.__repr__() class FlexSetter(Target):
    """
    Is connected to a Flex Target. Upon triggering, it takes the last manipulated
    targets from the interface and provides them to the Flex Target.
    """

    def __init__(self, name, parent, flex_parameter):
        super().__init__(name, parent)
        self.flex = flex_parameter
        self.deferred = None

    def trigger(self, sender, value=None):
        if self.deferred:
            self.flex = self.parent.targets[self.deferred]
            print(">>> Resolved deferred flex link", self, self.flex)
            self.deferred = None

        if value:
            self.flex.link(self.parent.last_modified_targets)

    def serialize(self, *args, **kwargs):
        s = super().serialize(*args, **kwargs) 
        s["flex"] = self.flex.name
        return s

    def from_dict(self, d):
        super().from_dict(d)
        try:
            self.flex = self.parent.targets[d["flex"]]
        except KeyError:
            eprint("Flex target %s for %s not yet instantiated. Defering until first trigger")
            self.deferred = d["flex"]

    @classmethod
    def blank(cls, parent):
        return cls("unnamed", parent, None) class FlexParameter(ValueTarget):
    """
    Forwards all triggers to the linked targets which are set by FlexSetter.
    """

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.parameters = set()

    def link(self, targets):
        """
        Gets called by the FlexSetter. Release old targets and link with new targets.
        """
        self.parameters = set(targets)
        ft = next(iter(targets))
        self._value = ft.value
        self.parent.target_triggered(self, self._value, self)

    def unlink(self):
        self.parameters = set()

    def trigger(self, sender, value=None):
        """
        Forward the trigger value and sender to the linked parameters.
        """
        real_value = None
        for parameter in self.parameters:
            real_value = parameter.trigger(self, value)

        return real_value

    @property
    def value(self):
        """
        Pick a target from the ones linked to and return their value
        """
        try:
            ft = next(iter(self.parameters))
            return ft.value
        except StopIteration:
            # We are not linked to anything
            return 0 

    def is_connected_to(self, target):
        return target in self.parameters class ModView(Target):
    """
    If the button is held:
    Constructs a temporary view based on the power of a Modifier on the
    Targets in the interface's active view.
    If the button is pushed momentarily:
    Constructs a temporary view to configure the modifier itself.
    """

    trigger_vals = [0, 127]

    def __init__(self, name, parent, modifier):
        super().__init__(name, parent)
        self.modifier = modifier
        self.prev_view = None
        self.deferred = None

        self.value = 0
        self.time_pressed = None
        self.in_config_view = False

        self.config_view_targets = {}
        self.power_view_targets = {}

    def construct_config_view(self):
        temp_view = View(self.parent.input, name="%s_ModConfigView" % self.modifier)

        ID = self.prev_view.find_IDs_by_target(self)[0]
        temp_view.map_this(ID, self)
        temp_view.configuration[ID]["toggle"] = False
        temp_view.configuration[ID]["blink"] = True

        # Filter out the ID we mapped to go back from the universal controls
        # so we don't end up stuck in config view or mapping to that ID twice
        type_IDs = self.parent.get_universal_controls_as_iterators(exclude=ID)

        def map_modconfig_target(attr_desc, target):
            try:
                ID = next(type_IDs[attr_desc.type])
                temp_view.map_this(ID, target)
            except StopIteration:
                eprint("Not enough universal controls available to make %s available in Config View" % target)

        # Construct a target that displays the Modifier's current value
        attribute_descriptor = AttributeDescriptor("value", -1.0, 1.0, float, AttributeType.span, True, None)
        try:
            modconfig = self.config_view_targets["value"]
        except KeyError:
            modconfig = ModConfig("value", self.parent, DummyModifier(), attribute_descriptor)
            self.modifier.target(modconfig, power=0.5)
            self.config_view_targets["value"] = modconfig
        map_modconfig_target(attribute_descriptor, modconfig)

        # Construct targets for the configurable attributes of the Modifier
        for attribute_descriptor in self.modifier.attribute_configs:
            try:
                modconfig = self.config_view_targets[attribute_descriptor.name]
            except KeyError:
                modconfig = ModConfig(attribute_descriptor.name, self.parent, self.modifier, attribute_descriptor)
                self.config_view_targets[attribute_descriptor.name] = modconfig

            map_modconfig_target(attribute_descriptor, modconfig)

        # Map the button we came here with to get us back
        return temp_view

    def construct_power_view(self):
        """
        Constructs and shows a view that let's the user adjust the power of the modifier
        on the targets available in the current view.
        """
        temp_view = View(self.parent.input, name="%s_ModView" % self.modifier)

        for ID, targets in self.prev_view.map.items():
            for target in targets:
                if isinstance(target, ValueTarget) and not isinstance(self.parent.input.controls[ID], Button):
                    try:
                        mp = self.power_view_targets[target]
                    except KeyError:
                        mp = ModPower("%s_%s_PWR" % (target, self.modifier), self.parent, self.modifier, target)
                        self.power_view_targets[target] = mp
                    temp_view.map_this(ID, mp)

        # Map the button we came here with to get us back
        ID = self.prev_view.find_IDs_by_target(self)[0]
        temp_view.map[ID].append(self)
        temp_view.configuration[ID]["toggle"] = False

        return temp_view

    def trigger(self, sender, value=None):
        """
        Switch to the mod_config_view or mod_power_view based on how long the button
        is held/pushed.
        """
        if value is False:
            return None

        if self.modifier is None:
            self.link_to_mod()

        super().trigger(sender, value)

        # Longpress/Shortpres logic
        if value == 127:
            self.value = 127
            if not self.in_config_view:
                self.time_pressed = time()
                self.prev_view = self.parent.view
                self.enter_power_view()
        elif value is 0:  # Ignore False, actually check for 0 (thanks Python)
            self.value = 0
            if not self.in_config_view:
                if self.time_pressed and (time() - self.time_pressed) <= LONGPRESS_TIME:
                    self.enter_config_view()
                else:
                    self.go_back()
            else:
                self.go_back()

    def enter_power_view(self):
        self.parent.switch_to_view(self.construct_power_view(), temp=True)

    def enter_config_view(self):
        """
        Constructs and shows a view that let's the user configure the modifier attributes
        itself (amplitude, frequeny ...) with a display of the waveform.
        """
        self.time_pressed = None
        self.in_config_view = True
        self.parent.switch_to_view(self.construct_config_view(), temp=True)

    def go_back(self):
        """
        Go back to the view that was shown before switching to the mod views.
        """
        self.time_pressed = None
        self.in_config_view = False
        self.parent.switch_to_view(self.prev_view)

    def serialize(self, *args, **kwargs):
        s = super().serialize(*args, **kwargs) 
        s["modifier"] = self.modifier.name
        return s

    def from_dict(self, d):
        super().from_dict(d)
        self.link_to_mod(name=d["modifier"])

    def link_to_mod(self, name=None):
        if name is None:
            name = self.deferred
        try:
            self.modifier = self.parent.get_modifier(name)
        except KeyError:
            print("Deferring resolution of modifier", name)
            self.deferred = name

    @classmethod
    def blank(cls, parent):
        return cls("unnamed", parent, None) class SnapshotButton(Target):
    """
    Is connected to a Flex Target. Upon triggering, it takes the last manipulated
    targets from the interface and provides them to the Flex Target.
    """

    trigger_vals = [0, 127]

    def __init__(self, name, parent, selector, *args, **kwargs):
        super().__init__(name, parent, *args, **kwargs)
        self.selector = selector
        self.deferred = None

    def trigger(self, sender, value=None):
        if self.deferred:
            self.selector = self.parent.targets[d[self.deferred]]
            self.deferred = None

        if value is 127:
            self.down = time()
            return None
        elif value is 0:
            delta = time() - self.down 
            if delta < LONGPRESS_THRESHOLD:
                self.selector.issue_load()
            else:
                self.selector.issue_save()

    def serialize(self, *args, **kwargs):
        s = super().serialize(*args, **kwargs) 
        s["selector"] = self.selector.name
        return s

    def from_dict(self, d):
        super().from_dict(d)
        try:
            self.selector = self.parent.targets[d["selector"]]
        except KeyError:
            eprint("Selector target %s for %s not yet instantiated. Defering until first trigger")
            self.deferred = d["flex"]

    @classmethod
    def blank(cls, parent):
        return cls("unnamed", parent, None) class SnapshotSelector(ValueTarget):
    """
    
    """

    def issue_load(self):
        self.parent.load_snapshot(self.value)

    def issue_save(self):
        self.parent.save_snapshot(self.value)

    @classmethod
    def blank(cls, parent):
        return cls("unnamed", parent) class Parameter(ValueTarget):
    """
    A type of target that simply maps the incoming value to a
    Control Change midi signal on the configured (output) device.
    Button values are (for now) hardcoded to 0 for Off and 127 for On.
    """
    def __init__(self, name, parent, channel, cc, initial=0, is_button=False, **kwargs):
        super().__init__(name, parent, initial, **kwargs)
        self.channel = channel
        self.cc = cc
        self.is_button = is_button

    def serialize(self, *args, **kwargs):
        s = super(Parameter, self).serialize(*args, **kwargs) 
        s["cc"] = self.cc
        s["channel"] = self.channel
        s["value"] = self.value
        s["is_button"] = self.is_button
        return s

    def trigger(self, sender, value=None):
        """
        Forwards the value to the configured (output) Device with
        the transmitted value.
        """
        if value is not None:
            if self.modifiers:
                # We want to modify the center value because the one
                # displayed on the Dial is shifted by the modifiers
                self.assume_center_value(value)
            else:
                # Convert anything that evaluates to True as 127
                if self.is_button:
                    if value:
                        value = 127
                    else:
                        value = 0
                self.value = value
        else:
            # Called without value, use own value
            pass

        if sender != self.parent.output:
            self.parent.to_output(self.channel, self.cc, self.value)

        # Skip the ValueTarget parent because of our complicated way of setting
        # self._value and go directly to its Parent, Target
        Target.trigger(self, sender, self.value)
        return self.value

    def from_dict(self, d):
        super(Parameter, self).from_dict(d)
        self.channel = d["channel"]
        self.cc = d["cc"]
        self.value = d["value"]
        self.is_button = d["is_button"]

    @classmethod
    def blank(cls, parent):
        return cls("unnamed", parent, 1, 0)

    def is_connected_to_output(self, channel, cc):
        return channel == self.channel and cc == self.cc class SwitchView(Target):
    """
    Issues the command to switch to a preconfigured View
    when triggered.
    """

    trigger_vals = [127]

    def __init__(self, name, parent, view):
        super().__init__(name, parent)
        if type(view) == str:
            self.view_name = view
        else:
            self.view_name = view.name

    def trigger(self, sender, value=None):
        super().trigger(sender, value)
        self.parent.switch_to_view(self.view_name)

    def serialize(self, *args, **kwargs):
        s = super(SwitchView, self).serialize(*args, **kwargs) 
        s["view"] = self.view_name
        return s

    def from_dict(self, d):
        super(SwitchView, self).from_dict(d)
        self.view_name = d["view"]

    @classmethod
    def blank(cls, parent):
        return cls("unnamed", parent, "") class PageFlip(Target):
    """
    Issues the command to pageflip on a device.
    """

    def __init__(self, name, parent, device, **kwargs):
        super().__init__(name, parent, **kwargs)
        self.device = device

    def trigger(self, sender, value=None):
        super().trigger(sender, value)
        self.device.page = 1 if value >= 64 else 0

    def serialize(self, *args, **kwargs):
        s = super().serialize(*args, **kwargs) 
        s["device"] = self.device.name
        return s

    def from_dict(self, d):
        super().from_dict(d)
        self.device = self.parent.get_device(d["device"])

    @classmethod
    def blank(cls, parent):
        return cls("unnamed", parent, parent.input) class BPM(ValueTarget):

    def __init__(self, name, parent, minimum=30, maximum=157):
        self.clock = parent.clock
        super().__init__(name, parent, minimum=minimum, maximum=maximum)

    @property
    def value(self):
        return self.clock.bpm - self.minimum

    @value.setter
    def value(self, value):
        before = self._value
        ValueTarget.value.fset(self, value)  # Pycharm complains here but it's correct
        if before != self._value:
            self.clock.bpm = self._value MIN_MOD = 0
MAX_MOD = 127
class Modifier(Configurable):
    def __init__(self, name, amplitude=MAX_MOD):
    def serialize(self):
    def from_dict(self, m, all_targets):
    def target(self, target, power=1.0):
    def remove_target(self, target):
    def clear_targets(self):
    def amplitude(self):
    def amplitude(self, a):
    def value(self):
    def modvalue(self):
    def tick(self, time_report):
    def calculate(self, t):
    def save(self):
    def load(self, d, i):
    def blank(cls):
    def __repr__(self):
    def __str__(self): class Basic(Modifier):
    """
    A basic modulator that just needs a frequency to operate. It can choose from a list
    of LFOs at any time.
    
    :param frequency: in Hz
    :param positive: centered mode vs positive mode (see module docstring)
    :param offset: time offset of the cycle 
    """

    attribute_configs = (
        ## TOP ROW
        # First Dial displays the LFO value
        AttributeDescriptor("freq_sync",    0, SYNC_MAX_CONFIG,   int, AttributeType.span, False, None),
        AttributeDescriptor("amplitude",    0,             127,   int, AttributeType.span, False, None),
        AttributeDescriptor("offset",    -0.5,             0.5, float, AttributeType.span, False, None),
        AttributeGapSpan,
        AttributeGapSpan,
        AttributeGapSpan,
        AttributeGapSpan,

        ## MIDDLE ROW
        AttributeDescriptor("lfo",          0, 127,   int, AttributeType.span, False, None),
        AttributeDescriptor("frequency", 0.01,  10, float, AttributeType.span, False,  100),

        AttributeDescriptor("positive", 0, 1, bool, AttributeType.boolean, False, None),
        AttributeDescriptor("synced",   0, 1, bool, AttributeType.boolean, False, None),
    )

    def __init__(self, name, frequency=0.25, positive=False, offset=0, init_lfo=Sine, synced=True, **kwargs):
        super().__init__(name, **kwargs)
        self.frequency = frequency

        self.frequency_synced = Fraction("1")

        self.positive = positive
        self.offset = offset
        self.lfos = [L() for L in LFOs]
        self._lfo = pick_lfo_from_list(init_lfo, self.lfos)

        # Variables for keeping in sync with the clock
        # TODO: When changing synced value, calculate frequency from frequency_synced and vice versa
        self.synced = synced
        self.last_prog = 0.0
        self.sync_segment = 0

        # Variables for keeping track of free running oscillation
        self.free_t = 0.0

    def calculate(self, time_report):
        """
        Returns the LFO's value based on the time report.
        :param time_report: injected from the clock object
        :return: LFO value in [-1, 1] c R
        """

        if self.synced:
            wrapped_prog = time_report.prog % self.frequency_synced
            #iprint(self.name == "LFOSine", self.frequency_synced, self.sync_segment, time_report.prog, wrapped_prog, self.last_prog)
            if wrapped_prog <= self.last_prog:
                self.sync_segment = (self.sync_segment + 1) % self.frequency_synced

            t = (self.sync_segment + wrapped_prog) / self.frequency_synced
            self.last_prog = wrapped_prog
        else:
            self.free_t += self.frequency * time_report.delta
            t = self.free_t

        t = (t + self.offset) % 1

        # Dampen the amplitude of "centered" waves by 0.5
        # That way, setting the power of modulation in ModView
        # the set range equates directly to the range of oscillation
        # However, it wouldn't allow full range modulation in centered
        # mode. It feels better, but makes the centered mode less useful.
        # * (1 + int(self.positive)) / 2
        return self._lfo.wave(t, self.positive)

    @property
    def freq_sync(self):
        return index_of_sync_freq(self.frequency_synced)

    @freq_sync.setter
    def freq_sync(self, index):
        try:
            self.frequency_synced = SYNC_STEPS[index]
        except IndexError:
            eprint(self, "Sync Step Index out of range")

    @property
    def lfo(self):
        return self.lfos.index(self._lfo)

    @lfo.setter
    def lfo(self, value):
        self.switch_to_lfo(value)

    def serialize(self):
        """
        Serializes Modifier attributes into a dictionary 
        :return: dict to recreate the object in its current state
        """
        m = super().serialize()
        m["frequency"] = self.frequency
        m["positive"] = self.positive
        m["offset"] = self.offset
        m["lfo"] = self._lfo.__class__.__name__
        return m

    def from_dict(self, m, *args, **kwargs):
        """
        Recreates the state of the object from a dictionary generated by serialize
        :param m: the dictionary
        :param args: see parent class
        :param kwargs: see parent class
        """
        super().from_dict(m, *args, **kwargs)
        self.frequency = m["frequency"]
        self.positive = m["positive"]
        self.offset = m["offset"]
        self.lfo = self.lfos.index(pick_lfo_from_list_by_name(m["lfo"], self.lfos))

    def save(self):
        d = super().save()
        d["frequency"] = self.frequency
        d["positive"] = self.positive
        d["offset"] = self.offset
        d["lfo"] = self._lfo.__class__.__name__
        return d

    def load(self, d, i):
        super().load(d, i)
        self.frequency = d["frequency"]
        self.positive = d["positive"]
        self.offset = d["offset"]
        self.lfo = self.lfos.index(pick_lfo_from_list_by_name(d["lfo"], self.lfos))

    def switch_to_lfo(self, index):
        """
        Switch the the LFO at the provided index in the Modifiers self.lfos list.
        Doesn't complain if the index is out of range.
        :param index: Index of the LFO to switch to
        """
        try:
            self._lfo = self.lfos[index]
        except IndexError:
            eprint("No LFO in slot", index) class Sine(LFO):
    def wave(self, t, positive=False):
        if positive:
            return (sin(t * 2 * pi) + 1) / 2
        else:
            return -cos(t * 2 * pi) class Square(LFO):
    def wave(self, t, positive=False):
        if positive:
            return int(t < 0.25 or t > 0.75)
        else:
            return int(t < 0.5) * 2 - 1 class Saw(LFO):
    def wave(self, t, positive=False):
        if positive:
            return t
        else:
            return t * 2 - 1 class SampledRandom(LFO):
    last_t = 0
    current_value = 0
    def wave(self, t, positive=False):
        if t <= self.last_t:
            if positive:
                self.current_value = random()
            else:
                self.current_value = random() * 2 - 1

        self.last_t = t
        return self.current_value class StepSequencer(Modifier):

    steps = [AttributeDescriptor("step_%i" % i, -1.0, 1.0, float, AttributeType.span, False, None) for i in range(16)]

    attribute_configs = (
        # First control is always the current value display
        AttributeDescriptor("frequency", 0.01,  10, float, AttributeType.span, False, 100),
        AttributeDescriptor("amplitude",    0, 127, int,   AttributeType.span, False, None),
        AttributeDescriptor("offset",    -0.5, 0.5, float, AttributeType.span, False, None),
        AttributeDescriptor("length",       1,  16, int,   AttributeType.span, False, None),
        AttributeGapSpan,
        AttributeGapSpan,
        AttributeGapSpan,

        *steps,
    )

    MAX_STEPS = 16

    def __init__(self, name, frequency=0.25, length=MAX_STEPS, **kwargs):
        super().__init__(name, **kwargs)
        self.frequency = frequency
        self.offset = 0.0
        self.length = 7
        self.steps = [0.0] * StepSequencer.MAX_STEPS

        self.cursor = 0.0
        self.last_t = None

    @staticmethod
    def step_from_attr(attr):
        return int(attr.split("_")[1])

    def __getattr__(self, attr):
        if attr.startswith("step_"):
            return self.steps[self.step_from_attr(attr)]
        else:
            return super().__getattribute__(attr)

    def __setattr__(self, attr, v):
        if attr.startswith("step_"):
            self.steps[self.step_from_attr(attr)] = v
        else:
            super().__setattr__(attr, v)

    def serialize(self):
        """
        Serializes Modifier attributes into a dictionary 
        :return: dict to recreate the object in its current state
        """
        m = super().serialize()
        m["frequency"] = self.frequency
        m["length"] = self.length
        m["offset"] = self.offset
        return m

    def from_dict(self, m, *args, **kwargs):
        """
        Recreates the state of the object from a dictionary generated by serialize
        :param m: the dictionary
        :param args: see parent class
        :param kwargs: see parent class
        """
        super().from_dict(m, *args, **kwargs)
        self.frequency = m["frequency"]
        self.length = m["length"]
        self.offset = m["offset"]

    def save(self):
        d = super().save()
        d["frequency"] = self.frequency
        d["length"] = self.length
        d["offset"] = self.offset
        return d

    def load(self, d, i):
        super().load(d, i)
        self.frequency = d["frequency"]
        self.length = d["length"]
        self.offset = d["offset"]

    def calculate(self, time_report):
        # TODO: Sync to midi clock
#        t *= self.frequency
        t = time_report.prog

        if self.last_t is None:
            self.cursor = t
        else:
            self.cursor += t - self.last_t

        self.last_t = t

        self.cursor = (self.cursor + self.offset) % 1

        return self.steps[int(self.cursor * 16)]
import util
from smci import View
from targets import Parameter, SwitchView, PageFlip, FlexSetter, FlexParameter, ModView, SnapshotButton, SnapshotSelector, BPM
from modifiers.modifier import AttributeType
from modifiers.basic import Basic, Sine, Square, Saw, SampledRandom
from modifiers.stepsequencer import StepSequencer
    """ INIT VIEW  DIALS
    Just map all of the dials to simple CC Parameters. Nothing fancy
    bcr.dials includes the second set of dials we can reach using the pageflip function.
    """
    for dial in bcr.dials:
        i.quick_parameter(dial.ID)

    bpm = BPM("BPM", i)
    view_init.map_this(bcr.dialsc[0][2].ID, bpm)


    """ MENU BUTTONS: Let's get complicated!
    The top row of buttons let's us select a track. On each track, the buttons in the bottom row activate
    and deactivate the 8 effects on that track. Each column of 6 dials (pageflip!) controls maps to the
    parameters of that effect. 
    
    That means, for each track we need to have a View with unique Parameters per Dial. We also need to make
    sure that in each view the top row still maps to the other Track Views. The button of the currently active
    view should blink and lead us back to the Init View.
    
    Suppose you're back in the Init View. Instead of choosing a track on the top row, you can select an effect on
    the bottom row. Now, the top row houses the activators for that effect for each track. These are the same
    Targets (not duplicates, the exact same objects) we mapped per track before that. We do the same things with
    the dials.
    
    Tapping another button in the bottom row chooses a different effect. Tapping the same button (that's again
    blinking) takes us back to Init View.
    """
    # Construct the Views. We'll fill them later but need them now to construct SwitchTargets
    views_tracks   = [View(bcr, "Track_%i" % (index + 1)) for index in range(8)]
    views_fx       = [View(bcr, "FX_%i"    % (index + 1)) for index in range(8)]

    # These SwitchTargets will bring us to the Views constructed above
    switch_tracks  = [SwitchView("To_T%i"  % (index + 1), i, views_tracks[index]) for index in range(8)]
    switch_fx      = [SwitchView("To_FX%i" % (index + 1), i, views_fx[index])     for index in range(8)]
    switch_to_init =  SwitchView("To_INIT",               i, view_init)

    # Distribute them on the Init View
    map_controls_to_targets(view_init, bcr.menu_rows(0), switch_tracks)
    configure_controls(view_init, bcr.menu_rows(0), "toggle", False)

    map_controls_to_targets(view_init, bcr.menu_rows(1), switch_fx)
    configure_controls(view_init, bcr.menu_rows(1), "toggle", False)


    ### FOR ALL OF THE FOLLOWING VIEWS
    ### We want to set up the follow controls, so lets define a helper function for that
    def controls_for_all(view):
        """
        - Macro Buttons and Dials
        - Pageflip
        - Modifiers
        :param view: The view on which to map
        """
        ## @TODO: Create some "map globally" functionality so we only need to do this once for all possible views
        ##        or at least a subset of views.

        # Macros
        for dial, target in zip(bcr.macros, macro_dial_targets):
            view.map_this(dial.ID, target)
        for mbutton, target in zip(bcr.macro_bank_buttons(0), macro_button_targets):
            view.map_this(mbutton.ID, target)
            view.configuration[mbutton.ID]["toggle"] = False

        # Command Buttons:
        view.map_this(pageflip_button.ID, global_pageflip)
        view.configuration[pageflip_button.ID]["toggle"] = True

        # Modifiers
        map_controls_to_targets(view, bcr.command_buttons, lfo_views)
        configure_controls(view, bcr.command_buttons, "toggle", False)

        view.map_this(bcr.command_buttons[3].ID, steps_view)
        view.configuration[bcr.command_buttons[3].ID]["toggle"] = False
    ### END GLOBAL STUFF


    ### We start by going track by track and mapping the FX activators, FX parameters and of course
    ### our top row menu buttons to lead us to the other tracks or back to Init View

    # Create a generator that will yield the next combination of Channel and CC Value for us
    gen = ccc(channel=i.parameter_maker.channel, cc=i.parameter_maker.next_cc, forbidden=i.parameter_maker.forbidden)
    gen_n = lambda n: [next(gen) for _ in range(n)]

    # We want to move through the dials by column, but do so in one big list
    dials = util.flatten(bcr.dialsc)

    # Now let's iterate per Track
    for track_index in range(8):
        view = views_tracks[track_index]

        ## First, let's map things that we want to keep from init view, our global controls if you will
        controls_for_all(view)

        # First Row Buttons: Switch To View
        index = 0
        for button, target in zip(bcr.menu_rows(0), switch_tracks):
            view.configuration[button.ID]["toggle"] = False
            if index != track_index:
                view.map_this(button.ID, target)
            else:
                view.map_this(button.ID, switch_to_init)
                view.configuration[button.ID]["blink"] = True
            index += 1

        # Second Row Buttons: Effects activators
        fx_onoff = []
        for index, (channel, cc) in enumerate(gen_n(8), start=1):
            p = Parameter("T%i_FX%i_OnOff" % (track_index + 1, index), i, channel, cc, is_button=True)
            fx_onoff.append(p)
        map_controls_to_targets(view, bcr.menu_rows(1), fx_onoff)
        # SPECIAL CASE: Stutter and Repeater momentary
        # @TODO: Move this somewhere else for easier configuration
        view.configuration[bcr.menu_rows(1)[4].ID]["toggle"] = False
        view.configuration[bcr.menu_rows(1)[6].ID]["toggle"] = False

        # Effects parameters
        fx_params = []
        subparam_index = 0
        for index, (channel, cc) in enumerate(gen_n(48), start=1):

            p = Parameter("T%i_FX%i_%i" % (track_index + 1, (index - 1) / 6 + 1, subparam_index + 1), i, channel, cc)