@@ -6,12 +6,7 @@ import VM from 'scratch-vm';
6
6
7
7
import { connect } from 'react-redux' ;
8
8
9
- import {
10
- computeChunkedRMS ,
11
- encodeAndAddSoundToVM ,
12
- downsampleIfNeeded ,
13
- dropEveryOtherSample
14
- } from '../lib/audio/audio-util.js' ;
9
+ import { computeChunkedRMS , encodeAndAddSoundToVM , SOUND_BYTE_LIMIT } from '../lib/audio/audio-util.js' ;
15
10
import AudioEffects from '../lib/audio/audio-effects.js' ;
16
11
import SoundEditorComponent from '../components/sound-editor/sound-editor.jsx' ;
17
12
import AudioBufferPlayer from '../lib/audio/audio-buffer-player.js' ;
@@ -44,8 +39,7 @@ class SoundEditor extends React.Component {
44
39
'paste' ,
45
40
'handleKeyPress' ,
46
41
'handleContainerClick' ,
47
- 'setRef' ,
48
- 'resampleBufferToRate'
42
+ 'setRef'
49
43
] ) ;
50
44
this . state = {
51
45
copyBuffer : null ,
@@ -138,32 +132,45 @@ class SoundEditor extends React.Component {
138
132
} ) ;
139
133
}
140
134
submitNewSamples ( samples , sampleRate , skipUndo ) {
141
- return downsampleIfNeeded ( { samples, sampleRate} , this . resampleBufferToRate )
142
- . then ( ( { samples : newSamples , sampleRate : newSampleRate } ) =>
143
- WavEncoder . encode ( {
144
- sampleRate : newSampleRate ,
145
- channelData : [ newSamples ]
146
- } ) . then ( wavBuffer => {
147
- if ( ! skipUndo ) {
148
- this . redoStack = [ ] ;
149
- if ( this . undoStack . length >= UNDO_STACK_SIZE ) {
150
- this . undoStack . shift ( ) ; // Drop the first element off the array
151
- }
152
- this . undoStack . push ( this . getUndoItem ( ) ) ;
153
- }
154
- this . resetState ( newSamples , newSampleRate ) ;
155
- this . props . vm . updateSoundBuffer (
156
- this . props . soundIndex ,
157
- this . audioBufferPlayer . buffer ,
158
- new Uint8Array ( wavBuffer ) ) ;
159
- return true ; // Edit was successful
160
- } )
161
- )
162
- . catch ( e => {
163
- // Encoding failed, or the sound was too large to save so edit is rejected
164
- log . error ( `Encountered error while trying to encode sound update: ${ e } ` ) ;
165
- return false ; // Edit was not applied
135
+ // Encode the new sound into a wav so that it can be stored
136
+ let wavBuffer = null ;
137
+ try {
138
+ wavBuffer = WavEncoder . encode . sync ( {
139
+ sampleRate : sampleRate ,
140
+ channelData : [ samples ]
166
141
} ) ;
142
+
143
+ if ( wavBuffer . byteLength > SOUND_BYTE_LIMIT ) {
144
+ // Cancel the sound update by setting to null
145
+ wavBuffer = null ;
146
+ log . error ( `Refusing to encode sound larger than ${ SOUND_BYTE_LIMIT } bytes` ) ;
147
+ }
148
+ } catch ( e ) {
149
+ // This error state is mostly for the mock sounds used during testing.
150
+ // Any incorrect sound buffer trying to get interpretd as a Wav file
151
+ // should yield this error.
152
+ // This can also happen if the sound is too be allocated in memory.
153
+ log . error ( `Encountered error while trying to encode sound update: ${ e } ` ) ;
154
+ }
155
+
156
+ // Do not submit sound if it could not be encoded (i.e. if too large)
157
+ if ( wavBuffer ) {
158
+ if ( ! skipUndo ) {
159
+ this . redoStack = [ ] ;
160
+ if ( this . undoStack . length >= UNDO_STACK_SIZE ) {
161
+ this . undoStack . shift ( ) ; // Drop the first element off the array
162
+ }
163
+ this . undoStack . push ( this . getUndoItem ( ) ) ;
164
+ }
165
+ this . resetState ( samples , sampleRate ) ;
166
+ this . props . vm . updateSoundBuffer (
167
+ this . props . soundIndex ,
168
+ this . audioBufferPlayer . buffer ,
169
+ new Uint8Array ( wavBuffer ) ) ;
170
+
171
+ return true ; // Update succeeded
172
+ }
173
+ return false ; // Update failed
167
174
}
168
175
handlePlay ( ) {
169
176
this . audioBufferPlayer . stop ( ) ;
@@ -202,15 +209,13 @@ class SoundEditor extends React.Component {
202
209
newSamples . set ( firstPart , 0 ) ;
203
210
newSamples . set ( secondPart , firstPart . length ) ;
204
211
}
205
- this . submitNewSamples ( newSamples , sampleRate ) . then ( ( ) => {
206
- this . setState ( {
207
- trimStart : null ,
208
- trimEnd : null
209
- } ) ;
212
+ this . submitNewSamples ( newSamples , sampleRate ) ;
213
+ this . setState ( {
214
+ trimStart : null ,
215
+ trimEnd : null
210
216
} ) ;
211
217
}
212
218
handleDeleteInverse ( ) {
213
- // Delete everything outside of the trimmers
214
219
const { samples, sampleRate} = this . copyCurrentBuffer ( ) ;
215
220
const sampleCount = samples . length ;
216
221
const startIndex = Math . floor ( this . state . trimStart * sampleCount ) ;
@@ -219,13 +224,10 @@ class SoundEditor extends React.Component {
219
224
if ( clippedSamples . length === 0 ) {
220
225
clippedSamples = new Float32Array ( 1 ) ;
221
226
}
222
- this . submitNewSamples ( clippedSamples , sampleRate ) . then ( success => {
223
- if ( success ) {
224
- this . setState ( {
225
- trimStart : null ,
226
- trimEnd : null
227
- } ) ;
228
- }
227
+ this . submitNewSamples ( clippedSamples , sampleRate ) ;
228
+ this . setState ( {
229
+ trimStart : null ,
230
+ trimEnd : null
229
231
} ) ;
230
232
}
231
233
handleUpdateTrim ( trimStart , trimEnd ) {
@@ -255,15 +257,14 @@ class SoundEditor extends React.Component {
255
257
effects . process ( ( renderedBuffer , adjustedTrimStart , adjustedTrimEnd ) => {
256
258
const samples = renderedBuffer . getChannelData ( 0 ) ;
257
259
const sampleRate = renderedBuffer . sampleRate ;
258
- this . submitNewSamples ( samples , sampleRate ) . then ( success => {
259
- if ( success ) {
260
- if ( this . state . trimStart === null ) {
261
- this . handlePlay ( ) ;
262
- } else {
263
- this . setState ( { trimStart : adjustedTrimStart , trimEnd : adjustedTrimEnd } , this . handlePlay ) ;
264
- }
260
+ const success = this . submitNewSamples ( samples , sampleRate ) ;
261
+ if ( success ) {
262
+ if ( this . state . trimStart === null ) {
263
+ this . handlePlay ( ) ;
264
+ } else {
265
+ this . setState ( { trimStart : adjustedTrimStart , trimEnd : adjustedTrimEnd } , this . handlePlay ) ;
265
266
}
266
- } ) ;
267
+ }
267
268
} ) ;
268
269
}
269
270
tooLoud ( ) {
@@ -286,22 +287,16 @@ class SoundEditor extends React.Component {
286
287
this . redoStack . push ( this . getUndoItem ( ) ) ;
287
288
const { samples, sampleRate, trimStart, trimEnd} = this . undoStack . pop ( ) ;
288
289
if ( samples ) {
289
- return this . submitNewSamples ( samples , sampleRate , true ) . then ( success => {
290
- if ( success ) {
291
- this . setState ( { trimStart : trimStart , trimEnd : trimEnd } , this . handlePlay ) ;
292
- }
293
- } ) ;
290
+ this . submitNewSamples ( samples , sampleRate , true ) ;
291
+ this . setState ( { trimStart : trimStart , trimEnd : trimEnd } , this . handlePlay ) ;
294
292
}
295
293
}
296
294
handleRedo ( ) {
297
295
const { samples, sampleRate, trimStart, trimEnd} = this . redoStack . pop ( ) ;
298
296
if ( samples ) {
299
297
this . undoStack . push ( this . getUndoItem ( ) ) ;
300
- return this . submitNewSamples ( samples , sampleRate , true ) . then ( success => {
301
- if ( success ) {
302
- this . setState ( { trimStart : trimStart , trimEnd : trimEnd } , this . handlePlay ) ;
303
- }
304
- } ) ;
298
+ this . submitNewSamples ( samples , sampleRate , true ) ;
299
+ this . setState ( { trimStart : trimStart , trimEnd : trimEnd } , this . handlePlay ) ;
305
300
}
306
301
}
307
302
handleCopy ( ) {
@@ -327,39 +322,25 @@ class SoundEditor extends React.Component {
327
322
} ) ;
328
323
}
329
324
resampleBufferToRate ( buffer , newRate ) {
330
- return new Promise ( ( resolve , reject ) => {
331
- const sampleRateRatio = newRate / buffer . sampleRate ;
332
- const newLength = sampleRateRatio * buffer . samples . length ;
333
- let offlineContext ;
334
- // Try to use either OfflineAudioContext or webkitOfflineAudioContext to resample
335
- // The constructors will throw if trying to resample at an unsupported rate
336
- // (e.g. Safari/webkitOAC does not support lower than 44khz).
337
- try {
338
- if ( window . OfflineAudioContext ) {
339
- offlineContext = new window . OfflineAudioContext ( 1 , newLength , newRate ) ;
340
- } else if ( window . webkitOfflineAudioContext ) {
341
- offlineContext = new window . webkitOfflineAudioContext ( 1 , newLength , newRate ) ;
342
- }
343
- } catch {
344
- // If no OAC available and downsampling by 2, downsample by dropping every other sample.
345
- if ( newRate === buffer . sampleRate / 2 ) {
346
- return resolve ( dropEveryOtherSample ( buffer ) ) ;
347
- }
348
- return reject ( 'Could not resample' ) ;
325
+ return new Promise ( resolve => {
326
+ if ( window . OfflineAudioContext ) {
327
+ const sampleRateRatio = newRate / buffer . sampleRate ;
328
+ const newLength = sampleRateRatio * buffer . samples . length ;
329
+ const offlineContext = new window . OfflineAudioContext ( 1 , newLength , newRate ) ;
330
+ const source = offlineContext . createBufferSource ( ) ;
331
+ const audioBuffer = offlineContext . createBuffer ( 1 , buffer . samples . length , buffer . sampleRate ) ;
332
+ audioBuffer . getChannelData ( 0 ) . set ( buffer . samples ) ;
333
+ source . buffer = audioBuffer ;
334
+ source . connect ( offlineContext . destination ) ;
335
+ source . start ( ) ;
336
+ offlineContext . startRendering ( ) ;
337
+ offlineContext . oncomplete = ( { renderedBuffer} ) => {
338
+ resolve ( {
339
+ samples : renderedBuffer . getChannelData ( 0 ) ,
340
+ sampleRate : newRate
341
+ } ) ;
342
+ } ;
349
343
}
350
- const source = offlineContext . createBufferSource ( ) ;
351
- const audioBuffer = offlineContext . createBuffer ( 1 , buffer . samples . length , buffer . sampleRate ) ;
352
- audioBuffer . getChannelData ( 0 ) . set ( buffer . samples ) ;
353
- source . buffer = audioBuffer ;
354
- source . connect ( offlineContext . destination ) ;
355
- source . start ( ) ;
356
- offlineContext . startRendering ( ) ;
357
- offlineContext . oncomplete = ( { renderedBuffer} ) => {
358
- resolve ( {
359
- samples : renderedBuffer . getChannelData ( 0 ) ,
360
- sampleRate : newRate
361
- } ) ;
362
- } ;
363
344
} ) ;
364
345
}
365
346
paste ( ) {
@@ -370,11 +351,8 @@ class SoundEditor extends React.Component {
370
351
const newSamples = new Float32Array ( newLength ) ;
371
352
newSamples . set ( samples , 0 ) ;
372
353
newSamples . set ( this . state . copyBuffer . samples , samples . length ) ;
373
- this . submitNewSamples ( newSamples , this . props . sampleRate , false ) . then ( success => {
374
- if ( success ) {
375
- this . handlePlay ( ) ;
376
- }
377
- } ) ;
354
+ this . submitNewSamples ( newSamples , this . props . sampleRate , false ) ;
355
+ this . handlePlay ( ) ;
378
356
} else {
379
357
// else replace the selection with the pasted sound
380
358
const trimStartSamples = this . state . trimStart * samples . length ;
@@ -393,14 +371,11 @@ class SoundEditor extends React.Component {
393
371
const newDurationSeconds = newSamples . length / this . state . copyBuffer . sampleRate ;
394
372
const adjustedTrimStart = trimStartSeconds / newDurationSeconds ;
395
373
const adjustedTrimEnd = trimEndSeconds / newDurationSeconds ;
396
- this . submitNewSamples ( newSamples , this . props . sampleRate , false ) . then ( success => {
397
- if ( success ) {
398
- this . setState ( {
399
- trimStart : adjustedTrimStart ,
400
- trimEnd : adjustedTrimEnd
401
- } , this . handlePlay ) ;
402
- }
403
- } ) ;
374
+ this . submitNewSamples ( newSamples , this . props . sampleRate , false ) ;
375
+ this . setState ( {
376
+ trimStart : adjustedTrimStart ,
377
+ trimEnd : adjustedTrimEnd
378
+ } , this . handlePlay ) ;
404
379
}
405
380
}
406
381
handlePaste ( ) {
0 commit comments