soiz1 commited on
Commit
3f2e5ba
·
verified ·
1 Parent(s): 92915ee

Update src/containers/sound-editor.jsx

Browse files
Files changed (1) hide show
  1. src/containers/sound-editor.jsx +93 -14
src/containers/sound-editor.jsx CHANGED
@@ -48,11 +48,12 @@ class SoundEditor extends React.Component {
48
  'setRef',
49
  'resampleBufferToRate',
50
  'handleModifyMenu',
 
51
  'getSelectionBuffer'
52
  ]);
53
  this.state = {
54
  copyBuffer: null,
55
- chunkLevels: computeChunkedRMS(this.props.samples),
56
  playhead: null, // null is not playing, [0 -> 1] is playing percent
57
  trimStart: null,
58
  trimEnd: null
@@ -69,6 +70,11 @@ class SoundEditor extends React.Component {
69
  document.addEventListener('keydown', this.handleKeyPress);
70
  }
71
  componentWillReceiveProps(newProps) {
 
 
 
 
 
72
  if (newProps.soundId !== this.props.soundId) { // A different sound has been selected
73
  this.redoStack = [];
74
  this.undoStack = [];
@@ -140,7 +146,7 @@ class SoundEditor extends React.Component {
140
  this.audioBufferPlayer.stop();
141
  this.audioBufferPlayer = new AudioBufferPlayer(samples, sampleRate);
142
  this.setState({
143
- chunkLevels: computeChunkedRMS(samples),
144
  playhead: null
145
  });
146
  }
@@ -240,7 +246,9 @@ class SoundEditor extends React.Component {
240
  this.handleStopPlaying();
241
  }
242
  effectFactory(name) {
243
- return () => this.handleEffect(name);
 
 
244
  }
245
  copyCurrentBuffer() {
246
  // Cannot reliably use props.samples because it gets detached by Firefox
@@ -249,7 +257,7 @@ class SoundEditor extends React.Component {
249
  sampleRate: this.audioBufferPlayer.buffer.sampleRate
250
  };
251
  }
252
- handleEffect(name) {
253
  const trimStart = this.state.trimStart === null ? 0.0 : this.state.trimStart;
254
  const trimEnd = this.state.trimEnd === null ? 1.0 : this.state.trimEnd;
255
 
@@ -258,7 +266,7 @@ class SoundEditor extends React.Component {
258
  return;
259
  }
260
 
261
- const effects = new AudioEffects(this.audioBufferPlayer.buffer, name, trimStart, trimEnd);
262
  effects.process((renderedBuffer, adjustedTrimStart, adjustedTrimEnd) => {
263
  const samples = renderedBuffer.getChannelData(0);
264
  const sampleRate = renderedBuffer.sampleRate;
@@ -448,16 +456,12 @@ class SoundEditor extends React.Component {
448
  this.handleUpdateTrim(null, null);
449
  }
450
  }
 
451
  handleModifyMenu() {
452
  // get selected audio
453
  const bufferSelection = this.getSelectionBuffer();
454
  // for preview
455
  const audio = new AudioContext();
456
- // const testNode = audio.createBiquadFilter();
457
- // testNode.type = "lowpass";
458
- // testNode.frequency.value = 880;
459
- // testNode.Q.value = 0.7;
460
- // testNode.connect(audio.destination);
461
  const gainNode = audio.createGain();
462
  gainNode.gain.value = 1;
463
  gainNode.connect(audio.destination);
@@ -470,7 +474,6 @@ class SoundEditor extends React.Component {
470
  const truePitch = isNaN(Number(pitch.value)) ? 0 : Number(pitch.value);
471
  const trueVolume = isNaN(Number(volume.value)) ? 0 : Number(volume.value);
472
  this.handleEffect({
473
- special: true,
474
  pitch: truePitch * 10,
475
  volume: trueVolume
476
  });
@@ -584,6 +587,79 @@ class SoundEditor extends React.Component {
584
  };
585
  valueVolume.oninput = valueVolume.onchange;
586
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
587
  displayPopup(title, width, height, okname, denyname, accepted, cancelled) {
588
  const div = document.createElement("div");
589
  document.body.append(div);
@@ -660,6 +736,7 @@ class SoundEditor extends React.Component {
660
  onFaster={this.effectFactory(effectTypes.FASTER)}
661
  onLouder={this.effectFactory(effectTypes.LOUDER)}
662
  onModifySound={this.handleModifyMenu}
 
663
  onMute={this.effectFactory(effectTypes.MUTE)}
664
  onPaste={this.handlePaste}
665
  onPlay={this.handlePlay}
@@ -689,7 +766,8 @@ SoundEditor.propTypes = {
689
  samples: PropTypes.instanceOf(Float32Array),
690
  soundId: PropTypes.string,
691
  soundIndex: PropTypes.number,
692
- vm: PropTypes.instanceOf(VM).isRequired
 
693
  };
694
 
695
  const mapStateToProps = (state, { soundIndex }) => {
@@ -708,10 +786,11 @@ const mapStateToProps = (state, { soundIndex }) => {
708
  samples: audioBuffer.getChannelData(0),
709
  isFullScreen: state.scratchGui.mode.isFullScreen,
710
  name: sound.name,
711
- vm: state.scratchGui.vm
 
712
  };
713
  };
714
 
715
  export default connect(
716
  mapStateToProps
717
- )(SoundEditor);
 
48
  'setRef',
49
  'resampleBufferToRate',
50
  'handleModifyMenu',
51
+ 'handleFormatMenu',
52
  'getSelectionBuffer'
53
  ]);
54
  this.state = {
55
  copyBuffer: null,
56
+ chunkLevels: computeChunkedRMS(this.props.samples, this.props.waveformChunkSize),
57
  playhead: null, // null is not playing, [0 -> 1] is playing percent
58
  trimStart: null,
59
  trimEnd: null
 
70
  document.addEventListener('keydown', this.handleKeyPress);
71
  }
72
  componentWillReceiveProps(newProps) {
73
+ if (newProps.waveformChunkSize !== this.props.waveformChunkSize) {
74
+ this.setState({
75
+ chunkLevels: computeChunkedRMS(newProps.samples, newProps.waveformChunkSize),
76
+ });
77
+ }
78
  if (newProps.soundId !== this.props.soundId) { // A different sound has been selected
79
  this.redoStack = [];
80
  this.undoStack = [];
 
146
  this.audioBufferPlayer.stop();
147
  this.audioBufferPlayer = new AudioBufferPlayer(samples, sampleRate);
148
  this.setState({
149
+ chunkLevels: computeChunkedRMS(samples, this.props.waveformChunkSize),
150
  playhead: null
151
  });
152
  }
 
246
  this.handleStopPlaying();
247
  }
248
  effectFactory(name) {
249
+ return () => this.handleEffect({
250
+ preset: name,
251
+ });
252
  }
253
  copyCurrentBuffer() {
254
  // Cannot reliably use props.samples because it gets detached by Firefox
 
257
  sampleRate: this.audioBufferPlayer.buffer.sampleRate
258
  };
259
  }
260
+ handleEffect(options) {
261
  const trimStart = this.state.trimStart === null ? 0.0 : this.state.trimStart;
262
  const trimEnd = this.state.trimEnd === null ? 1.0 : this.state.trimEnd;
263
 
 
266
  return;
267
  }
268
 
269
+ const effects = new AudioEffects(this.audioBufferPlayer.buffer, options, trimStart, trimEnd);
270
  effects.process((renderedBuffer, adjustedTrimStart, adjustedTrimEnd) => {
271
  const samples = renderedBuffer.getChannelData(0);
272
  const sampleRate = renderedBuffer.sampleRate;
 
456
  this.handleUpdateTrim(null, null);
457
  }
458
  }
459
+
460
  handleModifyMenu() {
461
  // get selected audio
462
  const bufferSelection = this.getSelectionBuffer();
463
  // for preview
464
  const audio = new AudioContext();
 
 
 
 
 
465
  const gainNode = audio.createGain();
466
  gainNode.gain.value = 1;
467
  gainNode.connect(audio.destination);
 
474
  const truePitch = isNaN(Number(pitch.value)) ? 0 : Number(pitch.value);
475
  const trueVolume = isNaN(Number(volume.value)) ? 0 : Number(volume.value);
476
  this.handleEffect({
 
477
  pitch: truePitch * 10,
478
  volume: trueVolume
479
  });
 
587
  };
588
  valueVolume.oninput = valueVolume.onchange;
589
  }
590
+ handleFormatMenu() {
591
+ const sampleRates = [
592
+ 3000, 4000, 8000, 11025, 16000, 22050, 32000, 44100,
593
+ 48000, 88200, 96000, 176400, 192000, 352800, 384000,
594
+ ];
595
+ let selectedSampleRate = this.props.sampleRate;
596
+ let selectedForceRate = false;
597
+ const menu = this.displayPopup("Format Sound", 580, 300, "Apply", "Cancel", () => {
598
+ // accepted
599
+ const edits = {
600
+ sampleRate: selectedSampleRate,
601
+ };
602
+ if (selectedForceRate) {
603
+ edits.sampleRateEnforced = selectedSampleRate;
604
+ }
605
+ this.handleEffect(edits);
606
+ });
607
+
608
+ menu.textarea.style = "padding:8px;";
609
+
610
+ const labelSampleRate = document.createElement("p");
611
+ labelSampleRate.innerHTML = "Sample Rate";
612
+ labelSampleRate.style = "font-size:14px;";
613
+ menu.textarea.append(labelSampleRate);
614
+ const inputSampleRate = document.createElement("select");
615
+ inputSampleRate.style = "width:50%;"
616
+ menu.textarea.append(inputSampleRate);
617
+ for (const rate of sampleRates) {
618
+ const option = document.createElement("option");
619
+ option.value = rate;
620
+ option.innerHTML = `${rate}`;
621
+ inputSampleRate.append(option);
622
+ }
623
+ inputSampleRate.selectedIndex = sampleRates.indexOf(this.props.sampleRate);
624
+ const labelSampleRateWarning = document.createElement("p");
625
+ labelSampleRateWarning.innerHTML = "Choosing a higher sample rate than the current rate will not make the existing audio higher quality.";
626
+ labelSampleRateWarning.style = "font-size:13px;opacity:0.5;";
627
+ menu.textarea.append(labelSampleRateWarning);
628
+ inputSampleRate.onchange = () => {
629
+ selectedSampleRate = inputSampleRate.value;
630
+ };
631
+
632
+ const labelResampleAudio = document.createElement("label");
633
+ labelResampleAudio.innerHTML = "Enforce New Sample Rate";
634
+ menu.textarea.append(labelResampleAudio);
635
+ const inputResampleAudio = document.createElement("input");
636
+ inputResampleAudio.type = "checkbox";
637
+ inputResampleAudio.style = "margin-right:8px;";
638
+ labelResampleAudio.prepend(inputResampleAudio);
639
+ const labelResampleAudioWarning = document.createElement("p");
640
+ labelResampleAudioWarning.innerHTML = "This changes the properties of the entire sound, "
641
+ + "making lower sample rates use less file size. "
642
+ + "However, audio added to this sound will only be able to use the new sample rate.";
643
+ labelResampleAudioWarning.style = "font-size:13px;opacity:0.5;";
644
+ menu.textarea.append(labelResampleAudioWarning);
645
+
646
+ const warning = document.createElement("p");
647
+ warning.innerHTML = "Applying these changes will cause the entire sound to change, not just the selected area.";
648
+ warning.style = "font-size:14px;";
649
+ warning.style.display = "none";
650
+ menu.textarea.append(warning);
651
+
652
+ inputResampleAudio.onchange = () => {
653
+ selectedForceRate = inputResampleAudio.checked;
654
+ if (selectedForceRate) {
655
+ warning.style.display = "";
656
+ } else {
657
+ warning.style.display = "none";
658
+ }
659
+ };
660
+ }
661
+
662
+ // TODO: use actual scratch-gui menus instead of this
663
  displayPopup(title, width, height, okname, denyname, accepted, cancelled) {
664
  const div = document.createElement("div");
665
  document.body.append(div);
 
736
  onFaster={this.effectFactory(effectTypes.FASTER)}
737
  onLouder={this.effectFactory(effectTypes.LOUDER)}
738
  onModifySound={this.handleModifyMenu}
739
+ onFormatSound={this.handleFormatMenu}
740
  onMute={this.effectFactory(effectTypes.MUTE)}
741
  onPaste={this.handlePaste}
742
  onPlay={this.handlePlay}
 
766
  samples: PropTypes.instanceOf(Float32Array),
767
  soundId: PropTypes.string,
768
  soundIndex: PropTypes.number,
769
+ vm: PropTypes.instanceOf(VM).isRequired,
770
+ waveformChunkSize: PropTypes.number,
771
  };
772
 
773
  const mapStateToProps = (state, { soundIndex }) => {
 
786
  samples: audioBuffer.getChannelData(0),
787
  isFullScreen: state.scratchGui.mode.isFullScreen,
788
  name: sound.name,
789
+ vm: state.scratchGui.vm,
790
+ waveformChunkSize: state.scratchGui.addonUtil.soundEditorWaveformChunkSize,
791
  };
792
  };
793
 
794
  export default connect(
795
  mapStateToProps
796
+ )(SoundEditor);