Skip to content

Commit

Permalink
Merge pull request #23 from MiguelGuthridge/v0.5.2
Browse files Browse the repository at this point in the history
V0.5.2
  • Loading branch information
MaddyGuthridge authored May 1, 2022
2 parents e3a8808 + 0003a37 commit e76995d
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 25 deletions.
7 changes: 6 additions & 1 deletion src/common/activitystate.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
* Miguel Guthridge [[email protected], HDSQ#2154]
"""

from common.profiler import profilerDecoration
from common.logger import log, verbosity
from common.util.apifixes import (
PluginIndex,
Expand All @@ -16,7 +17,8 @@
EffectIndex,
WindowIndex,
)
from common.util.apifixes import getFocusedPluginIndex, getFocusedWindowIndex
from common.util.apifixes import getFocusedPluginIndex, getFocusedWindowIndex,\
reset_generator_active


class ActivityState:
Expand Down Expand Up @@ -71,10 +73,13 @@ def _forcePlugUpdate(self) -> None:
else:
self._effect = plugin # type: ignore

@profilerDecoration("activity.tick")
def tick(self) -> None:
"""
Called frequently when we need to update the current window
"""
# HACK: Fix FL Studio bugs
reset_generator_active()
self._changed = False
if self._doUpdate:
# Manually update plugin using selection
Expand Down
8 changes: 7 additions & 1 deletion src/common/contextmanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
'unsafeResetContext'
]

from .profiler import profilerDecoration
from . import logger
from typing import NoReturn, Optional, Callable, TYPE_CHECKING
from time import time_ns
Expand Down Expand Up @@ -55,7 +56,8 @@ def __init__(self) -> None:
# Set the state of the script to wait for the device to be recognised
self.state: Optional[IScriptState] = None
if self.settings.get("debug.profiling"):
self.profiler: Optional[ProfilerManager] = ProfilerManager()
trace = self.settings.get("debug.exec_tracing")
self.profiler: Optional[ProfilerManager] = ProfilerManager(trace)
else:
self.profiler = None
# Time the device last ticked at
Expand All @@ -65,6 +67,7 @@ def __init__(self) -> None:
self._device: Optional['Device'] = None

@catchStateChangeException
@profilerDecoration("initialise")
def initialise(self, state: IScriptState) -> None:
"""Initialise the controller associated with this context manager.
Expand All @@ -75,6 +78,7 @@ def initialise(self, state: IScriptState) -> None:
state.initialise()

@catchStateChangeException
@profilerDecoration("deinitialise")
def deinitialise(self) -> None:
"""Deinitialise the controller when FL Studio closes or begins a render
"""
Expand All @@ -87,6 +91,7 @@ def deinitialise(self) -> None:

@catchUnsafeOperation
@catchStateChangeException
@profilerDecoration("processEvent")
def processEvent(self, event: EventData) -> None:
"""Process a MIDI event
Expand All @@ -103,6 +108,7 @@ def processEvent(self, event: EventData) -> None:

@catchUnsafeOperation
@catchStateChangeException
@profilerDecoration("tick")
def tick(self) -> None:
"""
Called frequently to allow any required updates to the controller
Expand Down
6 changes: 5 additions & 1 deletion src/common/defaultconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@
# Settings used for debugging
"debug": {
# Whether performance profiling should be enabled
"profiling": False
"profiling": False,
# Whether profiling should print the tracing of profiler contexts
# within the script. Useful for troubleshooting crashes in FL Studio's
# MIDI API. Requires profiling to be enabled.
"exec_tracing": False
},
# Settings used during script initialisation
"bootstrap": {
Expand Down
10 changes: 9 additions & 1 deletion src/common/profiler/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,10 @@ def _getProfileName(n: ProfileNode) -> str:
else:
return ProfilerManager._getProfileName(n.parent) + "." + n.name

def __init__(self) -> None:
def __init__(self, print_traces: bool) -> None:
self._print = print_traces
self._current: Optional[ProfileNode] = None
self._depth = 0
self._max_name = 0
self._totals: dict[str, float] = {}
self._number: dict[str, float] = {}
Expand All @@ -99,6 +101,9 @@ def openProfile(self, name: str):
### Args:
* `name` (`str`): name of profile to open
"""
self._depth += 1
if self._print:
print("+"*self._depth + name)
n = ProfileNode(self._current, name)
if self._current is not None:
self._current.addChild(n)
Expand All @@ -111,6 +116,9 @@ def closeProfile(self):
### Raises:
* `ValueError`: no profile to close
"""
if self._print:
print("-"*self._depth + self._current.name)
self._depth -= 1
self._current.close()
if self._current is None:
raise ValueError("No profile to close")
Expand Down
4 changes: 2 additions & 2 deletions src/common/states/mainstate.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def initialise(self) -> None:
def deinitialise(self) -> None:
pass

@profilerDecoration("tick")
@profilerDecoration("main.tick")
def tick(self) -> None:
with ProfilerContext("Device tick"):
self._device.doTick()
Expand Down Expand Up @@ -95,7 +95,7 @@ def tick(self) -> None:
with ProfilerContext(f"Apply {type(p)}"):
p.apply(thorough=True)

@profilerDecoration("processEvent")
@profilerDecoration("main.processEvent")
def processEvent(self, event: EventData) -> None:
with ProfilerContext("Match event"):
mapping = self._device.matchEvent(event)
Expand Down
65 changes: 46 additions & 19 deletions src/common/util/apifixes.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import playlist

from typing import Union, Optional
from common.profiler import profilerDecoration, ProfilerContext
from common.consts import PARAM_CC_START

GeneratorIndex = tuple[int]
Expand All @@ -29,11 +30,25 @@
UnsafeIndex = Union[UnsafePluginIndex, UnsafeWindowIndex]


# HACK: A terrible horrible no good really bad global variable to make sure
# that we hopefully avoid crashes in getFocusedPluginIndex
generator_previously_active = 0


def reset_generator_active():
"""Horrible hacky function to hopefully work around a bug in FL Studio"""
global generator_previously_active
if generator_previously_active != 0:
generator_previously_active -= 1


@profilerDecoration("getFocusedPluginIndex")
def getFocusedPluginIndex(force: bool = False) -> UnsafePluginIndex:
"""
Fixes the horrible ui.getFocusedFormIndex() function
Values are returned as tuples so that they can be unwrapped when
Values are returned as tuples so that they can be unwrapped when being
passed to other API functions
Args:
* `force` (`bool`, optional): whether to return the selected plugin on the
Expand All @@ -44,49 +59,61 @@ def getFocusedPluginIndex(force: bool = False) -> UnsafePluginIndex:
* `int`: grouped index of a channel rack plugin if one is focused
* `int, int`: index of a mixer plugin if one is focused
"""
# Check if a channel rack plugin is focused
# if ui.getFocused(7):
form_id = ui.getFocusedFormID()

# HACK: Move this elsewhere
global generator_previously_active
with ProfilerContext("getFocused"):
# for i in range(8):
# print(f" {ui.getFocused(i)=}, {i=}")
ui_6 = ui.getFocused(6)
ui_7 = ui.getFocused(7)
# If a mixer plugin is focused
if ui.getFocused(6):
if ui_6:
# HACK: Error checking to hopefully avoid a crash due to bugs in FL
# Studio
if generator_previously_active:
print("getFocusedPluginIndex() crash prevention")
return None
with ProfilerContext("getFocusedFormID @ mixer"):
form_id = ui.getFocusedFormID()
track = form_id // 4194304
slot = (form_id - 4194304 * track) // 65536
return track, slot
# Otherwise, assume that a channel is selected
# Use the channel rack index so that we always have one
elif ui.getFocused(7):
elif ui_7:
generator_previously_active = 3
with ProfilerContext("getFocusedFormID @ cr"):
form_id = ui.getFocusedFormID()
# NOTE: When using groups, ui.getFocusedFormID() returns the index
# respecting groups, instead of the global index, yuck
if form_id == -1:
# Plugin outside current group or invalid
return None
return (form_id,)
else:
generator_previously_active = 3
if force:
return (channels.selectedChannel(),)
with ProfilerContext("selectedChannel"):
ret = (channels.selectedChannel(),)
return ret
else:
return None


@profilerDecoration("getFocusedWindowIndex")
def getFocusedWindowIndex() -> Optional[int]:
"""
Fixes the horrible ui.getFocusedFormIndex() function
Values are returned as tuples so that they can be unwrapped when
Fixes the horrible ui.getFocused() function
Returns:
* `None`: if no window is focused
* `int`: index of window
"""
# Check if a channel rack plugin is focused
if getFocusedPluginIndex() is not None:
return None
else:
ret = ui.getFocusedFormID()
if ret == -1:
return None
return ret
for i in range(5):
if ui.getFocused(i):
return i
return None


# def getPluginName(index: UnsafeIndex) -> str:
# """
Expand Down

0 comments on commit e76995d

Please sign in to comment.