diff --git a/css/activities.css b/css/activities.css index d32cc145dc..da60cd4745 100644 --- a/css/activities.css +++ b/css/activities.css @@ -836,6 +836,11 @@ table { cursor: not-allowed; } +#playbackBtn.disabled { + opacity: 0.5; + cursor: not-allowed; +} + #right-arrow, #left-arrow { position: absolute; diff --git a/header-icons/mic.svg b/header-icons/mic.svg new file mode 100644 index 0000000000..4970f47405 --- /dev/null +++ b/header-icons/mic.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/header-icons/playback.svg b/header-icons/playback.svg new file mode 100644 index 0000000000..992cf59bb6 --- /dev/null +++ b/header-icons/playback.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/header-icons/record.svg b/header-icons/record.svg new file mode 100644 index 0000000000..0b88904f03 --- /dev/null +++ b/header-icons/record.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/js/widgets/sampler.js b/js/widgets/sampler.js index e4778ca058..864f8dd27d 100644 --- a/js/widgets/sampler.js +++ b/js/widgets/sampler.js @@ -217,6 +217,35 @@ function SampleWidget() { this.octaveCenter = parseInt(o); }; + + /** + * Gets the length of the sample and displays a warning if it exceeds 1MB. + * @returns {void} + */ + this.getSampleLength = function () { + if (this.sampleData.length > 1333333) { + this.activity.errorMsg(_("Warning: Sample is bigger than 1MB."), this.timbreBlock); + } + }; + + + /** + * Displays a message indicating that recording has started. + * @returns {void} + */ + function displayRecordingStartMessage() { + this.activity.textMsg(_("Recording started...")); + } + + /** + * Displays a message indicating that recording has stopped. + * @returns {void} + */ + function displayRecordingStopMessage() { + this.activity.textMsg(_("Recording complete...")); + } + + /** * Displays an error message when the uploaded sample is not a .wav file. * @returns {void} @@ -236,16 +265,18 @@ function SampleWidget() { that._addSample(); var newStack = [ + [0,"settimbre",100,100,[null,1,null,5]], [ - 0, + 1, ["customsample", { value: [that.sampleName, that.sampleData, "do", 4] }], 100, 100, - [null, 1, 2, 3] + [0, 2, 3, 4] ], - [1, ["audiofile", { value: [that.sampleName, that.sampleData] }], 0, 0, [0]], - [2, ["solfege", { value: that.samplePitch }], 0, 0, [0]], - [3, ["number", { value: that.sampleOctave }], 0, 0, [0]] + [2, ["audiofile", { value: [that.sampleName, that.sampleData] }], 0, 0, [1]], + [3, ["solfege", { value: that.samplePitch }], 0, 0, [1]], + [4, ["number", { value: that.sampleOctave }], 0, 0, [1]], + [5,"hidden",0,0,[0,null]] ]; that.activity.blocks.loadNewBlocks(newStack); @@ -336,8 +367,8 @@ function SampleWidget() { } else { if (!(this.sampleName == "")) { this.resume(); - this._playReferencePitch(); } + this._playReferencePitch(); } }; @@ -411,6 +442,98 @@ function SampleWidget() { } }; + this._recordBtn= widgetWindow.addButton( + "mic.svg", + ICONSIZE, + _("Toggle Mic"), + "" + ); + this._recordBtn.onclick = function() { + ToggleMic(this); + }.bind(this._recordBtn); + + this._playbackBtn= widgetWindow.addButton( + "playback.svg", + ICONSIZE, + _("Playback"), + ""); + this._playbackBtn.id="playbackBtn" + this._playbackBtn.classList.add("disabled"); + + + const togglePlaybackButtonState = () => { + if (!audioURL) { + this._playbackBtn.classList.add("disabled"); + } else { + this._playbackBtn.classList.remove("disabled"); + } + }; + + this._playbackBtn.onclick = () => { + playAudio(); + }; + + let can_record = false; + let is_recording = false; + let recorder = null; + let chunks = []; + let audioURL = null; + + async function setUpAudio() { + if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { + try { + const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); + recorder = new MediaRecorder(stream); + recorder.ondataavailable = e => { + chunks.push(e.data); + }; + recorder.onstop = async e => { + let blob = new Blob(chunks, { type: 'audio/webm' }); + chunks = []; + audioURL = URL.createObjectURL(blob); + displayRecordingStopMessage.call(that); + togglePlaybackButtonState(); + + const module = await import("https://cdn.jsdelivr.net/npm/webm-to-wav-converter@1.1.0/+esm"); + const getWaveBlob = module.getWaveBlob; + const wavBlob = await getWaveBlob(blob); + const wavAudioURL = URL.createObjectURL(wavBlob); + that.sampleData = wavAudioURL; + that.sampleName = `Recorded Audio ${audioURL}`; + that._addSample(); + }; + can_record = true; + } catch (err) { + console.log("The following error occurred: " + err); + } + } + } + function ToggleMic(buttonElement) { + if (!can_record) return; + + is_recording = !is_recording; + if (is_recording) { + recorder.start(); + buttonElement.getElementsByTagName('img')[0].src = "header-icons/record.svg"; + displayRecordingStartMessage.call(that); + } else { + recorder.stop(); + buttonElement.getElementsByTagName('img')[0].src = "header-icons/mic.svg"; + } + } + + function playAudio() { + if (audioURL) { + const audio = new Audio(audioURL); + audio.play(); + console.log("Playing audio."); + } else { + console.error("No recorded audio available."); + } + } + + setUpAudio(); + widgetWindow.sendToCenter(); this.widgetWindow = widgetWindow;