midiscripter 0.2__tar.gz → 0.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. {midiscripter-0.2 → midiscripter-0.4}/PKG-INFO +79 -38
  2. midiscripter-0.4/README.md +105 -0
  3. {midiscripter-0.2 → midiscripter-0.4}/midiscripter/__init__.py +3 -1
  4. midiscripter-0.4/midiscripter/base/__init__.py +1 -0
  5. midiscripter-0.4/midiscripter/base/msg_base.py +114 -0
  6. midiscripter-0.4/midiscripter/base/port_base.py +309 -0
  7. {midiscripter-0.2 → midiscripter-0.4}/midiscripter/cli/starters.py +5 -2
  8. {midiscripter-0.2 → midiscripter-0.4}/midiscripter/file_event/__init__.py +1 -1
  9. {midiscripter-0.2 → midiscripter-0.4}/midiscripter/file_event/file_event_msg.py +16 -8
  10. {midiscripter-0.2 → midiscripter-0.4}/midiscripter/file_event/file_event_port.py +22 -1
  11. {midiscripter-0.2 → midiscripter-0.4}/midiscripter/gui/__init__.py +1 -1
  12. {midiscripter-0.2 → midiscripter-0.4}/midiscripter/gui/app.py +22 -15
  13. {midiscripter-0.2 → midiscripter-0.4}/midiscripter/gui/gui_widgets/button.py +135 -22
  14. {midiscripter-0.2 → midiscripter-0.4}/midiscripter/gui/gui_widgets/gui_msg.py +16 -11
  15. {midiscripter-0.2 → midiscripter-0.4}/midiscripter/gui/gui_widgets/gui_widget_base.py +48 -27
  16. midiscripter-0.4/midiscripter/gui/gui_widgets/layout.py +80 -0
  17. midiscripter-0.4/midiscripter/gui/gui_widgets/list.py +104 -0
  18. {midiscripter-0.2 → midiscripter-0.4}/midiscripter/gui/gui_widgets/mixins.py +7 -8
  19. {midiscripter-0.2 → midiscripter-0.4}/midiscripter/gui/gui_widgets/text.py +44 -6
  20. {midiscripter-0.2 → midiscripter-0.4}/midiscripter/gui/log_widget.py +60 -32
  21. {midiscripter-0.2 → midiscripter-0.4}/midiscripter/gui/main_window.py +14 -14
  22. {midiscripter-0.2 → midiscripter-0.4}/midiscripter/gui/menu_bar.py +43 -59
  23. midiscripter-0.4/midiscripter/gui/ports_widget.py +350 -0
  24. {midiscripter-0.2 → midiscripter-0.4}/midiscripter/keyboard/__init__.py +1 -1
  25. {midiscripter-0.2 → midiscripter-0.4}/midiscripter/keyboard/keyboard_msg.py +23 -15
  26. {midiscripter-0.2 → midiscripter-0.4}/midiscripter/keyboard/keyboard_port.py +61 -21
  27. {midiscripter-0.2 → midiscripter-0.4}/midiscripter/logger/html_sink.py +0 -1
  28. {midiscripter-0.2 → midiscripter-0.4}/midiscripter/logger/log.py +32 -24
  29. midiscripter-0.4/midiscripter/metronome/metronome_port.py +84 -0
  30. {midiscripter-0.2 → midiscripter-0.4}/midiscripter/midi/midi_msg.py +86 -37
  31. {midiscripter-0.2 → midiscripter-0.4}/midiscripter/midi/midi_note_data.py +3 -4
  32. {midiscripter-0.2 → midiscripter-0.4}/midiscripter/midi/midi_port.py +99 -44
  33. {midiscripter-0.2 → midiscripter-0.4}/midiscripter/midi/midi_ports_update.py +14 -15
  34. midiscripter-0.4/midiscripter/mouse/__init__.py +2 -0
  35. midiscripter-0.4/midiscripter/mouse/mouse_msg.py +84 -0
  36. midiscripter-0.4/midiscripter/mouse/mouse_port.py +164 -0
  37. {midiscripter-0.2 → midiscripter-0.4}/midiscripter/osc/osc_msg.py +10 -6
  38. {midiscripter-0.2 → midiscripter-0.4}/midiscripter/osc/osc_port.py +35 -10
  39. {midiscripter-0.2 → midiscripter-0.4}/midiscripter/osc/osc_query_maker.py +0 -1
  40. midiscripter-0.2/midiscripter/base/shared.py → midiscripter-0.4/midiscripter/shared/__init__.py +6 -4
  41. midiscripter-0.4/midiscripter/shared/autostart.py +84 -0
  42. {midiscripter-0.2 → midiscripter-0.4}/pyproject.toml +4 -5
  43. midiscripter-0.2/README.md +0 -63
  44. midiscripter-0.2/midiscripter/base/msg_base.py +0 -95
  45. midiscripter-0.2/midiscripter/base/port_base.py +0 -251
  46. midiscripter-0.2/midiscripter/gui/gui_widgets/__init__.py +0 -0
  47. midiscripter-0.2/midiscripter/gui/gui_widgets/layout.py +0 -63
  48. midiscripter-0.2/midiscripter/gui/gui_widgets/list.py +0 -67
  49. midiscripter-0.2/midiscripter/gui/ports_widget.py +0 -309
  50. midiscripter-0.2/midiscripter/metronome/metronome_port.py +0 -72
  51. {midiscripter-0.2 → midiscripter-0.4}/LICENSE +0 -0
  52. {midiscripter-0.2 → midiscripter-0.4}/midiscripter/cli/__init__.py +0 -0
  53. {midiscripter-0.2/midiscripter/base → midiscripter-0.4/midiscripter/gui/gui_widgets}/__init__.py +0 -0
  54. {midiscripter-0.2 → midiscripter-0.4}/midiscripter/logger/__init__.py +0 -0
  55. {midiscripter-0.2 → midiscripter-0.4}/midiscripter/logger/console_sink.py +0 -0
  56. {midiscripter-0.2 → midiscripter-0.4}/midiscripter/metronome/__init__.py +0 -0
  57. {midiscripter-0.2 → midiscripter-0.4}/midiscripter/midi/__init__.py +0 -0
  58. {midiscripter-0.2 → midiscripter-0.4}/midiscripter/osc/__init__.py +0 -0
  59. {midiscripter-0.2 → midiscripter-0.4}/midiscripter/resources/icon.ico +0 -0
  60. {midiscripter-0.2 → midiscripter-0.4}/midiscripter/resources/icon.svg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: midiscripter
3
- Version: 0.2
3
+ Version: 0.4
4
4
  Summary: Framework for scripting MIDI, keyboard and Open Sound Control input and output
5
5
  Project-URL: Documentation, https://maboroshy.github.io/midi-scripter
6
6
  Project-URL: Issues, https://github.com/Maboroshy/midi-scripter/issues
@@ -175,11 +175,8 @@ License-File: LICENSE
175
175
  Keywords: MIDI,OSC,automation,input,script
176
176
  Classifier: Development Status :: 4 - Beta
177
177
  Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)
178
- Classifier: Operating System :: Microsoft :: Windows
179
- Classifier: Programming Language :: Python
180
178
  Classifier: Programming Language :: Python :: 3.11
181
179
  Classifier: Programming Language :: Python :: 3.12
182
- Classifier: Programming Language :: Python :: Implementation :: CPython
183
180
  Classifier: Topic :: Multimedia :: Sound/Audio
184
181
  Classifier: Topic :: Multimedia :: Sound/Audio :: MIDI
185
182
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
@@ -191,72 +188,116 @@ Requires-Dist: python-osc~=1.7
191
188
  Requires-Dist: python-rtmidi~=1.5
192
189
  Requires-Dist: pywin32; platform_system == 'Windows'
193
190
  Requires-Dist: watchdog~=4.0
191
+ Provides-Extra: dev
192
+ Requires-Dist: ruff; extra == 'dev'
194
193
  Provides-Extra: doc
195
194
  Requires-Dist: griffe-inherited-docstrings; extra == 'doc'
196
195
  Requires-Dist: mkdocs-material; extra == 'doc'
197
196
  Requires-Dist: mkdocstrings; extra == 'doc'
198
197
  Description-Content-Type: text/markdown
199
198
 
200
- # MIDI Scripter
199
+ # <img src="docs/icon.svg" width="24"/> MIDI Scripter
201
200
 
202
- MIDI Scripter is a framework for scripting MIDI, keyboard and Open Sound Control (OSC) input and output with a few lines of Python code.
201
+ MIDI Scripter is a framework filtering, modifying, routing and any other
202
+ scripting for MIDI, Open Sound Control (OSC), keyboard and mouse input and
203
+ output with Python.
203
204
 
204
- MIDI Scripter is intended for digital audio workstation (DAW) controls scripting but can also be used for general input conversion and automation. It fits where controller mappings are not enough but rewriting DAW controller integration is too much.
205
+ MIDI Scripter listens to the input ports and feeds incoming messages to
206
+ subscribed callables (functions, methods, etc.). These callables or any
207
+ other Python code can send modified or created messages with output ports.
208
+ MIDI Scripter can act as a proxy to filter, transform and convert the input.
209
+
210
+ MIDI Scripter includes customizable GUI for message logging, coding
211
+ assistance and GUI controls and indicators to use in scripts.
205
212
 
206
213
  An octave transposer with GUI controls in 10 lines of code:
207
214
 
208
215
  ``` python
209
- from midiscripter import * # safe * import with no bloat
216
+ from midiscripter import *
210
217
 
211
- midi_keyboard = MidiIn('Port name') # GUI will provide you with port names
212
- proxy_output = MidiOut('loopMIDI Port') # using loopMIDI virtual port for output
218
+ midi_keyboard = MidiIn('MIDI Keyboard') # GUI will provide you with port names
219
+ proxy_output = MidiOut('To DAW') # using virtual proxy port for output
213
220
 
214
- # GUI control in a single line, many widget available, custom widgets supported
221
+ # GUI control in a single line
215
222
  octave_selector = GuiButtonSelectorH(('-2', '-1', '0', '+1', '+2'), select='0')
216
223
 
217
224
  @midi_keyboard.subscribe # decorated function will receive port's messages
218
225
  def transpose(msg: MidiMsg) -> None:
219
226
  if msg.type == MidiType.NOTE_ON or msg.type == MidiType.NOTE_OFF: # filter
220
227
  msg.data1 += 12 * int(octave_selector.selected_item_text) # modify
221
- proxy_output.send(msg) # route
228
+ proxy_output.send(msg) # route
222
229
 
223
230
  if __name__ == '__main__': # combine multiple scripts by importing them
224
231
  start_gui() # opens helpful customizable GUI
225
232
  ```
226
233
 
227
- ![Screenshot](https://github.com/Maboroshy/midi-scripter/blob/master/examples/octave_transposer/screenshot.png?raw=true)
234
+ ![Screenshot after some widget arrangement](https://github.com/Maboroshy/midi-scripter/blob/master/examples/octave_transposer/screenshot.png?raw=true)
235
+
236
+ [You can find more examples here.](https://github.com/Maboroshy/midi-scripter/tree/master/examples)
237
+
238
+ [Overview and API documentation available.](https://maboroshy.github.io/midi-scripter)
239
+
240
+ The average measured roundtrip latency for the script above is less than 0.25
241
+ milliseconds.
242
+
243
+ Currently MIDI Scripter is at "beta" development stage. It's fully
244
+ functional but needs more user feedback. It works on Windows and Linux and
245
+ should work on macOS.
246
+
247
+ ## What it can do
228
248
 
229
- [Scripting guide and API documentation available](https://maboroshy.github.io/midi-scripter)
249
+ The basics:
230
250
 
231
- Easy tasks for MIDI Scripter:
232
- 1. Filter, modify and route MIDI, OSC and keyboard messages in any way.
233
- 2. Map MIDI, OSC and keyboard to each other.
234
- 3. Map any Python code to input message.
235
- 4. Make extra "shift" keys to multiply MIDI controls.
236
- 5. Organize mappings into sets / scenes with GUI controls.
237
- 6. Make an extra overlay mappings on top of MIDI controller's default DAW integration by using proxy.
251
+ - Receive MIDI, OSC, keyboard and mouse input messages.
252
+ - Filter, modify and do anything Python can with the messages.
253
+ - Send out modified or generated MIDI, OSC, keyboard and mouse messages.
238
254
 
239
- Complex tasks for MIDI Scripter:
240
- 1. Create and map complex macros involving multiple hardware or virtual MIDI controllers.
241
- 2. Make custom sequencer or MIDI input generator.
242
- 3. Make music education ot trainer GUI application based on MIDI input.
255
+ For performance MIDI setups:
243
256
 
244
- Currently MIDI Scripter is at "beta" development stage. It's fully functional but needs some more testing. It targets only Windows for now but Linux and macOS support will follow.
257
+ - Make extra banks and layers to multiply MIDI controls.
258
+ - Organize mappings into sets / scenes with GUI dashboard.
259
+ - Make an extra overlay mappings on top of MIDI controller's DAW integration by
260
+ using proxies.
261
+
262
+ For software control and automation:
263
+
264
+ - Map or convert the messages to each other with any conditions and logic.
265
+ - Use MIDI controllers or keyboard shortcuts to run any Python code.
266
+ - Use keyboard and mouse macros.
267
+
268
+ For writing MIDI related Python code:
269
+
270
+ - Prepare MIDI, OSC keyboard and mouse inputs and outputs with a single line,
271
+ without boilerplate code.
272
+ - Feed input messages to functions or any callables by decorators.
273
+ - Work with message objects instead of raw data different for each input type.
274
+ - Create GUI widgets with a single line and arrange them with mouse.
275
+ - Write basic MIDI related GUI applications with very little Python code.
245
276
 
246
277
  ## Installation
247
- 1. [Python 3.11+](https://www.python.org/downloads/) with "Add python.exe to PATH" option.
248
- 2. [loopMIDI](https://www.tobias-erichsen.de/software/loopmidi.html) for virtual MIDI output ports on Windows.
249
- 3. `pip install midiscripter` in console.
250
278
 
251
- ## Setup
252
- Run loopMIDI and add virtual ports you want to send MIDI messages. You can enable its autostart option.
279
+ 1. Install [Python 3.11+](https://www.python.org/downloads/) including pip.
280
+ 2. Run `pip install midiscripter`.
281
+
282
+ Extra steps for Windows:
253
283
 
254
- Enable virtual MIDI output ports as MIDI inputs in DAW.
284
+ 1. Enable `Add python .exe to PATH` option in Python installer.
285
+ 2. Install [loopMIDI](https://www.tobias-erichsen.de/software/loopmidi.html)
286
+ and set required virtual MIDI ports inside it.
255
287
 
256
288
  ## Quick Start
257
- 1. Paste [script template](examples/script_template.py) to Python IDE or plain text editor. IDE is greatly recommended.
258
- 2. Run unchanged template script from IDE or by `python .\script_template.py` to open GUI for more info on available ports and their input.
259
- 3. Turn on the ports' checkboxes to enable them and watch the log for input messages.
260
- 4. Click on port names and messages to copy their declarations to the clipboard. Paste the declarations to your script.
261
- 5. Write the functions you need. Subscribe them to messages with `@input_port.subscribe` decorator. Use `log('messages')` for debugging. Use `output_port.send(msg)` to send modified or created messages from a function.
262
- 6. Restart the script from GUI.
289
+
290
+ 1. Paste [script template](examples/script_template.py) to Python IDE or plain
291
+ text editor. IDE is recommended.
292
+ 2. Run template script as-is from IDE or by `python your_script.py` command to
293
+ open GUI for more info on available ports and incoming input.
294
+ 3. Turn on available ports' checkboxes to enable them, watch the log for
295
+ input messages.
296
+ 4. Click on port names and messages in log to copy their declarations to the
297
+ clipboard. Paste the declarations to your script.
298
+ 5. Modify the template function to make it do what you want.
299
+ Use `log('messages')` for debugging.
300
+ 6. Restart the script from GUI to check how in works.
301
+ 7. Write more complex scripts. Use more inputs, outputs and functions
302
+ (callables). Subscribe callables to input messages with
303
+ `@input_port.subscribe` decorator.
@@ -0,0 +1,105 @@
1
+ # <img src="docs/icon.svg" width="24"/> MIDI Scripter
2
+
3
+ MIDI Scripter is a framework filtering, modifying, routing and any other
4
+ scripting for MIDI, Open Sound Control (OSC), keyboard and mouse input and
5
+ output with Python.
6
+
7
+ MIDI Scripter listens to the input ports and feeds incoming messages to
8
+ subscribed callables (functions, methods, etc.). These callables or any
9
+ other Python code can send modified or created messages with output ports.
10
+ MIDI Scripter can act as a proxy to filter, transform and convert the input.
11
+
12
+ MIDI Scripter includes customizable GUI for message logging, coding
13
+ assistance and GUI controls and indicators to use in scripts.
14
+
15
+ An octave transposer with GUI controls in 10 lines of code:
16
+
17
+ ``` python
18
+ from midiscripter import *
19
+
20
+ midi_keyboard = MidiIn('MIDI Keyboard') # GUI will provide you with port names
21
+ proxy_output = MidiOut('To DAW') # using virtual proxy port for output
22
+
23
+ # GUI control in a single line
24
+ octave_selector = GuiButtonSelectorH(('-2', '-1', '0', '+1', '+2'), select='0')
25
+
26
+ @midi_keyboard.subscribe # decorated function will receive port's messages
27
+ def transpose(msg: MidiMsg) -> None:
28
+ if msg.type == MidiType.NOTE_ON or msg.type == MidiType.NOTE_OFF: # filter
29
+ msg.data1 += 12 * int(octave_selector.selected_item_text) # modify
30
+ proxy_output.send(msg) # route
31
+
32
+ if __name__ == '__main__': # combine multiple scripts by importing them
33
+ start_gui() # opens helpful customizable GUI
34
+ ```
35
+
36
+ ![Screenshot after some widget arrangement](https://github.com/Maboroshy/midi-scripter/blob/master/examples/octave_transposer/screenshot.png?raw=true)
37
+
38
+ [You can find more examples here.](https://github.com/Maboroshy/midi-scripter/tree/master/examples)
39
+
40
+ [Overview and API documentation available.](https://maboroshy.github.io/midi-scripter)
41
+
42
+ The average measured roundtrip latency for the script above is less than 0.25
43
+ milliseconds.
44
+
45
+ Currently MIDI Scripter is at "beta" development stage. It's fully
46
+ functional but needs more user feedback. It works on Windows and Linux and
47
+ should work on macOS.
48
+
49
+ ## What it can do
50
+
51
+ The basics:
52
+
53
+ - Receive MIDI, OSC, keyboard and mouse input messages.
54
+ - Filter, modify and do anything Python can with the messages.
55
+ - Send out modified or generated MIDI, OSC, keyboard and mouse messages.
56
+
57
+ For performance MIDI setups:
58
+
59
+ - Make extra banks and layers to multiply MIDI controls.
60
+ - Organize mappings into sets / scenes with GUI dashboard.
61
+ - Make an extra overlay mappings on top of MIDI controller's DAW integration by
62
+ using proxies.
63
+
64
+ For software control and automation:
65
+
66
+ - Map or convert the messages to each other with any conditions and logic.
67
+ - Use MIDI controllers or keyboard shortcuts to run any Python code.
68
+ - Use keyboard and mouse macros.
69
+
70
+ For writing MIDI related Python code:
71
+
72
+ - Prepare MIDI, OSC keyboard and mouse inputs and outputs with a single line,
73
+ without boilerplate code.
74
+ - Feed input messages to functions or any callables by decorators.
75
+ - Work with message objects instead of raw data different for each input type.
76
+ - Create GUI widgets with a single line and arrange them with mouse.
77
+ - Write basic MIDI related GUI applications with very little Python code.
78
+
79
+ ## Installation
80
+
81
+ 1. Install [Python 3.11+](https://www.python.org/downloads/) including pip.
82
+ 2. Run `pip install midiscripter`.
83
+
84
+ Extra steps for Windows:
85
+
86
+ 1. Enable `Add python .exe to PATH` option in Python installer.
87
+ 2. Install [loopMIDI](https://www.tobias-erichsen.de/software/loopmidi.html)
88
+ and set required virtual MIDI ports inside it.
89
+
90
+ ## Quick Start
91
+
92
+ 1. Paste [script template](examples/script_template.py) to Python IDE or plain
93
+ text editor. IDE is recommended.
94
+ 2. Run template script as-is from IDE or by `python your_script.py` command to
95
+ open GUI for more info on available ports and incoming input.
96
+ 3. Turn on available ports' checkboxes to enable them, watch the log for
97
+ input messages.
98
+ 4. Click on port names and messages in log to copy their declarations to the
99
+ clipboard. Paste the declarations to your script.
100
+ 5. Modify the template function to make it do what you want.
101
+ Use `log('messages')` for debugging.
102
+ 6. Restart the script from GUI to check how in works.
103
+ 7. Write more complex scripts. Use more inputs, outputs and functions
104
+ (callables). Subscribe callables to input messages with
105
+ `@input_port.subscribe` decorator.
@@ -1,8 +1,10 @@
1
- from .base.shared import restart_script, run_after_ports_opened
1
+ from .shared import restart_script, run_after_ports_opened
2
+ from .base import *
2
3
  from .cli import *
3
4
  from .file_event import *
4
5
  from .gui import *
5
6
  from .keyboard import *
7
+ from .mouse import *
6
8
  from .logger import *
7
9
  from .metronome import *
8
10
  from .midi import *
@@ -0,0 +1 @@
1
+ from .msg_base import Msg, Not
@@ -0,0 +1,114 @@
1
+ import enum
2
+ from typing import Any
3
+ from collections.abc import Container
4
+
5
+ import midiscripter.shared
6
+ from midiscripter.base.port_base import Input
7
+
8
+
9
+ class Not:
10
+ def __init__(self, value: Container | Any):
11
+ self.value = value
12
+
13
+
14
+ class AttrEnum(enum.StrEnum):
15
+ def __repr__(self):
16
+ return f'{self.__class__.__name__}({self.value.__repr__()})'
17
+
18
+
19
+ class Msg:
20
+ """The data object generated by input port that is sent as an argument to its subscribed calls
21
+ and can be sent with an output port"""
22
+
23
+ type: str
24
+ """Message type description for filtering and representation"""
25
+
26
+ ctime: float
27
+ """Message creation time in ["Unix time"](https://wikipedia.org/wiki/Unix_time) format"""
28
+
29
+ source: Input
30
+ """Input port instance that generated the message"""
31
+
32
+ __match_args__: tuple[str] = ('type',)
33
+
34
+ def __init__(self, type: str, source: Input | None = None):
35
+ """
36
+ Args:
37
+ source: Input port instance that generated the message
38
+ """
39
+ self.type = type
40
+ self.source = source
41
+ self.ctime = midiscripter.shared.precise_epoch_time()
42
+
43
+ self.ctime: float # workaround for mkdocstrings issue #607
44
+ """Message creation time in ["Unix time"](https://wikipedia.org/wiki/Unix_time) format"""
45
+
46
+ def __repr__(self):
47
+ return f'{self.__class__.__name__}({", ".join(repr(value) for value in self.__as_tuple())})'
48
+
49
+ def __str__(self):
50
+ return ' | '.join(str(value) for value in self.__as_tuple() if value is not None)
51
+
52
+ def __eq__(self, other_msg: 'Msg'):
53
+ return type(self) is type(other_msg) and self.__as_tuple() == other_msg.__as_tuple()
54
+
55
+ def matches(self, *conditions_args, **conditions_kwargs) -> bool:
56
+ """Checks if message's attributes match all provided attribute conditions:
57
+
58
+ 1. If condition is `None` or omitted, it matches anything.
59
+
60
+ 2. If condition equals attribute, it matches the attribute.
61
+
62
+ 3. If condition is a container and contains the attribute, it matches the attribute.
63
+
64
+ Use `Not(condition)` to invert condition matching.
65
+
66
+ Returns:
67
+ `True` if all attributes match, `False` if any does not match
68
+ """
69
+ attr_conditions = dict(zip(self.__match_args__, conditions_args, strict=False))
70
+ attr_conditions.update(conditions_kwargs)
71
+
72
+ for parameter_name, condition in attr_conditions.items():
73
+ if condition is None:
74
+ continue
75
+
76
+ try:
77
+ attr = getattr(self, parameter_name)
78
+ except AttributeError:
79
+ if isinstance(condition, Not):
80
+ continue
81
+ else:
82
+ return False
83
+
84
+ if attr == condition:
85
+ if isinstance(condition, Not):
86
+ return False
87
+ else:
88
+ continue
89
+
90
+ try:
91
+ if attr in condition and not isinstance(condition, str):
92
+ if isinstance(condition, Not):
93
+ return False
94
+ else:
95
+ continue
96
+ except TypeError: # condition is not a container
97
+ pass
98
+
99
+ return False
100
+ return True
101
+
102
+ @property
103
+ def _age_ms(self) -> float:
104
+ """Time passed since message creation in milliseconds"""
105
+ # Using faster rounding
106
+ return int((midiscripter.shared.precise_epoch_time() - self.ctime) * 1000000) / 1000
107
+
108
+ def __as_tuple(self) -> tuple:
109
+ """Converts message to a tuple based on message's __match_args__ class attribute
110
+
111
+ Returns:
112
+ Tuple with values of attributes specified in __match_args__ class attribute
113
+ """
114
+ return tuple(getattr(self, attr_name) for attr_name in self.__match_args__)