From 2672a549e7da0e71d803aca6a7f452aa14fac233 Mon Sep 17 00:00:00 2001 From: Miguel Guthridge Date: Sat, 12 Feb 2022 15:45:50 +1100 Subject: [PATCH 01/12] Create setup instructions --- docs/setup.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 docs/setup.md diff --git a/docs/setup.md b/docs/setup.md new file mode 100644 index 00000000..8d25dbdf --- /dev/null +++ b/docs/setup.md @@ -0,0 +1,29 @@ + +# Setup + +To install the script, the following steps are recommended: + +1. Make sure you're in the [Discord server](https://discord.gg/6vpfJUF), so + that you'll get notified when updates are released. You can also ask for + tech support there. +2. If you're on Windows, install Git (it should be pre-installed on MacOS and + Linux) so that the script can be updated easily. +3. Navigate to `Documents/Image-Line/FL Studio/Settings/Hardware` and open in a + terminal. +4. Run the command `git clone https://github.com/MiguelGuthridge/Universal-Controller-Script` + which will download and install the script. +5. Launch (or close and relaunch) FL Studio, and open the MIDI Settings window. +6. Set your desired controller's ports to be the same (non-zero) value in both + the input and output sections. +7. Select the controller in the input section, and change the controller type + to `Universal Controller (user)`. Make sure the controller is enabled. +8. Navigate to the script output window (View > Script output), and select your + device's tab. +9. Wait 3 seconds - if no errors appear, your device was detected successfully. + Feel free to familiarise yourself with your device's specific functionality, + in the (TODO) section. Enjoy using your device! +10. If you get an error, then your device couldn't be detected. Usually this + means that your device doesn't have a definition (I'd love if you + [contributed one](TODO)), but if you're sure your device does, it may just + need some manual configuration. Refer to its manual page in the (TODO) + section. From feb8d3816557e0487fd16dbd83ca0dc4f92bea55 Mon Sep 17 00:00:00 2001 From: Miguel Guthridge Date: Sat, 12 Feb 2022 16:14:43 +1100 Subject: [PATCH 02/12] Add documentation for devices --- .vscode/settings.json | 2 + README.md | 11 ++++-- docs/contributing/contributing.md | 4 ++ docs/devices/devices.md | 10 +++++ docs/devices/maudio/hammer88pro.md | 36 ++++++++++++++++++ docs/devices/novation/launchkey.mk2.md | 9 +++++ docs/setup.md | 8 ++-- .../UniversalPreset.Hammer88ProDawPreset | Bin .../UniversalPreset.Hammer88ProUserPreset | Bin 9 files changed, 72 insertions(+), 8 deletions(-) create mode 100644 docs/contributing/contributing.md create mode 100644 docs/devices/devices.md create mode 100644 docs/devices/maudio/hammer88pro.md create mode 100644 docs/devices/novation/launchkey.mk2.md rename {devices => resources/deviceconfigs}/maudio/hammer88pro/UniversalPreset.Hammer88ProDawPreset (100%) rename {devices => resources/deviceconfigs}/maudio/hammer88pro/UniversalPreset.Hammer88ProUserPreset (100%) diff --git a/.vscode/settings.json b/.vscode/settings.json index 3ec16883..9797a792 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -24,11 +24,13 @@ "capsys", "Cimbalom", "deinitialise", + "deviceconfigs", "dicttools", "Dpad", "flmidimsg", "ieventpattern", "ivaluestrategy", + "launchkey", "Maudio", "MIDIIN", "Novation", diff --git a/README.md b/README.md index 39f30983..4c014aa7 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,11 @@ # Universal-Controller-Script -A script aimed at adding compatibility for any MIDI controller in FL Studio +A script aimed at adding compatibility for any MIDI controller and any plugin in +FL Studio. -# WARNING: This script is currently not functional +# Setup -This is a complete rewrite from the ground up of a concept script I made for the same purpose. Eventually, it should greatly surpass all of my previous work for the legacy version, but it is currently non-functional. If you wish to look at the original, please head to [MiguelGuthridge/Legacy-Universal-Controller-Script](https://github.com/MiguelGuthridge/Legacy-Universal-Controller-Script). +Refer to the [documentation](docs/setup.md). -If you have any ideas for the development of the script, or want to contribute, please join the Discord Server [here](https://discord.gg/6vpfJUF). I'm excited to see where this project will go in the future: I have huge plans for it! +If you have any ideas for the development of the script, or want to contribute, +please join the Discord Server [here](https://discord.gg/6vpfJUF). I'm excited +to see where this project will go in the future: I have huge plans for it! diff --git a/docs/contributing/contributing.md b/docs/contributing/contributing.md new file mode 100644 index 00000000..55223509 --- /dev/null +++ b/docs/contributing/contributing.md @@ -0,0 +1,4 @@ + +# Contributing + +TODO diff --git a/docs/devices/devices.md b/docs/devices/devices.md new file mode 100644 index 00000000..471fa3a1 --- /dev/null +++ b/docs/devices/devices.md @@ -0,0 +1,10 @@ + +# Devices + +## M-Audio + +* [Hammer 88 Pro](maudio/hammer88pro.md) + +## Novation + +* [LaunchKey Mk2](novation/launchkey.mk2.md) diff --git a/docs/devices/maudio/hammer88pro.md b/docs/devices/maudio/hammer88pro.md new file mode 100644 index 00000000..743c7356 --- /dev/null +++ b/docs/devices/maudio/hammer88pro.md @@ -0,0 +1,36 @@ + +# M-Audio Hammer 88 Pro + +## Setup + +In order for the controller to work correctly, DAW and Preset configurations +need to be sent to the device. + +1. Open the Hammer 88 Pro Preset Editor Program (this can be installed with the + included software bundle for your controller). +2. Ensure that the Preset tab is selected. +3. In the file menu, choose "Load Preset". Navigate to the script's folder, then + choose the device's configuration from the + `resources/deviceconfigs/maudio/hammer88pro` folder. +4. In the file menu, choose "Send Preset", and send the preset to RAM. +5. Use the "Send Preset" tool again to send it to a bank of your choosing. + Note that if you don't choose Bank 1, this preset won't be loaded by default + in by the keyboard. +6. Switch to the DAW tab, then choose "Load Preset" again, selecting the + device's configuration again. +7. Use the "Send Preset" tool again to send the configuration to the device. + +## Usage + +#### Bank Selection +Currently, the script doesn't support bank switching. Please keep your device on +bank 1. + +#### Jog Wheel Usage +The controller can behave differently depending on the state of the jog wheel. +When it is used normally, it will change selections and scroll. If it is turned +while pressed, it will either move items, or scroll along a different axis. + +## Who to contact +This device is maintained by Miguel Guthridge. [Email](mailto:hdsq@outlook.com), +Discord: ***HDSQ#2154***. diff --git a/docs/devices/novation/launchkey.mk2.md b/docs/devices/novation/launchkey.mk2.md new file mode 100644 index 00000000..e3e266d8 --- /dev/null +++ b/docs/devices/novation/launchkey.mk2.md @@ -0,0 +1,9 @@ + +# Novation LaunchKey Mk2 + +This device should work out of the box with no additional configuration +required. + +## Who to contact +This device is maintained by Miguel Guthridge. [Email](mailto:hdsq@outlook.com), +Discord: ***HDSQ#2154***. diff --git a/docs/setup.md b/docs/setup.md index 8d25dbdf..7293c679 100644 --- a/docs/setup.md +++ b/docs/setup.md @@ -21,9 +21,9 @@ To install the script, the following steps are recommended: device's tab. 9. Wait 3 seconds - if no errors appear, your device was detected successfully. Feel free to familiarise yourself with your device's specific functionality, - in the (TODO) section. Enjoy using your device! + in the [devices section](devices/devices.md). Enjoy using your device! 10. If you get an error, then your device couldn't be detected. Usually this means that your device doesn't have a definition (I'd love if you - [contributed one](TODO)), but if you're sure your device does, it may just - need some manual configuration. Refer to its manual page in the (TODO) - section. + [contributed one](contributing/contributing.md)), but if you're sure your + device does, it may just need some manual configuration. Refer to its manual + page in the [devices section](devices/devices.md). diff --git a/devices/maudio/hammer88pro/UniversalPreset.Hammer88ProDawPreset b/resources/deviceconfigs/maudio/hammer88pro/UniversalPreset.Hammer88ProDawPreset similarity index 100% rename from devices/maudio/hammer88pro/UniversalPreset.Hammer88ProDawPreset rename to resources/deviceconfigs/maudio/hammer88pro/UniversalPreset.Hammer88ProDawPreset diff --git a/devices/maudio/hammer88pro/UniversalPreset.Hammer88ProUserPreset b/resources/deviceconfigs/maudio/hammer88pro/UniversalPreset.Hammer88ProUserPreset similarity index 100% rename from devices/maudio/hammer88pro/UniversalPreset.Hammer88ProUserPreset rename to resources/deviceconfigs/maudio/hammer88pro/UniversalPreset.Hammer88ProUserPreset From a55d8a560eb380460d543af7f89eb83799940998 Mon Sep 17 00:00:00 2001 From: Miguel Guthridge Date: Sat, 12 Feb 2022 16:31:00 +1100 Subject: [PATCH 03/12] Add style guidelines --- docs/contributing/contributing.md | 4 +++- docs/contributing/style.md | 28 ++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 docs/contributing/style.md diff --git a/docs/contributing/contributing.md b/docs/contributing/contributing.md index 55223509..a06f1801 100644 --- a/docs/contributing/contributing.md +++ b/docs/contributing/contributing.md @@ -1,4 +1,6 @@ # Contributing -TODO +## Getting Started + +* Get familiar with the project's [style guidelines](style.md) diff --git a/docs/contributing/style.md b/docs/contributing/style.md new file mode 100644 index 00000000..bb910702 --- /dev/null +++ b/docs/contributing/style.md @@ -0,0 +1,28 @@ + +# Style Guidelines + +This software is intended to be a collaborative work, and contributions are +welcome from anyone, but in order to maintain the quality of the program, there +are guidelines in place for code style. + +## Code Style + +* Indentation: 4 spaces +* Typing: where it is reasonable, type hints should always be included on + function definitions. The project should remain compliant with MyPy. +* Modules and functions should be documented with docstring. If you use VS Code, + a configuration for the Python Docstring Generator extension is provided which + should be used automatically. For reference, this file is provided in + `resources/docstring_template.mustache`. + +## Development environment + +* I'd recommend working on the project using + [VS Code](https://code.visualstudio.com), with the recommended extensions + installed. This should help ensure that the code is safe and clean. + You can use any editor you see fit, but it may be more difficult to maintain + code style requirements. +* You should work on this project within a + [virtual environment](https://docs.python.org/3/library/venv.html) in order to + avoid the risk of dependency conflicts. Make sure you install the dependencies + from `requirements.txt`. From 65e002e25dceefc1a625632c278838574aef77c0 Mon Sep 17 00:00:00 2001 From: Miguel Guthridge Date: Sat, 12 Feb 2022 17:02:58 +1100 Subject: [PATCH 04/12] Document event patterns --- .vscode/settings.json | 1 + devices/novation/launchkey/mk2/launchkey.py | 4 +-- docs/contributing/contributing.md | 3 ++ docs/contributing/devices.md | 25 ++++++++++++++++ docs/contributing/eventpattern.md | 32 +++++++++++++++++++++ docs/contributing/style.md | 10 ++++--- docs/devices/maudio/hammer88pro.md | 4 +++ 7 files changed, 72 insertions(+), 7 deletions(-) create mode 100644 docs/contributing/devices.md create mode 100644 docs/contributing/eventpattern.md diff --git a/.vscode/settings.json b/.vscode/settings.json index 9797a792..4dc2af2b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -27,6 +27,7 @@ "deviceconfigs", "dicttools", "Dpad", + "eventpattern", "flmidimsg", "ieventpattern", "ivaluestrategy", diff --git a/devices/novation/launchkey/mk2/launchkey.py b/devices/novation/launchkey/mk2/launchkey.py index 34b8f648..d624d71a 100644 --- a/devices/novation/launchkey/mk2/launchkey.py +++ b/devices/novation/launchkey/mk2/launchkey.py @@ -82,9 +82,7 @@ def __init__(self, matcher: BasicControlMatcher) -> None: matcher.addControl(StandardPitchWheel()) matcher.addControl(StandardModWheel()) - super().__init__( - matcher - ) + super().__init__(matcher) @staticmethod def getDrumPadSize() -> tuple[int, int]: diff --git a/docs/contributing/contributing.md b/docs/contributing/contributing.md index a06f1801..936455b8 100644 --- a/docs/contributing/contributing.md +++ b/docs/contributing/contributing.md @@ -4,3 +4,6 @@ ## Getting Started * Get familiar with the project's [style guidelines](style.md) +* This documentation is only of overarching designs and how-tos. Code in the + project is documented using docstring, and will be displayed inline by most + code editors. diff --git a/docs/contributing/devices.md b/docs/contributing/devices.md new file mode 100644 index 00000000..d895f717 --- /dev/null +++ b/docs/contributing/devices.md @@ -0,0 +1,25 @@ + +# Devices + +Device definitions should be contained in the directory +`devices/[manufacturer]/[model]/[generation]`. For example, the definition for +the Novation Launchkey Mk2 series of devices is found in the +`devices/novation/launchkey/mk2` directory. + +Generally, devices are created by defining control surfaces that the device +supports, then adding those controls to a control matcher, before calling the +parent device class to initialise it with that control matcher. + +## Defining a Control + +A control is defined by instantiating a type derived from the ControlSurface +class. Most of these types are quite self-explanatory, for example `StopButton` +represents a stop button. + +When a control is instantiated, it is usually given an +[event pattern](eventpattern.md) used to recognise matching events, and a data +strategy used to extract a value from the event. + +## Registering a Control + +A control can be registered by diff --git a/docs/contributing/eventpattern.md b/docs/contributing/eventpattern.md new file mode 100644 index 00000000..36dac964 --- /dev/null +++ b/docs/contributing/eventpattern.md @@ -0,0 +1,32 @@ + +# Event Patterns + +Import from `common.eventpattern` + +Event patterns are used to recognise events from their MIDI data. The most +common type is the `BasicPattern` which can be used to detect a variety of event +types. + +## `IEventPattern` +The interface used to define an event pattern. If none of the following patterns +match your needs, you can implement a custom pattern by implementing this. + +## `BasicPattern` +A basic event pattern that can recognise most events. + +## `ForwardedPattern` +A pattern used to recognise events that were forwarded from other devices. Its +constructor should be given another event pattern to recognise from. + +## `UnionPattern` +A pattern used to recognise events from the union of multiple patterns. Its +constructor should be given other event patterns to match from. + +## `ForwardedUnionPattern` +Represents a union between an event pattern and the forwarded version of that +pattern. Equivalent to +`UnionPattern(SomePattern(), ForwardedPattern(SomePattern()))`. + +## `NullPattern` +A pattern that won't match with anything. This can be used to instantiate +controls when they are being recognised through other code. diff --git a/docs/contributing/style.md b/docs/contributing/style.md index bb910702..840a2262 100644 --- a/docs/contributing/style.md +++ b/docs/contributing/style.md @@ -10,10 +10,12 @@ are guidelines in place for code style. * Indentation: 4 spaces * Typing: where it is reasonable, type hints should always be included on function definitions. The project should remain compliant with MyPy. -* Modules and functions should be documented with docstring. If you use VS Code, - a configuration for the Python Docstring Generator extension is provided which - should be used automatically. For reference, this file is provided in - `resources/docstring_template.mustache`. +* Modules, classes and functions should be documented with docstring. If you use + VS Code, a configuration for the Python Docstring Generator extension is + provided which should be used automatically. For reference, this file is + provided in `resources/docstring_template.mustache`. Functions that are + documented by a parent class shouldn't be documented again. +* Spell checking your code is recommended. ## Development environment diff --git a/docs/devices/maudio/hammer88pro.md b/docs/devices/maudio/hammer88pro.md index 743c7356..3fb98c1e 100644 --- a/docs/devices/maudio/hammer88pro.md +++ b/docs/devices/maudio/hammer88pro.md @@ -19,6 +19,10 @@ need to be sent to the device. 6. Switch to the DAW tab, then choose "Load Preset" again, selecting the device's configuration again. 7. Use the "Send Preset" tool again to send the configuration to the device. +8. Open FL Studio's MIDI Settings, and set a port for the controller listed as + `MIDIIN3 (Hammer 88 Pro)` in both the input and output sections. Assign this + device to the controller type "Universal Event Forwarded (user)", so that + events from this script can be forwarded to the main script to be processed. ## Usage From 1749b38d57e0bf9a09e27c0470738685ff07fbaf Mon Sep 17 00:00:00 2001 From: Miguel Guthridge Date: Sat, 12 Feb 2022 18:05:49 +1100 Subject: [PATCH 05/12] Document value strategies and control matchers --- .vscode/settings.json | 4 ++- devices/matchers/controlmatcher.py | 3 +- docs/contributing/controlmatcher.md | 34 ++++++++++++++++++ docs/contributing/controlsurface.md | 2 ++ docs/contributing/devices.md | 6 ++-- docs/contributing/eventpattern.md | 6 +++- docs/contributing/valuestrategy.md | 56 +++++++++++++++++++++++++++++ 7 files changed, 105 insertions(+), 6 deletions(-) create mode 100644 docs/contributing/controlmatcher.md create mode 100644 docs/contributing/controlsurface.md create mode 100644 docs/contributing/valuestrategy.md diff --git a/.vscode/settings.json b/.vscode/settings.json index 4dc2af2b..6cac7d96 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -23,6 +23,7 @@ "buttondatastrat", "capsys", "Cimbalom", + "controlsurfaces", "deinitialise", "deviceconfigs", "dicttools", @@ -40,6 +41,7 @@ "Sostenuto", "strat", "sysex", - "Tytel" + "Tytel", + "valuestrategy" ] } diff --git a/devices/matchers/controlmatcher.py b/devices/matchers/controlmatcher.py index 64e2438a..f135a20e 100644 --- a/devices/matchers/controlmatcher.py +++ b/devices/matchers/controlmatcher.py @@ -37,7 +37,8 @@ def matchEvent(self, event: eventData) -> Optional[ControlMapping]: @abstractmethod def getGroups(self) -> set[str]: """ - Return a set of groups for all the control surfaces. + Return a set of groups for all the control surfaces managed by this + matcher. Refer to the documentation for the group property in the ControlSurface type. diff --git a/docs/contributing/controlmatcher.md b/docs/contributing/controlmatcher.md new file mode 100644 index 00000000..723616c1 --- /dev/null +++ b/docs/contributing/controlmatcher.md @@ -0,0 +1,34 @@ + +# Control Matchers + +Control matchers are used to maintain a set of control surfaces, which can +be matched with incoming events. + +## `IControlMatcher` + +The interfaces used by control matchers. If the `BasicControlMatcher` doesn't +suit your needs, you can implement this interface to create your own control +matcher. + +### Methods to Implement +* `matchEvent(self, event: eventData) -> Optional[ControlMapping]`: Given an + event, return a mapping to a matched control, or `None` if there were no + matches. +* `getGroups(self) -> set[str]`: Return the set of control groups this control + matcher uses. +* `getControls(self, group:str=None) -> list[ControlSurface]`: Return a list of + the controls managed by this control matcher. + +## `BasicControlMatcher` + +A basic control matcher that can be used for most devices. It provides various +other methods for managing controls + +* `addControl(self, control: ControlSurface)`: Registers a control surface to + the matcher. +* `addControls(self, controls: list[ControlSurface])`: Registers a list of + control surfaces to the matcher. +* `addSubMatcher(self, matcher: IControlMatcher)`: If a small amount of + complexity is required with control matching, the basic matcher may not be + sufficiently powerful. This function can be used to add another + IControlMatcher to act as a component of this matcher. diff --git a/docs/contributing/controlsurface.md b/docs/contributing/controlsurface.md new file mode 100644 index 00000000..ecd24170 --- /dev/null +++ b/docs/contributing/controlsurface.md @@ -0,0 +1,2 @@ + +# Control Surfaces diff --git a/docs/contributing/devices.md b/docs/contributing/devices.md index d895f717..7f00446b 100644 --- a/docs/contributing/devices.md +++ b/docs/contributing/devices.md @@ -10,15 +10,15 @@ Generally, devices are created by defining control surfaces that the device supports, then adding those controls to a control matcher, before calling the parent device class to initialise it with that control matcher. -## Defining a Control +## Defining a Control Surface A control is defined by instantiating a type derived from the ControlSurface class. Most of these types are quite self-explanatory, for example `StopButton` represents a stop button. When a control is instantiated, it is usually given an -[event pattern](eventpattern.md) used to recognise matching events, and a data -strategy used to extract a value from the event. +[event pattern](eventpattern.md) used to recognise matching events, and a +[value strategy](valuestrategy.md) used to extract a value from the event. ## Registering a Control diff --git a/docs/contributing/eventpattern.md b/docs/contributing/eventpattern.md index 36dac964..0b05f31a 100644 --- a/docs/contributing/eventpattern.md +++ b/docs/contributing/eventpattern.md @@ -9,7 +9,11 @@ types. ## `IEventPattern` The interface used to define an event pattern. If none of the following patterns -match your needs, you can implement a custom pattern by implementing this. +match your needs, you can make a custom pattern by implementing this. + +### Methods to Implement +* `matchEvent(self, event: eventData) -> bool`: Given a MIDI event, return + whether that event matches with the pattern. ## `BasicPattern` A basic event pattern that can recognise most events. diff --git a/docs/contributing/valuestrategy.md b/docs/contributing/valuestrategy.md new file mode 100644 index 00000000..ae1c0292 --- /dev/null +++ b/docs/contributing/valuestrategy.md @@ -0,0 +1,56 @@ + +# Value Strategies + +Import from `controlsurfaces.valuestrategy` + +Value strategies are used to get the value from an event and convert it to a +floating point value for use within the rest of the script. + +## `IValueStrategy` + +The interface used by all value strategies. If none of the following strategies +match your requirements, you can make a custom strategy by implementing this. + +### Functions to Implement + +* `getValueFromEvent(self, event: eventData) -> T`: Returns a value for internal + use with this strategy, given a MIDI event. +* `getValueFromFloat(self, f: float) -> T`: Returns a value for internal use + with this strategy, given a float between 0 and 1. +* `getFloatFromValue(self, value: T) -> float`: Returns a float between 0 and 1 + given the internal value of this strategy. + +## `Data2Strategy` + +Gets the value from the data2 value of the event. Most standard events can use +this strategy. + +## `Data1Strategy` + +Gets the value from the data1 value of the event. This is used by some events +such as channel aftertouch which don't give a data2 value. + +## `ButtonData2Strategy` + +The same as the `Data2Strategy`, but only allowing press (127 -> 1.0) and +release (0 -> 0.0) values. Used for some button types. + +## `ButtonSinglePressStrategy` + +Used for buttons which only send an event when they are pressed. + +## `NullStrategy` + +Used by `NullEvents`. Has no meaningful value. + +## `ForwardedStrategy` + +Used to get values from forwarded events. Another value strategy should be +provided to the constructor. + +## `ForwardedUnionStrategy` + +Used to get values from events that can be either forwarded or not. If an event +is forwarded, a `ForwardedStrategy` will be used. Otherwise, the standard +strategy will be used. Another value strategy should be provided to the +constructor. From 341b3097af1b53b722c3f5e742edcc6e10528f91 Mon Sep 17 00:00:00 2001 From: Miguel Guthridge Date: Sat, 12 Feb 2022 18:33:54 +1100 Subject: [PATCH 06/12] Document control surfaces --- .vscode/settings.json | 1 + controlsurfaces/navigation.py | 10 ++--- docs/contributing/controlmatcher.md | 5 ++- docs/contributing/controlsurface.md | 65 +++++++++++++++++++++++++++++ docs/contributing/devices.md | 7 ++-- docs/contributing/valuestrategy.md | 1 + plugs/special/transport.py | 4 +- 7 files changed, 82 insertions(+), 11 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 6cac7d96..a3eacad8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -23,6 +23,7 @@ "buttondatastrat", "capsys", "Cimbalom", + "controlmatcher", "controlsurfaces", "deinitialise", "deviceconfigs", diff --git a/controlsurfaces/navigation.py b/controlsurfaces/navigation.py index b3f2ac5a..1d19df94 100644 --- a/controlsurfaces/navigation.py +++ b/controlsurfaces/navigation.py @@ -19,12 +19,12 @@ class NavigationControl(ControlSurface): def __init__(self, event_pattern: IEventPattern, value_strategy: IValueStrategy) -> None: super().__init__(event_pattern, value_strategy, "navigation") -class NavigationButtons(Button, NavigationControl): +class NavigationButton(Button, NavigationControl): """ Navigation buttons are used to navigate FL Studio """ -class DpadButtons(NavigationButtons): +class DpadButtons(NavigationButton): """ D-pad buttons are used to navigate FL Studio with directional inputs """ @@ -54,17 +54,17 @@ class DirectionSelect(DpadButtons): A select button (usually in the centre of a d-pad) """ -class NextPrevButtons(NavigationButtons): +class NextPrevButton(NavigationButton): """ Represents next or previous buttons """ -class DirectionNext(NextPrevButtons): +class DirectionNext(NextPrevButton): """ A next button """ -class DirectionPrevious(NextPrevButtons): +class DirectionPrevious(NextPrevButton): """ A previous button """ diff --git a/docs/contributing/controlmatcher.md b/docs/contributing/controlmatcher.md index 723616c1..e1e9dd5b 100644 --- a/docs/contributing/controlmatcher.md +++ b/docs/contributing/controlmatcher.md @@ -31,4 +31,7 @@ other methods for managing controls * `addSubMatcher(self, matcher: IControlMatcher)`: If a small amount of complexity is required with control matching, the basic matcher may not be sufficiently powerful. This function can be used to add another - IControlMatcher to act as a component of this matcher. + IControlMatcher to act as a component of this matcher. An example of this can + be seen in the implementation of the jog wheel on the M-Audio Hammer 88 Pro, + where the sub-matcher is used to make events map to a different type of jog + wheel depending on whether the encoder is pressed down or not. diff --git a/docs/contributing/controlsurface.md b/docs/contributing/controlsurface.md index ecd24170..ae0d6d16 100644 --- a/docs/contributing/controlsurface.md +++ b/docs/contributing/controlsurface.md @@ -1,2 +1,67 @@ # Control Surfaces + +Control surfaces represent a control on a device. The are instantiated during +the construction of `Device` objects, and are mapped to by plugins. + +## List of Control Surfaces + +* `Note`: Represents a note event +* `ModWheel`: Represents a modulation wheel +* `PitchWheel`: Represents a pitch bend wheel +* `AfterTouch`: Represents aftertouch events + * `ChannelAfterTouch` + * `NoteAfterTouch` +* `Pedal`: Represents a foot pedal + * `SustainPedal` + * `SostenutoPedal` + * `SoftPedal` +* `Button`: Represents a button (used by many transport controls) +* `JogWheel`: Represents an encoder used for transport and navigation + * `StandardJogWheel`: Scrolling and changing selection + * `MoveJogWheel`: Moving selection +* `TransportButton`: Buttons used for transport + * `PlayButton` + * `StopButton` + * `LoopButton`: Toggle FL Studio's loop mode + * `RecordButton` + * `FastForwardButton` + * `RewindButton` + * `MetronomeButton` +* `NavigationButton`: Buttons used for navigating FL Studio + * `DpadButtons`: Buttons used for directions + * `DirectionUp` + * `DirectionDown` + * `DirectionLeft` + * `DirectionRight` + * `DirectionSelect` + * `NextPrevButton`: Next and previous buttons + * `DirectionNext` + * `DirectionPrevious` +* `Fader`: Represents a fader (linear slider) +* `Knob`: Represents a knob (rotating dial) +* `Encoder`: Represents an encoder (endlessly rotating dial) +* `DrumPad`: Represents a drum pad + +## Creating New Control Surfaces + +As a general rule of thumb, new control surfaces types shouldn't be created +except for those that don't match any existing types. This should be discussed +in the Discord server before creating one. This is because having more control +surface types will make it harder to assign controls easily within plugins. + +## Extending Existing Control Surfaces + +By default, the provided control surfaces don't provide any advanced +functionality such as colour or annotation support. If a control on your device +supports this, or requires logic that doesn't work well with the parent class +(such as the M-Audio Hammer 88 Pro's pitch wheel), it should implement it in a +child class to the control surface it most accurately represents, and then +implement any required functions. + +### Methods to Implement if Required +* `onColorChange(self)`: Called when the color of the control has changed +* `onAnnotationChange(self)`: Called when the annotation of the control has + changed. +* `onValueChange(self)`: Called when the value of the control has changed. +* `tick(self)`: Called when a tick happens. diff --git a/docs/contributing/devices.md b/docs/contributing/devices.md index 7f00446b..c9aca02e 100644 --- a/docs/contributing/devices.md +++ b/docs/contributing/devices.md @@ -6,9 +6,10 @@ Device definitions should be contained in the directory the Novation Launchkey Mk2 series of devices is found in the `devices/novation/launchkey/mk2` directory. -Generally, devices are created by defining control surfaces that the device -supports, then adding those controls to a control matcher, before calling the -parent device class to initialise it with that control matcher. +Generally, devices are created by defining [control surfaces](controlsurface.md) +that the device supports, then adding those controls to a +[control matcher](controlmatcher.md), before calling the parent device class to +initialise it with that control matcher. ## Defining a Control Surface diff --git a/docs/contributing/valuestrategy.md b/docs/contributing/valuestrategy.md index ae1c0292..c42782dc 100644 --- a/docs/contributing/valuestrategy.md +++ b/docs/contributing/valuestrategy.md @@ -10,6 +10,7 @@ floating point value for use within the rest of the script. The interface used by all value strategies. If none of the following strategies match your requirements, you can make a custom strategy by implementing this. +Note that value strategies should be stateless. ### Functions to Implement diff --git a/plugs/special/transport.py b/plugs/special/transport.py index 40f550e4..a35dd3ae 100644 --- a/plugs/special/transport.py +++ b/plugs/special/transport.py @@ -18,7 +18,7 @@ MoveJogWheel, DirectionNext, DirectionPrevious, - NavigationButtons, + NavigationButton, DirectionUp, DirectionDown, DirectionRight, @@ -47,7 +47,7 @@ def __init__(self, shadow: DeviceShadow) -> None: shadow.bindMatch(RecordButton, self.recButton, raise_on_failure=False) shadow.bindMatch(LoopButton, self.loopButton, raise_on_failure=False) shadow.bindMatch(MetronomeButton, self.metroButton, raise_on_failure=False) - shadow.bindMatches(NavigationButtons, self.navigationButtons, raise_on_failure=False) + shadow.bindMatches(NavigationButton, self.navigationButtons, raise_on_failure=False) @classmethod def create(cls, shadow: DeviceShadow) -> 'SpecialPlugin': From 6dd2a37b55db9ea034576770d72ba61dea3247f0 Mon Sep 17 00:00:00 2001 From: Miguel Guthridge Date: Sat, 12 Feb 2022 18:53:41 +1100 Subject: [PATCH 07/12] Document example device --- .vscode/settings.json | 2 + devices/device.py | 25 +++--- devices/maudio/hammer88pro/hammer88pro.py | 2 +- devices/novation/launchkey/mk2/launchkey.py | 21 ++--- docs/contributing/devices.md | 88 ++++++++++++++++++++- 5 files changed, 113 insertions(+), 25 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index a3eacad8..41f69ad9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -23,6 +23,7 @@ "buttondatastrat", "capsys", "Cimbalom", + "classmethod", "controlmatcher", "controlsurfaces", "deinitialise", @@ -40,6 +41,7 @@ "pmeflags", "RRGGBB", "Sostenuto", + "staticmethod", "strat", "sysex", "Tytel", diff --git a/devices/device.py b/devices/device.py index 87701456..dc2992a2 100644 --- a/devices/device.py +++ b/devices/device.py @@ -76,10 +76,12 @@ def getId() -> str: def getUniversalEnquiryResponsePattern() -> Optional[IEventPattern]: """ Returns the event pattern from which a device can be recognised so that - its representation can be loaded + its representation can be loaded, or None, if this device can't be + matched using this pattern. ### Returns: - * `IEventPattern`: pattern to match universal device enquiry + * `IEventPattern`: pattern to match universal device enquiry, or None if + can't be matched. """ raise NotImplementedError("This method must be overridden by child " "classes") @@ -89,7 +91,7 @@ def getUniversalEnquiryResponsePattern() -> Optional[IEventPattern]: def matchDeviceName(name: str) -> bool: """ Returns whether this device matches the name given, where the name is - the return value of `device.getName()` + the return value of `device.getName()`. This is used as a fallback for matching the device if no universal device enquiry response is given. @@ -103,14 +105,7 @@ def matchDeviceName(name: str) -> bool: raise NotImplementedError("This method must be overridden by child " "classes") - def initialise(self) -> None: - """ - Called when the device is first recognised, and when FL Studio allows - communication. - - Can be overridden by child classes. - """ - + @abstractmethod @staticmethod def getDrumPadSize() -> tuple[int, int]: """ @@ -124,6 +119,14 @@ def getDrumPadSize() -> tuple[int, int]: """ return 0, 0 + def initialise(self) -> None: + """ + Called when the device is first recognised, and when FL Studio allows + communication. + + Can be overridden by child classes. + """ + # def deinitialise(self) -> None: # """ # Called when FL Studio is going to start blocking communication, such as diff --git a/devices/maudio/hammer88pro/hammer88pro.py b/devices/maudio/hammer88pro/hammer88pro.py index c3952f30..73406fa2 100644 --- a/devices/maudio/hammer88pro/hammer88pro.py +++ b/devices/maudio/hammer88pro/hammer88pro.py @@ -143,7 +143,7 @@ def getUniversalEnquiryResponsePattern(): 0x00, # Family code 0x3C, # Family code # Extra details omitted - ] + ] ) @staticmethod diff --git a/devices/novation/launchkey/mk2/launchkey.py b/devices/novation/launchkey/mk2/launchkey.py index d624d71a..434616ab 100644 --- a/devices/novation/launchkey/mk2/launchkey.py +++ b/devices/novation/launchkey/mk2/launchkey.py @@ -3,7 +3,7 @@ from common.eventpattern import BasicPattern from common.types import eventData from common.extensionmanager import ExtensionManager -from controlsurfaces.valuestrategies import Data2Strategy +from controlsurfaces.valuestrategies import Data2Strategy,ButtonData2Strategy from devices import Device, BasicControlMatcher from controlsurfaces.controlgenerators import getNotesAllChannels @@ -49,35 +49,35 @@ def __init__(self, matcher: BasicControlMatcher) -> None: # Transport matcher.addControl(StopButton( BasicPattern(0xB0, 0x72, ...), - Data2Strategy() + ButtonData2Strategy() )) matcher.addControl(PlayButton( BasicPattern(0xB0, 0x73, ...), - Data2Strategy() + ButtonData2Strategy() )) matcher.addControl(LoopButton( BasicPattern(0xB0, 0x74, ...), - Data2Strategy(), + ButtonData2Strategy(), )) matcher.addControl(RecordButton( BasicPattern(0xB0, 0x75, ...), - Data2Strategy() + ButtonData2Strategy() )) matcher.addControl(DirectionNext( BasicPattern(0xB0, 0x66, ...), - Data2Strategy() + ButtonData2Strategy() )) matcher.addControl(DirectionPrevious( BasicPattern(0xB0, 0x67, ...), - Data2Strategy(), + ButtonData2Strategy(), )) matcher.addControl(RewindButton( BasicPattern(0xB0, 0x70, ...), - Data2Strategy(), + ButtonData2Strategy(), )) matcher.addControl(FastForwardButton( BasicPattern(0xB0, 0x71, ...), - Data2Strategy(), + ButtonData2Strategy(), )) matcher.addControl(StandardPitchWheel()) matcher.addControl(StandardModWheel()) @@ -101,10 +101,11 @@ def __init__(self) -> None: Fader( BasicPattern(0xB0, 0x28 + i, ...), Data2Strategy(), - (i, 0) + (0, i) ) ) # Master fader + # TODO: Make this separate matcher.addControl( Fader( BasicPattern(0xB0, 0x07, ...), diff --git a/docs/contributing/devices.md b/docs/contributing/devices.md index c9aca02e..9212c814 100644 --- a/docs/contributing/devices.md +++ b/docs/contributing/devices.md @@ -13,7 +13,7 @@ initialise it with that control matcher. ## Defining a Control Surface -A control is defined by instantiating a type derived from the ControlSurface +A control is defined by instantiating a type derived from the `ControlSurface` class. Most of these types are quite self-explanatory, for example `StopButton` represents a stop button. @@ -21,6 +21,88 @@ When a control is instantiated, it is usually given an [event pattern](eventpattern.md) used to recognise matching events, and a [value strategy](valuestrategy.md) used to extract a value from the event. -## Registering a Control +## Methods to Implement +* `@classmethod create(cls, event: Optional[eventData]) -> Device`: Create an + instance of this device. +* `@staticmethod getId() -> str`: Returns the ID of the device + (`"Manufacturer.Model.Mark.Variant"`). +* `@staticmethod getUniversalEnquiryResponsePattern() -> Optional[IEventPattern]`: + Returns an event pattern used to match the device's response to a universal + device enquiry. +* `@staticmethod matchDeviceName(name: str) -> bool`: Given a device name, + return whether it matches this device. +* `@staticmethod getDrumPadSize() -> int, int`: Return the size of the drum + pad grid in terms of rows, cols. -A control can be registered by +# Methods to Implement if Required +* `initialise(self)`: Called when the device is initialised. +* `tick(self)`: Called when the script ticks. + +## Example Device Definition + +```py +class MyControl(Device): + """ + An example controller for the documentation + """ + + def __init__(self, matcher: BasicControlMatcher) -> None: + + # Notes + matcher.addControls(getNotesAllChannels()) + + # Create knob controls, using a loop + for i in range(8): + matcher.addControl( # Register the control + Knob( + BasicPattern(0xB0, i, ...), # Pattern for event + Data2Strategy(), # Get the value from data 2 + (0, i) # Coordinate should be the index in the loop + ) + ) + + # Add a stop button + matcher.addControl(StopButton( + BasicPattern(0xB0, 0x72, ...), + ButtonData2Strategy() + )) + # Add a standard pitch wheel + matcher.addControl(StandardPitchWheel()) + + # Finally finish the initialisation + super().__init__(matcher) + + @staticmethod + def getDrumPadSize() -> tuple[int, int]: + return 0, 0 # Our controller doesn't have drum pads + + @classmethod + def create(cls, event: Optional[eventData]) -> Device: + return cls() # Our constructor doesn't take any arguments + + @staticmethod + def getId() -> str: + return f"Demo.MyControl.Mk1" # The ID of our controller + + @staticmethod + def getUniversalEnquiryResponsePattern(): + return BasicPattern( + [ + 0xF0, # Sysex start + 0x7E, # Device response + ..., # OS Device ID + 0x06, # Separator + 0x02, # Separator + 0x00, # Manufacturer + 0x77, # Manufacturer + 0x77, # Manufacturer + 0x01, # Family code + 0x4D, # Family code + # Add any other required details + ] + ) + + @staticmethod + def matchDeviceName(name: str) -> bool: + return name == "My Control" +``` From 14ec4e7f68a44d401ccfbba0aa543a585c009228 Mon Sep 17 00:00:00 2001 From: Miguel Guthridge Date: Sat, 12 Feb 2022 19:30:45 +1100 Subject: [PATCH 08/12] Table of contents to docs --- docs/README.md | 5 +++++ docs/contributing/{contributing.md => README.md} | 0 docs/setup.md | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 docs/README.md rename docs/contributing/{contributing.md => README.md} (100%) diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..621afbda --- /dev/null +++ b/docs/README.md @@ -0,0 +1,5 @@ + +# Documentation + +* [Setup Instructions](setup.md) +* [Contributing](contributing/README.md) diff --git a/docs/contributing/contributing.md b/docs/contributing/README.md similarity index 100% rename from docs/contributing/contributing.md rename to docs/contributing/README.md diff --git a/docs/setup.md b/docs/setup.md index 7293c679..1b9d481c 100644 --- a/docs/setup.md +++ b/docs/setup.md @@ -24,6 +24,6 @@ To install the script, the following steps are recommended: in the [devices section](devices/devices.md). Enjoy using your device! 10. If you get an error, then your device couldn't be detected. Usually this means that your device doesn't have a definition (I'd love if you - [contributed one](contributing/contributing.md)), but if you're sure your + [contributed one](contributing/README.md)), but if you're sure your device does, it may just need some manual configuration. Refer to its manual page in the [devices section](devices/devices.md). From f44a37fea2e691f16734c7211ded586ba328b161 Mon Sep 17 00:00:00 2001 From: Miguel Guthridge Date: Sat, 12 Feb 2022 20:49:15 +1100 Subject: [PATCH 09/12] Document device shadow and mapping strategy --- docs/contributing/controlmatcher.md | 2 +- docs/contributing/devices.md | 2 +- docs/contributing/deviceshadow.md | 26 ++++++++++++++++++++++ docs/contributing/mappingstrategy.md | 21 +++++++++++++++++ docs/contributing/plugins.md | 19 ++++++++++++++++ plugs/mappingstrategies/mappingstrategy.py | 2 -- 6 files changed, 68 insertions(+), 4 deletions(-) create mode 100644 docs/contributing/deviceshadow.md create mode 100644 docs/contributing/mappingstrategy.md create mode 100644 docs/contributing/plugins.md diff --git a/docs/contributing/controlmatcher.md b/docs/contributing/controlmatcher.md index e1e9dd5b..41c7ba55 100644 --- a/docs/contributing/controlmatcher.md +++ b/docs/contributing/controlmatcher.md @@ -6,7 +6,7 @@ be matched with incoming events. ## `IControlMatcher` -The interfaces used by control matchers. If the `BasicControlMatcher` doesn't +The interface used by control matchers. If the `BasicControlMatcher` doesn't suit your needs, you can implement this interface to create your own control matcher. diff --git a/docs/contributing/devices.md b/docs/contributing/devices.md index 9212c814..b03c431e 100644 --- a/docs/contributing/devices.md +++ b/docs/contributing/devices.md @@ -34,7 +34,7 @@ When a control is instantiated, it is usually given an * `@staticmethod getDrumPadSize() -> int, int`: Return the size of the drum pad grid in terms of rows, cols. -# Methods to Implement if Required +## Methods to Implement if Required * `initialise(self)`: Called when the device is initialised. * `tick(self)`: Called when the script ticks. diff --git a/docs/contributing/deviceshadow.md b/docs/contributing/deviceshadow.md new file mode 100644 index 00000000..379cec44 --- /dev/null +++ b/docs/contributing/deviceshadow.md @@ -0,0 +1,26 @@ + +# Device Shadows + +A device shadow is an object representing a plugin's own private copy of a +device's state. It allows the plugin to manipulate the device without impacting +the state of other plugins. + +## Methods + +Refer to inline documentation for full descriptions. Note that only methods +relevant to plugin manipulation are listed here. + +* `getControlMatches(control: type[ControlSurface], ...) -> list[ControlShadow]`: + Get a list of controls matching the criteria. +* `getNumControlMatches(control: type[ControlSurface], ...) -> int`: Get the + number of controls matching the criteria. +* `bindControl(control: ControlShadow, bind_to: EventCallback, ...)`: Bind a + callback function to a control. +* `bindControls(controls: list[ControlShadow], bind_to: EventCallback, ...)`: + Bind a callback function to all elements of a list of controls. +* `bindMatch(control: type[ControlSurface], bind_to: EventCallback, ...) -> ` + `bool`: Bind the first matching control to the given callback. Essentially a + shorthand way to get a matching control and bind it. +* `bindMatches(control: type[ControlSurface], bind_to: EventCallback, ...) -> ` + `bool`: Bind the all matching controls to the given callback. Essentially a + shorthand way to get matching controls and bind them. diff --git a/docs/contributing/mappingstrategy.md b/docs/contributing/mappingstrategy.md new file mode 100644 index 00000000..cdc21ce3 --- /dev/null +++ b/docs/contributing/mappingstrategy.md @@ -0,0 +1,21 @@ + +# Mapping Strategies + +Mapping strategies are used to quickly map controls to pre-built functionality. + +## `IMappingStrategy` + +The interface used by mapping strategies. This should be implemented by any +mapping strategies. + +### Methods to Implement +* `apply(shadow: DeviceShadow)`: Apply the mapping to a device shadow. + +## `NoteStrategy` +Maps notes to note events on the channel rack. + +## `PedalStrategy` +Maps pedals to required CC parameters. + +## `WheelStrategy` +Maps pitch and mod wheels to required parameters. diff --git a/docs/contributing/plugins.md b/docs/contributing/plugins.md new file mode 100644 index 00000000..555eefe2 --- /dev/null +++ b/docs/contributing/plugins.md @@ -0,0 +1,19 @@ + +# Plugins + +Plugins are extensions that define how the program should handle events and +interact with FL Studio Windows, as well as generator and effect plugins. + +## Types of Plugins + +* `StandardPlugin`: Standard plugins interact with generators and effects +* `WindowPlugin`: Window plugins interact with FL Studio windows +* `SpecialPlugin`: Plugins that can be active at any time + +## Creating a Plugin + +When a plugin is created, it should bind callback functions to a +[`DeviceShadow`](deviceshadow.md) object, that represents the plugin's own +private copy of the device that is being mapped to. This can either be done +manually, or with [mapping strategies](mappingstrategy.md), given as arguments +to the `super` constructor. diff --git a/plugs/mappingstrategies/mappingstrategy.py b/plugs/mappingstrategies/mappingstrategy.py index f3c23d40..1e0ade04 100644 --- a/plugs/mappingstrategies/mappingstrategy.py +++ b/plugs/mappingstrategies/mappingstrategy.py @@ -13,8 +13,6 @@ class IMappingStrategy: Creates a quick and simple way to map controls to plugin parameters used by many plugins, for example pedal events """ - def __init__(self) -> None: - pass @abstractmethod def apply(self, shadow: DeviceShadow) -> None: From 19e556c0549d65a44515ff153217679520569d1f Mon Sep 17 00:00:00 2001 From: Miguel Guthridge Date: Sat, 12 Feb 2022 21:15:32 +1100 Subject: [PATCH 10/12] Document event filters and plugin example --- .vscode/settings.json | 4 +++ docs/contributing/eventfilter.md | 26 +++++++++++++++ docs/contributing/plugins.md | 55 +++++++++++++++++++++++++++++++- plugs/eventfilters/index.py | 27 +++++++++++++++- plugs/plugin.py | 4 +-- plugs/standard/fl/flex.py | 12 ++++--- 6 files changed, 119 insertions(+), 9 deletions(-) create mode 100644 docs/contributing/eventfilter.md diff --git a/.vscode/settings.json b/.vscode/settings.json index 41f69ad9..098d287a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -28,13 +28,17 @@ "controlsurfaces", "deinitialise", "deviceconfigs", + "deviceshadow", "dicttools", "Dpad", + "eventfilter", + "eventfilters", "eventpattern", "flmidimsg", "ieventpattern", "ivaluestrategy", "launchkey", + "mappingstrategy", "Maudio", "MIDIIN", "Novation", diff --git a/docs/contributing/eventfilter.md b/docs/contributing/eventfilter.md new file mode 100644 index 00000000..f580af9b --- /dev/null +++ b/docs/contributing/eventfilter.md @@ -0,0 +1,26 @@ + +# Event Filters + +Import from `plugs.eventfilters` + +Event filters are decorators that filter out events that shouldn't be processed +by a callback function. They provide a simple way to ensure type safety, and +add simple functionality. + +## `filterButtonLift` +Filter out button events if the button is being lifted. + +## `filterToPluginIndex` +Filter out events when the index is not a plugin + +## `filterToGeneratorIndex` +Filter out events when the index is not a generator plugin + +## `filterToEffectIndex` +Filter out events when the index is not an effect plugin + +## `filterToWindowIndex` +Filter out events when the index is not a window + +## `filterToSafeIndex` +Filter out events when the index is None diff --git a/docs/contributing/plugins.md b/docs/contributing/plugins.md index 555eefe2..384a7271 100644 --- a/docs/contributing/plugins.md +++ b/docs/contributing/plugins.md @@ -16,4 +16,57 @@ When a plugin is created, it should bind callback functions to a [`DeviceShadow`](deviceshadow.md) object, that represents the plugin's own private copy of the device that is being mapped to. This can either be done manually, or with [mapping strategies](mappingstrategy.md), given as arguments -to the `super` constructor. +to the `super` constructor. Callbacks can be decorated using +[event filters](eventfilter.md) to filter out unwanted events. + +## Methods to Implement + +* `@classmethod create(cls, shadow: DeviceShadow) -> Plugin`: Create and return + an instance of this plugin. +* `@staticmethod getPlugIds() -> tuple[str, ...]`: Returns a tuple of the + plugin IDs to associate this plugin with. Only for plugins of type + `StandardPlugin`. +* `@staticmethod getWindowId() -> int`: Returns the ID of the window to + associate this plugin with. Only for plugins of type `WindowPlugin` +* `@staticmethod shouldBeActive() -> bool`: Returns whether this plugin should + be active. Only for plugins of type `SpecialPlugin`. + +## Example Plugin + +```py +class MyPlugin(StandardPlugin): + """ + Used to interact with my imaginary plugin + """ + def __init__(self, shadow: DeviceShadow) -> None: + # Bind faders to myCallback + shadow.bindMatches( + Fader, # Type to bind + self.myCallback, # Function to bind to + target_num=5, # Number of controls to bind + allow_substitution=True, # Substitute faders for other types if needed + raise_on_failure=False # Don't give an error if we can't bind the controls + ) + # Call the super function + super().__init__(shadow, []) + + @classmethod + def create(cls, shadow: DeviceShadow) -> StandardPlugin: + # Create an instance of the plugin + return cls(shadow) + + @staticmethod + def getPlugIds() -> tuple[str, ...]: + # This plugin should map to plugins named MyPlugin + return ("MyPlugin",) + + @filterToGeneratorIndex # Filter out plugins when the active plugin isn't a generator + def myCallback(self, control: ControlShadow, index: GeneratorIndex, *args: Any) -> bool: + # Set the parameter + plugins.setParamValue(control.getCurrentValue(), control.coordinate[1], *index) + # Handle the event + return True + +# Register my plugin +ExtensionManager.registerPlugin(MyPlugin) +``` diff --git a/plugs/eventfilters/index.py b/plugs/eventfilters/index.py index e73d23dc..26893b80 100644 --- a/plugs/eventfilters/index.py +++ b/plugs/eventfilters/index.py @@ -34,7 +34,7 @@ def wrapper(control: ControlShadow, index: UnsafePluginIndex, *args: Any, **kwar def filterToGeneratorIndex(func, method:bool=True): """ - Filter out events when the index is not a plugin + Filter out events when the index is not a generator plugin ### Args: * `func` (`EventCallback`): Function to decorate @@ -57,6 +57,31 @@ def wrapper(control: ControlShadow, index: UnsafePluginIndex, *args: Any, **kwar return func(control, index, *args, **kwargs) return wrapper +def filterToEffectIndex(func, method:bool=True): + """ + Filter out events when the index is not an effect plugin + + ### Args: + * `func` (`EventCallback`): Function to decorate + * `method` (`bool`, optional): Whether to include a self parameter. Defaults + to `True`. + + ### Returns: + * `EventCallback`: decorated function + """ + if method: + def wrapper_method(self, control: ControlShadow, index: UnsafePluginIndex, *args: Any, **kwargs: Any) -> bool: + if not isinstance(index, tuple) or len(index) != 2: + return False + return func(self, control, index, *args, **kwargs) + return wrapper_method + else: + def wrapper(control: ControlShadow, index: UnsafePluginIndex, *args: Any, **kwargs: Any) -> bool: + if not isinstance(index, tuple) or len(index) != 2: + return False + return func(control, index, *args, **kwargs) + return wrapper + def filterToWindowIndex(func, method:bool=True): """ Filter out events when the index is not a window diff --git a/plugs/plugin.py b/plugs/plugin.py index c3d543bd..127204e8 100644 --- a/plugs/plugin.py +++ b/plugs/plugin.py @@ -6,7 +6,7 @@ """ from common import log, verbosity -from common.util.apifixes import UnsafeIndex +from common.util.apifixes import UnsafeIndex, WindowIndex from controlsurfaces import ControlMapping from devices import DeviceShadow from plugs.mappingstrategies import IMappingStrategy @@ -100,7 +100,7 @@ class WindowPlugin(Plugin): @abstractmethod @staticmethod - def getWindowId() -> int: + def getWindowId() -> WindowIndex: """ Returns the ID of the window this class should be associated with. diff --git a/plugs/standard/fl/flex.py b/plugs/standard/fl/flex.py index 5eed7753..4d12b1e0 100644 --- a/plugs/standard/fl/flex.py +++ b/plugs/standard/fl/flex.py @@ -16,11 +16,13 @@ class Flex(StandardPlugin): Used to interact with the Flex plugin """ def __init__(self, shadow: DeviceShadow) -> None: - # Bind a different callback depending on drum pad size - try: - shadow.bindMatches(Fader, self.faders, target_num=8, allow_substitution=True) - except ValueError: - pass + shadow.bindMatches( + Fader, + self.faders, + target_num=8, + allow_substitution=True, + raise_on_failure=False + ) super().__init__(shadow, []) @classmethod From 1c630cdc0543f7430913a66de3bad2dfa6620059 Mon Sep 17 00:00:00 2001 From: Miguel Guthridge Date: Sat, 12 Feb 2022 21:19:24 +1100 Subject: [PATCH 11/12] Improve documentation table of contents --- README.md | 7 ++++++- docs/README.md | 3 ++- docs/devices/{devices.md => README.md} | 0 3 files changed, 8 insertions(+), 2 deletions(-) rename docs/devices/{devices.md => README.md} (100%) diff --git a/README.md b/README.md index 4c014aa7..7ec4cb53 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,11 @@ FL Studio. Refer to the [documentation](docs/setup.md). +# Documentation + +Documentation is available [here](docs/README.md), and contains information for +users, as well as contributors. + If you have any ideas for the development of the script, or want to contribute, -please join the Discord Server [here](https://discord.gg/6vpfJUF). I'm excited +please [join the Discord Server](https://discord.gg/6vpfJUF). I'm excited to see where this project will go in the future: I have huge plans for it! diff --git a/docs/README.md b/docs/README.md index 621afbda..65be8caf 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,4 +2,5 @@ # Documentation * [Setup Instructions](setup.md) -* [Contributing](contributing/README.md) +* [Information for Contributors](contributing/README.md) +* [Device Information](devices/README.md) diff --git a/docs/devices/devices.md b/docs/devices/README.md similarity index 100% rename from docs/devices/devices.md rename to docs/devices/README.md From 28ee0ecf7d864c8a66b2106b2b9713a5d757f0c9 Mon Sep 17 00:00:00 2001 From: Miguel Guthridge Date: Sat, 12 Feb 2022 21:24:51 +1100 Subject: [PATCH 12/12] Add more docs on style guidelines --- .vscode/settings.json | 1 + docs/contributing/style.md | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/.vscode/settings.json b/.vscode/settings.json index 098d287a..7efa1125 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -30,6 +30,7 @@ "deviceconfigs", "deviceshadow", "dicttools", + "docstrings", "Dpad", "eventfilter", "eventfilters", diff --git a/docs/contributing/style.md b/docs/contributing/style.md index 840a2262..07104de0 100644 --- a/docs/contributing/style.md +++ b/docs/contributing/style.md @@ -16,6 +16,12 @@ are guidelines in place for code style. provided in `resources/docstring_template.mustache`. Functions that are documented by a parent class shouldn't be documented again. * Spell checking your code is recommended. +* You should list yourself as the author of any module you work on, including + your name, email (preferably the same as the one associated with your git + activity), and Discord username (if you have one). If your code is user-facing + (eg a plugin or device) or will be used by contributors (eg a control + surface), it is a good idea to add some information on it to these docs, as + well as the standard docstrings within the code. ## Development environment