soiz1 commited on
Commit
97dae4c
·
verified ·
1 Parent(s): e2a1935

Update src/containers/sound-editor.jsx

Browse files
Files changed (1) hide show
  1. src/containers/sound-editor.jsx +147 -136
src/containers/sound-editor.jsx CHANGED
@@ -458,6 +458,37 @@ class SoundEditor extends React.Component {
458
  }
459
 
460
  handleModifyMenu() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
461
  // get selected audio
462
  const bufferSelection = this.getSelectionBuffer();
463
  // for preview
@@ -465,14 +496,22 @@ class SoundEditor extends React.Component {
465
  const gainNode = audio.createGain();
466
  gainNode.gain.value = 1;
467
  gainNode.connect(audio.destination);
 
468
  // create inputs before menu so we can get the value easier
469
- const pitch = document.createElement("input");
470
- const volume = document.createElement("input");
 
 
 
 
 
 
471
  const menu = this.displayPopup("Modify Sound", 200, 280, "Apply", "Cancel", () => {
472
  // accepted
473
  audio.close();
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
@@ -482,181 +521,153 @@ class SoundEditor extends React.Component {
482
  audio.close();
483
  // we dont need to do anything else
484
  });
485
- menu.textarea.style = "position: relative;display: flex;justify-content: flex-end;flex-direction: row;height: calc(100% - (3.125em + 2.125em + 16px));align-items: center;";
486
- // set pitch stuff
487
- pitch.type = "range";
488
- pitch.classList.add(confirmStyles.verticalSlider);
489
- pitch.style = "position: absolute;left: -40px;top: 80px;";
490
- pitch.value = 0;
491
- pitch.min = -360;
492
- pitch.max = 360;
493
- pitch.step = 1;
494
- // set volume stuff
495
- volume.type = "range";
496
- volume.classList.add(confirmStyles.verticalSlider);
497
- volume.style = "position: absolute;left: 0px;top: 80px;";
498
- volume.value = 1;
499
- volume.min = 0;
500
- volume.max = 2;
501
- volume.step = 0.01;
502
- menu.textarea.append(pitch);
503
- menu.textarea.append(volume);
504
- const labelPitch = document.createElement("p");
505
- const labelVolume = document.createElement("p");
506
- labelPitch.style = "text-align: center;width: 35px;font-size: 12px;position: absolute;left: 7.5px;top: 3.5px;";
507
- labelVolume.style = "text-align: center;width: 35px;font-size: 12px;position: absolute;left: 47.5px;top: 3.5px;";
508
- labelPitch.innerHTML = "Pitch";
509
- labelVolume.innerHTML = "Volume";
510
- menu.textarea.append(labelPitch);
511
- menu.textarea.append(labelVolume);
512
- const valuePitch = document.createElement("input");
513
- const valueVolume = document.createElement("input");
514
- valuePitch.style = "text-align: center;width: 35px;font-size: 12px;position: absolute;left: 4px;top: 152.5px;";
515
- valueVolume.style = "text-align: center;width: 35px;font-size: 12px;position: absolute;left: 44px;top: 152.5px;";
516
- valuePitch.value = 0;
517
- valueVolume.value = 100;
518
- valuePitch.min = -360;
519
- valuePitch.max = 360;
520
- valuePitch.step = 1;
521
- valueVolume.min = 0;
522
- valueVolume.max = 200;
523
- valueVolume.step = 1;
524
- valuePitch.type = "number";
525
- valueVolume.type = "number";
526
- menu.textarea.append(valuePitch);
527
- menu.textarea.append(valueVolume);
528
  const previewButton = document.createElement("button");
529
- previewButton.style = "font-weight: bold;color: white;border-radius: 1000px;width: 46px;margin-right: 28px;height: 46px;border-style: none;background: #00c3ff;";
530
- previewButton.innerHTML = "Play";
531
  menu.textarea.append(previewButton);
532
- // playing audio
 
533
  // create an audio buffer using the selection
534
  const properBuffer = audio.createBuffer(1, bufferSelection.samples.length, bufferSelection.sampleRate);
535
  properBuffer.getChannelData(0).set(bufferSelection.samples);
536
- // button functionality
537
- let bufferSource;
538
- let audioPlaying = false;
539
  function play() {
540
  bufferSource = audio.createBufferSource();
541
  bufferSource.connect(gainNode);
542
  bufferSource.buffer = properBuffer;
543
  bufferSource.start(0);
544
- bufferSource.detune.value = pitch.value * 10;
545
- previewButton.innerHTML = "Stop";
546
  audioPlaying = true;
547
  bufferSource.onended = () => {
548
- previewButton.innerHTML = "Play";
549
  audioPlaying = false;
550
  }
551
  }
552
  function stop() {
553
  bufferSource.stop();
554
- previewButton.innerHTML = "Play";
555
  audioPlaying = false;
556
  }
557
  previewButton.onclick = () => {
558
- if (audioPlaying) {
559
- return stop();
560
- }
561
- play();
562
- }
563
- // updates
564
- pitch.onchange = (updateValue) => {
565
- if (updateValue !== false) {
566
- valuePitch.value = Number(pitch.value);
567
- };
568
- if (!bufferSource) return;
569
- bufferSource.detune.value = pitch.value * 10;
570
  }
571
- pitch.oninput = pitch.onchange;
572
- volume.onchange = (updateValue) => {
573
- gainNode.gain.value = volume.value;
574
- if (updateValue === false) return;
575
- valueVolume.value = Number(volume.value) * 100;
 
 
576
  }
577
- volume.oninput = volume.onchange;
578
- // value changes
579
- valuePitch.onchange = () => {
580
- pitch.value = valuePitch.value;
581
- pitch.onchange(false);
582
  };
583
- valuePitch.oninput = valuePitch.onchange;
584
- valueVolume.onchange = () => {
585
- volume.value = valueVolume.value / 100;
586
- volume.onchange(false);
 
 
 
 
 
 
 
 
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
@@ -793,4 +804,4 @@ const mapStateToProps = (state, { soundIndex }) => {
793
 
794
  export default connect(
795
  mapStateToProps
796
- )(SoundEditor);
 
458
  }
459
 
460
  handleModifyMenu() {
461
+ const playURI = `data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OSIgaGVpZ2h0PSI1MiIgdmlld0JveD0iLTUgMCA0OSA0OCI+PHBhdGggZmlsbD0iI0ZGRiIgZD0iTTM1LjUwOCAxOS4zNzRjNC4yNTkgMi41NTYgNC4yNTIgNi43MDIgMCA5LjI1NEwxMi43MTIgNDIuMzA1Yy00LjI1OCAyLjU1NS03LjcxLjU5Ny03LjcxLTQuMzhWMTAuMDc3YzAtNC45NzMgMy40NTgtNi45MyA3LjcxLTQuMzh6Ii8+PC9zdmc+`;
462
+ const stopURI = `data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1MiIgaGVpZ2h0PSI1MiIgdmlld0JveD0iMCAwIDUyIDUyIj48cmVjdCBmaWxsPSIjRkZGIiB3aWR0aD0iMzUiIGhlaWdodD0iMzUiIHJ4PSI0IiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg4LjUgOC41KSIvPjwvc3ZnPg==`;
463
+
464
+ const genSliderDiv = (title, params, scalar) => {
465
+ const div = document.createElement("div");
466
+ div.style = "margin: 0 10px 0 5px;width: 40px;display: flex;flex-direction: column;align-items: center;";
467
+
468
+ const label = document.createElement("div");
469
+ label.style = "text-align: center;width: 40px;font-size: 12px;font-weight: bold;";
470
+ label.textContent = title;
471
+
472
+ const slider = document.createElement("input");
473
+ slider.style = "transform: rotate(270deg);height: 40px;width: 120px;margin: 45px 10px;";
474
+ slider.type = "range";
475
+ slider.min = params.min;
476
+ slider.max = params.max;
477
+ slider.step = params.step;
478
+ slider.value = params.value;
479
+
480
+ const input = document.createElement("input");
481
+ input.style = "text-align: center;width: 40px;border: solid 1px gray;border-radius: 10px;";
482
+ input.type = "number";
483
+ input.min = params.min * scalar;
484
+ input.max = params.max * scalar;
485
+ input.step = params.step * scalar;
486
+ input.value = params.value * scalar;
487
+
488
+ div.append(label, slider, input);
489
+ return div;
490
+ };
491
+
492
  // get selected audio
493
  const bufferSelection = this.getSelectionBuffer();
494
  // for preview
 
496
  const gainNode = audio.createGain();
497
  gainNode.gain.value = 1;
498
  gainNode.connect(audio.destination);
499
+
500
  // create inputs before menu so we can get the value easier
501
+ const pitchDiv = genSliderDiv(
502
+ "Pitch", { min: -360, max: 360, step: 1, value: 0 }, 0
503
+ );
504
+ const volumeDiv = genSliderDiv(
505
+ "Volume", { min: 0, max: 2, step: 0.01, value: 1 }, 100
506
+ );
507
+ const pitchParts = pitchDiv.children;
508
+ const volumeParts = volumeDiv.children;
509
  const menu = this.displayPopup("Modify Sound", 200, 280, "Apply", "Cancel", () => {
510
  // accepted
511
  audio.close();
512
+ const pitch = pitchParts[1].value, volume = volumeParts[1].value;
513
+ const truePitch = isNaN(Number(pitch)) ? 0 : Number(pitch);
514
+ const trueVolume = isNaN(Number(volume)) ? 0 : Number(volume);
515
  this.handleEffect({
516
  pitch: truePitch * 10,
517
  volume: trueVolume
 
521
  audio.close();
522
  // we dont need to do anything else
523
  });
524
+
525
+ menu.textarea.style = "margin: 0 10px 0 10px;position: relative;display: flex;justify-content: flex-end;flex-direction: row;height: calc(100% - (3.125em + 2.125em + 16px));align-items: center;";
526
+ menu.textarea.append(pitchDiv, volumeDiv);
527
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
528
  const previewButton = document.createElement("button");
529
+ previewButton.style = "border-radius: 1000px;padding: 5px;width: 45px;height: 45px;margin-right: 10px;border-style: none;background: #00c3ff;";
530
+ previewButton.innerHTML = `<img draggable="false" style="max-width: 100%;max-height: 100%" src="${playURI}">`;
531
  menu.textarea.append(previewButton);
532
+
533
+ // preview functionality
534
  // create an audio buffer using the selection
535
  const properBuffer = audio.createBuffer(1, bufferSelection.samples.length, bufferSelection.sampleRate);
536
  properBuffer.getChannelData(0).set(bufferSelection.samples);
537
+
538
+ let bufferSource, audioPlaying = false;
 
539
  function play() {
540
  bufferSource = audio.createBufferSource();
541
  bufferSource.connect(gainNode);
542
  bufferSource.buffer = properBuffer;
543
  bufferSource.start(0);
544
+ bufferSource.detune.value = pitchParts[1].value * 10;
545
+ previewButton.innerHTML = `<img draggable="false" style="max-width: 100%;max-height: 100%" src="${stopURI}">`;
546
  audioPlaying = true;
547
  bufferSource.onended = () => {
548
+ previewButton.firstChild.src = playURI;
549
  audioPlaying = false;
550
  }
551
  }
552
  function stop() {
553
  bufferSource.stop();
554
+ previewButton.firstChild.src = stopURI;
555
  audioPlaying = false;
556
  }
557
  previewButton.onclick = () => {
558
+ if (audioPlaying) stop();
559
+ else play();
 
 
 
 
 
 
 
 
 
 
560
  }
561
+
562
+ // slider/number updates
563
+ const pSlider = pitchParts[1];
564
+ const pNumber = pitchParts[2];
565
+ pSlider.onchange = (updateValue) => {
566
+ if (updateValue !== false) pNumber.value = Number(pSlider.value);
567
+ if (bufferSource) bufferSource.detune.value = pSlider.value * 10;
568
  }
569
+ pSlider.oninput = pSlider.onchange;
570
+ pNumber.onchange = () => {
571
+ pSlider.value = pNumber.value;
572
+ pSlider.onchange(false);
 
573
  };
574
+ pNumber.oninput = pNumber.onchange;
575
+
576
+ const vSlider = volumeParts[1];
577
+ const vNumber = volumeParts[2];
578
+ vSlider.onchange = (updateValue) => {
579
+ gainNode.gain.value = vSlider.value;
580
+ if (updateValue !== false) vNumber.value = Number(vSlider.value) * 100;
581
+ }
582
+ vSlider.oninput = vSlider.onchange;
583
+ vNumber.onchange = () => {
584
+ vSlider.value = vNumber.value / 100;
585
+ vSlider.onchange(false);
586
  };
587
+ vNumber.oninput = vNumber.onchange;
588
  }
589
+
590
  handleFormatMenu() {
591
+ const genTitle = (text) => {
592
+ const label = document.createElement("div");
593
+ label.style = "font-weight: 500;font-size: 14px;margin-bottom: 5px;";
594
+ const inner = document.createElement("span");
595
+ inner.textContent = text;
596
+ label.appendChild(inner);
597
+ return label;
598
+ };
599
+ const genCheckableLabel = (text, id, isChecked) => {
600
+ const div = document.createElement("div");
601
+ div.classList.add("check-outer");
602
+ div.id = id;
603
+ div.style = "margin-top: 3px;";
604
+ const check = document.createElement("input");
605
+ check.style = "margin-right: 8px;";
606
+ check.type = "radio";
607
+ check.checked = isChecked ?? false;
608
+ const label = document.createElement("span");
609
+ label.textContent = text;
610
+ div.append(check, label);
611
+ return div;
612
+ };
613
+
614
  const sampleRates = [
615
  3000, 4000, 8000, 11025, 16000, 22050, 32000, 44100,
616
  48000, 88200, 96000, 176400, 192000, 352800, 384000,
617
  ];
618
  let selectedSampleRate = this.props.sampleRate;
619
  let selectedForceRate = false;
620
+ const menu = this.displayPopup("Format Sound", 350, 300, "Apply", "Cancel", () => {
621
  // accepted
622
+ const edits = { sampleRate: selectedSampleRate };
623
+ if (selectedForceRate) edits.sampleRateEnforced = selectedSampleRate;
 
 
 
 
624
  this.handleEffect(edits);
625
  });
626
 
627
+ menu.textarea.style = "padding: 10px 20px;";
628
+ const rateTitle = genTitle("New Sample Rate:");
629
 
630
+ const rateSelector = document.createElement("select");
631
+ rateSelector.style = "border-radius: 5px;text-align: center;margin-left: 10px;width: 50%;";
 
 
 
 
 
632
  for (const rate of sampleRates) {
633
  const option = document.createElement("option");
634
  option.value = rate;
635
+ option.textContent = rate;
636
+ rateSelector.append(option);
637
  }
638
+ rateSelector.selectedIndex = sampleRates.indexOf(this.props.sampleRate);
639
+ rateSelector.onchange = () => {
640
+ selectedSampleRate = rateSelector.value;
 
 
 
 
641
  };
642
+ rateTitle.appendChild(rateSelector);
643
 
644
+ const warningDiv = document.createElement("div");
645
+ warningDiv.style.marginBottom = "15px";
646
+ const warning = document.createElement("i");
647
+ warning.textContent = "Choosing a higher sample rate than the current rate will not make the existing audio higher quality";
648
+ warning.style = "font-size:13px;opacity:0.5;";
649
+ warningDiv.appendChild(warning);
650
+
651
+ const warningDiv2 = warning.cloneNode(true);
652
+ warningDiv2.textContent = "If 'whole sound' is selected, all added audio will use the new sample rate";
653
+
654
+ const applicatorDiv = document.createElement("div");
655
+ applicatorDiv.append(
656
+ genCheckableLabel("this selection", "0", true),
657
+ genCheckableLabel("whole sound", "1", false)
658
+ );
659
+ applicatorDiv.addEventListener("click", (e) => {
660
+ const div = e.target.closest(`div[class="check-outer"]`);
661
+ if (!div) return;
662
+
663
+ for (const checkable of Array.from(div.parentNode.children)) {
664
+ checkable.firstChild.checked = false;
 
 
 
 
 
665
  }
666
+ div.firstChild.checked = true;
667
+ selectedForceRate = div.id == "1";
668
+ e.stopPropagation();
669
+ });
670
+ menu.textarea.append(rateTitle, warningDiv, genTitle("Apply to:"), applicatorDiv, warningDiv2);
671
  }
672
 
673
  // TODO: use actual scratch-gui menus instead of this
 
804
 
805
  export default connect(
806
  mapStateToProps
807
+ )(SoundEditor);