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 */
10871118const 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