feat(midi): qualify routed startup and refresh workflow screenshots (phase 105)
This commit is contained in:
parent
f37ad8987a
commit
25d4ad5d3b
73 changed files with 208 additions and 37 deletions
65
tests/integration/test_configuration_startup.py
Normal file
65
tests/integration/test_configuration_startup.py
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Exercise the real GUI's default ~/.xtc.conf lifecycle in an isolated child HOME."""
|
||||
import configparser
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
binary = str(Path(sys.argv[1]).resolve())
|
||||
with tempfile.TemporaryDirectory(prefix="xtc-config-startup-") as directory:
|
||||
configuration = Path(directory) / ".xtc.conf"
|
||||
environment = dict(os.environ)
|
||||
environment["HOME"] = directory # Child only; never reads/writes the user's config.
|
||||
|
||||
def run_window(expected_width):
|
||||
process = subprocess.Popen([binary], env=environment, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE, text=True)
|
||||
try:
|
||||
window = None
|
||||
deadline = time.monotonic() + 15
|
||||
while time.monotonic() < deadline:
|
||||
if process.poll() is not None:
|
||||
raise AssertionError(f"GUI exited early: {process.communicate()}")
|
||||
found = subprocess.run(["xdotool", "search", "--onlyvisible", "--pid",
|
||||
str(process.pid), "--name", "xtc"],
|
||||
capture_output=True, text=True)
|
||||
if found.returncode == 0:
|
||||
window = found.stdout.splitlines()[0]
|
||||
geometry = subprocess.check_output(
|
||||
["xdotool", "getwindowgeometry", "--shell", window], text=True)
|
||||
if f"WIDTH={expected_width}\n" in geometry:
|
||||
break
|
||||
time.sleep(.05)
|
||||
else:
|
||||
raise AssertionError(f"Expected startup width {expected_width}; window={window}")
|
||||
subprocess.run(["xdotool", "windowfocus", window], check=True)
|
||||
subprocess.run(["xdotool", "key", "--window", window, "ctrl+q"], check=True)
|
||||
output, error = process.communicate(timeout=15)
|
||||
assert process.returncode == 0, (output, error)
|
||||
finally:
|
||||
if process.poll() is None:
|
||||
process.kill()
|
||||
process.communicate()
|
||||
|
||||
run_window(1280)
|
||||
assert configuration.exists() and configuration.stat().st_mode & 0o777 == 0o600
|
||||
saved = configparser.ConfigParser()
|
||||
saved.read(configuration)
|
||||
assert saved["midi"]["midi_input_channel"] == "-1"
|
||||
assert saved["editing"]["note_velocity"] == "100"
|
||||
configuration.write_text("[interface]\nwidth=1360\nheight=860\n"
|
||||
"[midi]\nmidi_input_channel=5\nmidi_output_channel=7\n"
|
||||
"[project_defaults]\ntempo=137000\n")
|
||||
run_window(1360)
|
||||
saved.read(configuration)
|
||||
assert saved["midi"]["midi_input_channel"] == "5"
|
||||
assert saved["midi"]["midi_output_channel"] == "7"
|
||||
assert saved["project_defaults"]["tempo"] == "137000"
|
||||
invalid = "[unknown]\nkeep_this_file=1\n"
|
||||
configuration.write_text(invalid)
|
||||
run_window(1280)
|
||||
assert configuration.read_text() == invalid
|
||||
print("Real startup ~/.xtc.conf creation, defaults, reload and malformed-file preservation passed.")
|
||||
|
|
@ -166,6 +166,8 @@ xtc_result xtc_backend_submit(xtc_backend *backend, const xtc_graph *graph)
|
|||
++engine_status.submitted_revision;
|
||||
return XTC_OK;
|
||||
}
|
||||
xtc_result xtc_backend_start_routed(xtc_backend *b,const char *input,const char *output)
|
||||
{auto r=xtc_backend_start(b);if(r==XTC_OK && input && *input)r=xtc_backend_route_midi(b,XTC_PORT_INPUT,input,0);if(r==XTC_OK && output && *output)r=xtc_backend_route_midi(b,XTC_PORT_OUTPUT,output,0);if(r!=XTC_OK && handle.running)(void)xtc_backend_stop(b);return r;}
|
||||
xtc_result xtc_backend_set_midi_options(xtc_backend *,int input,int output,int thru)
|
||||
{CHECK(input>=-1 && input<16 && output>=-1 && output<16 && thru>=0 && thru<2);return handle.running ? XTC_ERR_INVALID_STATE : XTC_OK;}
|
||||
xtc_result xtc_backend_visit_midi_ports(xtc_backend *,xtc_port_direction d,xtc_backend_port_visitor visit,void *c)
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ int main(void)
|
|||
jack_status_t flags = 0;
|
||||
probe state = {0};
|
||||
const char *destination;
|
||||
uint64_t track, clip, note;
|
||||
uint64_t track, clip, note;unsigned int before_scheduled;
|
||||
int result = EXIT_FAILURE;
|
||||
REQUIRE(mkdtemp(directory));
|
||||
snprintf(song, sizeof(song), "%s/song.xtc", directory);
|
||||
|
|
@ -77,7 +77,7 @@ int main(void)
|
|||
REQUIRE(state.input && state.right && state.midi && state.receive);
|
||||
REQUIRE(xtc_backend_set_midi_options(backend,-1,-1,1)==XTC_OK);
|
||||
REQUIRE(jack_set_process_callback(client, process, &state) == 0 && jack_activate(client) == 0);
|
||||
REQUIRE(xtc_backend_start(backend) == XTC_OK);
|
||||
REQUIRE(xtc_backend_start_routed(backend,jack_port_name(state.midi),jack_port_name(state.receive)) == XTC_OK);
|
||||
REQUIRE(xtc_backend_midi_port_name(backend, &destination) == XTC_OK);
|
||||
wait_blocks(); /* PipeWire JACK publishes newly activated ports asynchronously. */
|
||||
{
|
||||
|
|
@ -109,14 +109,18 @@ int main(void)
|
|||
REQUIRE(xtc_project_set_mix(p, track, 0.5f, 1, 0, 0) == XTC_OK);
|
||||
REQUIRE(xtc_project_save(p, song) == XTC_OK && xtc_project_load(q, song) == XTC_OK);
|
||||
REQUIRE(xtc_backend_prepare_project(backend, q) == XTC_OK);
|
||||
REQUIRE(xtc_backend_seek(backend, 0) == XTC_OK && xtc_backend_start(backend) == XTC_OK);
|
||||
before_scheduled=__atomic_load_n(&state.midi_on,__ATOMIC_RELAXED);
|
||||
REQUIRE(xtc_backend_seek(backend, 0) == XTC_OK && xtc_backend_start_routed(backend,NULL,jack_port_name(state.receive)) == XTC_OK);
|
||||
wait_blocks(); /* Publish restarted PipeWire ports before connecting. */
|
||||
REQUIRE(xtc_backend_connect(backend, XTC_PORT_OUTPUT, jack_port_name(state.input)) == XTC_OK);
|
||||
{ const char *own; REQUIRE(xtc_backend_output_name(backend, 1, &own) == XTC_OK);
|
||||
int connected = jack_connect(client, own, jack_port_name(state.right)); REQUIRE(connected == 0 || connected == EEXIST); }
|
||||
wait_blocks(); REQUIRE(!__atomic_load_n(&state.loud, __ATOMIC_RELAXED) && __atomic_load_n(&state.right_loud, __ATOMIC_RELAXED));
|
||||
REQUIRE(__atomic_load_n(&state.midi_on,__ATOMIC_RELAXED)>before_scheduled);
|
||||
{ xtc_backend_status status; REQUIRE(xtc_backend_get_status(backend, &status) == XTC_OK && status.peak_left == 0 && status.peak_right > 0); }
|
||||
REQUIRE(xtc_backend_stop(backend) == XTC_OK);
|
||||
REQUIRE(xtc_backend_seek(backend, 7680000) == XTC_OK && xtc_backend_start(backend) == XTC_OK);
|
||||
wait_blocks();
|
||||
REQUIRE(xtc_backend_connect(backend, XTC_PORT_OUTPUT, jack_port_name(state.input)) == XTC_OK);
|
||||
{ const char *own; REQUIRE(xtc_backend_output_name(backend, 1, &own) == XTC_OK);
|
||||
int connected = jack_connect(client, own, jack_port_name(state.right)); REQUIRE(connected == 0 || connected == EEXIST); }
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ static unsigned char sent[512][3];static unsigned int sent_time[512],sent_count;
|
|||
static unsigned int midi_count;
|
||||
static jack_midi_event_t midi_events[256];
|
||||
static int open_fail, register_fail, callback_fail, activate_fail, deactivate_fail, close_fail;
|
||||
static int active, closed, port_calls, null_buffer;
|
||||
static int active, closed, port_calls, null_buffer, process_during_connect;
|
||||
static unsigned int server_frames = 32U;
|
||||
jack_client_t *jack_client_open(const char *name, jack_options_t options, jack_status_t *status, ...)
|
||||
{
|
||||
|
|
@ -88,7 +88,7 @@ const char **jack_get_ports(jack_client_t *client, const char *pattern, const ch
|
|||
}
|
||||
void jack_free(void *pointer) { free(pointer); }
|
||||
int jack_connect(jack_client_t *client, const char *source, const char *dest)
|
||||
{ (void)client; return strstr(source, "missing") || strstr(dest, "missing") ? -1 : EEXIST; }
|
||||
{ (void)client; if(process_during_connect && process_cb)process_cb(32,context); return strstr(source, "missing") || strstr(dest, "missing") ? -1 : EEXIST; }
|
||||
int jack_disconnect(jack_client_t *client, const char *source, const char *dest)
|
||||
{ (void)client; (void)source; (void)dest; return 0; }
|
||||
static int visit(const char *name, void *arg)
|
||||
|
|
@ -413,7 +413,9 @@ int main(void)
|
|||
CHECK(xtc_project_add_midi_clip(p,t,0,3840,&c)==XTC_OK && xtc_project_add_note(p,c,0,960,60,90,1,&n)==XTC_OK);
|
||||
CHECK(xtc_backend_open(&config,&backend)==XTC_OK && xtc_backend_prepare_project(backend,p)==XTC_OK);
|
||||
CHECK(xtc_backend_midi_name(backend,XTC_PORT_OUTPUT,&midi_name)==XTC_OK && !strcmp(midi_name,"test:midi_out"));
|
||||
CHECK(xtc_backend_set_midi_options(backend,2,4,1)==XTC_OK && xtc_backend_start(backend)==XTC_OK);
|
||||
CHECK(xtc_backend_set_midi_options(backend,2,4,1)==XTC_OK);
|
||||
process_during_connect=1;CHECK(xtc_backend_start_routed(backend,"controller:out","device:in")==XTC_OK);process_during_connect=0;
|
||||
CHECK(xtc_backend_get_status(backend,&status)==XTC_OK && status.sample_position==0 && !sent_count);
|
||||
CHECK(xtc_backend_route_midi(backend,XTC_PORT_OUTPUT,"device:in",0)==XTC_OK);
|
||||
CHECK(xtc_backend_set_midi_options(backend,-1,-1,0)==XTC_ERR_INVALID_STATE);
|
||||
midi_count=1;midi_events[0]=(jack_midi_event_t){8,3,on};CHECK(process_cb(32,context)==0);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue