2026年6月13日土曜日

Atraの視覚と聴覚とテキストで複合想起 1 デモcode

 デモcode

<!DOCTYPE html>から</html>までなぞって下のファイル名でhtmlファイルを作ってください。 (GitHubとか使う気ないので、めんどくさいけど我慢してね)


atra_integrated_associatron_visual_audio_text_v9.html

####################  html  ######################


<!DOCTYPE html> <!-- =============================================================================== Atra Integrated Associatron Visual / Audio / Text Demo Atra 統合アソシアトロン 視覚・聴覚・テキスト デモ Copyright (c) 2026 C-sideLaboratory Yukihiro Watanabe.

All rights reserved. 無断転載、無断再配布、無断改変配布、商用利用を禁じます。 Unauthorized copying, redistribution, modified redistribution, or commercial use is prohibited. This file is a local research demonstration for Atra / Associatron experiments. このファイルは Atra / Associatron 実験用のローカル研究デモです。 Important copyright and data-use rule: - Do not use third-party copyrighted images, music, voices, videos, books, articles, or other protected works without permission. - Do not distribute recorded camera/microphone data from other people without consent. - Do not present this demo as a general-purpose recognition AI or dataset collector. - This demo should be used only with data that the experimenter has the right to use. 重要な著作権・データ利用ルール: - 第三者の著作物である画像、音楽、声、動画、書籍、記事などを無断で使わないこと。 - 他人のカメラ映像・マイク音声を同意なく記録・配布しないこと。 - このデモを一般的な認識AIやデータ収集装置として扱わないこと。 - 実験者が利用権限を持つデータだけを使うこと。 This notice must remain in this file. この表示は削除しないこと。 =============================================================================== --> <html lang="ja"> <head> <meta charset="UTF-8"> <title>Atra Integrated Demo v10 - Associatron / Visual Traces / Waveform / Spectrogram / Text Recall</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <style> /* 日本語: Atra統合デモの画面色。 背景は白とベージュ、文字は濃いブルーとグレーを混ぜた落ち着いた色にする。 English: Screen colors for the Atra integrated demo. The background uses white and beige. Text uses dark blue-gray tones. */ :root { --bg: #f7f1e6; --panel: #fffdf8; --panel2: #f1eadc; --ink: #23384f; --muted: #68717c; --line: #d8cdbb; --accent: #2f4f6f; --trace: #fff1b8; } .copyrightBox { border: 1px solid #d7c2a3; background: #fff8ec; color: #33445f; padding: 12px 14px; border-radius: 12px; margin: 12px 0 18px; font-size: 13px; line-height: 1.65; } body { margin: 0; font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: var(--bg); color: var(--ink); } header { padding: 20px 24px 8px; } h1 { margin: 0 0 8px; font-size: 24px; color: var(--ink); } .lead { margin: 0; color: var(--muted); line-height: 1.7; max-width: 1220px; font-size: 14px; } main { display: grid; grid-template-columns: 1.05fr 0.95fr; gap: 16px; padding: 16px 24px 24px; } section { background: var(--panel); border: 1px solid var(--line); border-radius: 16px; padding: 14px; box-shadow: 0 2px 8px rgba(40, 30, 20, 0.04); } h2 { margin: 0 0 10px; font-size: 17px; color: var(--accent); } h3 { margin: 16px 0 8px; font-size: 14px; color: var(--accent); } button { border: 1px solid var(--accent); color: var(--accent); background: #fff; border-radius: 10px; padding: 8px 12px; margin: 4px 4px 4px 0; cursor: pointer; font-size: 14px; } button:hover { background: var(--panel2); } /* 日本語: Learn と「一旦記憶を消す」は、操作上の区切りが大きい。 そのため薄いピンクにして、他の通常ボタンと区別する。 English: Learn and "clear temporary trace" are important operation boundaries. They use pale pink to distinguish them from ordinary buttons. */ .pinkButton { background: #fdecef; border-color: #d89aa6; color: #6f3b46; } .pinkButton:hover { background: #f9dce2; } textarea, input { border: 1px solid var(--line); border-radius: 10px; background: #fff; color: var(--ink); padding: 9px 10px; font: inherit; box-sizing: border-box; } textarea { width: 100%; min-height: 86px; resize: vertical; } input { width: 100%; margin-top: 8px; } .note { background: #fbf7ef; border: 1px solid var(--line); border-radius: 12px; padding: 10px; color: var(--muted); font-size: 13px; line-height: 1.65; margin: 8px 0 12px; } .stage { position: relative; width: 640px; max-width: 100%; aspect-ratio: 4 / 3; border: 1px solid var(--line); border-radius: 14px; overflow: hidden; background: #101827; } #video { /* 日本語: 実際のカメラ映像。visualCanvas より下に置く。 English: The actual camera image. It is placed under visualCanvas. */ position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; background: #101827; z-index: 1; } #visualCanvas { /* 日本語: これは映像の上に重ねる「枠だけを描くキャンバス」です。 背景を持たせると、下のvideo映像を隠してしまう。 そのため background は transparent、border は none にする。 English: This canvas is only for drawing boxes over the video. If it has a background, it hides the video underneath. Therefore its background must be transparent and border must be none. */ position: absolute; inset: 0; width: 100%; height: 100%; background: transparent; border: none; z-index: 2; pointer-events: none; } canvas { background: #fff; border: 1px solid var(--line); border-radius: 12px; max-width: 100%; } .wideCanvas { width: 100%; height: 130px; } .spectrogramCanvas { width: 100%; height: 190px; } .row3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 8px; margin-top: 10px; } .meter { background: var(--panel2); border-radius: 10px; padding: 10px; color: var(--muted); font-size: 13px; } .meter strong { display: block; color: var(--ink); font-size: 18px; margin-top: 4px; } pre { white-space: pre-wrap; word-break: break-word; background: #fff; border: 1px solid var(--line); border-radius: 12px; padding: 10px; max-height: 440px; overflow: auto; color: var(--ink); font-size: 12px; } .math { font-family: "Times New Roman", serif; color: var(--ink); background: #fff; border: 1px solid var(--line); border-radius: 10px; padding: 10px; line-height: 1.5; font-size: 15px; } .memoryItem { border: 1px solid var(--line); border-radius: 12px; padding: 9px; background: #fff; margin-top: 8px; color: var(--muted); font-size: 13px; line-height: 1.5; } .memoryItem b { color: var(--accent); } @media (max-width: 960px) { main { grid-template-columns: 1fr; padding: 12px; } header { padding: 16px 12px 4px; } } </style> </head> <body> <header> <h1>Atra Integrated Demo v10</h1> <div class="copyrightBox"> <b>Copyright / 著作権</b><br> Copyright © 2026 Yukihiro Watanabe. All rights reserved.<br> 無断転載・無断再配布・無断改変配布・商用利用を禁じます。<br><br> <b>Use rule / 利用ルール</b><br> このデモでは、第三者の著作物である画像・音声・動画・文章を無断で使わないこと。<br> Use only camera, microphone, text, and other data that the experimenter has the right to use. </div> <p class="lead"> Atraの目・聴覚・収束を1つにした単体デモ。映像は <b>visual traces</b> として差分枠で追い、音声は <b>waveform</b><b>spectrogram / voiceprint</b> で観察し、テキストも同じ場の <b>text_delta</b> として Learn / Recall する。 画像認識・顔認識・音声認識・文字起こし・正解ラベル化はしない。 <br> Integrated standalone demo for Atra visual traces, auditory waveform, spectrogram / voiceprint, text cue recall, and Associatron-style convergence. </p> </header> <main> <section> <h2>1. Atra Eye / Visual Traces / Atraの目</h2> <div class="note"> <b>日本語:</b> 映像そのものを覚えるのではなく、フレーム間の差分から「動いた場所」を枠として出します。 枠は人・顔・物体名の認識ではありません。<b>visual traces</b> の観察表示です。この版では視覚差分を12×8に上げています。 <br> <b>English:</b> This does not remember raw video. It extracts changed regions between frames and draws boxes. Boxes are not person, face, or object recognition. They are observer display for <b>visual traces</b>. This version uses a 12×8 visual delta grid. </div> <button onclick="startCamera()">Start camera / カメラ開始</button> <button onclick="stopCamera()">Stop camera / カメラ停止</button> <div class="stage"> <video id="video" autoplay muted playsinline></video> <canvas id="visualCanvas" width="640" height="480"></canvas> </div> <h3>Visual delta map / 視覚差分マップ</h3> <canvas id="visualDeltaCanvas" width="640" height="160" class="wideCanvas"></canvas> <h2>2. Atra Auditory Pre-stage / 聴覚前段</h2> <div class="note"> <b>日本語:</b> ここでは文字起こしをしません。音声を「言葉」として理解せず、<b>waveform</b><b>spectrogram / voiceprint</b> を観察します。 Atraに渡すのは音量、アタック、周波数分布の粗い auditory_delta です。 <br> <b>English:</b> No transcription. Sound is not treated as words. The demo observes <b>waveform</b> and <b>spectrogram / voiceprint</b>. Atra receives only rough auditory_delta such as energy, attack, and frequency distribution. </div> <button onclick="startMic()">Start mic / マイク開始</button> <button onclick="stopMic()">Stop mic / マイク停止</button> <h3>Waveform / 波形</h3> <canvas id="waveCanvas" width="640" height="130" class="wideCanvas"></canvas> <h3>Spectrogram / Voiceprint / 声紋</h3> <canvas id="spectrogramCanvas" width="640" height="190" class="spectrogramCanvas"></canvas> </section> <section> <h2>3. Text Cue / テキストcue</h2> <div class="note"> <b>日本語:</b> テキストは入力できます。これは正解ラベルではなく、同じ場に入る <b>text_delta</b> です。 Learn時に直近8秒の映像・音とテキストを同時に覚えます。あとでテキストだけ入力して Recall することもできます。 <br> <b>English:</b> Text can be typed. It is not a correct label, but <b>text_delta</b> in the same field. On Learn, visual and audio traces from the recent 8 seconds are stored together with text. Later, text alone can be used as a cue for Recall. </div> <textarea id="textInput" placeholder="ここにテキストを打つ。例: apple / mama / warm / 危ない / 音がした / 赤いもの"></textarea> <input id="humanNote" placeholder="human note / 人間用メモ。Atra内部の正解ではない"> <button onclick="sampleField()">Sample current field / 現在場を採取</button> <button class="pinkButton" onclick="learnField()">Learn / 学習</button> <button onclick="recallMixedCue()">Recall from mixed cue / 混合cueから想起</button> <div class="note"> <b>Learn rule / 学習操作:</b><br> 1つの経験につき Learn は1回だけ押す。連打すると同じ痕跡が強くなりすぎる。<br> Press Learn once per experience. Repeated pressing over-strengthens the same trace.<br> Recall does not stop live visual/audio sensing. / Recallしてもライブの視覚・聴覚センサーは止めない。 </div> <button onclick="recallTextOnly()">Recall from text only / テキストだけで想起</button> <button class="pinkButton" onclick="clearSensorCue()">一旦記憶を消す / Clear temporary sensory trace</button> <button onclick="resetMemory()">Reset / 初期化</button> <div class="row3"> <div class="meter">visual traces / 視覚痕跡<strong id="visualMeter">0.000</strong></div> <div class="meter">audio energy / 音圧<strong id="audioMeter">0.000</strong></div> <div class="meter">text pressure / 文字圧<strong id="textMeter">0.000</strong></div> </div> <h3>Learn readiness / 学習準備状態</h3> <div class="row3"> <div class="meter">visual buffer / 視覚バッファ<strong id="visualBufferMeter">empty</strong></div> <div class="meter">audio buffer / 聴覚バッファ<strong id="audioBufferMeter">empty</strong></div> <div class="meter">text buffer / テキスト<strong id="textBufferMeter">empty</strong></div> </div> <h2>4. Associatron / アソシアトロン</h2> <div class="math"> Associatron memory matrix / アソシアトロン記憶行列:<br> T<sub>ij</sub> = Σ x<sub>i</sub>x<sub>j</sub>, &nbsp; T<sub>ii</sub> = 0 <br><br> Cue recall / cueからの想起:<br> y = clamp(x<sub>cue</sub>, sign(Tx<sub>cue</sub>)) <br><br> Energy of the memory field / 記憶場のエネルギー:<br> V = − 1/2 Σ T<sub>ij</sub>x<sub>i</sub>x<sub>j</sub> </div> <h2>5. Log / Recall</h2> <pre id="log">Waiting. / 待機中。</pre> <div id="memoryList"></div> </section> </main> <script> /* ================================================================================ Atra Integrated Demo ================================================================================ 日本語: これはAtra本体ではなく、Atraの3つの前段を1つにまとめたデモである。 1. Atraの目: カメラ映像からフレーム差分を取り、動いた領域に枠を出す。 この枠を visual traces として表示する。 これは物体認識ではない。顔認識でもない。名前も付けない。 2. Atraの聴覚: マイク音声から waveform と spectrogram / voiceprint を描く。 これは音声認識ではない。文字起こしでもない。感情認識でもない。 3. Atraの収束: visual_delta + auditory_delta + text_delta を同時に1つの場としてLearnする。 text_delta も同じ場に入るので、テキストだけでもcueとしてRecallできる。 English: This is not Atra itself. It is an integrated demo of three pre-stages. 1. Atra Eye: It calculates frame differences from camera video and draws boxes on moving regions. These boxes are displayed as visual traces. This is not object recognition, face recognition, or labeling. 2. Atra Auditory: It draws waveform and spectrogram / voiceprint from microphone sound. This is not speech recognition, transcription, or emotion recognition. 3. Atra Convergence: visual_delta + auditory_delta + text_delta are learned together as one field. Since text_delta is part of the same field, text alone can recall a trace. ================================================================================ */ const COPYRIGHT_NOTICE = { owner: "Yukihiro Watanabe", year: "2026", rights: "All rights reserved", jp: "無断転載・無断再配布・無断改変配布・商用利用を禁じます。", en: "Unauthorized copying, redistribution, modified redistribution, or commercial use is prohibited." }; const TRACE_SIZE = 192; const VISUAL_SIZE = 96; const AUDIO_SIZE = 64; const TEXT_SIZE = 32; /* 日本語: 視覚差分グリッド。 以前の 8×4 は粗すぎて、小さな顔・手・身体の動きが入りにくかった。 この版では 12×8 = 96 に上げる。 English: Visual difference grid. The previous 8×4 grid was too coarse for small face / hand / body movements. This version uses 12×8 = 96. */ const VISUAL_COLS = 12; const VISUAL_ROWS = 8; /* 日本語: GPUについて: この版はまだCanvas 2Dで動かす。12×8程度ならCPU負荷は軽い。 将来WebGLで差分計算をGPUへ移せるが、今はまず差分の安定性と見え方を優先する。 English: About GPU: This version still uses Canvas 2D. A 12×8 grid is light enough on CPU. Later, WebGL can move difference calculation to GPU, but stability and observability come first here. */ let T = makeZeroMatrix(TRACE_SIZE); let memories = []; let currentTrace = makeEmptyTrace(); let cameraStream = null; let micStream = null; let audioContext = null; let analyser = null; let timeData = null; let freqData = null; let prevGray = null; let visualCells = Array(VISUAL_SIZE).fill(0); let visualBoxes = []; let stableBoxMemory = []; /* 日本語: 直近の感覚痕跡を少しだけ保持する。 声や動きは一瞬で消えるので、Learnボタンを押した時には0になってしまうことがある。 それを避けるため、視覚と聴覚の痕跡を数秒だけ残す。 English: Keeps recent sensory traces for a short time. Voice and movement disappear quickly, so they may become zero by the time Learn is pressed. To avoid this, visual and auditory traces are kept for a few seconds. */ let heldVisualCells = Array(VISUAL_SIZE).fill(0); let heldVisualUntil = 0; let heldAudioBands = Array(AUDIO_SIZE).fill(0); let heldAudioUntil = 0; /* 日本語: 直近8秒間の感覚履歴。 Learnボタンを押した瞬間だけを見ると、声や動きがすでに消えていてtext onlyになりやすい。 そこで、直近8秒間の visual / audio をバッファに残し、Learn時にまとめて使う。 English: Sensory history for the last 8 seconds. If Learn only samples the exact button moment, voice or movement may already be gone and learning becomes text-only. Therefore visual / audio traces from the recent 8 seconds are buffered and used during Learn. */ let visualHistory = []; let audioHistory = []; const LEARN_WINDOW_MS = 8000; let lastAudioBands = Array(AUDIO_SIZE).fill(0); let animationStarted = false; let sensorCueCleared = false; const TRACE_HOLD_MS = 8000; /* 日本語: visual traces の安定化設定。 前の版では、差分セルを最大値で正規化していたため、カメラノイズや露出揺れでも 「一番大きいノイズ」が枠になってしまった。 この版では、絶対量が小さい差分では枠を出さない。 Atraの目として、動いていないものに trace を出さないための柵である。 English: Stabilization settings for visual traces. The previous version normalized cells by the maximum cell value, so camera noise or exposure jitter could become a box just because it was the strongest noise. This version does not draw boxes when the absolute amount of difference is small. This is a guardrail so Atra's eye does not create traces for things that are not really moving. */ const VISUAL_PIXEL_THRESHOLD = 24; // per-pixel difference threshold / 画素ごとの差分しきい値 const VISUAL_MIN_ACTIVE_RATIO = 0.006; // at least 1.8% active pixels / 画面の1.8%以上が動いた時だけ const VISUAL_MIN_CELL_ENERGY = 0.010; // absolute cell energy threshold / セル絶対量しきい値 const VISUAL_BOX_SCORE = 0.28; // normalized candidate threshold / 枠候補しきい値 const VISUAL_BOX_HOLD = 4; // hold frames for stable display / 枠表示を少し保持 const video = document.getElementById("video"); const visualCanvas = document.getElementById("visualCanvas"); const visualCtx = visualCanvas.getContext("2d", { willReadFrequently: true }); const visualDeltaCanvas = document.getElementById("visualDeltaCanvas"); const visualDeltaCtx = visualDeltaCanvas.getContext("2d", { willReadFrequently: true }); const waveCanvas = document.getElementById("waveCanvas"); const waveCtx = waveCanvas.getContext("2d", { willReadFrequently: true }); const spectrogramCanvas = document.getElementById("spectrogramCanvas"); const spectrogramCtx = spectrogramCanvas.getContext("2d", { willReadFrequently: true }); async function startCamera() { /* 日本語: カメラを開始する。 HTMLのonclickから直接呼ぶので、ボタン接続の失敗を避ける。 English: Starts the camera. Called directly from HTML onclick to avoid button binding failure. */ try { log("Starting camera... / カメラ開始中..."); if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) { throw new Error("getUserMedia is not available."); } cameraStream = await navigator.mediaDevices.getUserMedia({ video: { width: { ideal: 640 }, height: { ideal: 480 } }, audio: false }); video.srcObject = cameraStream; video.muted = true; video.playsInline = true; await new Promise(resolve => { if (video.readyState >= 1) resolve(); else video.onloadedmetadata = resolve; }); await video.play(); /* 日本語: カメラ開始後、待機表示で塗ったoverlayを必ず一度消す。 これで「枠用キャンバスだけが残って映像が見えない」状態を避ける。 English: After the camera starts, clear the overlay that may have been filled by the waiting display. This prevents the overlay from hiding the video. */ visualCtx.clearRect(0, 0, visualCanvas.width, visualCanvas.height); const track = cameraStream.getVideoTracks()[0]; log( "Camera OK / カメラOK\n" + "device: " + (track ? track.label : "(unknown)") + "\n" + "videoWidth: " + video.videoWidth + "\n" + "videoHeight: " + video.videoHeight + "\n\n" + "visual traces will appear as boxes when movement is detected.\n" + "動きがあると visual traces が枠として表示されます。" ); sensorCueCleared = false; startMainLoop(); } catch (err) { log( "Camera ERROR / カメラエラー\n" + err.name + ": " + err.message + "\n\n" + "Check browser permission and Windows camera privacy settings.\n" + "ブラウザ権限とWindowsのカメラ設定を確認してください。" ); } } function stopCamera() { if (cameraStream) { cameraStream.getTracks().forEach(t => t.stop()); cameraStream = null; } prevGray = null; visualCells = Array(VISUAL_SIZE).fill(0); visualBoxes = []; visualCtx.clearRect(0, 0, visualCanvas.width, visualCanvas.height); drawWaitingVisual(); log("Camera stopped. / カメラ停止。"); } async function startMic() { /* 日本語: マイクを開始する。 waveform と spectrogram / voiceprint を描く。 English: Starts the microphone. Draws waveform and spectrogram / voiceprint. */ try { log("Starting mic... / マイク開始中..."); if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) { throw new Error("getUserMedia is not available."); } micStream = await navigator.mediaDevices.getUserMedia({ video: false, audio: true }); audioContext = new (window.AudioContext || window.webkitAudioContext)(); if (audioContext.state === "suspended") { await audioContext.resume(); } const source = audioContext.createMediaStreamSource(micStream); analyser = audioContext.createAnalyser(); analyser.fftSize = 1024; timeData = new Uint8Array(analyser.fftSize); freqData = new Uint8Array(analyser.frequencyBinCount); source.connect(analyser); const track = micStream.getAudioTracks()[0]; log( "Mic OK / マイクOK\n" + "device: " + (track ? track.label : "(unknown)") + "\n\n" + "waveform and spectrogram / voiceprint are active.\n" + "波形と声紋が動作します。" ); sensorCueCleared = false; startMainLoop(); } catch (err) { log( "Mic ERROR / マイクエラー\n" + err.name + ": " + err.message + "\n\n" + "Check browser permission and Windows microphone privacy settings.\n" + "ブラウザ権限とWindowsのマイク設定を確認してください。" ); } } function stopMic() { if (micStream) { micStream.getTracks().forEach(t => t.stop()); micStream = null; } if (audioContext) { audioContext.close(); audioContext = null; } analyser = null; timeData = null; freqData = null; lastAudioBands = Array(AUDIO_SIZE).fill(0); waveCtx.clearRect(0, 0, waveCanvas.width, waveCanvas.height); spectrogramCtx.clearRect(0, 0, spectrogramCanvas.width, spectrogramCanvas.height); drawWaitingAudio(); log("Mic stopped. / マイク停止。"); } function startMainLoop() { if (animationStarted) return; animationStarted = true; requestAnimationFrame(loop); } function loop() { updateVisualTracking(); updateAudioViews(); sampleField(false); updateLearnReadiness(); requestAnimationFrame(loop); } function updateVisualTracking() { /* 日本語: 映像を直接表示するのは video 要素。 visualCanvas はその上に重ね、visual traces の枠だけを描く。 重要: ノイズだけで枠を出さない。 カメラは暗所ノイズ、露出補正、圧縮ノイズ、液晶のちらつきでも微小差分を出す。 そのため「最大差分セル」をそのまま枠にしてはいけない。 English: The video element displays the camera image. visualCanvas is placed over it and draws only visual trace boxes. Important: Do not draw boxes from noise alone. Cameras produce tiny differences from sensor noise, auto exposure, compression noise, or screen flicker. Therefore the strongest cell must not automatically become a visual trace. */ visualCtx.clearRect(0, 0, visualCanvas.width, visualCanvas.height); if (!cameraStream || video.readyState < 2 || video.videoWidth === 0) { if (!cameraStream) drawWaitingVisual(); if (!sensorCueCleared) visualCells = Array(VISUAL_SIZE).fill(0); visualBoxes = []; stableBoxMemory = []; drawVisualDeltaMap(); return; } if (sensorCueCleared) { visualCells = Array(VISUAL_SIZE).fill(0); visualBoxes = []; stableBoxMemory = []; drawVisualDeltaMap(); return; } const smallW = 160; const smallH = 120; const off = updateVisualTracking.off || document.createElement("canvas"); off.width = smallW; off.height = smallH; updateVisualTracking.off = off; const offCtx = off.getContext("2d", { willReadFrequently: true }); offCtx.drawImage(video, 0, 0, smallW, smallH); const img = offCtx.getImageData(0, 0, smallW, smallH); const gray = new Uint8Array(smallW * smallH); for (let i = 0, p = 0; i < img.data.length; i += 4, p++) { gray[p] = (img.data[i] * 0.299 + img.data[i + 1] * 0.587 + img.data[i + 2] * 0.114) | 0; } if (!prevGray) { prevGray = gray; drawVisualDeltaMap(); drawNoTraceMessage("stabilizing..."); return; } const cols = VISUAL_COLS; const rows = VISUAL_ROWS; const cellRaw = Array(cols * rows).fill(0); const cellActive = Array(cols * rows).fill(0); let activePixels = 0; let totalStrongDiff = 0; for (let y = 0; y < smallH; y++) { for (let x = 0; x < smallW; x++) { const idx = y * smallW + x; const d = Math.abs(gray[idx] - prevGray[idx]); /* 日本語: 画素単位の小さな揺れは捨てる。 ここで捨てないと、照明や圧縮ノイズが trace になる。 English: Tiny pixel-level changes are ignored. Without this, lighting or compression noise becomes a trace. */ if (d > VISUAL_PIXEL_THRESHOLD) { activePixels += 1; totalStrongDiff += d; const cx = Math.min(cols - 1, Math.floor(x / (smallW / cols))); const cy = Math.min(rows - 1, Math.floor(y / (smallH / rows))); const ci = cy * cols + cx; cellRaw[ci] += d; cellActive[ci] += 1; } } } prevGray = gray; const activeRatio = activePixels / (smallW * smallH); /* 日本語: activeRatio が小さい時は、画面全体として動いていない。 その場合は、差分マップだけ薄く出し、枠は出さない。 English: If activeRatio is small, the screen is not really moving. In that case, only the delta map is updated faintly and no boxes are drawn. */ if (activeRatio < VISUAL_MIN_ACTIVE_RATIO) { /* 日本語: 枠として出すほど強くない動きでも、Learn用には弱いvisual bufferとして残す。 これにより、ノイズ枠は出さずに、実際の小さな動きは学習候補に残せる。 English: Even when movement is not strong enough to draw a box, keep it as a weak visual buffer for Learn. This avoids noise boxes while still preserving small real movement. */ if (activeRatio > 0.0015 && totalStrongDiff > 0) { const maxPossiblePerCellWeak = (smallW / cols) * (smallH / rows) * 255; const weakCells = cellRaw.map(v => clamp(v / maxPossiblePerCellWeak)); pushVisualHistory(weakCells, activeRatio, 0); } visualCells = Array(VISUAL_SIZE).fill(0); visualBoxes = decayStableBoxes([]); drawVisualBoxes(0, activeRatio, "no stable visual trace"); drawVisualDeltaMap(); return; } const maxPossiblePerCell = (smallW / cols) * (smallH / rows) * 255; const absoluteCells = cellRaw.map(v => v / maxPossiblePerCell); const maxCell = Math.max(...absoluteCells, 0.000001); /* 日本語: visualCells は表示用に0〜1へ寄せるが、枠判定には絶対量も使う。 ここが重要。正規化だけで枠を出すとノイズが枠になる。 English: visualCells are normalized for display, but box decisions also use absolute energy. This is important. If boxes use normalization alone, noise becomes a box. */ visualCells = absoluteCells.map(v => clamp(v / Math.max(maxCell, VISUAL_MIN_CELL_ENERGY))); const candidates = []; for (let i = 0; i < absoluteCells.length; i++) { const normalizedScore = visualCells[i]; const absoluteEnergy = absoluteCells[i]; if (absoluteEnergy >= VISUAL_MIN_CELL_ENERGY && normalizedScore >= VISUAL_BOX_SCORE) { const cx = i % cols; const cy = Math.floor(i / cols); candidates.push({ x: cx / cols, y: cy / rows, w: 1 / cols, h: 1 / rows, score: normalizedScore, absolute: absoluteEnergy, hold: VISUAL_BOX_HOLD }); } } candidates.sort((a, b) => (b.absolute + b.score) - (a.absolute + a.score)); visualBoxes = decayStableBoxes(candidates.slice(0, 4)); drawVisualBoxes(activeRatio, activeRatio, visualBoxes.length ? "" : "movement too weak"); drawVisualDeltaMap(); } function decayStableBoxes(newBoxes) { /* 日本語: 1フレームだけのノイズ枠を減らすための簡易保持。 新しい枠があるときは更新し、無いときは数フレームだけ前の枠を薄く残す。 ただし、今回の目的では「動いていないのに出続ける」ことを避けるため保持は短い。 English: Simple box holding to reduce one-frame noise. When new boxes exist, update them. When not, keep previous boxes only for a few frames. The hold is short to avoid showing boxes when nothing is moving. */ if (newBoxes.length > 0) { stableBoxMemory = newBoxes.map(b => ({ ...b, hold: VISUAL_BOX_HOLD })); return stableBoxMemory; } stableBoxMemory = stableBoxMemory .map(b => ({ ...b, hold: b.hold - 1 })) .filter(b => b.hold > 0); return stableBoxMemory; } function drawWaitingVisual() { visualCtx.fillStyle = "rgba(16, 24, 39, 0.90)"; visualCtx.fillRect(0, 0, visualCanvas.width, visualCanvas.height); visualCtx.fillStyle = "#ffffff"; visualCtx.font = "18px sans-serif"; visualCtx.fillText("waiting for camera...", 24, 42); visualCtx.font = "14px sans-serif"; visualCtx.fillText("カメラ開始を押してください", 24, 68); } function drawNoTraceMessage(message) { visualCtx.clearRect(0, 0, visualCanvas.width, visualCanvas.height); visualCtx.fillStyle = "rgba(35, 56, 79, 0.50)"; visualCtx.fillRect(12, 12, 190, 28); visualCtx.fillStyle = "#fffdf8"; visualCtx.font = "14px sans-serif"; visualCtx.fillText(message, 22, 31); } function drawVisualBoxes(motion, activeRatio, message) { /* 日本語: visual traces の枠を描く。 ただし、動きが安定して検出された場合だけ枠を出す。 動いていない時は「no stable visual trace」と表示する。 English: Draws visual trace boxes. Boxes are drawn only when stable movement is detected. When nothing is moving, it shows “no stable visual trace”. */ visualCtx.clearRect(0, 0, visualCanvas.width, visualCanvas.height); if (!visualBoxes || visualBoxes.length === 0) { visualCtx.fillStyle = "rgba(35, 56, 79, 0.50)"; visualCtx.fillRect(12, 12, 230, 50); visualCtx.fillStyle = "#fffdf8"; visualCtx.font = "14px sans-serif"; visualCtx.fillText(message || "no stable visual trace", 22, 32); visualCtx.fillText("active: " + activeRatio.toFixed(3), 22, 52); document.getElementById("visualMeter").textContent = "0.000"; return; } visualCtx.lineWidth = 3; visualCtx.strokeStyle = "rgba(255, 241, 184, 0.98)"; visualCtx.fillStyle = "rgba(35, 56, 79, 0.65)"; visualCtx.font = "15px sans-serif"; visualBoxes.forEach((b, index) => { const x = b.x * visualCanvas.width; const y = b.y * visualCanvas.height; const w = b.w * visualCanvas.width; const h = b.h * visualCanvas.height; visualCtx.strokeRect(x, y, w, h); visualCtx.fillRect(x, Math.max(0, y - 23), 132, 23); visualCtx.fillStyle = "#fffdf8"; visualCtx.fillText("visual trace " + (index + 1), x + 7, Math.max(17, y - 7)); visualCtx.fillStyle = "rgba(35, 56, 79, 0.65)"; }); /* 日本語: visual trace が出たら、その時の visualCells を少し保持する。 これで「動いてからLearnを押すまでの短い遅れ」を吸収する。 English: When visual traces appear, keep the current visualCells briefly. This absorbs the short delay between movement and pressing Learn. */ heldVisualCells = visualCells.slice(0, VISUAL_SIZE); heldVisualUntil = Date.now() + TRACE_HOLD_MS; pushVisualHistory(visualCells, motion, visualBoxes.length); document.getElementById("visualMeter").textContent = motion.toFixed(3); } function drawVisualDeltaMap() { visualDeltaCtx.clearRect(0, 0, visualDeltaCanvas.width, visualDeltaCanvas.height); visualDeltaCtx.fillStyle = "#fffdf8"; visualDeltaCtx.fillRect(0, 0, visualDeltaCanvas.width, visualDeltaCanvas.height); const cols = VISUAL_COLS; const rows = VISUAL_ROWS; const cw = visualDeltaCanvas.width / cols; const ch = visualDeltaCanvas.height / rows; for (let i = 0; i < visualCells.length; i++) { const v = visualCells[i]; const x = (i % cols) * cw; const y = Math.floor(i / cols) * ch; const shade = Math.floor(255 - v * 170); visualDeltaCtx.fillStyle = `rgb(${shade}, ${shade}, 255)`; visualDeltaCtx.fillRect(x, y, cw - 1, ch - 1); } } function updateAudioViews() { /* 日本語: waveform と spectrogram / voiceprint を更新する。 文字起こしや話者認識はしない。 English: Updates waveform and spectrogram / voiceprint. No transcription or speaker recognition. */ if (!analyser || !timeData || !freqData) { if (!analyser) drawWaitingAudio(); if (!sensorCueCleared) lastAudioBands = Array(AUDIO_SIZE).fill(0); return; } if (sensorCueCleared) { lastAudioBands = Array(AUDIO_SIZE).fill(0); return; } analyser.getByteTimeDomainData(timeData); analyser.getByteFrequencyData(freqData); drawWaveform(); drawSpectrogram(); updateAudioBands(); } function drawWaitingAudio() { waveCtx.fillStyle = "#fffdf8"; waveCtx.fillRect(0, 0, waveCanvas.width, waveCanvas.height); waveCtx.fillStyle = "#68717c"; waveCtx.font = "16px sans-serif"; waveCtx.fillText("waiting for mic... / マイク開始を押してください", 18, 38); spectrogramCtx.fillStyle = "#fffdf8"; spectrogramCtx.fillRect(0, 0, spectrogramCanvas.width, spectrogramCanvas.height); spectrogramCtx.fillStyle = "#68717c"; spectrogramCtx.font = "16px sans-serif"; spectrogramCtx.fillText("spectrogram / voiceprint will appear here / 声紋はここに出ます", 18, 38); } function drawWaveform() { /* 日本語: waveform / 波形。 音声の時間的な揺れを見る。言葉の意味は見ない。 English: waveform. Shows temporal vibration of sound. It does not read meaning. */ waveCtx.fillStyle = "#fffdf8"; waveCtx.fillRect(0, 0, waveCanvas.width, waveCanvas.height); waveCtx.strokeStyle = "#2f4f6f"; waveCtx.lineWidth = 2; waveCtx.beginPath(); for (let i = 0; i < timeData.length; i++) { const x = i / (timeData.length - 1) * waveCanvas.width; const y = (timeData[i] / 255) * waveCanvas.height; if (i === 0) waveCtx.moveTo(x, y); else waveCtx.lineTo(x, y); } waveCtx.stroke(); } function drawSpectrogram() { /* 日本語: spectrogram / voiceprint / 声紋。 左へ1px流し、右端に新しい周波数分布を描く。 強い周波数ほど濃く見える。 English: spectrogram / voiceprint. Scrolls left by 1px and draws the newest frequency distribution at the right edge. Stronger frequency energy appears darker. */ const w = spectrogramCanvas.width; const h = spectrogramCanvas.height; const old = spectrogramCtx.getImageData(1, 0, w - 1, h); spectrogramCtx.putImageData(old, 0, 0); for (let y = 0; y < h; y++) { const fi = Math.floor((1 - y / h) * (freqData.length - 1)); const v = freqData[fi] / 255; const shade = Math.floor(250 - v * 170); spectrogramCtx.fillStyle = `rgb(${shade}, ${shade + 2}, 255)`; spectrogramCtx.fillRect(w - 1, y, 1, 1); } } function updateAudioBands() { const bands = Array(AUDIO_SIZE).fill(0); const step = Math.floor(freqData.length / AUDIO_SIZE); let sum = 0; for (let b = 0; b < AUDIO_SIZE; b++) { let bandSum = 0; for (let i = b * step; i < Math.min(freqData.length, (b + 1) * step); i++) { bandSum += freqData[i]; } bands[b] = clamp((bandSum / Math.max(step, 1)) / 255); sum += bands[b]; } lastAudioBands = bands; const energy = sum / AUDIO_SIZE; /* 日本語: 聴覚痕跡も少し保持する。 「渡邉」と言ってからLearnを押すまでに音が消えても、直近の声紋が少し残る。 English: Auditory traces are also held briefly. Even if the sound disappears before Learn is pressed, the recent voiceprint remains for a short time. */ if (energy > 0.0015) { heldAudioBands = bands.slice(0, AUDIO_SIZE); heldAudioUntil = Date.now() + TRACE_HOLD_MS; pushAudioHistory(bands, energy); } document.getElementById("audioMeter").textContent = energy.toFixed(3); } function updateLearnReadiness() { /* 日本語: Learnを押す前に、何が学習に入る予定かを表示する。 これが empty のままなら、Learnしてもその要素は入らない。 English: Shows what will enter Learn before pressing Learn. If this stays empty, that modality will not enter learning. */ const now = Date.now(); visualHistory = visualHistory.filter(v => now - v.t <= LEARN_WINDOW_MS); audioHistory = audioHistory.filter(a => now - a.t <= LEARN_WINDOW_MS); const visualBox = document.getElementById("visualBufferMeter"); const audioBox = document.getElementById("audioBufferMeter"); const textBox = document.getElementById("textBufferMeter"); if (visualBox) { visualBox.textContent = visualHistory.length > 0 ? "ready" : "empty"; } if (audioBox) { audioBox.textContent = audioHistory.length > 0 ? "ready" : "empty"; } if (textBox) { const text = document.getElementById("textInput").value; textBox.textContent = text && text.length > 0 ? "ready" : "empty"; } } function pushVisualHistory(cells, motion, boxCount) { /* 日本語: visual traces の履歴を残す。 古いものは8秒を超えたら捨てる。 English: Keeps visual trace history. Old entries beyond 8 seconds are discarded. */ const now = Date.now(); visualHistory.push({ t: now, cells: cells.slice(0, VISUAL_SIZE), motion: motion, boxes: boxCount }); visualHistory = visualHistory.filter(v => now - v.t <= LEARN_WINDOW_MS); updateLearnReadiness(); } function pushAudioHistory(bands, energy) { /* 日本語: waveform / spectrogram 由来の聴覚履歴を残す。 古いものは8秒を超えたら捨てる。 English: Keeps auditory history derived from waveform / spectrogram. Old entries beyond 8 seconds are discarded. */ const now = Date.now(); audioHistory.push({ t: now, bands: bands.slice(0, AUDIO_SIZE), energy: energy }); audioHistory = audioHistory.filter(a => now - a.t <= LEARN_WINDOW_MS); updateLearnReadiness(); } function combineRecentVisual() { /* 日本語: 直近8秒のvisual tracesをまとめる。 最大値合成にすることで、一瞬出たtraceもLearnに入る。 English: Combines visual traces from the recent 8 seconds. Max-composition lets short visual traces enter Learn. */ const now = Date.now(); visualHistory = visualHistory.filter(v => now - v.t <= LEARN_WINDOW_MS); const out = Array(VISUAL_SIZE).fill(0); let boxes = 0; let motion = 0; visualHistory.forEach(v => { boxes = Math.max(boxes, v.boxes || 0); motion = Math.max(motion, v.motion || 0); for (let i = 0; i < VISUAL_SIZE; i++) { out[i] = Math.max(out[i], v.cells[i] || 0); } }); return { cells: out, boxes, motion, held: visualHistory.length > 0 }; } function combineRecentAudio() { /* 日本語: 直近8秒のauditory tracesをまとめる。 最大値合成にすることで、短い発声もLearnに入る。 English: Combines auditory traces from the recent 8 seconds. Max-composition lets short utterances enter Learn. */ const now = Date.now(); audioHistory = audioHistory.filter(a => now - a.t <= LEARN_WINDOW_MS); const out = Array(AUDIO_SIZE).fill(0); let energy = 0; audioHistory.forEach(a => { energy = Math.max(energy, a.energy || 0); for (let i = 0; i < AUDIO_SIZE; i++) { out[i] = Math.max(out[i], a.bands[i] || 0); } }); return { bands: out, energy, held: audioHistory.length > 0 }; } function sampleField(showLog = true, useRecentWindow = false) { /* 日本語: visual_delta + auditory_delta + text_delta を同時に採取し、 ひとつの双極パターン x にする。 English: Samples visual_delta + auditory_delta + text_delta together and converts them into one bipolar pattern x. */ /* 日本語: useRecentWindow=true の時は、直近8秒間の履歴を使う。 Learnはこのモードを使う。 Recallは現在cueを見るので、通常は現在値+短期保持を見る。 English: When useRecentWindow=true, the recent 8-second history is used. Learn uses this mode. Recall usually uses the current cue plus short hold. */ const now = Date.now(); let visual; let visualBoxesForTrace = visualBoxes.length; let visualHeldFlag = false; let visualMotionForTrace = 0; let auditory; let audioHeldFlag = false; let audioEnergyForTrace = 0; if (useRecentWindow) { const recentVisual = combineRecentVisual(); const recentAudio = combineRecentAudio(); visual = recentVisual.cells; visualBoxesForTrace = recentVisual.boxes; visualHeldFlag = recentVisual.held; visualMotionForTrace = recentVisual.motion; auditory = recentAudio.bands; audioHeldFlag = recentAudio.held; audioEnergyForTrace = recentAudio.energy; } else { visual = (now < heldVisualUntil) ? heldVisualCells.slice(0, VISUAL_SIZE) : ((visualBoxes && visualBoxes.length > 0) ? visualCells.slice(0, VISUAL_SIZE) : Array(VISUAL_SIZE).fill(0)); visualHeldFlag = now < heldVisualUntil; visualMotionForTrace = average(visual); auditory = (now < heldAudioUntil) ? heldAudioBands.slice(0, AUDIO_SIZE) : lastAudioBands.slice(0, AUDIO_SIZE); audioHeldFlag = now < heldAudioUntil; audioEnergyForTrace = average(auditory); } while (visual.length < VISUAL_SIZE) visual.push(0); while (auditory.length < AUDIO_SIZE) auditory.push(0); const text = makeTextDelta(document.getElementById("textInput").value); const merged = visual.concat(auditory, text); const pattern = toBipolar(merged); currentTrace = { visual_delta: visual, auditory_delta: auditory, text_delta: text, pattern: pattern, raw: { visual_boxes: visualBoxesForTrace, visual_motion: visualMotionForTrace, audio_energy: audioEnergyForTrace, text_pressure: average(text), visual_held: visualHeldFlag, audio_held: audioHeldFlag, used_recent_window: useRecentWindow } }; document.getElementById("visualMeter").textContent = currentTrace.raw.visual_motion.toFixed(3); document.getElementById("audioMeter").textContent = currentTrace.raw.audio_energy.toFixed(3); document.getElementById("textMeter").textContent = currentTrace.raw.text_pressure.toFixed(3); if (showLog) { log("Sampled current field. / 現在場を採取しました。\n\n" + summarizeTrace(currentTrace)); } return currentTrace; } function makeTextDelta(text) { /* 日本語: text_delta。 文字列を意味として読まず、文字コードと位置から粗い32値へ変換する。 これにより、テキストだけでも cue になる。 English: text_delta. Converts a string into 32 rough values using character codes and positions, without reading the meaning. This lets text alone become a cue. */ const out = Array(TEXT_SIZE).fill(0); const chars = Array.from(text || ""); if (chars.length === 0) return out; chars.forEach((ch, i) => { const code = ch.codePointAt(0); const idx1 = (code + i * 13) % TEXT_SIZE; const idx2 = (code * 7 + i * 5) % TEXT_SIZE; out[idx1] += 1.0; out[idx2] += 0.45; }); const max = Math.max(...out, 1); return out.map(v => clamp(v / max)); } function toBipolar(values) { const avg = average(values); return values.map(v => v >= avg ? 1 : -1); } function learnField() { /* 日本語: Learn時に、visual traces / waveform-spectrogram由来のauditory_delta / text_delta を 同時にアソシアトロン行列へ入れる。 English: On Learn, visual traces, auditory_delta from waveform/spectrogram, and text_delta are stored together into the Associatron matrix. */ const trace = sampleField(false, true); const note = document.getElementById("humanNote").value.trim(); const text = document.getElementById("textInput").value.trim(); associatronLearn(trace.pattern); memories.push({ id: Date.now(), note: note, text: text, trace: JSON.parse(JSON.stringify(trace)), learned_at: new Date().toLocaleString() }); renderMemoryList(); const modalityReport = makeModalityReport(trace); log( "Learned. / 学習しました。\n\n" + hardLearnWarning(trace) + modalityReport + "\n\n" + "Stored together / 同時に記憶:\n" + "- visual traces: " + (trace.raw.visual_motion > 0 ? "IN / 入った" : "ZERO / 入っていない") + "\n" + "- auditory waveform / spectrogram delta: " + (trace.raw.audio_energy > 0.002 ? "IN / 入った" : "ZERO / 入っていない") + "\n" + "- text_delta: " + (trace.raw.text_pressure > 0 ? "IN / 入った" : "ZERO / 入っていない") + "\n\n" + "Associatron memory matrix / アソシアトロン記憶行列:\n" + "Tij = Σ xi xj, Tii = 0\n\n" + summarizeTrace(trace) ); } function associatronLearn(x) { /* 日本語: アソシアトロン記憶行列への書き込み。 同時に立った痕跡 x の外積を T に加える。 T_ij += x_i x_j T_ii = 0 English: Writes into the Associatron memory matrix. It adds the outer product of the simultaneously active trace x to T. T_ij += x_i x_j T_ii = 0 */ for (let i = 0; i < TRACE_SIZE; i++) { for (let j = 0; j < TRACE_SIZE; j++) { if (i === j) T[i][j] = 0; else T[i][j] += x[i] * x[j]; } } } function recallMixedCue() { const seed = sampleField(false).pattern; runRecall(seed, "mixed cue / 混合cue"); } function recallTextOnly() { /* 日本語: テキストだけで想起する。 visual_delta と auditory_delta を0にし、text_deltaだけをcueにする。 English: Recall using text only. visual_delta and auditory_delta are set to zero, and only text_delta is used as cue. */ const text = makeTextDelta(document.getElementById("textInput").value); const visual = Array(VISUAL_SIZE).fill(0); const auditory = Array(AUDIO_SIZE).fill(0); const seed = toBipolar(visual.concat(auditory, text)); currentTrace = { visual_delta: visual, auditory_delta: auditory, text_delta: text, pattern: seed, raw: { visual_boxes: 0, visual_motion: 0, audio_energy: 0, text_pressure: average(text) } }; document.getElementById("visualMeter").textContent = "0.000"; document.getElementById("audioMeter").textContent = "0.000"; document.getElementById("textMeter").textContent = average(text).toFixed(3); runRecall(seed, "text only cue / テキストだけのcue"); } function actualCueName(trace, requestedName) { /* 日本語: ボタン名が mixed cue でも、実際に入っている要素が text だけなら mixed ではない。 ここで実際のcue構成を表示する。 English: Even if the button says mixed cue, it is not really mixed when only text is present. This function displays the actual cue composition. */ const parts = []; if (trace.raw.visual_motion > 0) parts.push("visual"); if (trace.raw.audio_energy > 0.002) parts.push("audio"); if (trace.raw.text_pressure > 0) parts.push("text"); if (parts.length === 0) return requestedName + " -> empty cue / 空cue"; if (parts.length === 1) return requestedName + " -> actually " + parts[0] + " only / 実際は" + parts[0] + "だけ"; return requestedName + " -> actually " + parts.join("+") + " / 実際は" + parts.join("+"); } function hardLearnWarning(trace) { /* 日本語: Learn時点で視覚・聴覚が入っていない場合、研究メモとしては重要なので強く表示する。 English: If visual/audio are missing at Learn time, show a strong warning because it matters for the experiment. */ const lines = []; const hasVisual = trace.raw.visual_motion > 0; const hasAudio = trace.raw.audio_energy > 0.002; const hasText = trace.raw.text_pressure > 0; if (!hasVisual || !hasAudio) { lines.push("IMPORTANT / 重要:"); lines.push("This Learn did not include all three modalities. / このLearnには3要素すべてが入っていません。"); if (!hasVisual) lines.push("- visual traces were zero / 視覚痕跡が0でした"); if (!hasAudio) lines.push("- auditory waveform/spectrogram was zero / 聴覚痕跡が0でした"); if (!hasText) lines.push("- text_delta was zero / テキスト痕跡が0でした"); lines.push(""); lines.push("For visual+audio+text learning, move in front of the camera, speak, then press Learn within about 8 seconds."); lines.push("視覚+聴覚+テキストで学習するには、カメラ前で動き、声を出し、約8秒以内にLearnを押してください。"); lines.push(""); } return lines.join("\n"); } function recalledPatternReport(pattern) { /* 日本語: 想起後patternのどの部分が立ち上がったかを粗く表示する。 current cue がtextだけでも、recalled pattern側にaudio/visualが戻ることがある。 v6: visual / audio / text のサイズを固定値ではなく定数から出す。 これで visual 96, audio 64, text 32 の表示が正しくなる。 English: Roughly displays which parts of the recalled pattern are active. Even if the current cue is text-only, audio/visual may appear in the recalled pattern. v6: Uses constants instead of fixed numbers, so visual 96, audio 64, text 32 are shown correctly. */ const visual = pattern.slice(0, VISUAL_SIZE).filter(v => v > 0).length; const audio = pattern.slice(VISUAL_SIZE, VISUAL_SIZE + AUDIO_SIZE).filter(v => v > 0).length; const text = pattern.slice(VISUAL_SIZE + AUDIO_SIZE, TRACE_SIZE).filter(v => v > 0).length; return [ "Recalled trace activity / 想起後の痕跡活動:", "- visual positive bits: " + visual + " / " + VISUAL_SIZE, "- audio positive bits: " + audio + " / " + AUDIO_SIZE, "- text positive bits: " + text + " / " + TEXT_SIZE ].join("\n"); } function clampedDifferenceReport(cuePattern, rawPattern, clampedPattern) { /* 日本語: 通常の想起で消えたcueを、cue保持型でどれだけ戻したかを表示する。 特に音が入力にあるのに消える問題を見る。 English: Shows how many cue bits were restored by clamped recall after plain recall erased them. Especially useful for audio disappearing even when it exists in the input. */ let restored = 0; let restoredVisual = 0; let restoredAudio = 0; let restoredText = 0; for (let i = 0; i < TRACE_SIZE; i++) { if (cuePattern[i] > 0 && rawPattern[i] < 0 && clampedPattern[i] > 0) { restored++; if (i < VISUAL_SIZE) restoredVisual++; else if (i < VISUAL_SIZE + AUDIO_SIZE) restoredAudio++; else restoredText++; } } return [ "Cue clamp / cue保持:", "- restored positive cue bits: " + restored, "- visual restored: " + restoredVisual, "- audio restored: " + restoredAudio, "- text restored: " + restoredText ].join("\n"); } function runRecall(seed, modeName) { /* 日本語: v9修正: v8では recalledRaw をログで使っていたが、runRecall内で作っていなかった。 そのため、Recallボタンを押しても結果が表示されないことがあった。 ここでは、 1) 通常のアソシアトロン想起 raw 2) positive cue を保持した clamped recall を明示的に分けて作る。 English: v9 fix: v8 used recalledRaw in the log but did not create it inside runRecall. That could stop Recall output. Here we explicitly create: 1) raw Associatron recall 2) clamped recall that preserves positive cue bits */ if (memories.length === 0) { log("No memory yet. / まだ記憶がありません。"); return; } const recalledRaw = associatronRecall(seed, 5); const recalled = clampPositiveCue(seed, recalledRaw); const energy = associatronEnergy(recalled); const scored = memories.map(m => ({ memory: m, score: similarity(recalled, m.trace.pattern) })).sort((a, b) => b.score - a.score); const best = scored[0]; let leak = "..."; const humanText = best.memory.text || best.memory.note || ""; if (best.score > 0.88) leak = humanText || "(trace rises, no text)"; else if (best.score > 0.70) leak = partialLeak(humanText); else if (best.score > 0.55) leak = "fragment..."; else leak = "..."; log( "Recall mode / 想起モード: " + actualCueName(currentTrace, modeName) + "\n" + "Recall leak / 想起の漏れ: " + leak + "\n\n" + makeModalityReport(currentTrace) + "\n\n" + recalledPatternReport(recalled) + "\n\n" + learnedTraceActivity(best.memory.trace) + "\n\n" + clampedDifferenceReport(currentTrace.pattern, recalledRaw, recalled) + "\n\n" + "Interpretation / 読み方:\n" + "current cue shows what is being used now. / current cue は今使っているcueです。\n" + "raw recall is sign(T xcue). / raw recall は sign(T xcue) です。\n" + "recalled pattern keeps positive cue bits and completes missing parts. / recalled pattern はpositive cueを保持し、不足部分を補完します。\n\n" + "best similarity / 最も近い痕跡: " + best.score.toFixed(3) + "\n" + "energy V / エネルギーV: " + energy.toFixed(3) + "\n" + "human note / 人間メモ: " + (best.memory.note || "(none)") + "\n" + "human text / 人間テキスト: " + (best.memory.text || "(none)") + "\n" + "learned_at / 学習時刻: " + best.memory.learned_at + "\n\n" + "Associatron recall / アソシアトロン想起:\n" + "raw: y = sign(T xcue)\n" + "shown: y = clamp_positive(xcue, raw)\n" + "V = -1/2 Σ Tij xi xj\n\n" + "current cue / 現在cue:\n" + summarizeTrace(currentTrace) + "\n\n" + "recalled pattern / 想起後pattern:\n" + JSON.stringify({ visual_first24: recalled.slice(0, 24), audio_first16: recalled.slice(VISUAL_SIZE, VISUAL_SIZE + 16), text_first16: recalled.slice(VISUAL_SIZE + AUDIO_SIZE, VISUAL_SIZE + AUDIO_SIZE + 16), raw_audio_first16_before_clamp: recalledRaw.slice(VISUAL_SIZE, VISUAL_SIZE + 16) }, null, 2) ); /* 日本語: Recall後も次のvisual traceが取れるように、ライブセンサー表示を再開状態に戻す。 Recallは記憶を読む操作であり、カメラ差分を停止させない。 English: After Recall, restore live sensor display so new visual traces can be captured. Recall reads memory; it must not stop camera difference tracking. */ resumeLiveSensorsAfterCueOperation(); } function clampPositiveCue(cuePattern, recalledPattern) { /* 日本語: positive cue保持。 いま入力に存在する +1 のcueは、想起後に消さない。 記憶場は、cueに足りない部分を補うために使う。 English: Positive cue clamp. +1 cue bits that exist in the input are not erased after recall. The memory field is used to complete missing parts. */ const out = recalledPattern.slice(0, TRACE_SIZE); for (let i = 0; i < TRACE_SIZE; i++) { if (cuePattern[i] > 0) out[i] = 1; } return out; } function associatronRecall(seed, steps) { let x = seed.slice(); for (let s = 0; s < steps; s++) { const next = Array(TRACE_SIZE).fill(-1); for (let i = 0; i < TRACE_SIZE; i++) { let sum = 0; for (let j = 0; j < TRACE_SIZE; j++) { sum += T[i][j] * x[j]; } next[i] = sum >= 0 ? 1 : -1; } x = next; } return x; } function associatronEnergy(x) { let sum = 0; for (let i = 0; i < TRACE_SIZE; i++) { for (let j = 0; j < TRACE_SIZE; j++) { sum += T[i][j] * x[i] * x[j]; } } return -0.5 * sum; } function resumeLiveSensorsAfterCueOperation() { /* 日本語: Recall や「一旦記憶を消す」の後でも、カメラ差分とマイク解析を止めない。 以前の版では、一時cueを消したあとに visual trace が再び立ち上がらないことがあった。 ここでは、表示用の一時状態だけを整え、次フレームから新しい差分を採取できるようにする。 English: Camera difference and microphone analysis must not stop after Recall or "clear temporary trace". Older versions could fail to produce new visual traces after clearing temporary cues. This resets only temporary display state so the next frames can produce new deltas again. */ sensorCueCleared = false; visualCells = Array(VISUAL_SIZE).fill(0); visualBoxes = []; stableBoxes = []; heldVisualCells = Array(VISUAL_SIZE).fill(0); heldVisualUntil = 0; heldAudioBands = Array(AUDIO_SIZE).fill(0); heldAudioUntil = 0; /* 日本語: previousFrame は消しすぎない。 完全に消すと、次の1フレームで画面全体が差分扱いになりやすい。 ただしフレームが止まっている場合に備え、次の updateVisualTracking が自然に上書きする。 English: Do not aggressively clear previousFrame. If it is fully cleared, the next frame may become a full-screen difference. updateVisualTracking will naturally refresh it. */ drawVisualDeltaMap(); updateLearnReadiness(); } function clearSensorCue() { /* 日本語: テキストだけで試したい時のために、視覚・聴覚cueを一時的に0へ落とす。 カメラやマイクを停止するわけではない。 English: Temporarily sets visual/audio cue to zero for text-only tests. This does not stop the camera or microphone. */ sensorCueCleared = false; visualCells = Array(VISUAL_SIZE).fill(0); visualBoxes = []; heldVisualCells = Array(VISUAL_SIZE).fill(0); heldVisualUntil = 0; lastAudioBands = Array(AUDIO_SIZE).fill(0); heldAudioBands = Array(AUDIO_SIZE).fill(0); heldAudioUntil = 0; visualHistory = []; audioHistory = []; drawVisualDeltaMap(); document.getElementById("visualMeter").textContent = "0.000"; document.getElementById("audioMeter").textContent = "0.000"; resumeLiveSensorsAfterCueOperation(); log( "Temporary visual/audio trace cleared. Live sensors are still running.\n" + "一旦記憶を消しました。ライブの視覚差分・聴覚解析は動いたままです。\n\n" + "Note: learned Associatron memory is not erased.\n" + "注意: Learn済みのアソシアトロン記憶は消していません。" ); } function makeModalityReport(trace) { /* 日本語: Learn/Recall時に、どの感覚が実際に入っているかを人間に見せる。 これが無いと mixed cue と表示されても、実際は text only ということが起きる。 English: Shows which modalities are actually present during Learn/Recall. Without this, it may say mixed cue even when it is actually text only. */ const lines = []; lines.push("Modality report / 入力要素の確認:"); if (trace.raw.visual_motion > 0) { lines.push("- visual traces: present / 視覚痕跡あり" + (trace.raw.visual_held ? " (held / 保持)" : "")); } else { lines.push("- visual traces: zero / 視覚痕跡なし"); } if (trace.raw.audio_energy > 0.002) { lines.push("- auditory waveform/spectrogram: present / 聴覚痕跡あり" + (trace.raw.audio_held ? " (held / 保持)" : "")); } else { lines.push("- auditory waveform/spectrogram: zero / 聴覚痕跡なし"); } if (trace.raw.text_pressure > 0) { lines.push("- text_delta: present / テキスト痕跡あり"); } else { lines.push("- text_delta: zero / テキスト痕跡なし"); } if (trace.raw.visual_motion === 0 && trace.raw.audio_energy <= 0.002 && trace.raw.text_pressure > 0) { lines.push(""); lines.push("WARNING / 注意: this is effectively text-only. / 実質テキストだけです。"); } return lines.join("\n"); } function makeZeroMatrix(n) { return Array.from({ length: n }, () => Array(n).fill(0)); } function makeEmptyTrace() { return { visual_delta: Array(VISUAL_SIZE).fill(0), auditory_delta: Array(AUDIO_SIZE).fill(0), text_delta: Array(TEXT_SIZE).fill(0), pattern: Array(TRACE_SIZE).fill(-1), raw: { visual_boxes: 0, visual_motion: 0, audio_energy: 0, text_pressure: 0 } }; } function resetMemory() { T = makeZeroMatrix(TRACE_SIZE); memories = []; currentTrace = makeEmptyTrace(); sensorCueCleared = false; visualHistory = []; audioHistory = []; heldVisualCells = Array(VISUAL_SIZE).fill(0); heldVisualUntil = 0; heldAudioBands = Array(AUDIO_SIZE).fill(0); heldAudioUntil = 0; document.getElementById("memoryList").innerHTML = ""; log("Reset. / 初期化しました。"); } function renderMemoryList() { const box = document.getElementById("memoryList"); box.innerHTML = ""; memories.slice().reverse().forEach((m, idx) => { const div = document.createElement("div"); div.className = "memoryItem"; div.innerHTML = "<b>trace " + (memories.length - idx) + "</b><br>" + "note: " + escapeHtml(m.note || "(none)") + "<br>" + "text: " + escapeHtml(m.text || "(none)") + "<br>" + "learned_at: " + escapeHtml(m.learned_at); box.appendChild(div); }); } function summarizeTrace(t) { /* 日本語: pattern は 192個。 0〜95 が visual、96〜159 が audio、160〜191 が text。 textだけのcueでは visual/audio は -1 が多くなる。 そのため、3分割して表示する。 English: The pattern has 192 values. 0..95 are visual, 96..159 are audio, and 160..191 are text. In text-only cues, visual/audio mostly become -1. Therefore the pattern is displayed in three segments. */ return JSON.stringify({ raw: t.raw, visual_delta_first24: t.visual_delta.slice(0, 24), auditory_delta_first16: t.auditory_delta.slice(0, 16), text_delta_first12: t.text_delta.slice(0, 12), pattern_visual_first24: t.pattern.slice(0, 24), pattern_audio_first16: t.pattern.slice(VISUAL_SIZE, VISUAL_SIZE + 16), pattern_text_first16: t.pattern.slice(VISUAL_SIZE + AUDIO_SIZE, VISUAL_SIZE + AUDIO_SIZE + 16) }, null, 2); } function learnedTraceActivity(trace) { /* 日本語: 最も近い記憶そのものに、visual/audio/text がどれだけ入っていたかを見る。 Recall後にaudioが消えるのか、そもそもLearn時にaudioが弱かったのかを分ける。 English: Shows how much visual/audio/text existed in the closest learned trace itself. This separates "audio disappeared after recall" from "audio was weak at learning time". */ if (!trace || !trace.pattern) return "Learned trace activity / 学習痕跡活動: none"; const p = trace.pattern; const visual = p.slice(0, VISUAL_SIZE).filter(v => v > 0).length; const audio = p.slice(VISUAL_SIZE, VISUAL_SIZE + AUDIO_SIZE).filter(v => v > 0).length; const text = p.slice(VISUAL_SIZE + AUDIO_SIZE, TRACE_SIZE).filter(v => v > 0).length; return [ "Closest learned trace activity / 最も近い学習痕跡:", "- visual positive bits: " + visual + " / " + VISUAL_SIZE, "- audio positive bits: " + audio + " / " + AUDIO_SIZE, "- text positive bits: " + text + " / " + TEXT_SIZE ].join("\n"); } function similarity(a, b) { let dot = 0; for (let i = 0; i < a.length; i++) dot += a[i] * b[i]; return (dot / a.length + 1) / 2; } function partialLeak(text) { if (!text) return "trace..."; const chars = Array.from(text); if (chars.length <= 2) return chars[0] + "..."; return chars.slice(0, Math.ceil(chars.length * 0.45)).join("") + "..."; } function average(arr) { if (!arr || arr.length === 0) return 0; return arr.reduce((a, b) => a + b, 0) / arr.length; } function clamp(v) { return Math.max(0, Math.min(1, v)); } function escapeHtml(s) { return String(s).replace(/[&<>"']/g, c => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#039;" }[c])); } function log(msg) { document.getElementById("log").textContent = msg; } /* 日本語: ページを開いた直後に待機表示を描く。 これで黒画面だけに見えない。 English: Draws waiting displays immediately after page load. This avoids an empty black-looking page. */ drawWaitingVisual(); drawWaitingAudio(); drawVisualDeltaMap(); updateLearnReadiness(); </script> <footer class="copyrightBox"> <b>Copyright notice / 著作権表示</b><br> Copyright © 2026 C-sideLabo Yukihiro Watanabe. All rights reserved.<br> This Atra / Associatron research demo is not licensed for unauthorized redistribution, modified redistribution, or commercial use.<br> この Atra / Associatron 研究デモは、無断転載・無断改変配布・商用利用を許可していません。 </footer> </body> </html>



####################  html  ######################

説明は、次のブログに記載します。(ダブルクリックでは立ち上がりません)








---------------------Research Note and Attribution Notice-----------------------
本ブログに含まれる Atra の一人称自律、差分、carry、field、trace、dream slack、外部LLMの翻訳層、非単調な漏れ、およびそれらの関係構造に関する設計記述は、c-side研究所による継続研究メモです。引用・参照・要約・翻案を行う場合は、出典を明記してください。

The design descriptions in this blog concerning Atra’s first-person autonomy, differences, carry, field, trace, dream slack, the translation layer of external LLMs, nonmonotonic leakage, and the relational structure among these elements are ongoing research notes by c-side Research Institute. If you quote, refer to, summarize, or adapt them, please clearly indicate the source.




0 件のコメント:

コメントを投稿

Atra Emotions_Conditions 感情・状態

 -----------------C++------------------ struct EmotionsConditionsNow { // Unpredictability double input_irregularity_now = 0.0 ; ...