Spaces:
Paused
Paused
Update src/containers/sound-editor.jsx
Browse files- 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
|
470 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
471 |
const menu = this.displayPopup("Modify Sound", 200, 280, "Apply", "Cancel", () => {
|
472 |
// accepted
|
473 |
audio.close();
|
474 |
-
const
|
475 |
-
const
|
|
|
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 |
-
|
486 |
-
|
487 |
-
|
488 |
-
|
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 = "
|
530 |
-
previewButton.innerHTML = "
|
531 |
menu.textarea.append(previewButton);
|
532 |
-
|
|
|
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 |
-
|
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 =
|
545 |
-
previewButton.innerHTML = "
|
546 |
audioPlaying = true;
|
547 |
bufferSource.onended = () => {
|
548 |
-
previewButton.
|
549 |
audioPlaying = false;
|
550 |
}
|
551 |
}
|
552 |
function stop() {
|
553 |
bufferSource.stop();
|
554 |
-
previewButton.
|
555 |
audioPlaying = false;
|
556 |
}
|
557 |
previewButton.onclick = () => {
|
558 |
-
if (audioPlaying)
|
559 |
-
|
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 |
-
|
572 |
-
|
573 |
-
|
574 |
-
|
575 |
-
|
|
|
|
|
576 |
}
|
577 |
-
|
578 |
-
|
579 |
-
|
580 |
-
|
581 |
-
pitch.onchange(false);
|
582 |
};
|
583 |
-
|
584 |
-
|
585 |
-
|
586 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
587 |
};
|
588 |
-
|
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",
|
598 |
// accepted
|
599 |
-
const edits = {
|
600 |
-
|
601 |
-
};
|
602 |
-
if (selectedForceRate) {
|
603 |
-
edits.sampleRateEnforced = selectedSampleRate;
|
604 |
-
}
|
605 |
this.handleEffect(edits);
|
606 |
});
|
607 |
|
608 |
-
menu.textarea.style = "padding:
|
|
|
609 |
|
610 |
-
const
|
611 |
-
|
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.
|
621 |
-
|
622 |
}
|
623 |
-
|
624 |
-
|
625 |
-
|
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
|
633 |
-
|
634 |
-
|
635 |
-
|
636 |
-
|
637 |
-
|
638 |
-
|
639 |
-
const
|
640 |
-
|
641 |
-
|
642 |
-
|
643 |
-
|
644 |
-
|
645 |
-
|
646 |
-
|
647 |
-
|
648 |
-
|
649 |
-
|
650 |
-
|
651 |
-
|
652 |
-
|
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);
|