demberto / pyflp Goto Github PK
View Code? Open in Web Editor NEWFL Studio project file parser
Home Page: https://pyflp.rtfd.io
License: GNU General Public License v3.0
FL Studio project file parser
Home Page: https://pyflp.rtfd.io
License: GNU General Public License v3.0
Currently, all the tables in the docstrings are in the simple reStructured text format. They don't render in VSCode. If I use GFM tables instead (like I did earlier), then Sphinx doesn't understand them.
Since, VSCode will have no way to render a rst table, I plan to convert all the tables into GFM again.
I have explored MyST parser's Python API a bit, but there seems to be no way to just pass in a string and get it back converted.
Sphinx autodoc won't ever probably support this usecase, neither napoleon.
Many model classes (classes which derive from ModelBase
), support dunder methods like __getitem__
, __iter__
, __index__
etc. to make the API more Pythonic / idiomatic and remove the need for certain properties and clumsily named functions which achieve the same thing.
To make this more apparent, I subclass my model classes with protocols from typing
or typing_extensions
whenever possible.
I have been wondering whether this really is necessary? It has no other benefits than to show support for a certain protocol at runtime and isinstance
checks with a ModelBase
class and a protocol work just fine without subclassing the protocol. ๐ค
React with ๐ if you think I should keep the way it is or ๐ if you don't
TIL, slicing needs to be separately implemented even when the __getitem__
method currently supports SupportsIndex
values.
In layman terms, this works:
obj[0], obj[1] # โ
but this doesn't:
obj[0:1] # โ
il = Path(os.environ["PROGRAMFILES"]) / "Image-Line"
File "/opt/homebrew/Cellar/[email protected]/3.9.13_1/Frameworks/Python.framework/Versions/3.9/lib/python3.9/os.py", line 679, in __getitem__
raise KeyError(key) from None
KeyError: 'PROGRAMFILES'```
TrackStruct
:Lines 102 to 103 in f71c62b
Track
:Lines 327 to 335 in f71c62b
Line 345 in f71c62b
In a test FLP (FL 20.8.4), I found these values:
Tracked.locked_height
Enabled | Disabled |
---|---|
56 | 4294967280 / -16 |
These values indicate that locked_height
is somehow dependant on height
. Although in FL's interface it just a check box
Track.height
Min | Max | Default |
---|---|---|
0 | 1097963930 | 1065353216 |
Lines 753 to 760 in e5cf566
Currently, model collections like Patterns
, ChannelRack
, Arrangements
, Mixer
, Insert
and so on implement __getitem__
to allow passing an index of the model.
Example:
project.arrangements[1] # Arrangement(name="...", ...)
This works, but has little value practically speaking. Nobody remembers the index of the pattern or much less of a channel.
Models have a name
property which can be used as a parameter for the __getitem__
method. Once that is done,
project.arrangements["arrangement_name"] # returns an Arrangement with that name
Currently docstrings contain bit.ly links pointing to images in docs/img folder. Sphinx docs frequently need to be cleaned and rebuilt and this step takes quite some time.
Lines 102 to 108 in f2bd7a0
These links can be cached into a mapping of shortened links to relative local links, which the transform_image_links
hook can directly parse.
Lines 311 to 322 in 816c78d
This code was run with the provided test FLP:
import pyflp
from pyflp.channel import ChannelID, ChannelType
project = pyflp.parse("./tests/assets/FL 20.8.4.flp")
for channel in project.channels:
print(f"{ChannelType(channel.events_asdict()[ChannelID.Type][0].value).name} - {channel.name}")
Output:
Native - BooBass
Sampler - Instrument track
Layer - Layer
Sampler - Sampler
Instrument - Colored
Automation - Automation Clip
Native - VST2
Instrument - Audio Clip
Native - MIDI Out
Native - Fruit Kick
Native - Plucked!
Instrument - 22in Kick
๐ Not quite what I expected.
Empty Audio Clip
instances are Instrument
(Colored, Audio Clip and 22in Kick).
3rd party plugin Sylenth1 (named VST2) is surprisingly of type Native
.
Channel
type detection (Sampler
, Instrument
or Native
) should not be done solely based upon ChannelID.Type
event
Lines 1011 to 1039 in 816c78d
Feels like I am fixing IL's bugs now ๐
construct
is a Python library especially made for parsing binary structs. Using it would make parsing structures of any kind immensely simpler, it would take away all the voodoo I do with metaclass of StructBase
.
While construct
itself is nothing new, I need to experiment with it to retrofit it inside the existing event system. Maybe I could use a construct.Struct
to replace all BytesIOEx
instances in DataEventBase
?
Lines 103 to 118 in 1571591
20 bytes worth of data is still undiscovered
Undiscovered properties:
Awesome project! really like it but got some questions is it possible to get in touch with you somehow?
Through discord or sum?
Thanks
Lines 439 to 463 in ceff918
Lines 87 to 88 in f71c62b
FLPEdit sources tell me that, track_index
a uint16
followed by a field called group
, another uint16
.
Possibly this can be implemented by making Parser
a singleton and as a bonus add a parse
method.
I am not 100% sure yet, if this is just a problem with how I am using PyFLP, but it seems like iterating over slots in an insert results in an infinite loop.
for slot in project.mixer[1]:
...
Iterating over inserts works fine on the other hand.
Related code memo:
https://github.com/demberto/PyFLP/blob/master/pyflp/mixer.py#L438-L457
This is a long term goal. It will require a lot of reverse engineering.
https://github.com/monadgroup/FLParser has some parsing logic for automations ๐ implemented it now
Its impossible and not the scope of this library to be able to parse all plugin data, these are a few which should be easier to implement.
Technically, all plugins with just knobs and sliders should be easy to implement, Most data is stored in 4 byte unsigned integers but 0-100% or whatever the extremes of the knobs/sliders are, they are pretty small values in terms of the available range.
Once you have implemented a parser for a plugin, please make sure that you add the relevant docstrings and docs. You can choose to add an image of the plugin in docs/img
folder of the repo, but don't add your own bit.ly links to docstrings, that will be done by me.
If you have any suggestions, please let me know or if you have PRs mention this issue.
Line 308 in 4a063da
StructBase
to encode and decode colour.Color
directly into StructEvent._props
Lines 521 to 530 in 4a063da
Lines 544 to 557 in 4a063da
ColorProp
to pyflp._base
, specifically for colours encoded in variable sized eventsThis is the better option imo, because it will not modify StructEvent._props
, which will be used to store event values without any type of interpretation (mainly for debugging purposes). Using the first solution also breaks one of the highlights of PyFLP 2.0.0 - zero pre-parse validation.
Lines 301 to 310 in 2ff2d3c
This happens when testing isn't enough :(
Currently, I have kept it at 12.0
Lines 134 to 138 in 2ff2d3c
However, recent discoveries in plugin presets have indicated that it can be as low as 11.5
Images and tables are a very integral part of PyFLP. Most properties use markdown image links. These perfectly work in VSCode but PyCharm, for example doesn't. Similarly for tables, VSCode supports the GFM syntax, but PyCharm supports rST tables.
I do dev in VSCode and plan to keep docstrings in markdown. If there's no IDE agnostic solution. If PyCharm has a extension which supports markdown in docstrings please let me know of it, so I can add that info to the docs.
EDIT: Looks like this might never be possible. So, PyFLP users - GO VSCODE!
Algorithm: Note index // 12 for octave and %% 12 for the key names.
Lines 148 to 150 in 2ff2d3c
And obviously the docstring needs to be fixed โ
the minimum and maximum values assigned to the pattern pan can be quickly exceeded by certain projects, which returns a value error.
1.1.1
import pyflp
import sys, os
os.chdir(sys.path[0])
parse = pyflp.Parser()
l = parse.parse("random.flp")
Warnings like these, come from magic I do in docs/conf.py
:
Inline interpreted text or phrase reference start-string without end-string.
Pattern timemarkers get added to the first arrangement's events.
Last arrangement isn't yielded by Arrangements.__iter__
at all.
Lines 444 to 463 in ceff918
Currently, all testing is limited to just the property getters and a few validated property setters. A fair amount of code is untested - ignore coverage scores it gets confused by descriptors
FL uses platform specific path separator, which is \
on Windows and /
on Mac.
Lines 472 to 484 in 227f862
IterProp
is for properties having multiple eventsLine 924 in f2bd7a0
Lines 952 to 953 in f2bd7a0
Lines 965 to 966 in f2bd7a0
Each of these have a separate backing event. For instance, every Envelope
or LFO
has a separate ChannelID.EnvelopeLFO
event.
Line 186 in f2bd7a0
Line 211 in f2bd7a0
Each of these are a collection of items obtained from a single event. Their associated Struct
s derive from ListEventBase
Many fields containing time and position information (as well as start / end offsets) have been discovered already. They are exposed to as-is, no conversions from the uint32
or whatever to a human readable representation consisting of beats, bars and divisions.
Time / position fields are most of the time dependant on the PPQ of the project (and not the tempo!),
For example, at a PPQ of 96, a
length
of 96 means that the entity's musical length is a quarter or, a beat or, 1/4th of a bar.
Using the PPQ as a runtime dependency for calculation is already possible, but the part where problems begin are time signatures.
The above calculations work only for a time signature of 4/4. If while measuring the length, a time signature occurs in the playlist or in a pattern, the formula probably changes and not accounting for this would mean all further calculations will go wrong.
The FLP format is very close to MIDI in terms of many things. I think, the MIDI format already has something of this kind and I would like to get insight from somebody who has experience in MIDI or how musical timings are represented in DAWs.
Achieving this would be a great feat ๐ฅณ, its one of those features that would make PyFLP 10x more useful that its right now.
Lines 133 to 134 in ceff918
The property name itself is ambiguous, it holds stamped scales and chords as a whole, not as 3 separate notes. This makes FL remember the grouping.
This issue might be related to #48. But unlike IterProp
properties, Track
instances are passed the playlist items they contain from their parent Arrangement
.
When I try to run the provided example,
I get the following error.
UnicodeDecodeError: 'ascii' codec can't decode byte 0xd1 in position 1: ordinal not in range(128)
Therefore neither this package, nor FLPInspect or FLPInfo work.
Can you please have a look at this?
About 16% of the files I tested v2.0 with raised some sort of error, though loadable through normal FL Studio. Most of these failed because of "KeyError: None" on line 243 of plugin.py, seemingly due to VST plugins.
2.0.0a0
def __bytes__(self) -> bytes:
self._stream.seek(0)
for event in self._events:
try:
key = getattr(_VSTPluginEventID(event.id), "key")
except ValueError:
key = event.id
-------> event.value = self._props[key]
self._stream.write(bytes(event))
return super().__bytes__()
Had to upload as a ZIP since GitHub doesn't support FLP files as attachments by default.
Projects causing VST plugin parse errors.zip
Lines 728 to 736 in e5cf566
Lines 408 to 424 in f71c62b
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.