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
12 changes: 10 additions & 2 deletions native/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -95,12 +95,12 @@ all: $(BUILD)/clap-qualify $(BUILD)/lv2_measure $(BUILD)/lv2_host_probe $(BUILD)
$(BUILD)/fixtures-faulty/crashes-at-load.clap $(BUILD)/fixtures-faulty/hangs.clap \
$(BUILD)/fixtures/synth.clap $(BUILD)/fixtures/synth-alloc.clap $(BUILD)/fixtures/synth-midi.clap \
$(BUILD)/fixtures/synth-voices.clap $(BUILD)/fixtures/synth-no-voices.clap $(BUILD)/fixtures/synth-input.clap \
$(BUILD)/fixtures/synth-strict.clap \
$(BUILD)/fixtures/synth-strict.clap $(BUILD)/fixtures/synth-drum.clap \
$(BUILD)/fixtures/requires-voice-info.clap $(BUILD)/fixtures/requires-timer.clap $(BUILD)/fixtures/requires-thread-pool.clap \
$(BUILD)/fixtures/fail-alloc.so $(BUILD)/fixtures-faulty/hogs-memory.clap \
$(BUILD)/fixtures/lv2/rt-safe.lv2/gain.so $(BUILD)/fixtures/lv2/rt-unsafe.lv2/gain.so \
$(BUILD)/fixtures/lv2/host-faithfulness.lv2/host-faithfulness.so \
$(BUILD)/fixtures/lv2/synth.lv2/synth.so $(BUILD)/fixtures/lv2/synth-alloc.lv2/synth.so \
$(BUILD)/fixtures/lv2/synth.lv2/synth.so $(BUILD)/fixtures/lv2/synth-alloc.lv2/synth.so $(BUILD)/fixtures/lv2/synth-drum.lv2/synth.so \
$(BUILD)/fixtures/lv2/latency.lv2/latency.so $(BUILD)/fixtures/lv2/spaced.lv2/manifest.ttl \
$(BUILD)/fixtures-faulty/synth-voice-memory.clap \
$(BUILD)/fixtures-faulty/lv2/leaks.lv2/gain.so $(BUILD)/fixtures-faulty/lv2/large-activate.lv2/gain.so
Expand Down Expand Up @@ -184,6 +184,9 @@ $(BUILD)/fixtures/synth-alloc.clap: fixtures/synth.c | $(BUILD)/fixtures
$(CC) $(CPPFLAGS) $(CFLAGS) $(CLAP_CFLAGS) -DFIXTURE_ALLOCATES_ON_NOTE -shared -fPIC -fvisibility=hidden $(LDFLAGS) -o $@ $< -lm
$(BUILD)/fixtures/synth-midi.clap: fixtures/synth.c | $(BUILD)/fixtures
$(CC) $(CPPFLAGS) $(CFLAGS) $(CLAP_CFLAGS) -DFIXTURE_MIDI_ONLY -shared -fPIC -fvisibility=hidden $(LDFLAGS) -o $@ $< -lm
# a drum kit: only keys 36 to 40 answer, each with a 20 ms hit
$(BUILD)/fixtures/synth-drum.clap: fixtures/synth.c | $(BUILD)/fixtures
$(CC) $(CPPFLAGS) $(CFLAGS) $(CLAP_CFLAGS) -DFIXTURE_DRUM -shared -fPIC -fvisibility=hidden $(LDFLAGS) -o $@ $< -lm
# the same declaring its voice capacity through voice-info, at 8 and at 0: what the cost arm holds
$(BUILD)/fixtures/synth-voices.clap: fixtures/synth.c | $(BUILD)/fixtures
$(CC) $(CPPFLAGS) $(CFLAGS) $(CLAP_CFLAGS) -DFIXTURE_VOICE_CAPACITY=8 -shared -fPIC -fvisibility=hidden $(LDFLAGS) -o $@ $< -lm
Expand Down Expand Up @@ -251,6 +254,11 @@ $(BUILD)/fixtures/lv2/spaced.lv2/manifest.ttl: fixtures/lv2-gain.c fixtures/spac
cp fixtures/spaced.lv2/manifest.ttl fixtures/spaced.lv2/gain.ttl $(@D)/
$(CC) $(CPPFLAGS) $(CFLAGS) $(LV2_CFLAGS) -DFIXTURE_SPACED -shared -fPIC -fvisibility=hidden $(LDFLAGS) -o "$(@D)/gain plugin.so" $<

$(BUILD)/fixtures/lv2/synth-drum.lv2/synth.so: fixtures/lv2-synth.c fixtures/synth-drum.lv2/manifest.ttl fixtures/synth-drum.lv2/synth.ttl
mkdir -p $(@D)
cp fixtures/synth-drum.lv2/manifest.ttl fixtures/synth-drum.lv2/synth.ttl $(@D)/
$(CC) $(CPPFLAGS) $(CFLAGS) $(LV2_CFLAGS) -DFIXTURE_DRUM -shared -fPIC -fvisibility=hidden $(LDFLAGS) -o $@ $< -lm

$(BUILD)/fixtures/lv2/synth.lv2/synth.so: fixtures/lv2-synth.c fixtures/synth.lv2/manifest.ttl fixtures/synth.lv2/synth.ttl
mkdir -p $(@D)
cp fixtures/synth.lv2/manifest.ttl fixtures/synth.lv2/synth.ttl $(@D)/
Expand Down
22 changes: 20 additions & 2 deletions native/fixtures/lv2-synth.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@
#include <lv2/midi/midi.h>
#include <lv2/urid/urid.h>

#ifdef FIXTURE_ALLOCATES_ON_NOTE
#ifdef FIXTURE_DRUM
#define FIXTURE_URI "urn:plugin-qualify:fixture:lv2-synth-drum"
#elif defined FIXTURE_ALLOCATES_ON_NOTE
#define FIXTURE_URI "urn:plugin-qualify:fixture:lv2-synth-alloc"
/* through a volatile pointer, so the compiler cannot pair the malloc with the free and drop both */
static void *(*volatile fixture_malloc)(size_t) = malloc;
Expand All @@ -36,7 +38,14 @@ static void (*volatile fixture_free)(void *) = free;

enum { PORT_MIDI, PORT_OUT_L, PORT_OUT_R, PORT_GAIN };

struct voice { int key; double phase, step; };
#ifdef FIXTURE_DRUM
/* a drum kit: only these keys are mapped, and each hit lasts 20 ms */
#define FIXTURE_DRUM_LOW 36
#define FIXTURE_DRUM_HIGH 40
#define FIXTURE_DRUM_HIT_S 0.02
#endif

struct voice { int key; double phase, step; long left; };

struct synth {
const LV2_Atom_Sequence *midi;
Expand Down Expand Up @@ -78,6 +87,9 @@ static void activate(LV2_Handle h) {
}

static void note_on(struct synth *s, int key) {
#ifdef FIXTURE_DRUM
if (key < FIXTURE_DRUM_LOW || key > FIXTURE_DRUM_HIGH) return;
#endif
#ifdef FIXTURE_ALLOCATES_ON_NOTE
fixture_free(fixture_malloc(256 * sizeof(double)));
#endif
Expand All @@ -86,6 +98,9 @@ static void note_on(struct synth *s, int key) {
s->voice[v].key = key;
s->voice[v].phase = 0.0;
s->voice[v].step = 2.0 * M_PI * 440.0 * pow(2.0, (key - 69) / 12.0) / s->rate;
#ifdef FIXTURE_DRUM
s->voice[v].left = (long)(s->rate * FIXTURE_DRUM_HIT_S);
#endif
return;
}
}
Expand All @@ -111,6 +126,9 @@ static void render(struct synth *s, uint32_t from, uint32_t to) {
if (s->voice[v].key >= 0) {
y += sin(s->voice[v].phase) * 0.05;
s->voice[v].phase += s->voice[v].step;
#ifdef FIXTURE_DRUM
if (--s->voice[v].left <= 0) s->voice[v].key = -1;
#endif
}
s->out[0][i] = s->out[1][i] = (float)y * k;
}
Expand Down
3 changes: 3 additions & 0 deletions native/fixtures/synth-drum.lv2/manifest.ttl
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
<urn:plugin-qualify:fixture:lv2-synth-drum> a lv2:Plugin ; lv2:binary <synth.so> ; rdfs:seeAlso <synth.ttl> .
18 changes: 18 additions & 0 deletions native/fixtures/synth-drum.lv2/synth.ttl
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
@prefix atom: <http://lv2plug.in/ns/ext/atom#> .
@prefix doap: <http://usefulinc.com/ns/doap#> .
@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
@prefix midi: <http://lv2plug.in/ns/ext/midi#> .
@prefix urid: <http://lv2plug.in/ns/ext/urid#> .

<urn:plugin-qualify:fixture:lv2-synth-drum> a lv2:Plugin , lv2:InstrumentPlugin ;
doap:name "synth answering only some keys" ;
doap:license <https://spdx.org/licenses/GPL-3.0-or-later> ;
lv2:minorVersion 1 ; lv2:microVersion 0 ;
lv2:requiredFeature urid:map ;
lv2:optionalFeature lv2:hardRTCapable ;
lv2:port [ a lv2:InputPort , atom:AtomPort ; atom:bufferType atom:Sequence ; atom:supports midi:MidiEvent ;
lv2:designation lv2:control ; lv2:index 0 ; lv2:symbol "midi" ; lv2:name "MIDI in" ] ,
[ a lv2:AudioPort , lv2:OutputPort ; lv2:index 1 ; lv2:symbol "out_l" ; lv2:name "Out L" ] ,
[ a lv2:AudioPort , lv2:OutputPort ; lv2:index 2 ; lv2:symbol "out_r" ; lv2:name "Out R" ] ,
[ a lv2:InputPort , lv2:ControlPort ; lv2:index 3 ; lv2:symbol "gain" ; lv2:name "Gain" ;
lv2:default 1.0 ; lv2:minimum 0.0 ; lv2:maximum 2.0 ] .
21 changes: 20 additions & 1 deletion native/fixtures/synth.c
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ static void (*volatile fixture_free)(void *) = free;
#elif defined FIXTURE_VOICE_CAPACITY && FIXTURE_VOICE_CAPACITY == 0
#define FIXTURE_ID "org.freemixer.plugin-qualify.fixture.synth-no-voices"
#define FIXTURE_NAME "synth declaring no voices"
#elif defined FIXTURE_DRUM
#define FIXTURE_ID "org.freemixer.plugin-qualify.fixture.synth-drum"
#define FIXTURE_NAME "synth answering only some keys"
#elif defined FIXTURE_VOICE_CAPACITY
#define FIXTURE_ID "org.freemixer.plugin-qualify.fixture.synth-voices"
#define FIXTURE_NAME "synth declaring its voices"
Expand Down Expand Up @@ -84,7 +87,14 @@ static const clap_plugin_descriptor_t descriptor = {
.features = features,
};

struct voice { int key; double phase, step; };
#ifdef FIXTURE_DRUM
/* a drum kit: only these keys are mapped, and each hit lasts 20 ms */
#define FIXTURE_DRUM_LOW 36
#define FIXTURE_DRUM_HIGH 40
#define FIXTURE_DRUM_HIT_S 0.02
#endif

struct voice { int key; double phase, step; long left; };

struct synth {
clap_plugin_t plugin;
Expand Down Expand Up @@ -118,6 +128,9 @@ static void synth_reset(const clap_plugin_t *p) {
}

static void note_on(struct synth *s, int key) {
#ifdef FIXTURE_DRUM
if (key < FIXTURE_DRUM_LOW || key > FIXTURE_DRUM_HIGH) return;
#endif
#ifdef FIXTURE_VOICE_MB
{
const size_t bytes = (size_t)FIXTURE_VOICE_MB << 20;
Expand All @@ -137,6 +150,9 @@ static void note_on(struct synth *s, int key) {
s->voice[v].key = key;
s->voice[v].phase = 0.0;
s->voice[v].step = 2.0 * M_PI * 440.0 * pow(2.0, (key - 69) / 12.0) / s->rate;
#ifdef FIXTURE_DRUM
s->voice[v].left = (long)(s->rate * FIXTURE_DRUM_HIT_S);
#endif
return;
}
}
Expand Down Expand Up @@ -201,6 +217,9 @@ static clap_process_status synth_process(const clap_plugin_t *p, const clap_proc
if (s->voice[v].key >= 0) {
y += sin(s->voice[v].phase) * 0.1;
s->voice[v].phase += s->voice[v].step;
#ifdef FIXTURE_DRUM
if (--s->voice[v].left <= 0) s->voice[v].key = -1;
#endif
}
for (uint32_t c = 0; c < out->channel_count; c++) {
double in = 0.0;
Expand Down
17 changes: 9 additions & 8 deletions native/isolated-signal.c
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
/* Copyright (C) 2026 Pau Aliagas <linuxnow@gmail.com> */
/* The isolated step's signal (plugin-qualify#249): a sine into every audio input of a JACK client, notes held on its MIDI
* inputs for an instrument, and what comes out of its audio outputs over a window. The notes start once the links are made,
* which an instrument needs to hear them, and each third of the window adds one more, held, so a patch that is slow to start
* is never restarted:
* inputs for an instrument, and what comes out of its audio outputs over a window, read from before the first note. The
* notes start once the links are made, which an instrument needs to hear them, and walk a wide range of keys across the
* window, each held: a drum kit answers only its mapped keys, and a patch slow to start is never restarted:
* isolated_signal <client> <ms>
* prints one JSON line, {"latencyFrames":n,"xruns":n,"finite":bool,"silent":bool,"midiInputsLinked":n}. Exit 2 when the server or the client's
* ports could not be had: nothing was learned about the plugin. libjack is opened at run time, as jack_peak opens it: the
Expand All @@ -22,8 +22,9 @@
#define SINE_HZ 1000.0
#define SINE_PEAK 0.25f
#define SILENCE_PEAK 1e-5f
static const unsigned char NOTES[] = {60, 64, 67};
#define NOTE_COUNT ((int)(sizeof NOTES / sizeof NOTES[0]))
/* the same keys as tools/mod-host-admit.mjs plays */
#define NOTE_FIRST 36
#define NOTE_COUNT 30
#define NOTE_VELOCITY 100
#define JACK_NO_START_SERVER 1
#define JACK_PORT_IS_INPUT 1ul
Expand Down Expand Up @@ -70,7 +71,7 @@ static int on_process(uint32_t n, void *arg) {
void *midi = p_port_buffer(g_midi, n);
p_midi_clear(midi);
if (g_notes_sent < atomic_load(&g_notes_due)) {
unsigned char on[3] = {0x90, NOTES[g_notes_sent], NOTE_VELOCITY};
unsigned char on[3] = {0x90, (unsigned char)(NOTE_FIRST + g_notes_sent), NOTE_VELOCITY};
p_midi_write(midi, 0, on, sizeof on);
g_notes_sent++;
}
Expand Down Expand Up @@ -162,10 +163,10 @@ int main(int argc, char **argv) {
for (int i = 0; i < g_inputs; i++) p_connect(c, outs[i], p_port_name(g_in[i]));
long ms = atol(argv[2]);
long part = ms / NOTE_COUNT;
struct timespec third = {part / 1000, (part % 1000) * 1000000L};
struct timespec step = {part / 1000, (part % 1000) * 1000000L};
for (int n = 1; n <= NOTE_COUNT; n++) {
atomic_store(&g_notes_due, n);
nanosleep(&third, NULL);
nanosleep(&step, NULL);
}
struct latency_range range = {0, 0};
p_latency_range(p_port_by_name(c, outs[0]), JACK_CAPTURE_LATENCY, &range);
Expand Down
22 changes: 21 additions & 1 deletion src/adapter-real-instrument.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
import { spawnSync } from "node:child_process";
import { existsSync, mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { describe, expect, it } from "vitest";

import { ADAPTER_HOST, SPAWNED_ADMISSIONS, classifyProtocolRun } from "./host-admission.js";
Expand All @@ -28,6 +29,25 @@ const peakTool = nativeToolCandidates(JACK_PEAK_TOOL_NAME).find((p) => existsSyn
const ready = peakTool !== undefined && installed("jackd") && installed(ADAPTER_HOST) && LV2_DIRS.some((d) => existsSync(join(d, "mda.lv2")));

describe("the adapter pass on a real instrument", () => {
const FIXTURES = join(dirname(fileURLToPath(import.meta.url)), "..", "native", "build", "fixtures", "lv2");

it("a drum kit, which answers only some keys with a short hit, is heard", (ctx) => {
if (!ready) {
if (process.env["CI"] !== undefined) throw new Error("jackd, omx-clap-host, jack_peak or mda-lv2 is missing: CI installs them");
ctx.skip("needs jackd, omx-clap-host, native/build and mda-lv2");
}
const server = startPrivateJack(CELL);
if (server === undefined) throw new Error("no private JACK server came up");
const state = mkdtempSync(join(tmpdir(), "adapter-drum-"));
try {
const run = SPAWNED_ADMISSIONS.adapter!("urn:plugin-qualify:fixture:lv2-synth-drum", { ...process.env, JACK_DEFAULT_SERVER: server.name, LV2_PATH: FIXTURES, OMX_JACK_PEAK: peakTool, [ADMIT_STATE_DIR_ENV]: state, HOME: state });
expect(run.sound?.peak, JSON.stringify(run)).toBeGreaterThan(SOUNDING_PEAK);
} finally {
server.stop();
rmSync(state, { recursive: true, force: true });
}
});

it("mda Piano is admitted and sounds under its held note", (ctx) => {
if (!ready) {
if (process.env["CI"] !== undefined) throw new Error("jackd, omx-clap-host, jack_peak or mda-lv2 is missing: CI installs them");
Expand Down
5 changes: 3 additions & 2 deletions src/host-admission.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -398,9 +398,10 @@ net.createServer((c) => c.on("data", (d) => {

it("a note goes in between the add and the remove, every output port is read, and the figure comes back", () => {
const { run, sent } = admit({ STAND_CLASS: "instrument" });
expect(sent).toEqual(["add urn:x 0", "audio_ports 0", "note_on 0 0 60 100", "remove 0"]);
const keys = Array.from({ length: 30 }, (_, k) => `note_on 0 0 ${String(36 + k)} 100`);
expect(sent).toEqual(["add urn:x 0", "audio_ports 0", ...keys, "remove 0"]);
expect(readFileSync(`${log}.ports`, "utf8").trim().split("\n")).toEqual(["effect_0:out_1", "effect_0:out_2", "effect_0:aux_out_1_1", "effect_0:aux_out_1_2"]);
expect(run).toEqual({ installed: true, steps: [{ cmd: "add", resp: 0 }, { cmd: "remove", resp: 0 }], sound: { peak: 0.2 } });
expect(run).toEqual({ installed: true, steps: [{ cmd: "add", resp: 0 }, { cmd: "remove", resp: 0 }], sound: { notes: 30, peak: 0.2 } });
});
it("silence comes back as silence and the run does not admit", () => {
expect(classifyProtocolRun(ADAPTER_HOST, admit({ STAND_CLASS: "instrument", STAND_PEAK: "0" }).run).code).toBe("omx-clap-host.silent-under-note");
Expand Down
5 changes: 3 additions & 2 deletions src/host-admission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ export interface ModHostRun {
* An instrument under the adapter, played one held note at its JACK ports: the peak in linear magnitude, or why it could
* not be read. Absent for a plugin that is no instrument, and for any plugin under mod-host.
*/
readonly sound?: { readonly peak?: number; readonly problem?: string };
/** `notes`: how many keys were played; a run from when one key was played has none. */
readonly sound?: { readonly peak?: number; readonly notes?: number; readonly problem?: string };
}

/** At or under this magnitude (-120 dBFS) a held note is taken as digital silence: clap-qualify's own SILENCE. */
Expand Down Expand Up @@ -120,7 +121,7 @@ export function classifyProtocolRun(host: ProtocolHost, run: ModHostRun): HostAd
if (run.sound !== undefined) {
const { peak, problem } = run.sound;
if (peak === undefined) return { host, measured: true, admitted: false, code: ADAPTER_LEVEL_UNMEASURED_CODE, params: { problem: problem ?? "no figure" } };
if (!(peak > SILENT_PEAK)) return { host, measured: true, admitted: false, code: ADAPTER_SILENT_CODE, params: { peak } };
if (!(peak > SILENT_PEAK)) return { host, measured: true, admitted: false, code: ADAPTER_SILENT_CODE, params: { peak, ...(run.sound.notes === undefined ? {} : { notes: run.sound.notes }) } };
}
return admitted(host);
}
Expand Down
7 changes: 7 additions & 0 deletions src/qualify-adapter-pass.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,13 @@ describe("--adapter-only: resuming", () => {
const d = device([LV2_CLEAN]);
await pass(d, stand([]));
expect(adapterFinished(d.record(LV2_CLEAN), VERSIONS)).toBe(true);
// silent under the one key the run once played is run again; silent under the wide range is not
const silentCell = (params: Record<string, number>) => {
const r = d.record(LV2_CLEAN);
return { ...r, adapter: { ...r.adapter!, measured: true, admitted: false, cells: r.adapter!.cells!.map((c) => ({ ...c, measured: true, admitted: false, code: "omx-clap-host.silent-under-note", params })) } };
};
expect(adapterFinished(silentCell({ peak: 0 }), VERSIONS)).toBe(false);
expect(adapterFinished(silentCell({ peak: 0, notes: 30 }), VERSIONS)).toBe(true);
expect(adapterFinished(d.record(LV2_CLEAN), { ...VERSIONS, "omx-clap-host": "0.26.0" })).toBe(false);
const seen: Seen[] = [];
await pass(d, stand(seen, () => admitted, { versions: (tool) => ({ status: 0, stdout: `x version: ${tool.includes("omx-clap") ? "0.26.0" : "0.15.3"}\n` }) }));
Expand Down
4 changes: 3 additions & 1 deletion src/qualify-adapter-pass.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { existsSync, mkdtempSync, readFileSync, readdirSync, rmSync } from "node
import { tmpdir } from "node:os";
import { basename, join } from "node:path";

import { ADAPTER_HOST, SPAWNED_ADMISSIONS, admitUnderAdapter, type AdmissionRuns, type HostAdmission } from "./host-admission.js";
import { ADAPTER_HOST, ADAPTER_SILENT_CODE, SPAWNED_ADMISSIONS, admitUnderAdapter, type AdmissionRuns, type HostAdmission } from "./host-admission.js";
import { HOSTD_ENV } from "./hostd-pin-files.js";
import { cellKey, type GridCell } from "./measurement-grid.js";
import { defaultAddressSpaceLimit } from "./direct-limit.js";
Expand Down Expand Up @@ -187,6 +187,8 @@ export const mergeAdapterRun = (record: QualificationRecord, block: AdapterBlock
export function adapterFinished(record: QualificationRecord, versions: Readonly<Record<string, string>>, cells: readonly GridCell[] = ADAPTER_PASS_CELLS): boolean {
const a = record.adapter;
if (a === undefined || !a.measured || a.versions === undefined || a.cells === undefined) return false;
// silence under the one key the run once played says nothing of a plugin that maps others: it is run again
if (a.cells.some((c) => c.code === ADAPTER_SILENT_CODE && c.params["notes"] === undefined)) return false;
const same = Object.keys(versions).length === Object.keys(a.versions).length && Object.entries(versions).every(([k, v]) => a.versions![k] === v);
return same && cells.every((c) => a.cells!.some((x) => cellKey(x) === cellKey(c)));
}
Expand Down
9 changes: 9 additions & 0 deletions src/qualify-isolated.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,15 @@ describe("the step itself, on a private server", () => {
expect(facts.midiInputsLinked, JSON.stringify(facts)).toBeGreaterThan(0);
});

it("a drum kit, which answers only some keys with a short hit, is heard: isolated", (ctx) => {
if (!ready) {
if (process.env["CI"] !== undefined) throw new Error("jackd, plugin-hostd or native/build is missing: CI installs them");
ctx.skip("needs jackd, plugin-hostd and native/build");
}
const facts = spawnedIsolatedRunner()(`clap:${join(dirname(FIXTURE), "synth-drum.clap")}#${SYNTH_ID}-drum`, CELL, process.env);
expect(judgeIsolatedRun(facts).result, JSON.stringify(facts)).toBe(ISOLATED_RESULT.isolated);
});

it("a pass runs the step plugin after plugin: the server of the last one leaves its name free", (ctx) => {
if (!ready) {
if (process.env["CI"] !== undefined) throw new Error("jackd, plugin-hostd or native/build is missing: CI installs them");
Expand Down
2 changes: 1 addition & 1 deletion src/scanned-descriptors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ describe("the CLAP fixture set through clap-qualify and descriptors.py", () => {
const doc = parseClapScan(text);
const rows = clapScanDescriptors(doc);
const written = readDir(dir);
expect(rows.length).toBe(18);
expect(rows.length).toBe(19);
expect([...written.keys()].sort()).toEqual(rows.map((r) => r.uri.replace(/^urn:clap:/, "")).sort());
for (const row of rows) {
const id = row.uri.replace(/^urn:clap:/, "");
Expand Down
Loading
Loading