{"id":4619,"date":"2025-04-14T13:05:06","date_gmt":"2025-04-14T17:05:06","guid":{"rendered":"https:\/\/faculty.fiu.edu\/~theobald\/?page_id=4619"},"modified":"2026-10-16T10:34:27","modified_gmt":"2026-10-16T14:34:27","slug":"hearing","status":"publish","type":"page","link":"https:\/\/faculty.fiu.edu\/~theobald\/fun\/neurobiology-fun\/hearing\/","title":{"rendered":"Hearing"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"4619\" class=\"elementor elementor-4619\">\n\t\t\t\t\t\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-f7f4ee4 elementor-section-full_width elementor-section-stretched elementor-section-height-default elementor-section-height-default\" data-id=\"f7f4ee4\" data-element_type=\"section\" data-settings=\"{&quot;stretch_section&quot;:&quot;section-stretched&quot;}\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-71d0849\" data-id=\"71d0849\" data-element_type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t\t\t<div class=\"elementor-element elementor-element-1de6251 elementor-widget__width-inherit elementor-widget elementor-widget-html\" data-id=\"1de6251\" data-element_type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t<!-- Generated by hearing\/generate-localization-hh-wordpress.py.\nCopy this entire fragment into a WordPress Custom HTML block.\nRegenerate: python3 hearing\/generate-localization-hh-wordpress.py\nOptional: --assets-url URL --index-url URL\nNavigation is copied from the local simulator unless --index-url is supplied.\nShared dependency: neuron.js imports hh.js and bezier.js from the same directory.\n-->\n<div data-neuro-hearing-embed>\n<template>\n<style>\n:host { all: initial; display: block; }\n\n:host { color-scheme:dark; --bg:#0b101b; --fg:#e6e8ee; --muted:#a6afc2; }\n* { box-sizing:border-box; }\n.simulator { margin:0; background:var(--bg); color:var(--fg); font:14px\/1.5 system-ui,sans-serif; }\nmain { display:grid; grid-template-columns:220px minmax(0,1fr); gap:32px; max-width:1600px; margin:auto; padding:24px; }\naside { padding-top:16px; } a { color:var(--muted); text-decoration:none; font-size:13px; } a:hover { color:var(--fg); }\n.control { display:block; margin:26px 0; } .control span { display:flex; justify-content:space-between; gap:8px; }\ninput[type=range] { width:100%; margin:10px 0 0; accent-color:#a7c7ff; }\noutput { font-variant-numeric:tabular-nums; white-space:nowrap; }\nfieldset { border:0; padding:0; margin:26px 0; display:flex; gap:18px; }\nlegend { padding:0; margin-bottom:8px; } fieldset label { cursor:pointer; } input[type=checkbox] { accent-color:#a7c7ff; }\n.left { color:#80c9ff; } .right { color:#e5b2ff; }\n.buttons { display:flex; gap:8px; }\nbutton { color:var(--fg); background:#1b2435; border:1px solid #45516a; border-radius:5px; padding:6px 12px; font:inherit; cursor:pointer; }\nbutton:hover { background:#30415e; } :focus-visible { outline:2px solid #80c9ff; outline-offset:3px; }\n.apparatus { min-width:0; } canvas { display:block; width:100%; } #circuit { aspect-ratio:1000\/530; } #scope { height:300px; margin-top:8px; }\n@media(max-width:760px) { main { grid-template-columns:1fr; gap:12px; padding:16px; } aside { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:12px 24px; padding:0; } aside a { grid-column:1\/-1; } .control, fieldset { margin:0; } .buttons { align-items:center; } #scope { height:250px; } }\n\n<\/style>\n<div class=\"simulator\">\n<main aria-label=\"Sound localization experiment\">\n<aside aria-label=\"Experimental controls\">\n<a href=\"..\/index.html\">\u2190 Simulations<\/a>\n<label class=\"control\" for=\"strength\"><span>Synaptic strength <output id=\"strengthValue\"><\/output><\/span><input id=\"strength\" type=\"range\" min=\"0\" max=\"30\" step=\"0.5\" value=\"10\"><\/label>\n<label class=\"control\" for=\"decay\"><span>EPSP duration \u03c4 <output id=\"decayValue\"><\/output><\/span><input id=\"decay\" type=\"range\" min=\"0.2\" max=\"20\" step=\"0.1\" value=\"1\"><\/label>\n<label class=\"control\" for=\"speed\"><span>Playback speed <output id=\"speedValue\"><\/output><\/span><input id=\"speed\" type=\"range\" min=\"0.25\" max=\"4\" step=\"0.25\" value=\"1\"><\/label>\n<fieldset><legend>Inputs<\/legend><label class=\"left\"><input id=\"leftEar\" type=\"checkbox\" checked aria-label=\"Left input\"> L<\/label><label class=\"right\"><input id=\"rightEar\" type=\"checkbox\" checked aria-label=\"Right input\"> R<\/label><\/fieldset>\n<div class=\"buttons\"><button id=\"pause\">Pause<\/button><button id=\"reset\">Reset<\/button><\/div>\n<\/aside>\n<div class=\"apparatus\">\n<canvas id=\"circuit\" width=\"1000\" height=\"530\" aria-label=\"Sound field, ears, axons, and five selectable neurons\"><\/canvas>\n<canvas id=\"scope\" width=\"1000\" height=\"300\" aria-label=\"Membrane voltage in millivolts versus simulation time in milliseconds\"><\/canvas>\n<\/div>\n<\/main>\n\n<\/div>\n<\/template>\n<\/div>\n<script>\n(() => {\n  \/\/ Capture this particular block synchronously before any asynchronous imports.\n  \/\/ Each pasted copy owns a separate shadow tree and private simulation state.\n  const host = document.currentScript.previousElementSibling;\n  const template = host.querySelector('template');\n  const shadow = host.attachShadow({ mode: 'open' });\n  shadow.append(template.content);\n  template.remove();\n  (async () => {\n  const moduleBase = new URL(\"\/~theobald\/wp-content\/plugins\/neuro_demos\/assets\/\", window.location.origin);\n  const [\n  { BezierCurve },\n  { Neuron },\n  { NeuronRenderer, RingBuffer, resizeCanvasToCSS, vcol }\n  ] = await Promise.all([\n  import(new URL(\"bezier.js\", moduleBase).href),\n  import(new URL(\"neuron.js\", moduleBase).href),\n  import(new URL(\"render.js\", moduleBase).href)\n  ]);\n\n\/\/ ---- localization-hh-model.js (generated from the local source) ----\n\/\/ Fixed-step model, independent of the DOM and the playback clock.\n\/\/ HH units: volts, seconds, amperes. UI conductance: nS; time: ms.\nconst DT = 0.01e-3;\nconst SAMPLE_EVERY = 10; \/\/ Record every 0.1 ms, including the initial state.\nconst TAPS = [12, 22, 32, 42, 52];\nconst EARS = [[100, 210], [900, 210]];\nconst SOUND_SPEED = 25; \/\/ diagram units\/ms; deliberately enlarged delays\nconst DEFAULTS = { strength: 10, decay: 1 };\nconst MIN_DURATION = 120; \/\/ ms; extend long-decay trials to include recovery\n\nclass LocalizationTrial {\n  constructor({ source = [500, 70], strength = DEFAULTS.strength,\n                decay = DEFAULTS.decay, ears = [true, true] } = {}) {\n    this.source = [...source];\n    this.strength = strength;\n    this.decay = decay;\n    this.earTimes = EARS.map(([x, y], i) => ears[i]\n      ? Math.hypot(source[0] - x, source[1] - y) \/ SOUND_SPEED : Infinity);\n    this.duration = Math.max(MIN_DURATION, Math.ceil((Math.max(0,\n      ...this.earTimes.filter(Number.isFinite)) + 55 + 6 * decay) \/ 20) * 20);\n    this.steps = 0;\n    this.time = 0;\n    this.lines = [0, 1].map(side => {\n      const neuron = new Neuron({ dt: DT });\n      const y = side === 0 ? 300 : 350;\n      const axon = neuron.addAxon({ num_pts: 65,\n        cpts: side === 0 ? [[100, y], [900, y]] : [[900, y], [100, y]],\n        draw_rad: 4 });\n      return { neuron, axon };\n    });\n    this.detectors = TAPS.map(tap => {\n      const neuron = new Neuron({ dt: DT });\n      const soma = neuron.addSoma([100 + 800 * tap \/ 64, 450], 18);\n      return { neuron, soma, g: [0, 0], arrivals: [[], []], spikes: [],\n        trace: [soma.V], peak: soma.V };\n    });\n    this.times = [0];\n  }\n\n  get done() { return this.steps >= Math.round(this.duration \/ (DT * 1000)); }\n\n  \/\/ Continuous scopes keep integrating after the trial and supply bounded storage.\n  \/\/ Defaults retain the finite-trial interface used by the numerical checks.\n  step({ continueAfterEnd = false, record = true } = {}) {\n    if (this.done && !continueAfterEnd) return;\n    const previous = this.lines.map(({ axon }, side) =>\n      TAPS.map(tap => axon[side === 0 ? tap : 64 - tap].V));\n    for (let side = 0; side < 2; side++) {\n      const line = this.lines[side];\n      const age = this.time - this.earTimes[side];\n      \/\/ A brief sensory stimulus initiates a genuine propagating HH spike.\n      const current = age >= 0 && age < 1 ? 30e-6 * line.axon[0].p.area_cm2 : 0;\n      line.neuron.step(DT, { [line.axon[0].name]: current });\n    }\n    const nextTime = (this.steps + 1) * DT * 1000;\n    const attenuation = Math.exp(-DT * 1000 \/ this.decay);\n    for (let i = 0; i < 5; i++) {\n      const d = this.detectors[i];\n      for (let side = 0; side < 2; side++) {\n        const tap = side === 0 ? TAPS[i] : 64 - TAPS[i];\n        const voltage = this.lines[side].axon[tap].V;\n        \/\/ Zero crossing only detects a spike already generated by HH.\n        if (previous[side][i] < 0 && voltage >= 0) {\n          d.g[side] += this.strength * 1e-9;\n          d.arrivals[side].push(nextTime);\n        }\n      }\n      const before = d.soma.V;\n      const current = (d.g[0] + d.g[1]) * (0 - before); \/\/ excitatory Erev = 0 V\n      d.neuron.step(DT, { [d.soma.name]: current });\n      if (before < 0 && d.soma.V >= 0) d.spikes.push(nextTime);\n      d.peak = Math.max(d.peak, d.soma.V);\n      d.g[0] *= attenuation;\n      d.g[1] *= attenuation;\n    }\n    this.steps++;\n    this.time = nextTime;\n    if (record && this.steps % SAMPLE_EVERY === 0) {\n      this.times.push(this.time);\n      for (const d of this.detectors) d.trace.push(d.soma.V);\n    }\n  }\n\n  run() { while (!this.done) this.step(); return this; }\n  summary() {\n    return { earTimes: this.earTimes, fired: this.detectors.flatMap((d, i) => d.spikes.length ? [i + 1] : []),\n      peaks: this.detectors.map(d => d.peak * 1000),\n      arrivals: this.detectors.map(d => d.arrivals), spikes: this.detectors.map(d => d.spikes) };\n  }\n}\n\n\/\/ Wall time controls only how many identical fixed model steps are displayed.\nclass Playback {\n  constructor(trial) { this.trial = trial; this.pending = 0; this.paused = false; }\n  advance(wallMilliseconds, speed) {\n    if (this.paused || this.trial.done) return;\n    this.pending += wallMilliseconds * 0.02 * speed;\n    let budget = 1000; \/\/ Bound frame work without dropping model steps.\n    while (this.pending + 1e-12 >= DT * 1000 && !this.trial.done && budget-- > 0) {\n      this.trial.step();\n      this.pending = Math.max(0, this.pending - DT * 1000);\n    }\n  }\n}\n\n\/\/ ---- localization-hh-scope.js (generated from the local source) ----\nconst WINDOW_MS = 120;\nconst SAMPLE_MS = DT * 1000 * SAMPLE_EVERY;\nconst CAPACITY = Math.round(WINDOW_MS \/ SAMPLE_MS) + 1;\n\n\/\/ A continuous simulation clock and five bounded recordings, independent of\n\/\/ selection and of the per-sound trial clock. New trials still start from rest.\nclass ScrollingRecording {\n  constructor() { this.reset(); }\n\n  reset() {\n    this.model = new LocalizationTrial({ ears: [false, false] });\n    this.trial = null;\n    this.steps = 0;\n    this.pending = 0;\n    this.paused = false;\n    this.origin = 0;\n    this.lastSample = 0;\n    this.markers = [];\n    this.arrivalCounts = Array.from({ length: 5 }, () => [0, 0]);\n    this.buffers = this.model.detectors.map(d => {\n      const buffer = new RingBuffer(CAPACITY);\n      buffer.fill(d.soma.V); \/\/ Flat resting prehistory, as in the other scopes.\n      return buffer;\n    });\n  }\n\n  get time() { return this.steps * DT * 1000; }\n\n  start(options) {\n    this.model = this.trial = new LocalizationTrial(options);\n    this.origin = this.time;\n    this.arrivalCounts = Array.from({ length: 5 }, () => [0, 0]);\n    \/\/ Preserve the scope clock and history across sound events.\n  }\n\n  step() {\n    this.model.step({ continueAfterEnd: true, record: false });\n    this.steps++;\n    for (let i = 0; i < 5; i++) {\n      for (let side = 0; side < 2; side++) {\n        const arrivals = this.model.detectors[i].arrivals[side];\n        if (arrivals.length > this.arrivalCounts[i][side]) {\n          this.markers.push({ neuron: i, side, time: this.origin + arrivals.at(-1) });\n          this.arrivalCounts[i][side] = arrivals.length;\n        }\n      }\n    }\n    if (this.steps % SAMPLE_EVERY === 0) {\n      this.lastSample = this.time;\n      this.model.detectors.forEach((d, i) => this.buffers[i].push(d.soma.V));\n      this.markers = this.markers.filter(m => m.time >= this.time - WINDOW_MS);\n    }\n  }\n\n  advance(wallMilliseconds, speed) {\n    if (this.paused) return;\n    this.pending += wallMilliseconds * 0.02 * speed;\n    let budget = 1000;\n    while (this.pending + 1e-12 >= DT * 1000 && budget-- > 0) {\n      this.step();\n      this.pending = Math.max(0, this.pending - DT * 1000);\n    }\n  }\n}\n\n\/\/ ---- localization-hh.js (generated from the local source) ----\nconst $ = id => shadow.getElementById(id);\nconst circuit = $('circuit'), scene = circuit.getContext('2d');\nconst scope = $('scope'), graph = scope.getContext('2d');\nconst renderer = new NeuronRenderer();\nconst colors = ['#80c9ff', '#e5b2ff'];\nconst identity = { map: (x, y) => [x, y], scale: 1 };\nconst recording = new ScrollingRecording();\nlet trial = null;\nconst resting = recording.model;\nlet selected = 2, lastFrame = null;\n\n\/\/ Cache the Bezier geometry, not neuronal activity. These branches are schematic\n\/\/ synaptic connections; axon compartment colors come directly from the HH model.\nconst branches = resting.detectors.map(d => [0, 1].map(side => {\n  const x = d.soma.pos[0], dx = side === 0 ? -22 : 22;\n  return new BezierCurve([[x, side === 0 ? 300 : 350], [x + dx, 390],\n    [x + dx, 410], [x + dx * 0.65, 434]])\n    .pointAtFraction(Array.from({ length: 28 }, (_, i) => i \/ 27));\n}));\n\nfunction label(ctx, text, x, y, color = '#a6afc2', size = 14, align = 'left') {\n  ctx.fillStyle = color; ctx.font = `${size}px system-ui`;\n  ctx.textAlign = align; ctx.fillText(text, x, y);\n}\nfunction line(ctx, points, color, width = 1) {\n  ctx.beginPath();\n  points.forEach(([x, y], i) => i ? ctx.lineTo(x, y) : ctx.moveTo(x, y));\n  ctx.strokeStyle = color; ctx.lineWidth = width; ctx.stroke();\n}\nfunction circle(ctx, x, y, r, color, fill = false) {\n  ctx.beginPath(); ctx.arc(x, y, r, 0, 2 * Math.PI);\n  if (fill) { ctx.fillStyle = color; ctx.fill(); }\n  else { ctx.strokeStyle = color; ctx.lineWidth = 2; ctx.stroke(); }\n}\n\nfunction updateLabels() {\n  $('strengthValue').value = `${Number($('strength').value).toFixed(1)} nS`;\n  $('decayValue').value = `${Number($('decay').value).toFixed(1)} ms`;\n  $('speedValue').value = `${Number($('speed').value).toFixed(2)}\u00d7`;\n}\nfunction launch(source) {\n  recording.start({ source, strength: +$('strength').value,\n    decay: +$('decay').value, ears: [$('leftEar').checked, $('rightEar').checked] });\n  trial = recording.trial;\n  recording.paused = false;\n  $('pause').disabled = false; $('pause').textContent = 'Pause';\n  lastFrame = null;\n}\nfunction reset() {\n  recording.reset(); trial = null;\n  lastFrame = null;\n  $('pause').disabled = false; $('pause').textContent = 'Pause';\n}\n\nfor (const id of ['strength', 'decay', 'leftEar', 'rightEar']) {\n  $(id).addEventListener(id.endsWith('Ear') ? 'change' : 'input', () => {\n    updateLabels(); if (trial) launch(trial.source);\n  });\n}\n$('speed').addEventListener('input', updateLabels);\n$('pause').addEventListener('click', () => {\n  recording.paused = !recording.paused;\n  $('pause').textContent = recording.paused ? 'Resume' : 'Pause';\n});\n$('reset').addEventListener('click', reset);\nfunction circuitTarget(e) {\n  const rect = circuit.getBoundingClientRect();\n  const x = (e.clientX - rect.left) * 1000 \/ rect.width;\n  const y = (e.clientY - rect.top) * 530 \/ rect.height;\n  const neuron = resting.detectors.findIndex(d =>\n    Math.hypot(d.soma.pos[0] - x, d.soma.pos[1] - y) <= d.soma.draw_rad);\n  return { x, y, neuron, sound: x >= 0 && x <= 1000 && y >= 0 && y < EARS[0][1] - 25 };\n}\ncircuit.addEventListener('click', e => {\n  const hit = circuitTarget(e);\n  if (hit.neuron >= 0) selected = hit.neuron;\n  else if (hit.sound) launch([hit.x, hit.y]);\n});\ncircuit.addEventListener('pointermove', e => {\n  const hit = circuitTarget(e);\n  circuit.style.cursor = hit.neuron >= 0 ? 'pointer' : hit.sound ? 'crosshair' : 'default';\n});\ncircuit.addEventListener('pointerleave', () => { circuit.style.cursor = 'default'; });\n\nfunction drawCircuit() {\n  resizeCanvasToCSS(circuit, scene);\n  scene.setTransform(circuit.width \/ 1000, 0, 0, circuit.height \/ 530, 0, 0);\n  scene.fillStyle = '#0b101b'; scene.fillRect(0, 0, 1000, 530);\n  const model = recording.model;\n  scene.save(); scene.beginPath(); scene.rect(0, 0, 1000, 238); scene.clip();\n  if (trial) {\n    const [x, y] = trial.source;\n    circle(scene, x, y, trial.time * SOUND_SPEED, '#d7e6ff');\n    circle(scene, x, y, 5, '#d7e6ff', true);\n  }\n  scene.restore();\n  for (let side = 0; side < 2; side++) {\n    const [x, y] = EARS[side];\n    const enabled = side === 0 ? $('leftEar').checked : $('rightEar').checked;\n    circle(scene, x, y, 19, enabled ? colors[side] : '#566071');\n    \/\/ Stylized ear outline, followed by the schematic sensory connection.\n    scene.save(); scene.translate(x, y);\n    if (side === 0) scene.scale(-1, 1);\n    scene.beginPath(); scene.ellipse(0, 0, 8, 13, 0, -.8 * Math.PI, .8 * Math.PI);\n    scene.strokeStyle = enabled ? colors[side] : '#566071'; scene.lineWidth = 3; scene.stroke(); scene.restore();\n    const endY = side === 0 ? 300 : 350;\n    scene.setLineDash([4, 5]); line(scene, [[x, y + 25], [x, endY]], '#47546c'); scene.setLineDash([]);\n  }\n  for (const l of model.lines) renderer.drawNeuron(scene, l.neuron, identity);\n  model.detectors.forEach((d, i) => {\n    branches[i].forEach((points, side) => {\n      const active = d.g[side] > 0.1e-9;\n      scene.globalAlpha = active ? 1 : 0.4;\n      line(scene, points, colors[side], active ? 3 : 2);\n      const [x, y] = points[0]; circle(scene, x, y, 5, colors[side], true);\n      scene.globalAlpha = 1;\n    });\n    renderer.drawNeuron(scene, d.neuron, identity);\n  });\n  const detector = model.detectors[selected];\n  renderer.drawElectrodes(scene, detector.neuron, { record: [{\n    compName: detector.soma.name, label: ' ', color: '#80c9ff',\n    drawAngleRad: Math.PI \/ 9\n  }] }, identity);\n}\n\n\/\/ drawScope() stretches its sample array across the available width and has no\n\/\/ timestamp axis. Keep the labeled axes and voltage palette around the shared\n\/\/ RingBuffer history, with the right edge anchored to the continuous clock.\nfunction drawRecording() {\n  const { dpr } = resizeCanvasToCSS(scope, graph);\n  graph.setTransform(dpr, 0, 0, dpr, 0, 0);\n  const w = scope.width \/ dpr, h = scope.height \/ dpr;\n  graph.fillStyle = '#0b101b'; graph.fillRect(0, 0, w, h);\n  const model = recording.model;\n  const start = recording.time - WINDOW_MS;\n  const x0 = 52, x1 = w - 18, y0 = 27, y1 = h - 42;\n  const xx = t => x0 + (t - start) \/ WINDOW_MS * (x1 - x0);\n  const yy = v => y1 - (v * 1000 + 90) \/ 140 * (y1 - y0);\n  for (const mv of [-80, -60, -40, -20, 0, 20, 40]) {\n    line(graph, [[x0, yy(mv \/ 1000)], [x1, yy(mv \/ 1000)]], '#253047');\n    label(graph, String(mv), x0 - 8, yy(mv \/ 1000) + 4, '#a6afc2', 11, 'right');\n  }\n  for (let i = 0; i <= 6; i++) {\n    const t = start + WINDOW_MS * i \/ 6;\n    line(graph, [[xx(t), y0], [xx(t), y1]], '#253047');\n    label(graph, t.toFixed(1), xx(t), y1 + 18, '#a6afc2', 11, 'center');\n  }\n  label(graph, 'mV', 13, 17, '#e6e8ee', 12);\n  label(graph, 'Time (ms)', (x0 + x1) \/ 2, h - 5, '#e6e8ee', 12, 'center');\n  const data = recording.buffers[selected].toArray();\n  const firstTime = recording.lastSample - WINDOW_MS;\n  graph.save(); graph.beginPath(); graph.rect(x0, y0, x1 - x0, y1 - y0); graph.clip();\n  for (let i = 1; i < data.length; i++) {\n    const c = vcol((data[i - 1] + data[i]) \/ 2);\n    line(graph, [[xx(firstTime + (i - 1) * SAMPLE_MS), yy(data[i - 1])],\n      [xx(firstTime + i * SAMPLE_MS), yy(data[i])]], `rgb(${c.r},${c.g},${c.b})`, 2);\n  }\n  \/\/ Include the current HH voltage between stored samples so the tip stays at now.\n  const voltage = model.detectors[selected].soma.V;\n  const c = vcol(voltage);\n  line(graph, [[xx(recording.lastSample), yy(data.at(-1))], [x1, yy(voltage)]], `rgb(${c.r},${c.g},${c.b})`, 2);\n  graph.restore();\n}\n\nfunction frame(now) {\n  const elapsed = lastFrame === null ? 0 : Math.min(250, now - lastFrame);\n  lastFrame = now;\n  recording.advance(elapsed, +$('speed').value);\n  drawCircuit(); drawRecording(); requestAnimationFrame(frame);\n}\nupdateLabels(); requestAnimationFrame(frame);\n\n\/\/ Read-only summary for browser verification; activity remains owned by the model.\nfunction snapshot() {\n  return { ...recording.model.summary(), time: recording.time,\n    trialTime: trial?.time ?? null, origin: recording.origin, done: trial?.done ?? false,\n    paused: recording.paused, selected, window: WINDOW_MS,\n    samples: recording.buffers[selected].capacity, lastSample: recording.lastSample,\n    selectedTrace: Array.from(recording.buffers[selected].toArray()),\n    markers: recording.markers.filter(m => m.neuron === selected).map(m => ({ ...m })) };\n}\n\n  })().catch(error => console.error('HH hearing embed could not initialize:', error));\n})();\n<\/script>\n\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t\t\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"<p>\u2190 Simulations Synaptic strength EPSP duration \u03c4 Playback speed Inputs L R PauseReset<\/p>\n","protected":false},"author":3,"featured_media":0,"parent":4438,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-4619","page","type-page","status-publish","hentry","entry"],"_links":{"self":[{"href":"https:\/\/faculty.fiu.edu\/~theobald\/wp-json\/wp\/v2\/pages\/4619","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/faculty.fiu.edu\/~theobald\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/faculty.fiu.edu\/~theobald\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/faculty.fiu.edu\/~theobald\/wp-json\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/faculty.fiu.edu\/~theobald\/wp-json\/wp\/v2\/comments?post=4619"}],"version-history":[{"count":23,"href":"https:\/\/faculty.fiu.edu\/~theobald\/wp-json\/wp\/v2\/pages\/4619\/revisions"}],"predecessor-version":[{"id":5032,"href":"https:\/\/faculty.fiu.edu\/~theobald\/wp-json\/wp\/v2\/pages\/4619\/revisions\/5032"}],"up":[{"embeddable":true,"href":"https:\/\/faculty.fiu.edu\/~theobald\/wp-json\/wp\/v2\/pages\/4438"}],"wp:attachment":[{"href":"https:\/\/faculty.fiu.edu\/~theobald\/wp-json\/wp\/v2\/media?parent=4619"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}