def onTransport(self, control, event): result = fpt.handleTransport(control, event) if result: if control == controls.play: self.timeDisplay = True elif control == controls.stop: self.timeDisplay = False else: self.lcdText(result) elif control in (controls.forward, controls.rewind): direction = (1 if control is controls.forward else -1) if controls.shift.value and control.value: # Fixed steps. Hacky. Please fix the API. ppb = general.getRecPPB() move_ticks = direction * ppb song_length_ticks = transport.getSongLength( midi.SONGLENGTH_ABSTICKS) current_pos_ticks = transport.getSongPos() * song_length_ticks next_pos_ticks = current_pos_ticks + move_ticks next_pos_ticks = round(next_pos_ticks / ppb) * ppb # Hacky snap transport.setSongPos(next_pos_ticks / (song_length_ticks or 1)) else: transport.continuousMovePos(5 * direction, control.value * 2) transport.continuousMove(direction, control.value * 1) self.displayTime() event.handled = True
def _move_song_pos(delta, unit='bars'): """Move the song position by delta units. Params: delta: +/- value indicating amount to move the song position by. unit: one of 'bars', 'steps', 'ticks """ if unit == 'bars': delta_ticks = delta * _ticks_per_bar() elif unit == 'steps': delta_ticks = delta * _ticks_per_step() elif unit == 'ticks': delta_ticks = delta else: raise NotImplementedError( '%s is not a supported unit. Must be one of: bars, steps, ticks' % unit) current_ticks = transport.getSongPos(midi.SONGLENGTH_ABSTICKS) transport.setSongPos(current_ticks + delta_ticks, midi.SONGLENGTH_ABSTICKS) if PYKEYS_ENABLED: # Center on the current time marker Actions.fl_windows_shortcut("0", shift=1)
def save_load_song_pos(save_load): global saved_song_pos if save_load == 1: print("save_song_pos") saved_song_pos = transport.getSongPos(midi.SONGLENGTH_ABSTICKS) elif save_load == 0: print("load_song_pos") transport.setSongPos(saved_song_pos, midi.SONGLENGTH_ABSTICKS)
def Sync(self, flags): """ Syncs up all visual indicators on keyboard with changes from FL Studio. """ # Update buttons if flags & midi.HW_Dirty_LEDs: led_map = { ArturiaLights.ID_TRANSPORTS_RECORD: ArturiaLights.AsOnOffByte(transport.isRecording()), ArturiaLights.ID_TRANSPORTS_LOOP: ArturiaLights.AsOnOffByte(ui.isLoopRecEnabled()), ArturiaLights.ID_GLOBAL_METRO: ArturiaLights.AsOnOffByte(ui.isMetronomeEnabled()), ArturiaLights.ID_GLOBAL_SAVE: ArturiaLights.AsOnOffByte(transport.getLoopMode() == 1), ArturiaLights.ID_GLOBAL_UNDO: ArturiaLights.AsOnOffByte(general.getUndoHistoryLast() == 0), ArturiaLights.ID_TRACK_SOLO: ArturiaLights.AsOnOffByte( channels.isChannelSolo(channels.selectedChannel())), ArturiaLights.ID_TRACK_MUTE: ArturiaLights.AsOnOffByte( channels.isChannelMuted(channels.selectedChannel())), ArturiaLights.ID_TRANSPORTS_STOP: ArturiaLights.AsOnOffByte(not transport.isPlaying()), ArturiaLights.ID_TRANSPORTS_PLAY: ArturiaLights.AsOnOffByte(transport.getSongPos() > 0), ArturiaLights.ID_GLOBAL_OUT: ArturiaLights.AsOnOffByte( arrangement.selectionEnd() > arrangement.selectionStart()), ArturiaLights.ID_NAVIGATION_LEFT: ArturiaLights.AsOnOffByte(ui.getVisible(midi.widChannelRack)), ArturiaLights.ID_NAVIGATION_RIGHT: ArturiaLights.AsOnOffByte(ui.getVisible(midi.widMixer)), ArturiaLights.ID_OCTAVE_PLUS: ArturiaLights.LED_OFF, ArturiaLights.ID_OCTAVE_MINUS: ArturiaLights.LED_OFF, } self._lights.SetLights(led_map) self._encoders.Refresh() # Update display channel_name = channels.getChannelName(channels.selectedChannel()) pattern_number = patterns.patternNumber() pattern_name = patterns.getPatternName(pattern_number) update = ( flags & ( 1024 # HW_Dirty_Patterns | 2048 # HW_Dirty_Tracks | 16384 # HW_Dirty_Names | 32 # HW_Dirty_FocusedWindow (channel selection) )) > 0 self._paged_display.SetPageLines( 'main', line1='[%d:%d] %s' % (channels.selectedChannel() + 1, pattern_number, channel_name), line2='%s' % pattern_name, update=update)
def Sync(self): """ Syncs up all visual indicators on keyboard with changes from FL Studio. """ # Update buttons active_index = channels.selectedChannel() led_map = { ArturiaLights.ID_TRANSPORTS_RECORD: ArturiaLights.AsOnOffByte(transport.isRecording()), ArturiaLights.ID_TRANSPORTS_LOOP: ArturiaLights.AsOnOffByte(ui.isLoopRecEnabled()), ArturiaLights.ID_GLOBAL_METRO: ArturiaLights.AsOnOffByte(ui.isMetronomeEnabled()), ArturiaLights.ID_GLOBAL_SAVE: ArturiaLights.AsOnOffByte(transport.getLoopMode() == 1), ArturiaLights.ID_GLOBAL_UNDO: ArturiaLights.AsOnOffByte(general.getUndoHistoryLast() == 0), ArturiaLights.ID_TRACK_SOLO: ArturiaLights.AsOnOffByte(channels.isChannelSolo(active_index)), ArturiaLights.ID_TRACK_MUTE: ArturiaLights.AsOnOffByte(channels.isChannelMuted(active_index)), ArturiaLights.ID_TRANSPORTS_STOP: ArturiaLights.AsOnOffByte(not transport.isPlaying()), ArturiaLights.ID_TRANSPORTS_PLAY: ArturiaLights.AsOnOffByte(transport.getSongPos() > 0), ArturiaLights.ID_GLOBAL_OUT: ArturiaLights.AsOnOffByte( arrangement.selectionEnd() > arrangement.selectionStart()), } self._lights.SetLights(led_map) # Update selected channel bank_lights = [ArturiaLights.LED_OFF] * 9 if active_index < len(bank_lights): bank_lights[active_index] = ArturiaLights.LED_ON self._lights.SetBankLights(bank_lights) # Update display channel_name = channels.getChannelName(active_index) pattern_number = patterns.patternNumber() pattern_name = patterns.getPatternName(pattern_number) # Update knob mode if self._encoders.GetCurrentMode() == ArturiaInputControls.INPUT_MODE_CHANNEL_PLUGINS: plugin_name = plugins.getPluginName(active_index) if plugins.isValid(active_index) else '' self._encoders.SetKnobMode(plugin_name) self._encoders.SetSliderMode(plugin_name) if channel_name.startswith('Analog Lab'): channel_name='Analog Lab' self._paged_display.SetPageLines( 'main', line1='[%d:%d] %s' % (active_index + 1, pattern_number, channel_name), line2='%s' % pattern_name) self._encoders.Refresh()
def OnUpdateTimeMarker(self, delta, power=0): num_beats = patterns.getPatternLength(patterns.patternNumber()) step_size = 1.0 / float(num_beats) pos = transport.getSongPos() delta *= (2**power) transport.setSongPos(self.clip(0.0, 1.0, pos + step_size * delta))
def OnMidiIn(event): """ Wrapper for the OnMidiIn thread. """ # Play button if event.data1 == nihia.buttons.get("PLAY"): event.handled = True transport.start() # Restart button elif event.data1 == nihia.buttons.get("RESTART"): event.handled = True transport.setLoopMode() # Record button elif event.data1 == nihia.buttons.get("REC"): event.handled = True transport.record() # Count-In button elif event.data1 == nihia.buttons.get("COUNT_IN"): event.handled = True # Defines the standard behaviour (just to toggle "Countdown before recording" on/off) if COUNT_IN_BEHAVIOUR == 0: transport.globalTransport(midi.FPT_CountDown, 1) # Defines behaviour of the button if the user chooses the Maschine-alike behaviour if COUNT_IN_BEHAVIOUR == 1: # Toggles recording on if it isn't enabled already if transport.isRecording() == 0: transport.record() # Toggles countdown before recording on if it isn't enabled already if ui.isPrecountEnabled() == 0: transport.globalTransport(midi.FPT_CountDown, 1) # Stops playback if FL Studio is playing if transport.isPlaying() == True: transport.stop() # Then turns playback on again. This time record and countdown before recording will be activated transport.start() # Stop button elif event.data1 == nihia.buttons.get("STOP"): event.handled = True transport.stop() # Clear button # This one in other DAWs (in Maschine, specifically) this button is meant to clear the MIDI clip you're # on so you can record again on it without having to use a mouse to delete all of the notes on the clip before # recording again # # However, since the MIDI API on FL Studio doesn't allow control over the piano roll specifically, for now it will only just # emulate the delete button (which does the same) elif event.data1 == nihia.buttons.get("CLEAR"): event.handled = True ui.delete() # Loop button (toggles loop recording on/off) elif event.data1 == nihia.buttons.get("LOOP"): event.handled = True transport.globalTransport(midi.FPT_LoopRecord, 1) # Metronome button elif event.data1 == nihia.buttons.get("METRO"): event.handled = True transport.globalTransport(midi.FPT_Metronome, 1) # Tempo button elif event.data1 == nihia.buttons.get("TEMPO"): event.handled = True transport.globalTransport(midi.FPT_TapTempo, 1) # Undo button elif event.data1 == nihia.buttons.get("UNDO"): event.handled = True general.undoUp() # Redo button elif event.data1 == nihia.buttons.get("REDO"): event.handled = True general.undo() # Quantize button # TODO: Not imlpemented yet in FL Studio MIDI API # # Instead, it changes between FL windows # TODO: The code is correctly written, but the ui.showWindow() method has a bug that causes the Piano roll and Browser windows not to # appear when invoked. It has been said it should be fixed in a future update. # ----------------------------------------------------------------------------------------------------------------------------------- # if event.data1 == nihia.buttons.get("QUANTIZE"): # global window # window += 1 # if window <= 4: # ui.showWindow(window) # print("if reached") # elif window > 4: # window = 0 # ui.showWindow(window) # ----------------------------------------------------------------------------------------------------------------------------------- # # Alternative implementation: Emulate the Fn buttons elif event.data1 == nihia.buttons.get("QUANTIZE"): event.handled = True global window2 window2 += 1 # Normal behaviour if the action ID is between the desired range if window2 <= 68 and window2 != 67: transport.globalTransport(window2, 1) # Skips the 67 value which calls the full screen plugin picker and calls the mixer instead elif window2 == 67: window2 += 1 transport.globalTransport(window2, 1) # Once window value is out of range, it sets it again to the first value in range elif window2 > 68: window2 = 64 transport.globalTransport(window2, 1) # Automation button # Enables and disables the recording automation events # TODO: Not implemented yet in FL Studio MIDI API # # Instead, it shows the full-screen plugin browser elif event.data1 == nihia.buttons.get("AUTO"): event.handled = True transport.globalTransport(midi.FPT_F8, 1) # Mute button - A-Series elif event.data1 == nihia.buttons.get("MUTE_SELECTED"): event.handled = True mixer.muteTrack(mixer.trackNumber()) # Solo button - A-Series elif event.data1 == nihia.buttons.get("SOLO_SELECTED"): event.handled = True mixer.soloTrack(mixer.trackNumber()) # Mute button - S-Series elif event.data1 == nihia.buttons.get("MUTE"): event.handled = True mixerMuteSoloHandler("MUTE", event.data2, mixer.trackNumber()) # Solo button - S-Series elif event.data1 == nihia.buttons.get("SOLO"): event.handled = True mixerMuteSoloHandler("SOLO", event.data2, mixer.trackNumber()) # 4D Encoder + elif event.data1 == nihia.buttons.get( "ENCODER_GENERAL") and event.data2 == nihia.buttons.get("PLUS"): event.handled = True # Mixer navigation (right) if ui.getFocused(midi.widMixer) == True: ui.right() # Playback jogging elif (ui.getFocused(midi.widPianoRoll) == True) or (ui.getFocused( midi.widPlaylist) == True): transport.setSongPos( transport.getSongPos(midi.SONGLENGTH_S) + 1, midi.SONGLENGTH_S) # General navigation else: ui.down() # 4D Encoder - elif event.data1 == nihia.buttons.get( "ENCODER_GENERAL") and event.data2 == nihia.buttons.get("MINUS"): event.handled = True # Mixer navigation if ui.getFocused(midi.widMixer) == True: ui.left() elif (ui.getFocused(midi.widPianoRoll) == True) or (ui.getFocused( midi.widPlaylist) == True): transport.setSongPos( transport.getSongPos(midi.SONGLENGTH_S) - 1, midi.SONGLENGTH_S) # General navigation else: ui.up() # 4D Encoder + (selected track volume) elif event.data1 == nihia.buttons.get( "ENCODER_VOLUME_SELECTED") and event.data2 == nihia.buttons.get( "PLUS"): event.handled = True mixer.setTrackVolume(mixer.trackNumber(), mixer.getTrackVolume(mixer.trackNumber()) + 0.01) # 4D Encoder - (selected track volume) elif event.data1 == nihia.buttons.get( "ENCODER_VOLUME_SELECTED") and event.data2 == nihia.buttons.get( "MINUS"): event.handled = True mixer.setTrackVolume(mixer.trackNumber(), mixer.getTrackVolume(mixer.trackNumber()) - 0.01) # 4D Encoder + (selected track pan) elif event.data1 == nihia.buttons.get( "ENCODER_PAN_SELECTED") and event.data2 == nihia.buttons.get( "PLUS"): event.handled = True mixer.setTrackPan(mixer.trackNumber(), mixer.getTrackPan(mixer.trackNumber()) + 0.01) # 4D Encoder + (selected track pan) elif event.data1 == nihia.buttons.get( "ENCODER_PAN_SELECTED") and event.data2 == nihia.buttons.get( "MINUS"): event.handled = True mixer.setTrackPan(mixer.trackNumber(), mixer.getTrackPan(mixer.trackNumber()) - 0.01) # 4D Encoder up elif event.data1 == encoderHandler( "Y") and event.data2 == nihia.buttons.get("UP"): event.handled = True ui.up() # 4D Encoder down elif event.data1 == encoderHandler( "Y") and event.data2 == nihia.buttons.get("DOWN"): event.handled = True ui.down() # 4D Encoder (using FPT because ui.left doesn't work on the playlist) elif event.data1 == encoderHandler( "X") and event.data2 == nihia.buttons.get("LEFT"): event.handled = True if ui.getFocused(midi.widMixer) == True: # This one doesn't move the mixer view as you get to the border # ---------------------------------------------------- # mixer.setTrackNumber(mixer.trackNumber() - 1) # ---------------------------------------------------- ui.left() else: ui.left() # 4D Encoder (using FPT because ui.right doesn't work on the playlist) elif event.data1 == encoderHandler( "X") and event.data2 == nihia.buttons.get("RIGHT"): event.handled = True if ui.getFocused(midi.widMixer) == True: # This one doesn't move the mixer view as you get to the border # ---------------------------------------------------- # mixer.setTrackNumber(mixer.trackNumber() + 1) # ---------------------------------------------------- ui.right() else: ui.right() # 4D Encoder button elif event.data1 == nihia.buttons.get("ENCODER_BUTTON"): event.handled = True ui.enter() # 4D Encoder button (shifted) elif event.data1 == nihia.buttons.get("ENCODER_BUTTON_SHIFTED"): event.handled = True transport.globalTransport(midi.FPT_Menu, 1) # Knobs # Normal knobs - increase values elif event.data1 == nihia.knobs.get( "KNOB_1A") and event.data2 == nihia.knobs.get("INCREASE"): event.handled = True adjustMixer(0, "VOLUME", "INCREASE", mixer.trackNumber()) elif event.data1 == nihia.knobs.get( "KNOB_2A") and event.data2 == nihia.knobs.get("INCREASE"): event.handled = True adjustMixer(1, "VOLUME", "INCREASE", mixer.trackNumber()) elif event.data1 == nihia.knobs.get( "KNOB_3A") and event.data2 == nihia.knobs.get("INCREASE"): event.handled = True adjustMixer(2, "VOLUME", "INCREASE", mixer.trackNumber()) elif event.data1 == nihia.knobs.get( "KNOB_4A") and event.data2 == nihia.knobs.get("INCREASE"): event.handled = True adjustMixer(3, "VOLUME", "INCREASE", mixer.trackNumber()) elif event.data1 == nihia.knobs.get( "KNOB_5A") and event.data2 == nihia.knobs.get("INCREASE"): event.handled = True adjustMixer(4, "VOLUME", "INCREASE", mixer.trackNumber()) elif event.data1 == nihia.knobs.get( "KNOB_6A") and event.data2 == nihia.knobs.get("INCREASE"): event.handled = True adjustMixer(5, "VOLUME", "INCREASE", mixer.trackNumber()) elif event.data1 == nihia.knobs.get( "KNOB_7A") and event.data2 == nihia.knobs.get("INCREASE"): event.handled = True # Handles track group 15 exception if math.trunc(1 / 8 * mixer.trackNumber()) == 15: return else: adjustMixer(6, "VOLUME", "INCREASE", mixer.trackNumber()) elif event.data1 == nihia.knobs.get( "KNOB_8A") and event.data2 == nihia.knobs.get("INCREASE"): event.handled = True # Handles track group 15 exception if math.trunc(1 / 8 * mixer.trackNumber()) == 15: return else: adjustMixer(7, "VOLUME", "INCREASE", mixer.trackNumber()) # Normal knobs - decrease values elif event.data1 == nihia.knobs.get( "KNOB_1A") and event.data2 == nihia.knobs.get("DECREASE"): event.handled = True adjustMixer(0, "VOLUME", "DECREASE", mixer.trackNumber()) elif event.data1 == nihia.knobs.get( "KNOB_2A") and event.data2 == nihia.knobs.get("DECREASE"): event.handled = True adjustMixer(1, "VOLUME", "DECREASE", mixer.trackNumber()) elif event.data1 == nihia.knobs.get( "KNOB_3A") and event.data2 == nihia.knobs.get("DECREASE"): event.handled = True adjustMixer(2, "VOLUME", "DECREASE", mixer.trackNumber()) elif event.data1 == nihia.knobs.get( "KNOB_4A") and event.data2 == nihia.knobs.get("DECREASE"): event.handled = True adjustMixer(3, "VOLUME", "DECREASE", mixer.trackNumber()) elif event.data1 == nihia.knobs.get( "KNOB_5A") and event.data2 == nihia.knobs.get("DECREASE"): event.handled = True adjustMixer(4, "VOLUME", "DECREASE", mixer.trackNumber()) elif event.data1 == nihia.knobs.get( "KNOB_6A") and event.data2 == nihia.knobs.get("DECREASE"): event.handled = True adjustMixer(5, "VOLUME", "DECREASE", mixer.trackNumber()) elif event.data1 == nihia.knobs.get( "KNOB_7A") and event.data2 == nihia.knobs.get("DECREASE"): event.handled = True # Handles track group 15 exception if math.trunc(1 / 8 * mixer.trackNumber()) == 15: return else: adjustMixer(6, "VOLUME", "DECREASE", mixer.trackNumber()) elif event.data1 == nihia.knobs.get( "KNOB_8A") and event.data2 == nihia.knobs.get("DECREASE"): event.handled = True # Handles track group 15 exception if math.trunc(1 / 8 * mixer.trackNumber()) == 15: return else: adjustMixer(7, "VOLUME", "DECREASE", mixer.trackNumber()) # Shifted knobs - increase values elif event.data1 == nihia.knobs.get( "KNOB_1B") and event.data2 == nihia.knobs.get("INCREASE"): event.handled = True adjustMixer(0, "PAN", "INCREASE", mixer.trackNumber()) elif event.data1 == nihia.knobs.get( "KNOB_2B") and event.data2 == nihia.knobs.get("INCREASE"): event.handled = True adjustMixer(1, "PAN", "INCREASE", mixer.trackNumber()) elif event.data1 == nihia.knobs.get( "KNOB_3B") and event.data2 == nihia.knobs.get("INCREASE"): event.handled = True adjustMixer(2, "PAN", "INCREASE", mixer.trackNumber()) elif event.data1 == nihia.knobs.get( "KNOB_4B") and event.data2 == nihia.knobs.get("INCREASE"): event.handled = True adjustMixer(3, "PAN", "INCREASE", mixer.trackNumber()) elif event.data1 == nihia.knobs.get( "KNOB_5B") and event.data2 == nihia.knobs.get("INCREASE"): event.handled = True adjustMixer(4, "PAN", "INCREASE", mixer.trackNumber()) elif event.data1 == nihia.knobs.get( "KNOB_6B") and event.data2 == nihia.knobs.get("INCREASE"): event.handled = True adjustMixer(5, "PAN", "INCREASE", mixer.trackNumber()) elif event.data1 == nihia.knobs.get( "KNOB_7B") and event.data2 == nihia.knobs.get("INCREASE"): event.handled = True # Handles track group 15 exception if math.trunc(1 / 8 * mixer.trackNumber()) == 15: return else: adjustMixer(6, "PAN", "INCREASE", mixer.trackNumber()) elif event.data1 == nihia.knobs.get( "KNOB_8B") and event.data2 == nihia.knobs.get("INCREASE"): event.handled = True # Handles track group 15 exception if math.trunc(1 / 8 * mixer.trackNumber()) == 15: return else: adjustMixer(7, "PAN", "INCREASE", mixer.trackNumber()) # Shifted knobs - decrease values elif event.data1 == nihia.knobs.get( "KNOB_1B") and event.data2 == nihia.knobs.get("DECREASE"): event.handled = True adjustMixer(0, "PAN", "DECREASE", mixer.trackNumber()) elif event.data1 == nihia.knobs.get( "KNOB_2B") and event.data2 == nihia.knobs.get("DECREASE"): event.handled = True adjustMixer(1, "PAN", "DECREASE", mixer.trackNumber()) elif event.data1 == nihia.knobs.get( "KNOB_3B") and event.data2 == nihia.knobs.get("DECREASE"): event.handled = True adjustMixer(2, "PAN", "DECREASE", mixer.trackNumber()) elif event.data1 == nihia.knobs.get( "KNOB_4B") and event.data2 == nihia.knobs.get("DECREASE"): event.handled = True adjustMixer(3, "PAN", "DECREASE", mixer.trackNumber()) elif event.data1 == nihia.knobs.get( "KNOB_5B") and event.data2 == nihia.knobs.get("DECREASE"): event.handled = True adjustMixer(4, "PAN", "DECREASE", mixer.trackNumber()) elif event.data1 == nihia.knobs.get( "KNOB_6B") and event.data2 == nihia.knobs.get("DECREASE"): event.handled = True adjustMixer(5, "PAN", "DECREASE", mixer.trackNumber()) elif event.data1 == nihia.knobs.get( "KNOB_7B") and event.data2 == nihia.knobs.get("DECREASE"): event.handled = True # Handles track group 15 exception if math.trunc(1 / 8 * mixer.trackNumber()) == 15: return else: adjustMixer(6, "PAN", "DECREASE", mixer.trackNumber()) elif event.data1 == nihia.knobs.get( "KNOB_8B") and event.data2 == nihia.knobs.get("DECREASE"): event.handled = True # Handles track group 15 exception if math.trunc(1 / 8 * mixer.trackNumber()) == 15: return else: adjustMixer(7, "PAN", "DECREASE", mixer.trackNumber())