Skip to content

Commit

Permalink
Merge pull request #6 from MiguelGuthridge/migue/features
Browse files Browse the repository at this point in the history
Update to v0.3
  • Loading branch information
MaddyGuthridge authored Feb 28, 2022
2 parents 603b786 + 1d9a238 commit 82089a8
Show file tree
Hide file tree
Showing 80 changed files with 1,609 additions and 435 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -127,4 +127,6 @@ dmypy.json

# Pyre type checker
.pyre/
temp.py

# Temporary files
temp.*
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,15 @@
"flmidimsg",
"ieventpattern",
"ivaluestrategy",
"matttytel",
"launchkey",
"mappingstrategy",
"matttytel",
"Maudio",
"MIDIIN",
"Novation",
"pmeflags",
"RRGGBB",
"sorta",
"Sostenuto",
"staticmethod",
"strat",
Expand Down
10 changes: 6 additions & 4 deletions common/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,17 @@
import general
from . import consts
if general.getVersion() < consts.MIN_API_VERSION:
raise RuntimeError(
f"Your FL Studio version is out of date: expected API "
f"v{consts.MIN_API_VERSION}, got {general.getVersion()}"
)
# raise RuntimeError(
# f"Your FL Studio version is out of date: expected API "
# f"v{consts.MIN_API_VERSION}, got {general.getVersion()}"
# )
pass
del general

from .consts import getVersionString

from .logger import log, verbosity
from .profiler import ProfilerContext, profilerDecoration

from .contextmanager import getContext, resetContext, catchContextResetException

Expand Down
16 changes: 16 additions & 0 deletions common/activitystate.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ def __init__(self) -> None:
self._effect: EffectIndex = (0, 0)
self._plugin: PluginIndex = self._generator
self._plug_active = True if self._plugin is not None else False
self._changed = False

def printUpdate(self):
print(f"Window: {self._window}, Plugin: {self._plugin}")
Expand All @@ -51,14 +52,19 @@ def tick(self) -> None:
"""
Called frequently when we need to update the current window
"""
self._changed = False
if self._doUpdate:
# Manually update plugin using selection
if (window := getFocusedWindowIndex()) is not None:
if window != self._window:
self._changed = True
self._window = window
if not self._split:
self._plug_active = False
self._forcePlugUpdate()
elif (plugin := getFocusedPluginIndex()) is not None:
if plugin != self._plugin:
self._changed = True
self._plugin = plugin
# Ignore typing because len(plugin) doesn't narrow types in mypy
if len(plugin) == 1:
Expand All @@ -70,6 +76,15 @@ def tick(self) -> None:
else:
self._forcePlugUpdate()

def hasChanged(self) -> bool:
"""
Returns whether the active plugin has changed in the last tick
### Returns:
* `bool`: whether the active plugin changed
"""
return self._changed

def getActive(self) -> UnsafeIndex:
"""
Returns the currently active window or plugin
Expand Down Expand Up @@ -166,5 +181,6 @@ def toggleWindowsPlugins(self, value: bool = None) -> bool:
raise ValueError("Can't toggle between windows and plugins unless "
"they are being addressed independently")
else:
self._changed = True
self._plug_active = not self._plug_active if value is None else value
return self._plug_active
6 changes: 5 additions & 1 deletion common/consts.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"""

# Version info
VERSION = (0, 2, 1)
VERSION = (0, 3, 0)

# Sub versions
VERSION_MAJOR = VERSION[0]
Expand Down Expand Up @@ -72,5 +72,9 @@ def getVersionString() -> str:
'.''
"""

# Device type constants
DEVICE_TYPE_CONTROLLER = 1
DEVICE_TYPE_FORWARDER = 2

# The starting point for control change parameters in plugins
PARAM_CC_START = 4096
46 changes: 45 additions & 1 deletion common/contextmanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@
]

from typing import NoReturn, Optional, Callable
from time import time_ns

from .settings import Settings
from .activitystate import ActivityState

from .util.misc import NoneNoPrintout
from .util.events import isEventForwarded, isEventForwardedHere
from .types import EventData
from .profiler import ProfilerManager

from .states import (
IScriptState,
Expand All @@ -45,6 +47,14 @@ def __init__(self) -> None:
self.active = ActivityState()
# Set the state of the script to wait for the device to be recognised
self.state: IScriptState = WaitingForDevice()
if self.settings.get("debug.profiling"):
self.profiler: Optional[ProfilerManager] = ProfilerManager()
else:
self.profiler = None
# Time the device last ticked at
self._last_tick = time_ns()
self._ticks = 0
self._dropped_ticks = 0

@catchStateChangeException
def initialise(self) -> None:
Expand All @@ -70,8 +80,42 @@ def tick(self) -> None:
"""
Called frequently to allow any required updates to the controller
"""
self.state.tick()
# Update number of ticks
self._ticks += 1
# If the last tick was over 60 ms ago, then our script is getting laggy
# Skip this tick to compensate
last_tick = self._last_tick
self._last_tick = time_ns()
if (self._last_tick - last_tick) / 1_000_000 > 60:
self._dropped_ticks += 1
return
# Tick active plugin
self.active.tick()
# The tick the current script state
self.state.tick()

def getTickNumber(self) -> int:
"""
Returns the tick number of the script
This is the number of times the script has been ticked
### Returns:
* `int`: tick number
"""
return self._ticks

def getDroppedTicks(self) -> str:
"""
Returns the number of ticks dropped by the controller
This is a good indicator of script performance
### Returns:
* `str`: info on dropped ticks
"""
percent = int(self._dropped_ticks / self._ticks * 100)
return f"{self._dropped_ticks} dropped ticks ({percent}%)"

def setState(self, new_state: IScriptState) -> NoReturn:
"""
Expand Down
8 changes: 8 additions & 0 deletions common/defaultconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@
# Maximum verbosity for which watched categories of logged messages
# will be printed
"max_watched_verbosity": verbosity.INFO,
# Verbosity levels at or above this will be discarded entirely be the
# logger to improve performance
"discard_verbosity": verbosity.NOTE,
},
# Settings used for debugging
"debug": {
# Whether performance profiling should be enabled
"profiling": False
},
# Settings used during script initialisation
"bootstrap": {
Expand Down
11 changes: 3 additions & 8 deletions common/eventpattern/basicpattern.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

from typing import TYPE_CHECKING, Any, Callable, Optional

from common.types import EventData
from common.types.eventdata import EventData, isEventStandard, isEventSysex
from . import ByteMatch, IEventPattern

class BasicPattern(IEventPattern):
Expand Down Expand Up @@ -140,7 +140,7 @@ def _matchSysex(self, event: 'EventData') -> bool:
"""
Matcher function for sysex events
"""
if event.sysex is None:
if not isEventSysex(event):
return False
# If we have more sysex data than them, it can't possibly be a match
if len(self.sysex) > len(event.sysex):
Expand All @@ -151,13 +151,8 @@ def _matchStandard(self, event: 'EventData') -> bool:
"""
Matcher function for standard events
"""
if (
event.status is None
):
if not isEventStandard(event):
return False
if TYPE_CHECKING:
assert event.data1 is not None
assert event.data2 is not None
return all(self._matchByte(expected, actual) for expected, actual in
zip(
[self.status, self.data1, self.data2],
Expand Down
4 changes: 2 additions & 2 deletions common/eventpattern/forwardedpattern.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

from typing import TYPE_CHECKING

from common.util.events import eventFromForwarded, isEventForwardedHereFrom, eventToString
from common.util.events import decodeForwardedEvent, isEventForwardedHereFrom, eventToString
from . import IEventPattern, UnionPattern

from common.types import EventData
Expand Down Expand Up @@ -43,7 +43,7 @@ def matchEvent(self, event: 'EventData') -> bool:
# Extract the event and determine if it matches with the
# underlying pattern
# print(eventToString(eventFromForwarded(event, null+2)))
return self._pattern.matchEvent(eventFromForwarded(event))
return self._pattern.matchEvent(decodeForwardedEvent(event))

class ForwardedUnionPattern(IEventPattern):
"""
Expand Down
20 changes: 10 additions & 10 deletions common/extensionmanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,11 +238,11 @@ def getPluginById(cls, id: str, device: 'Device') -> Optional['StandardPlugin']:
return cls._instantiated_plugins[id]
# Plugin doesn't exist
else:
log(
"extensions.manager",
f"No plugins associated with plugin ID '{id}'",
verbosity=verbosity.NOTE
)
# log(
# "extensions.manager",
# f"No plugins associated with plugin ID '{id}'",
# verbosity=verbosity.NOTE
# )
return None

@classmethod
Expand Down Expand Up @@ -271,11 +271,11 @@ def getWindowById(cls, id: int, device: 'Device') -> Optional['WindowPlugin']:
return cls._instantiated_windows[id]
# Plugin doesn't exist
else:
log(
"extensions.manager",
f"No plugins associated with window ID '{id}'",
verbosity=verbosity.NOTE
)
# log(
# "extensions.manager",
# f"No plugins associated with window ID '{id}'",
# verbosity=verbosity.NOTE
# )
return None

@classmethod
Expand Down
9 changes: 8 additions & 1 deletion common/logger/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
]

from .logitem import LogItem
from .verbosity import Verbosity, DEFAULT, ERROR
from .verbosity import Verbosity, DEFAULT, ERROR, NOTE

from ..util.misc import NoneNoPrintout

Expand Down Expand Up @@ -219,6 +219,13 @@ def __call__(self, category: str, msg: str, verbosity: Verbosity = DEFAULT, deta
* `msg` (`str`): message to log
* `verbosity` (`Verbosity`, optional): verbosity to log under. Defaults to `DEFAULT`.
"""
import common
try:
discarded = common.getContext().settings.get("logger.critical_verbosity")
except common.contextmanager.MissingContextException:
discarded = NOTE
if verbosity > discarded:
return
# TODO: Maybe get traceback
item = LogItem(category, msg, detailed_msg, verbosity, len(self._history))
self._history.append(item)
Expand Down
1 change: 1 addition & 0 deletions common/logger/verbosity.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
__all__ = [
'Verbosity',
'MOST_VERBOSE',
'EVENT',
'NOTE',
'INFO',
"WARNING",
Expand Down
8 changes: 8 additions & 0 deletions common/profiler/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"""
common > profiler
Contains code used to profile the performance of the device
"""

from .profilecontext import ProfilerContext, profilerDecoration
from .manager import ProfilerManager
Loading

0 comments on commit 82089a8

Please sign in to comment.