Skip to content

Commit

Permalink
add audio recorder feature
Browse files Browse the repository at this point in the history
  • Loading branch information
terryzfeng committed Apr 26, 2024
1 parent c4d80bb commit b4d952c
Show file tree
Hide file tree
Showing 13 changed files with 376 additions and 3 deletions.
3 changes: 2 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
"rules": {
"indent": [
"error",
4
4,
{ "SwitchCase": 1 }
],
"linebreak-style": [
"error",
Expand Down
3 changes: 3 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,9 @@
<button id="removeButton" type="button" class="chuck-button" disabled>
<img src="img/remove.svg" alt="Remove" class="h-11" draggable="false">
</button>
<button id="recordButton" type="button" class="chuck-button" disabled>
<img id="recordImage" src="img/record-button.svg" alt="Remove" class="h-11" draggable="false">
</button>
</div>

</div>
Expand Down
32 changes: 31 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,15 @@
"vite": "^4.2.0"
},
"dependencies": {
"@xterm/addon-web-links": "^0.11.0",
"js-search": "^2.0.1",
"jszip": "^3.10.1",
"monaco-editor": "^0.37.1",
"monaco-vim": "^0.4.0",
"pako": "^2.1.0",
"webchuck": "github:ccrma/webchuck",
"xterm": "^5.3.0",
"xterm-addon-fit": "^0.8.0"
"xterm-addon-fit": "^0.8.0",
"xterm-link-provider": "^1.3.1"
}
}
5 changes: 5 additions & 0 deletions public/img/armed-button.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions public/img/record-button.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions public/img/stop-button.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
102 changes: 102 additions & 0 deletions public/js/wave-worker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
self.onmessage = function (e) {
var wavPCM = new WavePCM(e["data"]["config"]);
wavPCM.record(e["data"]["pcmArrays"]);
wavPCM.requestData();
};

var WavePCM = function (config) {
this.sampleRate = config["sampleRate"] || 48000;
this.bitDepth = config["bitDepth"] || 16;
this.recordedBuffers = [];
this.bytesPerSample = this.bitDepth / 8;
};

WavePCM.prototype.record = function (buffers) {
this.numberOfChannels = this.numberOfChannels || buffers.length;
var bufferLength = buffers[0].length;
var reducedData = new Uint8Array(
bufferLength * this.numberOfChannels * this.bytesPerSample
);

// Interleave
for (var i = 0; i < bufferLength; i++) {
for (var channel = 0; channel < this.numberOfChannels; channel++) {
var outputIndex =
(i * this.numberOfChannels + channel) * this.bytesPerSample;
var sample = buffers[channel][i];

// Check for clipping
if (sample > 1) {
sample = 1;
} else if (sample < -1) {
sample = -1;
}

// bit reduce and convert to uInt
switch (this.bytesPerSample) {
case 4:
sample = sample * 2147483648;
reducedData[outputIndex] = sample;
reducedData[outputIndex + 1] = sample >> 8;
reducedData[outputIndex + 2] = sample >> 16;
reducedData[outputIndex + 3] = sample >> 24;
break;

case 3:
sample = sample * 8388608;
reducedData[outputIndex] = sample;
reducedData[outputIndex + 1] = sample >> 8;
reducedData[outputIndex + 2] = sample >> 16;
break;

case 2:
sample = sample * 32768;
reducedData[outputIndex] = sample;
reducedData[outputIndex + 1] = sample >> 8;
break;

case 1:
reducedData[outputIndex] = (sample + 1) * 128;
break;

default:
throw "Only 8, 16, 24 and 32 bits per sample are supported";
}
}
}

this.recordedBuffers.push(reducedData);
};

WavePCM.prototype.requestData = function () {
var bufferLength = this.recordedBuffers[0].length;
var dataLength = this.recordedBuffers.length * bufferLength;
var headerLength = 44;
var wav = new Uint8Array(headerLength + dataLength);
var view = new DataView(wav.buffer);

view.setUint32(0, 1380533830, false); // RIFF identifier 'RIFF'
view.setUint32(4, 36 + dataLength, true); // file length minus RIFF identifier length and file description length
view.setUint32(8, 1463899717, false); // RIFF type 'WAVE'
view.setUint32(12, 1718449184, false); // format chunk identifier 'fmt '
view.setUint32(16, 16, true); // format chunk length
view.setUint16(20, 1, true); // sample format (raw)
view.setUint16(22, this.numberOfChannels, true); // channel count
view.setUint32(24, this.sampleRate, true); // sample rate
view.setUint32(
28,
this.sampleRate * this.bytesPerSample * this.numberOfChannels,
true
); // byte rate (sample rate * block align)
view.setUint16(32, this.bytesPerSample * this.numberOfChannels, true); // block align (channel count * bytes per sample)
view.setUint16(34, this.bitDepth, true); // bits per sample
view.setUint32(36, 1684108385, false); // data chunk identifier 'data'
view.setUint32(40, dataLength, true); // data chunk length

for (var i = 0; i < this.recordedBuffers.length; i++) {
wav.set(this.recordedBuffers[i], i * bufferLength + headerLength);
}

self.postMessage(wav, [wav.buffer]);
self.close();
};
9 changes: 9 additions & 0 deletions src/components/chuckBar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import { theChuck, startChuck, connectMic } from "@/host";
import Editor from "@/components/monaco/editor";
import VmMonitor from "@/components/vmMonitor";
import Recorder from "@/components/recorder";

// detect operating system
const isWindows = navigator.userAgent.includes("Windows");
Expand All @@ -28,6 +29,7 @@ export default class ChuckBar {
public static playButton: HTMLButtonElement;
public static replaceButton: HTMLButtonElement;
public static removeButton: HTMLButtonElement;
public static recordButton: HTMLButtonElement;

public static running: boolean = false;

Expand All @@ -43,13 +45,16 @@ export default class ChuckBar {
document.querySelector<HTMLButtonElement>("#replaceButton")!;
ChuckBar.removeButton =
document.querySelector<HTMLButtonElement>("#removeButton")!;
ChuckBar.recordButton =
document.querySelector<HTMLButtonElement>("#recordButton")!;

// Add tooltips
ChuckBar.webchuckButton.title = `Start ChucK VM [${metaKey} + .]`;
ChuckBar.micButton.title = `Connect Microphone`;
ChuckBar.playButton.title = `Run [${metaKey} + Enter]`;
ChuckBar.replaceButton.title = `Replace [${metaKey} + \\]`;
ChuckBar.removeButton.title = `Remove [${metaKey} + ⌫]`;
ChuckBar.recordButton.title = `Record`;

// Add button event listeners
ChuckBar.webchuckButton.addEventListener("click", async () => {
Expand All @@ -68,6 +73,9 @@ export default class ChuckBar {
ChuckBar.removeButton.addEventListener("click", async () => {
ChuckBar.removeCode();
});

// Configure the recorder button
new Recorder(ChuckBar.recordButton);
}

static runEditorCode() {
Expand Down Expand Up @@ -112,6 +120,7 @@ export default class ChuckBar {
ChuckBar.playButton.disabled = false;
ChuckBar.replaceButton.disabled = false;
ChuckBar.removeButton.disabled = false;
ChuckBar.recordButton.disabled = false;

ChuckBar.running = true;
}
Expand Down
11 changes: 11 additions & 0 deletions src/components/console.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,14 @@

import { Terminal } from "xterm";
import { FitAddon } from "xterm-addon-fit";
// import { WebLinksAddon } from "@xterm/addon-web-links";
import "@styles/xterm.css";

import { theChuck } from "@/host";
import { LinkProvider } from "xterm-link-provider";

// Define a custom regular expression that matches blob URIs
const blobRegex = /(blob:https?:\/\/\S+)/;

export default class Console {
public static terminal: Terminal;
Expand Down Expand Up @@ -44,6 +49,12 @@ export default class Console {
Console.terminal.open(Console.terminalElement);
Console.fit();

// Blob Links
Console.terminal.registerLinkProvider(new LinkProvider(Console.terminal, blobRegex, (e, uri) => {

Check failure on line 53 in src/components/console.ts

View workflow job for this annotation

GitHub Actions / build

'e' is declared but its value is never read.
window.open(uri, "_blank");
}));
// Console.terminal.loadAddon(new WebLinksAddon());

// Resize listener
window.addEventListener("resize", () => {
Console.resizeConsole();
Expand Down
Loading

0 comments on commit b4d952c

Please sign in to comment.