Skip to content

Instantly share code, notes, and snippets.

@thebwt
Created October 4, 2023 03:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save thebwt/8142c510c2d2ae31f0c8e6bbfb45016a to your computer and use it in GitHub Desktop.
Save thebwt/8142c510c2d2ae31f0c8e6bbfb45016a to your computer and use it in GitHub Desktop.
foundry macro with stop detection
// Function to record audio and return as Blob
async function recordAudio() {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
const mediaRecorder = new MediaRecorder(stream);
const audioChunks = [];
let audioContext = new AudioContext();
let analyser = audioContext.createAnalyser();
let source = audioContext.createMediaStreamSource(stream);
source.connect(analyser);
let data = new Uint8Array(analyser.frequencyBinCount); // for analyzing sound
let silenceStart = performance.now();
let triggered = false; // flag to trigger recording start
analyser.minDecibels = -70; // adjust this to your needs
// Function to check silence
const checkSilence = () => {
analyser.getByteFrequencyData(data);
if (data.some(v => v)) {
silenceStart = performance.now();
triggered = true;
} else {
if (triggered && performance.now() - silenceStart > 1000) {
mediaRecorder.stop();
}
}
};
// start the checking loop
let interval = setInterval(checkSilence, 100);
mediaRecorder.start();
return new Promise(async (resolve) => {
mediaRecorder.addEventListener("dataavailable", (event) => {
audioChunks.push(event.data);
});
mediaRecorder.addEventListener("stop", async () => {
clearInterval(interval); // stop the checking loop
audioContext.close(); // close audio context
const audioBlob = new Blob(audioChunks, { type: "audio/wav" });
// Auto send the recorded audio to the server for transcription
const transcribedText = await sendAudioToServer(audioBlob);
resolve(transcribedText);
});
});
}
// Function to send audio data to FastAPI endpoint for transcription
async function sendAudioToServer(audioBlob) {
const formData = new FormData();
formData.append("audio", audioBlob);
const response = await fetch("http://localhost:8083/transcribe", {
method: "POST",
body: formData,
});
const data = await response.json();
return data.text;
}
async function fetchTranscribedText() {
// Assume you've captured audio and it's stored in a variable called `audioBlob`
const formData = new FormData();
formData.append("audio", audioBlob, "audio.mp3");
const response = await fetch("http://localhost:8083/transcribe", {
method: "POST",
body: formData,
});
if (!response.ok) {
console.error("Failed to transcribe audio");
return null;
}
const jsonData = await response.json();
return jsonData.text; // Assuming the JSON has a "text" property
}
// Check if a token is selected
let selectedActor = game.actors.get(canvas.tokens.controlled[0]?.actor?.id);
if (!selectedActor) {
ui.notifications.warn("You must select a token first.");
return;
}
// Fetch the voice name from the custom flag, or prompt for a new one if not set
let voiceName = selectedActor.getFlag("world", "voiceName");
if (!voiceName) {
let dialogContent = `
<form>
<div class="form-group">
<label for="voiceName">Set Voice Name:</label>
<input id="voiceName" name="voiceName" type="text"/>
</div>
</form>
`;
new Dialog({
title: "Set Voice for Actor",
content: dialogContent,
buttons: {
set: {
label: "Set Voice",
callback: async (html) => {
voiceName = html.find("#voiceName")[0].value;
await selectedActor.setFlag("world", "voiceName", voiceName);
ui.notifications.info(
`Voice for ${selectedActor.name} set to ${voiceName}`
);
},
},
},
default: "set",
close: () => {
if (voiceName) {
// If voiceName is set, proceed with the Sound Text Input
showSoundTextInput();
}
},
}).render(true);
} else {
showSoundTextInput();
}
async function showSoundTextInput() {
let dialogContent = `
<form>
<div class="form-group">
<label for="soundText">Enter the sound text:</label>
<input id="soundText" name="soundText" type="text"/>
</div>
</form>
`;
// Create a dialog for Sound Text Input
new Dialog({
title: "Sound Text Input",
content: dialogContent,
buttons: {
ok: {
label: "Submit",
callback: (html) => {
// Get the sound text from the dialog
const text = html.find("#soundText")[0].value;
// Use the voice name from the custom flag or the newly set voice
ui.chat.processMessage(`/playsound [${voiceName}] ${text}`);
// Make the actor "speak" in the chat
ChatMessage.create({
speaker: {
alias: selectedActor.name,
actor: selectedActor.id,
},
content: text,
});
},
},
cancel: {
label: "Cancel",
callback: () => console.log("Sound text input cancelled"),
},
},
default: "ok",
render: async (html) => {
// Auto start the recording and transcription when dialog is rendered
const transcribedText = await recordAudio();
if (transcribedText) {
html.find("#soundText").val(transcribedText).focus();
const text = html.find("#soundText")[0].value;
ui.chat.processMessage(`/playsound [${voiceName}] ${text}`);
// Make the actor "speak" in the chat
ChatMessage.create({
speaker: {
alias: selectedActor.name,
actor: selectedActor.id,
},
content: text,
});
// Close the dialog
html.closest('.dialog').remove();
}
},
}).render(true);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment