Phase 1-4: Audio stack, mixer engine, MIDI, and network API
Lint & Validate / lint (push) Has been cancelled
Lint & Validate / lint (push) Has been cancelled
P2-R1: ALSA + JACK2 low-latency config (scripts, quirks, tuning) P2-R2: Carla integration (build scripts, 8ch rack config, NAM LV2 support) P2-R3: Plugin manager, categories, blacklist, NAM model support P3-R1: Mixer DSP engine (channel strip, routing matrix, bus mgr, automation) P4-R1: MIDI engine (learn mode, clock sync, HID discovery, mapping store) P4-R2: Network API (OSC server, FastAPI REST, WebSocket, auth, rate limiter) P5-R1: Touchscreen UI evaluation + main entry point docs: Audio stack, Carla integration, MIDI support, UI evaluation tests: Full test suite (292 passing)
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
"""Tests for mapping persistence."""
|
||||
|
||||
import json
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from src.midi.types import MIDIMapping, MIDIMessageType, ParameterType
|
||||
from src.midi.mapping_store import (
|
||||
mapping_to_dict,
|
||||
mapping_from_dict,
|
||||
save_mappings,
|
||||
load_mappings,
|
||||
list_sessions,
|
||||
delete_session,
|
||||
DEFAULT_MAPPINGS_DIR,
|
||||
DEFAULT_SESSION_NAME,
|
||||
)
|
||||
|
||||
|
||||
class TestSerialisation:
|
||||
def test_roundtrip_cc_mapping(self):
|
||||
original = MIDIMapping(
|
||||
msg_type=MIDIMessageType.CONTROL_CHANGE,
|
||||
channel=0,
|
||||
controller=7,
|
||||
param_type=ParameterType.VOLUME,
|
||||
param_channel=0,
|
||||
invert=True,
|
||||
curve="logarithmic",
|
||||
label="CH0 Vol",
|
||||
)
|
||||
d = mapping_to_dict(original)
|
||||
restored = mapping_from_dict(d)
|
||||
|
||||
assert restored.msg_type == original.msg_type
|
||||
assert restored.channel == original.channel
|
||||
assert restored.controller == original.controller
|
||||
assert restored.param_type == original.param_type
|
||||
assert restored.param_channel == original.param_channel
|
||||
assert restored.invert == original.invert
|
||||
assert restored.curve == original.curve
|
||||
assert restored.label == original.label
|
||||
|
||||
def test_roundtrip_nrpn_mapping(self):
|
||||
original = MIDIMapping(
|
||||
msg_type=MIDIMessageType.CONTROL_CHANGE,
|
||||
channel=1,
|
||||
is_nrpn=True,
|
||||
nrpn_number=258,
|
||||
param_type=ParameterType.COMP_THRESHOLD,
|
||||
param_channel=3,
|
||||
)
|
||||
d = mapping_to_dict(original)
|
||||
restored = mapping_from_dict(d)
|
||||
|
||||
assert restored.is_nrpn
|
||||
assert restored.nrpn_number == 258
|
||||
|
||||
def test_roundtrip_with_device(self):
|
||||
original = MIDIMapping(
|
||||
controller=10,
|
||||
source_device="X-Touch",
|
||||
)
|
||||
d = mapping_to_dict(original)
|
||||
restored = mapping_from_dict(d)
|
||||
|
||||
assert restored.source_device == "X-Touch"
|
||||
|
||||
def test_roundtrip_disabled(self):
|
||||
original = MIDIMapping(enabled=False)
|
||||
d = mapping_to_dict(original)
|
||||
restored = mapping_from_dict(d)
|
||||
assert not restored.enabled
|
||||
|
||||
def test_dict_format_has_expected_keys(self):
|
||||
m = MIDIMapping()
|
||||
d = mapping_to_dict(m)
|
||||
|
||||
assert "midi" in d
|
||||
assert "target" in d
|
||||
assert "value" in d
|
||||
assert "meta" in d
|
||||
assert d["midi"]["type"] == "CONTROL_CHANGE"
|
||||
assert d["target"]["parameter"] == "volume"
|
||||
|
||||
|
||||
class TestFileIO:
|
||||
@pytest.fixture(autouse=True)
|
||||
def temp_dir(self, monkeypatch):
|
||||
"""Redirect mappings to a temp directory."""
|
||||
td = tempfile.mkdtemp(prefix="rpi-mixer-test-")
|
||||
monkeypatch.setattr(
|
||||
"src.midi.mapping_store.DEFAULT_MAPPINGS_DIR",
|
||||
Path(td),
|
||||
)
|
||||
yield td
|
||||
# Cleanup
|
||||
import shutil
|
||||
shutil.rmtree(td, ignore_errors=True)
|
||||
|
||||
def test_save_and_load(self):
|
||||
mappings = [
|
||||
MIDIMapping(
|
||||
msg_type=MIDIMessageType.CONTROL_CHANGE,
|
||||
channel=0,
|
||||
controller=7,
|
||||
param_type=ParameterType.VOLUME,
|
||||
param_channel=0,
|
||||
label="CH0 Vol",
|
||||
),
|
||||
MIDIMapping(
|
||||
msg_type=MIDIMessageType.CONTROL_CHANGE,
|
||||
channel=0,
|
||||
controller=10,
|
||||
param_type=ParameterType.PAN,
|
||||
param_channel=0,
|
||||
label="CH0 Pan",
|
||||
),
|
||||
]
|
||||
|
||||
path = save_mappings(mappings, DEFAULT_SESSION_NAME)
|
||||
assert path.exists()
|
||||
|
||||
loaded = load_mappings(DEFAULT_SESSION_NAME)
|
||||
assert len(loaded) == 2
|
||||
assert loaded[0].controller == 7
|
||||
assert loaded[1].controller == 10
|
||||
assert loaded[0].label == "CH0 Vol"
|
||||
assert loaded[1].label == "CH0 Pan"
|
||||
|
||||
def test_missing_session_returns_empty(self):
|
||||
loaded = load_mappings("nonexistent-session")
|
||||
assert loaded == []
|
||||
|
||||
def test_list_sessions(self, temp_dir):
|
||||
save_mappings([MIDIMapping()], "session-a")
|
||||
save_mappings([MIDIMapping()], "session-b")
|
||||
|
||||
sessions = list_sessions()
|
||||
assert "session-a" in sessions
|
||||
assert "session-b" in sessions
|
||||
|
||||
def test_delete_session(self):
|
||||
save_mappings([MIDIMapping()], "to-delete")
|
||||
assert delete_session("to-delete")
|
||||
assert load_mappings("to-delete") == []
|
||||
|
||||
def test_delete_nonexistent_session(self):
|
||||
assert not delete_session("never-existed")
|
||||
|
||||
def test_save_empty_mappings(self):
|
||||
path = save_mappings([], DEFAULT_SESSION_NAME)
|
||||
assert path.exists()
|
||||
|
||||
loaded = load_mappings(DEFAULT_SESSION_NAME)
|
||||
assert loaded == []
|
||||
|
||||
def test_json_is_valid(self):
|
||||
"""Saved JSON should be parseable by standard json module."""
|
||||
mappings = [
|
||||
MIDIMapping(controller=7, param_type=ParameterType.VOLUME, param_channel=0),
|
||||
MIDIMapping(controller=10, param_type=ParameterType.PAN, param_channel=1, invert=True),
|
||||
]
|
||||
path = save_mappings(mappings, DEFAULT_SESSION_NAME)
|
||||
|
||||
with open(path) as fh:
|
||||
doc = json.load(fh)
|
||||
|
||||
assert doc["version"] == 1
|
||||
assert len(doc["mappings"]) == 2
|
||||
assert doc["mappings"][0]["target"]["parameter"] == "volume"
|
||||
assert doc["mappings"][1]["value"]["invert"] is True
|
||||
Reference in New Issue
Block a user