Skip to content

Arrangement

Arrangement

Bases: _FLObject

Represents an arrangement. FL 12.89 introduced support for multiple arrangements. Every arrangement has its own Track and TimeMarker objects as well as a Playlist.

Source code in pyflp/arrangement/arrangement.py
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
class Arrangement(_FLObject):
    """Represents an arrangement. FL 12.89 introduced support for multiple
    arrangements. Every arrangement has its own `Track` and `TimeMarker`
    objects as well as a `Playlist`."""

    _props = ("name", "index", "")

    def __init__(self, project: "Project") -> None:
        super().__init__(project, None)
        self._playlist: Optional[Playlist] = None
        self._timemarkers: List[TimeMarker] = []
        self._tracks: List[Track] = []

    @enum.unique
    class EventID(enum.IntEnum):
        """Events used by `Arrangement`."""

        Name = TEXT + 49
        """See `Arrangement.name`. Default event stores **Arrangement**."""

        New = WORD + 35
        """Marks the beginning of an arrangement. See `Arrangement.index`."""

    # * Properties
    name: Optional[str] = _StrProperty()

    index: Optional[int] = _UIntProperty()

    @property
    def tracks(self) -> List[Track]:
        """A list of `Track` objects of an arrangement contains."""
        return self._tracks

    @property
    def playlist(self) -> Optional[Playlist]:
        """The `Playlist` of an arrangement."""
        return self._playlist

    @property
    def timemarkers(self) -> List[TimeMarker]:
        """A list of `TimeMarker` objects an arrangement contains."""
        return getattr(self, "_timemarkers", [])

    # * Parsing logic
    def parse_event(self, e: EventType) -> None:
        if e.id_ in Playlist.EventID.__members__.values():
            if not self._playlist:
                self._playlist = Playlist(self._project)
            self._playlist.parse_event(e)
        elif e.id_ == Track.EventID.Name:
            self._cur_track.parse_event(e)
        # * TimeMarkers get parsed by Parser.
        else:
            super().parse_event(e)

    def _parse_word_event(self, e: WordEventType) -> None:
        if e.id_ == Arrangement.EventID.New:
            self._parse_H(e, "index")

    def _parse_text_event(self, e: TextEventType) -> None:
        if e.id_ == Arrangement.EventID.Name:
            self._parse_s(e, "name")

    def _parse_data_event(self, e: DataEventType) -> None:
        if e.id_ == Track.EventID.Data:
            self._cur_track = Track()
            self._cur_track.parse_event(e)
            self._tracks.append(self._cur_track)

    def _save(self) -> List[EventType]:
        events = super()._save()

        if self.playlist:
            events.extend(self.playlist._save())

        if self.timemarkers:
            for timemarker in self.timemarkers:
                events.extend(timemarker._save())

        if self.tracks:
            for track in self.tracks:
                events.extend(track._save())

        return events

name: Optional[str] = _StrProperty() class-attribute

index: Optional[int] = _UIntProperty() class-attribute

EventID

Bases: enum.IntEnum

Events used by Arrangement.

Source code in pyflp/arrangement/arrangement.py
44
45
46
47
48
49
50
51
52
@enum.unique
class EventID(enum.IntEnum):
    """Events used by `Arrangement`."""

    Name = TEXT + 49
    """See `Arrangement.name`. Default event stores **Arrangement**."""

    New = WORD + 35
    """Marks the beginning of an arrangement. See `Arrangement.index`."""

Name = TEXT + 49 class-attribute

See Arrangement.name. Default event stores Arrangement.

New = WORD + 35 class-attribute

Marks the beginning of an arrangement. See Arrangement.index.

playlist() property

The Playlist of an arrangement.

Source code in pyflp/arrangement/arrangement.py
64
65
66
67
@property
def playlist(self) -> Optional[Playlist]:
    """The `Playlist` of an arrangement."""
    return self._playlist

timemarkers() property

A list of TimeMarker objects an arrangement contains.

Source code in pyflp/arrangement/arrangement.py
69
70
71
72
@property
def timemarkers(self) -> List[TimeMarker]:
    """A list of `TimeMarker` objects an arrangement contains."""
    return getattr(self, "_timemarkers", [])

tracks() property

A list of Track objects of an arrangement contains.

Source code in pyflp/arrangement/arrangement.py
59
60
61
62
@property
def tracks(self) -> List[Track]:
    """A list of `Track` objects of an arrangement contains."""
    return self._tracks

Playlist

Bases: _FLObject

Source code in pyflp/arrangement/playlist.py
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
class Playlist(_FLObject):
    @enum.unique
    class EventID(enum.IntEnum):
        """Events used by `Playlist`."""

        # _LoopBar = WORD + 20
        # _LoopEndBar = WORD + 26
        # _Item = DWORD + 1

        Events = DATA + 25
        """See `Playlist.items`."""

    # * Properties
    @property
    def items(self) -> Dict[int, List[PlaylistItemType]]:
        return self._items

    # * Parsing logic
    def _parse_data_event(self, event: DataEventType):
        if event.id_ == Playlist.EventID.Events:
            self._events["event"] = event

            # Validation
            if not len(event.data) % 32 == 0:
                warnings.warn(
                    "Playlist event is not divisible into 32 byte chunks", UserWarning
                )
                return
            self._r = r = BytesIOEx(event.data)
            while True:
                position = r.read_I()  # 4
                if position is None:
                    break
                pattern_base = r.read_H()  # 6
                item_idx = r.read_H()  # 8
                length = r.read_I()  # 12
                track = r.read_i()  # 16
                if FLVersion(self._project.misc.version).major >= 20:
                    track = 499 - track
                else:
                    track = 198 - track
                r.seek(2, 1)  # 18
                item_flags = r.read_H()  # 20
                r.seek(4, 1)  # 24
                muted = True if (item_flags & 0x2000) > 0 else False

                # Init the list if not
                track_events = self.items.get(track)
                if not track_events:
                    track_events = []

                if item_idx <= pattern_base:
                    ppq = self._project.misc.ppq
                    start_offset = int(r.read_f() * ppq)  # 28
                    end_offset = int(r.read_f() * ppq)  # 32

                    # Cannot access tracks from here; handled by Parser
                    track_events.append(
                        ChannelPlaylistItem(
                            position,
                            length,
                            start_offset,
                            end_offset,
                            muted,
                            channel=item_idx,
                        )
                    )
                else:
                    start_offset = r.read_i()  # 28
                    end_offset = r.read_i()  # 32

                    track_events.append(
                        PatternPlaylistItem(
                            position,
                            length,
                            start_offset,
                            end_offset,
                            muted,
                            pattern=item_idx - pattern_base - 1,
                        )
                    )

    def __init__(self, project: "Project") -> None:
        super().__init__(project, None)
        self._items: Dict[int, List[PlaylistItemType]] = {}

EventID

Bases: enum.IntEnum

Events used by Playlist.

Source code in pyflp/arrangement/playlist.py
56
57
58
59
60
61
62
63
64
65
@enum.unique
class EventID(enum.IntEnum):
    """Events used by `Playlist`."""

    # _LoopBar = WORD + 20
    # _LoopEndBar = WORD + 26
    # _Item = DWORD + 1

    Events = DATA + 25
    """See `Playlist.items`."""

Events = DATA + 25 class-attribute

See Playlist.items.

items() property

Source code in pyflp/arrangement/playlist.py
68
69
70
@property
def items(self) -> Dict[int, List[PlaylistItemType]]:
    return self._items

TimeMarker

Bases: _FLObject

Represents a time marker or a time signature stamp.

Source code in pyflp/arrangement/timemarker.py
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
class TimeMarker(_FLObject):
    """Represents a time marker or a time signature stamp."""

    @enum.unique
    class EventID(enum.IntEnum):
        """Events used by `TimeMarker`"""

        Position = DWORD + 20
        """See `TimeMarker.position`."""

        Numerator = 33
        """See `TimeMarker.numerator`."""

        Denominator = 34
        """See `TimeMarker.denominator`."""

        Name = TEXT + 13
        """See `TimeMarker.name`."""

    @enum.unique
    class Kind(enum.IntEnum):
        """Used by `TimeMarker.kind`."""

        Marker = 0
        """Normal text marker."""

        Signature = 134217728
        """Specifies a change in the time signature."""

    def _setprop(self, n: str, v: Any) -> None:
        if n == "kind":
            if v == TimeMarker.Kind.Marker and self.kind == TimeMarker.Kind.Signature:
                self.position -= TimeMarker.Kind.Signature
            elif v == TimeMarker.Kind.Signature and self.kind == TimeMarker.Kind.Marker:
                self.position += TimeMarker.Kind.Signature
        super()._setprop(n, v)

    kind: Kind = _EnumProperty(Kind)
    """Type of timemarker. See `Kind`."""

    name: str = _StrProperty()
    """Name; e.g. `4/4` could be time signature, `Chorus` could be marker."""

    position: int = _UIntProperty()
    """Position in the playlist, from the start and proportional to PPQ."""

    numerator: int = _IntProperty(min_=1, max_=16)
    """Min: 1, Max: 16."""

    denominator: Literal[2, 4, 8, 16] = _UIntProperty(_OneOfValidator((2, 4, 8, 16)))

    # * Parsing logic
    def _parse_byte_event(self, e: ByteEventType) -> None:
        if e.id_ == TimeMarker.EventID.Numerator:
            self._parse_B(e, "numerator")
        elif e.id_ == TimeMarker.EventID.Denominator:
            self._parse_B(e, "denominator")

    def _parse_dword_event(self, e: DWordEventType) -> None:
        if e.id_ == TimeMarker.EventID.Position:
            pos = e.to_uint32()
            if pos >= TimeMarker.Kind.Signature:
                self._kind = TimeMarker.Kind.Signature
                pos -= TimeMarker.Kind.Signature
            else:
                self._kind = TimeMarker.Kind.Marker
            self._parseprop(e, "position", pos)

    def _parse_text_event(self, e: TextEventType) -> None:
        if e.id_ == TimeMarker.EventID.Name:
            self._parse_s(e, "name")

denominator: Literal[2, 4, 8, 16] = _UIntProperty(_OneOfValidator((2, 4, 8, 16))) class-attribute

name: str = _StrProperty() class-attribute

Name; e.g. 4/4 could be time signature, Chorus could be marker.

numerator: int = _IntProperty(min_=1, max_=16) class-attribute

Min: 1, Max: 16.

position: int = _UIntProperty() class-attribute

Position in the playlist, from the start and proportional to PPQ.

EventID

Bases: enum.IntEnum

Events used by TimeMarker

Source code in pyflp/arrangement/timemarker.py
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
@enum.unique
class EventID(enum.IntEnum):
    """Events used by `TimeMarker`"""

    Position = DWORD + 20
    """See `TimeMarker.position`."""

    Numerator = 33
    """See `TimeMarker.numerator`."""

    Denominator = 34
    """See `TimeMarker.denominator`."""

    Name = TEXT + 13
    """See `TimeMarker.name`."""

Denominator = 34 class-attribute

See TimeMarker.denominator.

Name = TEXT + 13 class-attribute

See TimeMarker.name.

Numerator = 33 class-attribute

See TimeMarker.numerator.

Position = DWORD + 20 class-attribute

See TimeMarker.position.

Track

Event information

Size: 66 (as of FL 20.8.3)

Structure:

Parameter Offset Type
number 0 I
color 4 i
icon 8 i
enabled 12 bool
height 13 f
locked_height 17 f
locked_to_content 21 bool
motion 22 I
press 26 I
trigger_sync 30 I
queued 34 I
tolerant 38 I
position_sync 42 I
grouped 46 bool
locked 47 bool

Bases: _FLObject

Source code in pyflp/arrangement/track.py
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
class Track(_FLObject):
    def _setprop(self, n: str, v: Any):
        if n != "name":
            self.__tde.dump(n, v)
        setattr(self, "_" + n, v)

    @enum.unique
    class EventID(enum.IntEnum):
        """Events used by `Track`."""

        Name = TEXT + 47
        """See `Track.name`. Default event does not exist."""

        Data = DATA + 30
        """See `TrackDataEvent`."""

    @enum.unique
    class Press(enum.IntEnum):
        """Used by `Track.press`."""

        Retrigger = 0
        HoldStop = 1
        HoldMotion = 2
        Latch = 3

    @enum.unique
    class Motion(enum.IntEnum):
        """Used by `Track.motion`."""

        Stay = 0
        OneShot = 1
        MarchWrap = 2
        MarchStay = 3
        MarchStop = 4
        Random = 5
        ExclusiveRandom = 6

    @enum.unique
    class Sync(enum.IntEnum):
        """Used by `Track.position_sync` and `Track.trigger_sync`."""

        Off = 0
        QuarterBeat = 1
        HalfBeat = 2
        Beat = 3
        TwoBeats = 4
        FourBeats = 5
        Auto = 6

    # * Properties
    name: Optional[str] = _StrProperty()

    number: Optional[int] = _IntProperty(min_=1, max_=500)
    """Min: 1, Max: 500.

    Earlier versions of FL 20 didn't dump unused tracks.
    Now, all 500 are dumped regardless of their use.
    """

    color: Optional[colour.Color] = _ColorProperty()

    icon: Optional[int] = _IntProperty()

    enabled: Optional[bool] = _BoolProperty()
    """Whether in enabled state or not."""

    height: Optional[float] = _FloatProperty(min_=0.0, max_=18.4)
    """Min: 0.0 (0%), Max: 18.4 (1840%), Default: 1.0 (100%)."""

    # TODO: What value this stores exactly is unclear yet.
    locked_height: Optional[int] = _IntProperty()

    locked_to_content: Optional[bool] = _BoolProperty()

    motion: Optional[Motion] = _EnumProperty(Motion)
    """Performance settings -> Motion. See `Motion`. Default: `Motion.Stay`."""

    press: Optional[Press] = _EnumProperty(Press)
    """Performance settings -> Press. See `Press`. Default: `Press.Retrigger`."""

    trigger_sync: Optional[Sync] = _EnumProperty(Sync)
    """Performance settings -> Trigger sync. See `Sync`. Default: `Sync.Off`."""

    queued: Optional[bool] = _BoolProperty()
    """Performance settings -> Queued. Default: False."""

    tolerant: Optional[bool] = _BoolProperty()
    """Performance settings -> Tolerant. Default: True."""

    position_sync: Optional[Sync] = _EnumProperty(Sync)
    """Performance settings -> Position sync. See `Sync`. Default: `Sync.FourBeats`."""

    grouped: Optional[bool] = _BoolProperty()
    """Whether grouped with above or not. Default: False."""

    locked: Optional[bool] = _BoolProperty()
    """Whether in locked state or not. Default: False."""

    @property
    def items(self) -> List[PlaylistItemType]:
        return self._items

    # * Parsing logic
    def _parse_text_event(self, event: _TextEvent):
        if event.id_ == Track.EventID.Name:
            self._parse_s(event, "name")

    def _parse_data_event(self, e: TrackDataEvent):
        self.__tde = self._events["data"] = e
        self._number = e.number
        self._color = e.color
        self._icon = e.icon
        self._enabled = e.enabled
        self._height = e.height
        self._locked_height = e.locked_height
        self._locked_to_content = e.locked_to_content
        self._motion = e.motion
        self._press = e.press
        self._trigger_sync = e.trigger_sync
        self._queued = e.queued
        self._tolerant = e.tolerant
        self._position_sync = e.position_sync
        self._grouped = e.grouped
        self._locked = e.locked

    def __init__(self, project=None, max_instances=None):
        super().__init__(project, max_instances)
        self._items: List[PlaylistItemType] = []

color: Optional[colour.Color] = _ColorProperty() class-attribute

enabled: Optional[bool] = _BoolProperty() class-attribute

Whether in enabled state or not.

grouped: Optional[bool] = _BoolProperty() class-attribute

Whether grouped with above or not. Default: False.

height: Optional[float] = _FloatProperty(min_=0.0, max_=18.4) class-attribute

Min: 0.0 (0%), Max: 18.4 (1840%), Default: 1.0 (100%).

icon: Optional[int] = _IntProperty() class-attribute

locked: Optional[bool] = _BoolProperty() class-attribute

Whether in locked state or not. Default: False.

locked_height: Optional[int] = _IntProperty() class-attribute

locked_to_content: Optional[bool] = _BoolProperty() class-attribute

motion: Optional[Motion] = _EnumProperty(Motion) class-attribute

Performance settings -> Motion. See Motion. Default: Motion.Stay.

name: Optional[str] = _StrProperty() class-attribute

number: Optional[int] = _IntProperty(min_=1, max_=500) class-attribute

Min: 1, Max: 500.

Earlier versions of FL 20 didn't dump unused tracks. Now, all 500 are dumped regardless of their use.

press: Optional[Press] = _EnumProperty(Press) class-attribute

Performance settings -> Press. See Press. Default: Press.Retrigger.

tolerant: Optional[bool] = _BoolProperty() class-attribute

Performance settings -> Tolerant. Default: True.

trigger_sync: Optional[Sync] = _EnumProperty(Sync) class-attribute

Performance settings -> Trigger sync. See Sync. Default: Sync.Off.

queued: Optional[bool] = _BoolProperty() class-attribute

Performance settings -> Queued. Default: False.

EventID

Bases: enum.IntEnum

Events used by Track.

Source code in pyflp/arrangement/track.py
121
122
123
124
125
126
127
128
129
@enum.unique
class EventID(enum.IntEnum):
    """Events used by `Track`."""

    Name = TEXT + 47
    """See `Track.name`. Default event does not exist."""

    Data = DATA + 30
    """See `TrackDataEvent`."""

Data = DATA + 30 class-attribute

See TrackDataEvent.

Name = TEXT + 47 class-attribute

See Track.name. Default event does not exist.

Press

Bases: enum.IntEnum

Used by Track.press.

Source code in pyflp/arrangement/track.py
131
132
133
134
135
136
137
138
@enum.unique
class Press(enum.IntEnum):
    """Used by `Track.press`."""

    Retrigger = 0
    HoldStop = 1
    HoldMotion = 2
    Latch = 3

HoldMotion = 2 class-attribute

HoldStop = 1 class-attribute

Latch = 3 class-attribute

Retrigger = 0 class-attribute

Motion

Bases: enum.IntEnum

Used by Track.motion.

Source code in pyflp/arrangement/track.py
140
141
142
143
144
145
146
147
148
149
150
@enum.unique
class Motion(enum.IntEnum):
    """Used by `Track.motion`."""

    Stay = 0
    OneShot = 1
    MarchWrap = 2
    MarchStay = 3
    MarchStop = 4
    Random = 5
    ExclusiveRandom = 6

ExclusiveRandom = 6 class-attribute

MarchStay = 3 class-attribute

MarchStop = 4 class-attribute

MarchWrap = 2 class-attribute

OneShot = 1 class-attribute

Random = 5 class-attribute

Stay = 0 class-attribute

Sync

Bases: enum.IntEnum

Used by Track.position_sync and Track.trigger_sync.

Source code in pyflp/arrangement/track.py
152
153
154
155
156
157
158
159
160
161
162
@enum.unique
class Sync(enum.IntEnum):
    """Used by `Track.position_sync` and `Track.trigger_sync`."""

    Off = 0
    QuarterBeat = 1
    HalfBeat = 2
    Beat = 3
    TwoBeats = 4
    FourBeats = 5
    Auto = 6

Auto = 6 class-attribute

Beat = 3 class-attribute

FourBeats = 5 class-attribute

HalfBeat = 2 class-attribute

Off = 0 class-attribute

QuarterBeat = 1 class-attribute

TwoBeats = 4 class-attribute

items() property

Source code in pyflp/arrangement/track.py
213
214
215
@property
def items(self) -> List[PlaylistItemType]:
    return self._items