Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 12 additions & 39 deletions .github/workflows/validate_and_release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ on:
jobs:
lint:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v3
- uses: awalsh128/cache-apt-pkgs-action@latest
Expand All @@ -17,63 +18,35 @@ jobs:
version: 1.0
- uses: actions/setup-python@v4
with:
python-version: 3.8
python-version: 3.11
cache: pip

- run: python -m pip install poetry
- run: make install
- run: make lint

check-types:
runs-on: macos-latest
timeout-minutes: 30
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Set up Homebrew
id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master

- uses: actions/checkout@v3
- uses: awalsh128/cache-apt-pkgs-action@latest
with:
packages: libasound2-dev
version: 1.0
- uses: actions/setup-python@v4
with:
python-version: 3.8
python-version: 3.11
cache: pip

- run: python -m pip install poetry
- run: make install

# LLM_TODO: The above cacheable actions create and populate the
# "__ext__/System_MIDIRemoteScripts/" directory. This content can be cached based
# on the Ableton Live version, which can be retrieved using `brew info --cask
# --json=v2 ableton-live-lite | jq -r '.casks[0].version'`. Rewrite the actions so
# that they only run if necessary, and cache the generated decompilation
# directory between runs.

# We can cache the decompiled remote scripts based on the Live version, to avoid
# the expensive installation/decompilation process if possible.
- name: Get Ableton Live version
id: get-ableton-live-version
run: echo "version=$(brew info --cask --json=v2 ableton-live-lite | jq -r '.casks[0].version')" >> $GITHUB_OUTPUT

- name: Cache Ableton Live decompilation
uses: actions/cache@v3
id: cache-decompilation
with:
path: __ext__/System_MIDIRemoteScripts/
key: ableton-live-decompilation-${{ steps.get-ableton-live-version.outputs.version }}

- name: Install Ableton Live
if: steps.cache-decompilation.outputs.cache-hit != 'true'
run: brew install --cask ableton-live-lite

- name: Decompile system remote scripts libraries
if: steps.cache-decompilation.outputs.cache-hit != 'true'
run: make decompile

# Run the command manually (not using `make`) to avoid failures if Live isn't installed.
- run: poetry run pyright .
- run: make check

release:
name: Publish release to GitHub
runs-on: ubuntu-latest
timeout-minutes: 15
concurrency: release
environment:
name: release
Expand Down
3 changes: 0 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@ __pycache__/
# Virtual environment.
/.venv/

# Decompiled system scripts.
/__ext__/System_MIDIRemoteScripts/

# Markers for Makefile tasks which would otherwise have no artifacts.
/.make.*

Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "__ext__/AbletonLive12_MIDIRemoteScripts"]
path = __ext__/AbletonLive12_MIDIRemoteScripts
url = https://github.com/gluon/AbletonLive12_MIDIRemoteScripts.git
29 changes: 6 additions & 23 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,6 @@ default: lint check
.PHONY: install
install: .make.install

.PHONY: decompile
decompile: __ext__/System_MIDIRemoteScripts/.make.decompile

.PHONY: lint
lint: .make.install
$(POETRY) run ruff format --check .
Expand All @@ -28,7 +25,7 @@ format: .make.install
$(POETRY) run ruff check --fix .

.PHONY: check
check: .make.install __ext__/System_MIDIRemoteScripts/.make.decompile
check: .make.install __ext__/AbletonLive12_MIDIRemoteScripts/README.md
$(POETRY) run pyright .

.PHONY: test
Expand All @@ -41,32 +38,18 @@ img: .make.install

.PHONY: clean
clean:
rm -rf __ext__/System_MIDIRemoteScripts/
# The .venv folder gets created by poetry (because virtualenvs.in-project is enabled).
rm -rf .venv/
rm -f .make.install
rm -f .make.*

# Proxy target for the remote scripts submodule.
__ext__/AbletonLive12_MIDIRemoteScripts/README.md: .gitmodules
git submodule update --init "$(@D)"

# Set files with different configurations for testing.
$(TEST_PROJECT_DIR)/%.als: .make.install $(TEST_PROJECT_DIR)/create_set.py
$(POETRY) run python $(TEST_PROJECT_DIR)/create_set.py $*

__ext__/System_MIDIRemoteScripts/.make.decompile: $(SYSTEM_MIDI_REMOTE_SCRIPTS_DIR) | .make.install
# Sanity check before rm'ing.
@if [ -z "$(@D)" ]; then \
echo "Sanity check failed: compile dir is not set"; \
exit 1; \
fi
rm -rf $(@D)/
mkdir -p $(@D)/ableton/
@if [ -z $(SYSTEM_MIDI_REMOTE_SCRIPTS_DIR) ]; then \
echo "System remote scripts directory not found" ; \
exit 1; \
fi
@if [ ! -d $(SYSTEM_MIDI_REMOTE_SCRIPTS_DIR) ]; then \
echo "The specified remote scripts directory ("$(SYSTEM_MIDI_REMOTE_SCRIPTS_DIR)") does not exist"; \
exit 1; \
fi

# decompyle3 works for most files, and the ones where it doesn't don't
# matter for our purposes.
$(POETRY) run decompyle3 -r -o $(@D)/ableton/ $(SYSTEM_MIDI_REMOTE_SCRIPTS_DIR)/ableton/
Expand Down
1 change: 1 addition & 0 deletions __ext__/AbletonLive12_MIDIRemoteScripts
4 changes: 2 additions & 2 deletions control_surface/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from contextlib import contextmanager
from functools import partial

from ableton.v3.base import const, depends, inject, listens, task
from ableton.v3.base import const, depends, inject, task
from ableton.v3.control_surface import (
ControlSurface,
ControlSurfaceSpecification,
Expand All @@ -30,7 +30,7 @@
from .display import display_specification
from .elements import NUM_GRID_COLS, NUM_ROWS, Elements
from .hardware import HardwareComponent
from .live import lazy_attribute
from .live import lazy_attribute, listens
from .mappings import (
DISABLED_MODE_NAME,
STANDALONE_INIT_MODE_NAME,
Expand Down
3 changes: 2 additions & 1 deletion control_surface/channel_strip.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import logging
from enum import Enum

from ableton.v3.base import listens
from ableton.v3.control_surface.components.channel_strip import (
ChannelStripComponent as ChannelStripComponentBase,
)
from ableton.v3.control_surface.controls import MappedControl
from ableton.v3.live import liveobj_valid

from .live import listens

logger = logging.getLogger(__name__)


Expand Down
3 changes: 2 additions & 1 deletion control_surface/clip_slot.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
from logging import getLogger
from typing import Optional

from ableton.v3.base import depends, listens
from ableton.v3.base import depends
from ableton.v3.control_surface.components import (
ClipSlotComponent as ClipSlotComponentBase,
)

from .configuration import Configuration
from .live import listens
from .types import ClipSlotAction

logger = getLogger(__name__)
Expand Down
10 changes: 9 additions & 1 deletion control_surface/display.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
from typing import Any, Dict, Optional, Union

from ableton.v3.control_surface.display import (
DefaultNotifications,
DefaultNotifications as __DefaultNotifications,
)
from ableton.v3.control_surface.display import (
DisplaySpecification,
Event,
State,
Expand Down Expand Up @@ -106,6 +108,12 @@ def _slider_value_notification(value: str):
return NotificationData(text=_right_align("", value), flash_on_repeat=False)


# The type-checker gets confused by the notifications inheritance structure, and thinks
# inner classes like `DefaultNotifications.Clip` are undefined. Just force it to ignore
# such checks by assigning to an `Any`.
DefaultNotifications: Any = __DefaultNotifications


class Notifications(DefaultNotifications):
class Clip(DefaultNotifications.Clip):
quantize = _quantize_notification
Expand Down
4 changes: 2 additions & 2 deletions control_surface/elements/elements.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from ableton.v2.control_surface.defaults import (
TIMER_DELAY,
)
from ableton.v3.base import clamp, depends, flatten
from ableton.v3.base import clamp, depends
from ableton.v3.control_surface import (
MIDI_CC_TYPE,
ControlElement,
Expand All @@ -31,7 +31,7 @@
)

from .. import sysex
from ..live import lazy_attribute
from ..live import flatten, lazy_attribute
from ..types import KeySafetyStrategy, TypedDict
from .button import LightedButtonElement
from .display import DisplayElement
Expand Down
4 changes: 2 additions & 2 deletions control_surface/elements/light.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@

from ableton.v2.control_surface import MIDI_INVALID_TYPE
from ableton.v2.control_surface.elements import ButtonElementMixin
from ableton.v3.base import EventObject, listens, memoize, task
from ableton.v3.base import EventObject, task
from ableton.v3.control_surface.elements import ButtonElement, Color
from ableton.v3.control_surface.midi import CC_STATUS

from ..colors import OFF, ColorInterfaceMixin, Skin
from ..live import lazy_attribute
from ..live import lazy_attribute, listens, memoize
from .compound import TransitionalProcessedValueElement

logger = getLogger(__name__)
Expand Down
4 changes: 2 additions & 2 deletions control_surface/elements/slider.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@

from ableton.v2.base import linear
from ableton.v2.control_surface.defaults import TIMER_DELAY
from ableton.v3.base import clamp, listens, nop, task
from ableton.v3.base import clamp, nop, task
from ableton.v3.control_surface import InputControlElement
from ableton.v3.control_surface.display import Renderable

from ..live import lazy_attribute
from ..live import lazy_attribute, listens
from ..xy import get_xy_value
from .light import LightedTransitionalProcessedValueElement

Expand Down
13 changes: 12 additions & 1 deletion control_surface/live.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,15 @@
# This file exports elements of the Live API for which we want to
# provide more specific types than the ones inferred by the type
# checker. Types are specified in the associated .pyi file.
from ableton.v3.base import lazy_attribute # noqa: F401
#
# Note the type-checker sees some of these as missing imports due to issues in the
# decompiled types, but in practice they're available.
#
# type: ignore
from ableton.v3.base import (
find_if, # noqa: F401
flatten, # noqa: F401
lazy_attribute, # noqa: F401
listens, # noqa: F401
memoize, # noqa: F401
)
12 changes: 12 additions & 0 deletions control_surface/live.pyi
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
import typing

from ableton.v2.base import Slot as __Slot

T = typing.TypeVar("T")

def find_if(
predicate: typing.Callable[[T], typing.Any], seq: typing.Iterable[T]
) -> typing.Optional[T]: ...
def flatten(list: typing.Iterable[typing.Iterable[T]]) -> typing.Iterable[T]: ...

class lazy_attribute(typing.Generic[T]):
def __init__(self, func: typing.Callable[[typing.Any], T], name=...) -> None: ...
def __get__(self, obj, cls=...) -> T: ...

def listens(
event_path: str, *a, **k
) -> typing.Callable[[typing.Callable[..., typing.Any]], __Slot]: ...
def memoize(function: typing.Callable[..., T]) -> typing.Callable[..., T]: ...
4 changes: 2 additions & 2 deletions control_surface/mappings.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
)

from ableton.v2.control_surface.mode import SetAttributeMode
from ableton.v3.base import depends, find_if, memoize
from ableton.v3.base import depends
from ableton.v3.control_surface import Component, ControlSurface
from ableton.v3.control_surface.component_map import ComponentMap
from ableton.v3.control_surface.layer import Layer
Expand All @@ -30,7 +30,7 @@
)

from .elements import NUM_COLS, NUM_GRID_COLS, NUM_ROWS
from .live import lazy_attribute
from .live import find_if, lazy_attribute, memoize
from .mode import (
DISABLED_MODE_NAME,
MODE_SELECT_MODE_NAME,
Expand Down
3 changes: 2 additions & 1 deletion control_surface/mode.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from functools import partial
from time import time

from ableton.v3.base import depends, listenable_property, memoize
from ableton.v3.base import depends, listenable_property
from ableton.v3.control_surface.controls import ButtonControl
from ableton.v3.control_surface.mode import (
CallFunctionMode,
Expand All @@ -17,6 +17,7 @@
from ableton.v3.control_surface.mode import ModesComponent as ModesComponentBase

from .hardware import HardwareComponent
from .live import memoize
from .types import MainMode

if typing.TYPE_CHECKING:
Expand Down
Loading
Loading