|
|
PIANO ROLL
Scripting Reference
Piano roll scripts allow you to manipulate the note data in the Piano roll under the control of Python code. Custom scripts can be creating for creating, modifying, and deleting notes and markers from the FL Studio Piano Roll, with custom user interfaces for controlling the details of how the scripts behave. For example, a script could be written that selects all notes higher than C3 (MIDI note number 36).
Support forum
Visit the FL Studio Piano roll Scripting user forum to download Piano roll scripts, ask questions or discuss scripting and python in general.
Script File Locations and File Names
FL Studio will check the locations listed below and show any Scripts it finds in the Piano roll Scripts menu. If you make or edit scripts put them in the User data 'Piano roll scripts' sub-folder. The user interfaces you see associated with scripts are automatically created based on the variables and text entry fields in the script.
- Script file naming - Files must end with the .pyscript extension. E.g., Any text you like here.pyscript
- User editable scripts - When creating your own scripts or editing ours, put them in the User scripts folder:
- User scripts folder - ...Documents\Image-Line\FL Studio\Settings\Piano roll scripts. NOTE: You can use sub folders to create category folders in the Piano roll script menu.
- Default installation folders - We don't recommend editing these scripts or editing the contents of the folders. Rather copy any scripts you want to modify to your 'User scripts folder', add 'edited' to the name so you don't get confused. If you complain about bugs in factory scripts that you created because you didn't follow this advice, our support staff will chastise you many times!
- Browser Library Download - ...Documents\Image-Line\Downloads\Piano roll scripts. Scripts downloaded from the FL Studio Browser Library.
- Windows Installation - ... \FL Studio 2024\System\Config\Piano roll scripts. Default Scripts installed at installation.
- macOS Installation - ... /Applications/FL Studio 2024/Contents/Resources/FL/System/Config/Piano roll scripts. Default Scripts installed at installation.
NOTE: Check the factory script folder for the file 'Piano roll script reference.txt'. This may be more updated than this page in the manual.
API Reference
| Object |
Members |
Notes |
| # |
User comments |
Start comment lines with # |
| Note |
|
|
|
number |
Note number (MIDI standard, 48 = C4) |
|
time |
Note start time, in ticks |
|
length |
Note duration, in ticks |
|
group |
Group number this note belongs to (0 means it doesn't belong to a group) |
|
pan |
Note pan, 0.0 (left) to 1.0 (right), default 0.5 (middle) |
|
velocity |
Note velocity, 0.0 (min) to 1.0 (max), default 0.8 |
|
release |
Note release velocity, 0.0 to 1.0 |
|
color |
Note color (MIDI channel), 0 to 15 (corresponding to colors 1 through 16), default 0 |
|
fcut |
Note filter cutoff frequency / Mod X, 0.0 to 1.0, default 0.5 |
|
fres |
Note filter resonance (Q) / Mod Y, 0.0 to 1.0, default 0.5 |
|
pitchofs |
Pitch fine-tune offset, -120 to 120, in semitones/10 |
|
slide |
Note slide, True/False |
|
porta |
Note portamento, True/False |
|
muted |
Note mute, True/False |
|
selected |
Note selected, True/False |
|
clone() |
Creates a copy of a note object |
| Marker |
|
|
|
time |
Marker time, in ticks |
|
name |
Marker name |
|
mode |
Marker mode, integer |
|
tsnum |
When marker is a time signature, numerator |
|
tsden |
When marker is a time signature, denominator |
|
scale_root (scale markers only) |
Note class of the scale's tonic, with C as 0 (int) |
|
scale_helper (scale markers only) |
Comma-separated string, representing note classes from C through to B, with '0' for in scale and '1' for out of scale. E.g. C# Major (Ionian) would have scale_helper '1,0,1,0,1,0,0,1,0,1,0,0' |
| Score |
|
|
|
Use the global variable 'score' to access these functions: |
|
|
PPQ |
Ticks per quarter note / beat (read-only) |
|
tsnum |
Current project time signature numerator (read-only) |
|
tsden |
Current project time signature denominator (read-only) |
|
clear([all]) |
Remove notes and markers. Specify "True" to clear all, instead of just selected |
|
clearNotes([all]) |
Remove notes. Specify "True" to clear all, instead of just selected |
|
clearMarkers([all]) |
Remove markers. Specify "True" to clear all, instead of just selected |
|
noteCount |
Total number of notes (read-only) |
|
addNote(note) |
Add a note to the Piano Roll |
|
getNote(index) |
Get a note from the Piano Roll |
|
deleteNote(index) |
Delete the indexed note |
|
markerCount |
Total number of markers (read-only) |
|
addMarker(marker) |
Add a marker to the Piano Roll |
|
getMarker(index) |
Get a marker from the Piano Roll |
|
deleteMarker(index) |
Delete the indexed marker |
| ScriptDialog |
|
|
|
ScriptDialog(Title, Description) |
Initializes a new script dialog (user interface) |
|
addInput(aName, Value) |
Adds a generic input control (continuous control from 0 to 1) |
|
addInputKnob(aName, Value, Min, Max) |
Adds a knob input control with floating point value |
|
addInputKnobInt(aName, Value, Min, Max) |
Adds a knob input control with integer value |
|
addInputCombo(aName, ValueList, ValueIndex) |
Adds a combobox input control |
|
addInputText(aName, Value) |
Adds a text input control |
|
addInputCheckbox(aName, Value) |
Adds a checkbox input control with boolean value |
|
getInputValue(aName) |
Retrieve the current value of the input with the specified name |
|
Execute |
Returns TRUE if the user pressed OK, FALSE if the dialog was cancelled |
| TUtils |
|
|
|
ProgressMsg(Msg, Pos, Total) |
Shows a progress message |
|
ShowMessage(Msg) |
Shows a message in a dialog box |
|
log(Msg) |
Writes a message to the FL Studio debug log tab |
| Utility functions |
|
|
|
Use global variable "Utils" to access these functions: |
|
|
Utils.ShowMessage('Hello world') |
Displays a message as entered; e.g. "Hello world" |
Tutorial Scripts
The following simple scripts include many comments describing the function of the various parts of the code, and can be used as a general guide for how Piano Roll scripts should be constructed. These scripts are also inlcuded among the factory Piano Roll scripts.
Tutorial 1 - Add A Note
"""
1. Write A Note - Writes a single note in the Piano Roll. This is done by creating a
new note object, setting it's various parameters (like pitch, velocity, etc.), and
then adding the note to the Piano Roll.
"""
import flpianoroll as flp # Piano Roll Scripting API, for FL <-> Python communication
def createDialog() -> flp.ScriptDialog:
"""
The createDialog() call is for creating the UI for controlling the script. A description
of the script can be given and all of the controls are defined here. A "form" object is
used to keep track of the control values. In this example, we will create a control for
each of the parameters of the note that'll be created.
"""
# The ScriptDialog() call creates the menu, and takes as arguments strings for the title
# of the script and a description of it that will be displayed. A form object is returned,
# which is used to keep track of the controls.
form = flp.ScriptDialog('Add A Note', 'Add a single note to the Piano Roll.')
# Now we will create a control for each of the note parameters. We give each of the controls
# a name, which will be referenced when you use the control values in the apply() function.
form.addInputKnobInt('Note Number', 36, 0, 131) # Note number is pitch in semitones
form.addInputKnobInt('Note Time', 0, 0, 4 * flp.score.PPQ) # Start time in ticks
form.addInputKnobInt('Note Length', flp.score.PPQ, 0, 4 * flp.score.PPQ) # Note duration in ticks
form.addInputKnobInt('Note Group', 0, 0, 8) # 0 corresponds to no group
form.addInputKnob('Note Pan', 0.5, 0, 1) # Pan values are between 0 (lfet) and 1 (right)
form.addInputKnob('Note Velocity', 0.8, 0, 1) # Velocities are between 0 (min) and 1 (max)
form.addInputKnob('Note Release', 0.5, 0, 1) # Note release between 0 and 1
form.addInputKnobInt('Note Color', 0, 0, 15) # Colors range from 0 (color 1) to 15 (color 16)
form.addInputKnob('Note fcut', 0.5, 0, 1) # Note fcut between 0 and 1
form.addInputKnob('Note fres', 0.5, 0, 1) # Note fres between 0 and 1
form.addInputKnobInt('Pitch Offset', 0, -120, 120) # Pitch offset in semitones/10
form.addInputCheckbox('Note Slide', False)
form.addInputCheckbox('Note Portamento', False)
form.addInputCheckbox('Note Muted', False)
form.addInputCheckbox('Note Selected', True)
return form
def apply(form: flp.ScriptDialog) -> None:
"""
The apply(form) function contains the main task of the script
"""
note = flp.Note() # Create a new note object.
# We now set all of the note parameters using the values of the controls.
note.number = form.getInputValue('Note Number')
# The Note Number specifies the pitch of the note, given in semitones. A note number of 60
# corresponds to C5 (which is like the MIDI standard, but an octave higher).
note.time = form.getInputValue('Note Time')
# The Note Time specifies the start time of the note, in ticks. Ticks per quarter note
# (PPQ) is a project-wide setting, and can be accessed in the script using score.PPQ.
# Generally times will be specified as a function of score.PPQ in scripts, so that the
# outcome in musical time is independing of the PPQ setting.
note.length = form.getInputValue('Note Length')
# Note Length specifies the duration of the note, in ticks. As with note time, note length
# is also generally specified as a function of score.PPQ.
note.group = form.getInputValue('Note Group')
# Note Group controls which group the note is associated with, and is an integer. If 0,
# then the note is ungrouped (i.e. does not belong to a group).
note.pan = form.getInputValue('Note Pan')
# Note Pan specifies the panning of the note in the stereo field, and is a value between
# 0 (hard-panned left) and 1 (hard-panned right). The default value is 0.5 (centered).
note.velocity = form.getInputValue('Note Velocity')
# Note Velocity specifies the velocity of the note, which generally controls how loud it
# is. It is a value between 0 (most quiet) to 1 (loudest). Note that this is different
# than the MIDI standard: MIDI velocity of 127 (max) corresponds to FL velocity of 1.0.
note.release = form.getInputValue('Note Release')
# Note Release specifies the release velocity of the note, i.e. how quickly the note is
# let go. It is up to the synth plugin to interpret this value. Range from 0 to 1.
note.color = form.getInputValue('Note Color')
# Note Color determines the color of the note, which in FL Studio is roughly analogous to
# the MIDI channel. This allows one to route the note separately in Patcher, for instance.
# Note colors go from 0 to 15 (corresponding to colors 1 to 16 in the Piano Roll).
note.fcut = form.getInputValue('Note fcut')
# Note fcut is the filter cutoff frequency control, which is sometimes referred to as Mod X
# in FL Studio. While nominally controlling filter cutoff, it can be used to modulate a
# variety of plugin parameters in FL Studio.
note.fres = form.getInputValue('Note fres')
# Note fres is the filter resonance control, which is sometimes referred to as Mod Y in
# FL Studio. Similarly, it can be used to modulate various plugin parameters.
note.pitchofs = form.getInputValue('Pitch Offset')
# Pitch Offset (pitchofs) can be used to fine tune notes in the Piano Roll. The scale is a tenth of a
# semitone, and it's range is from -120 (an octave below) to 120 (an octave above)
note.slide = form.getInputValue('Note Slide')
# Note Slide toggles whether it is a slide note or not. Slide notes are like portamento,
# only the pitch transition happens more slowly.
note.porta = form.getInputValue('Note Portamento')
# Note Portamento (porta) toggles whether it is a portamento or not. Portamento is like a
# slide note, but the pitch transition happens more quickly.
note.muted = form.getInputValue('Note Muted')
# Note Mute (muted) toggles whether the note is muted or not in the Piano Roll. If a note is
# muted, it will not play.
note.selected = form.getInputValue('Note Selected')
# Note Selected toggles whether the note is selected or not. This can be useful in a script
# that selects all notes of a certain type (e.g. lowest notes in a chord).
# Add the note to the Piano Roll.
flp.score.addNote(note)
Tutorial 2 - Modify Existing Notes
"""
2. Modify Existing Notes - Takes the existing notes in the Piano Roll, transposes
their pitches (by a specifies number of semitones) and shifts them in time (by a
specified number of half beats). Also deletes notes that go out of range.
"""
import flpianoroll as flp # Piano Roll Scripting API, for FL <-> Python communication
def createDialog() -> flp.ScriptDialog:
# Create the user interface:
script_description = 'Transposes and time-shifts existing notes.'
form = flp.ScriptDialog('Modify Existing Notes', script_description)
# Create the user controls for the script:
form.addInputKnobInt('Note Transpose', 0, -36, 36, hint='Semitones')
form.addInputKnobInt('Time Shift', 0, -12, 12, hint='Half-beats')
return form
def apply(form: flp.ScriptDialog) -> None:
# We will keep track of indices for notes that are shifted out of
# range of the Piano Roll, so we can delete them at the end.
notes_to_delete = []
# Transpose all notes in pitch:
note_shift = form.getInputValue('Note Transpose')
for n in range(flp.score.noteCount):
note = flp.score.getNote(n)
note.number += note_shift
# If notes are beyond the range of the Piano Roll,
# add their indices to the delete list.
if not 0 <= note.number < 132:
notes_to_delete.append(n)
# Shift all notes in time:
time_shift = int(0.5 * flp.score.PPQ * form.getInputValue('Time Shift'))
for n in range(flp.score.noteCount):
# Don't bother to shift notes that are already
# marked for deletion.
if n in notes_to_delete:
continue
note = flp.score.getNote(n)
note.time += time_shift
# If notes are shifted entirely before time 0, add
# their indices to the delete list.
if note.time < 0 and (note.time + note.length) <= 0:
notes_to_delete.append(n)
# If only the beginning of the note if shifted before
# time zero, shorten the note so that it starts at 0.
elif note.time < 0:
note.length += note.time
note.time = 0
# Delete the notes that have been marked. Delete them in descending
# order of index, so that the indices of the remaining notes do not
# change as notes are deleted.
notes_to_delete.sort(reverse=True)
for n in notes_to_delete:
flp.score.deleteNote(n)
Tutorial 3 - Snap To Scale
"""
3. Snap to Scale - Modify the existing notes so that they are in a specified key/scale.
"""
import flpianoroll as flp # Piano Roll Scripting API, for FL <-> Python communication
import random # We need the random library for the option to snap in a random direction.
def createDialog() -> flp.ScriptDialog:
# Create the user interface:
script_description = 'Snap the existing notes to a specified key/scale.'
form = flp.ScriptDialog('Snap to Scale', script_description)
# Create the user controls for the script:
form.addInputCheckbox('Enable', 1, hint='Enable snapping to scale')
form.addInputCombo('Key', SnapToScale.tonic_names, 0, hint='Snap to scale: key / tonic')
form.addInputCombo('Snap Direction', SnapToScale.snap_names, 0, hint='Snapping behavior')
form.addInputCombo('Scale', SnapToScale.scale_names, 0, hint='Snap to scale: scale type')
return form
def apply(form: flp.ScriptDialog) -> None:
# Create the scale snapper based on the key/scale chosen by the user:
tonic_name = SnapToScale.tonic_names[form.getInputValue('Key')]
scale_name = SnapToScale.scale_names[form.getInputValue('Scale')]
snap_name = SnapToScale.snap_names[form.getInputValue('Snap Direction')]
snap_to_scale = SnapToScale(scale_name, tonic_name, snap_name)
# List of notes to delete for being snapped out of range:
notes_to_delete = []
# If 'Scap to Scale' is enabled,
if form.getInputValue('Enable'):
# go through the list of existing notes.
for n in range(flp.score.noteCount):
note = flp.score.getNote(n) # Grab each note
offset = snap_to_scale.note_offset(note.number) # Compute the offset in semitones
note.number += offset # Apply the offset to the note
# If the note ends up being out of the range of
# the Piano Roll, then mark that note for deletion.
if not 0 <= note.number < 132:
notes_to_delete.append(n)
# Delete the notes that have been marked, in reverse
# order so that the indices stay in the correct order.
for n in reversed(notes_to_delete):
flp.score.deleteNote(n)
class SnapToScale:
"""
Class to handle snapping notes to a musical scale.
"""
# Define the set of available scales (in-key notes relative to root).
scales = {
#'Chromatic': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
'Ionian (Major)': [1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1],
'Dorian': [1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0],
'Phrygian': [1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0],
'Lydian': [1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1],
'Mixolydian': [1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0],
'Aeolian (Minor)': [1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0],
'Locrian': [1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0],
'Major Pentatonic': [1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0],
'Minor Pentatonic': [1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0],
}
scale_names = list(scales.keys())
tonic_offsets = {
'C': 0,
'C# / Db': 1,
'D': 2,
'D# / Eb': 3,
'E': 4,
'F': 5,
'F# / Gb': 6,
'G': 7,
'G# / Ab': 8,
'A': 9,
'A# / Bb': 10,
'B': 11,
}
tonic_names = list(tonic_offsets.keys())
snap_names = ['Up', 'Down', 'Random']
def __init__(self, scale_name: str, tonic_name: str, snap_direction: str) -> None:
"""
args:
scale_name: name of the musical scale
tonic_name: name of the tonic / key
snap_direction: snapping behavior
"""
self.scale: list[int] = self.scales[scale_name]
self.tonic_offset: int = self.tonic_offsets[tonic_name]
self.snap_direction: str = snap_direction
def get_shift(self) -> int:
"""
Method to set shift value.
The shift value determines the direction to search (cyclically) up or down semitone
positions in the scale definition until an 'in-scale' position is found.
"""
if self.snap_direction == 'Random':
# Choose a random shift
return random.choice([-1, 1])
else:
# set shift to 1 for 'Up' and -1 for 'Down'
return self.snap_names.index(self.snap_direction) * -2 + 1
def note_offset(self, input_note: int) -> int:
"""
Compute note offset.
Search up or down the scale definition until an 'in-scale' position is found.
"""
offset = 0
start_position = (input_note - self.tonic_offset) % 12
in_scale = self.scale[start_position]
if in_scale:
# no need to shift, this note is in the scale already
return offset
# calculate the offset by looking for the next 'in-scale' position
shift = self.get_shift()
while not in_scale:
# move 1 position in the chosen direction
offset += shift
# check if the new position is in the scale
in_scale = self.scale[(start_position + offset) % 12]
return offset
Tutorial 4 - Random Melody
"""
4. Generate a Random Melody - Generates a random sequence of notes, with the option to specify
the length of the notes, the number of notes, and which key/scale to use.
"""
import flpianoroll as flp # Piano Roll Scripting API, for FL <-> Python communication
import random # We need the random library for choosing random notes.
# Define the available scales to use (in-scale notes relative to root).
SCALES = {
'None': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
'Ionian (Major)': [1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1],
'Dorian': [1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0],
'Phrygian': [1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0],
'Lydian': [1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1],
'Mixolydian': [1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0],
'Aeolian (Minor)': [1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0],
'Locrian': [1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0],
'Major Pentatonic': [1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0],
'Minor Pentatonic': [1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0],
}
SCALE_NAMES = list(SCALES.keys())
# Define the available tonic notes / roots / keys.
TONIC_OFFSETS = {
'C': 0,
'C# / Db': 1,
'D': 2,
'D# / Eb': 3,
'E': 4,
'F': 5,
'F# / Gb': 6,
'G': 7,
'G# / Ab': 8,
'A': 9,
'A# / Bb': 10,
'B': 11,
}
TONIC_NAMES = list(TONIC_OFFSETS.keys())
def createDialog() -> flp.ScriptDialog:
# Create the user interface:
script_description = 'Generates a random sequence of notes, with constraints.'
form = flp.ScriptDialog('Random Melody', script_description)
# Create the user controls for the script:
form.addInputKnobInt('Note Count', 1, 1, 16)
form.addInputKnob('Note Density', 1, 0, 1)
form.addInputKnobInt('Note Length', 1, 1, 8, hint='Quarter-beats')
form.addInputKnobInt('Octave Start', 5, 0, 8)
form.addInputCombo('Key', TONIC_NAMES, 0)
form.addInputCombo('Scale', SCALE_NAMES, 0)
form.addInputCheckbox('Clear Notes First?', 1, hint='Clear all PR notes first')
return form
def apply(form: flp.ScriptDialog) -> None:
# If the users chooses, all existing notes in the Piano Roll
# will be cleared before the random sequence is generated.
if form.getInputValue('Clear Notes First?'):
flp.score.clearNotes(True)
# Set key/scale based on user input.
tonic_name = TONIC_NAMES[form.getInputValue('Key')]
scale_name = SCALE_NAMES[form.getInputValue('Scale')]
note_count = form.getInputValue('Note Count')
note_length = form.getInputValue('Note Length') * (flp.score.PPQ // 4)
tonic_note = 12 * form.getInputValue('Octave Start') + TONIC_OFFSETS[tonic_name]
density = form.getInputValue('Note Density')
options = [i for i, v in enumerate(SCALES[scale_name]) if v]
# Loop over the number of notes in the sequence.
for n in range(note_count):
# The 'density' control specifies the probability that notes
# will play, so for each step in the sequence we only generate
# a note if a random number between (0,1) is less than 'density'.
if random.random() < density:
note = flp.Note() # Create the note
note_offset = random.choice(options) # Choose random offset based on scale
note.number = tonic_note + note_offset # Offset by tonic note
note.length = note_length # Set the note duration
note.time = n * note_length # Set the start time
note.velocity = 0.8 # Set the velocity to 0.8 (reasonable default)
flp.score.addNote(note) # Add the note to the Piano Roll
Tutorial 5 - Note Repeat
"""
5. Note Repeats - Creates copies of the existing notes in the Piano Roll, and adds them
to the Piano Roll with optional time and pitch offsets.
"""
import flpianoroll as flp # Piano Roll Scripting API, for FL <-> Python communication
def createDialog() -> flp.ScriptDialog:
# Create the user interface:
script_description = 'Creates copies of the existing notes, offset in time and pitch.'
form = flp.ScriptDialog('Note Repeats', script_description)
# Create the user controls for the script:
form.addInputKnobInt('Repeats', 1, 0, 10)
form.addInputKnobInt('Delay', 1, 1, 32, hint='Quarter-beats')
form.addInputKnob('Amplitude Scale', 1, 0, 1)
form.addInputKnobInt('Pitch Offset', 0, -12, 12)
return form
def apply(form: flp.ScriptDialog) -> None:
# Apply the user controls:
num_repeats = form.getInputValue('Repeats')
delay = form.getInputValue('Delay') * (flp.score.PPQ // 4)
amp_scale = form.getInputValue('Amplitude Scale')
pitch_offset = form.getInputValue('Pitch Offset')
# Loop over the number of existing notes.
for n in range(flp.score.noteCount):
# Loop over the specified number of repeats.
for r in range(1, num_repeats + 1):
note = flp.score.getNote(n).clone() # Make a copy of the note
note.time += r * delay # Offset the note copy in time
note.velocity *= amp_scale ** r # Scale the velocity of the note copy
note.number += r * pitch_offset # Offset the pitch of the note copy
# Add the note to the Piano Roll, but only
# if its in the allowable pitch range.
if 0 <= note.number < 132:
flp.score.addNote(note)
Tutorial 6 - Marker Copy
"""
6. Marker Copy - Creates copies of the existing time markers in the Piano Roll.
"""
import flpianoroll as flp # Piano Roll Scripting API, for FL <-> Python communication
# Check to see if there are any markers in the Piano Roll, and if there are:
if flp.score.markerCount > 0:
def createDialog() -> flp.ScriptDialog:
# Create the user interface:
script_description = 'Creates copies of the existing time markers.'
form = flp.ScriptDialog('Marker Copy', script_description)
# Create the user controls for the script:
form.addInputKnobInt('Number of Copies', 1, 0, 10)
form.addInputKnobInt('Time Offset', 1, 1, 16, hint='Beats')
form.addInputCheckbox('Add Number to Name', False)
return form
def apply(form: flp.ScriptDialog) -> None:
# Apply the user controls:
num_copies = form.getInputValue('Number of Copies')
tick_offset_mult = form.getInputValue('Time Offset') * flp.score.PPQ
add_number_to_name = form.getInputValue('Add Number to Name')
# Loop over the existing time markers.
for m in range(flp.score.markerCount):
this_marker = flp.score.getMarker(m) # Grab the marker
# Loop over the number of marker copies.
for c in range(1, num_copies + 1):
marker = flp.Marker() # Make a new marker
marker.name = this_marker.name # Give marker same name as original
if add_number_to_name:
marker.name = f"{marker.name} {c}" # Optionally add number to name
marker.time = this_marker.time + c * tick_offset_mult # Offset in time
flp.score.addMarker(marker) # Add marker copy to Piano Roll
# If there are no existing markers in the Piano Roll, display a message to the user. The
# ShowMessage() command creates a modal popup message, and thus should not be called from
# within the apply() call, since it would result in a popup every time a control is changed
# and thus the preview updates. Use log() for debugging within the apply() call.
else:
flp.Utils.ShowMessage('No Markers to Copy!')
|