class Defaults:
    """
    some default values used to fill in missing data
    """
    note = "a4"
    dur = 1 / 4
    playeddur = 0.9
    lag = 0
    vol = 70
    tempo = 100
    octave = "4" NO_OF_CONTROLLERS = 129 NO_OF_OFFICIAL_CONTROLLERS = 128 NO_OF_TRACKS = 16 class PhraseProperty:
    """
    list of animatable properties
    """
    NOTE = "0"
    DUR = "1"
    PLAYEDDUR = "2"
    VOL = "3"
    LAG = "4"
    TEMPO = "5"

    @classmethod
    def ctrl_dur_key(cls, cc_number):
        """
        calculates a key for specifying control change duration value (internal usage)
        :param cc_number: control change number
        :return: cc key
        """
        return f"D{cc_number}"

    @classmethod
    def ctrl_val_key(cls, cc_number):
        """
        calculates a key for specifying control change value (internal usage)
        :param cc_number: control change number
        :return: cc key
        """
        return f"V{cc_number}" class MidiControlChanges:
    """
    list of midi control change messages
    """
    BankSelect = 0  # Bank Select Allows user to switch bank for patch selection.Program change used with
    # Bank Select. MIDI can access 16384 patches per MIDI channel.

    Modulation = 1  # Generally this CC controls a vibrato effect(pitch, loudness, brighness).What is modulated is
    # based n the patch.

    BreathController = 2  # Often times associated with aftertouch messages.It was originally intended for use with a
    # breath MIDI controller in which blowing harder produced higher MIDI control values.
    # It can be used for modulation as well.

    FootController = 4  # Often used with aftertouch messages.It can send a continuous stream of values based on how
    # the pedal is used.

    PortamentoTime = 5  # Controls portamento rate to slide between 2 notes played subsequently

    DataEntry = 6  # Data Entry Most Significant Bit(MSB) Controls Value for NRPN or RPN parameters.

    Volume = 7  # the volume of the channel

    Balance = 8  # Balance Controls the left and right balance, generally for stereo patches.
    # 0 = hard left, 64 = center, 127 = hard right

    Pan = 10  # Pan Controls the left and right balance, generally for mono patches.
    # 0 = hard left, 64 = center, 127 = hard right

    Expression = 11  # Expression is a percentage of volume(CC7).

    EffectController1 = 12  # Effect Controller. Usually used to control a parameter of an effect within the
    # synth / workstation.
    EffectController2 = 13  # Usually used to control a parameter of an effect within the synth / workstation.

    GeneralPurpose1 = 16  # General purpose MIDI CC

    GeneralPurpose2 = 17  # General purpose MIDI CC

    GeneralPurpose3 = 18  # General purpose MIDI CC

    GeneralPurpose4 = 19  # General purpose MIDI CC

    Controller0LSb = 32  # Least Significant Bit (LSB)
    Controller1LSb = 33  # Least Significant Bit (LSB)
    Controller2LSb = 34  # Least Significant Bit (LSB)
    Controller3LSb = 35  # Least Significant Bit (LSB)
    Controller4LSb = 36  # Least Significant Bit (LSB)
    Controller5LSb = 37  # Least Significant Bit (LSB)
    Controller6LSb = 38  # Least Significant Bit (LSB)
    Controller7LSb = 39  # Least Significant Bit (LSB)
    Controller8LSb = 40  # Least Significant Bit (LSB)
    Controller9LSb = 41  # Least Significant Bit (LSB)
    Controller10LSb = 42  # Least Significant Bit (LSB)
    Controller11LSb = 43  # Least Significant Bit (LSB)
    Controller12LSb = 44  # Least Significant Bit (LSB)
    Controller13LSb = 45  # Least Significant Bit (LSB)
    Controller14LSb = 46  # Least Significant Bit (LSB)
    Controller15LSb = 47  # Least Significant Bit (LSB)
    Controller16LSb = 48  # Least Significant Bit (LSB)
    Controller17LSb = 49  # Least Significant Bit (LSB)
    Controller18LSb = 50  # Least Significant Bit (LSB)
    Controller19LSb = 51  # Least Significant Bit (LSB)
    Controller20LSb = 52  # Least Significant Bit (LSB)
    Controller21LSb = 53  # Least Significant Bit (LSB)
    Controller22LSb = 54  # Least Significant Bit (LSB)
    Controller23LSb = 55  # Least Significant Bit (LSB)
    Controller24LSb = 56  # Least Significant Bit (LSB)
    Controller25LSb = 57  # Least Significant Bit (LSB)
    Controller26LSb = 58  # Least Significant Bit (LSB)
    Controller27LSb = 59  # Least Significant Bit (LSB)
    Controller28LSb = 60  # Least Significant Bit (LSB)
    Controller29LSb = 61  # Least Significant Bit (LSB)
    Controller30LSb = 62  # Least Significant Bit (LSB)
    Controller31LSb = 63  # Least Significant Bit (LSB)

    Damper = 64  # Damper Pedal / Sustain Pedal On / Off switch that controls sustain.(See also Sostenuto CC 66)
    # 0 to 63 = Off, 64 to 127 = On

    Portamento = 65  # Portamento On / Off Switch On / Off switch 0 to 63 = Off, 64 to 127 = On

    Sostenuto = 66  # Sostenuto On / Off Switch On / Off switch – Like the Sustain controller(CC 64), However it
    # only holds notes that were “On” when the pedal was pressed.People use it to “hold” chords”
    # and play melodies over the held chord. 0 to 63 = Off, 64 to 127 = On

    SoftPedal = 67  # Soft Pedal On / Off Switch On / Off switch - Lowers the volume of notes played.
    # 0 to 63 = Off, 64 to 127 = On

    Legato = 68  # Legato FootSwitch On / Off switch - Turns Legato effect between 2 subsequent notes
    # On or Off. 0 to 63 = Off, 64 to 127 = On

    Hold = 69  # Hold 2 Another way to “hold notes” (see MIDI CC 64 and MIDI CC 66).However notes fade out
    # according to their release parameter rather than when the pedal is released.

    SoundController1 = 70  # Sound Controller 1 Usually controls the way a sound is produced.Default = Sound Variation.

    SoundController2 = 71  # Sound Controller 2 Allows shaping the Voltage Controlled Filter(VCF).
    # Default = Resonance - also(Timbre or Harmonics)
    SoundController3 = 72  # Sound Controller 3 Controls release time of the Voltage controlled Amplifier(VCA).
    # Default = Release Time.
    SoundController4 = 73  # Sound Controller 4 Controls the “Attack’ of a sound.The attack is the amount of time it
    # takes forthe sound to reach maximum amplitude.
    SoundController5 = 74  # Sound Controller 5 Controls VCFs cutoff frequency of the filter.

    SoundController6 = 75  # Sound Controller 6 Generic – Some manufacturers may use to further shave their sounds.

    SoundController7 = 76  # Sound Controller 7 - Generic – Some manufacturers may use to further shave their sounds.

    SoundController8 = 77  # Sound Controller 8 - Generic – Some manufacturers may use to further shave their sounds.

    SoundController9 = 78  # Sound Controller 9 -  Generic – Some manufacturers may use to further shave their sounds.

    SoundController10 = 79  # Sound Controller 10 - Generic – Some manufacturers may use to further shave their sounds.

    GeneralPurpose5 = 80  # General Purpose MIDI CC Controller Generic On / Off switch 0 to 63 = Off, 64 to     127 = On

    GeneralPurpose6 = 81  # General Purpose MIDI CC Controller Generic On / Off switch 0 to 63 = Off, 64 to 127 = On

    GeneralPurpose7 = 82  # General Purpose MIDI CC Controller Generic On / Off switch 0 to 63 = Off, 64 to 127 = On

    GeneralPurpose8 = 83  # General Purpose MIDI CC Controller Generic On / Off switch 0 to 63 = Off, 64 to 127 = On

    PortamentoCCControl = 84  # Controls the amount of Portamento.

    Effect1 = 91  # Effect 1 Depth Usually controls reverb send amount
    Effect2 = 92  # Effect 2 Depth Usually controls tremolo
    Effect3 = 93  # Effect 3 Depth Usually controls chorus amount
    Effect4 = 94  # Depth Usually controls detune amount MIDI CC 95
    Effect5 = 95  # Effect 5 Depth Usually controls phaser amount

    DataIncrement = 97  # Data Increment Usually tested to increment data for RPN and NRPN messages.
    DataDecrement = 96  # Usually used to decrement data for RPN and NRPN messages.

    NRNNoneRegParamLSB = 98  # Non - Registered Parameter Number LSB(NRPN) For controllers 6, 38, 96, and 97,
    # it selects the NRPN parameter.
    NRPNNonRegParamMSB = 99  # Non - Registered Parameter Number MSB(NRPN) For controllers 6, 38, 96, and 97,
    # it selects the NRPN parameter.

    ReParameterNumberLSBR = 100  # For controllers 6, 38, 96, and 97, it selects     the RPN parameter.

    ReParameterNumberMSB = 101  # Registered Parameter Number MSB(RPN) For controllers

    ChannelModeMessage1 = 120  # 120 to 127 are “Channel Mode Messages.”
    AllSoundOff = 120  # All Sound Off Mutes all sounding notes.It does so regardless of release
    # time or sustain.(See MIDI CC 123)
    ResetAllControllers = 121  # reset all controllers. It will reset all controllers to their default.
    LocalOnOffSwitch = 122  # Local On / Off Switch Turns internal connection of a MIDI keyboard / workstation, etc.On
    # or if you use a computer, you will most likely want local control off to avoid notes
    #  being played twice.Once locally and twice whent the note is sent back from the computer
    # to your keyboard.
    AllNotes = 123  # Off Mutes all sounding notes.Release time will still be maintained, and notes held sustain
    # will not turn off until sustain pedal is depressed.
    OmniMode = 124  # 1Omni Mode Off Sets to “Omni Off” mode. MIDI CC
    OmonModeOnSets = 125  # 125 Omni Mode On Sets to “Omni On” mode
    MonoModeMode = 126  # Mono Mode Sets device mode to Monophonic.
    PolyMode = 127  # Poly Mode Sets device mode to Polyphonic.

    PitchWheel = 128  # Own addition... class Phrase:
    """
    class to hold a Phrase, i.e. a dictionary of (animated) phrase properties
    The key is one of
        PhraseProperty.NOTE,
        PhraseProperty.DUR,
        PhraseProperty.LAG,
        PhraseProperty.PLAYEDDUR,
        PhraseProperty.VOL,
        PhraseProperty.TEMPO,
        PhraseProperty.CtrlDur(CCNumber)
        PhraseProperty.CtrlVal(CCNumber)
    """

    def __init__(self, properties: dict = None):
        self.n2m = Note2Midi()
        if properties is None:
            properties = {}
        self.p = properties
        if PP.NOTE not in self.p:
            self.p[PP.NOTE] = Pconst(self.n2m.lookup(Defaults.note), 1)
        if PP.DUR not in self.p:
            self.p[PP.DUR] = Pconst(Defaults.dur)
        if PP.LAG not in self.p:
            self.p[PP.LAG] = Pconst(Defaults.lag)
        if PP.PLAYEDDUR not in self.p:
            self.p[PP.PLAYEDDUR] = Pconst(Defaults.playeddur)
        if PP.VOL not in self.p:
            self.p[PP.VOL] = Pconst(Defaults.vol)
        if PP.TEMPO not in self.p:
            self.p[PP.TEMPO] = Pconst(Defaults.tempo)

        self.note_time = 0
        self.ctrl_time = [0 for _ in range(NO_OF_CONTROLLERS)]

    def __iter__(self):
        """
        converts the phrase to an iterable containing events

        :return: total time taken by this phrase
        """
        self.note_time = 0
        self.ctrl_time = [0 for _ in range(NO_OF_CONTROLLERS)]

        # note events are basically governed by the DUR key
        for value in zip(self.p[PP.NOTE], self.p[PP.DUR], self.p[PP.PLAYEDDUR], self.p[PP.VOL], self.p[PP.LAG],
                         self.p[PP.TEMPO]):
            if isinstance(value[0], Pchord) or value[0] != REST:  # 128 denotes a rest
                yield {
                    PP.NOTE: value[0],
                    PP.DUR: value[1] * 4,  # convert from quarter note to beat
                    PP.PLAYEDDUR: value[2],
                    PP.VOL: int(value[3]),
                    PP.LAG: value[4],
                    PP.TEMPO: value[5]
                }
            self.note_time += value[1] * 4  # convert from quarter note to beat

        # control change events can have their own timeline (e.g. this allows for pitchbend or modwheel variations
        # while notes are playing)
        for cc in range(NO_OF_OFFICIAL_CONTROLLERS):
            if PP.ctrl_dur_key(cc) in self.p and PP.ctrl_val_key(cc) in self.p:
                for value in zip(self.p[PP.ctrl_dur_key(cc)], self.p[PP.ctrl_val_key(cc)]):
                    if value[0] is not None and value[1] is not None:
                        yield {
                            PP.ctrl_dur_key(cc): value[0] * 4,
                            PP.ctrl_val_key(cc): value[1]
                        }
                    self.ctrl_time[cc] += value[0] * 4
        for cc in [MCC.PitchWheel]:
            if PP.ctrl_dur_key(cc) in self.p and PP.ctrl_val_key(cc) in self.p:
                for value in zip(self.p[PP.ctrl_dur_key(cc)], self.p[PP.ctrl_val_key(cc)]):
                    if value[0] is not None and value[1] is not None:
                        yield {
                            PP.ctrl_dur_key(cc): value[0] * 4,
                            PP.ctrl_val_key(cc): int(value[1])
                        }
                    self.ctrl_time[cc] += value[0] * 4

    def generated_duration(self):
        """

        :return: total duration of this note phrase
        """
        # only works as expected after you iterated over the phrase already
        return self.note_time

    def generated_ctrl_duration(self, cc_value):
        """
        :param cc_value: midi control change id
        :return: total duration taken by control changes for controller cc_value
        """
        # only works as expected after you iterated over the phrase already
        return self.ctrl_time[cc_value] REST = 128 class Note2Midi:
    """
    class to convert note names to midi numbers

    a note name consists of
    [note name][modifier][octave]
    where
    note name: one of a, b, c, d, e, f, g
    modifier: one of #, b, x, bb
    octave: 0-10

    anything not recognized as a note becomes a rest
    """
    chromatic_scale = [['c', 'b#', 'dbb', 'd--'],  # one row contains all synonyms (i.e. synonym for our purpose)
                       ['c#', 'bx', 'db', 'd-'],
                       ['d', 'cx', 'ebb', 'e--'],
                       ['d#', 'eb', 'e-', 'fbb', 'f--'],
                       ['e', 'dx', 'fb', 'f-'],
                       ['f', 'e#', 'gbb', 'g--'],
                       ['f#', 'ex', 'gb', 'g-'],
                       ['g', 'fx', 'abb', 'a--'],
                       ['g#', 'ab', 'a-'],
                       ['a', 'gx', 'bbb', 'b--'],
                       ['a#', 'bb', 'b-', 'cbb', 'c--'],
                       ['b', 'ax', 'cb', 'c-']]

    corner_case_octave_lower = {"b#", "bx"}
    corner_case_octave_higher = {"cb", "c-", "cbb", "c--"}

    drummap = {
        ('acousticbassdrum', 'abd'): 35,
        ('bassdrum', 'bad'): 36,
        ('sidestick', 'sis'): 37,
        ('acousticsnare', 'acs'): 38,
        ('brushtap', 'brt'): 38,  # brush kit instead of drum kit
        ('handclap', 'hac'): 39,
        ('brushslap', 'brs'): 39,  # brush kit instead of drum kit
        ('electricsnare', 'els'): 40,
        ('brushswirl', 'brw'): 40,  # brush kit instead of drum kit
        ('lowfloortom', 'lft'): 41,
        ('closedhihat', 'chh'): 42,
        ('highfloortom', 'hft'): 43,
        ('pedalhihat', 'phh'): 44,
        ('lowtom', 'lot'): 45,
        ('openhihat', 'ohh'): 46,
        ('lowmidtom', 'lmt'): 47,
        ('highmidtom', 'hmt'): 48,
        ('crashsymbal1', 'cs1'): 49,
        ('hightom', 'hit'): 50,
        ('ridecymbal1', 'rc1'): 51,
        ('chinesecymbal', 'chc'): 52,
        ('ridebell', 'rib'): 53,
        ('tambourine', 'tam'): 54,
        ('splashcymbal', 'spc'): 55,
        ('cowbell', 'cob'): 56,
        ('crashsymbal2', 'cc2'): 57,
        ('vibraslap', 'vis'): 58,
        ('ridecymbal2', 'rc2'): 59,
        ('highbongo', 'hib'): 60,
        ('lowbongo', 'lob'): 61,
        ('mutehiconga', 'mhc'): 62,
        ('openhiconga', 'ohc'): 63,
        ('lowconga', 'loc'): 64,
        ('hightimbale', 'him'): 65,
        ('lowtimbale', 'lom'): 66,
        ('highagogo', 'hag'): 67,
        ('lowagogo', 'lag'): 68,
        ('cabasa', 'cab'): 69,
        ('maracas', 'mar'): 70,
        ('shortwhistle', 'swh'): 71,
        ('longwhistle', 'lwh'): 72,
        ('shortguiro', 'shg'): 73,
        ('longguiro', 'log'): 74,
        ('claves', 'cla'): 75,
        ('hiwoodblock', 'hwb'): 76,
        ('lowoodblock', 'lwb'): 77,
        ('mutecuica', 'muc'): 78,
        ('opencuica', 'opc'): 79,
        ('mutetriangle', 'mut'): 80,
        ('opentriangle', 'opt'): 81,
        ('shaker', 'shk'): 82
    }

    def __init__(self):
        # '#' denotes a sharp, 'b' denotes a flat, x denotes a double sharp, bb denotes a double flat
        self.note_to_midi = {}
        notenum = 0
        for octave in range(10):
            for note_synonyms in self.chromatic_scale:
                if notenum <= 127:
                    for note in note_synonyms:

                        o = octave - 1
                        if note in self.corner_case_octave_lower:
                            o = o - 1
                        elif note in self.corner_case_octave_higher:
                            o = o + 1
                        self.note_to_midi["{0}{1}".format(note, o)] = notenum
                    notenum += 1

        self.all_drum_notes = set([])
        for d in self.drummap:
            self.note_to_midi["{0}".format(d[0])] = self.drummap[d]
            self.note_to_midi["{0}".format(d[1])] = self.drummap[d]
            # check that we didn't use the same acronym twice...
            assert d[0] not in self.all_drum_notes
            assert d[1] not in self.all_drum_notes
            self.all_drum_notes.add(d[0])
            self.all_drum_notes.add(d[1])

    def get_drumnotes_for_grammar(self):
        """
        internal helper function
        :return: list of drumnotes for inclusion in the textX grammar
        """
        from functools import cmp_to_key
        def mycmp(s1, s2):
            if len(s1) < len(s2):
                return 1
            if len(s1) > len(s2):
                return -1
            if s1 < s2:
                return 1
            if s1 > s2:
                return -1
            return 0

        strng = "|".join(sorted(["'" + d.strip() + "'" for d in self.all_drum_notes], key=cmp_to_key(mycmp)))
        return strng

    def lookup(self, note):
        """

        :param note: lookup simple note or Pchord
        :return: midi number corresponding to the note name or the notes in the Pchord
        """
        try:
            if isinstance(note, Pchord):
                return Pchord([self.note_to_midi[n.lower()] for n in note.notes])
            else:
                return self.note_to_midi[note.lower()]
        except KeyError:
            return REST

    def convert(self, notelist):
        """

        :param notelist: list of music notes
        :return: iterator iterating over the converted list of notes
        """
        for n in notelist:
            yield self.lookup(n)

    def convert2(self, notelist):
        """

        :param notelist:  listof music notes and/or Pchords
        :return: list of midi numbers and Pchords of midi numbers
        """
        return [self.lookup(note) for note in notelist] class Pchord(Pattern):
    """
    special pattern that is never flattened; interpreted as a chord by the rest of the system
    """

    def __init__(self, notes=0):
        super().__init__()
        self.notes = notes

    def __str__(self):
        return "{0}({1})".format(self.__class__.__name__, self.notes)

    def __iter__(self):
        yield Pchord(self.notes)

    def __repr__(self):
        return self.__str__()

    def __eq__(self, other):
        return self.notes == other.notes
from midiutil import MIDIFile
from expremigen.io.constants import Defaults, NO_OF_CONTROLLERS, NO_OF_OFFICIAL_CONTROLLERS, NO_OF_TRACKS
from expremigen.io.constants import PhraseProperty as PP
from expremigen.io.midicontrolchanges import MidiControlChanges
from expremigen.io.phrase import Phrase
from expremigen.musicalmappings.constants import REST
from expremigen.musicalmappings.note2midi import Note2Midi
from expremigen.patterns.pchord import Pchord



class Pat2Midi:
    """
    class to convert Pattern to Midi
    """

    def __init__(self, num_tracks: int = 1, remove_duplicates: bool = True, deinterleave: bool = True,
                 file_format: int = 1):
        """

        :param num_tracks: number of tracks (default: 1)
        :param remove_duplicates: remove notes if they start at the same time on the same channel if they have
               the same pitch  (default: True)
        :param deinterleave: clean up two note-ons with no note-off in between (default: True)
        :param file_format: 1 or 2 (default: 1)
        """
        self.midiFile = MIDIFile(numTracks=num_tracks, removeDuplicates=remove_duplicates, deinterleave=deinterleave,
                                 adjust_origin=False, file_format=file_format)
        self.last_set_tempo = [Defaults.tempo for _ in range(16)]  # set every track to default tempo
        self.set_tempo(Defaults.tempo, 0)
        self.last_set_cc = [[None for _ in range(NO_OF_CONTROLLERS)] for _ in range(NO_OF_TRACKS)]
        self.note2midi = Note2Midi()

    def set_tempo(self, tempo=100, time=0):
        """

        :param tempo: bpm (default: 100)
        :param time: time at which the tempo change should be inserted in the midi stream (default: 0)
        """
        self.midiFile.addTempo(track=0, time=time, tempo=tempo)
        self.last_set_tempo[0] = tempo
        self.last_set_cc = [[None for _ in range(NO_OF_CONTROLLERS)] for _ in range(NO_OF_TRACKS)]

    def add_phrase(self, phrase: Phrase, track=0, channel=0, start_time=0):
        """

        :param phrase: a Phrase containing patterns and animations
        :param track: default: 0
        :param channel: default: 0
        :param start_time: time at which the phrase should be inserted default: 0
        :return: total duration of the inserted phrase
        """
        for event in phrase:
            # set tempo events only if they changed since last time
            # handle note events
            if PP.NOTE in event:
                if event[PP.TEMPO] != self.last_set_tempo[track]:
                    self.midiFile.addTempo(track, start_time + phrase.generated_duration(), event[PP.TEMPO])
                    self.last_set_tempo[track] = event[PP.TEMPO]
                # set notes always
                if isinstance(event[PP.NOTE], Pchord):
                    for n in event[PP.NOTE].notes:
                        try:
                            intnote = int(n)
                        except ValueError:
                            intnote = self.note2midi.lookup(n)
                            if intnote == REST:
                                continue

                        self.midiFile.addNote(
                            track=track,
                            channel=channel,
                            pitch=intnote,
                            time=start_time + phrase.generated_duration() + event[PP.LAG],
                            duration=event[PP.DUR] * event[PP.PLAYEDDUR],
                            volume=int(event[PP.VOL]),
                            annotation=None)

                else:

                    try:
                        intnote = int(event[PP.NOTE])
                    except ValueError:
                        intnote = self.note2midi.lookup(event[PP.NOTE])
                        if intnote == REST:
                            continue

                    self.midiFile.addNote(
                        track=track,
                        channel=channel,
                        pitch=intnote,
                        time=start_time + phrase.generated_duration() + event[PP.LAG],
                        duration=event[PP.DUR] * event[PP.PLAYEDDUR],
                        volume=int(event[PP.VOL]),
                        annotation=None)

                self.handle_control_changes(channel, event, phrase, start_time, track)

            # handle controller events (only if they changed since last time)
            else:
                self.handle_control_changes(channel, event, phrase, start_time, track)

        return phrase.generated_duration()

    def handle_control_changes(self, channel, event, phrase, start_time, track):
        """
        iterate over all control changes in the phrase and add them to the midi file
        :param channel: midi channel
        :param event: python dict containing phrase properties
        :param phrase:
        :param start_time: time offset
        :param track: midi track id
        :return:
        """
        for cc in range(NO_OF_OFFICIAL_CONTROLLERS):
            if PP.ctrl_dur_key(cc) in event:
                time = start_time + phrase.generated_ctrl_duration(cc)
                value = event[PP.ctrl_val_key(cc)]
                if value is not None:
                    self.midiFile.addControllerEvent(track=track,
                                                     channel=channel,
                                                     time=time,
                                                     controller_number=cc,
                                                     parameter=value)

        for cc in [MidiControlChanges.PitchWheel]: