Created
October 4, 2023 03:29
-
-
Save thebwt/8142c510c2d2ae31f0c8e6bbfb45016a to your computer and use it in GitHub Desktop.
foundry macro with stop detection
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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