Skip to content

Commit 254c0db

Browse files
authored
Merge pull request #101 from LouDnl/dev
Dev v0.7.7
2 parents cf1ffb3 + 7876391 commit 254c0db

11 files changed

Lines changed: 396 additions & 91 deletions

File tree

examples/config-tool-web/index.html

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -103,23 +103,23 @@ <h1 class="c64-cursor">USBSID-Pico</h1>
103103
<label class="c64-radio">
104104
<input type="radio" name="emulator-mode" value="usplayer-asid">
105105
<span class="c64-radio-text">ASID (USBSID-Player)</span>
106+
<span id="asid-midi-row" style="display:none">
107+
<label for="asid-midi-outputs" style="margin-left:12px">ASID MIDI:</label>
108+
<select id="asid-midi-outputs" class="c64-select">
109+
<option value="">- no MIDI -</option>
110+
</select>
111+
</span>
106112
</label>
107113
<label class="c64-radio">
108114
<input type="radio" name="emulator-mode" value="sendsid">
109115
<span class="c64-radio-text">SendSID (onboard USBSID-Player)</span>
116+
<span id="sendsid-socket-row" style="display:none;align-items:center;gap:6px;margin-left:12px">
117+
<input type="checkbox" id="chk-sendsid-socket2" style="cursor:pointer;accent-color:var(--c64-cyan)">
118+
<label for="chk-sendsid-socket2" style="cursor:pointer;font-size:0.9rem;color:var(--c64-cyan);text-transform:uppercase;user-select:none">Force socket 2 play</label>
119+
</span>
110120
</label>
111121
</div>
112122
</fieldset>
113-
<span id="asid-midi-row" style="display:none">
114-
<label for="asid-midi-outputs" style="margin-left:12px">ASID MIDI:</label>
115-
<select id="asid-midi-outputs" class="c64-select">
116-
<option value="">- no MIDI -</option>
117-
</select>
118-
</span>
119-
<span id="sendsid-socket-row" style="display:none;align-items:center;gap:6px;margin-left:12px">
120-
<input type="checkbox" id="chk-sendsid-socket2" style="cursor:pointer;accent-color:var(--c64-cyan)">
121-
<label for="chk-sendsid-socket2" style="cursor:pointer;font-size:0.9rem;color:var(--c64-cyan);text-transform:uppercase;user-select:none">Force socket 2</label>
122-
</span>
123123
</div>
124124
</div>
125125

examples/config-tool-web/usbsid-app.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ async function onDeviceConnected() {
242242
refreshTransportButtons();
243243

244244
/* Auto-read config */
245-
/* setTimeout(() => doReadConfig(), 300); */
245+
setTimeout(() => doReadConfig(), 300);
246246
}
247247

248248
/* Check if v1.5+ board needs config confirmation and update UI accordingly */

examples/config-tool-web/usbsid-driver.js

Lines changed: 92 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,25 @@
11
/**
22
* USBSID-Pico WebUSB Driver
33
* Direct WebUSB implementation (no worker) for config + playback in same page.
4+
*
5+
* ONE FILE, FOUR PLACES. Byte identical in all of them, and it has to stay that
6+
* way: this is the only driver that recovers from a lost reply, and a copy that
7+
* falls behind takes its host's config reads with it.
8+
*
9+
* repo/examples/config-tool-web/usbsid-driver.js loaded by a <script>
10+
* tag, index.html:16
11+
* usbsid.loudai.nl/public_html/usbsid-driver.js the same, live
12+
* git.deepsid/js/handlers/usplayer/usbsid-driver.js imported as a module
13+
* by usplayer-adapter-deepsid.js
14+
* deepsid/public_html/deepsid/js/handlers/usplayer/... the same, live
15+
*
16+
* There is deliberately no copy in `player-repo/web`: the player does not use it,
17+
* the hosts do, so there is no source tree that owns it. Compare the four before
18+
* changing any one of them.
19+
*
20+
* Loaded both ways, which is why the tail of this file assigns to `globalThis`
21+
* rather than using `export`: a file with an export in it is a module, and the
22+
* plain script tag above could no longer load it.
423
* Updated to match config.h command set.
524
*/
625

@@ -289,8 +308,9 @@ class USBSIDDevice {
289308
}
290309
await this._device.claimInterface(this._ifaceNum);
291310
await this._device.selectAlternateInterface(this._ifaceNum, 0);
292-
try { await this._device.clearHalt('out', this._epOut); } catch (_) {}
293-
try { await this._device.clearHalt('in', this._epIn); } catch (_) {}
311+
// The following two lines are commented out, they cause reading issues!
312+
// try { await this._device.clearHalt('out', this._epOut); } catch (_) {}
313+
// try { await this._device.clearHalt('in', this._epIn); } catch (_) {}
294314
await this._device.controlTransferOut({
295315
requestType: 'class',
296316
recipient: 'interface',
@@ -353,7 +373,8 @@ class USBSIDDevice {
353373
const buf = data instanceof Uint8Array ? data : new Uint8Array(data);
354374
try {
355375
await this._device.transferOut(this._epOut, buf);
356-
const result = await this._device.transferIn(this._epIn, readLen);
376+
const result = await this._device.transferIn(this._epIn, MAX_PACKET_SIZE); /* Vendor is fixed at 64 bytes */
377+
this._device.transferIn(this._epIn, 0); /* Account for the second 0 length packet */
357378
return new Uint8Array(result.data.buffer);
358379
} catch (e) {
359380
this._log('writeAndRead error:', e);
@@ -365,7 +386,8 @@ class USBSIDDevice {
365386
async read(readLen = MAX_PACKET_SIZE) {
366387
if (!this._isOpen) return null;
367388
try {
368-
const result = await this._device.transferIn(this._epIn, readLen);
389+
const result = await this._device.transferIn(this._epIn, MAX_PACKET_SIZE); /* Vendor is fixed at 64 bytes */
390+
this._device.transferIn(this._epIn, 0); /* Account for the second 0 length packet */
369391
return new Uint8Array(result.data.buffer);
370392
} catch (e) {
371393
this._log('read error:', e);
@@ -417,9 +439,10 @@ class USBSIDDevice {
417439
* SID count and the FM/OPL slot one after another produced three zeros and
418440
* then a fourth read that returned one of *their* replies, which is how this
419441
* was found. Serialised, so the pairing holds. */
420-
const mine = this._cfgReadChain = (this._cfgReadChain || Promise.resolve())
421-
.then(() => this._configCmdReadOnce(sub, b2, b3, b4, b5, numBytes, timeoutMs),
422-
() => this._configCmdReadOnce(sub, b2, b3, b4, b5, numBytes, timeoutMs));
442+
const mine = this._configCmdReadOnce(sub, b2, b3, b4, b5, numBytes, timeoutMs);
443+
// const mine = this._cfgReadChain = (this._cfgReadChain || Promise.resolve())
444+
// .then(() => this._configCmdReadOnce(sub, b2, b3, b4, b5, numBytes, timeoutMs)/* ,
445+
// () => this._configCmdReadOnce(sub, b2, b3, b4, b5, numBytes, timeoutMs) */);
423446
return await mine;
424447
}
425448

@@ -442,23 +465,24 @@ class USBSIDDevice {
442465
* consumes it. Raced, because the alternative is waiting for ever when the
443466
* timeout was a command the firmware does not implement, and given a long
444467
* enough limit that a board which is going to answer has answered. */
445-
if (this._cfgReadSkew) {
446-
this._cfgReadSkew = false;
447-
try {
448-
const stale = await Promise.race([
449-
this._device.transferIn(this._epIn, MAX_PACKET_SIZE),
450-
new Promise((res) => setTimeout(() => res(null), 1500)),
451-
]);
452-
if (stale) {
453-
usbsidLog('configCmdRead: resynchronised, discarded a late reply');
454-
} else {
455-
/* Nothing came, so the earlier timeout was a question this board does
456-
* not answer. Say so once: the reads are in step, there is simply no
457-
* answer to that one. */
458-
usbsidLog('configCmdRead: no late reply, the board does not answer that command');
459-
}
460-
} catch (_) { /* closed under us; the next read will report it */ }
461-
}
468+
// if (this._cfgReadSkew) {
469+
// this._cfgReadSkew = false;
470+
// try {
471+
// const stale = await Promise.race([
472+
// this._device.transferIn(this._epIn, MAX_PACKET_SIZE), /* Vendor is fixed at 64 bytes */
473+
// new Promise((res) => setTimeout(() => res(null), 1500)),
474+
// ]);
475+
// this._device.transferIn(this._epIn, 0); /* Account for the second 0 length packet */
476+
// if (stale) {
477+
// usbsidLog('configCmdRead: resynchronised, discarded a late reply');
478+
// } else {
479+
// /* Nothing came, so the earlier timeout was a question this board does
480+
// * not answer. Say so once: the reads are in step, there is simply no
481+
// * answer to that one. */
482+
// usbsidLog('configCmdRead: no late reply, the board does not answer that command');
483+
// }
484+
// } catch (_) { /* closed under us; the next read will report it */ }
485+
// }
462486

463487
try {
464488
await this._device.transferOut(this._epOut, cmdBuf);
@@ -467,12 +491,14 @@ class USBSIDDevice {
467491
return packets;
468492
}
469493
try {
470-
const r = await Promise.race([
471-
this._device.transferIn(this._epIn, numBytes),
472-
new Promise((_, reject) =>
473-
setTimeout(() => reject(new Error('configCmdRead timeout')), timeoutMs)
474-
),
475-
]);
494+
const r = await this._device.transferIn(this._epIn, MAX_PACKET_SIZE); /* Vendor is fixed at 64 bytes */
495+
// const r = await Promise.race([
496+
// this._device.transferIn(this._epIn, MAX_PACKET_SIZE), /* Vendor is fixed at 64 bytes */
497+
// new Promise((_, reject) =>
498+
// setTimeout(() => reject(new Error('configCmdRead timeout')), timeoutMs)
499+
// ),
500+
// ]);
501+
this._device.transferIn(this._epIn, 0); /* Account for the second 0 length packet */
476502
packets.push(new Uint8Array(r.data.buffer));
477503
} catch (e) {
478504
/* Remember it: the next read clears up after this one. The abandoned
@@ -530,10 +556,13 @@ class USBSIDDevice {
530556
' has not answered after 10s, still waiting (reads are serialised,'
531557
+ ' so nothing else will run until it does)');
532558
}, 10000);
559+
const CC = this.cmd(COMMAND, CONFIG);
560+
const cmdBuf = new Uint8Array([CC, sub, b2, b3, b4, b5]);
533561
try {
534-
await this._device.transferOut(this._epOut,
535-
new Uint8Array([this.cmd(COMMAND, CONFIG), sub, b2, b3, b4, b5]));
536-
const r = await this._device.transferIn(this._epIn, len);
562+
await this._device.transferOut(this._epOut, cmdBuf);
563+
const r = await this._device.transferIn(this._epIn, MAX_PACKET_SIZE); /* Vendor is fixed at 64 bytes */
564+
r.data.byteLength == 64 ? this._device.transferIn(this._epIn, 0) : null ; /* Account for the second 0 length packet */
565+
await us_delay(100);
537566
return new Uint8Array(r.data.buffer);
538567
} catch (e) {
539568
usbsidLog('configReadNoRace error:', e.message || e);
@@ -556,8 +585,9 @@ class USBSIDDevice {
556585
* without this it can run at the same time as one of those and the two take
557586
* each other's replies: one reader at a time is the only thing that keeps a
558587
* reply paired with its question. */
559-
const mine = this._cfgReadChain = (this._cfgReadChain || Promise.resolve())
560-
.then(() => this._readConfigOnce(), () => this._readConfigOnce());
588+
const mine = this._readConfigOnce();
589+
// const mine = this._cfgReadChain = (this._cfgReadChain || Promise.resolve())
590+
// .then(() => this._readConfigOnce(), () => this._readConfigOnce());
561591
return await mine;
562592
}
563593

@@ -579,8 +609,9 @@ class USBSIDDevice {
579609
* in the buffer are stale and skipped by magic checks in later reads.
580610
* WebUSB rejects all pending transferIn on disconnect, so no infinite hang. */
581611
await this._device.transferOut(this._epOut, cmdBuf);
582-
for (let i = 0; i < 8; i++) {
583-
const r = await this._device.transferIn(this._epIn, MAX_PACKET_SIZE);
612+
for (let i = 0; i < 4; i++) {
613+
const r = await this._device.transferIn(this._epIn, MAX_PACKET_SIZE); /* Vendor is fixed at 64 bytes */
614+
r.data.byteLength == 64 ? this._device.transferIn(this._epIn, 0) : null ; /* Account for the second 0 length packet */
584615
const chunk = new Uint8Array(r.data.buffer);
585616
if (all.length === 0) {
586617
if (chunk.length === 0) { this._log('readConfig: skipping zero-length packet'); continue; }
@@ -1085,3 +1116,27 @@ class USBSID_queue {
10851116

10861117
/* Singleton device instance */
10871118
const usbsidDevice = new USBSIDDevice();
1119+
1120+
/* Usable from an ES module as well as from a classic script.
1121+
*
1122+
* `export` is deliberately not used: config-tool-web loads this file with a
1123+
* plain <script src="usbsid-driver.js"> tag (index.html:16), and a file with an
1124+
* export in it is a module, which that tag cannot load. Properties on globalThis
1125+
* work in both, and the declarations above still shadow them for anything
1126+
* referring to the bare names, so nothing that works today changes.
1127+
*
1128+
* `usbsidLog` is the host's, not this file's: config-tool-web declares it in
1129+
* usbsid-app.js, a classic script, so it is simply there. Loaded as a module by
1130+
* another host there is no such global and the thirteen calls to it in here
1131+
* throw a ReferenceError from inside a read. The fallback is only installed when
1132+
* the host has not supplied one.
1133+
*/
1134+
if (typeof usbsidLog === 'undefined') {
1135+
globalThis.usbsidLog = (...args) => console.debug('[usbsid-driver]', ...args);
1136+
}
1137+
if (typeof globalThis !== 'undefined') {
1138+
if (!globalThis.USBSIDDevice) globalThis.USBSIDDevice = USBSIDDevice;
1139+
/* The singleton this file already creates, so a second host talks to the same
1140+
* board through the same driver rather than opening its own. */
1141+
if (!globalThis.usbsidDevice) globalThis.usbsidDevice = usbsidDevice;
1142+
}

0 commit comments

Comments
 (0)