From 7ef595f1f1b6126213e188924211353cff8d5988 Mon Sep 17 00:00:00 2001 From: CreatorADOfficial Date: Mon, 6 Apr 2026 22:30:16 +0000 Subject: [PATCH] Automated Extension submission for issue #2077 --- extensions/community/AdvancedMicrophone.json | 534 +++++++++++++++++++ 1 file changed, 534 insertions(+) create mode 100644 extensions/community/AdvancedMicrophone.json diff --git a/extensions/community/AdvancedMicrophone.json b/extensions/community/AdvancedMicrophone.json new file mode 100644 index 000000000..14d86e77f --- /dev/null +++ b/extensions/community/AdvancedMicrophone.json @@ -0,0 +1,534 @@ +{ + "author": "", + "category": "Advanced", + "dimension": "2D", + "extensionNamespace": "", + "fullName": "Advanced Microphone", + "gdevelopVersion": "", + "helpPath": "", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0ibWRpLW1pY3JvcGhvbmUtc2V0dGluZ3MiIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMTksMTBIMTcuM0MxNy4zLDEzIDE0Ljc2LDE1LjEgMTIsMTUuMUM5LjI0LDE1LjEgNi43LDEzIDYuNywxMEg1QzUsMTMuNDEgNy43MiwxNi4yMyAxMSwxNi43MlYyMEgxM1YxNi43MkMxNi4yOCwxNi4yMyAxOSwxMy40MSAxOSwxME0xNSwyNEgxN1YyMkgxNU0xMSwyNEgxM1YyMkgxMU0xMiwxM0EzLDMgMCAwLDAgMTUsMTBWNEEzLDMgMCAwLDAgMTIsMUEzLDMgMCAwLDAgOSw0VjEwQTMsMyAwIDAsMCAxMiwxM003LDI0SDlWMjJIN1YyNFoiIC8+PC9zdmc+", + "name": "AdvancedMicrophone", + "previewIconUrl": "https://asset-resources.gdevelop.io/public-resources/Icons/b0dadbd0c48f94bbdfa94ebfb8c82ff4fe8c75edfec6313109d693db62cac2b2_microphone-settings.svg", + "shortDescription": "Adds Recording Audio, Can be Downloaded Or Base64 Converted! You can also play it back.", + "version": "1.0.0", + "description": [ + "Action;", + "- Request Microphone", + "- Start Recording", + "- Stop Recording", + "- Download Recording (Webm Format)", + "- Convert Recoding To Base64", + "- Play Base64 Audio", + "Conditions;", + "" + ], + "tags": [ + "Microphone", + "Base64", + "Record" + ], + "authorIds": [ + "LmDPcZ5ey4WWF4VdoEQcmPVTmFu2" + ], + "dependencies": [], + "globalVariables": [], + "sceneVariables": [], + "eventsFunctions": [ + { + "description": "Requests Microphone.", + "fullName": "Request Microphone", + "functionType": "Action", + "name": "RequestMC", + "sentence": "Request Microphone", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Once" + }, + "parameters": [] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "navigator.mediaDevices.getUserMedia({ audio: true })", + ".then(stream => {", + " window.gdMicSystem = window.gdMicSystem || {};", + " window.gdMicSystem.mediaStream = stream;", + " window.gdMicSystem.recordedChunks = [];", + " console.log(\"Ready\");", + " })", + " .catch(err => {", + " console.error(\"Failed\", err);", + " });" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ] + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "description": "Starts Recording Audio.", + "fullName": "Start Recording", + "functionType": "Action", + "name": "StartREC", + "sentence": "Start Recording", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Once" + }, + "parameters": [] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "if (window.gdMicSystem && window.gdMicSystem.mediaStream) {", + " window.gdMicSystem.recordedChunks = [];", + " window.gdMicSystem.mediaRecorder = new MediaRecorder(window.gdMicSystem.mediaStream);", + " window.gdMicSystem.mediaRecorder.ondataavailable = e => {", + " if (e.data.size > 0) window.gdMicSystem.recordedChunks.push(e.data);", + " };", + " window.gdMicSystem.mediaRecorder.start();", + " }" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ] + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "description": "Stops recording and saves the audio.", + "fullName": "Stop Recording", + "functionType": "Action", + "name": "StopREC", + "sentence": "Stop Recording", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Once" + }, + "parameters": [] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "if (window.gdMicSystem) {", + " // Stop MediaRecorder", + " if (window.gdMicSystem.mediaRecorder && window.gdMicSystem.mediaRecorder.state !== \"inactive\") {", + " window.gdMicSystem.mediaRecorder.stop();", + " }", + "", + " // Stop all microphone tracks", + " if (window.gdMicSystem.mediaStream) {", + " window.gdMicSystem.mediaStream.getTracks().forEach(track => track.stop());", + " }", + "", + " // Clean up", + " window.gdMicSystem.mediaStream = null;", + " window.gdMicSystem.mediaRecorder = null;", + " window.gdMicSystem.recordedChunks = [];", + " }" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ] + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "description": "Downloads Recording.", + "fullName": "Download Recording", + "functionType": "Action", + "name": "DownloadREC", + "sentence": "Download Recording _PARAM1_,_PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Once" + }, + "parameters": [] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "// Arguments: filename (string), format (\"webm\" or \"ogg\")", + "const filename = eventsFunctionContext.getArgument(\"filename\") || \"recording\";", + "const format = (eventsFunctionContext.getArgument(\"format\") || \"webm\").toLowerCase();", + "", + "// Make sure we have recorded chunks", + "if (window.gdMicSystem && window.gdMicSystem.recordedChunks.length > 0) {", + " let mimeType = \"audio/webm\";", + "", + " if (format === \"ogg\") {", + " mimeType = \"audio/ogg\";", + " }", + "", + " // Create the Blob", + " const blob = new Blob(window.gdMicSystem.recordedChunks, { type: mimeType });", + "", + " // Prepare the full filename", + " let fullName = filename.trim();", + " if (!fullName.toLowerCase().endsWith(\".\" + format)) {", + " fullName += \".\" + format;", + " }", + "", + " // Create temporary download link and click it", + " const url = URL.createObjectURL(blob);", + " const a = document.createElement(\"a\");", + " a.href = url;", + " a.download = fullName;", + " a.click();", + "", + " // Cleanup URL", + " URL.revokeObjectURL(url);", + " }" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ] + } + ], + "parameters": [ + { + "description": "File Name", + "name": "FileName", + "type": "string" + }, + { + "description": "Format (Webm-Recomended)", + "name": "format", + "supplementaryInformation": "[\"webm\",\"ogg\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Converts the recording to base64, can be used for transferring audio as string.", + "fullName": "Save Recording As Base64", + "functionType": "Action", + "name": "SaveAsBase64", + "sentence": "Save Recording In Variable _PARAM1_ As Base64", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Once" + }, + "parameters": [] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "if (window.gdMicSystem && window.gdMicSystem.recordedChunks.length > 0) {", + " // Get the scene variable to store the Base64 result", + " const variableToChange = eventsFunctionContext.getArgument(\"VariableToChange\");", + "", + " // Combine recorded chunks into a Blob", + " const blob = new Blob(window.gdMicSystem.recordedChunks, { type: \"audio/webm\" });", + "", + " // Convert to Base64", + " const reader = new FileReader();", + " reader.onloadend = function() {", + " const base64data = reader.result.split(\",\")[1]; // pure Base64 string", + "", + " // Store it in the scene variable", + " variableToChange.setString(base64data);", + "", + " console.log(\"Base64 audio stored in variable:\", base64data);", + " };", + "", + " reader.readAsDataURL(blob);", + " }" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ] + } + ], + "parameters": [ + { + "description": "Variable To Store", + "name": "VariableToChange", + "type": "variable" + } + ], + "objectGroups": [] + }, + { + "description": "Play's Base64 Audio.", + "fullName": "Play Base64 Audio", + "functionType": "Action", + "name": "PlayBase64Audio", + "sentence": "Play Base64 Audio. _PARAM1_,_PARAM2_,_PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Once" + }, + "parameters": [] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const variableUsed = eventsFunctionContext.getArgument(\"VariableUsed\"); // your Base64 variable", + "let volume = eventsFunctionContext.getArgument(\"volume\") || 100; // 0 to 100", + "const pitch = eventsFunctionContext.getArgument(\"pitch\") || 1.0; // pitch multiplier", + "", + "// Map 0–100 volume to 0.0–1.0 for Web Audio API", + "volume = Math.max(0, Math.min(volume, 100)) / 100;", + "", + "const base64data = variableUsed.getAsString();", + "const byteArray = Uint8Array.from(atob(base64data), c => c.charCodeAt(0));", + "const blob = new Blob([byteArray], { type: \"audio/webm\" });", + "const audioURL = URL.createObjectURL(blob);", + "", + "const audioContext = new (window.AudioContext || window.webkitAudioContext)();", + "const gainNode = audioContext.createGain();", + "gainNode.gain.value = volume; // now 0–1 scale", + "gainNode.connect(audioContext.destination);", + "", + "// Decode and play", + "fetch(audioURL)", + " .then(response => response.arrayBuffer())", + " .then(arrayBuffer => audioContext.decodeAudioData(arrayBuffer))", + " .then(audioBuffer => {", + " const source = audioContext.createBufferSource();", + " source.buffer = audioBuffer;", + " source.playbackRate.value = pitch; // pitch control (affects speed)", + " source.connect(gainNode);", + " source.start(0);", + " })", + " .catch(err => console.error(\"Audio play error:\", err));", + "", + " // Cleanup URL after a bit", + " setTimeout(() => URL.revokeObjectURL(audioURL), 5000);" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ] + } + ], + "parameters": [ + { + "description": "Variable With Base64", + "name": "VariableUsed", + "type": "variable" + }, + { + "description": "Volume (0-100)", + "name": "volume", + "type": "expression" + }, + { + "description": "Pitch (1 = default)", + "name": "pitch", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Linear Amplitude Measure (Volume Of Speaking).", + "fullName": "Microphone Volume (exports only)", + "functionType": "Expression", + "name": "MicVolume", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "// Expression: MicVolume (0–100)", + "if (!window.gdMicSystem) window.gdMicSystem = {};", + "", + "// Guard: make sure the mic stream exists", + "const stream = window.gdMicSystem.stream;", + "if (!stream) return 0;", + "", + "// Initialize AudioContext and analyser once", + "if (!window.gdMicSystem.volumeAnalyser) {", + " const audioContext = new (window.AudioContext || window.webkitAudioContext)();", + " window.gdMicSystem.audioContext = audioContext;", + "", + " // Resume AudioContext if suspended (required by browser)", + " if (audioContext.state === \"suspended\") {", + " audioContext.resume().catch(err => console.warn(\"AudioContext resume failed:\", err));", + " }", + "", + " const analyser = audioContext.createAnalyser();", + " analyser.fftSize = 512;", + " window.gdMicSystem.volumeAnalyser = analyser;", + "", + " const source = audioContext.createMediaStreamSource(stream);", + " source.connect(analyser);", + "", + " // mic is ready", + " window.gdMicSystem.micReady = true;", + " }", + "", + " // Guard: only proceed if analyser exists", + " const analyser = window.gdMicSystem.volumeAnalyser;", + " if (!analyser) return 0;", + "", + " // Read time-domain data", + " const dataArray = new Uint8Array(analyser.frequencyBinCount);", + " analyser.getByteTimeDomainData(dataArray);", + "", + " // RMS calculation for volume", + " let sum = 0;", + " for (let i = 0; i < dataArray.length; i++) {", + " const norm = (dataArray[i] - 128) / 128;", + " sum += norm * norm;", + " }", + " const rms = Math.sqrt(sum / dataArray.length);", + "", + " // Scale 0–100", + " return Math.round(rms * 100);" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [], + "objectGroups": [] + }, + { + "description": "Converts the recording to base64 every second, can be used for transferring audio as string for sound streaming.", + "fullName": "Save Recording As Base64 (Streaming)", + "functionType": "Action", + "name": "Function", + "sentence": "Save Recording In Variable _PARAM1_;_PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "// Make sure recording exists and has at least one chunk", + "if (window.gdMicSystem && window.gdMicSystem.recordedChunks.length > 0) {", + " // Take the newest chunk (last one in the array)", + " const chunk = window.gdMicSystem.recordedChunks[window.gdMicSystem.recordedChunks.length - 1];", + "", + " if (chunk) {", + " const variableToChange = eventsFunctionContext.getArgument(\"VariableToChange2\");", + " const reader = new FileReader();", + "", + " reader.onloadend = function() {", + " const base64data = reader.result.split(\",\")[1]; // pure Base64 string", + " variableToChange.setString(base64data); // overwrite the variable", + " };", + "", + " reader.readAsDataURL(chunk);", + " }", + " }" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Variable To Store", + "name": "VariableToChange2", + "type": "variable" + }, + { + "description": "Capture Interval (200ms default)", + "longDescription": "Lower = more updates\n1000/200 = 5 updates in second.", + "name": "CaptureInterval", + "type": "expression" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "MicVolume" + }, + { + "functionName": "RequestMC" + }, + { + "functionName": "StartREC" + }, + { + "functionName": "StopREC" + }, + { + "functionName": "DownloadREC" + }, + { + "functionName": "SaveAsBase64" + }, + { + "functionName": "PlayBase64Audio" + }, + { + "functionName": "Function" + } + ] + }, + "eventsBasedBehaviors": [], + "eventsBasedObjects": [] +}