mirror of
https://github.com/got-feedBack/feedBack-desktop.git
synced 2026-07-20 11:51:53 +00:00
126 lines
5.8 KiB
JavaScript
126 lines
5.8 KiB
JavaScript
// Multi-input source API smoke test (Phase 1).
|
|
//
|
|
// Exercises the native source pool + source-indexed scoring API without a live
|
|
// audio device: addSource/removeSource/listSources and the *Source* methods keep
|
|
// their shapes, the pool is bounded, sources[0] is permanent, and the legacy
|
|
// un-suffixed methods still target source 0 (backward compatibility).
|
|
|
|
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const path = require('node:path');
|
|
|
|
const ADDON = path.join(__dirname, '..', 'build', 'Release', 'slopsmith_audio.node');
|
|
|
|
let audio;
|
|
try {
|
|
audio = require(ADDON);
|
|
} catch (e) {
|
|
test('multi-source (skipped — addon not built)', { skip: true }, () => {});
|
|
}
|
|
|
|
const CHART = {
|
|
arrangement: 'guitar',
|
|
stringCount: 6,
|
|
tuningOffsets: [0, 0, 0, 0, 0, 0],
|
|
timingTolerance: 0.1,
|
|
notes: [{ id: 'n0', t: 1.0, s: 0, f: 3, sus: 0 }],
|
|
};
|
|
const SCORE_REQ = {
|
|
arrangement: 'guitar',
|
|
stringCount: 6,
|
|
offsets: [0, 0, 0, 0, 0, 0],
|
|
notes: [{ s: 0, f: 3 }, { s: 1, f: 2 }],
|
|
};
|
|
|
|
if (audio) {
|
|
test('multi-input source API', async (t) => {
|
|
audio.init();
|
|
t.after(() => { try { audio.shutdown(); } catch { /* ignore */ } });
|
|
|
|
await t.test('listSources starts with the permanent source 0', () => {
|
|
const list = audio.listSources();
|
|
assert.ok(Array.isArray(list), 'listSources returns an array');
|
|
assert.equal(list.length, 1, 'only source 0 active at start');
|
|
assert.equal(list[0].id, 0);
|
|
assert.equal(list[0].active, true);
|
|
});
|
|
|
|
let sid;
|
|
await t.test('addSource activates a pooled chain bound to a channel', () => {
|
|
sid = audio.addSource(1);
|
|
assert.equal(typeof sid, 'number');
|
|
assert.ok(sid >= 1, 'new source id is >= 1 (0 is permanent)');
|
|
const list = audio.listSources();
|
|
assert.equal(list.length, 2, 'two sources active');
|
|
const added = list.find((s) => s.id === sid);
|
|
assert.ok(added, 'added source is listed');
|
|
assert.equal(added.inputChannel, 1, 'bound to requested channel');
|
|
});
|
|
|
|
await t.test('setSourceChart accepts a chart for a valid id, rejects a bad id', () => {
|
|
assert.equal(audio.setSourceChart(sid, CHART), true, 'valid id + chart -> true');
|
|
assert.equal(audio.setSourceChart(999, CHART), false, 'out-of-range id -> false');
|
|
assert.equal(audio.setSourceChart(sid, { stringCount: 6, tuningOffsets: [0], notes: [] }), false,
|
|
'malformed chart (offsets != stringCount) -> false');
|
|
});
|
|
|
|
await t.test('scoreSourceChord keeps its shape; bad id -> empty failure', () => {
|
|
const res = audio.scoreSourceChord(sid, SCORE_REQ);
|
|
assert.ok(res && typeof res === 'object');
|
|
for (const k of ['score', 'hitStrings', 'totalStrings', 'isHit', 'results'])
|
|
assert.ok(k in res, `result has ${k}`);
|
|
assert.equal(res.results.length, 2, 'one result per note');
|
|
const bad = audio.scoreSourceChord(999, SCORE_REQ);
|
|
assert.equal(bad.totalStrings, 0, 'bad id -> no-request failure shape');
|
|
});
|
|
|
|
await t.test('getSourceNoteVerdicts / getSourceRawAudioFrame / getSourcePitchDetection', () => {
|
|
const verdicts = audio.getSourceNoteVerdicts(sid, 1.0, true);
|
|
assert.ok(Array.isArray(verdicts), 'verdicts is an array (empty without a device)');
|
|
assert.equal(audio.getSourceNoteVerdicts(999), null, 'bad id -> null');
|
|
|
|
const frame = audio.getSourceRawAudioFrame(sid, 2048);
|
|
assert.ok(frame instanceof Float32Array && frame.length === 2048, 'raw frame sized');
|
|
assert.equal(audio.getSourceRawAudioFrame(999).length, 0, 'bad id -> empty');
|
|
|
|
for (const fn of ['getSourcePitchDetection', 'getSourceRawPitchDetection']) {
|
|
const det = audio[fn](sid);
|
|
for (const k of ['frequency', 'confidence', 'midiNote', 'cents', 'noteName'])
|
|
assert.ok(k in det, `${fn} detection has ${k}`);
|
|
assert.equal(audio[fn](999).frequency, -1, `${fn} bad id -> no-detection shape`);
|
|
}
|
|
});
|
|
|
|
await t.test('the pool is bounded and the 0 source is permanent', () => {
|
|
// Fill the remaining slots (pool is 8; source 0 + sid already used).
|
|
const added = [];
|
|
for (let i = 0; i < 8; i++) {
|
|
const id = audio.addSource(-1);
|
|
if (id < 0) break;
|
|
added.push(id);
|
|
}
|
|
assert.equal(audio.addSource(-1), -1, 'addSource returns -1 when the pool is full');
|
|
assert.equal(audio.removeSource(0), false, 'source 0 cannot be removed');
|
|
// Clean the ones we added in this subtest.
|
|
for (const id of added) assert.equal(audio.removeSource(id), true, `removeSource(${id})`);
|
|
});
|
|
|
|
await t.test('removeSource deactivates and frees the slot for reuse', () => {
|
|
assert.equal(audio.removeSource(sid), true, 'removeSource(sid) -> true');
|
|
assert.equal(audio.removeSource(sid), false, 'removing twice -> false');
|
|
const list = audio.listSources();
|
|
assert.equal(list.length, 1, 'back to just source 0');
|
|
const reused = audio.addSource(2);
|
|
assert.ok(reused >= 1, 'a freed slot is reusable');
|
|
audio.removeSource(reused);
|
|
});
|
|
|
|
await t.test('legacy methods still target source 0 (backward compat)', () => {
|
|
assert.equal(audio.setChart(CHART), true, 'legacy setChart works');
|
|
const res = audio.scoreChord(SCORE_REQ);
|
|
assert.equal(res.results.length, 2, 'legacy scoreChord works');
|
|
assert.ok(Array.isArray(audio.getNoteVerdicts()), 'legacy getNoteVerdicts works');
|
|
});
|
|
});
|
|
}
|