Full Code of demberto/PyFLP for AI

master f937126b888c cached
177 files
395.7 KB
109.7k tokens
600 symbols
1 requests
Download .txt
Showing preview only (427K chars total). Download the full file or copy to clipboard to get everything.
Repository: demberto/PyFLP
Branch: master
Commit: f937126b888c
Files: 177
Total size: 395.7 KB

Directory structure:
gitextract_89mlfebd/

├── .all-contributorsrc
├── .editorconfig
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.yml
│   │   ├── config.yml
│   │   └── feature_request.yml
│   ├── dependabot.yml
│   └── workflows/
│       ├── codeql-analysis.yml
│       ├── publish.yml
│       └── test.yml
├── .gitignore
├── .pre-commit-config.yaml
├── .readthedocs.yaml
├── .vscode/
│   ├── extensions.json
│   ├── settings.json
│   └── tasks.json
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── LICENSE
├── MANIFEST.in
├── README.md
├── docs/
│   ├── Makefile
│   ├── architecture/
│   │   ├── flp-format.rst
│   │   ├── how-it-works.rst
│   │   └── reference.rst
│   ├── architecture.rst
│   ├── changelog.rst
│   ├── conf.py
│   ├── contributing.rst
│   ├── faq.rst
│   ├── features.rst
│   ├── guides/
│   │   ├── plugin.rst
│   │   └── reversing.rst
│   ├── guides.rst
│   ├── handbook.rst
│   ├── helping.rst
│   ├── index.rst
│   ├── limitations.rst
│   ├── make.bat
│   ├── reference/
│   │   ├── arrangement/
│   │   │   ├── arrangement.rst
│   │   │   ├── index.rst
│   │   │   ├── playlist.rst
│   │   │   └── track.rst
│   │   ├── channel/
│   │   │   ├── automation.rst
│   │   │   ├── channel.rst
│   │   │   ├── display-group.rst
│   │   │   ├── index.rst
│   │   │   ├── instrument.rst
│   │   │   ├── layer.rst
│   │   │   ├── sampler.rst
│   │   │   └── shared.rst
│   │   ├── controllers.rst
│   │   ├── events.rst
│   │   ├── exceptions.rst
│   │   ├── mixer/
│   │   │   ├── index.rst
│   │   │   ├── insert.rst
│   │   │   └── slot.rst
│   │   ├── patterns/
│   │   │   ├── index.rst
│   │   │   └── pattern.rst
│   │   ├── plugins/
│   │   │   ├── effects.rst
│   │   │   ├── generators.rst
│   │   │   ├── index.rst
│   │   │   └── vst.rst
│   │   ├── project.rst
│   │   └── timemarkers.rst
│   ├── reference.rst
│   └── requirements.txt
├── pyflp/
│   ├── __init__.py
│   ├── _adapters.py
│   ├── _descriptors.py
│   ├── _events.py
│   ├── _models.py
│   ├── arrangement.py
│   ├── channel.py
│   ├── controller.py
│   ├── exceptions.py
│   ├── mixer.py
│   ├── pattern.py
│   ├── plugin.py
│   ├── project.py
│   ├── py.typed
│   ├── timemarker.py
│   └── types.py
├── pyproject.toml
├── requirements.txt
├── tests/
│   ├── __init__.py
│   ├── assets/
│   │   ├── FL 20.8.4.flp
│   │   ├── channels/
│   │   │   ├── +4800-cents.fst
│   │   │   ├── -4800-cents.fst
│   │   │   ├── 100%-left.fst
│   │   │   ├── 100%-right.fst
│   │   │   ├── arp.fst
│   │   │   ├── automation-lfo.fst
│   │   │   ├── automation-points.fst
│   │   │   ├── colored.fst
│   │   │   ├── cut-groups.fst
│   │   │   ├── delay.fst
│   │   │   ├── disabled.fst
│   │   │   ├── envelope.fst
│   │   │   ├── full-volume.fst
│   │   │   ├── iconified.fst
│   │   │   ├── keyboard.fst
│   │   │   ├── layer-crossfade.fst
│   │   │   ├── layer-random.fst
│   │   │   ├── level-adjusts.fst
│   │   │   ├── lfo.fst
│   │   │   ├── locked.fst
│   │   │   ├── polyphony.fst
│   │   │   ├── routed.fst
│   │   │   ├── sampler-content.fst
│   │   │   ├── sampler-filter.fst
│   │   │   ├── sampler-fx.fst
│   │   │   ├── sampler-path.fst
│   │   │   ├── sampler-playback.fst
│   │   │   ├── sampler-stretching.fst
│   │   │   ├── time.fst
│   │   │   ├── tracking.fst
│   │   │   └── zero-volume.fst
│   │   ├── corrupted/
│   │   │   ├── invalid-data-magic.flp
│   │   │   ├── invalid-event-size.flp
│   │   │   ├── invalid-format.flp
│   │   │   ├── invalid-header-magic.flp
│   │   │   ├── invalid-header-size.flp
│   │   │   └── invalid-ppq.flp
│   │   ├── inserts/
│   │   │   ├── 100%-left.fst
│   │   │   ├── 100%-merged.fst
│   │   │   ├── 100%-right.fst
│   │   │   ├── 100%-separated.fst
│   │   │   ├── 50ms-input-latency.fst
│   │   │   ├── 50ms-track-latency.fst
│   │   │   ├── armed.fst
│   │   │   ├── channels-swapped.fst
│   │   │   ├── colored.fst
│   │   │   ├── disabled.fst
│   │   │   ├── effects-bypassed.fst
│   │   │   ├── iconified.fst
│   │   │   ├── locked.fst
│   │   │   ├── polarity-reversed.fst
│   │   │   ├── post-eq.fst
│   │   │   ├── separator.fst
│   │   │   └── zero-volume.fst
│   │   ├── patterns/
│   │   │   ├── c-major-scale.fsc
│   │   │   ├── c5-1bar.fsc
│   │   │   ├── color-9.fsc
│   │   │   ├── common-group.fsc
│   │   │   ├── empty.fsc
│   │   │   ├── fine-pitch-min-max.fsc
│   │   │   ├── modx-min-max.fsc
│   │   │   ├── mody-min-max.fsc
│   │   │   ├── multi-channel.flp
│   │   │   ├── pan-min-max.fsc
│   │   │   ├── release-min-max.fsc
│   │   │   ├── slide-note.fsc
│   │   │   └── velocity-min-max.fsc
│   │   └── plugins/
│   │       ├── boobass.fst
│   │       ├── fruit-kick.fst
│   │       ├── fruity-balance.fst
│   │       ├── fruity-blood-overdrive.fst
│   │       ├── fruity-center.fst
│   │       ├── fruity-fast-dist.fst
│   │       ├── fruity-send.fst
│   │       ├── fruity-soft-clipper.fst
│   │       ├── fruity-stereo-enhancer.fst
│   │       ├── fruity-wrapper.fst
│   │       ├── plucked.fst
│   │       ├── soundgoodizer.fst
│   │       └── xfer-djmfilter.fst
│   ├── conftest.py
│   ├── test_arrangement.py
│   ├── test_channel.py
│   ├── test_corrupted.py
│   ├── test_events.py
│   ├── test_mixer.py
│   ├── test_models.py
│   ├── test_pattern.py
│   ├── test_plugin.py
│   └── test_project.py
└── tox.ini

================================================
FILE CONTENTS
================================================

================================================
FILE: .all-contributorsrc
================================================
{
  "files": [
    "README.md"
  ],
  "imageSize": 50,
  "commit": false,
  "contributors": [
    {
      "login": "nickberry17",
      "name": "nickberry17",
      "avatar_url": "https://avatars.githubusercontent.com/u/18670565?v=4",
      "profile": "https://github.com/nickberry17",
      "contributions": [
        "code"
      ]
    },
    {
      "login": "zacanger",
      "name": "zacanger",
      "avatar_url": "https://avatars.githubusercontent.com/u/12520493?v=4",
      "profile": "https://github.com/zacanger",
      "contributions": [
        "bug",
        "doc"
      ]
    },
    {
      "login": "ttaschke",
      "name": "Tim",
      "avatar_url": "https://avatars.githubusercontent.com/u/7067750?v=4",
      "profile": "https://github.com/ttaschke",
      "contributions": [
        "doc",
        "code",
        "maintenance"
      ]
    }
  ],
  "contributorsPerLine": 7,
  "projectName": "PyFLP",
  "projectOwner": "demberto",
  "repoType": "github",
  "repoHost": "https://github.com",
  "skipCi": true
}


================================================
FILE: .editorconfig
================================================
# EditorConfig is awesome: https://EditorConfig.org

# top-most EditorConfig file
root = true

[*]
charset = utf-8
insert_final_newline = true
indent_size = 2
indent_style = space
trim_trailing_whitespace = true

# 4 space indentation
[*.{cfg,py}]
indent_size = 4


================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.yml
================================================
name: Bug Report
description: File a bug report
title: "🐞 "
labels: ["bug"]
body:
  - type: textarea
    id: description
    attributes:
      label: Describe the issue
      description: A clear and a concise description of what happened.
    validations:
      required: true
  - type: input
    id: version
    attributes:
      label: What version of PyFLP are you using?
    validations:
      required: true
  - type: textarea
    id: code
    attributes:
      label: What code caused this issue?
      render: python3
    validations:
      required: true
  - type: textarea
    id: additional
    attributes:
      label: Screenshots, Additional info
  - type: checkboxes
    id: terms
    attributes:
      label: Code of Conduct
      description: By submitting this issue, you agree to follow PyFLP's [Code of Conduct](https://github.com/demberto/PyFLP/blob/master/CODE_OF_CONDUCT.md)
      options:
        - label: I agree to follow this project's Code of Conduct
          required: true


================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
contact_links:
  - name: Discussions
    url: https://github.com/demberto/PyFLP/discussions
    about: Please ask and answer questions here.


================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.yml
================================================
name: Feature request
description: ✨ I want a new feature
title: "✨ "
labels: ["enhancement"]
body:
  - type: textarea
    id: description
    attributes:
      label: Describe the feature
    validations:
      required: true
  - type: input
    id: version
    attributes:
      label: What version of PyFLP are you using?
    validations:
      required: true
  - type: textarea
    id: additional
    attributes:
      label: Screenshots, Additional info
  - type: checkboxes
    id: terms
    attributes:
      label: Code of Conduct
      description: By submitting this issue, you agree to follow PyFLP's [Code of Conduct](https://github.com/demberto/PyFLP/blob/master/CODE_OF_CONDUCT.md)
      options:
        - label: I agree to follow this project's Code of Conduct
          required: true


================================================
FILE: .github/dependabot.yml
================================================
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates

version: 2
updates:
  - package-ecosystem: pip
    directory: / # Location of package manifests
    schedule:
      interval: weekly
    assignees:
      - demberto
    ignore:
      - dependency-name: m2r2


================================================
FILE: .github/workflows/codeql-analysis.yml
================================================
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
name: "CodeQL"

on:
  push:
    branches: [ "master" ]
  pull_request:
    # The branches below must be a subset of the branches above
    branches: [ "master" ]
  schedule:
    - cron: '36 19 * * 2'

jobs:
  analyze:
    name: Analyze
    runs-on: ubuntu-latest
    permissions:
      actions: read
      contents: read
      security-events: write

    strategy:
      fail-fast: false
      matrix:
        language: [ 'python' ]
        # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
        # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support

    steps:
    - name: Checkout repository
      uses: actions/checkout@v3

    # Initializes the CodeQL tools for scanning.
    - name: Initialize CodeQL
      uses: github/codeql-action/init@v2
      with:
        languages: ${{ matrix.language }}
        # If you wish to specify custom queries, you can do so here or in a config file.
        # By default, queries listed here will override any specified in a config file.
        # Prefix the list here with "+" to use these queries and those in the config file.

        # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
        # queries: security-extended,security-and-quality


    # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).
    # If this step fails, then you should remove it and run the build manually (see below)
    - name: Autobuild
      uses: github/codeql-action/autobuild@v2

    # ℹ️ Command-line programs to run using the OS shell.
    # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun

    #   If the Autobuild fails above, remove it and uncomment the following three lines.
    #   modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.

    # - run: |
    #   echo "Run, Build Application using script"
    #   ./location_of_script_within_repo/buildscript.sh

    - name: Perform CodeQL Analysis
      uses: github/codeql-action/analyze@v2
      with:
        category: "/language:${{matrix.language}}"


================================================
FILE: .github/workflows/publish.yml
================================================
name: publish

on:
  push:
    tags:
      - v*
  workflow_dispatch:

jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Set up Python
        uses: actions/setup-python@v3
        with:
          python-version: "3.x"
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install build twine
      - name: Build package
        run: python -m build
      - name: Twine check
        run: twine check dist/*
      - name: Get changelog for release
        id: changelog
        uses: mindsers/changelog-reader-action@v2
      - name: Create release
        uses: ncipollo/release-action@v1
        with:
          artifacts: "dist/*"
          body: ${{ steps.changelog.outputs.changes }}
      - name: Publish package
        uses: pypa/gh-action-pypi-publish@release/v1
        with:
          user: __token__
          password: ${{ secrets.PYPI_API_TOKEN }}


================================================
FILE: .github/workflows/test.yml
================================================
name: test

on:
  push:
    branches:
      - master
  pull_request:
    branches:
      - master
  workflow_dispatch:

jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        python-version:
          ["3.8", "3.9", "3.10", "3.11", "pypy3.8", "pypy3.9"]
        os: ["macos-latest", "windows-latest", "ubuntu-latest"]
    steps:
      - uses: actions/checkout@v3
      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v4
        with:
          python-version: ${{ matrix.python-version }}
          cache: "pip"
      - name: Install dependencies
        run: |
          python -m pip install -U pip
          pip install tox tox-gh
      - name: Test with tox
        run: tox
      - name: Upload coverage artifacts
        uses: actions/upload-artifact@v3
        with:
          name: coverage-artifacts
          path: ./.coverage.*
  upload-to-codecov:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Set up Python 3.10
        uses: actions/setup-python@v4
        with:
          python-version: "3.10"
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install coverage[toml]
      - name: Download artifacts
        uses: actions/download-artifact@v3
        with:
          name: coverage-artifacts
      - name: Coverage data preparation for shitty codecov
        run: coverage combine
      - name: Upload to Codecov
        uses: codecov/codecov-action@v3


================================================
FILE: .gitignore
================================================
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# setuptools_scm
pyflp/_version.py

# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/
docs/_images/

# PyBuilder
.pybuilder/
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
#   For a library or package, you might want to ignore these files since the code is
#   intended to run in multiple environments; otherwise, check them in:
# .python-version

# pipenv
#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
#   However, in case of collaboration, if having platform-specific dependencies or dependencies
#   having no cross-platform support, pipenv may install dependencies that don't work, or not
#   install all needed dependencies.
#Pipfile.lock

# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

# pytype static type analyzer
.pytype/

# Cython debug symbols
cython_debug/

# Ruff
.ruff_cache/

# Just easier than running tests
main.py


================================================
FILE: .pre-commit-config.yaml
================================================
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.4.0
    hooks:
      - id: trailing-whitespace
      - id: end-of-file-fixer
      - id: check-toml
      - id: check-yaml
      - id: check-added-large-files
      - id: requirements-txt-fixer
      - id: check-vcs-permalinks
  - repo: https://github.com/psf/black
    rev: 23.3.0
    hooks:
      - id: black
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.0.277
    hooks:
      - id: ruff
  - repo: https://github.com/asottile/pyupgrade
    rev: v3.9.0
    hooks:
      - id: pyupgrade
        args: ["--py38-plus"]


================================================
FILE: .readthedocs.yaml
================================================
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details

# Required
version: 2

# Set the version of Python and other tools you might need
build:
  os: ubuntu-22.04
  tools:
    python: "3"

# Build documentation in this directory with Sphinx
sphinx:
  configuration: docs/conf.py

# Optionally declare the Python requirements required to build your docs
python:
  install:
    - path: . # Required by importlib.version for Sphinx (setuptools_scm)
    - requirements: docs/requirements.txt
    - requirements: requirements.txt


================================================
FILE: .vscode/extensions.json
================================================
{
  "recommendations": [
    "aaron-bond.better-comments", // Highlighting annotated comments
    "bierner.markdown-preview-github-styles", // Github-style markdown preview
    "charliermarsh.ruff", // Ruff, btw
    "DavidAnson.vscode-markdownlint", // Lint markdown files, just like code
    "EditorConfig.EditorConfig", // For .editorconfig
    "leonhard-s.python-sphinx-highlight", // Highlight RST elements in docstrings
    "ms-vscode.hexeditor", // Useful if you don't have a hex editor
    "ms-python.python", // Pyright, docstrings
    "njpwerner.autodocstring", // Create docstrings quickly
    "redhat.vscode-yaml", // For YAML files
    "swyddfa.esbonio",  // RST language server
    "tamasfe.even-better-toml", // For pyproject.toml
    "trond-snekvik.simple-rst", // Sphinx rST docs
  ]
}


================================================
FILE: .vscode/settings.json
================================================
// Suggested settings for contributors using VSCode.
{
  "git.enableCommitSigning": true, // nice "Verified" badges @ GH
  "python.analysis.autoImportCompletions": false, // almost always wrong
  "python.analysis.importFormat": "relative", // from .module import ...
  "python.analysis.inlayHints.functionReturnTypes": true, // time saver
  "python.analysis.inlayHints.variableTypes": false, // almost a PITA
  "python.analysis.typeCheckingMode": "strict", // pylance strict mode
  "python.formatting.provider": "black", // the best out there
  "python.languageServer": "Pylance", // obviously
  "python.linting.enabled": true,
  "python.linting.mypyEnabled": true, // ah ofc this shitty typechecker
  "python.terminal.activateEnvironment": false, // venvs don't deactivate properly
  "python.terminal.activateEnvInCurrentTerminal": true, // save my time instead
  "python.testing.pytestEnabled": true, // use pytest, and...
  "python.testing.unittestEnabled": false // not unittest
}


================================================
FILE: .vscode/tasks.json
================================================
{
  // See https://go.microsoft.com/fwlink/?LinkId=733558
  // for the documentation about the tasks.json format
  "version": "2.0.0",
  "tasks": [
    {
      "label": "coverage: all",
      "type": "shell",
      "command": "coverage run -m pytest && coverage combine && coverage report && coverage html && start htmlcov/index.html",
      "problemMatcher": []
    },
    {
      "label": "sphinx: clean run",
      "type": "shell",
      "command": "./docs/make.bat clean && ./docs/make.bat html && start ./docs/_build/index.html",
      "problemMatcher": []
    }
  ]
}


================================================
FILE: CHANGELOG.md
================================================
<!-- markdownlint-disable no-duplicate-heading -->
<!-- markdownlint-disable link-image-reference-definitions -->

# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [2.2.1] - 2023-06-05

### Fixed

- Python 3.8 compatibility.

## [2.2.0] - 2023-05-28

### Changed

- All event parsing happens during `pyflp.parse` itself.
- `colour.Color` replaced with `pyflp.types.RGBA`.
- Increase *line-length* of 100.

### Fixed

- Backtracking issues in nested dictionaries.

### Removed

- Python 3.7 support.
- Bunch of intermediate `EventBase` subclasses.
- Removed dependency on `colour` library.

## [2.1.1] - 2023-05-24

### Changed

- Refactored `VSTPluginEvent` sub-event handling into `_VSTPluginProp`.
- All `VSTPluginEvent` string sub-events decoded as UTF8.

### Fixed

- `VSTPlugin.name` encoded in UTF8 [#150].

[#150]: https://github.com/demberto/PyFLP/issues/150

## [2.1.0] - 2023-04-18

### Added

- Plugin data parsers: `FruitKick` and `Plucked`.
- `ArrangementsID.PLSelection` [#132].

### Changed

- Unbound descriptors return `self` - more `property`*esque* behaviour.
  This is primarily done to allow `flpinspect` to inspect descriptor types.
- Moved `Sampler.pitch_shift` upto its base class `_SamplerInstrument`.

### Deprecated

- `ArrangementID.LoopPos` [#132].

[#132]: https://github.com/demberto/PyFLP/issues/132

## [2.0.0] - 2023-03-18

Welcome PyFLP 2.0 🎉
Read the previous changelogs to get the complete list of changes.

### Added

- `FruityBloodOverdrive` - thanks to @@ttaschke [#120].

### Changed

- Docs are way more easier to navigate now.

### Fixed

- `VSTPluginEvent.__setitem__` and `_VSTPluginProp._set` [#113].

### Removed

- Support for PyPy 3.7 (unable to run tox, cannot find a download).

[#113]: https://github.com/demberto/PyFLP/issues/113
[#120]: https://github.com/demberto/PyFLP/pull/120

## [2.0.0a7] - 2022-12-19

### Added

- `Pattern` timemarkers [#27].
- Low-level API support for FL Studio 21's `PlaylistEvent` [#108].

### Changed

- Renamed `PlaylistEvent.track_index` to `PlaylistEvent.track_rvidx`.
- Optimized `Arrangement.tracks` iteration logic - 50% lesser time to run tests.
- `StructEventBase.value` raises `NotImplementedError`.
- Ambiguous `Pattern.__iter__` refactored into a property `Pattern.notes`.
- `Pattern.index` renamed to `Pattern.iid`.
- Improved `__repr__` strings; replaced with `ModelReprMixin` at some places
  use `__str__` for a more human readable representation.

### Fixed

- `Patterns.__getitem__` didn't work with pattern names as documented.

### Removed

- Ambiguous `__index__` methods from a bunch of model classes.
- Unimplemented `Slot.controllers`.

[#27]: https://github.com/demberto/PyFLP/issues/27
[#108]: https://github.com/demberto/PyFLP/issues/108

## [2.0.0a6] - 2022-11-19

### Added

- `Keyboard.main_pitch`, `Keyboard.add_root`, `Keyboard.key_region` [#92].
- `Sampler.filter` and `Filter` [#99].

### Changed

- `Channel.group` becomes a read-only property (modify event to change channel group).
- `PLItemBase.offsets` and its fields in `PlaylistEvent` are [float32](https://stackoverflow.com/a/74247360/)
  Thanks to `chrslg` from Stackoverflow and @jubabrut.
- `Track.height` returns an `str` of its percentage e.g. `100%`.
- `Instrument.plugin` and `Slot.plugin` return `_PluginBase` for unimplemented
  native plugins [#102].
- Reimplemented `EventTree` to use a list and got a 10+% perf boost in unit tests.

### Fixed

- `Channel.group` remained unitialised [#100].
- `Chanel.plugin` failed due to base class type parameter check [#101].

### Removed

- `Track.locked_height` as what this quantity stores is unknown to me yet.
- Use of fixture factories in unittests [#74].

[#74]: https://github.com/demberto/PyFLP/issues/74
[#92]: https://github.com/demberto/PyFLP/issues/92
[#99]: https://github.com/demberto/PyFLP/issues/99
[#100]: https://github.com/demberto/PyFLP/issues/100
[#101]: https://github.com/demberto/PyFLP/issues/101
[#102]: https://github.com/demberto/PyFLP/issues/102

## [2.0.0a5.post] - 2022-10-31

### Changed

- Upgrade `construct-typing` to 0.5.3.

## [2.0.0a5] - 2022-10-28

### Added

- Implementation for `Channel` and `Pattern` playlist items [#84].
- `FX.remove_dc`, `FX.trim`, `FX.fix_trim`, `FX.crossfade`,
  `FX.length`, `FX.normalize`, `FX.inverted`, `FX.start` [#55].
- Normalized linear values for certain properties, more user friendly to deal with.
  The required encode / decode is done at event level itself.
- `TimeStretching.time`, `TimeStretching.pitch`, `TimeStretching.multiplier` [#87].
- (Undiscovered) `MIDIControllerEvent`.
- `Delay.mod_x`, `Delay.mod_y`, `Delay.fat_mode` and `Delay.ping_pong` [#88].
- Improve enum performance by using `f-enum` library (`pyflp.parse` is 50% faster).
- `Time.gate`, `Time.shift` and `Time.full_porta` [#89].
- *Experimental* Python 3.11 support is back.
- A shit ton of flags in `VSTPlugin` and refactoring [#95].
- `WrapperEvent.page`, `WrapperEvent.height`, `WrapperEvent.width` [#93].
- `ItemModel.__setitem__` propagates back changes to owner event [#97].

### Changed

- `PlaylistItemBase.offsets` now returns start and end offsets.
- Use git commit for `construct-typing` which has fixed certain bugs.
- Rename `PlaylistItemBase` to `PLItemBase` and `PatternPlaylistItem` to `PatternPLItem`.
- Rename `Polyphony` members `is_mono` to `mono` and `is_porta` to `porta`.
- `NoModelsFound` also bases `LookupError` now.
- Compiled `VSTPluginEvent.STRUCT`.

### Fixed

- `EventTree.divide` fails to yield the only element [#90].
- `TrackID.Name` events were grouped instead of getting divided [#96].
- `PropBase.__set__` always raises `PropertyCannotBeSet` [#97].

### Removed

- `PlaylistItemBase.start_offset` and `PlaylistItemBase.end_offset`.
- Redundant exceptions `ExpectedValue`, `UnexpectedType`.
- Undiscovered `num_inputs`, `num_outputs` and `vst_number` from `VSTPlugin`.

[#55]: https://github.com/demberto/PyFLP/issues/55
[#84]: https://github.com/demberto/PyFLP/issues/84
[#87]: https://github.com/demberto/PyFLP/issues/87
[#88]: https://github.com/demberto/PyFLP/issues/88
[#89]: https://github.com/demberto/PyFLP/issues/89
[#90]: https://github.com/demberto/PyFLP/issues/90
[#93]: https://github.com/demberto/PyFLP/issues/93
[#95]: https://github.com/demberto/PyFLP/issues/95
[#96]: https://github.com/demberto/PyFLP/issues/96
[#97]: https://github.com/demberto/PyFLP/issues/97

## [2.0.0a4] - 2022-10-22

The way models were passed events has changed. I designed a new data structure
called `EventTree` (check `pyflp._events`) to allow the insertion and
deletion of events like a list while preserving the speed of a dict lookups.

Sounds *awfully* like `multidict` except that it doesn't allow mutable views.
`EventTree` knows its parents and any attempt to insert or delete an event
from it will also affect its parents *and vice-versa*. Took quite some to do.

`EventTree` will allow for insertion / removal of events when corresponding
descriptor setters / deleters (yet to implement) are invoked. This can allow
for wonderful things like creating new channels, moving inserts etc.

### Added

- A multidict with mutable dict view `EventTree`.
- PyPy 3.7+ support [#77].
- Slicing for ModelBase collections [#31].
- Fruity Center parser [#42].
- Dependency on `sortedcontainers` library for `EventTree`.
- Remaining and some new images for docstrings [#47].
- GUI locations of descriptors (w.r.t. FL 20.8.4) [#80].

### Changed

- Simplified some `__repr__` strings.
- Event IDs are all `EventEnum` members (better repr-strings).
- PyFLP is guaranteed to be not thread-safe.
- Moved up `Sampler.cut_group` to `_SamplerInstrument`.

### Fixed

- `ModelReprMixin`.

### Removed

- `Track.index` in favour of the redundant `Track.__index__`.
- `Track.items`. Iterate over a track, to get them now.
- Subclassing of protocol classes keeping [PEP544] in mind [#50].
- Models are no longer hashable as events were made unhashable previously.
- Commented out currently unimplemented `Channel.controllers`.

[#31]: https://github.com/demberto/PyFLP/issues/31
[#42]: https://github.com/demberto/PyFLP/discussions/42
[#47]: https://github.com/demberto/PyFLP/issues/47
[#50]: https://github.com/demberto/PyFLP/issues/50
[#77]: https://github.com/demberto/PyFLP/issues/77
[#80]: https://github.com/demberto/PyFLP/issues/80
[PEP544]: https://peps.python.org/pep-0544

## [2.0.0a3] - 2022-10-08

### Added

- 100% mypy tested *for all you mypy geeks*. It makes me play cat-and-mouse.
- `Automation` points and LFO, via [#29].

### Changed

- All `StructBaseEvent` classes overhauled to use the `construct` library.
- `EventBase.__len__` is now `EventBase.size`, a property.
- Shift all subclass event parsing to `PODEventBase`.
- Replace all uses of `bytesioex` with equivalents from `construct`.
- Struct definitions moved to `StructEventBase` itself.
- Enums used in structs directly now inherit from `construct_typed.EnumBase`.
- `LFO` renamed to `SamplerLFO` to be distinguishable from `AutomationLFO`.

### Fixed

- `InsertEQ` was't working [#46].
- Negative `FileFormat` weren't being read.
- Incorrect event size calculation in `StructEventBase` [#72].
- `Pattern.__repr__` failed for empty patterns.

### Removed

- `_StructMeta` (voodoo magic) and `StructBase` from `pyflp._events`.
- `SoundgoodizerMode`, `FruityFastDistKind`, `StereoEnhancerInvertPosition`,
  `StereoEnhancerEffectPosition` from `pyflp.plugin` in favour of equivalent
  string literals.
- Protocol subclassing of `EventBase` hierarchy.
- Faulty `EventBase.__hash__`.
- Python 3.11 support due to <https://github.com/timrid/construct-typing/issues/15>
- Incomplete support for `Sequence` in model collections.

[#29]: https://github.com/demberto/PyFLP/issues/29
[#46]: https://github.com/demberto/PyFLP/issues/46
[#72]: https://github.com/demberto/PyFLP/issues/72

## [2.0.0a2] - 2022-10-01

### Added

- `FX.clip`, `FX.fade_stereo`, `FX.freq_tilt`, `FX.pogo`, `FX.ringmod`,
  `FX.swap_stereo` & `FX.reverse` [#55].
- `TimeStretching.mode` and `StretchMode` [#56].
- `Playback.start_offset` [#57].
- `Content.declick_mode` and `DeclickMode` [#58].
- User guide and contibutor's guide.
- Official support for Python 3.11.
- Super basic `__repr__` for `StructBase` to ease debugging.
- `Envelope.amount`, `Envelope.synced`, `LFO.amount`,
  `LFO.attack`, `LFO.predelay` & `LFO.speed` [#69].

### Changed

- Moved `stretching` to `Sampler`, instruments don't have it.
- `Note.key` now returns a note name with octave [#66].
- A cleaner implementation of `MixerParamsEvent`.
- `Layer.__repr__` now shows the number of children also.
- Separated test assets into presets for better isolation of results [#6].
- Renamed `LFO.is_synced` to `LFO.synced` and `LFO.is_retrig` to `LFO.retring`.
- `StructBase` and `ListEventBase` are lazily evaluated now.
- Model collections are indexable by item names as well [#45].

### Fixed

- String are decoded as UTF16 when version is 11.5+ now [#65].
- `Insert.stereo_separation` docstring for maximum, minimum value.
- `U16TupleEvent.value` [#68].
- Minimum and maximum value docstrings for certain `FX` properties.
- `Sampler.pitch_shift` internal representation.

### Removed

- Images for individual FX properties as they were redundant.
- Redundant member `_SamplerInstrument.flags`.

[#6]: https://github.com/demberto/PyFLP/issues/6
[#45]: https://github.com/demberto/PyFLP/issues/45
[#55]: https://github.com/demberto/PyFLP/issues/55
[#56]: https://github.com/demberto/PyFLP/issues/56
[#57]: https://github.com/demberto/PyFLP/issues/57
[#58]: https://github.com/demberto/PyFLP/issues/58
[#65]: https://github.com/demberto/PyFLP/issues/65
[#66]: https://github.com/demberto/PyFLP/issues/66
[#68]: https://github.com/demberto/PyFLP/issues/68
[#69]: https://github.com/demberto/PyFLP/issues/69

## [2.0.0a1] - 2022-09-21

### Added

- `PlaylistItemBase.group` for `ChannelPlaylistItem` and `PatternPlaylistItem` [#36].
- More info in contributor's guide.
- VSCode Python extension configuration, recommended extensions and tasks.
- `ChannelRack.height` which tells the height of the channel rack in pixels.
- `Track[x]` returns `Track.items[x]`.
- `Patterns` warns when tried to be accessed with an index of 0.
- `Note.group`, a number which notes of the same group share [#28].
- `Note.slide` which indicates whether a note is a sliding note.
- Plugin wrapper properties to docs.
- A user guide section in docs.
- `Sampler.content`, `Layer.random` & `Layer.crossfade` [#24].
- `Playback.ping_pong_loop`.

### Changed

- `Pattern.notes` refactored into `Pattern.__iter__`.
- `Sampler.sample_path` returns `pathlib.Path` instead of `str` now [#41].
- `PluginID.Data` events get parsed during event collection itself.
- All models are now equatable and hashable.

### Fixed

- `Arrangement` parsing logic is incorrect [#32].
- `Track.color` returns `int` instead of `colour.Color` [#33].
- `_PlaylistItemStruct.track_index` should be 2 bytes [#36].
- Tracks don't get assigned playlist items [#37].
- KeyError when accessing `Track.content_locked` [#38].
- Channel type wasn't correctly detected at times [#40].
- `Arrangements.height` was actually `ChannelRack.height` [#43].
- TypeError when accessing `Insert.dock` [#44].
- `Pattern.note` and `Pattern.controllers` [#48].
- `Track.items` [#49]
- Certain properties of `Note` were interpreted incorrectly.
- `Slot.plugin` wasn't working at all (events, properties, repr) [#53].
- `FruitySend.send_to` was interepreted incorrectly.
- `Instrument.plugin` and `Slot.plugin` setter.
- `Playback.use_loop_points`.

### Removed

- `Arrangements.height`.

[#24]: https://github.com/demberto/PyFLP/issues/24
[#28]: https://github.com/demberto/PyFLP/issues/28
[#32]: https://github.com/demberto/PyFLP/issues/32
[#33]: https://github.com/demberto/PyFLP/issues/33
[#36]: https://github.com/demberto/PyFLP/issues/36
[#37]: https://github.com/demberto/PyFLP/issues/37
[#38]: https://github.com/demberto/PyFLP/issues/38
[#40]: https://github.com/demberto/PyFLP/issues/40
[#41]: https://github.com/demberto/PyFLP/issues/41
[#43]: https://github.com/demberto/PyFLP/issues/43
[#44]: https://github.com/demberto/PyFLP/issues/44
[#48]: https://github.com/demberto/PyFLP/issues/48
[#49]: https://github.com/demberto/PyFLP/issues/49
[#53]: https://github.com/demberto/PyFLP/issues/53

## [2.0.0a0] - 2022-09-14

PyFLP has been rewritten ✨

Highlights:

1. Richer events: Variable data events now parse their structure themselves.
   Fixed size events are categorized closely to the data they represent.
2. Lazy evaluation: Properties are evaluated as lazily as possible to prevent
   the use of private variables and keep them synced with event data.
3. Neatly organised models: Appropriate use of composition and subclassing.
4. Zero pre-parse field validation: Makes sense for an undocumented format.
5. Fully type hinted: Ensures strict adherence with pyright.
6. Simplified single-level module hierarchy to ease imports.
7. Docs now contain images for corresponding model types.

*The major version number bump indicates a breaking change, however I would highly
encourage you to upgrade to this version. **I WILL NOT BE MAINTAINING OLDER VERSIONS.***

## 1.1.2 - Unreleased

### Fixed

- [#9](https://github.com/demberto/PyFLP/pull/9), thanks to @zacanger.

## [1.1.1] - 2022-07-10

### Added

- Avoid mkdocs warnings in tox.

### Changed

- `_FLObject._save` always returns a list now.
- CI: Merge `dev` and `publish` workflows into one.

### Fixed

- [#8](https://github.com/demberto/PyFLP/issues/8).
- Type hints and type variables are much better.
- `FSoftClipper` property setter typo caused it to be set to zero.
- `ChannelParameters._save()` didn't return an event.

### Removed

- Wait action in CI workflow.
- `setup-cfg-fmt` pre-commit hook, [why?](https://github.com/asottile/setup-cfg-fmt/issues/147)

## [1.1.0] - 2022-05-29

### Added

- Support for Fruity Stereo Enhancer @@nickberry17
- Instructions for alternate methods to install PyFLP.

### Changed

- Improvements to CI

### Fixed

- Incorrect encoding used to dump UTF-16 strings in `_TextEvent`.
- [#4](https://github.com/demberto/PyFLP/issues/4).

### Removed

- `_FLObject.max_count`, `MaxInstancesError`, `test_flobject.py` and
  `_MaxInstancedFLObject`.
- Gitter links from README and room itself, due to inactivity.

## [1.0.1] - 2022-04-02

This update is more about QOL improvements, testing and refactoring. Few bugs
have been fixed as well, while Python 3.6 support has been deprecated.

### Added

- Adopted `bandit`.
- `_MaxInstancedFLObject`: `FLObject` with a limit on number of instances.
- GPL3 short license headers.
- Missing docs about `PatternNote` and `PatternController` events.
- Exceptions: `InvalidHeaderSizeError`, `InvalidMagicError` and `MaxInstancesError`.
- Import statements in submodules to simplify import process externally.
- Test validators and properties and project version setter.
- OTT plugin to test project to test VST plugins.

### Changed

- All use of `assert` has been replaced by exceptions (bandit: assert-used).
- Version links in changelog now show changes.
- LF line endings used and enforced everywhere.
- `ppq` field moved to `_FLObject` from `Playlist`.
- Much improved `tox.ini` and pre-commit configuration.
- Modules which aren't meant for external use are prefixed with a _.
- Simplified property declaration.

### Deprecated

- Python 3.6 support will be dropped in a future major release.

### Fixed

- All this time, `VSTPluginEvent` was never getting created/saved.
- Lint errors reported by flake8, pylint and bandit.
- Just realised `__setattr__` works only on instances 😅, came up with
  `_FLObjectMeta` which is the metaclass used by `_FLObject`.

### Removed

- Redundant `__repr__` from `PatternNote`.

## [1.0.0] - 2021-11-12

### **Highlights**

- The entire module hierarchy of PyFLP has been simplified.
- Internal/abstract base classes have bee renamed to start with _.
- `repr` for `_FLObject` subclasses.
- The way properties are handled is now completely changed.
- Data events get parsed by a `DataEvent` subclass.
- Way better testing, with a coverage of whooping 79%.
- `color` properties now return a `colour.Color` object.
- Almost everything has a docstring now, even enum members.
- PyFLP has adopted Contributor Covenant Code of Conduct v2.1.

### Added

- `__repr__()` for all `_FLObject` subclasses.
- `Channel.color`, `Insert.color` and `Pattern.color` now return `colour.Color`.
  This is implemented by `ColorEvent` (*which subclasses `DWordEvent`*).
- New event implementations for `ChannelFX.EventID` (`Cutoff`, `Fadein`, `Fadeout` and more).
- New event implementations for `Channel.EventID` (`ChannelTracking`,
  `ChannelLevels`, `ChannelLevelOffsets`, `ChannelPolyphony` and more).
- `Channel.cut_group` implementing `Channel.EventID.CutSelfCutBy`.
- Remote controllers (`RemoteController`). Accessible from `Project.controllers`.
- Saving for `VSTPlugin`.
- All enum members used by `FLObject` subclasses now have a docstring.
- Added links in docstrings to official FL Studio Manual wherever possible.
- `Parser.__build_event_store()` uses inner methods now to parse different kind of events; very helpful for the new `DataEvents`.
- Added support for pattern controller events (`PatternController`, `PatternControllerEvent` who implement `PatternEventID.Controllers`).
- Many attribute docstrings now include minimum, maximum and default values. **These limits are enforced by setters**.
- Added `.editorconfig`, *using CRLF line endings btw*.
- Added `test_parser.py` and `test_events.py`.
- `Parser.parse_zip` now accepts a `bytes` object for `zip_file` parameter.
- `Misc.registered` for `Misc.EventID.Registered`.

### Changed

- All `_FLObject` subclasses have been moved to parent `pyflp/` from `pyflp/flobject/` to ease import names.
- All `Event` subclasses have been moved in a single `event.py` and `event/` folder is removed.
- All event ID enum names are now inner classes of `_FLObject` subclasses.
- Constructor of `Project` has been simplified.
- `VSTPlugin`'s underlying event now supports saving, it has been refactored out of `_parse_data_event` also.
- `InsertParametersEvent` to replace the equivalent parsing in `Insert._parse_data_event`.
- The `TODO` *(deleted now)* has been changed to reflect the type of goals.
- `_FLObject.save` is now `_FLObject._save`.
- Some constants present in `utils.py` have been moved to `constants.py`.
- Docs include a brief summary of the underlying data event wherever applicable.
- Minor property name changes; made them more concise.
- Absolute imports are used everywhere now.

### Fixed

- `ChannelFXReverb` was not getting initialised.
- `InsertParamsEvent` was not getting initialised.
- Syntax is highlighted in the [docs](https://pyflp.rtfd.io/) as expected now.
- `FNotebook2` text parsing.
- `Insert.routing` returned `True` for all tracks.
- `Misc.start_date` and `Misc.work_time` parsing.

### Removed

- Any and all sort of logging, not useful anymore. Haven't seen any 3rd party
  Python library ever using it. Used `warnings` wherever necessary.
- `mypy`. Its useless tbh, I will use types as I see fit.
- Setters for all properties containing `_FLObject` (or any sort of a collection of them), *e.g. Arrangement.tracks*.

## [0.2.0]

### **Highlights**

- **PyFLP has passed the null test for a full project of mine (FL 20.7.2) 🥳**.
- This library uses code from [FLParser](https://github.com/monadgroup/FLParser),
  a GPL license project, PyFLP is now under GPL.
- API reference documentation is complete now.
- Few new events implemented for `Channel`.
- Refactored `FLObject` and `Plugin`.

#### `FLObject` refactoring

- `parseprop` is now `_parseprop`.
- All `_parseprop` delegates are now "protected" as well.
- `setprop` is now `_setprop`.

### Added

- `ChannelEvent.Delay` is implemented by `ChannelDelay` and `Channel.delay`.
- `Event.to_raw` and `Event.dump` now log when they are called.
- Exceptions `DataCorruptionDetected` and `OperationNotPermitted`.

### Fixed

- Can definitely say, all naming inconsistencies have been fixed.
- Fixed `TimeMarker` assign to `Arrangement` logic in `Parser`.
- Extraneous data dumped sometimes by `InsertSlotEvent.Plugin`, caused due to
  double dumping of same events.
- Empty pattern events, `PatternEvent.Name` and `PatternEvent.Color` don't get saved.

---

❗ These versions below don't work due to naming inconsistencies 😅, you will not find them 👇

## [0.1.2]

### Added

- More docs.
- Add some new properties/events to `Channel`.
- A sample empty FLP has been provided to allow running tests.
- All `FLObject` subclasses now have a basic `__repr__` method.

### Fixed

- Improve the GitHub workflow action, uploads to PyPI will not happen unless the test is passed.
- ~~Fix all naming inconsistencies caused due to migration to [`BytesIOEx`](https://github.com/demberto/BytesIOEx)~~ Not all.

### Known issues

Same as in 0.1.1

## [0.1.1]

~~The first version of PyFLP that works correctly 🥳~~ No, unfortunately

### **Highlights**

- Changed documentation from Sphinx to MkDocs.
- [FLPInfo](https://github.com/demberto/FLPInfo) is now a separate package.
- FLPInspect is now a separate package.
- PyFLP now uses [BytesIOEx](https://github.com/demberto/BytesIOEx/) as an external dependency.

### Fixed

- `ByteEvent`, `WordEvent` and `DWordEvent` now raise a `TypeError`.
  when they are initialised with the wrong size of data.
- Fix setup.cfg, project structure is now as expected, imports will work.
- [Docs](https://pyflp.rtfd.io/) are now up and running.

### Known issues

- Extraneous data dumped sometimes by `InsertSlotEvent.Plugin`, why this is caused is not known.

---

**❗ These versions below don't work because I didn't know how to configure `setup.cfg` properly 😅**

## 0.1.0

- `flpinspect` - An FLP Event Viewer made using Tkinter.
- `flpinfo` - A CLI utility to get basic information about an FLP.
- Switched to MIT License.

### Added

- Lots of changes, refactoring and code cleanup of `pyflp`.
- New docs.
- Changes to `README`.
- Adopted [`black`](https://github.com/psf/black) coding style.
- Added a `log_level` argument to `Parser`.
- `Project.create_zip` copies stock samples as well now.
- `Project.get_events` for getting just the events; they are not parsed.
  Read [docs](https://pyflp.rtfd.io) for more info about this.
- `Event` classes now have an `__eq__` and `__repr__` method.

### Fixed

- Tests don't give module import errors.
- `Pattern` event parsing.
- Initialise `_count` to 0, everytime `Parser` is initialised.
- `Project.create_zip` now works as intended.
- Overhauled logging.
- A lot of potential bugs in `FLObject` subclasses.

### Known issues

- `flpinfo` doesn't output correctly sometimes due to long strings.
- Extraneous data dumped sometimes by `InsertSlotEvent.Plugin`, why this is caused is not known.

[2.2.1]: https://github.com/demberto/PyFLP/compare/v2.2.0...v2.2.1
[2.2.0]: https://github.com/demberto/PyFLP/compare/v2.1.1...v2.2.0
[2.1.1]: https://github.com/demberto/PyFLP/compare/v2.1.0...v2.1.1
[2.1.0]: https://github.com/demberto/PyFLP/compare/v2.0.0...v2.1.0
[2.0.0]: https://github.com/demberto/PyFLP/compare/v2.0.0a7.post0...v2.0.0
[2.0.0a7]: https://github.com/demberto/PyFLP/compare/v2.0.0a6...v2.0.0a7
[2.0.0a6]: https://github.com/demberto/PyFLP/compare/v2.0.0a5.post...v2.0.0a6
[2.0.0a5.post]: https://github.com/demberto/PyFLP/compare/v2.0.0a5...v2.0.0a5.post
[2.0.0a5]: https://github.com/demberto/PyFLP/compare/v2.0.0a4...v2.0.0a5
[2.0.0a4]: https://github.com/demberto/PyFLP/compare/v2.0.0a3...v2.0.0a4
[2.0.0a3]: https://github.com/demberto/PyFLP/compare/v2.0.0a2...v2.0.0a3
[2.0.0a2]: https://github.com/demberto/PyFLP/compare/v2.0.0a1...v2.0.0a2
[2.0.0a1]: https://github.com/demberto/PyFLP/compare/v2.0.0a0...v2.0.0a1
[2.0.0a0]: https://github.com/demberto/PyFLP/compare/v1.1.1...v2.0.0a0
[1.1.1]: https://github.com/demberto/PyFLP/compare/1.1.0...v1.1.1
[1.1.0]: https://github.com/demberto/PyFLP/compare/1.0.1...1.1.0
[1.0.1]: https://github.com/demberto/PyFLP/compare/1.0.0...1.0.1
[1.0.0]: https://github.com/demberto/PyFLP/compare/0.2.0...1.0.0
[0.2.0]: https://github.com/demberto/PyFLP/compare/0.1.2...0.2.0
[0.1.2]: https://github.com/demberto/PyFLP/compare/0.1.1...0.1.2
[0.1.1]: https://github.com/demberto/PyFLP/releases/tag/0.1.1


================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct

## Our Pledge

We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, caste, color, religion, or sexual identity
and orientation.

We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.

## Our Standards

Examples of behavior that contributes to a positive environment for our
community include:

* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
  and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
  overall community

Examples of unacceptable behavior include:

* The use of sexualized language or imagery, and sexual attention or
  advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
  address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
  professional setting

## Enforcement Responsibilities

Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.

Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.

## Scope

This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.

## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
[demberto@protonmail.com](demberto@protonmail.com).
All complaints will be reviewed and investigated promptly and fairly.

All community leaders are obligated to respect the privacy and security of the
reporter of any incident.

## Enforcement Guidelines

Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:

### 1. Correction

**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.

**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.

### 2. Warning

**Community Impact**: A violation through a single incident or series
of actions.

**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.

### 3. Temporary Ban

**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.

**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.

### 4. Permanent Ban

**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior,  harassment of an
individual, or aggression toward or disparagement of classes of individuals.

**Consequence**: A permanent ban from any sort of public interaction within
the community.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.1, available at
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].

Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].

For answers to common questions about this code of conduct, see the FAQ at
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available
at [https://www.contributor-covenant.org/translations][translations].

[homepage]: https://www.contributor-covenant.org
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations


================================================
FILE: LICENSE
================================================
                    GNU GENERAL PUBLIC LICENSE
                       Version 3, 29 June 2007

 Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

                            Preamble

  The GNU General Public License is a free, copyleft license for
software and other kinds of works.

  The licenses for most software and other practical works are designed
to take away your freedom to share and change the works.  By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.  We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors.  You can apply it to
your programs, too.

  When we speak of free software, we are referring to freedom, not
price.  Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.

  To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights.  Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.

  For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received.  You must make sure that they, too, receive
or can get the source code.  And you must show them these terms so they
know their rights.

  Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.

  For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software.  For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.

  Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so.  This is fundamentally incompatible with the aim of
protecting users' freedom to change the software.  The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable.  Therefore, we
have designed this version of the GPL to prohibit the practice for those
products.  If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.

  Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary.  To prevent this, the GPL assures that
patents cannot be used to render the program non-free.

  The precise terms and conditions for copying, distribution and
modification follow.

                       TERMS AND CONDITIONS

  0. Definitions.

  "This License" refers to version 3 of the GNU General Public License.

  "Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.

  "The Program" refers to any copyrightable work licensed under this
License.  Each licensee is addressed as "you".  "Licensees" and
"recipients" may be individuals or organizations.

  To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy.  The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.

  A "covered work" means either the unmodified Program or a work based
on the Program.

  To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy.  Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.

  To "convey" a work means any kind of propagation that enables other
parties to make or receive copies.  Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.

  An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License.  If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.

  1. Source Code.

  The "source code" for a work means the preferred form of the work
for making modifications to it.  "Object code" means any non-source
form of a work.

  A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.

  The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form.  A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.

  The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities.  However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work.  For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.

  The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.

  The Corresponding Source for a work in source code form is that
same work.

  2. Basic Permissions.

  All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met.  This License explicitly affirms your unlimited
permission to run the unmodified Program.  The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work.  This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.

  You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force.  You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright.  Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.

  Conveying under any other circumstances is permitted solely under
the conditions stated below.  Sublicensing is not allowed; section 10
makes it unnecessary.

  3. Protecting Users' Legal Rights From Anti-Circumvention Law.

  No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.

  When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.

  4. Conveying Verbatim Copies.

  You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.

  You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.

  5. Conveying Modified Source Versions.

  You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:

    a) The work must carry prominent notices stating that you modified
    it, and giving a relevant date.

    b) The work must carry prominent notices stating that it is
    released under this License and any conditions added under section
    7.  This requirement modifies the requirement in section 4 to
    "keep intact all notices".

    c) You must license the entire work, as a whole, under this
    License to anyone who comes into possession of a copy.  This
    License will therefore apply, along with any applicable section 7
    additional terms, to the whole of the work, and all its parts,
    regardless of how they are packaged.  This License gives no
    permission to license the work in any other way, but it does not
    invalidate such permission if you have separately received it.

    d) If the work has interactive user interfaces, each must display
    Appropriate Legal Notices; however, if the Program has interactive
    interfaces that do not display Appropriate Legal Notices, your
    work need not make them do so.

  A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit.  Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.

  6. Conveying Non-Source Forms.

  You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:

    a) Convey the object code in, or embodied in, a physical product
    (including a physical distribution medium), accompanied by the
    Corresponding Source fixed on a durable physical medium
    customarily used for software interchange.

    b) Convey the object code in, or embodied in, a physical product
    (including a physical distribution medium), accompanied by a
    written offer, valid for at least three years and valid for as
    long as you offer spare parts or customer support for that product
    model, to give anyone who possesses the object code either (1) a
    copy of the Corresponding Source for all the software in the
    product that is covered by this License, on a durable physical
    medium customarily used for software interchange, for a price no
    more than your reasonable cost of physically performing this
    conveying of source, or (2) access to copy the
    Corresponding Source from a network server at no charge.

    c) Convey individual copies of the object code with a copy of the
    written offer to provide the Corresponding Source.  This
    alternative is allowed only occasionally and noncommercially, and
    only if you received the object code with such an offer, in accord
    with subsection 6b.

    d) Convey the object code by offering access from a designated
    place (gratis or for a charge), and offer equivalent access to the
    Corresponding Source in the same way through the same place at no
    further charge.  You need not require recipients to copy the
    Corresponding Source along with the object code.  If the place to
    copy the object code is a network server, the Corresponding Source
    may be on a different server (operated by you or a third party)
    that supports equivalent copying facilities, provided you maintain
    clear directions next to the object code saying where to find the
    Corresponding Source.  Regardless of what server hosts the
    Corresponding Source, you remain obligated to ensure that it is
    available for as long as needed to satisfy these requirements.

    e) Convey the object code using peer-to-peer transmission, provided
    you inform other peers where the object code and Corresponding
    Source of the work are being offered to the general public at no
    charge under subsection 6d.

  A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.

  A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling.  In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage.  For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product.  A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.

  "Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source.  The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.

  If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information.  But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).

  The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed.  Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.

  Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.

  7. Additional Terms.

  "Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law.  If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.

  When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it.  (Additional permissions may be written to require their own
removal in certain cases when you modify the work.)  You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.

  Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:

    a) Disclaiming warranty or limiting liability differently from the
    terms of sections 15 and 16 of this License; or

    b) Requiring preservation of specified reasonable legal notices or
    author attributions in that material or in the Appropriate Legal
    Notices displayed by works containing it; or

    c) Prohibiting misrepresentation of the origin of that material, or
    requiring that modified versions of such material be marked in
    reasonable ways as different from the original version; or

    d) Limiting the use for publicity purposes of names of licensors or
    authors of the material; or

    e) Declining to grant rights under trademark law for use of some
    trade names, trademarks, or service marks; or

    f) Requiring indemnification of licensors and authors of that
    material by anyone who conveys the material (or modified versions of
    it) with contractual assumptions of liability to the recipient, for
    any liability that these contractual assumptions directly impose on
    those licensors and authors.

  All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10.  If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term.  If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.

  If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.

  Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.

  8. Termination.

  You may not propagate or modify a covered work except as expressly
provided under this License.  Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).

  However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.

  Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.

  Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License.  If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.

  9. Acceptance Not Required for Having Copies.

  You are not required to accept this License in order to receive or
run a copy of the Program.  Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance.  However,
nothing other than this License grants you permission to propagate or
modify any covered work.  These actions infringe copyright if you do
not accept this License.  Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.

  10. Automatic Licensing of Downstream Recipients.

  Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License.  You are not responsible
for enforcing compliance by third parties with this License.

  An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations.  If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.

  You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License.  For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.

  11. Patents.

  A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based.  The
work thus licensed is called the contributor's "contributor version".

  A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version.  For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.

  Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.

  In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement).  To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.

  If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients.  "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.

  If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.

  A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License.  You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.

  Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.

  12. No Surrender of Others' Freedom.

  If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License.  If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all.  For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.

  13. Use with the GNU Affero General Public License.

  Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work.  The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.

  14. Revised Versions of this License.

  The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time.  Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.

  Each version is given a distinguishing version number.  If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation.  If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.

  If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.

  Later license versions may give you additional or different
permissions.  However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.

  15. Disclaimer of Warranty.

  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.

  16. Limitation of Liability.

  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.

  17. Interpretation of Sections 15 and 16.

  If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.

                     END OF TERMS AND CONDITIONS

            How to Apply These Terms to Your New Programs

  If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.

  To do so, attach the following notices to the program.  It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.

    <one line to give the program's name and a brief idea of what it does.>
    Copyright (C) <year>  <name of author>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.

Also add information on how to contact you by electronic and paper mail.

  If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:

    <program>  Copyright (C) <year>  <name of author>
    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
    This is free software, and you are welcome to redistribute it
    under certain conditions; type `show c' for details.

The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License.  Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".

  You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.

  The GNU General Public License does not permit incorporating your program
into proprietary programs.  If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library.  If this is what you want to do, use the GNU Lesser General
Public License instead of this License.  But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.


================================================
FILE: MANIFEST.in
================================================
prune .github
exclude .all-contributorsrc
exclude .gitignore
exclude .pre-commit-config.yaml
exclude .readthedocs.yaml


================================================
FILE: README.md
================================================
# PyFLP

PyFLP is an unofficial parser for [FL Studio](https://www.image-line.com/fl-studio/)
project and preset files written in Python.

<!-- SHIELDS -->
<!-- markdownlint-disable -->
<table>
  <colgroup>
    <col style="width: 10%;"/>
    <col style="width: 90%;"/>
  </colgroup>
  <tbody>
    <tr>
      <th>CI</th>
      <td>
        <a href="https://pyflp.readthedocs.io/en/latest/">
          <img alt="Documentation Build Status" src="https://img.shields.io/readthedocs/pyflp/latest?logo=read-the-docs"/>
        </a>
        <a href="https://results.pre-commit.ci/latest/github/demberto/PyFLP/master">
          <img alt="pre-commit-ci" src="https://results.pre-commit.ci/badge/github/demberto/PyFLP/master.svg"/>
        </a>
      </td>
    </tr>
    <tr>
      <th>PyPI</th>
      <td>
        <a href="https://pypi.org/project/PyFLP">
          <img alt="PyPI - Package Version" src="https://img.shields.io/pypi/v/PyFLP"/>
        </a>
        <a href="https://pypi.org/project/PyFLP">
          <img alt="PyPI - Supported Python Versions" src="https://img.shields.io/pypi/pyversions/PyFLP?logo=python&amp;logoColor=white"/>
        </a>
        <a href="https://pypi.org/project/PyFLP">
          <img alt="PyPI - Supported Implementations" src="https://img.shields.io/pypi/implementation/PyFLP"/>
        </a>
        <a href="https://pypi.org/project/PyFLP">
          <img alt="PyPI - Wheel" src="https://img.shields.io/pypi/wheel/PyFLP"/>
        </a>
      </td>
    </tr>
    <tr>
      <th>Activity</th>
      <td>
        <img alt="Maintenance" src="https://img.shields.io/maintenance/yes/2023"/>
        <a href="https://pypistats.org/packages/pyflp">
          <img alt="PyPI - Downloads" src="https://img.shields.io/pypi/dm/PyFLP"/>
        </a>
      </td>
    </tr>
    <tr>
      <th>QA</th>
      <td>
        <a href="https://codecov.io/gh/demberto/PyFLP">
          <img alt="codecov" src="https://codecov.io/gh/demberto/PyFLP/branch/master/graph/badge.svg?token=RGSRMMF8PF"/>
        </a>
        <a href="https://codefactor.io/repository/github/demberto/PyFLP">
          <img alt="CodeFactor Grade" src="https://img.shields.io/codefactor/grade/github/demberto/PyFLP?logo=codefactor"/>
        </a>
        <a href="http://mypy-lang.org/">
          <img alt="Checked with mypy" src="http://www.mypy-lang.org/static/mypy_badge.svg">
        </a>
        <a href="https://github.com/pre-commit/pre-commit">
          <img alt="pre-commit" src="https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&amp;logoColor=white"/>
        </a>
        <a href="https://github.com/PyCQA/bandit">
          <img alt="Security Status" src="https://img.shields.io/badge/security-bandit-yellow.svg"/>
        </a>
      </td>
    </tr>
    <tr>
      <th>Other</th>
      <td>
        <a href="https://github.com/demberto/PyFLP/blob/master/LICENSE">
          <img alt="License" src="https://img.shields.io/github/license/demberto/PyFLP"/>
        </a>
        <img alt="GitHub top language" src="https://img.shields.io/github/languages/top/demberto/PyFLP"/>
        <a href="https://github.com/psf/black">
          <img alt="Code Style: Black" src="https://img.shields.io/badge/code%20style-black-black"/>
        </a>
        <a href="https://github.com/demberto/PyFLP/blob/master/CODE_OF_CONDUCT.md">
          <img alt="covenant" src="https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg"/>
        </a>
      </td>
    </tr>
  </tbody>
</table>
<!-- markdownlint-restore -->

From a very general point-of-view, this is the state of what is currently
implemented. Click on a link to go to the documentation for that feature.

<!-- FEATURE TABLE -->
<!-- markdownlint-disable -->
<table>
  <tr>
    <th>Group</th>
    <th>Feature</th>
    <th>Issues</th>
  </tr>
  <tr>
    <td rowspan="3">
      <a href="https://pyflp.readthedocs.io/en/latest/reference/arrangements.html">Arrangements</a><br/>
      <a href="https://github.com/demberto/PyFLP/issues?q=is%3Aopen+is%3Aissue+label%3Aarrangement-general">
        <img alt="open arrangement-general issues" src="https://img.shields.io/github/issues-raw/demberto/PyFLP/arrangement-general?label=open&style=flat-square"/>
      </a>
      <a href="https://github.com/demberto/PyFLP/issues?q=is%3Aclosed+is%3Aissue+label%3Aarrangement-general">
        <img alt="closed arrangement-general issues" src="https://img.shields.io/github/issues-closed-raw/demberto/PyFLP/arrangement-general?label=closed&style=flat-square"/>
      </a>
    </td>
    <td><a href="https://pyflp.readthedocs.io/en/latest/reference/arrangements.html#playlist">🎼 Playlist</a></td>
    <td>
      <a href="https://github.com/demberto/PyFLP/issues?q=is%3Aopen+is%3Aissue+label%3Aarrangement-playlist">
        <img alt="open arrangement-playlist issues" src="https://img.shields.io/github/issues-raw/demberto/PyFLP/arrangement-playlist?label=open&style=flat-square"/>
      </a>
      <a href="https://github.com/demberto/PyFLP/issues?q=is%3Aclosed+is%3Aissue+label%3Aarrangement-playlist">
        <img alt="closed arrangement-playlist issues" src="https://img.shields.io/github/issues-closed-raw/demberto/PyFLP/arrangement-playlist?label=closed&style=flat-square"/>
      </a>
    </td>
  </tr>
  <tr></tr> <!-- only for formatting --->
  <tr>
    <td><a href="https://pyflp.readthedocs.io/en/latest/reference/arrangements.html#track">🎞️ Tracks</a></td>
    <td>
      <a href="https://github.com/demberto/PyFLP/issues?q=is%3Aopen+is%3Aissue+label%3Aarrangement-track">
        <img alt="open arrangement-track issues" src="https://img.shields.io/github/issues-raw/demberto/PyFLP/arrangement-track?label=open&style=flat-square"/>
      </a>
      <a href="https://github.com/demberto/PyFLP/issues?q=is%3Aclosed+is%3Aissue+label%3Aarrangement-track">
        <img alt="closed arrangement-track issues" src="https://img.shields.io/github/issues-closed-raw/demberto/PyFLP/arrangement-track?label=closed&style=flat-square"/>
      </a>
    </td>
  </tr>
  <tr>
    <td rowspan="4">
      <a href="https://pyflp.readthedocs.io/en/latest/reference/channels.html">Channel Rack</a><br/>
      <a href="https://github.com/demberto/PyFLP/issues?q=is%3Aopen+is%3Aissue+label%3Achannel-general">
        <img alt="open channel-general issues" src="https://img.shields.io/github/issues-raw/demberto/PyFLP/channel-general?label=open&style=flat-square"/>
      </a>
      <a href="https://github.com/demberto/PyFLP/issues?q=is%3Aclosed+is%3Aissue+label%3Achannel-general">
        <img alt="closed channel-general issues" src="https://img.shields.io/github/issues-closed-raw/demberto/PyFLP/channel-general?label=closed&style=flat-square"/>
      </a>
    </td>
    <td><a href="https://pyflp.readthedocs.io/en/latest/reference/channels.html#pyflp.channel.Automation">📈 Automations</a></td>
    <td>
      <a href="https://github.com/demberto/PyFLP/issues?q=is%3Aopen+is%3Aissue+label%channel-automation">
        <img alt="open channel-automation issues" src="https://img.shields.io/github/issues-raw/demberto/PyFLP/channel-automation?label=open&style=flat-square"/>
      </a>
      <a href="https://github.com/demberto/PyFLP/issues?q=is%3Aclosed+is%3Aissue+label%3Achannel-automation">
        <img alt="closed channel-automation issues" src="https://img.shields.io/github/issues-closed-raw/demberto/PyFLP/channel-automation?label=closed&style=flat-square"/>
      </a>
    </td>
  </tr>
  <tr>
    <td><a href="https://pyflp.readthedocs.io/en/latest/reference/channels.html#pyflp.channel.Instrument">🎹 Instruments</a></td>
    <td>
      <a href="https://github.com/demberto/PyFLP/issues?q=is%3Aopen+is%3Aissue+label%3Achannel-instrument">
        <img alt="channel-instrument issues" src="https://img.shields.io/github/issues-raw/demberto/PyFLP/channel-instrument?label=open&style=flat-square"/>
      </a>
      <a href="https://github.com/demberto/PyFLP/issues?q=is%3Aclosed+is%3Aissue+label%3Achannel-instrument">
        <img alt="closed channel-instrument issues" src="https://img.shields.io/github/issues-closed-raw/demberto/PyFLP/channel-instrument?label=closed&style=flat-square"/>
      </a>
    </td>
  </tr>
  <tr>
    <td><a href="https://pyflp.readthedocs.io/en/latest/reference/channels.html#pyflp.channel.Layer">📚 Layer</a></td>
    <td>
      <a href="https://github.com/demberto/PyFLP/issues?q=is%3Aopen+is%3Aissue+label%3Achannel-layer">
        <img alt="open channel-layer issues" src="https://img.shields.io/github/issues-raw/demberto/PyFLP/channel-layer?label=open&style=flat-square"/>
      </a>
      <a href="https://github.com/demberto/PyFLP/issues?q=is%3Aclosed+is%3Aissue+label%3Achannel-layer">
        <img alt="closed channel-layer issues" src="https://img.shields.io/github/issues-closed-raw/demberto/PyFLP/channel-layer?label=closed&style=flat-square"/>
      </a>
    </td>
  </tr>
  <tr>
    <td><a href="https://pyflp.readthedocs.io/en/latest/reference/channels.html#pyflp.channel.Sampler">📁 Sampler</a></td>
    <td>
      <a href="https://github.com/demberto/PyFLP/issues?q=is%3Aopen+is%3Aissue+label%3Achannel-sampler">
        <img alt="open channel-sampler issues" src="https://img.shields.io/github/issues-raw/demberto/PyFLP/channel-sampler?label=open&style=flat-square">
      </a>
      <a href="https://github.com/demberto/PyFLP/issues?q=is%3Aclosed+is%3Aissue+label%3Achannel-sampler">
        <img alt="closed channel-sampler issues" src="https://img.shields.io/github/issues-closed-raw/demberto/PyFLP/channel-sampler?label=closed&style=flat-square"/>
      </a>
    </td>
  </tr>
  <tr>
    <td rowspan="2">
      <a href="https://pyflp.readthedocs.io/en/latest/reference/mixer.html">Mixer</a><br/>
      <a href="https://github.com/demberto/PyFLP/issues?q=is%3Aopen+is%3Aissue+label%3Amixer-general">
        <img alt="open mixer-general issues" src="https://img.shields.io/github/issues-raw/demberto/PyFLP/mixer-general?label=open&style=flat-square"/>
      </a>
      <a href="https://github.com/demberto/PyFLP/issues?q=is%3Aclosed+is%3Aissue+label%3Amixer-general">
        <img alt="closed mixer-general issues" src="https://img.shields.io/github/issues-closed-raw/demberto/PyFLP/mixer-general?label=closed&style=flat-square"/>
      </a>
    </td>
    <td><a href="https://pyflp.readthedocs.io/en/latest/reference/mixer.html#pyflp.mixer.Insert">🎚️ Inserts</a></td>
    <td>
      <a href="https://github.com/demberto/PyFLP/issues?q=is%3Aopen+is%3Aissue+label%3Amixer-insert">
        <img alt="open mixer-insert issues" src="https://img.shields.io/github/issues-raw/demberto/PyFLP/mixer-insert?label=open&style=flat-square">
      </a>
      <a href="https://github.com/demberto/PyFLP/issues?q=is%3Aclosed+is%3Aissue+label%3Amixer-insert">
        <img alt="closed mixer-insert issues" src="https://img.shields.io/github/issues-closed-raw/demberto/PyFLP/mixer-insert?label=closed&style=flat-square"/>
      </a>
    </td>
  </tr>
    <tr>
    <td><a href="https://pyflp.readthedocs.io/en/latest/reference/mixer.html#pyflp.mixer.Slot">🎰 Effect slots</a></td>
    <td>
      <a href="https://github.com/demberto/PyFLP/issues?q=is%3Aopen+is%3Aissue+label%3Amixer-slot">
        <img alt="open mixer-slot issues" src="https://img.shields.io/github/issues-raw/demberto/PyFLP/mixer-slot?label=open&style=flat-square">
      </a>
      <a href="https://github.com/demberto/PyFLP/issues?q=is%3Aclosed+is%3Aissue+label%3Amixer-slot">
        <img alt="closed mixer-slot issues" src="https://img.shields.io/github/issues-closed-raw/demberto/PyFLP/mixer-slot?label=closed&style=flat-square"/>
      </a>
    </td>
  </tr>
  <tr>
    <td rowspan="3">
      <a href="https://pyflp.readthedocs.io/en/latest/reference/patterns.html">🎶 Patterns</a><br/>
      <a href="https://github.com/demberto/PyFLP/issues?q=is%3Aopen+is%3Aissue+label%3Apattern-general">
        <img alt="open pattern-general issues" src="https://img.shields.io/github/issues-raw/demberto/PyFLP/pattern-general?label=open&style=flat-square"/>
      </a>
      <a href="https://github.com/demberto/PyFLP/issues?q=is%3Aclosed+is%3Aissue+label%3Apattern-general">
        <img alt="closed pattern-general issues" src="https://img.shields.io/github/issues-closed-raw/demberto/PyFLP/pattern-general?label=closed&style=flat-square"/>
      </a>
    </td>
    <td><a href="https://pyflp.readthedocs.io/en/latest/reference/patterns.html#pyflp.pattern.Controller">🎛 Controllers</a></td>
    <td>
      <a href="https://github.com/demberto/PyFLP/issues?q=is%3Aopen+is%3Aissue+label%3Apattern-controller">
        <img alt="open pattern-controller issues" src="https://img.shields.io/github/issues-raw/demberto/PyFLP/pattern-controller?label=open&style=flat-square"/>
      </a>
      <a href="https://github.com/demberto/PyFLP/issues?q=is%3Aclosed+is%3Aissue+label%3Apattern-controller">
        <img alt="closed pattern-controller issues" src="https://img.shields.io/github/issues-closed-raw/demberto/PyFLP/pattern-controller?label=closed&style=flat-square"/>
      </a>
    </td>
  </tr>
    <tr>
    <td><a href="https://pyflp.readthedocs.io/en/latest/reference/patterns.html#pyflp.pattern.Note">🎵 Notes</a></td>
    <td>
      <a href="https://github.com/demberto/PyFLP/issues?q=is%3Aopen+is%3Aissue+label%3Apattern-note">
        <img alt="open pattern-note issues" src="https://img.shields.io/github/issues-raw/demberto/PyFLP/pattern-note?label=open&style=flat-square">
      </a>
      <a href="https://github.com/demberto/PyFLP/issues?q=is%3Aclosed+is%3Aissue+label%3Apattern-note">
        <img alt="closed pattern-note issues" src="https://img.shields.io/github/issues-closed-raw/demberto/PyFLP/pattern-note?label=closed&style=flat-square"/>
      </a>
    </td>
  </tr>
  <tr></tr> <!-- for formatting --->
  <tr>
    <td>
      <a href="https://pyflp.readthedocs.io/en/latest/reference/timemarkers.html">🚩 Timemarkers</a>
    </td>
    <td></td>
    <td>
      <a href="https://github.com/demberto/PyFLP/issues?q=is%3Aopen+is%3Aissue+label%3Atimemarker">
        <img alt="open timemarker issues" src="https://img.shields.io/github/issues-raw/demberto/PyFLP/timemarker?label=open&style=flat-square"/>
      </a>
      <a href="https://github.com/demberto/PyFLP/issues?q=is%3Aclosed+is%3Aissue+label%3Atimemarker">
        <img alt="closed timemarker issues" src="https://img.shields.io/github/issues-closed-raw/demberto/PyFLP/timemarker?label=closed&style=flat-square"/>
      </a>
    </td>
  </tr>
  <tr>
    <td rowspan="2">
      <a href="https://pyflp.readthedocs.io/en/latest/reference/plugins.html">Plugins</a>
    </td>
    <td>
      Native -
      8 <a href="https://pyflp.readthedocs.io/en/latest/reference/plugins.html#effects">effects</a>,
      1 <a href="https://pyflp.readthedocs.io/en/latest/reference/plugins.html#generators">synth</a>
    </td>
    <td>
      <a href="https://github.com/demberto/PyFLP/issues?q=is%3Aopen+is%3Aissue+label%3Aplugin-native">
        <img alt="open plugin-native issues" src="https://img.shields.io/github/issues-raw/demberto/PyFLP/plugin-native?label=open&style=flat-square">
      </a>
      <a href="https://github.com/demberto/PyFLP/issues?q=is%3Aclosed+is%3Aissue+label%3Aplugin-native">
        <img alt="closed plugin-native issues" src="https://img.shields.io/github/issues-closed-raw/demberto/PyFLP/plugin-native?label=closed&style=flat-square"/>
      </a>
    </td>
  </tr>
  <tr>
    <td>
      <a href="https://pyflp.readthedocs.io/en/latest/reference/plugins.html#pyflp.plugin.VSTPlugin">VST 2/3</a>
    </td>
    <td>
      <a href="https://github.com/demberto/PyFLP/issues?q=is%3Aopen+is%3Aissue+label%3Aplugin-3rdparty">
        <img alt="plugin-3rdparty issues" src="https://img.shields.io/github/issues-raw/demberto/PyFLP/plugin-3rdparty?label=open&style=flat-square">
      </a>
      <a href="https://github.com/demberto/PyFLP/issues?q=is%3Aclosed+is%3Aissue+label%3Aplugin-3rdparty">
        <img alt="closed plugin-3rdparty issues" src="https://img.shields.io/github/issues-closed-raw/demberto/PyFLP/plugin-3rdparty?label=closed&style=flat-square"/>
      </a>
    </td>
  </tr>
  <tr>
    <td rowspan="2" colspan="2">
      <a href="https://pyflp.readthedocs.io/en/latest/reference/project.html">Project</a>
      - Settings and song metadata
    </td>
    <td colspan="2">
      <a href="https://github.com/demberto/PyFLP/issues?q=is%3Aopen+is%3Aissue+label%3Aproject-general">
        <img alt="open project-general issues" src="https://img.shields.io/github/issues-raw/demberto/PyFLP/project-general?label=open&style=flat-square">
      </a>
      <a href="https://github.com/demberto/PyFLP/issues?q=is%3Aclosed+is%3Aissue+label%3Aproject-general">
        <img alt="closed project-general issues" src="https://img.shields.io/github/issues-closed-raw/demberto/PyFLP/project-general?label=closed&style=flat-square"/>
      </a>
    </td>
  </tr>
</table>
<!-- markdownlint-restore -->

## ⏬ Installation

CPython 3.8+ / PyPy 3.8+ required.

```none
python -m pip install -U pyflp
```

## ▶ Usage

[Load](https://pyflp.readthedocs.io/en/latest/reference.html#pyflp.parse) a project file:

```py
import pyflp
project = pyflp.parse("/path/to/parse.flp")
```

> If you get any sort of errors or warnings while doing this, please open an
> [issue](https://github.com/demberto/PyFLP/issues).

[Save](https://pyflp.readthedocs.io/en/latest/reference.html#pyflp.save) the project:

```py
pyflp.save(project, "/path/to/save.flp")
```

> It is advised to do a backup of your projects before doing any changes.
> It is also recommended to open the modified project in FL Studio to ensure
> that it works as intended.

Check the [reference](https://pyflp.rtfd.io/en/latest/reference.html) for a
complete list of useable features.

## 🙏 Acknowledgements

- Monad.FLParser: <https://github.com/monadgroup/FLParser>
- FLPEdit (repo deleted by [author](https://github.com/roadcrewworker))

## ✨ Contributors

<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
![All Contributors](https://img.shields.io/badge/all_contributors-3-orange.svg?style=flat-square)
<!-- ALL-CONTRIBUTORS-BADGE:END -->

Thanks goes to these wonderful people:

<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<table>
  <tbody>
    <tr>
      <td align="center"><a href="https://github.com/nickberry17"><img src="https://avatars.githubusercontent.com/u/18670565?v=4?s=50" width="50px;" alt=""/><br /><sub><b>nickberry17</b></sub></a><br /><a href="https://github.com/demberto/PyFLP/commits?author=nickberry17" title="Code">💻</a></td>
      <td align="center"><a href="https://github.com/zacanger"><img src="https://avatars.githubusercontent.com/u/12520493?v=4?s=50" width="50px;" alt=""/><br /><sub><b>zacanger</b></sub></a><br /><a href="https://github.com/demberto/PyFLP/issues?q=author%3Azacanger" title="Bug reports">🐛</a> <a href="https://github.com/demberto/PyFLP/commits?author=zacanger" title="Documentation">📖</a></td>
      <td align="center"><a href="https://github.com/ttaschke"><img src="https://avatars.githubusercontent.com/u/7067750?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Tim</b></sub></a><br /><a href="https://github.com/demberto/PyFLP/commits?author=ttaschke" title="Documentation">📖</a> <a href="https://github.com/demberto/PyFLP/commits?author=ttaschke" title="Code">💻</a> <a href="#maintenance-ttaschke" title="Maintenance">🚧</a></td>
    </tr>
  </tbody>
</table>
<!-- markdownlint-restore -->
<!-- prettier-ignore-end -->
<!-- ALL-CONTRIBUTORS-LIST:END -->

This project follows the [all-contributors](https://allcontributors.org/) specification.
Contributions of any kind are welcome!

Please see the [contributor's guide](https://pyflp.rtfd.io/en/latest/contributing.html)
for more information about contributing.

## 📧 Contact

You can contact me either via [issues](https://github.com/demberto/PyFLP/issues)
and [discussions](https://github.com/demberto/PyFLP/discussions) or through
email via ``demberto(at)proton(dot)me``.

## © License

The code in this project has been licensed under the
[GNU Public License v3](https://www.gnu.org/licenses/gpl-3.0.en.html).


================================================
FILE: docs/Makefile
================================================
# Minimal makefile for Sphinx documentation
#

# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS    ?=
SPHINXBUILD   ?= sphinx-build
SOURCEDIR     = .
BUILDDIR      = _build

# Put it first so that "make" without argument is like "make help".
help:
	@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

.PHONY: help Makefile

# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
	@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)


================================================
FILE: docs/architecture/flp-format.rst
================================================
Part I: FLP Format & Events
===========================

FLP is a binary format used by Image-Line FL Studio, a music production
software, to store project files. Instead of using C-style structs entirely,
the FLP format has evolved from what once was a MIDI-like format to a really
bad and messy combination of :wikipedia:`Type-length-value` encoded "events"
and structs.

Specification
-------------

An FLP file contains of basically 2 sections or "chunks", one is the header
and other is the "data" section, which contains all the "events".

Header chunk
^^^^^^^^^^^^

.. tab-set::

   .. tab-item:: C / C++

      .. code-block:: c

         struct {
             char magic[4];          // 'FLhd'
             uint32_t size;          // always been 6
             int16_t format;         // Internal file format
             uint16_t num_channels;  // Number of channels in channel rack
             uint16_t ppq;           // Pulses per quarter
         }

   .. tab-item:: Python

      .. code-block:: python

         class Header:
             magic: str
             size: int
             format: int
             num_channels: int
             ppq: int

.. currentmodule:: pyflp.project
.. seealso::

   :attr:`Project.format`, :attr:`Project.channel_count`, :attr:`Project.ppq`

Data chunk
^^^^^^^^^^

.. code-block:: c

   struct {
       char magic[4];  // 'FLdt'
       uint32_t size;  // Total combined size of events
       void* events;   // Event data
   }

.. _architecture-event:

Event
-----

An event can be thought of as a "flattened" :class:`dict` of attributes
composing a class. It can *roughly* be represented as:

.. tab-set::

   .. tab-item:: C / C++

      .. code-block:: c

         struct {
             uint8_t type;
             void* value;
         }

   .. tab-item:: Python

      .. code-block:: python

         class Event:
             type: int
             value: object

Types
^^^^^

There are basically 4 kinds of events depending on the range of ``type``:

+----------+--------------------+-----------------+
| Event ID | Size of ``value`` | Total event size |
+==========+===================+==================+
| 0-63     | 1 byte            | 1 + 1 = **2**    |
+----------+-------------------+------------------+
| 64-127   | 2 bytes           | 1 + 2 = **3**    |
+----------+-------------------+------------------+
| 128-191  | 4 bytes           | 1 + 4 = **5**    |
+----------+-------------------+------------------+
| 192-255  | Length prefixed   | >= 2             |
+----------+-------------------+------------------+

.. note:: Length prefixed events

   These events store the length of the ``value`` they contain after ``type``
   in a varint. It can be considered as the only true TLV encoded event type.

   .. code-block:: c

      struct {
          uint8_t type;     // 192-255
          uint8_t* length;  // varint
          void* value;      // string, struct or subevent
      }

It should be clearer by now how the FLP format is a misfit for the data it
represents.

Representation
^^^^^^^^^^^^^^

Event IDs 0-191 are used for storing fixed size data like integers, floats and
booleans. IDs from 192-255 are used for storing structs, subevents and strings.


================================================
FILE: docs/architecture/how-it-works.rst
================================================
Part II: How PyFLP works
========================

    💡 You should read Part I before this.

PyFLP's entry-point :meth:`pyflp.parse` verifies the headers and parses all the
events. These events are collected into an :class:`pyflp._events.EventTree`.

Schematic diagram
-----------------

.. svgbob::
   :align: center

    ┌──────────────────────────────────────────────────────────────────────────┐
    │     Events - binary representation - low level API - Stage 1 parser      │
    │                                                                          │
    │ ┌─────────────────────────┐ ┌─────────────────────────┐ ┌─────────────┐  │
    │ │  Project-wide / 1-time  │ │      Per-instance       │ │    Shared   │  │
    │ │┌─────────┐   ┌─────────┐│ │┌─────────┐   ┌─────────┐│ │ ┌─────────┐ │  │
    │ ││ Event 1 │   │ Event 2 ││ ││ Event 3 │   │ Event 4 ││ │ │ Event 5 │ │  │
    │ ││ id: 199 │ → │ id: 159 ││→││ id: 64  │ → │ id: 215 ││→│ │ id: 225 │ │  │
    │ ││ string  │   │ integer ││ ││ integer │   │ struct  ││ │ │   AoS   │ │  │
    │ │└─────────┘   └─────────┘│ │└─────────┘   └─────────┘│ │ └─────────┘ │  │
    │ └─────│──────────────│────┘ └──────│────────────│─────┘ └──────│──────┘  │
    │       └──┬───────────╯╭────────────┴────────────╯              │         │
    │ ┌───────────────┐ ┌───────┬──────────┬──────────────┐ ┌────────────────┐ │
    │ │    Model A    │ │ Model │ Model B1 │ attr_64: int │ │ Model C1: e[0] │ │
    │ │ attr_199: str │ │ list  ├──────────┼──────────────┤ ├────────────────┤ │
    │ │ attr_159: int │ │ of B  │ Model B2 │ attr_215: X  │ │ Model C2: e[1] │ │
    │ └───────────────┘ └───────┴──────────┴──────────────┘ └────────────────┘ │
    │                                                                          │
    │    Models - PyFLP's representation - high level API - Stage 2 parser     │
    └──────────────────────────────────────────────────────────────────────────┘

PyFLP provides a high-level and a low-level API. Normally the high-level API
should get your work done. However, it might be possible that due to a bug or
super old versions of FLPs the high level API fails to parse. In that case,
one can use the low-level API. Using it requires a deeper understanding of
the FLP format and how the GUI hierarchies relate to their underlying events.

What it does?
-------------

In a nutshell, PyFLP parses the events and creates a better semantic structure
from it (as shown in the above diagram; stage 2 parser). I call this a "model".

.. _architecture-model:

Model
-----

A model acts like a "view" or alternate representation of the event data. It
has no state of its own and its composed of descriptors which get and set
values from the events directly. A model is essentially stateless.

This has some advantages as compared to stateful models:

1. The underlying event data and the values returned from the model descriptors
   *i.e. its attributes or properties* always remain in sync with each other.
2. Since modifying the event data at a binary level means conforming to the
   various size and range limits imposed by C's data types, it can act as basic
   validation for no extra cost or implementation.
3. Avoid the use of private members in the models itself. Private members maybe
   a good idea in languages which have better implementation of such concepts,
   but in Python its quite as good as shooting yourself in the foot. Due to
   Python's do-whatever-you-want nature, it can lead to some very bad coding
   practices. This is one of the big reasons why PyFLP underwent a rewrite.
4. Nothing is done in class constructor, so if a particular set of events are
   out of order or follow a sequence not yet understood by PyFLP, they will
   fail only for the attributes which use them. Hence, what is *parseable* can
   still be parsed. This lazy evaluation can be good and bad both, but with
   adequate unit tests its more good than it is bad.

Creating a model involves a good amount of reverse engineering and insight. The
models PyFLP has are based as close to the GUI objects inside FL Studio. For
e.g. a pattern is represented by :class:`pyflp.pattern.Pattern`.

A model is constructed with events it requires and additional information (like
PPQ) its descriptors might need.


================================================
FILE: docs/architecture/reference.rst
================================================
Developer Reference
===================

This page documents PyFLP's internals which consists of :mod:`pyflp._events`,
:mod:`pyflp._descriptors` and :mod:`pyflp._models`.

    The content below assumes you have fairly good knowledge of the following:

    - OOP and descriptors, especially
    - Type annotations
    - Binary data types and streams

Events
------

.. automodule:: pyflp._events

If you have read Part I, you know how events use a TLV encoding scheme.

Type
^^^^

The ``type`` represents the event ID. A custom enum class (and a metaclass)
supporting unknown IDs and member check using Python's ``... in ...`` syntax is
used.

.. autoclass:: _EventEnumMeta
   :members:
.. autoclass:: EventEnum
   :members:

Length
^^^^^^

The ``length`` is a field prefixed for IDs in the range of 192-255. It is the
size of ``value`` and is encoded as a VLQ128 (variable length quantity base-128).

Value
^^^^^

Below are the list of classes PyFLP has, grouped w.r.t the ID range.

.. dropdown:: 0-63

    .. autoclass:: ByteEventBase
    .. autoclass:: U8Event
    .. autoclass:: BoolEvent
    .. autoclass:: I8Event

.. dropdown:: 64-127

    .. autoclass:: WordEventBase
    .. autoclass:: U16Event
    .. autoclass:: I16Event

.. dropdown:: 128-191

    .. autoclass:: DWordEventBase
    .. autoclass:: U32Event
    .. autoclass:: I32Event
    .. autoclass:: ColorEvent
    .. autoclass:: U16TupleEvent

.. dropdown:: 192-255

    .. autoclass:: StrEventBase
    .. autoclass:: AsciiEvent
    .. autoclass:: UnicodeEvent
    .. autoclass:: StructEventBase
    .. autoclass:: ListEventBase
    .. autoclass:: UnknownDataEvent

EventTree
^^^^^^^^^

.. autoclass:: EventTree
   :members:

Models
------

.. automodule:: pyflp._models

Implementing a model
^^^^^^^^^^^^^^^^^^^^

A look at the **source code** will definitely help, although these are a few
points that must be kept in mind when Implementing a model:

1. Does the model mimic the hierarchy exposed by FL Studio's GUI?

   .. tip::

      Browse through the hierarchies of :class:`pyflp.channel.Channel`
      subclasses to get a very good idea of this.

2. Are ``__dunder__`` methods provided by Python used whenever possible?
3. Is either :class:`ModelReprMixin` subclassed or ``__repr__`` implemented?

Descriptors
-----------

.. automodule:: pyflp._descriptors

Adapters
--------

.. automodule:: pyflp._adapters


================================================
FILE: docs/architecture.rst
================================================
🏠 Architecture
================

.. toctree::

   1️⃣ FLP Format & Events <architecture/flp-format>
   2️⃣ How it works? <architecture/how-it-works>
   3️⃣ Developer Reference <architecture/reference>


================================================
FILE: docs/changelog.rst
================================================
.. mdinclude:: ../CHANGELOG.md


================================================
FILE: docs/conf.py
================================================
# type: ignore

"""Sphinx configuration script."""

from __future__ import annotations

import enum
import importlib.metadata
import inspect
import re

import m2r2

from pyflp._descriptors import EventProp, FlagProp, NestedProp, StructProp
from pyflp._events import EventEnum, RGBA
from pyflp._models import ModelBase
from pyflp.arrangement import _TrackColorProp

BITLY_LINK = re.compile(r"!\[.*\]\((https://bit\.ly/[A-z0-9]*)\)")
"""Shortened URLs for links to in-docstring images and docs."""

NEW_IN_FL = re.compile(r"\*New in FL Studio v([^\*]*)\*[\.:](.*)")
"""Matched in docstrings and replaced with an SVG by :meth:`badge_flstudio`."""

EVENT_ID_DOC = re.compile(r"([0-9\.]*)\+")
FL_BADGE = "https://img.shields.io/badge/FL%20Studio-{}+-5f686d?labelColor=ff7629&style=for-the-badge"  # noqa
GHUC_PREFIX = "https://raw.githubusercontent.com/demberto/PyFLP/master/docs/"
"""Raw image URL root used for in-docstring images and docs."""

IGNORED_BITLY = ["3RDM1yn"]

project = "PyFLP"
author = "demberto"
copyright = f"2022, {author}"
release = importlib.metadata.version("pyflp")  # Needs package installation!
extensions = [
    "hoverxref.extension",
    "m2r2",  # Markdown to reStructuredText conversion
    "sphinx_copybutton",  # Copy button for code blocks
    "sphinx_design",  # Grids, cards, icons and tabs
    "sphinxcontrib.spelling",  # Catch spelling mistakes
    "sphinxcontrib.svgbob",  # ASCII diagrams -> SVG
    "sphinx.ext.autodoc",  # Sphinx secret sauce
    "sphinx.ext.autosummary",  # Summary of contents table
    "sphinx.ext.coverage",  # Find what I missed to autodoc
    "sphinx.ext.duration",  # Time required to build docs
    "sphinx.ext.intersphinx",  # Automatic links to Python docs
    "sphinx.ext.napoleon",  # Google-style docstrings
    "sphinx.ext.todo",  # Items I need to document
    "sphinx.ext.viewcode",  # "Show source" button next to autodoc output
    "sphinx_toolbox",  # Badges and goodies
    "sphinx_toolbox.github",  # Link to project issues / PRs easily
    "sphinx_toolbox.more_autodoc.autoprotocol",  # Autodoc extension for typing.Protocol
    "sphinx_toolbox.more_autodoc.sourcelink",  # Python docs-style source code link
    "sphinx_toolbox.sidebar_links",  # Links to repo and PyPi project in the sidebar
    "sphinx_toolbox.wikipedia",  # Diretive for wikipedia topics.
]
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
html_theme = "furo"  # Nice light/dark theme; has an auto-switch mode
autodoc_inherit_docstrings = False
autodoc_default_options = {
    "undoc-members": True,  # Show undocumented members
    "exclude-members": "INTERNAL_NAME",  # Exclude these members
    "no-value": True,  # Don't show a default value (for descriptors mainly)
}
needs_sphinx = "5.0"
hoverxref_auto_ref = True  # Convert all :ref: roles to hoverxrefs
napoleon_preprocess_types = True
napoleon_attr_annotations = True
html_permalinks_icon = "<span>#</span>"  # Get rid of the weird paragraph icon
github_username = author  # sphinx_toolbox.github config
github_repository = project  # sphinx_toolbox.github config
autodoc_show_sourcelink = True  # sphinx_toolbox.more_autodoc.sourcelink
todo_include_todos = True  # Include .. todo:: directives in output
todo_emit_warnings = True  # Emit warnings about it as well, so I don't forget
html_css_files = [
    "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.3.0/css/all.min.css"
]  # https://sphinx-design.rtfd.io/en/furo-theme/badges_buttons.html#fontawesome-icons
sd_fontawesome_latex = True  # Output FontAwesome icons in LaTeX
intersphinx_mapping = {
    "python": ("https://docs.python.org/3", None),
    "construct": ("https://construct.readthedocs.io/en/latest", None),
}  # Put hyperlinks to docs of other projects

linkcheck_allowed_redirects = {
    r"https://bit.ly/.*": r"https://raw.githubusercontent.com/demberto/PyFLP/master/docs/img/.*",  # noqa
    r"https://pyflp.rtfd.io.*": r"https://pyflp.readthedocs.io/en/latest/.*",
    r"https://www.python.org/dev/peps/.*": r"https://peps.python.org/.*",
    r"https://github.com/demberto/PyFLP/files/.*": r"https://objects.githubusercontent.com/.*",  # noqa
    r"https://stackoverflow.com/a/.*": r"https://stackoverflow.com/questions/.*",
}


def badge_flstudio(app, what, name, obj, options, lines):
    """Convert FL Studio version information in docstrings to nice badges."""
    for line in lines:
        if name.split(".")[-2].endswith("ID"):  # Event ID member
            match = EVENT_ID_DOC.fullmatch(line)
        else:
            match = NEW_IN_FL.fullmatch(line)

        if match is not None:
            groups = tuple(
                filter(
                    lambda group: group != "",
                    map(lambda group: group.strip(), match.groups()),
                )
            )

            if len(groups) == 1:
                lines.insert(0, f".. image:: {FL_BADGE.format(groups[0])}")
                lines.insert(1, "")
            elif len(groups) == 2:
                grid = f"""
                .. figure:: {FL_BADGE.format(groups[0])}
                    :alt: New in FL Studio v{groups[0]}

                    {groups[1].strip()}

                """
                lines[:0] = grid.splitlines()  # https://stackoverflow.com/a/25855473
            lines.remove(line)


def add_annotations(app, what, name, obj, options, signature, return_annotation):
    """Add type annotations for descriptors."""
    if what == "class" and issubclass(obj, ModelBase):
        annotations = {}
        for name_, type in vars(obj).items():
            if isinstance(type, _TrackColorProp):
                annotations[name_] = RGBA
            elif isinstance(type, NestedProp):
                annotations[name_] = type._type
            elif isinstance(type, FlagProp):
                annotations[name_] = bool | None
            elif hasattr(type, "__orig_class__"):
                annotations[name_] = type.__orig_class__.__args__[0]

            if isinstance(type, (EventProp, StructProp)):
                annotations[name_] |= None

        if hasattr(obj, "__annotations__"):
            obj.__annotations__.update(annotations)
        else:
            obj.__annotations__ = annotations


def autodoc_markdown(app, what, name, obj, options, lines):
    """Convert all markdown in docstrings to reStructuredText.

    This includes images and tables. Docstrings are in markdown for VSCode
    compatibility.
    """
    filtered = [line for line in lines for link in IGNORED_BITLY if link not in line]
    newlines = m2r2.convert("\n".join(filtered)).splitlines()
    lines.clear()
    lines.extend(newlines)


def remove_model_signature(app, what, name, obj, options, signature, return_annotation):
    """Removes the :func:`ModelBase.__init__` args from the docstrings.

    It's an implementation detail, and only clutters the docs.
    """
    if what == "class" and issubclass(obj, ModelBase):
        return ("", return_annotation)


def remove_enum_signature(app, what, name, obj, options, signature, return_annotation):
    """Removes erroneous :attr:`signature` = '(value)' for `enum.Enum` subclasses."""
    if inspect.isclass(obj) and issubclass(obj, enum.Enum):  # Event ID class
        return ("", return_annotation)


def include_obsolete_ids(app, what, name, obj, skip, options):
    """Includes obsolete / undocumented (prefixed with a `_`) event IDs."""
    if isinstance(obj, EventEnum):  # EventID member
        return False


def show_model_dunders(app, what, name, obj, skip, options):
    """Subclasses of ``ModelBase`` show these dunders regardless of any settings."""
    if name in ("__getitem__", "__setitem__", "__iter__", "__len__"):
        return False


def setup(app):
    """Connects all callbacks to their event handlers."""
    app.connect("autodoc-process-docstring", badge_flstudio)
    app.connect("autodoc-process-docstring", autodoc_markdown)
    app.connect("autodoc-process-signature", add_annotations)
    app.connect("autodoc-process-signature", remove_model_signature)
    app.connect("autodoc-process-signature", remove_enum_signature)
    app.connect("autodoc-skip-member", include_obsolete_ids)
    app.connect("autodoc-skip-member", show_model_dunders)


================================================
FILE: docs/contributing.rst
================================================
\ :fas:`user-gear` Contributor's Guide
======================================

🤝 All contributions are welcome.

.. important::

   PyFLP adheres to the `Contributor Covenant Code of Conduct
   <https://github.com/demberto/PyFLP/blob/master/CODE_OF_CONDUCT.md>`_.
   Please make sure you have read it and accept it before proceeding further.

⬇ The sections below are ordered roughly in the order one would follow.

\ :fas:`code-pull-request;sd-text-primary` Making a PR
------------------------------------------------------

.. tip:: Format code with ``black``

   PyFLP use the black code style, format your code with it.

:fas:`clone` Clone the repo
^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. code-block:: console

   git clone https://github.com/demberto/PyFLP

:fas:`code-branch` Create a branch
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. code-block:: console

   git branch my_new_feature
   git checkout my_new_feature

🌱 Create a virtual environment
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

I prefer to use `venv <https://docs.python.org/3/library/venv.html>`_.

.. code-block:: console

   python -m venv venv

This will create a folder named ``venv`` in the current directory.

Now, activate the venv:

.. code-block:: console

   ./venv/Scripts/activate

📌 Install dependencies
^^^^^^^^^^^^^^^^^^^^^^^^

Install all dev, user and docs dependencies.

.. code-block:: console

   python -m pip install --upgrade pip
   pip install -r requirements.txt -r docs/requirements.txt tox

|vscode-icon| VS Code integration
---------------------------------

I use VS Code for development. I have made certain changes to the workspace to
suit my workflows and make life easier.

.. todo Inspect whether venv creation can be automated through VSCode.

:fab:`python` Python extension configuration
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

To ease linting, enforce strict type checking and improve code quality, I have
modified certain settings for the official Python / Pylance extension, so that
you don't need to manually configure it or encounter issues while committing.
Check `settings.json
<https://github.com/demberto/PyFLP/blob/master/.vscode/settings.json>`_.

:material-sharp:`extension;1.2em;sd-pb-1` Recommended extensions
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

When you open the repo directory in VS Code, you will get recommendations for
extensions. I use these extensions myself. You can check `extensions.json
<https://github.com/demberto/PyFLP/blob/master/.vscode/extensions.json>`_ to
know why and where they are used.

:fas:`list-check` Tasks
^^^^^^^^^^^^^^^^^^^^^^^

If you use :fab:`windows;sd-text-secondary` Windows, I have made some shortcuts
for common tasks. Check `tasks.json
<https://github.com/demberto/PyFLP/blob/master/.vscode/tasks.json>`_.

.. |vscode-icon| image:: /img/contributing/vscode.svg
   :width: 32

|pytest-icon| Testing
---------------------

FL Studio comes with a handy feature 🚀 to export "presets" for various
:doc:`models <./architecture>` like :class:`Channel`, :class:`Insert` and so
on. This is used for **isolating** test results. A look 👀 at `tests/assets
directory <https://github.com/demberto/PyFLP/tree/master/tests/assets>`_ shows
what possible models and properties could be tested from a preset file. I have
divided the tests such that they test a model or an individual property.

These presets have the same layout of a normal full FLP would use, but only the
required events are kept. This *might* and **has** caused some problems while
testing properties dependant on data passed from its parent 😔. For instance, an
:class:`Insert` gets version from :class:`Mixer` which gets it from
:class:`Project` itself. This kind of dependency is not good in my opinion 😐,
and I continue to look at ways to improve testing.

There also are models which cannot be exported into presets, notable being
:class:`Pattern` (although scores can be exported) and the entire
:mod:`pyflp.arrangement` module. Currently, I have kept the testing for these
in a common FLP.

✴️ Guidelines
^^^^^^^^^^^^^^

1. Follow the naming scheme of the test functions, it generally follows the
   format of  ``test_{model_collection}`` *or* ``test_{model}_{descriptor}``.
2. Create separate test assets, whenever possible.

.. |pytest-icon| image:: /img/contributing/pytest.svg
    :width: 32

📖 Docs
--------

Don't forget to update the `docs <https://pyflp.rtfd.io/>`_ after you are done
with a feature or a bug fix that affects the documentation.

✴️ Guidelines
^^^^^^^^^^^^^^

1. ↔ **80 columns** max, wherever possible. Don't consider this for inlined links
   and tables.

   .. tip::

      Don't start a new sentence at the end of a line. Remember that it should
      be easily readable to you, first of all.

2. 🌐 **Inline links** if they aren't used twice in the same document.
3. 📝 Should look **clean** enough in a simple text viewer as well.
4. 💡 Use **emojis** if it conveys the meaning of the text next to it.
5. ⚫⚪ Add images for both **light** and **dark** modes.

🛠 Sphinx configuration
^^^^^^^^^^^^^^^^^^^^^^^

Sphinx is the tool I use for generating PyFLP's docs. It comes with a handy
plugin called ``sphinx-autodoc`` to automatically generate documentation for
the code from Python docstrings.

One thing about it, however is that its primarily reStructuredText driven,
while my docstrings are all in Github-flavored markdown. Luckily, Sphinx being
powerful and extensible provides APIs to modify the docstrings that are sent to
the ``sphinx-autodoc`` plugin.

Currently, the transformation is divided into these steps (in order):

- ⤵ Replacing ``*New in FL Studio ...*`` with shields like these:

  .. image:: https://img.shields.io/badge/FL%20Studio-20+-5f686d?labelColor=ff7629&style=for-the-badge

- ➕ Adding the correct annotations for the :doc:`descriptors <./architecture>`.
- ⤵ Converting GFM tables and images in the docstrings to reStructuredText.
- ➖ Removing erroneous ``__init__`` method signatures from enums and models.
- ➕ Include "private" (obsolete) :class:`pyflp._events.EventEnum` members.
- ➕ Include model dunder methods like ``__len__``, ``__iter__`` etc.

Check `conf.py <https://github.com/demberto/PyFLP/blob/master/docs/conf.py>`_
for understanding how it is done.

🚧 Still to be documented
^^^^^^^^^^^^^^^^^^^^^^^^^^

.. todolist::


================================================
FILE: docs/faq.rst
================================================
❓ FAQ
======

Now I don't frequently get asked any questions, *(I would love to)* but these
are some questions I think a newcomer or a contributor might ask?

🗣 How do I get help?
^^^^^^^^^^^^^^^^^^^^^

- Check the [discussions](https://github.com/demberto/PyFLP/discussions), open
  a new one.
- Open an issue if you spot a bug 🐛 or want a new feature ✨.
- Email me on demberto(at)proton.me.

I will generally get back to you within a day ⏰.

✨ Is "X" supported / implemented?
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Pretty much. I have organised PyFLP's code to be pretty self explanatory.
If you are completely new to the terminology used by PyFLP, you should also
open up FL Studio's documentation open besides the `reference <./reference>`_.

If you find something isn't yet implemented, open an issue or, a
:doc:`PR <./contributing>` if you have implemented something.

➕➖ Why is there no functionality to **add** / **remove** items from collections?
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

*Also answers alternative specific questions like "Why can't I add a channel to
the channel rack?", "Why can't I add a new arrangement?"*

**Because of version compatibility**. The FLP format is a closed-source format
with no documentation. It evolves completely at the whims of Image-Line. I don't
work there, nor have a contributor who knows for sure that a particular thing I
implement will work for sure. *So*, instead of developing a feature which isn't
guaranteed to work, I can better devote my time to support the **modification**
of existing properties and items.

However, some good news now. I am planning to add support for adding / removing
notes from a pattern, adding / deleting automation points. Basically stuff,
which hasn't changed a lot since FL Studio first introduced it.

🤝 I want to contribute
^^^^^^^^^^^^^^^^^^^^^^^^

Please check the :doc:`contributor's guide <./contributing>` if you are new to
PyFLP. Check the :doc:`architecture` to understand the internals.

Also check out the :doc:`developer guides <./guides>`.

🧵 Is PyFLP thread safe?
^^^^^^^^^^^^^^^^^^^^^^^^^

**No.** PyFLP uses ``sortedcontainers``, an awesome library which unfortunately
`isn't thread-safe <https://github.com/grantjenks/python-sortedcontainers/issues/105>`_.


================================================
FILE: docs/features.rst
================================================
✨ Features
============

Non-destructive editing
-----------------------

The modifications you make will have a minimum effect on the internal structure
of an FLP. Infact, I guarantee you that if you save a :class:`pyflp.Project`
as-is, the new file will be exactly alike (compare hashes if you want).

📝 Godlevel docstrings
----------------------

PyFLP has been carefully written to take advantage of the features provided
by a modern editor, like VS Code. One area, I particularly devoted a lot of
time to are docstrings.

Since PyFLP's entire documentation is only its reference, I thought it might
be challenging for a first time user to know where to find the data they need.

:fas:`eye;sd-text-info` Visual hints
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. image:: /img/features/images-dark.png
   :align: center
   :class: only-dark

.. image:: /img/features/images-light.png
   :align: center
   :class: only-light

To make it somewhat easier of a journey, I haved added links to images and GIFs
from FL Studio's interface.

:fas:`table;sd-text-info` Minimums, maximums and defaults
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. image:: /img/features/tables-dark.png
   :align: center
   :class: only-dark

.. image:: /img/features/tables-light.png
   :align: center
   :class: only-light

A lot of properties also have *suggested* minimum, maximum and default values.
When I say suggested, I mean that FLP is a closed format owned by Image-Line.
Its on their whims what they do with it. The values I suggest are only on a
*last-I-checked-they-were-these* basis. However, my research till now has
shown me that they rarely change.

.. important:: For non-VS Code users

   VS Code uses a rather unstandardised format for parsing docstrings. Unlike
   PyCharm, it cannot parse rST docstrings. Hence PyCharm users will get a
   rather bad result from the docstring previews where I have used tables and
   images, *unfortunately*.

   I haven't found a way to make docstrings look good while being equally
   accessible in both PyCharm and VSCode.

   .. seealso:: `#52 <https://github.com/demberto/PyFLP/issues/52>`_

:octicon:`check-circle-fill;1em;sd-text-success` 100% type tested
-----------------------------------------------------------------

PyFLP is fully tested with `pyright <https://github.com/microsoft/pyright>`_,
a static type checker built right into VS Code as well as mypy.

:fas:`umbrella;sd-text-primary` 85%+ code coverage
--------------------------------------------------

PyFLP boasts a total of more than 85+ combined code coverage across supported
Python versions. Higher the coverage ⬆, lesser the amount of bugs 🐞


================================================
FILE: docs/guides/plugin.rst
================================================
🚶‍♂️ Walkthrough: Implementing a plugin data parser
====================================================

Implementing a native plugin data parser can be easy. Below is a walkthrough
for implementing a simple effect, :class:`Fruity Balance <pyflp.plugin.FruityBalance>`.

.. note:: Prequisites

   The steps ahead assume that you have an understanding of how binary data
   types (C's integral types, in this context) work along with a basic
   understanding of Python itself.

1. Note the parameters exposed by the plugin

.. image:: /img/guides/plugin/1-parameters.png
   :align: center
   :alt: Fruity Balance paramerers

Also take note of the **order** in which they occur. Here its **Balance**
followed by **Volume**.

2. Prepare a test FLP

Create an empty new FLP, add a **Fruity Balance** to one of the insert slots.

.. image:: /img/guides/plugin/2-load-plugin.png
   :align: center
   :alt: Fruity Balance in an insert slot

Save this FLP as ``fruity-balance.flp``.

3. Getting the plugin data

Since this is an **empty** FLP, with no other plugins loaded, you can simply
access the plugin data by,

.. code-block:: python

   import pyflp
   from pyflp.plugin import PluginID

   # Parse the FLP file into a project
   project = pyflp.parse("fruity-balance.flp")

   # Collect all the events as a dict of ID to event
   events = project.events_asdict()

   # Get the first plugin data event - the Fruity Balance one itself
   plugin_data_event = events[PluginID.Data][0]

   # Get the raw data and convert it to a tuple of 8-bit unsigned integers
   data = tuple(bytearray(plugin_data_event._struct))
   print(data)

4. Observe and analyze the output

▶ You will get an output like this:

.. code-block:: python

   (0, 0, 0, 0, 256, 0, 0, 0)

That's a total of **8 bytes** worth of data for **2 knobs**.

FL Studio *generally* uses 4 bytes for most type of data, so let's assume each
knob takes **4 bytes**.

Now compare it with the **positions** of the knobs in Fruity Balance.

.. image:: /img/guides/plugin/3-observe-knob-positions.png
   :align: center
   :alt: Observe knob positions

‼ Suddenly the data above makes sense.

How? Let me explain.

- **Balance** knob is at 12 o' clock
- **Volume** knob is somewhere at 80% of its maximum.

Now convert the 8-bit integer tuple into a two 32-bit integer tuple. We get the
values ``0`` and ``256`` respectively. So, now we know, that **Balance** is 0
(because its centred) and **Volume** is at 256. Also, since we didn't modify
them at all, these are the **default** values.

🥳 Success! We cracked it!

5. Exercise: Find out the minimum and maximums (optional, but recommended)

By rotating the knobs to their extremes and following steps 3-4 again, you can
find out the minimum and maximums of each knob.

.. hint::

   One very important place for finding out the extremes is the hint panel.

   .. image:: /img/guides/plugin/4-hint-panel.png
      :align: center
      :alt: FL Studio hint panel

6. Writing the plugin code

ℹ All plugins are implemented in the :mod:`pyflp.plugin` module.

.. note::

   Take care to follow the naming conventions as shown below.

Begin with writing the code for the plugin :ref:`event <architecture-event>`:

.. code-block:: python

   class FruityBalanceEvent(StructEventBase):
       STRUCT = c.Struct("pan" / c.Int32ul, "volume" / c.Int32ul).compile()

.. note:: What is ``c.Struct``?

   PyFLP uses the :mod:`construct` library to define and binary structures.
   Its a fairly simple to understand declarative binary parser creator.

   .. tip::

      Call :meth:`construct.Struct.compile()` to get a faster version of the
      "Struct". Check <https://construct.readthedocs.io/en/latest/compilation.html>
      for more information.

Now create a :ref:`model <architecture-model>` for the event we just created
in the same module:

.. code-block:: python

   class FruityBalance(_PluginBase[FruityBalanceEvent]):
       pan = _PluginDataProp[int]()
       volume = _PluginDataProp[int]()

You don't need to worry about ``_PluginBase`` and ``_PluginDataProp``. They are
implementation-level details, you don't *generally* need to worry about.

Derive our newly create ``FruityBalance`` from ``_IPlugin`` and implement it:

.. important::

   Don't forget to do this. Otherwise the event will not be parsed.

.. code-block:: python
   :emphasize-lines: 1, 2

   class FruityBalance(_PluginBase[FruityBalanceEvent], _IPlugin):
       INTERNAL_NAME = "Fruity Balance"
       pan = _PluginDataProp[int]()
       volume = _PluginDataProp[int]()

.. note::

   Use :doc:`FLPEdit <./reversing>` to find out ``INTERNAL_NAME`` of a plugin.

🎉 And that's basically it. The implementation is complete! Now all we need to
do is glue ``FruityBalanceEvent`` and ``FruityBalance`` to the effect slot's
:attr:`pyflp.mixer.Slot.plugin` attribute.

7. Glue the implementation to :class:`pyflp.mixer.Slot`:

Import our newly created classes in :mod:`pyflp.mixer` and add an entry to
:attr:`pyflp.mixer.Slot.plugin` like so:

.. code-block:: python
   :emphasize-lines: 3

   plugin = PluginProp(
        {
            FruityBalanceEvent: FruityBalance,
            ...
        }
    )


================================================
FILE: docs/guides/reversing.rst
================================================
🤓 Reversing FLP format
========================

    You should first take a look at :doc:`what events are <../architecture>`.
    A decent knowledge of the topics mentioned there as well as Python itself
    is assumed.

One could use a hex editor, but its too tedious. I have a simpler solution:

.. figure:: /img/guides/reversing/flpedit.png
   :alt: A test FLP opened in FLPEdit

   **FLPEdit**, an event view for FLP (and related formats) files.

   Download it `here <https://github.com/demberto/PyFLP/files/9586342/FLPEdit.zip>`_.

   This is an unmaintained software, written actually in C#. Event ID names are
   different but the file attached above has source code as well. Check the
   ``FLPFileFormat/FLP_File.cs`` file for a list of event IDs and compare them
   to the ones from :class:`pyflp._events.EventEnum` in PyFLP.

Events
------

Which event needs to be inspected can only be understood when you observe the
ordering of events, whether they occur for default values or not as well as
a general knowledge of new features and changes occuring inside FL Studio.

Check `this discussion <https://github.com/demberto/PyFLP/discussions/34>`_ for
a list of unknown / undiscovered events.

Struct fields
-------------

Structs whose field names are prefixed with a ``_u`` are undiscovered fields.
Wherever possible, I have added helpful comments right next to them.

Also, throughout PyFLP's codebase, there are a number of ``# TODO`` comments.
Some of these can have additional information about them.

My workflow
-----------

1. Create a new test FLP or a preset and save it.
2. Parse the file with PyFLP and record the initial values.
3. Turn knobs / faders all the way to their extremes, save and repeat (2)

.. hint:: WhatsNew.rtf

   FL Studio's changelog file ``WhatsNew.rtf`` exists in its install folder.
   It is a very helpful source for understanding which features were added
   when.


================================================
FILE: docs/guides.rst
================================================
📖 Developer guides
====================

Want to be a **contributor**? Interested in the internals of the FLP format?
This is the perfect place to begin. Reading :doc:`architecture` is also
recommended but not necessarily required.

.. toctree::
   :glob:

   guides/*


================================================
FILE: docs/handbook.rst
================================================
📚 Handbook
============

This page contains some ideas on how one can use PyFLP for automating
tasks (*to a certain extent*) which can only be done via FL Studio.

A basic-to-intermediate level of Python knowledge is assumed. No prior
knowledge of PyFLP is required for any of the sections below.

*These all are written from a dev's POV. I would ♥ to get more ideas and hear
about different use cases.*

📦 Exporting to a ZIP
----------------------

Imagine you had a folder structure like this:

.. code-block::

   📁 Samples
      ├─── 🥁 kick.wav
      ├─── 👏 clap.wav
      └─── 🎵 toms.wav
   📄 MyGreatSong.flp


    For the purpose of simplicity, assume that ``📄 MyGreatSong.flp`` uses only
    the samples from ``📁 Samples`` and all **sample file names are unique**.

The code below will create a ZIP containing all the samples used

.. code-block:: python

   from zipfile import ZipFile
   import pyflp

   project = pyflp.parse("MyGreatSong.flp")

   with ZipFile("MyGreatSong.zip", "x") as zp:
       zp.write("MyGreatSong.flp")

       for sampler in project.channels.samplers:
           if sampler.sample_path is not None:
               zp.write(sampler.sample_path)

.. caution:: Missing samples

   The above code assumes that all the samples exist at the paths the FLP has
   stored. If any of the samples aren't found, there will be an error.

   FL Studio doesn't give up this easily. It searches up a lot of paths,
   including but not limited to the recursive scanning of folders which are:

   - Current directory.
   - Added to the sample browser.
   - Containing previous samples / missing samples.

This will create a ZIP file of the structure:

.. code-block::

   📦 MyGreatSong.zip
      ├─── 📄 MyGreatSong.flp
      ├─── 🥁 kick.wav
      ├─── 👏 clap.wav
      └─── 🎵 toms.wav

.. hint:: FL Studio stock samples

   While this will work for 3rd party samples *unless there's 2 samples with
   the same name*, FL Studio doesn't store the full path inside an FLP for
   stock samples. See :attr:`pyflp.channel.Sampler.sample_path` for more info.

🔓 Unlocking demo version FLPs
-------------------------------

.. caution::

   This doesn't work for FL Studio 21 projects.
   See `#146 <https://github.com/demberto/PyFLP/discussions/146>`

FLPs saved with a trial version of FL Studio cannot be reopened again without
saving in a registered version. The state of demo versions of native plugins'
is not retained either.

.. hint::

   This section **doesn't** explain how to make 3rd party plugin demos
   recall their state. They have their own mechanisms for doing that.

It is possible to undo both of these:

.. seealso::

   :attr:`Project.licensed <pyflp.project.Project.licensed>` and
   :attr:`_PluginBase.demo_mode <pyflp.plugin._PluginBase.demo_mode>`.

.. code-block:: python

   import pyflp

   project = pyflp.parse("/path/to/myflp.flp")

   # Unlock the FLP itself
   project.licensed = True

   # Unlock trial version native plugins
   for instument in project.channels.instruments:
       instrument.plugin.demo_mode = False

   for insert in project.mixer:
       for slot in insert:
           if slot.plugin is not None:
              slot.plugin.demo_mode = False

   pyflp.save(project, "/path/to/myflp_unlocked.flp")

.. note::

   An unregistered version of FL Studio will roll back these changes once you
   save an FLP in it (even previously registered ones), so you need to repeat
   this process everytime.


================================================
FILE: docs/helping.rst
================================================
🙌 Helping PyFLP
=================

PyFLP is completely free and open source (FOSS) software. It takes a lot of
time and efforts to maintain it and keep improving it. I try to help anyone
having any issues or anyone who wants to contribute in any way possible to the
best of my efforts.

I don't ask for donations or any sort of funding. If you like PyFLP and want it
to grow and improve, you can do the following things:

⭐ Star **PyFLP** on Github
----------------------------

.. image:: /img/helping/star-repo-dark.gif
   :align: center
   :class: only-dark
   :target: https://github.com/demberto/PyFLP
   :alt: ⭐ How to star PyFLP?

.. image:: /img/helping/star-repo-light.gif
   :align: center
   :class: only-light
   :target: https://github.com/demberto/PyFLP
   :alt: ⭐ How to star PyFLP?

You can "star" the repo if you have a Github account. It is analogous to
"following" on social media and helps :abbr:`SEO (Search engine optimization)`.

👀 Watch **PyFLP** on Github
-----------------------------

.. image:: /img/helping/watch-repo-dark.gif
   :align: center
   :class: only-dark
   :target: https://github.com/demberto/PyFLP
   :alt: 👀 How to watch PyFLP?

.. image:: /img/helping/watch-repo-light.gif
   :align: center
   :class: only-light
   :target: https://github.com/demberto/PyFLP
   :alt: 👀 How to watch PyFLP?

You can "watch" the repo if you have a Github account. It will notify you about
all changes taking places in PyFLP right in your 📨 email.

🐞 Reporting bugs
------------------

.. image:: /img/helping/open-issue-dark.png
   :align: center
   :class: only-dark
   :target: https://github.com/demberto/PyFLP
   :alt: 🐞 How to open an issue?

.. image:: /img/helping/open-issue-light.png
   :align: center
   :class: only-light
   :target: https://github.com/demberto/PyFLP
   :alt: 🐞 How to open an issue?

If you find out that something isn't quite working as its supposed to, please
open an issue `here <https://github.com/demberto/PyFLP/issues>`_ and follow
the instructions provided in the template to fill out a bug report.

🔎 Check out the **Discussions**
---------------------------------

🗣 `Discussions <https://github.com/demberto/PyFLP/discussions>`_ is the place
where I announce what's coming new, when its coming and a few other topics
related to the FLP format.

❗ If you have a taste in reverse engineering or binary formats, you must most
definitely check it out.

🙌 You can also open a new discussion to tell me what you made with PyFLP and
how it helped you. I am more than glad to find how PyFLP is getting used.

Help the tools that power **PyFLP**
-----------------------------------

- `construct <https://github.com/construct/construct>`_
- `f-enum <https://github.com/Bobronium/fastenum>`_
- `sortedcontainers <https://github.com/grantjenks/python-sortedcontainers>`_


================================================
FILE: docs/index.rst
================================================
.. mdinclude:: ../README.md

Navigation
----------

.. toctree::
   :maxdepth: 2
   :titlesonly:

   handbook
   reference
   features
   architecture
   contributing
   guides
   faq
   limitations
   helping
   ⏰ Changelog <changelog>

.. sidebar-links::
   :github:
   :pypi: PyFLP

Indices and tables
------------------

* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`


================================================
FILE: docs/limitations.rst
================================================
🚫 Limitations
===============

Before you begin reading, I would like to **emphasize** that FLP is a closed
and undocumented format. The knowledge needed for understanding the internals
is published nowhere, except for a few notes lying around here and there and
some existing implementations which I deeply thank for saving my time.

Whatever PyFLP does, is on a best-effort level. Things can go wrong so its
always wise to have **backups** and *avoid* **overwriting**.

Most properties are discovered; their representations aren't
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

You will find almost all the properties you could imagine. However FLP being
a binary format stores any and all kinds of stuff as integers. Its actually
harder to calculate the formula used for representing stuff like frequency,
volume and other such non-linear stuff.

Another thing is musical timings, check :github:issue:`75` for a more info.

Items cannot be added or removed, only modified
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

I am working on making it possible to add or remove items (like channels,
patterns, MIDI notes, arrangements etc.) currently.

Certain things like MIDI notes are simpler to add, but things like patterns
and channels will be harder to get correct at. For those things, a **clone**
like operation is easier to implement.

.. note::

   It is possible, however (as of PyFLP 2.0.0a4) to add or remove events.
   If you don't know what they are, you probably shouldn't be handling them.
   If you are confident about working with events directly, you can very
   much add new events to effectively do things like adding your own items.

Why is it *slow*?
^^^^^^^^^^^^^^^^^

Slow is a relative term - to some it might not be noticeable at all.
Although in my opinion, PyFLP has become way slower since I migrated to
``construct``, which provides a lot more benefits than what I did earlier.

``construct`` has an opt-in compilation feature which although isn't usable
for all kinds of structs, is available for most of them, which gived quite a
speed boost for structs that occur a lot (MIDI notes, playlist items to name
a few.)

* Due to PyFLP's lazily evaluated nature, most delays don't occur upfront i.e
  during :meth:`pyflp.parse`.
* Python enums are quite slow, to the point that adding the ``f-enum`` library
  patch, reduced parse time by 50%.
* :class:`pyflp._events.EventTree` class' need for ``sortedcontainers.SortedList``
  which is implemented in pure Python.

Difficult to make ports
^^^^^^^^^^^^^^^^^^^^^^^

The current working of PyFLP is non-replicable in most other languages.
Descriptors are a Python specific feature I have yet to find anywhere else.
Therefore, the possibility of a port that's as clean (and featured) as PyFLP is
less. Most languages however have some sort of 3rd party Python interop library
available, so its not like PyFLP is completely unuseable from other languages.

A quick search on Github will return some FLP parsers available for other
languages, but almost all of them are pretty much unmaintained or archived.

Unit-testing is paramount
^^^^^^^^^^^^^^^^^^^^^^^^^

Due to the lazy nature of models and their descriptors, each of them should be
tested so as to ensure that no changes in the event handling affect or break.

For a long time, I used only a single FLP to test all of PyFLP's API. Things
have changed now and I use presets exported from FL Studio itself for the
testing of a huge chunk of API to ensure isolation of test results.

The problem is that all the test data comes from FL Studio itself and can
be only really validated in the same. That's the reason I usually don't
raise any errors event if I know quite surely that, for example a value out of
range is set for some property.


================================================
FILE: docs/make.bat
================================================
@ECHO OFF

pushd %~dp0

REM Command file for Sphinx documentation

if "%SPHINXBUILD%" == "" (
	set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=.
set BUILDDIR=_build

%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
	echo.
	echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
	echo.installed, then set the SPHINXBUILD environment variable to point
	echo.to the full path of the 'sphinx-build' executable. Alternatively you
	echo.may add the Sphinx directory to PATH.
	echo.
	echo.If you don't have Sphinx installed, grab it from
	echo.https://www.sphinx-doc.org/
	exit /b 1
)

if "%1" == "" goto help

%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
goto end

:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%

:end
popd


================================================
FILE: docs/reference/arrangement/arrangement.rst
================================================
\ :fas:`trowel-bricks` Arrangement
==================================

.. currentmodule:: pyflp.arrangement

.. autoclass:: Arrangement
   :members:

.. autoclass:: TimeSignature
   :members:

.. autoclass:: ArrangementID
   :members:
   :member-order: bysource


================================================
FILE: docs/reference/arrangement/index.rst
================================================
Arrangements
============

.. module:: pyflp.arrangement

.. toctree::
   :maxdepth: 2
   :titlesonly:
   :caption: Contents:
   :glob:

   *

.. autoclass:: Arrangements
   :members:

.. autoclass:: ArrangementsID
   :members:
   :member-order: bysource


================================================
FILE: docs/reference/arrangement/playlist.rst
================================================
\ :material-sharp:`playlist_play;1.2em;sd-pb-1` Playlist
========================================================

.. currentmodule:: pyflp.arrangement

.. autoclass:: PLItemBase
   :members:

.. autoclass:: ChannelPLItem
   :members:
   :show-inheritance: PLItemBase

.. autoclass:: PatternPLItem
   :members:
   :show-inheritance: PLItemBase


================================================
FILE: docs/reference/arrangement/track.rst
================================================
Track
=====

.. currentmodule:: pyflp.arrangement

.. autoclass:: Track
   :members:

.. grid::

   .. grid-item::

      .. autoclass:: TrackMotion
         :members:

   .. grid-item::
      :child-align: center
      :columns: auto

      .. image:: /img/arrangement/track/motion.png

.. grid::

   .. grid-item::

      .. autoclass:: TrackPress
         :members:

   .. grid-item::
      :child-align: center
      :columns: auto

      .. image:: /img/arrangement/track/press.png

.. grid::

   .. grid-item::

      .. autoclass:: TrackSync
         :members:

   .. grid-item::
      :child-align: center
      :columns: auto

      .. image:: /img/arrangement/track/sync.png

.. autoclass:: TrackID
   :members:
   :member-order: bysource


================================================
FILE: docs/reference/channel/automation.rst
================================================
\ :fas:`bezier-curve` Automation
================================

.. currentmodule:: pyflp.channel

.. autoclass:: Automation
   :show-inheritance:
   :members:

.. autoclass:: AutomationLFO
   :members:

.. autoclass:: AutomationPoint
   :members:


================================================
FILE: docs/reference/channel/channel.rst
================================================
Channel
=======

.. currentmodule:: pyflp.channel

.. autoclass:: Channel
   :members:

Enums
-----

.. autoclass:: ChannelType
   :members:

.. autoclass:: ChannelID
   :members:
   :member-order: bysource


================================================
FILE: docs/reference/channel/display-group.rst
================================================
DisplayGroup
============

.. currentmodule:: pyflp.channel

.. autoclass:: DisplayGroup
   :members:

.. autoclass:: DisplayGroupID
   :members:
   :member-order: bysource


================================================
FILE: docs/reference/channel/index.rst
================================================
\ :material-sharp:`dns;1.2em;sd-pb-1` Channel Rack
==================================================

.. toctree::
   :maxdepth: 2
   :titlesonly:
   :caption: Contents:
   :glob:

   *

.. module:: pyflp.channel
.. autoclass:: ChannelRack
   :members:

.. autoclass:: RackID
   :members:
   :member-order: bysource

.. autoexception:: ChannelNotFound


================================================
FILE: docs/reference/channel/instrument.rst
================================================
Instrument
==========

.. currentmodule:: pyflp.channel

.. autoclass:: Instrument
   :show-inheritance:
   :members:
   :inherited-members: Channel


================================================
FILE: docs/reference/channel/layer.rst
================================================
\ :fas:`layer-group` Layer
==========================

.. currentmodule:: pyflp.channel

.. autoclass:: Layer
   :show-inheritance:
   :members:


================================================
FILE: docs/reference/channel/sampler.rst
================================================
\ :material-sharp:`audio_file;1.2em;sd-pb-1` Sampler
====================================================

.. currentmodule:: pyflp.channel

.. autoclass:: Sampler
   :show-inheritance:
   :members:
   :inherited-members: Channel

.. autoclass:: Content
   :members:

.. autoclass:: Envelope
   :members:

.. autoclass:: Filter
   :members:

.. autoclass:: FX
   :members:

.. autoclass:: Playback
   :members:

.. autoclass:: Reverb
   :members:

.. autoclass:: SamplerLFO
   :members:

.. autoclass:: TimeStretching
   :members:

Enums
-----

.. autoclass:: DeclickMode
   :members:

.. autoclass:: LFOShape
   :members:

.. autoclass:: ReverbType
   :members:

.. grid::

   .. grid-item::

      .. autoclass:: StretchMode
         :members:

   .. grid-item::
      :child-align: center
      :columns: auto

      .. image:: /img/channel/stretch-mode.png


================================================
FILE: docs/reference/channel/shared.rst
================================================
Shared
======

.. currentmodule:: pyflp.channel

These implement functionality used by :class:`Channel` or its subclasses.

.. autoclass:: Arp
   :members:

.. autoclass:: Delay
   :members:

.. autoclass:: Keyboard
   :members:

.. autoclass:: LevelAdjusts
   :members:

.. autoclass:: Polyphony
   :members:

.. autoclass:: Time
   :members:

.. autoclass:: Tracking
   :members:

Enums
-----

.. autoclass:: ArpDirection
   :members:


================================================
FILE: docs/reference/controllers.rst
================================================
🎛 Controllers
=============

.. module:: pyflp.controller
.. autoclass:: RemoteController
   :members:

Enums
-----

.. autoclass:: ControllerID
   :members:
   :member-order: bysource


================================================
FILE: docs/reference/events.rst
================================================
\ :fas:`ellipsis` Events
========================

    This section is intended for those who want to delve into PyFLP's low-level
    API or understand how internally events are ordered. A good understanding
    of FL Studio's GUI is assumed.

When to use the low level API?
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

If PyFLP fails to parse a particular model or you want to dive deep into the
true / raw / real (whatever you want to call it) representation of an FLP.

.. seealso:: :ref:`Binary layout <architecture-event>` of an event

Structure
---------

    Very early versions of FL Studio were literally a **dump** of the changes
    taking place in FL's GUI. Say for example, you create a channel and *then*
    add notes to some pattern; the events for those would be dumped in the same
    order.

    Hopefully its not the same now, but some of those characteristics are still
    visible.

.. caution::

    DO NOT use the following section as a definitive / complete source of
    information for adding / removing your own events. While *most likely* your
    events will get parsed correctly, there's always a chance of corrupting
    your FLPs.

This is *roughly* the order of the events (as of latest FL Studio):

1. Project-wide / metadata
2. Display groups / channel filters
3. Initialised controls
4. Pattern notes / controllers
5. MIDI remote controllers
6. Internal remote controllers / automations
7. 1st channel
8. Pattern metadata
9. Remaining channels
10. Arrangements:

    a. Index, name
    b. Playlist items
    c. Time markers
    d. Tracks:

        I. All data except name
        II. Name

11. Mixer:

    a. Inserts:

        A list of events in the order:

        I. Color, name, icon and flags
        II. Effect slots
        III. Post EQ, input/output, routing

    b. Remaining insert data
12. Channel rack height

Channel
^^^^^^^

There are currently 5 types of channels (specified in :class:`ChannelType`).
Although some of them don't use certain events, FL Studio dumps the same
event tree for any type of channel. For e.g. a :class:`Layer` channel will have
all the events a :class:`Sampler` channel has, irrespective of whether the
events have any meaning in that context. Certain channels have extra events.

=== =================================== ================================================
#   Event ID                            Model / property
=== =================================== ================================================
1   :attr:`ChannelID.New`               :attr:`Channel.iid`
2   :attr:`ChannelID.Type`              :class:`Channel` subclasses
3   :attr:`PluginID.InternalName`       :attr:`Channel.internal_name`
4   :attr:`PluginID.Wrapper`            :attr:`Instrument.plugin`
5   :attr:`PluginID.Name`               :attr:`Channel.name`
6   :attr:`PluginID.Icon`               :attr:`Channel.icon`
7   :attr:`PluginID.Color`              :attr:`Channel.color`
8   :attr:`PluginID.Data`               :attr:`Instrument.plugin`
9   :attr:`ChannelID.IsEnabled`         :attr:`Channel.enabled`
10  :attr:`ChannelID.Delay`             :attr:`_SamplerInstrument.delay` [#1]_
11  :attr:`ChannelID.DelayModXY`        :attr:`_SamplerInstrument.delay` [#1]_
12  :attr:`ChannelID.Reverb`            :attr:`Sampler.fx.reverb`
13  :attr:`ChannelID.TimeShift`         :attr:`_SamplerInstrument.time.shift` [#1]
14  :attr:`ChannelID.Swing`             :attr:`_SamplerInstrument.time.swing` [#1]_
15  :attr:`ChannelID.FreqTilt`          :attr:`Sampler.fx.freq_tilt`
16  :attr:`ChannelID.Pogo`              :attr:`Sampler.fx.pogo`
17  :attr:`ChannelID.Cutoff`            :attr:`Sampler.fx.cutoff`
18  :attr:`ChannelID.Resonance`         :attr:`Sampler.fx.reso`
19  :attr:`ChannelID.Preamp`            :attr:`Sampler.fx.boost`
20  :attr:`ChannelID.FadeOut`           :attr:`Sampler.fx.fade_out`
21  :attr:`ChannelID.FadeIn`            :attr:`Sampler.fx.fade_in`
22  :attr:`ChannelID.StereoDelay`       :attr:`Sampler.fx.stereo_delay`
23  :attr:`ChannelID.RingMod`           :attr:`Sampler.fx.ringmod`
24  :attr:`ChannelID.FXFlags`           Quite a few, refer code.
25  :attr:`ChannelID.RoutedTo`          :attr:`_SamplerInstrument.insert` [#1]_
26  :attr:`ChannelID.Levels`            :attr:`Sampler.filter` + few more
27  :attr:`ChannelID.LevelAdjusts`      :attr:`_SamplerInstrument.level_adjusts` [#1]_
28  :attr:`ChannelID.Polyphony`         :attr:`_SamplerInstrument.polyphony` [#1]_
29  :attr:`ChannelID.Parameters`        A lot; spread across many models.
30  :attr:`ChannelID.CutGroup`          :attr:`_SamplerInstrument.cut_group` [#1]_
31  :attr:`ChannelID.LayerFlags`        :attr:`Layer.random`, :attr:`Layer.crossfade`
32  :attr:`ChannelID.GroupNum`          :attr:`Channel.group`
33* :attr:`ChannelID.Automation`        :class:`Automation`
34  :attr:`ChannelID.IsLocked`          :attr:`Channel.locked`
35  :attr:`ChannelID.Tracking` * 2      :attr:`_SamplerInstrument.tracking` [#1]_
37  :attr:`ChannelID.EnvelopeLFO` * 5   :attr:`Sampler.envelopes`, :attr:`Sampler.lfos`
42  :attr:`ChannelID.SamplerFlags`      Certain :class:`Sampler` properties.
43  :attr:`ChannelID.PingPongLoop`      :attr:`Sampler.playback.ping_pong_loop`
44* :attr:`ChannelID.SamplePath`        :attr:`Sampler.sample_path` [#2]_
=== =================================== ================================================

.. [#1] :class:`Sampler` & :class:`Instrument` base off of :class:`_SamplerInstrument`.
.. [#2] Optional event for :class:`Sampler` only.

Pattern
^^^^^^^

:class:`Pattern` events are serialised at 2 different places inside an FLP.
The first section contains the notes and controllers held by a pattern if any.

= ============================= ===========================
# Event ID                      Property
= ============================= ===========================
1 :attr:`PatternID.New`         :attr:`Pattern.iid`
2 :attr:`PatternID.Controllers` :attr:`Pattern.controllers`
3 :attr:`PatternID.Notes`       :attr:`Pattern.notes`
= ============================= ===========================

The next section contains colour, icon, timemarkers and any new events get
added here. Some events aren't listed because their order is not confirmed yet.

= ============================= ======================
# Event ID                      Property
= ============================= ======================
1 :attr:`PatternID.New` [#3]_   :attr:`Pattern.iid`
2 :attr:`PatternID.Name`        :attr:`Pattern.name`
3 :attr:`PatternID.Color`       :attr:`Pattern.color`
4 157 [#3]_                     N.A.
5 158 [#3]_                     N.A
6 164 [#3]_                     N.A.
= ============================= ======================

.. [#3] Acts as an identifier here.
.. [#4] Unknown events; complete list `here <https://github.com/demberto/PyFLP/discussions/34>`_.

VST plugin parsing
^^^^^^^^^^^^^^^^^^

Implemented in :class:`VSTPluginEvent`, this is arguably the hardest event to
parse *cleanly*. If you are familiar with PyFLP's internals, you might be
surprised to know that this event has events *inside events*. Why a struct
wasn't usable is beyond me.


================================================
FILE: docs/reference/exceptions.rst
================================================
🛑 Exceptions
==============

.. automodule:: pyflp.exceptions
   :members:
   :show-inheritance:
   :undoc-members:


================================================
FILE: docs/reference/mixer/index.rst
================================================
\ :material-sharp:`settings_input_component;1.2em;sd-pb-1` Mixer
================================================================

.. module:: pyflp.mixer

.. toctree::
   :maxdepth: 2
   :titlesonly:
   :caption: Contents:
   :glob:

   *

.. autoclass:: Mixer
   :members:

Enums
-----

.. autoclass:: MixerID
   :members:
   :member-order: bysource


================================================
FILE: docs/reference/mixer/insert.rst
================================================
\ :fas:`sliders` Insert
=======================

.. currentmodule:: pyflp.mixer

.. autoclass:: Insert
   :members:

.. autoclass:: InsertEQ
   :members:

.. autoclass:: InsertEQBand
   :members:

Enums
-----

.. grid:: auto

   .. grid-item::

      .. autoclass:: InsertDock
         :members:

   .. grid-item::
      :child-align: center

      .. image:: /img/mixer/insert/dock.png

.. autoclass:: InsertID
   :members:
   :member-order: bysource


================================================
FILE: docs/reference/mixer/slot.rst
================================================
\ :fas:`folder-tree` Slot
=========================

.. currentmodule:: pyflp.mixer

.. autoclass:: Slot
   :members:

Enums
-----

.. autoclass:: SlotID
   :members:
   :member-order: bysource


================================================
FILE: docs/reference/patterns/index.rst
================================================
🎹 Patterns
============

.. module:: pyflp.pattern

.. toctree::
   :maxdepth: 2
   :titlesonly:
   :caption: Contents:
   :glob:

   *

.. autoclass:: Patterns
   :members:

Enums
-----

.. autoclass:: PatternsID
   :members:
   :member-order: bysource


================================================
FILE: docs/reference/patterns/pattern.rst
================================================
Pattern
=======

.. currentmodule:: pyflp.pattern

.. autoclass:: Pattern
   :members:

.. autoclass:: Controller
   :members:

.. autoclass:: Note
   :members:

Enums
-----

.. autoclass:: PatternID
   :members:
   :member-order: bysource


================================================
FILE: docs/reference/plugins/effects.rst
================================================
Effects
=======

.. currentmodule:: pyflp.plugin

.. autoclass:: FruityBalance
   :members:

.. autoclass:: FruityBloodOverdrive
   :members:

.. autoclass:: FruityCenter
   :members:

.. autoclass:: FruityFastDist
   :members:

.. autoclass:: FruityNotebook2
   :members:

.. autoclass:: FruitySend
   :members:

.. autoclass:: FruitySoftClipper
   :members:

.. autoclass:: FruityStereoEnhancer
   :members:

.. autoclass:: Soundgoodizer
   :members:


================================================
FILE: docs/reference/plugins/generators.rst
================================================
Generators
==========

.. currentmodule:: pyflp.plugin

.. autoclass:: BooBass
   :members:


================================================
FILE: docs/reference/plugins/index.rst
================================================
\ :material-sharp:`extension;1.2em;sd-pb-1` Plugins
===================================================

.. module:: pyflp.plugin

.. toctree::
   :maxdepth: 2
   :titlesonly:
   :caption: Contents:
   :glob:

   *

.. autoclass:: _PluginBase
   :members:

.. autoclass:: PluginIOInfo
   :members:

Enums
-----

.. autoclass:: WrapperPage
   :members:

.. autoclass:: PluginID
   :members:
   :member-order: bysource


================================================
FILE: docs/reference/plugins/vst.rst
================================================
VST
===

.. currentmodule:: pyflp.plugin

.. autoclass:: VSTPlugin
   :members:

   .. tab-set::

      .. tab-item:: Settings

         .. image:: /img/plugin/wrapper/settings.png

         .. autoclass:: pyflp.plugin::VSTPlugin._AutomationOptions
            :members:
         .. autoclass:: pyflp.plugin::VSTPlugin._MIDIOptions
            :members:
         .. autoclass:: pyflp.plugin::VSTPlugin._UIOptions
            :members:

      .. tab-item:: Processing

         .. image:: /img/plugin/wrapper/processing.png

         .. autoclass:: pyflp.plugin::VSTPlugin._ProcessingOptions
            :members:

      .. tab-item:: Troubleshooting

         .. image:: /img/plugin/wrapper/troubleshooting.png

         .. autoclass:: pyflp.plugin::VSTPlugin._CompatibilityOptions
            :members:


================================================
FILE: docs/reference/project.rst
================================================
\ :fas:`file-waveform` Project
==============================

.. module:: pyflp.project

.. autoclass:: Project
   :members:

   .. dropdown:: Information page
      :open:

      .. grid::

         .. grid-item::
            :columns: auto

            * :attr:`Project.artists`
            * :attr:`Project.created_on`
            * :attr:`Project.comments`
            * :attr:`Project.genre`
            * :attr:`Project.show_info`
            * :attr:`Project.url`
            * :attr:`Project.time_spent`

         .. grid-item::
            :columns: 12 8 8 8
            :margin: auto

            .. image:: /img/project/info.png

   .. dropdown:: Settings page
      :open:

      .. grid::

         .. grid-item::

            * :attr:`Project.data_path`
            * :attr:`Project.pan_law`
            * :attr:`Project.ppq`
            * :attr:`Arrangements.time_signature <pyflp.arrangement.Arrangements.time_signature>`
            * :attr:`Patterns.play_cut_notes <pyflp.pattern.Patterns.play_cut_notes>`

         .. grid-item::

            .. image:: /img/project/settings.png
               :align: right

Enums
-----

.. autoclass:: FileFormat
   :members:
   :member-order: bysource

.. autoclass:: PanLaw
   :members:
   :member-order: bysource

.. autoclass:: ProjectID
   :members:
   :member-order: bysource


================================================
FILE: docs/reference/timemarkers.rst
================================================
\ :fas:`timeline` Timemarkers
=============================

.. module:: pyflp.timemarker

.. autoclass:: TimeMarker
   :members:

Enums
-----

.. grid::

   .. grid-item::

      .. autoclass:: TimeMarkerType
         :members:

   .. grid-item::
      :child-align: center
      :columns: auto

      .. image:: /img/arrangement/timemarker/action.png

.. autoclass:: TimeMarkerID
   :members:
   :member-order: bysource


================================================
FILE: docs/reference.rst
================================================
🧾 Reference
=============

.. toctree::
   :maxdepth: 2
   :titlesonly:
   :caption: Contents:
   :glob:

   reference/*/index
   reference/*

:material-outlined:`api` API
----------------------------

PyFLP provides a low-level events-based API and a high-level API. Generally,
you should only need the high level API though.

.. module:: pyflp
.. autofunction:: parse
.. autofunction:: save


================================================
FILE: docs/requirements.txt
================================================
furo==2023.5.20
m2r2==0.3.2  # https://github.com/CrossNox/m2r2/issues/55
sphinx==6.1.3
sphinx-copybutton==0.5.2
sphinx-design==0.4.1
sphinx-hoverxref
sphinx-toolbox==3.4.0
sphinxcontrib-spelling==8.0.0
sphinxcontrib-svgbob==0.2.1


================================================
FILE: pyflp/__init__.py
================================================
# PyFLP - An FL Studio project file (.flp) parser
# Copyright (C) 2022 demberto
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version. This program is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
# Public License for more details. You should have received a copy of the
# GNU General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.

"""
PyFLP - FL Studio project file parser
=====================================

Load a project file:

    >>> import pyflp
    >>> project = pyflp.parse("/path/to/parse.flp")

Save the project:

    >>> pyflp.save(project, "/path/to/save.flp")

Full docs are available at https://pyflp.rtfd.io.
"""  # noqa

from __future__ import annotations

import io
import os
import pathlib
import struct
import sys

import construct as c

from pyflp._events import (
    DATA,
    DWORD,
    NEW_TEXT_IDS,
    TEXT,
    WORD,
    AnyEvent,
    AsciiEvent,
    EventEnum,
    EventTree,
    IndexedEvent,
    U8Event,
    U16Event,
    U32Event,
    UnicodeEvent,
    UnknownDataEvent,
)
from pyflp.exceptions import HeaderCorrupted, VersionNotDetected
from pyflp.plugin import PluginID, get_event_by_internal_name
from pyflp.project import VALID_PPQS, FileFormat, Project, ProjectID

__all__ = ["parse", "save"]

FLP_HEADER = struct.Struct("4sIh2H")

if sys.version_info < (3, 11):  # https://github.com/Bobronium/fastenum/issues/2
    import fastenum

    fastenum.enable()  # 33% faster parse()


def parse(file: pathlib.Path | str) -> Project:
    """Parses an FL Studio project file and returns a parsed :class:`Project`.

    Args:
        file: Path to the FLP.

    Raises:
        HeaderCorrupted: When an invalid value is found in the file header.
        VersionNotDetected: A correct string type couldn't be determined.
    """
    with open(file, "rb") as flp:
        stream = io.BytesIO(flp.read())

    events: list[AnyEvent] = []
    header = stream.read(FLP_HEADER.size)

    try:
        hdr_magic, hdr_size, fmt, channel_count, ppq = FLP_HEADER.unpack(header)
    except struct.error as exc:
        raise HeaderCorrupted("Couldn't read the header entirely") from exc

    if hdr_magic != b"FLhd":
        raise HeaderCorrupted("Unexpected header chunk magic; expected 'FLhd'")

    if hdr_size != 6:
        raise HeaderCorrupted("Unexpected header chunk size; expected 6")

    try:
        file_format = FileFormat(fmt)
    except ValueError as exc:
        raise HeaderCorrupted("Unsupported project file format") from exc

    if ppq not in VALID_PPQS:
        raise HeaderCorrupted("Invalid PPQ")

    if stream.read(4) != b"FLdt":
        raise HeaderCorrupted("Unexpected data chunk magic; expected 'FLdt'")

    events_size = int.from_bytes(stream.read(4), "little")
    if not events_size:  # pragma: no cover
        raise HeaderCorrupted("Data chunk size couldn't be read")

    stream.seek(0, os.SEEK_END)
    file_size = stream.tell()
    if file_size != events_size + 22:
        raise HeaderCorrupted("Data chunk size corrupted")

    plug_name = None
    str_type: type[AsciiEvent] | type[UnicodeEvent] | None = None
    stream.seek(22)  # Back to start of events
    while stream.tell() < file_size:
        event_type: type[AnyEvent] | None = None
        id = EventEnum(int.from_bytes(stream.read(1), "little"))

        if id < WORD:
            value = stream.read(1)
        elif id < DWORD:
            value = stream.read(2)
        elif id < TEXT:
            value = stream.read(4)
        else:
            size = c.VarInt.parse_stream(stream)
            value = stream.read(size)

        if id == ProjectID.FLVersion:
            parts = value.decode("ascii").rstrip("\0").split(".")
            if [int(part) for part in parts][0:2] >= [11, 5]:
                str_type = UnicodeEvent
            else:
                str_type = AsciiEvent

        for enum_ in EventEnum.__subclasses__():
            if id in enum_:
                event_type = getattr(enum_(id), "type")
                break

        if event_type is None:
            if id < WORD:
                event_type = U8Event
            elif id < DWORD:
                event_type = U16Event
            elif id < TEXT:
                event_type = U32Event
            elif id < DATA or id.value in NEW_TEXT_IDS:
                if str_type is None:  # pragma: no cover
                    raise VersionNotDetected  # ! This should never happen
                event_type = str_type

                if id == PluginID.InternalName:
                    plug_name = event_type(id, value).value
            elif id == PluginID.Data and plug_name is not None:
                event_type = get_event_by_internal_name(plug_name)
            else:
                event_type = UnknownDataEvent

        events.append(event_type(id, value))

    return Project(
        EventTree(init=(IndexedEvent(r, e) for r, e in enumerate(events))),
        channel_count=channel_count,
        format=file_format,
        ppq=ppq,
    )


def save(project: Project, file: pathlib.Path | str) -> None:
    """Save a parsed project back into a file.

    Caution:
        Always have a backup ready, just in case 😉

    Args:
        project: The object returned by :meth:`parse`.
        file: The file in which the contents of :attr:`project` are serialised back.
    """
    buf = bytearray()
    num_channels = len(project.channels)
    header = FLP_HEADER.pack(b"FLhd", 6, project.format, num_channels, project.ppq)
    buf.extend(header)
    buf.extend(b"FLdt" + (b"\0" * 4))
    total_size = 0
    for event in project.events:
        raw = bytes(event)
        total_size += len(raw)
        buf.extend(raw)
    buf[18:22] = total_size.to_bytes(4, "little")

    with open(file, "wb") as fp:
        fp.write(buf)


================================================
FILE: pyflp/_adapters.py
================================================
# PyFLP - An FL Studio project file (.flp) parser
# Copyright (C) 2022 demberto
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version. This program is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
# Public License for more details. You should have received a copy of the
# GNU General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.

from __future__ import annotations

import math
import warnings
from typing import Any, List, Tuple

import construct as c
import construct_typed as ct
from typing_extensions import TypeAlias

from pyflp.types import ET, MusicalTime, T, U

SimpleAdapter: TypeAlias = ct.Adapter[T, T, U, U]
"""Duplicates type parameters for `construct.Adapter`."""

FourByteBool: c.ExprAdapter[int, int, bool, int] = c.ExprAdapter(
    c.Int32ul, lambda obj_, *_: bool(obj_), lambda obj_, *_: int(obj_)  # type: ignore
)


class List2Tuple(SimpleAdapter[Any, Tuple[int, int]]):
    def _decode(self, obj: c.ListContainer[int], *_: Any) -> tuple[int, int]:
        _1, _2 = tuple(obj)
        return _1, _2

    def _encode(self, obj: tuple[int, int], *_: Any) -> c.ListContainer[int]:
        return c.ListContainer([*obj])


class LinearMusical(SimpleAdapter[int, MusicalTime]):
    def _encode(self, obj: MusicalTime, *_: Any) -> int:
        if obj.ticks % 5:
            warnings.warn("Ticks must be a multiple of 5", UserWarning)

        return (obj.bars * 768) + (obj.beats * 48) + int(obj.ticks * 0.2)

    def _decode(self, obj: int, *_: Any) -> MusicalTime:
        bars, remainder = divmod(obj, 768)
        beats, remainder = divmod(remainder, 48)
        return MusicalTime(bars, beats, ticks=remainder * 5)


class Log2(SimpleAdapter[int, float]):
    def __init__(self, subcon: Any, factor: int) -> None:
        super().__init__(subcon)  # type: ignore[call-arg]
        self.factor = factor

    def _encode(self, obj: float, *_: Any) -> int:
        return int(self.factor * math.log2(obj))

    def _decode(self, obj: int, *_: Any) -> float:
        return 2 ** (obj / self.factor)


# Thanks to @algmyr from Python Discord server for finding out the formulae used
# ! See https://github.com/construct/construct/issues/999
class LogNormal(SimpleAdapter[List[int], float]):
    def __init__(self, subcon: Any, bound: tuple[int, int]) -> None:
        super().__init__(subcon)  # type: ignore[call-arg]
        self.lo, self.hi = bound

    def _encode(self, obj: float, *_: Any) -> list[int]:
        """Clamps the integer representation of ``obj`` and returns it."""
        if not 0.0 <= obj <= 1.0:
            raise ValueError(f"Expected a value between 0.0 to 1.0; got {obj}")

        if not obj:  # log2(0.0) --> -inf ==> 0
            return [0, 0]

        return [min(max(self.lo, int(2**12 * (math.log2(obj) + 15))), self.hi), 63]

    def _decode(self, obj: list[int], *_: Any) -> float:
        """Returns a float representation of ``obj[0]`` between 0.0 to 1.0."""
        if not obj[0]:
            return 0.0

        if obj[1] != 63:
            raise ValueError(f"Not a LogNormal, 2nd int must be 63; not {obj[1]}")

        return max(min(1.0, 2 ** (obj[0] / 2**12) / 2**15), 0.0)


class StdEnum(SimpleAdapter[int, ET]):
    def _encode(self, obj: ET, *_: Any) -> int:
        return obj.value

    def _decode(self, obj: int, *_: Any) -> ET:
        return self.__orig_class__.__args__[0](obj)  # type: ignore


================================================
FILE: pyflp/_descriptors.py
================================================
# PyFLP - An FL Studio project file (.flp) parser
# Copyright (C) 2022 demberto
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version. This program is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
# Public License for more details. You should have received a copy of the
# GNU General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.

"""Contains the descriptor and adaptor classes used by models and events."""

from __future__ import annotations

import abc
import enum
from typing import Any, Protocol, overload, runtime_checkable

from typing_extensions import Self, final

from pyflp._events import AnyEvent, EventEnum, StructEventBase
from pyflp._models import VE, EMT_co, EventModel, ItemModel, ModelBase
from pyflp.exceptions import PropertyCannotBeSet
from pyflp.types import T, T_co


@runtime_checkable
class ROProperty(Protocol[T_co]):
    """Protocol for a read-only descriptor."""

    def __get__(self, ins: Any, owner: Any = None) -> T_co | Self | None:
        ...


@runtime_checkable
class RWProperty(ROProperty[T], Protocol):
    """Protocol for a read-write descriptor."""

    def __set__(self, ins: Any, value: T) -> None:
        ...


class NamedPropMixin:
    def __init__(self, prop: str | None = None) -> None:
        self._prop = prop or ""

    def __set_name__(self, _: Any, name: str) -> None:
        if not self._prop:
            self._prop = name


class PropBase(abc.ABC, RWProperty[T]):
    def __init__(self, *ids: EventEnum, default: T | None = None, readonly: bool = False):
        self._ids = ids
        self._default = default
        self._readonly = readonly

    @overload
    def _get_event(self, ins: ItemModel[VE]) -> ItemModel[VE]:
        ...

    @overload
    def _get_event(self, ins: EventModel) -> AnyEvent | None:
        ...

    def _get_event(self, ins: ItemModel[VE] | EventModel):
        if isinstance(ins, ItemModel):
            return ins

        if not self._ids:
            if len(ins.events) > 1:  # Prevent ambiguous situations
                raise LookupError("Event ID not specified")

            return tuple(ins.events)[0]

        for id in self._ids:
            if id in ins.events:
                return ins.events.first(id)

    @property
    def default(self) -> T | None:  # Configure version based defaults here
        return self._default

    @abc.abstractmethod
    def _get(self, ev_or_ins: Any) -> T | None:
        ...

    @abc.abstractmethod
    def _set(self, ev_or_ins: Any, value: T) -> None:
        ...

    @final
    def __get__(self, ins: Any, owner: Any = None) -> T | Self | None:
        if ins is None:
            return self

        if owner is None:
            return NotImplemented

        event: Any = self._get_event(ins)
        if event is not None:
            return self._get(event)

        return self.default

    @final
    def __set__(self, ins: Any, value: T) -> None:
        if self._readonly:
            raise PropertyCannotBeSet(*self._ids)

        event: Any = self._get_event(ins)
        if event is not None:
            self._set(event, value)
        else:
            raise PropertyCannotBeSet(*self._ids)


class FlagProp(PropBase[bool]):
    """Properties derived from enum flags."""

    def __init__(
        self,
        flag: enum.IntFlag,
        *ids: EventEnum,
        prop: str = "flags",
        inverted: bool = False,
        default: bool | None = None,
    ) -> None:
        """
        Args:
            flag: The flag which is to be checked for.
            id: Event ID (required for MultiEventModel).
            prop: The dict key which contains the flags in a `Struct`.
            inverted: If this is true, property getter and setters
                      invert the value to be set / returned.
        """
        self._flag = flag
        self._flag_type = type(flag)
        self._prop = prop
        self._inverted = inverted
        super().__init__(*ids, default=default)

    def _get(self, ev_or_ins: Any) -> bool | None:
        if isinstance(ev_or_ins, (ItemModel, StructEventBase)):
            flags = ev_or_ins[self._prop]
        else:
            flags = ev_or_ins.value  # type: ignore

        if flags is not None:
            retbool = self._flag in self._flag_type(flags)
            return not retbool if self._inverted else retbool

    def _set(self, ev_or_ins: Any, value: bool) -> None:
        if self._inverted:
            value = not value

        if isinstance(ev_or_ins, (ItemModel, StructEventBase)):
            if value:
                ev_or_ins[self._prop] |= self._flag
            else:
                ev_or_ins[self._prop] &= ~self._flag
        else:
            if value:
                ev_or_ins.value |= self._flag  # type: ignore
            else:
                ev_or_ins.value &= ~self._flag  # type: ignore


class KWProp(NamedPropMixin, RWProperty[T]):
    """Properties derived from non-local event values.

    These values are passed to the class constructor as keyword arguments.
    """

    def __get__(self, ins: ModelBase | None, owner: Any = None) -> T | Self:
        if ins is None:
            return self

        if owner is None:
            return NotImplemented
        return ins._kw[self._prop]

    def __set__(self, ins: ModelBase, value: T) -> None:
        if self._prop not in ins._kw:
            raise KeyError(self._prop)
        ins._kw[self._prop] = value


class EventProp(PropBase[T]):
    """Properties bound directly to one of fixed size or string events."""

    def _get(self, ev_or_ins: AnyEvent) -> T | None:
        return ev_or_ins.value

    def _set(self, ev_or_ins: AnyEvent, value: T) -> None:
        ev_or_ins.value = value


class NestedProp(ROProperty[EMT_co]):
    def __init__(self, type: type[EMT_co], *ids: EventEnum) -> None:
        self._ids = ids
        self._type = type

    def __get__(self, ins: EventModel, owner: Any = None) -> EMT_co:
        if owner is None:
            return NotImplemented

        return self._type(ins.events.subtree(lambda e: e.id in self._ids))


class StructProp(PropBase[T], NamedPropMixin):
    """Properties obtained from a :class:`construct.Struct`."""

    def __init__(self, *ids: EventEnum, prop: str | None = None, **kwds: Any) -> None:
        super().__init__(*ids, **kwds)
        NamedPropMixin.__init__(self, prop)

    def _get(self, ev_or_ins: ItemModel[Any]) -> T | None:
        return ev_or_ins[self._prop]

    def _set(self, ev_or_ins: ItemModel[Any], value: T) -> None:
        ev_or_ins[self._prop] = value


================================================
FILE: pyflp/_events.py
================================================
# PyFLP - An FL Studio project file (.flp) parser
# Copyright (C) 2022 demberto
#
# This program is free software/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version. This program is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
# Public License for more details. You should have received a copy of the
# GNU General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.

"""Contains implementations for various types of event data and its container.

These types serve as the backbone for model creation and simplify marshalling
and unmarshalling.
"""

from __future__ import annotations

import abc
import enum
import warnings
from collections.abc import Callable, Iterable, Iterator, Sequence
from dataclasses import dataclass, field
from itertools import zip_longest
from typing import TYPE_CHECKING, Any, ClassVar, Final, Generic, Tuple, cast

import construct as c
from sortedcontainers import SortedList
from typing_extensions import Concatenate, TypeAlias

from pyflp.exceptions import (
    EventIDOutOfRange,
    InvalidEventChunkSize,
    PropertyCannotBeSet,
)
from pyflp.types import RGBA, P, T, AnyContainer, AnyListContainer, AnyList, AnyDict

BYTE: Final = 0
WORD: Final = 64
DWORD: Final = 128
TEXT: Final = 192
DATA: Final = 208
NEW_TEXT_IDS: Final = (
    TEXT + 49,  # ArrangementID.Name
    TEXT + 39,  # DisplayGroupID.Name
    TEXT + 47,  # TrackID.Name
)


class _EventEnumMeta(enum.EnumMeta):
    def __contains__(self, obj: object) -> bool:
        """Whether ``obj`` is one of the integer values of enum members.

        Args:
            obj: Can be an ``int`` or an ``EventEnum``.
        """
        return obj in tuple(self)


class EventEnum(int, enum.Enum, metaclass=_EventEnumMeta):
    """IDs used by events.

    Event values are stored as a tuple of event ID and its designated type.
    The types are used to serialise/deserialise events by the parser.

    All event names prefixed with an underscore (_) are deprecated w.r.t to
    the latest version of FL Studio, *to the best of my knowledge*.
    """

    def __new__(cls, id: int, type: type[AnyEvent] | None = None):
        obj = int.__new__(cls, id)
        obj._value_ = id
        setattr(obj, "type", type)
        return obj

    # This allows EventBase.id to actually use EventEnum for representation and
    # not just equality checks. It will be much simpler to debug problematic
    # events, if the name of the ID is directly visible.
    @classmethod
    def _missing_(cls, value: object) -> EventEnum | None:
        """Allows unknown IDs in the range of 0-255."""
        if isinstance(value, int) and 0 <= value <= 255:
            # First check in existing subclasses
            for sc in cls.__subclasses__():
                if value in sc:
                    return sc(value)

            # Else create a new pseudo member
            pseudo_member = cls._value2member_map_.get(value, None)
            if pseudo_member is None:
                new_member = int.__new__(cls, value)
                new_member._name_ = str(value)
                new_member._value_ = value
                pseudo_member = cls._value2member_map_.setdefault(value, new_member)
            return cast(EventEnum, pseudo_member)
        # Raises ValueError in Enum.__new__


class EventBase(Generic[T]):
    """Generic ABC representing an event."""

    STRUCT: c.Construct[T, T]
    ALLOWED_IDS: ClassVar[Sequence[int]] = []

    def __init__(self, id: EventEnum, data: bytes, **kwds: Any) -> None:
        if self.ALLOWED_IDS and id not in self.ALLOWED_IDS:
            raise EventIDOutOfRange(id, *self.ALLOWED_IDS)

        if id < TEXT:
            if id < WORD:
                expected_size = 1
            elif id < DWORD:
                expected_size = 2
            else:
                expected_size = 4

            if len(data) != expected_size:
                raise InvalidEventChunkSize(expected_size, len(data))

        self.id = EventEnum(id)
        self._kwds = kwds
        self.value = self.STRUCT.parse(data, **self._kwds)

    def __eq__(self, o: object) -> bool:
        if not isinstance(o, EventBase):
            raise TypeError(f"Cannot find equality of an {type(o)} and {type(self)!r}")
        return self.id == o.id and self.value == cast(EventBase[T], o).value

    def __ne__(self, o: object) -> bool:
        if not isinstance(o, EventBase):
            raise TypeError(f"Cannot find inequality of a {type(o)} and {type(self)!r}")
        return self.id != o.id or self.value != cast(EventBase[T], o).value

    def __bytes__(self) -> bytes:
        id = c.Byte.build(self.id)
        data = self.STRUCT.build(self.value, **self._kwds)

        if self.id < TEXT:
            return id + data

        length = c.VarInt.build(len(data))
        return id + length + data

    def __repr__(self) -> str:
        return f"<{type(self)!r}(id={self.id!r}, value={self.value!r})>"

    @property
    def size(self) -> int:
        """Serialised event size (in bytes)."""

        if self.id >= TEXT:
            return len(bytes(self))
        elif self.id >= DWORD:
            return 5
        elif self.id >= WORD:
            return 3
        else:
            return 2


AnyEvent: TypeAlias = EventBase[Any]


class ByteEventBase(EventBase[T]):
    """Base class of events used for storin
Download .txt
gitextract_89mlfebd/

├── .all-contributorsrc
├── .editorconfig
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.yml
│   │   ├── config.yml
│   │   └── feature_request.yml
│   ├── dependabot.yml
│   └── workflows/
│       ├── codeql-analysis.yml
│       ├── publish.yml
│       └── test.yml
├── .gitignore
├── .pre-commit-config.yaml
├── .readthedocs.yaml
├── .vscode/
│   ├── extensions.json
│   ├── settings.json
│   └── tasks.json
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── LICENSE
├── MANIFEST.in
├── README.md
├── docs/
│   ├── Makefile
│   ├── architecture/
│   │   ├── flp-format.rst
│   │   ├── how-it-works.rst
│   │   └── reference.rst
│   ├── architecture.rst
│   ├── changelog.rst
│   ├── conf.py
│   ├── contributing.rst
│   ├── faq.rst
│   ├── features.rst
│   ├── guides/
│   │   ├── plugin.rst
│   │   └── reversing.rst
│   ├── guides.rst
│   ├── handbook.rst
│   ├── helping.rst
│   ├── index.rst
│   ├── limitations.rst
│   ├── make.bat
│   ├── reference/
│   │   ├── arrangement/
│   │   │   ├── arrangement.rst
│   │   │   ├── index.rst
│   │   │   ├── playlist.rst
│   │   │   └── track.rst
│   │   ├── channel/
│   │   │   ├── automation.rst
│   │   │   ├── channel.rst
│   │   │   ├── display-group.rst
│   │   │   ├── index.rst
│   │   │   ├── instrument.rst
│   │   │   ├── layer.rst
│   │   │   ├── sampler.rst
│   │   │   └── shared.rst
│   │   ├── controllers.rst
│   │   ├── events.rst
│   │   ├── exceptions.rst
│   │   ├── mixer/
│   │   │   ├── index.rst
│   │   │   ├── insert.rst
│   │   │   └── slot.rst
│   │   ├── patterns/
│   │   │   ├── index.rst
│   │   │   └── pattern.rst
│   │   ├── plugins/
│   │   │   ├── effects.rst
│   │   │   ├── generators.rst
│   │   │   ├── index.rst
│   │   │   └── vst.rst
│   │   ├── project.rst
│   │   └── timemarkers.rst
│   ├── reference.rst
│   └── requirements.txt
├── pyflp/
│   ├── __init__.py
│   ├── _adapters.py
│   ├── _descriptors.py
│   ├── _events.py
│   ├── _models.py
│   ├── arrangement.py
│   ├── channel.py
│   ├── controller.py
│   ├── exceptions.py
│   ├── mixer.py
│   ├── pattern.py
│   ├── plugin.py
│   ├── project.py
│   ├── py.typed
│   ├── timemarker.py
│   └── types.py
├── pyproject.toml
├── requirements.txt
├── tests/
│   ├── __init__.py
│   ├── assets/
│   │   ├── FL 20.8.4.flp
│   │   ├── channels/
│   │   │   ├── +4800-cents.fst
│   │   │   ├── -4800-cents.fst
│   │   │   ├── 100%-left.fst
│   │   │   ├── 100%-right.fst
│   │   │   ├── arp.fst
│   │   │   ├── automation-lfo.fst
│   │   │   ├── automation-points.fst
│   │   │   ├── colored.fst
│   │   │   ├── cut-groups.fst
│   │   │   ├── delay.fst
│   │   │   ├── disabled.fst
│   │   │   ├── envelope.fst
│   │   │   ├── full-volume.fst
│   │   │   ├── iconified.fst
│   │   │   ├── keyboard.fst
│   │   │   ├── layer-crossfade.fst
│   │   │   ├── layer-random.fst
│   │   │   ├── level-adjusts.fst
│   │   │   ├── lfo.fst
│   │   │   ├── locked.fst
│   │   │   ├── polyphony.fst
│   │   │   ├── routed.fst
│   │   │   ├── sampler-content.fst
│   │   │   ├── sampler-filter.fst
│   │   │   ├── sampler-fx.fst
│   │   │   ├── sampler-path.fst
│   │   │   ├── sampler-playback.fst
│   │   │   ├── sampler-stretching.fst
│   │   │   ├── time.fst
│   │   │   ├── tracking.fst
│   │   │   └── zero-volume.fst
│   │   ├── corrupted/
│   │   │   ├── invalid-data-magic.flp
│   │   │   ├── invalid-event-size.flp
│   │   │   ├── invalid-format.flp
│   │   │   ├── invalid-header-magic.flp
│   │   │   ├── invalid-header-size.flp
│   │   │   └── invalid-ppq.flp
│   │   ├── inserts/
│   │   │   ├── 100%-left.fst
│   │   │   ├── 100%-merged.fst
│   │   │   ├── 100%-right.fst
│   │   │   ├── 100%-separated.fst
│   │   │   ├── 50ms-input-latency.fst
│   │   │   ├── 50ms-track-latency.fst
│   │   │   ├── armed.fst
│   │   │   ├── channels-swapped.fst
│   │   │   ├── colored.fst
│   │   │   ├── disabled.fst
│   │   │   ├── effects-bypassed.fst
│   │   │   ├── iconified.fst
│   │   │   ├── locked.fst
│   │   │   ├── polarity-reversed.fst
│   │   │   ├── post-eq.fst
│   │   │   ├── separator.fst
│   │   │   └── zero-volume.fst
│   │   ├── patterns/
│   │   │   ├── c-major-scale.fsc
│   │   │   ├── c5-1bar.fsc
│   │   │   ├── color-9.fsc
│   │   │   ├── common-group.fsc
│   │   │   ├── empty.fsc
│   │   │   ├── fine-pitch-min-max.fsc
│   │   │   ├── modx-min-max.fsc
│   │   │   ├── mody-min-max.fsc
│   │   │   ├── multi-channel.flp
│   │   │   ├── pan-min-max.fsc
│   │   │   ├── release-min-max.fsc
│   │   │   ├── slide-note.fsc
│   │   │   └── velocity-min-max.fsc
│   │   └── plugins/
│   │       ├── boobass.fst
│   │       ├── fruit-kick.fst
│   │       ├── fruity-balance.fst
│   │       ├── fruity-blood-overdrive.fst
│   │       ├── fruity-center.fst
│   │       ├── fruity-fast-dist.fst
│   │       ├── fruity-send.fst
│   │       ├── fruity-soft-clipper.fst
│   │       ├── fruity-stereo-enhancer.fst
│   │       ├── fruity-wrapper.fst
│   │       ├── plucked.fst
│   │       ├── soundgoodizer.fst
│   │       └── xfer-djmfilter.fst
│   ├── conftest.py
│   ├── test_arrangement.py
│   ├── test_channel.py
│   ├── test_corrupted.py
│   ├── test_events.py
│   ├── test_mixer.py
│   ├── test_models.py
│   ├── test_pattern.py
│   ├── test_plugin.py
│   └── test_project.py
└── tox.ini
Download .txt
SYMBOL INDEX (600 symbols across 26 files)

FILE: docs/conf.py
  function badge_flstudio (line 94) | def badge_flstudio(app, what, name, obj, options, lines):
  function add_annotations (line 125) | def add_annotations(app, what, name, obj, options, signature, return_ann...
  function autodoc_markdown (line 148) | def autodoc_markdown(app, what, name, obj, options, lines):
  function remove_model_signature (line 160) | def remove_model_signature(app, what, name, obj, options, signature, ret...
  function remove_enum_signature (line 169) | def remove_enum_signature(app, what, name, obj, options, signature, retu...
  function include_obsolete_ids (line 175) | def include_obsolete_ids(app, what, name, obj, skip, options):
  function show_model_dunders (line 181) | def show_model_dunders(app, what, name, obj, skip, options):
  function setup (line 187) | def setup(app):

FILE: pyflp/__init__.py
  function parse (line 71) | def parse(file: pathlib.Path | str) -> Project:
  function save (line 176) | def save(project: Project, file: pathlib.Path | str) -> None:

FILE: pyflp/_adapters.py
  class List2Tuple (line 34) | class List2Tuple(SimpleAdapter[Any, Tuple[int, int]]):
    method _decode (line 35) | def _decode(self, obj: c.ListContainer[int], *_: Any) -> tuple[int, int]:
    method _encode (line 39) | def _encode(self, obj: tuple[int, int], *_: Any) -> c.ListContainer[int]:
  class LinearMusical (line 43) | class LinearMusical(SimpleAdapter[int, MusicalTime]):
    method _encode (line 44) | def _encode(self, obj: MusicalTime, *_: Any) -> int:
    method _decode (line 50) | def _decode(self, obj: int, *_: Any) -> MusicalTime:
  class Log2 (line 56) | class Log2(SimpleAdapter[int, float]):
    method __init__ (line 57) | def __init__(self, subcon: Any, factor: int) -> None:
    method _encode (line 61) | def _encode(self, obj: float, *_: Any) -> int:
    method _decode (line 64) | def _decode(self, obj: int, *_: Any) -> float:
  class LogNormal (line 70) | class LogNormal(SimpleAdapter[List[int], float]):
    method __init__ (line 71) | def __init__(self, subcon: Any, bound: tuple[int, int]) -> None:
    method _encode (line 75) | def _encode(self, obj: float, *_: Any) -> list[int]:
    method _decode (line 85) | def _decode(self, obj: list[int], *_: Any) -> float:
  class StdEnum (line 96) | class StdEnum(SimpleAdapter[int, ET]):
    method _encode (line 97) | def _encode(self, obj: ET, *_: Any) -> int:
    method _decode (line 100) | def _decode(self, obj: int, *_: Any) -> ET:

FILE: pyflp/_descriptors.py
  class ROProperty (line 31) | class ROProperty(Protocol[T_co]):
    method __get__ (line 34) | def __get__(self, ins: Any, owner: Any = None) -> T_co | Self | None:
  class RWProperty (line 39) | class RWProperty(ROProperty[T], Protocol):
    method __set__ (line 42) | def __set__(self, ins: Any, value: T) -> None:
  class NamedPropMixin (line 46) | class NamedPropMixin:
    method __init__ (line 47) | def __init__(self, prop: str | None = None) -> None:
    method __set_name__ (line 50) | def __set_name__(self, _: Any, name: str) -> None:
  class PropBase (line 55) | class PropBase(abc.ABC, RWProperty[T]):
    method __init__ (line 56) | def __init__(self, *ids: EventEnum, default: T | None = None, readonly...
    method _get_event (line 62) | def _get_event(self, ins: ItemModel[VE]) -> ItemModel[VE]:
    method _get_event (line 66) | def _get_event(self, ins: EventModel) -> AnyEvent | None:
    method _get_event (line 69) | def _get_event(self, ins: ItemModel[VE] | EventModel):
    method default (line 84) | def default(self) -> T | None:  # Configure version based defaults here
    method _get (line 88) | def _get(self, ev_or_ins: Any) -> T | None:
    method _set (line 92) | def _set(self, ev_or_ins: Any, value: T) -> None:
    method __get__ (line 96) | def __get__(self, ins: Any, owner: Any = None) -> T | Self | None:
    method __set__ (line 110) | def __set__(self, ins: Any, value: T) -> None:
  class FlagProp (line 121) | class FlagProp(PropBase[bool]):
    method __init__ (line 124) | def __init__(
    method _get (line 146) | def _get(self, ev_or_ins: Any) -> bool | None:
    method _set (line 156) | def _set(self, ev_or_ins: Any, value: bool) -> None:
  class KWProp (line 172) | class KWProp(NamedPropMixin, RWProperty[T]):
    method __get__ (line 178) | def __get__(self, ins: ModelBase | None, owner: Any = None) -> T | Self:
    method __set__ (line 186) | def __set__(self, ins: ModelBase, value: T) -> None:
  class EventProp (line 192) | class EventProp(PropBase[T]):
    method _get (line 195) | def _get(self, ev_or_ins: AnyEvent) -> T | None:
    method _set (line 198) | def _set(self, ev_or_ins: AnyEvent, value: T) -> None:
  class NestedProp (line 202) | class NestedProp(ROProperty[EMT_co]):
    method __init__ (line 203) | def __init__(self, type: type[EMT_co], *ids: EventEnum) -> None:
    method __get__ (line 207) | def __get__(self, ins: EventModel, owner: Any = None) -> EMT_co:
  class StructProp (line 214) | class StructProp(PropBase[T], NamedPropMixin):
    method __init__ (line 217) | def __init__(self, *ids: EventEnum, prop: str | None = None, **kwds: A...
    method _get (line 221) | def _get(self, ev_or_ins: ItemModel[Any]) -> T | None:
    method _set (line 224) | def _set(self, ev_or_ins: ItemModel[Any], value: T) -> None:

FILE: pyflp/_events.py
  class _EventEnumMeta (line 53) | class _EventEnumMeta(enum.EnumMeta):
    method __contains__ (line 54) | def __contains__(self, obj: object) -> bool:
  class EventEnum (line 63) | class EventEnum(int, enum.Enum, metaclass=_EventEnumMeta):
    method __new__ (line 73) | def __new__(cls, id: int, type: type[AnyEvent] | None = None):
    method _missing_ (line 83) | def _missing_(cls, value: object) -> EventEnum | None:
  class EventBase (line 102) | class EventBase(Generic[T]):
    method __init__ (line 108) | def __init__(self, id: EventEnum, data: bytes, **kwds: Any) -> None:
    method __eq__ (line 127) | def __eq__(self, o: object) -> bool:
    method __ne__ (line 132) | def __ne__(self, o: object) -> bool:
    method __bytes__ (line 137) | def __bytes__(self) -> bytes:
    method __repr__ (line 147) | def __repr__(self) -> str:
    method size (line 151) | def size(self) -> int:
  class ByteEventBase (line 167) | class ByteEventBase(EventBase[T]):
    method __init__ (line 172) | def __init__(self, id: EventEnum, data: bytes) -> None:
  class BoolEvent (line 185) | class BoolEvent(ByteEventBase[bool]):
  class I8Event (line 191) | class I8Event(ByteEventBase[int]):
  class U8Event (line 197) | class U8Event(ByteEventBase[int]):
  class WordEventBase (line 203) | class WordEventBase(EventBase[int], abc.ABC):
    method __init__ (line 208) | def __init__(self, id: EventEnum, data: bytes) -> None:
  class I16Event (line 221) | class I16Event(WordEventBase):
  class U16Event (line 227) | class U16Event(WordEventBase):
  class DWordEventBase (line 233) | class DWordEventBase(EventBase[T], abc.ABC):
    method __init__ (line 238) | def __init__(self, id: EventEnum, data: bytes) -> None:
  class F32Event (line 251) | class F32Event(DWordEventBase[float]):
  class I32Event (line 257) | class I32Event(DWordEventBase[int]):
  class U32Event (line 263) | class U32Event(DWordEventBase[int]):
  class U16TupleEvent (line 269) | class U16TupleEvent(DWordEventBase[Tuple[int, int]]):
  class ColorEvent (line 279) | class ColorEvent(DWordEventBase[RGBA]):
  class StrEventBase (line 289) | class StrEventBase(EventBase[str]):
    method __init__ (line 294) | def __init__(self, id: EventEnum, data: bytes) -> None:
  class AsciiEvent (line 306) | class AsciiEvent(StrEventBase):
  class UnicodeEvent (line 317) | class UnicodeEvent(StrEventBase):
  class StructEventBase (line 328) | class StructEventBase(EventBase[AnyContainer], AnyDict):
    method __init__ (line 335) | def __init__(self, id: EventEnum, data: bytes) -> None:
    method __setitem__ (line 339) | def __setitem__(self, key: str, value: Any) -> None:
  class ListEventBase (line 349) | class ListEventBase(EventBase[AnyListContainer], AnyList):
    method __init__ (line 360) | def __init__(self, id: EventEnum, data: bytes, **kwds: Any) -> None:
  class UnknownDataEvent (line 381) | class UnknownDataEvent(EventBase[bytes]):
  class IndexedEvent (line 388) | class IndexedEvent:
  function yields_child (line 396) | def yields_child(func: Callable[Concatenate[EventTree, P], Iterator[Even...
  class EventTree (line 407) | class EventTree:
    method __init__ (line 418) | def __init__(
    method __contains__ (line 435) | def __contains__(self, id: EventEnum) -> bool:
    method __eq__ (line 439) | def __eq__(self, o: object) -> bool:
    method __iadd__ (line 446) | def __iadd__(self, *events: AnyEvent) -> None:
    method __iter__ (line 451) | def __iter__(self) -> Iterator[AnyEvent]:
    method __len__ (line 454) | def __len__(self) -> int:
    method __repr__ (line 457) | def __repr__(self) -> str:
    method _get_ie (line 460) | def _get_ie(self, *ids: EventEnum) -> Iterator[IndexedEvent]:
    method _recursive (line 463) | def _recursive(self, action: Callable[[EventTree], None]) -> None:
    method append (line 471) | def append(self, event: AnyEvent) -> None:
    method count (line 475) | def count(self, id: EventEnum) -> int:
    method divide (line 480) | def divide(self, separator: EventEnum, *ids: EventEnum) -> Iterator[Ev...
    method first (line 496) | def first(self, id: EventEnum) -> AnyEvent:
    method get (line 507) | def get(self, *ids: EventEnum) -> Iterator[AnyEvent]:
    method group (line 512) | def group(self, *ids: EventEnum) -> Iterator[EventTree]:
    method insert (line 517) | def insert(self, pos: int, e: AnyEvent) -> None:
    method pop (line 529) | def pop(self, id: EventEnum, pos: int = 0) -> AnyEvent:
    method remove (line 544) | def remove(self, id: EventEnum, pos: int = 0) -> None:
    method separate (line 549) | def separate(self, id: EventEnum) -> Iterator[EventTree]:
    method subtree (line 553) | def subtree(self, select: Callable[[AnyEvent], bool | None]) -> EventT...
    method subtrees (line 569) | def subtrees(
    method ids (line 595) | def ids(self) -> frozenset[EventEnum]:
    method indexes (line 599) | def indexes(self) -> frozenset[int]:

FILE: pyflp/_models.py
  class ModelBase (line 40) | class ModelBase(abc.ABC):
    method __init__ (line 41) | def __init__(self, *args: Any, **kw: Any) -> None:
  class ItemModel (line 45) | class ItemModel(ModelBase, Generic[VE]):
    method __init__ (line 48) | def __init__(self, item: c.Container[Any], index: int, parent: VE, **k...
    method __getitem__ (line 61) | def __getitem__(self, prop: str):
    method __setitem__ (line 64) | def __setitem__(self, prop: str, value: Any) -> None:
  class EventModel (line 73) | class EventModel(ModelBase):
    method __init__ (line 74) | def __init__(self, events: EventTree, **kw: Any) -> None:
    method __eq__ (line 78) | def __eq__(self, o: object) -> bool:
  class ModelCollection (line 90) | class ModelCollection(Iterable[MT_co], Protocol[MT_co]):
    method __getitem__ (line 92) | def __getitem__(self, i: int | str) -> MT_co:
    method __getitem__ (line 96) | def __getitem__(self, i: slice) -> Sequence[MT_co]:
  function supports_slice (line 100) | def supports_slice(func: Callable[[ModelCollection[MT_co], str | int | s...
  class ModelReprMixin (line 120) | class ModelReprMixin:
    method __repr__ (line 123) | def __repr__(self) -> str:

FILE: pyflp/arrangement.py
  class PLSelectionEvent (line 66) | class PLSelectionEvent(StructEventBase):
  class PlaylistEvent (line 70) | class PlaylistEvent(ListEventBase):
    method __init__ (line 89) | def __init__(self, id: EventEnum, data: bytes) -> None:
  class TrackMotion (line 94) | class TrackMotion(ct.EnumBase):
  class TrackPress (line 105) | class TrackPress(ct.EnumBase):
  class TrackSync (line 113) | class TrackSync(ct.EnumBase):
  class HeightAdapter (line 123) | class HeightAdapter(ct.Adapter[float, float, str, str]):
    method _decode (line 124) | def _decode(self, obj: float, *_: Any) -> str:
    method _encode (line 127) | def _encode(self, obj: str, *_: Any) -> float:
  class TrackEvent (line 131) | class TrackEvent(StructEventBase):
  class ArrangementsID (line 153) | class ArrangementsID(EventEnum):
  class ArrangementID (line 163) | class ArrangementID(EventEnum):
  class TrackID (line 171) | class TrackID(EventEnum):
  class PLItemBase (line 176) | class PLItemBase(ItemModel[PlaylistEvent], ModelReprMixin):
    method offsets (line 187) | def offsets(self) -> tuple[float, float]:
    method offsets (line 195) | def offsets(self, value: tuple[float, float]) -> None:
  class ChannelPLItem (line 202) | class ChannelPLItem(PLItemBase, ModelReprMixin):
    method channel (line 209) | def channel(self) -> Channel:
    method channel (line 213) | def channel(self, channel: Channel) -> None:
  class PatternPLItem (line 218) | class PatternPLItem(PLItemBase, ModelReprMixin):
    method pattern (line 225) | def pattern(self) -> Pattern:
    method pattern (line 229) | def pattern(self, pattern: Pattern) -> None:
  class _TrackColorProp (line 234) | class _TrackColorProp(StructProp[RGBA]):
    method _get (line 235) | def _get(self, ev_or_ins: Any) -> RGBA | None:
    method _set (line 240) | def _set(self, ev_or_ins: Any, value: RGBA) -> None:
  class _TrackKW (line 244) | class _TrackKW(TypedDict):
  class Track (line 248) | class Track(EventModel, ModelCollection[PLItemBase]):
    method __init__ (line 254) | def __init__(self, events: EventTree, **kw: Unpack[_TrackKW]) -> None:
    method __getitem__ (line 257) | def __getitem__(self, index: int | slice | str):
    method __iter__ (line 262) | def __iter__(self) -> Iterator[PLItemBase]:
    method __len__ (line 266) | def __len__(self) -> int:
    method __repr__ (line 269) | def __repr__(self) -> str:
  class _ArrangementKW (line 338) | class _ArrangementKW(TypedDict):
  class Arrangement (line 344) | class Arrangement(EventModel):
    method __init__ (line 352) | def __init__(self, events: EventTree, **kw: Unpack[_ArrangementKW]) ->...
    method __repr__ (line 355) | def __repr__(self) -> str:
    method timemarkers (line 370) | def timemarkers(self) -> Iterator[TimeMarker]:
    method tracks (line 374) | def tracks(self) -> Iterator[Track]:
  class TimeSignature (line 403) | class TimeSignature(EventModel, ModelReprMixin):
    method __str__ (line 406) | def __str__(self) -> str:
  class Arrangements (line 428) | class Arrangements(EventModel, ModelCollection[Arrangement]):
    method __init__ (line 431) | def __init__(self, events: EventTree, **kw: Unpack[_ArrangementKW]) ->...
    method __getitem__ (line 435) | def __getitem__(self, i: int | str | slice) -> Arrangement:
    method __iter__ (line 458) | def __iter__(self) -> Iterator[Arrangement]:
    method __len__ (line 481) | def __len__(self) -> int:
    method __repr__ (line 491) | def __repr__(self) -> str:
    method current (line 495) | def current(self) -> Arrangement | None:
    method loop_pos (line 511) | def loop_pos(self) -> tuple[int, int] | None:
    method loop_pos (line 529) | def loop_pos(self, value: tuple[int, int]) -> None:
    method max_tracks (line 539) | def max_tracks(self) -> Literal[500, 199]:

FILE: pyflp/channel.py
  class ChannelNotFound (line 80) | class ChannelNotFound(ModelNotFound, KeyError):
  class AutomationEvent (line 84) | class AutomationEvent(StructEventBase):
    method _get_position (line 86) | def _get_position(stream: c.StreamType, index: int) -> float:
  class DelayEvent (line 122) | class DelayEvent(StructEventBase):
  class _EnvLFOFlags (line 133) | class _EnvLFOFlags(enum.IntFlag):
  class LFOShape (line 141) | class LFOShape(ct.EnumBase):
  class EnvelopeLFOEvent (line 150) | class EnvelopeLFOEvent(StructEventBase):
  class LevelAdjustsEvent (line 172) | class LevelAdjustsEvent(StructEventBase):
  class FilterType (line 182) | class FilterType(ct.EnumBase):
  class LevelsEvent (line 193) | class LevelsEvent(StructEventBase):
  class ArpDirection (line 205) | class ArpDirection(ct.EnumBase):
  class DeclickMode (line 217) | class DeclickMode(ct.EnumBase):
  class _DelayFlags (line 227) | class _DelayFlags(enum.IntFlag):
  class StretchMode (line 233) | class StretchMode(ct.EnumBase):
  class ParametersEvent (line 247) | class ParametersEvent(StructEventBase):
  class _PolyphonyFlags (line 291) | class _PolyphonyFlags(enum.IntFlag):
  class PolyphonyEvent (line 297) | class PolyphonyEvent(StructEventBase):
  class TrackingEvent (line 305) | class TrackingEvent(StructEventBase):
  class ChannelID (line 315) | class ChannelID(EventEnum):
  class DisplayGroupID (line 378) | class DisplayGroupID(EventEnum):
  class RackID (line 383) | class RackID(EventEnum):
  class ReverbType (line 390) | class ReverbType(enum.IntEnum):
  class ChannelType (line 402) | class ChannelType(ct.EnumBase):  # cuz Type would be a super generic name
  class _FXFlags (line 416) | class _FXFlags(enum.IntFlag):
  class _LayerFlags (line 423) | class _LayerFlags(enum.IntFlag):
  class _SamplerFlags (line 428) | class _SamplerFlags(enum.IntFlag):
  class DisplayGroup (line 436) | class DisplayGroup(EventModel, ModelReprMixin):
    method __str__ (line 437) | def __str__(self) -> str:
  class Arp (line 445) | class Arp(EventModel, ModelReprMixin):
  class Delay (line 474) | class Delay(EventModel, ModelReprMixin):
    method mod_x (line 500) | def mod_x(self) -> int:
    method mod_x (line 505) | def mod_x(self, value: int) -> None:
    method mod_y (line 510) | def mod_y(self) -> int:
    method mod_y (line 515) | def mod_y(self, value: int) -> None:
  class Filter (line 554) | class Filter(EventModel, ModelReprMixin):
  class LevelAdjusts (line 570) | class LevelAdjusts(EventModel, ModelReprMixin):
  class Time (line 584) | class Time(EventModel, ModelReprMixin):
  class Reverb (line 619) | class Reverb(EventModel, ModelReprMixin):
    method type (line 626) | def type(self) -> ReverbType | None:
    method type (line 632) | def type(self, value: ReverbType) -> None:
    method mix (line 639) | def mix(self) -> int | None:
    method mix (line 650) | def mix(self, value: int) -> None:
  class FX (line 657) | class FX(EventModel, ModelReprMixin):
  class Envelope (line 788) | class Envelope(EventModel, ModelReprMixin):
  class SamplerLFO (line 911) | class SamplerLFO(EventModel, ModelReprMixin):
  class Polyphony (line 971) | class Polyphony(EventModel, ModelReprMixin):
  class Tracking (line 999) | class Tracking(EventModel, ModelReprMixin):
  class Keyboard (line 1035) | class Keyboard(EventModel, ModelReprMixin):
  class Playback (line 1065) | class Playback(EventModel, ModelReprMixin):
  class TimeStretching (line 1084) | class TimeStretching(EventModel, ModelReprMixin):
  class Content (line 1110) | class Content(EventModel, ModelReprMixin):
  class AutomationLFO (line 1141) | class AutomationLFO(EventModel, ModelReprMixin):
  class AutomationPoint (line 1153) | class AutomationPoint(ItemModel[AutomationEvent], ModelReprMixin):
    method __setitem__ (line 1154) | def __setitem__(self, prop: str, value: Any) -> None:
  class Channel (line 1171) | class Channel(EventModel):
    method __repr__ (line 1174) | def __repr__(self) -> str:
    method group (line 1202) | def group(self) -> DisplayGroup:  # TODO Setter
    method pan (line 1235) | def pan(self) -> int | None:
    method pan (line 1250) | def pan(self, value: int) -> None:
    method volume (line 1263) | def volume(self) -> int | None:
    method volume (line 1278) | def volume(self, value: int) -> None:
    method zipped (line 1292) | def zipped(self) -> bool:
    method display_name (line 1302) | def display_name(self) -> str | None:
  class Automation (line 1307) | class Automation(Channel, ModelCollection[AutomationPoint]):
    method __getitem__ (line 1319) | def __getitem__(self, i: int | slice) -> AutomationPoint:
    method __iter__ (line 1325) | def __iter__(self) -> Iterator[AutomationPoint]:
  class Layer (line 1335) | class Layer(Channel, ModelCollection[Channel]):
    method __getitem__ (line 1344) | def __getitem__(self, i: int | str | slice) -> Channel:
    method __iter__ (line 1359) | def __iter__(self) -> Iterator[Channel]:
    method __len__ (line 1364) | def __len__(self) -> int:
    method __repr__ (line 1371) | def __repr__(self) -> str:
  class _SamplerInstrument (line 1381) | class _SamplerInstrument(Channel):
    method pitch_shift (line 1407) | def pitch_shift(self) -> int | None:
    method pitch_shift (line 1417) | def pitch_shift(self, value: int) -> None:
    method tracking (line 1432) | def tracking(self) -> dict[str, Tracking] | None:
  class Instrument (line 1442) | class Instrument(_SamplerInstrument):
  class Sampler (line 1450) | class Sampler(_SamplerInstrument):
    method __repr__ (line 1456) | def __repr__(self) -> str:
    method envelopes (line 1468) | def envelopes(self) -> dict[EnvelopeName, Envelope] | None:
    method lfos (line 1497) | def lfos(self) -> dict[LFOName, SamplerLFO] | None:
    method sample_path (line 1512) | def sample_path(self) -> pathlib.Path | None:
    method sample_path (line 1523) | def sample_path(self, value: pathlib.Path) -> None:
  class ChannelRack (line 1535) | class ChannelRack(EventModel, ModelCollection[Channel]):
    method __repr__ (line 1541) | def __repr__(self) -> str:
    method __getitem__ (line 1545) | def __getitem__(self, i: str | int | slice) -> Channel:
    method __iter__ (line 1560) | def __iter__(self) -> Iterator[Channel]:
    method __len__ (line 1589) | def __len__(self) -> int:
    method automations (line 1600) | def automations(self) -> Iterator[Automation]:
    method groups (line 1608) | def groups(self) -> Iterator[DisplayGroup]:
    method instruments (line 1616) | def instruments(self) -> Iterator[Instrument]:
    method layers (line 1621) | def layers(self) -> Iterator[Layer]:
    method samplers (line 1626) | def samplers(self) -> Iterator[Sampler]:

FILE: pyflp/controller.py
  class MIDIControllerEvent (line 29) | class MIDIControllerEvent(StructEventBase):
  class RemoteControllerEvent (line 33) | class RemoteControllerEvent(StructEventBase):
  class ControllerID (line 46) | class ControllerID(EventEnum):
  class RemoteController (line 51) | class RemoteController(EventModel, ModelReprMixin):
    method parameter (line 58) | def parameter(self) -> int | None:
    method controls_vst (line 67) | def controls_vst(self) -> bool | None:

FILE: pyflp/exceptions.py
  class Error (line 33) | class Error(Exception):
  class EventIDOutOfRange (line 44) | class EventIDOutOfRange(Error, ValueError):
    method __init__ (line 47) | def __init__(self, id: int, *expected: int) -> None:
  class InvalidEventChunkSize (line 51) | class InvalidEventChunkSize(Error, BufferError):
    method __init__ (line 54) | def __init__(self, expected: int, got: int) -> None:
  class PropertyCannotBeSet (line 58) | class PropertyCannotBeSet(Error, AttributeError):
    method __init__ (line 59) | def __init__(self, *ids: enum.Enum | int) -> None:
  class DataCorrupted (line 63) | class DataCorrupted(Error):
  class HeaderCorrupted (line 67) | class HeaderCorrupted(DataCorrupted, ValueError):
    method __init__ (line 74) | def __init__(self, desc: str) -> None:
  class NoModelsFound (line 78) | class NoModelsFound(DataCorrupted, LookupError):
  class ModelNotFound (line 82) | class ModelNotFound(DataCorrupted, IndexError):
  class VersionNotDetected (line 86) | class VersionNotDetected(DataCorrupted):

FILE: pyflp/mixer.py
  class _InsertFlags (line 66) | class _InsertFlags(enum.IntFlag):
  class _MixerParamsID (line 87) | class _MixerParamsID(ct.EnumBase):
  class InsertFlagsEvent (line 105) | class InsertFlagsEvent(StructEventBase):
  class InsertRoutingEvent (line 113) | class InsertRoutingEvent(ListEventBase):
  class _InsertItems (line 118) | class _InsertItems:
  class MixerParamsEvent (line 125) | class MixerParamsEvent(ListEventBase):
    method __init__ (line 136) | def __init__(self, id: Any, data: bytearray) -> None:
  class InsertID (line 153) | class InsertID(EventEnum):
  class MixerID (line 164) | class MixerID(EventEnum):
  class SlotID (line 170) | class SlotID(EventEnum):
  class InsertDock (line 175) | class InsertDock(enum.Enum):
  class _InsertEQBandKW (line 187) | class _InsertEQBandKW(TypedDict, total=False):
  class _InsertEQBandProp (line 193) | class _InsertEQBandProp(NamedPropMixin, RWProperty[int]):
    method __get__ (line 194) | def __get__(self, ins: InsertEQBand, owner: Any = None) -> int | None:
    method __set__ (line 199) | def __set__(self, ins: InsertEQBand, value: int) -> None:
  class InsertEQBand (line 203) | class InsertEQBand(ModelBase, ModelReprMixin):
    method __init__ (line 204) | def __init__(self, **kw: Unpack[_InsertEQBandKW]) -> None:
    method size (line 208) | def size(self) -> int:
  class _InsertEQPropArgs (line 235) | class _InsertEQPropArgs(NamedTuple):
  class _InsertEQProp (line 241) | class _InsertEQProp(NamedPropMixin, ROProperty[InsertEQBand]):
    method __init__ (line 242) | def __init__(self, ids: _InsertEQPropArgs) -> None:
    method __get__ (line 246) | def __get__(self, ins: InsertEQ, owner: Any = None) -> InsertEQBand:
  class InsertEQ (line 262) | class InsertEQ(ModelBase, ModelReprMixin):
    method __init__ (line 271) | def __init__(self, params: _InsertItems) -> None:
    method size (line 275) | def size(self) -> int:
  class _MixerParamProp (line 294) | class _MixerParamProp(RWProperty[T]):
    method __init__ (line 295) | def __init__(self, id: int) -> None:
    method __get__ (line 298) | def __get__(self, ins: Insert, owner: object = None) -> T | None:
    method __set__ (line 306) | def __set__(self, ins: Insert, value: T) -> None:
  class Slot (line 314) | class Slot(EventModel):
    method __init__ (line 320) | def __init__(self, events: EventTree, params: list[dict[str, Any]] | N...
    method __repr__ (line 323) | def __repr__(self) -> str:
  class _InsertKW (line 365) | class _InsertKW(TypedDict):
  class Insert (line 375) | class Insert(EventModel, ModelCollection[Slot]):
    method __init__ (line 381) | def __init__(self, events: EventTree, **kw: Unpack[_InsertKW]) -> None:
    method __repr__ (line 385) | def __repr__(self) -> str:
    method __getitem__ (line 389) | def __getitem__(self, i: int | str) -> Slot:
    method iid (line 406) | def iid(self) -> int:
    method __iter__ (line 410) | def __iter__(self) -> Iterator[Slot]:
    method __len__ (line 415) | def __len__(self) -> int:
    method dock (line 438) | def dock(self) -> InsertDock | None:
    method eq (line 464) | def eq(self) -> InsertEQ:
    method routes (line 511) | def routes(self) -> Iterator[int]:
  class _MixerKW (line 554) | class _MixerKW(TypedDict):
  class Mixer (line 560) | class Mixer(EventModel, ModelCollection[Insert]):
    method __init__ (line 578) | def __init__(self, events: EventTree, **kw: Unpack[_MixerKW]) -> None:
    method __getitem__ (line 583) | def __getitem__(self, i: int | str | slice) -> Insert:
    method __iter__ (line 600) | def __iter__(self) -> Iterator[Insert]:
    method __len__ (line 618) | def __len__(self) -> int:
    method __str__ (line 628) | def __str__(self) -> str:
    method max_inserts (line 635) | def max_inserts(self) -> int:
    method max_slots (line 655) | def max_slots(self) -> int:

FILE: pyflp/pattern.py
  class ControllerEvent (line 49) | class ControllerEvent(ListEventBase):
  class _NoteFlags (line 63) | class _NoteFlags(enum.IntFlag):
  class NotesEvent (line 67) | class NotesEvent(ListEventBase):
  class PatternsID (line 88) | class PatternsID(EventEnum):
  class PatternID (line 95) | class PatternID(EventEnum):
  class Note (line 110) | class Note(ItemModel[NotesEvent]):
    method __repr__ (line 113) | def __repr__(self) -> str:
    method __str__ (line 118) | def __str__(self) -> str:
    method key (line 140) | def key(self) -> str:
    method key (line 152) | def key(self, value: int | str) -> None:
  class Controller (line 223) | class Controller(ItemModel[ControllerEvent], ModelReprMixin):
    method __str__ (line 224) | def __str__(self) -> str:
  class Pattern (line 234) | class Pattern(EventModel):
    method __repr__ (line 237) | def __repr__(self) -> str:
    method controllers (line 262) | def controllers(self) -> Iterator[Controller]:
    method iid (line 269) | def iid(self) -> int:
    method iid (line 279) | def iid(self, value: int) -> None:
    method notes (line 299) | def notes(self) -> Iterator[Note]:
    method timemarkers (line 312) | def timemarkers(self) -> Iterator[TimeMarker]:
  class Patterns (line 317) | class Patterns(EventModel, ModelCollection[Pattern]):
    method __str__ (line 318) | def __str__(self) -> str:
    method __getitem__ (line 323) | def __getitem__(self, i: int | str | slice) -> Pattern:
    method __iter__ (line 340) | def __iter__(self) -> Iterator[Pattern]:
    method __len__ (line 357) | def __len__(self) -> int:
    method current (line 377) | def current(self) -> Pattern | None:

FILE: pyflp/plugin.py
  class _WrapperFlags (line 61) | class _WrapperFlags(enum.IntFlag):
  class BooBassEvent (line 76) | class BooBassEvent(StructEventBase):
  class FruitKickEvent (line 85) | class FruitKickEvent(StructEventBase):
  class FruityBalanceEvent (line 98) | class FruityBalanceEvent(StructEventBase):
  class FruityBloodOverdriveEvent (line 102) | class FruityBloodOverdriveEvent(StructEventBase):
  class FruityCenterEvent (line 116) | class FruityCenterEvent(StructEventBase):
  class FruityFastDistEvent (line 122) | class FruityFastDistEvent(StructEventBase):
  class FruityNotebook2Event (line 132) | class FruityNotebook2Event(StructEventBase):
  class FruitySendEvent (line 149) | class FruitySendEvent(StructEventBase):
  class FruitySoftClipperEvent (line 158) | class FruitySoftClipperEvent(StructEventBase):
  class FruityStereoEnhancerEvent (line 162) | class FruityStereoEnhancerEvent(StructEventBase):
  class PluckedEvent (line 173) | class PluckedEvent(StructEventBase):
  class SoundgoodizerEvent (line 183) | class SoundgoodizerEvent(StructEventBase):
  class WrapperPage (line 195) | class WrapperPage(ct.EnumBase):
  class WrapperEvent (line 212) | class WrapperEvent(StructEventBase):
  class _VSTPluginEventID (line 226) | class _VSTPluginEventID(ct.EnumBase):
  class _VSTFlags (line 242) | class _VSTFlags(enum.IntFlag):
  class _VSTFlags2 (line 265) | class _VSTFlags2(enum.IntFlag):
  class VSTPluginEvent (line 270) | class VSTPluginEvent(StructEventBase):
    method __init__ (line 315) | def __init__(self, id: Any, data: bytearray) -> None:
  class PluginID (line 328) | class PluginID(EventEnum):
  class _IPlugin (line 343) | class _IPlugin(Protocol):
  class _WrapperProp (line 351) | class _WrapperProp(FlagProp):
    method __init__ (line 352) | def __init__(self, flag: _WrapperFlags, **kw: Any) -> None:
  class _PluginBase (line 356) | class _PluginBase(EventModel, Generic[_PE_co]):
    method __init__ (line 357) | def __init__(self, events: EventTree, **kw: Any) -> None:
  class PluginProp (line 415) | class PluginProp(RWProperty[AnyPlugin]):
    method __init__ (line 416) | def __init__(self, *types: type[AnyPlugin]) -> None:
    method _get_plugin_events (line 420) | def _get_plugin_events(ins: EventModel) -> EventTree:
    method __get__ (line 423) | def __get__(self, ins: EventModel, owner: Any = None) -> AnyPlugin | N...
    method __set__ (line 440) | def __set__(self, ins: EventModel, value: AnyPlugin) -> None:
  class _NativePluginProp (line 450) | class _NativePluginProp(StructProp[T]):
    method __init__ (line 451) | def __init__(self, prop: str | None = None, **kwds: Any) -> None:
  class _VSTPluginProp (line 455) | class _VSTPluginProp(RWProperty[T], NamedPropMixin):
    method __init__ (line 456) | def __init__(self, id: _VSTPluginEventID, prop: str | None = None) -> ...
    method __get__ (line 460) | def __get__(self, ins: EventModel, _=None) -> T:
    method _get (line 467) | def _get(self, value: Any) -> T:
    method __set__ (line 470) | def __set__(self, ins: EventModel, value: T) -> None:
    method _set (line 473) | def _set(self, event: VSTPluginEvent, value: T) -> None:
  class _VSTFlagProp (line 480) | class _VSTFlagProp(_VSTPluginProp[bool]):
    method __init__ (line 481) | def __init__(
    method _get (line 488) | def _get(self, value: Any) -> bool:
    method _set (line 492) | def _set(self, event: VSTPluginEvent, value: bool) -> None:
  class PluginIOInfo (line 505) | class PluginIOInfo(EventModel):
  class VSTPlugin (line 510) | class VSTPlugin(_PluginBase[VSTPluginEvent], _IPlugin):
    method __repr__ (line 519) | def __repr__(self) -> str:
    class _AutomationOptions (line 522) | class _AutomationOptions(EventModel):
    class _CompatibilityOptions (line 531) | class _CompatibilityOptions(EventModel):
    class _MIDIOptions (line 569) | class _MIDIOptions(EventModel):
    class _ProcessingOptions (line 603) | class _ProcessingOptions(EventModel):
    class _UIOptions (line 653) | class _UIOptions(EventModel):
    method __init__ (line 680) | def __init__(self, events: EventTree, **kw: Any) -> None:
  class BooBass (line 718) | class BooBass(_PluginBase[BooBassEvent], _IPlugin, ModelReprMixin):
  class FruitKick (line 747) | class FruitKick(_PluginBase[FruitKickEvent], _IPlugin, ModelReprMixin):
  class FruityBalance (line 811) | class FruityBalance(_PluginBase[FruityBalanceEvent], _IPlugin, ModelRepr...
  class FruityBloodOverdrive (line 836) | class FruityBloodOverdrive(_PluginBase[FruityBloodOverdriveEvent], _IPlu...
  class FruityCenter (line 902) | class FruityCenter(_PluginBase[FruityCenterEvent], _IPlugin, ModelReprMi...
  class FruityFastDist (line 913) | class FruityFastDist(_PluginBase[FruityFastDistEvent], _IPlugin, ModelRe...
  class FruityNotebook2 (line 956) | class FruityNotebook2(_PluginBase[FruityNotebook2Event], _IPlugin, Model...
  class FruitySend (line 973) | class FruitySend(_PluginBase[FruitySendEvent], _IPlugin, ModelReprMixin):
  class FruitySoftClipper (line 1010) | class FruitySoftClipper(_PluginBase[FruitySoftClipperEvent], _IPlugin, M...
  class FruityStereoEnhancer (line 1035) | class FruityStereoEnhancer(_PluginBase[FruityStereoEnhancerEvent], _IPlu...
  class Plucked (line 1086) | class Plucked(_PluginBase[PluckedEvent], _IPlugin, ModelReprMixin):
  class Soundgoodizer (line 1119) | class Soundgoodizer(_PluginBase[SoundgoodizerEvent], _IPlugin, ModelRepr...
  function get_event_by_internal_name (line 1135) | def get_event_by_internal_name(name: str) -> type[AnyEvent]:

FILE: pyflp/project.py
  class TimestampEvent (line 65) | class TimestampEvent(StructEventBase):
  class PanLaw (line 70) | class PanLaw(ct.EnumBase):
  class FileFormat (line 78) | class FileFormat(enum.IntEnum):
  class ProjectID (line 114) | class ProjectID(EventEnum):
  class _ProjectKW (line 138) | class _ProjectKW(TypedDict):
  class Project (line 144) | class Project(EventModel):
    method __init__ (line 147) | def __init__(self, events: EventTree, **kw: Unpack[_ProjectKW]) -> None:
    method __repr__ (line 150) | def __repr__(self) -> str:
    method __str__ (line 153) | def __str__(self) -> str:
    method arrangements (line 157) | def arrangements(self) -> Arrangements:
    method channel_count (line 192) | def channel_count(self) -> int:
    method channel_count (line 203) | def channel_count(self, value: int) -> None:
    method channels (line 209) | def channels(self) -> ChannelRack:
    method created_on (line 237) | def created_on(self) -> datetime.datetime | None:
    method data_path (line 250) | def data_path(self) -> pathlib.Path | None:
    method data_path (line 261) | def data_path(self, value: str | pathlib.Path) -> None:
    method licensee (line 289) | def licensee(self) -> str | None:
    method licensee (line 314) | def licensee(self, value: str) -> None:
    method mixer (line 342) | def mixer(self) -> Mixer:
    method patterns (line 359) | def patterns(self) -> Patterns:
    method ppq (line 385) | def ppq(self) -> int:
    method ppq (line 412) | def ppq(self, value: int) -> None:
    method tempo (line 434) | def tempo(self) -> int | float | None:
    method tempo (line 461) | def tempo(self, value: int | float) -> None:
    method time_spent (line 484) | def time_spent(self) -> datetime.timedelta | None:
    method version (line 506) | def version(self) -> FLVersion:
    method version (line 526) | def version(self, value: FLVersion | str | tuple[int, ...]) -> None:

FILE: pyflp/timemarker.py
  class TimeMarkerID (line 28) | class TimeMarkerID(EventEnum):
  class TimeMarkerType (line 35) | class TimeMarkerType(enum.IntEnum):
  class TimeMarker (line 43) | class TimeMarker(EventModel, ModelReprMixin):
    method __str__ (line 49) | def __str__(self) -> str:
    method position (line 65) | def position(self) -> int | None:
    method type (line 73) | def type(self) -> TimeMarkerType | None:

FILE: pyflp/types.py
  class FLVersion (line 33) | class FLVersion:
    method __str__ (line 39) | def __str__(self) -> str:
  class MusicalTime (line 46) | class MusicalTime(NamedTuple):
  class RGBA (line 57) | class RGBA(NamedTuple):
    method from_bytes (line 64) | def from_bytes(buf: bytes) -> RGBA:
    method __bytes__ (line 67) | def __bytes__(self) -> bytes:

FILE: tests/conftest.py
  function project (line 18) | def project():
  function arrangements (line 23) | def arrangements(project: Project):
  function rack (line 28) | def rack(project: Project):
  function mixer (line 33) | def mixer(project: Project):
  function inserts (line 38) | def inserts(mixer: Mixer):
  function patterns (line 43) | def patterns(project: Project):
  function get_model (line 47) | def get_model(suffix: str, type: type[MT], *only: EventEnum) -> MT:

FILE: tests/test_arrangement.py
  function test_arrangements (line 20) | def test_arrangements(arrangements: Arrangements):
  function arrangement (line 30) | def arrangement(arrangements: Arrangements):
  function tracks (line 38) | def tracks(arrangement: Callable[[int], Arrangement]):
  function test_track_color (line 42) | def test_track_color(tracks: tuple[Track, ...]):
  function test_track_content_locked (line 51) | def test_track_content_locked(tracks: tuple[Track, ...]):
  function test_track_enabled (line 58) | def test_track_enabled(tracks: tuple[Track, ...]):
  function test_track_grouped (line 63) | def test_track_grouped(tracks: tuple[Track, ...]):
  function test_track_height (line 68) | def test_track_height(tracks: tuple[Track, ...]):
  function test_track_icon (line 78) | def test_track_icon(tracks: tuple[Track, ...]):
  function test_track_items (line 83) | def test_track_items(tracks: tuple[Track, ...]):
  function test_track_locked (line 102) | def test_track_locked(tracks: tuple[Track, ...]):
  function test_track_motion (line 107) | def test_track_motion(tracks: tuple[Track, ...]):
  function test_track_name (line 116) | def test_track_name(tracks: tuple[Track, ...]):
  function test_track_position_sync (line 143) | def test_track_position_sync(tracks: tuple[Track, ...]):
  function test_track_press (line 152) | def test_track_press(tracks: tuple[Track, ...]):
  function test_track_tolerant (line 161) | def test_track_tolerant(tracks: tuple[Track, ...]):
  function test_track_queued (line 166) | def test_track_queued(tracks: tuple[Track, ...]):
  function test_first_arrangement (line 171) | def test_first_arrangement(arrangement: Callable[[int], Arrangement]):
  function test_second_arrangement (line 178) | def test_second_arrangement(arrangement: Callable[[int], Arrangement]):

FILE: tests/test_channel.py
  function _load_channel (line 27) | def _load_channel(preset: str, type: type[CT]):
  function load_channel (line 33) | def load_channel(preset: str):
  function load_automation (line 37) | def load_automation(preset: str):
  function load_instrument (line 41) | def load_instrument(preset: str):
  function load_layer (line 45) | def load_layer(preset: str):
  function load_sampler (line 49) | def load_sampler(preset: str):
  function test_channels (line 53) | def test_channels(project: Project, rack: ChannelRack):
  function test_automation_lfo (line 61) | def test_automation_lfo():
  function test_automation_points (line 66) | def test_automation_points():
  function test_channel_color (line 71) | def test_channel_color():
  function test_channel_enabled (line 75) | def test_channel_enabled():
  function test_channel_group (line 79) | def test_channel_group(rack: ChannelRack):
  function test_channel_icon (line 89) | def test_channel_icon():
  function test_channel_pan (line 93) | def test_channel_pan():
  function test_channel_volume (line 98) | def test_channel_volume():
  function test_channel_zipped (line 103) | def test_channel_zipped(rack: ChannelRack):
  function test_instrument_delay (line 111) | def test_instrument_delay():
  function test_instrument_keyboard (line 123) | def test_instrument_keyboard():
  function test_instrument_polyphony (line 132) | def test_instrument_polyphony():
  function test_instrument_routing (line 140) | def test_instrument_routing():
  function test_instrument_time (line 144) | def test_instrument_time():
  function test_instrument_tracking (line 152) | def test_instrument_tracking():
  function test_layer_crossfade (line 167) | def test_layer_crossfade():
  function test_layer_random (line 171) | def test_layer_random():
  function test_sampler_content (line 175) | def test_sampler_content():
  function test_sampler_cut_group (line 184) | def test_sampler_cut_group():
  function test_sampler_envelopes (line 188) | def test_sampler_envelopes():
  function test_sampler_filter (line 216) | def test_sampler_filter():
  function test_sampler_fx (line 223) | def test_sampler_fx():
  function test_sampler_lfo (line 250) | def test_sampler_lfo():
  function test_sampler_path (line 273) | def test_sampler_path():
  function test_sampler_pitch_shift (line 279) | def test_sampler_pitch_shift():
  function test_sampler_playback (line 284) | def test_sampler_playback():
  function test_sampler_stretching (line 291) | def test_sampler_stretching():

FILE: tests/test_corrupted.py
  function test_invalid_header_magic (line 13) | def test_invalid_header_magic():
  function test_invalid_header_size (line 18) | def test_invalid_header_size():
  function test_invalid_format (line 23) | def test_invalid_format():
  function test_invalid_ppq (line 28) | def test_invalid_ppq():
  function test_invalid_data_magic (line 34) | def test_invalid_data_magic():
  function test_invalid_data_size (line 39) | def test_invalid_data_size():

FILE: tests/test_events.py
  function test_id_out_of_range (line 9) | def test_id_out_of_range():
  function test_invalid_chunk_size (line 17) | def test_invalid_chunk_size():
  function test_event_tree (line 22) | def test_event_tree():

FILE: tests/test_mixer.py
  function get_insert (line 11) | def get_insert(preset: str):
  function test_insert_bypassed (line 25) | def test_insert_bypassed():
  function test_insert_channels_swapped (line 29) | def test_insert_channels_swapped():
  function test_insert_color (line 33) | def test_insert_color():
  function test_insert_dock (line 37) | def test_insert_dock(inserts: tuple[Insert, ...]):
  function test_insert_enabled (line 48) | def test_insert_enabled():
  function test_insert_locked (line 52) | def test_insert_locked():
  function test_insert_pan (line 56) | def test_insert_pan():
  function test_insert_polarity_reversed (line 61) | def test_insert_polarity_reversed():
  function test_insert_routes (line 65) | def test_insert_routes(inserts: tuple[Insert, ...]):
  function test_insert_stereo_separation (line 69) | def test_insert_stereo_separation():
  function test_insert_eq (line 74) | def test_insert_eq():
  function test_mixer (line 87) | def test_mixer(mixer: Mixer):

FILE: tests/test_models.py
  function test_flversion (line 6) | def test_flversion():

FILE: tests/test_pattern.py
  function get_notes (line 9) | def get_notes(score: str):
  function test_patterns (line 13) | def test_patterns(patterns: Patterns):
  function test_pattern_color (line 19) | def test_pattern_color(patterns: Patterns):
  function test_pattern_names (line 23) | def test_pattern_names(patterns: Patterns):
  function test_pattern_timemarkers (line 33) | def test_pattern_timemarkers(patterns: Patterns):
  function test_empty_pattern (line 37) | def test_empty_pattern():
  function test_note_color (line 41) | def test_note_color():
  function test_note_fine_pitch (line 45) | def test_note_fine_pitch():
  function test_note_group (line 49) | def test_note_group():
  function test_note_length (line 53) | def test_note_length():
  function test_note_mod_x (line 57) | def test_note_mod_x():
  function test_note_mod_y (line 61) | def test_note_mod_y():
  function test_note_key (line 65) | def test_note_key():
  function test_note_pan (line 70) | def test_note_pan():
  function test_note_position (line 74) | def test_note_position():
  function test_note_rack_channel (line 79) | def test_note_rack_channel():
  function test_note_release (line 83) | def test_note_release():
  function test_note_slide (line 87) | def test_note_slide():
  function test_note_velocity (line 91) | def test_note_velocity():

FILE: tests/test_plugin.py
  function get_plugin (line 28) | def get_plugin(preset_file: str, type: type[T]):
  function test_boobass (line 32) | def test_boobass():
  function test_fruit_kick (line 37) | def test_fruit_kick():
  function test_fruity_balance (line 47) | def test_fruity_balance():
  function test_fruity_blood_overdrive (line 53) | def test_fruity_blood_overdrive():
  function test_fruity_center (line 62) | def test_fruity_center():
  function test_fruity_fast_dist (line 67) | def test_fruity_fast_dist():
  function test_fruity_send (line 76) | def test_fruity_send():
  function test_fruity_soft_clipper (line 84) | def test_fruity_soft_clipper():
  function test_fruity_stereo_enhancer (line 90) | def test_fruity_stereo_enhancer():
  function test_plucked (line 100) | def test_plucked():
  function test_soundgoodizer (line 109) | def test_soundgoodizer():
  function test_vst_plugin (line 115) | def test_vst_plugin():
  function test_fruity_wrapper (line 125) | def test_fruity_wrapper():

FILE: tests/test_project.py
  function test_project (line 13) | def test_project(project: Project):
  function test_null_check (line 62) | def test_null_check(project: Project, tmp_path: pathlib.Path):
Condensed preview — 177 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (431K chars).
[
  {
    "path": ".all-contributorsrc",
    "chars": 1030,
    "preview": "{\n  \"files\": [\n    \"README.md\"\n  ],\n  \"imageSize\": 50,\n  \"commit\": false,\n  \"contributors\": [\n    {\n      \"login\": \"nick"
  },
  {
    "path": ".editorconfig",
    "chars": 264,
    "preview": "# EditorConfig is awesome: https://EditorConfig.org\n\n# top-most EditorConfig file\nroot = true\n\n[*]\ncharset = utf-8\ninser"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "chars": 1003,
    "preview": "name: Bug Report\ndescription: File a bug report\ntitle: \"🐞 \"\nlabels: [\"bug\"]\nbody:\n  - type: textarea\n    id: description"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "chars": 141,
    "preview": "contact_links:\n  - name: Discussions\n    url: https://github.com/demberto/PyFLP/discussions\n    about: Please ask and an"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yml",
    "chars": 802,
    "preview": "name: Feature request\ndescription: ✨ I want a new feature\ntitle: \"✨ \"\nlabels: [\"enhancement\"]\nbody:\n  - type: textarea\n "
  },
  {
    "path": ".github/dependabot.yml",
    "chars": 530,
    "preview": "# To get started with Dependabot version updates, you'll need to specify which\n# package ecosystems to update and where "
  },
  {
    "path": ".github/workflows/codeql-analysis.yml",
    "chars": 2560,
    "preview": "# For most projects, this workflow file will not need changing; you simply need\n# to commit it to your repository.\n#\n# Y"
  },
  {
    "path": ".github/workflows/publish.yml",
    "chars": 970,
    "preview": "name: publish\n\non:\n  push:\n    tags:\n      - v*\n  workflow_dispatch:\n\njobs:\n  publish:\n    runs-on: ubuntu-latest\n    st"
  },
  {
    "path": ".github/workflows/test.yml",
    "chars": 1548,
    "preview": "name: test\n\non:\n  push:\n    branches:\n      - master\n  pull_request:\n    branches:\n      - master\n  workflow_dispatch:\n\n"
  },
  {
    "path": ".gitignore",
    "chars": 2148,
    "preview": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packagi"
  },
  {
    "path": ".pre-commit-config.yaml",
    "chars": 720,
    "preview": "# See https://pre-commit.com for more information\n# See https://pre-commit.com/hooks.html for more hooks\nrepos:\n  - repo"
  },
  {
    "path": ".readthedocs.yaml",
    "chars": 582,
    "preview": "# Read the Docs configuration file\n# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details\n\n# Requir"
  },
  {
    "path": ".vscode/extensions.json",
    "chars": 802,
    "preview": "{\n  \"recommendations\": [\n    \"aaron-bond.better-comments\", // Highlighting annotated comments\n    \"bierner.markdown-prev"
  },
  {
    "path": ".vscode/settings.json",
    "chars": 985,
    "preview": "// Suggested settings for contributors using VSCode.\n{\n  \"git.enableCommitSigning\": true, // nice \"Verified\" badges @ GH"
  },
  {
    "path": ".vscode/tasks.json",
    "chars": 574,
    "preview": "{\n  // See https://go.microsoft.com/fwlink/?LinkId=733558\n  // for the documentation about the tasks.json format\n  \"vers"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 26584,
    "preview": "<!-- markdownlint-disable no-duplicate-heading -->\n<!-- markdownlint-disable link-image-reference-definitions -->\n\n# Cha"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "chars": 5516,
    "preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participa"
  },
  {
    "path": "LICENSE",
    "chars": 35149,
    "preview": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free "
  },
  {
    "path": "MANIFEST.in",
    "chars": 119,
    "preview": "prune .github\nexclude .all-contributorsrc\nexclude .gitignore\nexclude .pre-commit-config.yaml\nexclude .readthedocs.yaml\n"
  },
  {
    "path": "README.md",
    "chars": 20226,
    "preview": "# PyFLP\n\nPyFLP is an unofficial parser for [FL Studio](https://www.image-line.com/fl-studio/)\nproject and preset files w"
  },
  {
    "path": "docs/Makefile",
    "chars": 634,
    "preview": "# Minimal makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line, and also\n# from the "
  },
  {
    "path": "docs/architecture/flp-format.rst",
    "chars": 3233,
    "preview": "Part I: FLP Format & Events\n===========================\n\nFLP is a binary format used by Image-Line FL Studio, a music pr"
  },
  {
    "path": "docs/architecture/how-it-works.rst",
    "chars": 4282,
    "preview": "Part II: How PyFLP works\n========================\n\n    💡 You should read Part I before this.\n\nPyFLP's entry-point :meth:"
  },
  {
    "path": "docs/architecture/reference.rst",
    "chars": 2384,
    "preview": "Developer Reference\n===================\n\nThis page documents PyFLP's internals which consists of :mod:`pyflp._events`,\n:"
  },
  {
    "path": "docs/architecture.rst",
    "chars": 201,
    "preview": "🏠 Architecture\n================\n\n.. toctree::\n\n   1️⃣ FLP Format & Events <architecture/flp-format>\n   2️⃣ How it works?"
  },
  {
    "path": "docs/changelog.rst",
    "chars": 31,
    "preview": ".. mdinclude:: ../CHANGELOG.md\n"
  },
  {
    "path": "docs/conf.py",
    "chars": 8222,
    "preview": "# type: ignore\n\n\"\"\"Sphinx configuration script.\"\"\"\n\nfrom __future__ import annotations\n\nimport enum\nimport importlib.met"
  },
  {
    "path": "docs/contributing.rst",
    "chars": 6321,
    "preview": "\\ :fas:`user-gear` Contributor's Guide\n======================================\n\n🤝 All contributions are welcome.\n\n.. impo"
  },
  {
    "path": "docs/faq.rst",
    "chars": 2305,
    "preview": "❓ FAQ\n======\n\nNow I don't frequently get asked any questions, *(I would love to)* but these\nare some questions I think a"
  },
  {
    "path": "docs/features.rst",
    "chars": 2665,
    "preview": "✨ Features\n============\n\nNon-destructive editing\n-----------------------\n\nThe modifications you make will have a minimum"
  },
  {
    "path": "docs/guides/plugin.rst",
    "chars": 5173,
    "preview": "🚶‍♂️ Walkthrough: Implementing a plugin data parser\n====================================================\n\nImplementing a"
  },
  {
    "path": "docs/guides/reversing.rst",
    "chars": 1914,
    "preview": "🤓 Reversing FLP format\n========================\n\n    You should first take a look at :doc:`what events are <../architect"
  },
  {
    "path": "docs/guides.rst",
    "chars": 269,
    "preview": "📖 Developer guides\n====================\n\nWant to be a **contributor**? Interested in the internals of the FLP format?\nTh"
  },
  {
    "path": "docs/handbook.rst",
    "chars": 3458,
    "preview": "📚 Handbook\n============\n\nThis page contains some ideas on how one can use PyFLP for automating\ntasks (*to a certain exte"
  },
  {
    "path": "docs/helping.rst",
    "chars": 2826,
    "preview": "🙌 Helping PyFLP\n=================\n\nPyFLP is completely free and open source (FOSS) software. It takes a lot of\ntime and "
  },
  {
    "path": "docs/index.rst",
    "chars": 377,
    "preview": ".. mdinclude:: ../README.md\n\nNavigation\n----------\n\n.. toctree::\n   :maxdepth: 2\n   :titlesonly:\n\n   handbook\n   referen"
  },
  {
    "path": "docs/limitations.rst",
    "chars": 3786,
    "preview": "🚫 Limitations\n===============\n\nBefore you begin reading, I would like to **emphasize** that FLP is a closed\nand undocume"
  },
  {
    "path": "docs/make.bat",
    "chars": 765,
    "preview": "@ECHO OFF\n\npushd %~dp0\n\nREM Command file for Sphinx documentation\n\nif \"%SPHINXBUILD%\" == \"\" (\n\tset SPHINXBUILD=sphinx-bu"
  },
  {
    "path": "docs/reference/arrangement/arrangement.rst",
    "chars": 262,
    "preview": "\\ :fas:`trowel-bricks` Arrangement\n==================================\n\n.. currentmodule:: pyflp.arrangement\n\n.. autoclas"
  },
  {
    "path": "docs/reference/arrangement/index.rst",
    "chars": 255,
    "preview": "Arrangements\n============\n\n.. module:: pyflp.arrangement\n\n.. toctree::\n   :maxdepth: 2\n   :titlesonly:\n   :caption: Cont"
  },
  {
    "path": "docs/reference/arrangement/playlist.rst",
    "chars": 344,
    "preview": "\\ :material-sharp:`playlist_play;1.2em;sd-pb-1` Playlist\n========================================================\n\n.. cu"
  },
  {
    "path": "docs/reference/arrangement/track.rst",
    "chars": 749,
    "preview": "Track\n=====\n\n.. currentmodule:: pyflp.arrangement\n\n.. autoclass:: Track\n   :members:\n\n.. grid::\n\n   .. grid-item::\n\n    "
  },
  {
    "path": "docs/reference/channel/automation.rst",
    "chars": 250,
    "preview": "\\ :fas:`bezier-curve` Automation\n================================\n\n.. currentmodule:: pyflp.channel\n\n.. autoclass:: Auto"
  },
  {
    "path": "docs/reference/channel/channel.rst",
    "chars": 207,
    "preview": "Channel\n=======\n\n.. currentmodule:: pyflp.channel\n\n.. autoclass:: Channel\n   :members:\n\nEnums\n-----\n\n.. autoclass:: Chan"
  },
  {
    "path": "docs/reference/channel/display-group.rst",
    "chars": 173,
    "preview": "DisplayGroup\n============\n\n.. currentmodule:: pyflp.channel\n\n.. autoclass:: DisplayGroup\n   :members:\n\n.. autoclass:: Di"
  },
  {
    "path": "docs/reference/channel/index.rst",
    "chars": 353,
    "preview": "\\ :material-sharp:`dns;1.2em;sd-pb-1` Channel Rack\n==================================================\n\n.. toctree::\n   :"
  },
  {
    "path": "docs/reference/channel/instrument.rst",
    "chars": 149,
    "preview": "Instrument\n==========\n\n.. currentmodule:: pyflp.channel\n\n.. autoclass:: Instrument\n   :show-inheritance:\n   :members:\n  "
  },
  {
    "path": "docs/reference/channel/layer.rst",
    "chars": 145,
    "preview": "\\ :fas:`layer-group` Layer\n==========================\n\n.. currentmodule:: pyflp.channel\n\n.. autoclass:: Layer\n   :show-i"
  },
  {
    "path": "docs/reference/channel/sampler.rst",
    "chars": 861,
    "preview": "\\ :material-sharp:`audio_file;1.2em;sd-pb-1` Sampler\n====================================================\n\n.. currentmod"
  },
  {
    "path": "docs/reference/channel/shared.rst",
    "chars": 437,
    "preview": "Shared\n======\n\n.. currentmodule:: pyflp.channel\n\nThese implement functionality used by :class:`Channel` or its subclasse"
  },
  {
    "path": "docs/reference/controllers.rst",
    "chars": 185,
    "preview": "🎛 Controllers\n=============\n\n.. module:: pyflp.controller\n.. autoclass:: RemoteController\n   :members:\n\nEnums\n-----\n\n.. "
  },
  {
    "path": "docs/reference/events.rst",
    "chars": 7122,
    "preview": "\\ :fas:`ellipsis` Events\n========================\n\n    This section is intended for those who want to delve into PyFLP's"
  },
  {
    "path": "docs/reference/exceptions.rst",
    "chars": 116,
    "preview": "🛑 Exceptions\n==============\n\n.. automodule:: pyflp.exceptions\n   :members:\n   :show-inheritance:\n   :undoc-members:\n"
  },
  {
    "path": "docs/reference/mixer/index.rst",
    "chars": 352,
    "preview": "\\ :material-sharp:`settings_input_component;1.2em;sd-pb-1` Mixer\n======================================================="
  },
  {
    "path": "docs/reference/mixer/insert.rst",
    "chars": 452,
    "preview": "\\ :fas:`sliders` Insert\n=======================\n\n.. currentmodule:: pyflp.mixer\n\n.. autoclass:: Insert\n   :members:\n\n.. "
  },
  {
    "path": "docs/reference/mixer/slot.rst",
    "chars": 194,
    "preview": "\\ :fas:`folder-tree` Slot\n=========================\n\n.. currentmodule:: pyflp.mixer\n\n.. autoclass:: Slot\n   :members:\n\nE"
  },
  {
    "path": "docs/reference/patterns/index.rst",
    "chars": 254,
    "preview": "🎹 Patterns\n============\n\n.. module:: pyflp.pattern\n\n.. toctree::\n   :maxdepth: 2\n   :titlesonly:\n   :caption: Contents:\n"
  },
  {
    "path": "docs/reference/patterns/pattern.rst",
    "chars": 240,
    "preview": "Pattern\n=======\n\n.. currentmodule:: pyflp.pattern\n\n.. autoclass:: Pattern\n   :members:\n\n.. autoclass:: Controller\n   :me"
  },
  {
    "path": "docs/reference/plugins/effects.rst",
    "chars": 453,
    "preview": "Effects\n=======\n\n.. currentmodule:: pyflp.plugin\n\n.. autoclass:: FruityBalance\n   :members:\n\n.. autoclass:: FruityBloodO"
  },
  {
    "path": "docs/reference/plugins/generators.rst",
    "chars": 92,
    "preview": "Generators\n==========\n\n.. currentmodule:: pyflp.plugin\n\n.. autoclass:: BooBass\n   :members:\n"
  },
  {
    "path": "docs/reference/plugins/index.rst",
    "chars": 417,
    "preview": "\\ :material-sharp:`extension;1.2em;sd-pb-1` Plugins\n===================================================\n\n.. module:: pyf"
  },
  {
    "path": "docs/reference/plugins/vst.rst",
    "chars": 804,
    "preview": "VST\n===\n\n.. currentmodule:: pyflp.plugin\n\n.. autoclass:: VSTPlugin\n   :members:\n\n   .. tab-set::\n\n      .. tab-item:: Se"
  },
  {
    "path": "docs/reference/project.rst",
    "chars": 1338,
    "preview": "\\ :fas:`file-waveform` Project\n==============================\n\n.. module:: pyflp.project\n\n.. autoclass:: Project\n   :mem"
  },
  {
    "path": "docs/reference/timemarkers.rst",
    "chars": 422,
    "preview": "\\ :fas:`timeline` Timemarkers\n=============================\n\n.. module:: pyflp.timemarker\n\n.. autoclass:: TimeMarker\n   "
  },
  {
    "path": "docs/reference.rst",
    "chars": 393,
    "preview": "🧾 Reference\n=============\n\n.. toctree::\n   :maxdepth: 2\n   :titlesonly:\n   :caption: Contents:\n   :glob:\n\n   reference/*"
  },
  {
    "path": "docs/requirements.txt",
    "chars": 231,
    "preview": "furo==2023.5.20\nm2r2==0.3.2  # https://github.com/CrossNox/m2r2/issues/55\nsphinx==6.1.3\nsphinx-copybutton==0.5.2\nsphinx-"
  },
  {
    "path": "pyflp/__init__.py",
    "chars": 6120,
    "preview": "# PyFLP - An FL Studio project file (.flp) parser\n# Copyright (C) 2022 demberto\n#\n# This program is free software: you c"
  },
  {
    "path": "pyflp/_adapters.py",
    "chars": 3742,
    "preview": "# PyFLP - An FL Studio project file (.flp) parser\n# Copyright (C) 2022 demberto\n#\n# This program is free software: you c"
  },
  {
    "path": "pyflp/_descriptors.py",
    "chars": 6917,
    "preview": "# PyFLP - An FL Studio project file (.flp) parser\n# Copyright (C) 2022 demberto\n#\n# This program is free software: you c"
  },
  {
    "path": "pyflp/_events.py",
    "chars": 19137,
    "preview": "# PyFLP - An FL Studio project file (.flp) parser\n# Copyright (C) 2022 demberto\n#\n# This program is free software/or mod"
  },
  {
    "path": "pyflp/_models.py",
    "chars": 3953,
    "preview": "# PyFLP - An FL Studio project file (.flp) parser\n# Copyright (C) 2022 demberto\n#\n# This program is free software: you c"
  },
  {
    "path": "pyflp/arrangement.py",
    "chars": 17705,
    "preview": "# PyFLP - An FL Studio project file (.flp) parser\n# Copyright (C) 2022 demberto\n#\n# This program is free software: you c"
  },
  {
    "path": "pyflp/channel.py",
    "chars": 50356,
    "preview": "# PyFLP - An FL Studio project file (.flp) parser\n# Copyright (C) 2022 demberto\n#\n# This program is free software: you c"
  },
  {
    "path": "pyflp/controller.py",
    "chars": 2502,
    "preview": "# PyFLP - An FL Studio project file (.flp) parser\n# Copyright (C) 2022 demberto\n#\n# This program is free software: you c"
  },
  {
    "path": "pyflp/exceptions.py",
    "chars": 2805,
    "preview": "# PyFLP - An FL Studio project file (.flp) parser\n# Copyright (C) 2022 demberto\n#\n# This program is free software/or mod"
  },
  {
    "path": "pyflp/mixer.py",
    "chars": 19754,
    "preview": "# PyFLP - An FL Studio project file (.flp) parser\n# Copyright (C) 2022 demberto\n#\n# This program is free software: you c"
  },
  {
    "path": "pyflp/pattern.py",
    "chars": 12461,
    "preview": "# PyFLP - An FL Studio project file (.flp) parser\n# Copyright (C) 2022 demberto\n#\n# This program is free software: you c"
  },
  {
    "path": "pyflp/plugin.py",
    "chars": 34899,
    "preview": "# PyFLP - An FL Studio project file (.flp) parser\n# Copyright (C) 2022 demberto\n#\n# This program is free software: you c"
  },
  {
    "path": "pyflp/project.py",
    "chars": 18882,
    "preview": "# PyFLP - An FL Studio project file (.flp) parser\n# Copyright (C) 2022 demberto\n#\n# This program is free software: you c"
  },
  {
    "path": "pyflp/py.typed",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "pyflp/timemarker.py",
    "chars": 2903,
    "preview": "# PyFLP - An FL Studio project file (.flp) parser\n# Copyright (C) 2022 demberto\n#\n# This program is free software: you c"
  },
  {
    "path": "pyflp/types.py",
    "chars": 2385,
    "preview": "# PyFLP - An FL Studio project file (.flp) parser\n# Copyright (C) 2023 demberto\n#\n# This program is free software: you c"
  },
  {
    "path": "pyproject.toml",
    "chars": 2552,
    "preview": "[build-system]\nrequires = [\"setuptools>=61.0.0\", \"setuptools_scm[toml]>=6.2\"]\nbuild-backend = \"setuptools.build_meta\"\n\n["
  },
  {
    "path": "requirements.txt",
    "chars": 110,
    "preview": "construct-typing==0.5.6\nf-enum==0.2.0;python_version<=\"3.10\"\nsortedcontainers==2.4.0\ntyping_extensions==4.7.1\n"
  },
  {
    "path": "tests/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "tests/conftest.py",
    "chars": 1131,
    "preview": "from __future__ import annotations\n\nimport pathlib\nfrom typing import TypeVar\n\nimport pytest\n\nimport pyflp\nfrom pyflp im"
  },
  {
    "path": "tests/test_arrangement.py",
    "chars": 5147,
    "preview": "from __future__ import annotations\n\nfrom typing import Callable\n\nimport pytest\n\nfrom pyflp._events import RGBA\nfrom pyfl"
  },
  {
    "path": "tests/test_channel.py",
    "chars": 7944,
    "preview": "from __future__ import annotations\n\nimport pathlib\nfrom typing import TypeVar\n\nfrom pyflp._events import RGBA\nfrom pyflp"
  },
  {
    "path": "tests/test_corrupted.py",
    "chars": 1183,
    "preview": "from __future__ import annotations\n\nimport pathlib\n\nimport pytest\n\nimport pyflp\nfrom pyflp.exceptions import HeaderCorru"
  },
  {
    "path": "tests/test_events.py",
    "chars": 838,
    "preview": "from __future__ import annotations\n\nimport pytest\n\nfrom pyflp._events import AsciiEvent, EventEnum, EventTree, U8Event, "
  },
  {
    "path": "tests/test_mixer.py",
    "chars": 2732,
    "preview": "from __future__ import annotations\n\nfrom typing import cast\n\nfrom pyflp._events import RGBA\nfrom pyflp.mixer import Inse"
  },
  {
    "path": "tests/test_models.py",
    "chars": 201,
    "preview": "from __future__ import annotations\n\nfrom pyflp.types import FLVersion\n\n\ndef test_flversion():\n    assert str(FLVersion(2"
  },
  {
    "path": "tests/test_pattern.py",
    "chars": 2249,
    "preview": "from __future__ import annotations\n\nfrom pyflp._events import RGBA\nfrom pyflp.pattern import Pattern, PatternID, Pattern"
  },
  {
    "path": "tests/test_plugin.py",
    "chars": 5104,
    "preview": "from __future__ import annotations\n\nfrom typing import TypeVar\n\nfrom pyflp.plugin import (\n    AnyPlugin,\n    BooBass,\n "
  },
  {
    "path": "tests/test_project.py",
    "chars": 2374,
    "preview": "from __future__ import annotations\n\nimport datetime\nimport pathlib\nimport textwrap\n\nimport pytest\n\nimport pyflp\nfrom pyf"
  },
  {
    "path": "tox.ini",
    "chars": 780,
    "preview": "[tox]\nenvlist = precommit,py{38,39,310,311},pypy{38,39},docs\nminversion = 4.0\nparallel = auto\n\n[testenv]\ndeps =\n  -rrequ"
  }
]

// ... and 81 more files (download for full content)

About this extraction

This page contains the full source code of the demberto/PyFLP GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 177 files (395.7 KB), approximately 109.7k tokens, and a symbol index with 600 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!