@@ -20,16 +20,25 @@ import {
2020} from "../constants/sounds" ;
2121
2222async function gethowler ( ) : Promise < typeof import ( "howler" ) > {
23- return await import ( "howler" ) ;
23+ return import ( "howler" ) ;
24+ }
25+
26+ let isInit = false ;
27+ const loadedBundles : Set < PlaySoundOnClick > = new Set ( ) ;
28+ const howlers : Record < string , Howl > = { } ;
29+
30+ async function getHowl ( src : string ) : Promise < Howl > {
31+ const cached = howlers [ src ] ;
32+
33+ if ( cached !== undefined ) return cached ;
34+
35+ const Howl = ( await gethowler ( ) ) . Howl ;
36+ const howl = new Howl ( { src } ) ;
37+ howlers [ src ] = howl ;
38+
39+ return howl ;
2440}
2541
26- type ClickSounds = Record <
27- string ,
28- {
29- sounds : Howl [ ] ;
30- counter : number ;
31- } [ ]
32- > ;
3342type ErrorSounds = Record <
3443 Exclude < PlaySoundOnError , "off" > ,
3544 {
@@ -39,26 +48,19 @@ type ErrorSounds = Record<
3948> ;
4049
4150let errorSounds : ErrorSounds | null = null ;
42- let clickSounds : ClickSounds | null = null ;
4351
4452let timeWarning : Howl | null = null ;
4553
4654let fartReverb : Howl | null = null ;
4755
4856async function initTimeWarning ( ) : Promise < void > {
49- const Howl = ( await gethowler ( ) ) . Howl ;
5057 if ( timeWarning !== null ) return ;
51- timeWarning = new Howl ( {
52- src : "../sound/timeWarning.wav" ,
53- } ) ;
58+ timeWarning = await getHowl ( "../sound/timeWarning.wav" ) ;
5459}
5560
5661async function initFartReverb ( ) : Promise < void > {
57- const Howl = ( await gethowler ( ) ) . Howl ;
5862 if ( fartReverb !== null ) return ;
59- fartReverb = new Howl ( {
60- src : "../sound/fart-reverb.wav" ,
61- } ) ;
63+ fartReverb = await getHowl ( "../sound/fart-reverb.wav" ) ;
6264}
6365
6466async function initErrorSound ( ) : Promise < void > {
@@ -98,43 +100,59 @@ async function initErrorSound(): Promise<void> {
98100}
99101
100102async function init ( ) : Promise < void > {
101- const Howl = ( await gethowler ( ) ) . Howl ;
102- if ( clickSounds !== null ) return ;
103- clickSounds = Object . fromEntries (
104- Object . entries ( clickSoundConfig ) . map ( ( [ key , value ] ) => [
105- key ,
106- value . map ( ( it ) => ( {
107- ...it ,
108- sounds : it . sounds . map ( ( src ) => new Howl ( { src } ) ) ,
109- } ) ) ,
110- ] ) ,
111- ) ;
112- Howler . volume ( Config . soundVolume ) ;
103+ if ( ! isInit ) {
104+ isInit = true ;
105+ const howler = await gethowler ( ) ;
106+ howler . Howler . volume ( Config . soundVolume ) ;
107+ }
108+
109+ //preload sounds
110+ const clickId = Config . playSoundOnClick ;
111+ if ( clickId === "off" ) return ;
112+
113+ if ( ! loadedBundles . has ( clickId ) ) {
114+ loadedBundles . add ( clickId ) ;
115+
116+ const config = clickSoundConfig [ clickId ] ;
117+
118+ if ( config === undefined ) return ;
119+
120+ await Promise . all (
121+ config . flatMap ( ( it ) => it . sounds ) . map ( async ( it ) => getHowl ( it ) ) ,
122+ ) ;
123+ }
124+
125+ //preload error sounds
126+ await initErrorSound ( ) ;
113127}
114128
115- export async function previewClick ( val : PlaySoundOnClick ) : Promise < void > {
116- if ( val === "off" ) return ;
129+ export async function previewClick ( clickId : PlaySoundOnClick ) : Promise < void > {
130+ if ( clickId === "off" ) return ;
117131
118- const config = soundsConfig [ val ] ;
132+ const config = soundsConfig [ clickId ] ;
119133
120134 if ( "oscillatorType" in config ) {
121135 playNote ( { codeOverride : "KeyQ" , oscillatorType : config . oscillatorType } ) ;
122136 return ;
123137 }
124138
125139 if ( "validNotes" in config ) {
126- scaleConfigurations [ val ] ?. preview ( ) ;
140+ scaleConfigurations [ clickId ] ?. preview ( ) ;
127141 }
128142
129- if ( clickSounds === null ) await init ( ) ;
130-
131- const safeClickSounds = clickSounds as ClickSounds ;
143+ await init ( ) ;
132144
133- const clickSoundIds = Object . keys ( safeClickSounds ) ;
134- if ( ! clickSoundIds . includes ( val ) ) return ;
145+ const safeClickSounds = clickSoundConfig [ clickId ] ;
146+ if (
147+ safeClickSounds === undefined ||
148+ safeClickSounds [ 0 ] ?. sounds [ 0 ] === undefined
149+ ) {
150+ return ;
151+ }
135152
136- safeClickSounds ?. [ val ] ?. [ 0 ] ?. sounds [ 0 ] ?. seek ( 0 ) ;
137- safeClickSounds ?. [ val ] ?. [ 0 ] ?. sounds [ 0 ] ?. play ( ) ;
153+ const howl = await getHowl ( safeClickSounds [ 0 ] ?. sounds [ 0 ] ) ;
154+ howl . seek ( 0 ) ;
155+ howl . play ( ) ;
138156}
139157
140158export async function previewError ( val : PlaySoundOnError ) : Promise < void > {
@@ -257,7 +275,7 @@ function createPreviewScale(validNotes: ValidNotes[]): () => void {
257275 } ;
258276
259277 return async ( ) => {
260- if ( clickSounds === null ) await init ( ) ;
278+ await init ( ) ;
261279 playScale ( validNotes , scale ) ;
262280 } ;
263281}
@@ -387,14 +405,15 @@ export async function playClick(codeOverride?: string): Promise<void> {
387405 return ;
388406 }
389407
390- if ( clickSounds === null ) await init ( ) ;
391-
392- const sounds = ( clickSounds as ClickSounds ) [ Config . playSoundOnClick ] ;
408+ await init ( ) ;
393409
410+ const sounds = clickSoundConfig [ val ] ;
394411 if ( sounds === undefined ) throw new Error ( "Invalid click sound ID" ) ;
395-
396412 const randomSound = randomElementFromArray ( sounds ) ;
397- const soundToPlay = randomSound . sounds [ randomSound . counter ] as Howl ;
413+
414+ const src = randomSound . sounds [ randomSound . counter ] ;
415+ if ( src === undefined ) throw new Error ( "Invalid click sound ID" ) ;
416+ const soundToPlay = await getHowl ( src ) ;
398417
399418 randomSound . counter ++ ;
400419 if ( randomSound . counter === randomSound . sounds . length ) {
0 commit comments