1+ namespace pxsim . AudioContextManager {
2+ const waveForms : OscillatorType [ ] = [ null , "triangle" , "sawtooth" , "sine" ]
3+ let noiseBuffer : AudioBuffer
4+ let rectNoiseBuffer : AudioBuffer
5+ let cycleNoiseBuffer : AudioBuffer [ ] = [ ]
6+ let squareBuffer : AudioBuffer [ ] = [ ]
7+
8+ function getNoiseBuffer ( context : AudioContext ) {
9+ if ( ! noiseBuffer ) {
10+ const bufferSize = 100000 ;
11+ noiseBuffer = context . createBuffer ( 1 , bufferSize , context . sampleRate ) ;
12+ const output = noiseBuffer . getChannelData ( 0 ) ;
13+
14+ let x = 0xf01ba80 ;
15+ for ( let i = 0 ; i < bufferSize ; i ++ ) {
16+ x ^= x << 13 ;
17+ x ^= x >> 17 ;
18+ x ^= x << 5 ;
19+ output [ i ] = ( ( x & 1023 ) / 512.0 ) - 1.0 ;
20+ }
21+ }
22+ return noiseBuffer
23+ }
24+
25+ function getRectNoiseBuffer ( context : AudioContext ) {
26+ // Create a square wave filtered by a pseudorandom bit sequence.
27+ // This uses four samples per cycle to create square-ish waves.
28+ // The Web Audio API's frequency scaling may be using linear
29+ // interpolation which would turn a two-sample wave into a triangle.
30+ if ( ! rectNoiseBuffer ) {
31+ const bufferSize = 131072 ; // must be a multiple of 4
32+ rectNoiseBuffer = context . createBuffer ( 1 , bufferSize , context . sampleRate ) ;
33+ const output = rectNoiseBuffer . getChannelData ( 0 ) ;
34+
35+ let x = 0xf01ba80 ;
36+ for ( let i = 0 ; i < bufferSize ; i += 4 ) {
37+ // see https://en.wikipedia.org/wiki/Xorshift
38+ x ^= x << 13 ;
39+ x ^= x >> 17 ;
40+ x ^= x << 5 ;
41+ if ( x & 0x8000 ) {
42+ output [ i ] = 1.0 ;
43+ output [ i + 1 ] = 1.0 ;
44+ output [ i + 2 ] = - 1.0 ;
45+ output [ i + 3 ] = - 1.0 ;
46+ } else {
47+ output [ i ] = 0.0 ;
48+ output [ i + 1 ] = 0.0 ;
49+ output [ i + 2 ] = 0.0 ;
50+ output [ i + 3 ] = 0.0 ;
51+ }
52+ }
53+ }
54+ return rectNoiseBuffer
55+ }
56+
57+ function getCycleNoiseBuffer ( context : AudioContext , bits : number ) {
58+ if ( ! cycleNoiseBuffer [ bits ] ) {
59+ // Buffer size needs to be a multiple of 4x the largest cycle length,
60+ // 4*64 in this case.
61+ const bufferSize = 1024 ;
62+ const buf = context . createBuffer ( 1 , bufferSize , context . sampleRate ) ;
63+ const output = buf . getChannelData ( 0 ) ;
64+
65+ // See pxt-common-packages's libs/mixer/melody.cpp for details.
66+ // "bits" must be in the range 4..6.
67+ const cycle_bits : number [ ] = [ 0x2df0eb47 , 0xc8165a93 ] ;
68+ const mask_456 : number [ ] = [ 0xf , 0x1f , 0x3f ] ;
69+ for ( let i = 0 ; i < bufferSize ; i += 4 ) {
70+ let cycle : number = i / 4 ;
71+ let is_on : boolean ;
72+ let cycle_mask = mask_456 [ bits - 4 ] ;
73+ cycle &= cycle_mask ;
74+ is_on = ( cycle_bits [ cycle >> 5 ] & ( 1 << ( cycle & 0x1f ) ) ) != 0 ;
75+ if ( is_on ) {
76+ output [ i ] = 1.0 ;
77+ output [ i + 1 ] = 1.0 ;
78+ output [ i + 2 ] = - 1.0 ;
79+ output [ i + 3 ] = - 1.0 ;
80+ } else {
81+ output [ i ] = 0.0 ;
82+ output [ i + 1 ] = 0.0 ;
83+ output [ i + 2 ] = 0.0 ;
84+ output [ i + 3 ] = 0.0 ;
85+ }
86+ }
87+ cycleNoiseBuffer [ bits ] = buf
88+ }
89+ return cycleNoiseBuffer [ bits ]
90+ }
91+
92+ function getSquareBuffer ( context : AudioContext , param : number ) {
93+ if ( ! squareBuffer [ param ] ) {
94+ const bufferSize = 1024 ;
95+ const buf = context . createBuffer ( 1 , bufferSize , context . sampleRate ) ;
96+ const output = buf . getChannelData ( 0 ) ;
97+ for ( let i = 0 ; i < bufferSize ; i ++ ) {
98+ output [ i ] = i < ( param / 100 * bufferSize ) ? 1 : - 1 ;
99+ }
100+ squareBuffer [ param ] = buf
101+ }
102+ return squareBuffer [ param ]
103+ }
104+
105+ /*
106+ #define SW_TRIANGLE 1
107+ #define SW_SAWTOOTH 2
108+ #define SW_SINE 3
109+ #define SW_TUNEDNOISE 4
110+ #define SW_NOISE 5
111+ #define SW_SQUARE_10 11
112+ #define SW_SQUARE_50 15
113+ #define SW_SQUARE_CYCLE_16 16
114+ #define SW_SQUARE_CYCLE_32 17
115+ #define SW_SQUARE_CYCLE_64 18
116+ */
117+
118+
119+ /*
120+ struct SoundInstruction {
121+ uint8_t soundWave;
122+ uint8_t flags;
123+ uint16_t frequency;
124+ uint16_t duration;
125+ uint16_t startVolume;
126+ uint16_t endVolume;
127+ };
128+ */
129+
130+ function getGenerator ( context : AudioContext , waveFormIdx : number , hz : number ) : OscillatorNode | AudioBufferSourceNode {
131+ let form = waveForms [ waveFormIdx ]
132+ if ( form ) {
133+ let src = context . createOscillator ( )
134+ src . type = form
135+ src . frequency . value = hz
136+ return src
137+ }
138+
139+ let buffer : AudioBuffer
140+ if ( waveFormIdx == 4 )
141+ buffer = getRectNoiseBuffer ( context )
142+ else if ( waveFormIdx == 5 )
143+ buffer = getNoiseBuffer ( context )
144+ else if ( 11 <= waveFormIdx && waveFormIdx <= 15 )
145+ buffer = getSquareBuffer ( context , ( waveFormIdx - 10 ) * 10 )
146+ else if ( 16 <= waveFormIdx && waveFormIdx <= 18 )
147+ buffer = getCycleNoiseBuffer ( context , ( waveFormIdx - 16 ) + 4 )
148+ else
149+ return null
150+
151+ let node = context . createBufferSource ( ) ;
152+ node . buffer = buffer ;
153+ node . loop = true ;
154+ const isFilteredNoise = waveFormIdx == 4 || ( 16 <= waveFormIdx && waveFormIdx <= 18 ) ;
155+ if ( isFilteredNoise )
156+ node . playbackRate . value = hz / ( context . sampleRate / 4 ) ;
157+ else if ( waveFormIdx != 5 )
158+ node . playbackRate . value = hz / ( context . sampleRate / 1024 ) ;
159+
160+ return node
161+ }
162+
163+ export class PlayInstructionsSource extends AudioSource {
164+ analyser : AnalyserNode ;
165+ gain : GainNode ;
166+
167+ constructor ( public context : AudioContext , public destination : AudioNode ) {
168+ super ( context , destination ) ;
169+ this . analyser = context . createAnalyser ( ) ;
170+ this . analyser . fftSize = 2048 ;
171+ this . analyser . connect ( this . vca ) ;
172+
173+ this . gain = context . createGain ( ) ;
174+ this . gain . connect ( this . analyser ) ;
175+ }
176+
177+ playInstructionsAsync ( instructions : Uint8Array , isCancelled ?: ( ) => boolean , onPull ?: ( freq : number , volume : number ) => void ) {
178+ return new Promise < void > ( async resolve => {
179+ soundEventCallback ?.( "playinstructions" , instructions ) ;
180+ let resolved = false ;
181+
182+ const oscillators : pxt . Map < OscillatorNode | AudioBufferSourceNode > = { } ;
183+ const gains : pxt . Map < GainNode > = { } ;
184+ let startTime = this . context . currentTime ;
185+ let currentTime = startTime ;
186+ let currentWave = 0 ;
187+
188+ let totalDuration = 0 ;
189+
190+ /** Square waves are perceved as much louder than other sounds, so scale it down a bit to make it less jarring **/
191+ const scaleVol = ( n : number , isSqWave ?: boolean ) => ( n / 1024 ) / 4 * ( isSqWave ? .5 : 1 ) ;
192+
193+ const disconnectNodes = ( ) => {
194+ if ( resolved ) return ;
195+ resolved = true ;
196+
197+ for ( const wave of Object . keys ( oscillators ) ) {
198+ oscillators [ wave ] . stop ( ) ;
199+ oscillators [ wave ] . disconnect ( ) ;
200+ gains [ wave ] . disconnect ( ) ;
201+ }
202+ resolve ( ) ;
203+ }
204+
205+ for ( let i = 0 ; i < instructions . length ; i += 12 ) {
206+ const wave = instructions [ i ] ;
207+ const startFrequency = readUint16 ( instructions , i + 2 ) ;
208+ const duration = readUint16 ( instructions , i + 4 ) / 1000 ;
209+ const startVolume = readUint16 ( instructions , i + 6 ) ;
210+ const endVolume = readUint16 ( instructions , i + 8 ) ;
211+ const endFrequency = readUint16 ( instructions , i + 10 ) ;
212+ totalDuration += duration
213+
214+ if ( wave === 0 ) {
215+ currentTime += duration ;
216+ continue ;
217+ }
218+
219+ const isSquareWave = 11 <= wave && wave <= 15 ;
220+
221+ if ( ! oscillators [ wave ] ) {
222+ oscillators [ wave ] = getGenerator ( this . context , wave , startFrequency ) ;
223+ gains [ wave ] = this . context . createGain ( ) ;
224+ gains [ wave ] . gain . value = 0 ;
225+ gains [ wave ] . connect ( this . gain ) ;
226+ oscillators [ wave ] . connect ( gains [ wave ] ) ;
227+ oscillators [ wave ] . start ( ) ;
228+ }
229+
230+ if ( currentWave && wave !== currentWave ) {
231+ gains [ currentWave ] . gain . setTargetAtTime ( 0 , currentTime , 0.015 ) ;
232+ }
233+
234+ const osc = oscillators [ wave ] ;
235+ const gain = gains [ wave ] ;
236+
237+ if ( osc instanceof OscillatorNode ) {
238+ osc . frequency . setValueAtTime ( startFrequency , currentTime ) ;
239+ osc . frequency . linearRampToValueAtTime ( endFrequency , currentTime + duration ) ;
240+ }
241+ else {
242+ const isFilteredNoise = wave == 4 || ( 16 <= wave && wave <= 18 ) ;
243+
244+ if ( isFilteredNoise )
245+ osc . playbackRate . linearRampToValueAtTime ( endFrequency / ( this . context . sampleRate / 4 ) , currentTime + duration ) ;
246+ else if ( wave != 5 )
247+ osc . playbackRate . linearRampToValueAtTime ( endFrequency / ( this . context . sampleRate / 1024 ) , currentTime + duration ) ;
248+ }
249+ gain . gain . setValueAtTime ( scaleVol ( startVolume , isSquareWave ) , currentTime ) ;
250+ gain . gain . linearRampToValueAtTime ( scaleVol ( endVolume , isSquareWave ) , currentTime + duration ) ;
251+
252+ currentWave = wave ;
253+ currentTime += duration ;
254+ }
255+ this . gain . gain . setTargetAtTime ( 0 , currentTime , 0.015 ) ;
256+
257+ if ( isCancelled || onPull ) {
258+ const handleAnimationFrame = ( ) => {
259+ const time = this . context . currentTime ;
260+ if ( time > startTime + totalDuration ) {
261+ return ;
262+ }
263+
264+ if ( ( isCancelled && isCancelled ( ) ) || this . isDisposed ( ) ) {
265+ disconnectNodes ( ) ;
266+ return ;
267+ }
268+
269+ const { frequency, volume } = findFrequencyAndVolumeAtTime ( ( time - startTime ) * 1000 , instructions ) ;
270+ if ( onPull ) onPull ( frequency , volume / 1024 ) ;
271+
272+ requestAnimationFrame ( handleAnimationFrame )
273+ }
274+ requestAnimationFrame ( handleAnimationFrame ) ;
275+ }
276+
277+ await U . delay ( totalDuration * 1000 )
278+ disconnectNodes ( ) ;
279+ } ) ;
280+ }
281+
282+ dispose ( ) : void {
283+ if ( this . isDisposed ( ) ) return ;
284+ super . dispose ( ) ;
285+ this . analyser . disconnect ( ) ;
286+ this . gain . disconnect ( ) ;
287+ }
288+ }
289+
290+
291+ function readUint16 ( buf : Uint8Array , offset : number ) {
292+ const temp = new Uint8Array ( 2 ) ;
293+ temp [ 0 ] = buf [ offset ] ;
294+ temp [ 1 ] = buf [ offset + 1 ] ;
295+ return new Uint16Array ( temp . buffer ) [ 0 ] ;
296+ }
297+
298+ function findFrequencyAndVolumeAtTime ( millis : number , instructions : Uint8Array ) {
299+ let currentTime = 0 ;
300+
301+ for ( let i = 0 ; i < instructions . length ; i += 12 ) {
302+ const startFrequency = readUint16 ( instructions , i + 2 ) ;
303+ const duration = readUint16 ( instructions , i + 4 ) ;
304+ const startVolume = readUint16 ( instructions , i + 6 ) ;
305+ const endVolume = readUint16 ( instructions , i + 8 ) ;
306+ const endFrequency = readUint16 ( instructions , i + 10 ) ;
307+
308+ if ( currentTime + duration < millis ) {
309+ currentTime += duration ;
310+ continue ;
311+ }
312+
313+ const offset = ( millis - currentTime ) / duration ;
314+
315+ return {
316+ frequency : startFrequency + ( endFrequency - startFrequency ) * offset ,
317+ volume : startVolume + ( endVolume - startVolume ) * offset ,
318+ }
319+ }
320+
321+ return {
322+ frequency : - 1 ,
323+ volume : - 1
324+ } ;
325+ }
326+ }
0 commit comments