Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 62 additions & 9 deletions future/web/audio-processor.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,17 @@ import { mapFrame } from "./grid-dispatcher.js";
import { playSineWave } from "./synthesis-methods/engines/sine-wave.js";
import { playFMSynthesis } from "./synthesis-methods/engines/fm-synthesis.js";

export let audioContext = null;
export let isAudioInitialized = false;
export let oscillators = [];
let audioContext = null;
let isAudioInitialized = false;
let oscillators = [];
let micSource = null;

export function setAudioContext(newContext) {
function setAudioContext(newContext) {
audioContext = newContext;
isAudioInitialized = false; // Reset state when setting a new context
}

export async function initializeAudio(context) {
async function initializeAudio(context) {
if (isAudioInitialized || !context) {
console.warn("initializeAudio: Already initialized or no context provided");
return false;
Expand Down Expand Up @@ -70,7 +71,7 @@ export async function initializeAudio(context) {
}
}

export function playAudio(
function playAudio(
frameData,
width,
height,
Expand Down Expand Up @@ -132,7 +133,7 @@ export function playAudio(
};
}

export async function cleanupAudio() {
async function cleanupAudio() {
if (!isAudioInitialized || !audioContext) {
console.warn("cleanupAudio: No audio context to clean up");
return;
Expand All @@ -144,7 +145,6 @@ export async function cleanupAudio() {
osc.disconnect();
gain.disconnect();
panner.disconnect();
// Clean up FM modulators
if (osc.frequency?.connectedNodes) {
osc.frequency.connectedNodes.forEach((node) => {
if (node instanceof OscillatorNode) {
Expand All @@ -154,6 +154,10 @@ export async function cleanupAudio() {
});
}
});
if (micSource) {
micSource.disconnect();
micSource = null;
}
oscillators = [];
isAudioInitialized = false;
audioContext = null;
Expand All @@ -168,6 +172,55 @@ export async function cleanupAudio() {
}
}

export async function stopAudio() {
async function stopAudio() {
await cleanupAudio();
}

function initializeMicAudio(micStream) {
if (!audioContext || !isAudioInitialized) {
console.warn("initializeMicAudio: Audio context not initialized");
if (window.dispatchEvent) {
window.dispatchEvent("logError", {
message: "Audio context not initialized for microphone",
});
}
return null;
}
try {
if (micStream) {
micSource = audioContext.createMediaStreamSource(micStream);
const gain = audioContext.createGain();
gain.gain.setValueAtTime(1, audioContext.currentTime);
micSource.connect(gain).connect(audioContext.destination);
console.log("initializeMicAudio: Microphone stream connected to output");
return micSource;
} else {
if (micSource) {
micSource.disconnect();
micSource = null;
}
console.log("initializeMicAudio: No microphone stream provided, disconnected");
return null;
}
} catch (error) {
console.error("initializeMicAudio: Error initializing microphone:", error.message);
if (window.dispatchEvent) {
window.dispatchEvent("logError", {
message: `Microphone init error: ${error.message}`,
});
}
return null;
}
}

export {
setAudioContext,
initializeAudio,
playAudio,
cleanupAudio,
stopAudio,
initializeMicAudio,
audioContext,
isAudioInitialized,
oscillators
};
23 changes: 4 additions & 19 deletions future/web/main.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
/**
* Entry point for AcoustSee, initializing UI handlers and event dispatcher.
*/
import { setupRectangleHandlers } from "./ui/rectangle-handlers.js";
import { setupSettingsHandlers } from "./ui/settings-handlers.js";
import { setupUIController } from "./ui/ui-controller.js";
import { createEventDispatcher } from "./ui/event-dispatcher.js";
import { initDOM } from "./ui/dom.js";
import { setDOM, setDispatchEvent } from "./context.js";
Expand All @@ -16,27 +12,16 @@ document.addEventListener("DOMContentLoaded", async () => {
setDispatchEvent(dispatchEvent);
console.log("DOM loaded, initializing AcoustSee");
try {
// Wait for DOM elements to be assigned and get the DOM object
const DOM = await initDOM();
console.log("DOM initialized:", DOM);

// Create event dispatcher with DOM
const { dispatchEvent } = createEventDispatcher(DOM);
window.dispatchEvent = dispatchEvent; // For mailto: feature
console.log("Dispatcher created:", dispatchEvent);

// Setup handlers with DOM explicitly passed
setupRectangleHandlers({ dispatchEvent, DOM });
console.log("Rectangle handlers set up");
setupSettingsHandlers({ dispatchEvent, DOM });
console.log("Settings handlers set up");

// Initial UI update
setupUIController({ dispatchEvent, DOM });
console.log("UI controller set up");
dispatchEvent("updateUI", { settingsMode: false, streamActive: false });
console.log("Initial UI update dispatched");
} catch (err) {
console.error("Initialization failed:", err.message);
}
});

console.log("main.js: Initialization script loaded");
console.log("main.js: Initialization script loaded");
1 change: 0 additions & 1 deletion future/web/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ export let settings = {

export let skipFrame = false;
export let frameCount = 0;
export let lastTime = 0;
export let prevFrameDataLeft = null;
export let prevFrameDataRight = null;

Expand Down
16 changes: 0 additions & 16 deletions future/web/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -127,20 +127,4 @@ body {
display: inline-block;
}

@media (orientation: landscape) {
body::before {
content: "Please use portrait orientation (9:16)";
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.9);
color: white;
display: flex;
justify-content: center;
align-items: center;
font-size: 5vw;
z-index: 50;
}
}
78 changes: 78 additions & 0 deletions future/web/ui/audio-controls.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// ui/audio-controls.js
import { settings } from "../state.js";
import { speak } from "./utils.js";
import { initializeAudio, cleanupAudio, initializeMicAudio } from "../audio-processor.js";
import { getDispatchEvent } from "../context.js";

let isAudioContextInitialized = false;
let audioContext = null;

export function setupAudioControls({ dispatchEvent, DOM }) {
if (!DOM || !DOM.powerOn || !DOM.button2 || !DOM.splashScreen || !DOM.mainContainer) {
console.error("Missing DOM elements in audio-controls");
dispatchEvent("logError", { message: "Missing DOM elements in audio-controls" });
return;
}

// Power On: Initialize Audio Context
DOM.powerOn.addEventListener("touchstart", async (event) => {
if (event.cancelable) event.preventDefault();
console.log("powerOn touched");
try {
audioContext = new (window.AudioContext || window.webkitAudioContext)();
if (!audioContext) throw new Error("AudioContext creation failed");
await initializeAudio(audioContext);
isAudioContextInitialized = true;
DOM.splashScreen.style.display = "none";
DOM.mainContainer.style.display = "grid";
await speak("audioOn");
dispatchEvent("updateUI", { settingsMode: false, streamActive: false, micActive: false });
console.log("powerOn: AudioContext initialized, UI updated");
} catch (err) {
console.error("Power on error:", err.message);
dispatchEvent("logError", { message: `Power on error: ${err.message}` });
await speak("audioError");
for (let i = 0; i < 3; i++) {
await new Promise((resolve) => setTimeout(resolve, 1000));
try {
console.log(`PowerOn: Retry ${i + 1} for AudioContext`);
audioContext = new (window.AudioContext || window.webkitAudioContext)();
await initializeAudio(audioContext);
isAudioContextInitialized = true;
DOM.splashScreen.style.display = "none";
DOM.mainContainer.style.display = "grid";
await speak("audioOn");
dispatchEvent("updateUI", { settingsMode: false, streamActive: false, micActive: false });
console.log("PowerOn: AudioContext initialized on retry");
break;
} catch (retryErr) {
console.error(`Retry ${i + 1} failed:`, retryErr.message);
dispatchEvent("logError", { message: `Audio retry ${i + 1} failed: ${retryErr.message}` });
}
}
if (!isAudioContextInitialized) await speak("audioError");
}
});

// Button 2: Toggle Microphone (non-settings mode)
DOM.button2.addEventListener("touchstart", async (event) => {
if (event.cancelable) event.preventDefault();
if (settings.isSettingsMode) return; // Handled in ui-settings.js
if (!isAudioContextInitialized) {
console.error("Audio not initialized");
dispatchEvent("logError", { message: "Audio not initialized" });
await speak("audioNotEnabled");
return;
}
try {
console.log("button2: Dispatching toggleMic");
dispatchEvent("toggleMic", { settingsMode: false });
} catch (err) {
console.error("Mic toggle error:", err.message);
dispatchEvent("logError", { message: `Mic toggle error: ${err.message}` });
await speak("micError");
}
});

console.log("setupAudioControls: Setup complete");
}
38 changes: 38 additions & 0 deletions future/web/ui/cleanup-manager.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// ui/cleanup-manager.js
import { settings, setStream, setAudioInterval } from "../state.js";
import { cleanupAudio } from "../audio-processor.js";

let isAudioInitialized = false;
let audioContext = null;

export function setupCleanupManager() {
window.addEventListener("beforeunload", async () => {
if (settings.stream) {
settings.stream.getTracks().forEach((track) => track.stop());
setStream(null);
}
if (settings.micStream) {
settings.micStream.getTracks().forEach((track) => track.stop());
settings.micStream = null;
}
if (settings.audioInterval) {
clearInterval(settings.audioInterval);
setAudioInterval(null);
}
if (isAudioInitialized && audioContext) {
await cleanupAudio();
await audioContext.close();
isAudioInitialized = false;
audioContext = null;
}
console.log("cleanupManager: Cleanup completed");
});

console.log("setupCleanupManager: Setup complete");
}

// Expose for audio-controls.js to update audioContext state
export function setAudioContextState(context, initialized) {
audioContext = context;
isAudioInitialized = initialized;
}
53 changes: 53 additions & 0 deletions future/web/ui/debug-controls.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// ui/debug-controls.js
import { speak } from "./utils.js";
import { getDispatchEvent } from "../context.js";

export function setupDebugControls({ dispatchEvent, DOM }) {
if (!DOM || !DOM.button4 || !DOM.button5 || !DOM.debug) {
console.error("Missing DOM elements in debug-controls");
dispatchEvent("logError", { message: "Missing DOM elements in debug-controls" });
return;
}

function tryVibrate(event) {
if (event.cancelable && navigator.vibrate) {
try {
navigator.vibrate(50);
} catch (err) {
console.warn("Vibration blocked:", err.message);
}
}
}

// Button 4: View Debug (settings mode)
DOM.button4.addEventListener("touchstart", async (event) => {
if (event.cancelable) event.preventDefault();
if (!settings.isSettingsMode) return; // Handled in settings-persistence.js
console.log("button4 touched (settings mode)");
tryVibrate(event);
try {
dispatchEvent("saveSettings", { settingsMode: true });
} catch (err) {
console.error("button4 error:", err.message);
dispatchEvent("logError", { message: `button4 error: ${err.message}` });
await speak("saveError");
}
});

// Button 5: Email Debug (settings mode)
DOM.button5.addEventListener("touchstart", async (event) => {
if (event.cancelable) event.preventDefault();
if (!settings.isSettingsMode) return; // Handled in settings-persistence.js
console.log("button5 touched (settings mode)");
tryVibrate(event);
try {
dispatchEvent("emailDebug");
} catch (err) {
console.error("button5 error:", err.message);
dispatchEvent("logError", { message: `button5 error: ${err.message}` });
await speak("emailDebug", { state: "error" });
}
});

console.log("setupDebugControls: Setup complete");
}
11 changes: 0 additions & 11 deletions future/web/ui/dom.diagram.md

This file was deleted.

Loading