Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added docs/img/plugin/generators/fruity-slicer.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
122 changes: 122 additions & 0 deletions pyflp/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"FruityFastDist",
"FruityNotebook2",
"FruitySend",
"FruitySlicer",
"FruitySoftClipper",
"FruityStereoEnhancer",
"Plucked",
Expand Down Expand Up @@ -155,6 +156,49 @@ class FruitySendEvent(StructEventBase):
).compile()


class FruitySlicerEvent(StructEventBase):
STRUCT = c.Struct(
"_u1" / c.Bytes(8),
"bpm" / c.Float32l,
"pitch_shift" / c.Int32sl,
"time_stretch" / c.Int32sl,
"stretching_method"
/ c.Enum(
c.Int32ul,
fill_gaps=0,
alt_fill_gaps=1,
pro_default=2,
pro_transient=3,
transient=4,
tonal=5,
monophonic=6,
speech=7,
),
"fade_in" / c.Int32ul,
"fade_out" / c.Int32ul,
"file_path" / c.PascalString(c.Int8ul, "utf-8"),
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be a VarInt prefixed string. If 2 bytes are used to encode a path having >127 characters then its definitely VarInt prefixed.

"slices"
/ c.PrefixedArray(
c.Int32ul,
c.Struct(
"name" / c.PascalString(c.Int8ul, "utf-8"),
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just checked these, the file path does seem to be a u8, but interestingly, the slice name field seems to be a bit unusual. The name is capped at 257 bytes, but it uses a different size format that seems to adapt to the actual length of the string. From what I can tell, the first byte is either the length of the string or 0xFF to indicate that the length is actually defined by the next four bytes (a u32). For example, hex 40 would indicate a string of 64 bytes, and the size field would only take up one byte. However, hex ff01010000 would indicate a string of 257 bytes, and the size field would take up five bytes, the first simply defining the next four as the size. I haven't checked, and it doesn't really matter for this purpose, but it might be possible that this could extend to a u64 as well using the same pattern. Perhaps an adapter (AdaptiveInteger?) could be implemented for this?

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is legit weird. Whatever could have stopped IL from using a VarInt instead? An adapter isn't enough here. We need a Construct subclass here.
Also are you sure about the file path? Windows allows path names upto 32767 characters with the 260 limitation removed. It might be that the file_path uses the same encoding as slice name

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The file path does seem to be limited to 255 characters, but it gets weirder -- attempting to load a sample with a longer path results in FL Studio itself crashing with the memory error free(): invalid next size (normal). I tested this in both FL 20 and 21 with the same result. It's probably safe to say that the path is a u8, but if you can't reproduce this, I'm not sure what's going on here.

Copy link
Copy Markdown
Owner

@demberto demberto Jul 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get no such error in FL Studio 20.8.3.2304 - either while loading, or saving the project, or even re-opening the project.
image
This is how the file path was serialised, in the form of old 8.3 format using the ~1 hack.

The full path is:

F:\ABCABCABCABCABCABCABCABCABCABCABCABCABCBACBABCBABCBABCBABCBABCBABCBABCBABCBABCBABCBABCBABCBABCBABCBABCBABCBBABCBBCBABCBC\ABCABCABCABCABCABCABCABCABCABCABCABCABCBACBABCBABCBABCBABCBABCBABCBABCBABCBABCBABCBABCBABCBABCBABCBABCBABCBBABCBBCBABCBC\ABCABCABCABCABCABCABCABCABCABCABCABCABCBACBABCBABCBABCBABCBABCBABCBABCBABCBABCBABCBABCBABCBABCBABCBABCBABCBBABCBBCBABCBC\DJAY...

There is a setting on Windows to remove the 260 limitation. Have you enabled it?
https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=registry

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do have enabled, but I believe the reason it was crashing may have been due to Wine (I run Linux). With a unique file path of 259 characters, it seemed to use the same format as the slice names (the data field was FF 03 01 00 00). I assume maybe it shortens paths that are 260 characters or longer, but keeps paths that are longer than 260 characters the same, where this obscure format is only used for paths between 255 and 259 characters.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright the length doesn't matter here, our construct will just need to check if the FF marker exists (and parse the next 4 bytes if it does).

"sample_offset" / c.Int32ul,
"key" / c.Int32sl,
"_u1" / c.Float32l,
"reversed" / c.Flag,
),
),
"animate" / c.Flag,
"starting_note" / c.Int32ul,
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shorter name start_note

"play_to_end" / c.Flag,
"bitrate" / c.Int32ul,
"auto_dump" / c.Flag,
"declick" / c.Flag,
"auto_fit" / c.Flag,
"view_spectrum" / FourByteBool,
).compile()


class FruitySoftClipperEvent(StructEventBase):
STRUCT = c.Struct("threshold" / c.Int32ul, "post" / c.Int32ul).compile()

Expand Down Expand Up @@ -1007,6 +1051,84 @@ class FruitySend(_PluginBase[FruitySendEvent], _IPlugin, ModelReprMixin):
"""


class FruitySlicer(_PluginBase[FruitySlicerEvent], _IPlugin, ModelReprMixin):
"""![](https://bit.ly/46mngih)"""

INTERNAL_NAME = "Fruity Slicer"
bpm = _NativePluginProp[float]()
"""The BPM (beats per minute) of the sample."""

pitch_shift = _NativePluginProp[int]()
"""Pitch shift, in cents. Linear, 1:1.
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Linear and 1:1 is the same thing. Remove 1:1.

I plan on creating a glossary section in my docs which explains all these terms.


| Type | Value | Representation |
|---------|-------|----------------|
| Min | -1200 | -1200 cents |
| Max | 1200 | +1200 cents |
| Default | 0 | +0 cent |
"""

time_stretch = _NativePluginProp[int]()
"""Logarithmic.

| Type | Value | Representation |
|---------|--------|--------------------------|
| Min | -20000 | 25% / 60 bpm to 240 bpm |
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use a shorter 60-240 BPM for all 3 of them

| Max | 20000 | 400% / 60 bpm to 15 bpm |
| Default | 0 | 100% / 0 bpm to 0 bpm |
"""

stretching_method = _NativePluginProp[
Literal[
"fill_gaps",
"alt_fill_gaps",
"pro_default",
"pro_transient",
"transient",
"tonal",
"monophonic",
"speech",
]
]()
"""The stretching method to use on the sample when `time_stretch` is not 0."""

fade_in = _NativePluginProp[int]()
"""Slice fade in, in milliseconds."""

fade_out = _NativePluginProp[int]()
"""Slice fade out, in milliseconds."""

file_path = _NativePluginProp[str]()
"""The file path of the sample."""

slices = _NativePluginProp[list[dict]]()
"""A list of slices."""

animate = _NativePluginProp[bool]()
"""Whether to highlight the slices as they are played."""

starting_note = _NativePluginProp[int]()
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shorter name start_note

"""The MIDI note for slicing to start on. Default 60."""

play_to_end = _NativePluginProp[bool]()
"""Whether to play slices to the end of the sample."""

bitrate = _NativePluginProp[int]()
"""The bitrate of the sample."""

auto_dump = _NativePluginProp[bool]()
"""Whether to automatically dump slices to the piano roll."""

declick = _NativePluginProp[bool]()
"""Whether to prevent clicking on slices."""

auto_fit = _NativePluginProp[bool]()
"""Whether to automatically fit the beat to the project tempo on load."""

view_spectrum = _NativePluginProp[bool]()
"""Whether to view the slices as a spectrum instead of a waveform."""


class FruitySoftClipper(_PluginBase[FruitySoftClipperEvent], _IPlugin, ModelReprMixin):
"""![](https://bit.ly/3BCWfJX)"""

Expand Down