Skip to content

Commit aafdf25

Browse files
authored
Midi port selection based on selected controller (#79)
1 parent 6b7f955 commit aafdf25

File tree

4 files changed

+76
-2
lines changed

4 files changed

+76
-2
lines changed

midi_app_controller/gui/midi_status.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ class MidiStatus(QWidget):
4040
current_midi_in : DynamicQComboBox
4141
Button that allows to select MIDI input port using its menu. Its
4242
text is set to currently selected port.
43-
current_midi_in : DynamicQComboBox
43+
current_midi_out : DynamicQComboBox
4444
Button that allows to select MIDI output port using its menu. Its
4545
text is set to currently selected port.
4646
status : QLabel
@@ -159,6 +159,10 @@ def refresh(self):
159159

160160
self.current_binds.refresh_items()
161161
self.current_binds.set_current(state.selected_binds)
162+
self.current_midi_in.refresh_items()
163+
self.current_midi_in.set_current(state.selected_midi_in)
164+
self.current_midi_out.refresh_items()
165+
self.current_midi_out.set_current(state.selected_midi_out)
162166
self.show_binds_file_button.setEnabled(state.selected_binds is not None)
163167
self.edit_binds_button.setEnabled(state.selected_binds is not None)
164168
self.delete_binds_button.setEnabled(state.selected_binds is not None)
@@ -182,6 +186,7 @@ def _select_controller(self, controller: Optional[SelectedItem]) -> None:
182186
state = get_state_manager()
183187
state.select_controller(controller)
184188
state.select_recent_binds()
189+
state.select_recent_midi_ports()
185190
self.refresh()
186191

187192
def _copy_binds(self) -> None:

midi_app_controller/models/app_state.py

+3
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ class AppState(YamlBaseModel):
1919
Selected MIDI output port.
2020
recent_binds_for_controller: dict[Path, Optional[Path]]
2121
Mapping between each controller and which binds setup was last used.
22+
recent_midi_ports_for_controller: dict[Path, dict[str, Optional[str]]]
23+
Mapping between controllers and the last used MIDI ports (in/out)
2224
"""
2325

2426
selected_controller_path: Optional[Path]
@@ -27,3 +29,4 @@ class AppState(YamlBaseModel):
2729
selected_midi_out: Optional[str]
2830

2931
recent_binds_for_controller: dict[Path, Optional[Path]]
32+
recent_midi_ports_for_controller: dict[Path, dict[str, Optional[str]]]

midi_app_controller/state/_tests/test_state_manager.py

+37
Original file line numberDiff line numberDiff line change
@@ -402,3 +402,40 @@ def test_start_handling_without_fields_set(mock_midi_in_out, state_manager):
402402
with pytest.raises(Exception) as excinfo:
403403
state_manager.start_handling()
404404
assert "No MIDI output" not in str(excinfo.value)
405+
406+
407+
@pytest.mark.parametrize(
408+
(
409+
"available_midi_in, available_midi_out, recent_ports, "
410+
"expected_midi_in, expected_midi_out"
411+
),
412+
[
413+
(["in1", "in2"], ["out1", "out2"], {"in": "in1", "out": "out2"}, "in1", "out2"),
414+
(["in1", "in2"], ["out1", "out2"], {}, None, None),
415+
(["in1", "in2"], ["out1", "out2"], {"in": "in3", "out": "out3"}, None, None),
416+
],
417+
)
418+
def test_select_recent_midi_ports(
419+
mock_midi_in_out,
420+
state_manager,
421+
available_midi_in,
422+
available_midi_out,
423+
recent_ports,
424+
expected_midi_in,
425+
expected_midi_out,
426+
):
427+
mock_midi_in, mock_midi_out = mock_midi_in_out
428+
mock_midi_in.get_ports.return_value = available_midi_in
429+
mock_midi_out.get_ports.return_value = available_midi_out
430+
431+
controller_path = Path("test_controller_path")
432+
state_manager.selected_controller = SelectedItem("TestController", controller_path)
433+
434+
state_manager.recent_midi_ports_for_controller = (
435+
{controller_path: recent_ports} if recent_ports else {}
436+
)
437+
438+
state_manager.select_recent_midi_ports()
439+
440+
assert state_manager.selected_midi_in == expected_midi_in
441+
assert state_manager.selected_midi_out == expected_midi_out

midi_app_controller/state/state_manager.py

+30-1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ class StateManager:
4949
recent_binds_for_controller: dict[Path, Path] = {}
5050
Mapping of controller schemas to the binds set most recently used
5151
with the schema.
52+
recent_midi_ports_for_controller: dict[Path, dict[str, str]]
53+
Mapping of controller schemas to the MIDI ports most
54+
recently used (precisely dict with keys "in" and "out")
55+
with the schema.
5256
selected_midi_in : Optional[str]
5357
Name of currently selected MIDI input.
5458
selected_midi_out : Optional[str]
@@ -68,7 +72,8 @@ class StateManager:
6872
def __init__(self, app: Application):
6973
self.selected_controller = None
7074
self.selected_binds = None
71-
self.recent_binds_for_controller: dict[Path, Path] = {}
75+
self.recent_binds_for_controller = {}
76+
self.recent_midi_ports_for_controller = {}
7277
self.selected_midi_in = None
7378
self.selected_midi_out = None
7479
self.app = app
@@ -204,13 +209,23 @@ def select_midi_in(self, port_name: Optional[str]) -> None:
204209
Does not have any immediate effect except updating the value.
205210
"""
206211
self.selected_midi_in = port_name
212+
if self.selected_controller:
213+
self.recent_midi_ports_for_controller[self.selected_controller.path] = {
214+
"in": self.selected_midi_in,
215+
"out": self.selected_midi_out,
216+
}
207217

208218
def select_midi_out(self, port_name: Optional[str]) -> None:
209219
"""Updates currently selected MIDI output port name.
210220
211221
Does not have any immediate effect except updating the value.
212222
"""
213223
self.selected_midi_out = port_name
224+
if self.selected_controller:
225+
self.recent_midi_ports_for_controller[self.selected_controller.path] = {
226+
"in": self.selected_midi_in,
227+
"out": self.selected_midi_out,
228+
}
214229

215230
def stop_handling(self) -> None:
216231
"""Stops handling any MIDI signals."""
@@ -269,6 +284,18 @@ def start_handling(self) -> None:
269284
midi_out=self._midi_out,
270285
)
271286

287+
def select_recent_midi_ports(self):
288+
"""Select MIDI ports that were recently used with the current controller."""
289+
if (
290+
self.selected_controller
291+
and self.selected_controller.path in self.recent_midi_ports_for_controller
292+
):
293+
ports = self.recent_midi_ports_for_controller[self.selected_controller.path]
294+
if ports["in"] in self.get_available_midi_in():
295+
self.selected_midi_in = ports["in"]
296+
if ports["out"] in self.get_available_midi_out():
297+
self.selected_midi_out = ports["out"]
298+
272299
def save_state(self):
273300
"""Saves the current settings to the disk."""
274301
AppState(
@@ -281,6 +308,7 @@ def save_state(self):
281308
selected_midi_in=self.selected_midi_in,
282309
selected_midi_out=self.selected_midi_out,
283310
recent_binds_for_controller=self.recent_binds_for_controller,
311+
recent_midi_ports_for_controller=self.recent_midi_ports_for_controller,
284312
).save_to(Config.APP_STATE_FILE)
285313

286314
def load_state(self):
@@ -316,6 +344,7 @@ def load_state(self):
316344
self.select_midi_in(state.selected_midi_in)
317345
self.select_midi_out(state.selected_midi_out)
318346
self.recent_binds_for_controller = state.recent_binds_for_controller
347+
self.recent_midi_ports_for_controller = state.recent_midi_ports_for_controller
319348

320349

321350
_STATE_MANAGER = None

0 commit comments

Comments
 (0)