{"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":"2025-04-14T13:08:51","modified_gmt":"2025-04-14T17:08:51","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-boxed elementor-section-height-default elementor-section-height-default\" data-id=\"f7f4ee4\" data-element_type=\"section\">\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 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<!DOCTYPE HTML>\n<html>\n  <meta charset=\"utf-8\" \/>\n  <style>\n    .nacolor {\n\tcolor: #909000;\n\t     }\n    .kcolor {\n\tcolor: #8e60e6;\n\t     }\n    .clcolor {\n\tcolor: #169000;\n\t     }\n  <\/style>\n\n\n  \n  <body>\n    <h2>Hearing localization\n    <\/h2>\n\n\n    <!-- table of node sliders -->\n    <table style=\"width:800px\">\n      <tr>\n\t<th style=\"width:20%\">num nodes<\/th>\n\t<td style=\"width:20%\"><input type=\"range\" min=\"1\" max=\"9\" value=\"5\" step=\"1\" class=\"fslider\" name=\"fslider\" id=\"nnodes_slider\"><\/td>\n\t<td style=\"width:15%\"><label id=\"nnodes_slider_display\">8<\/label><br><\/td>\n      <\/tr>\n\n      <tr>\n\t<th style=\"width:20%\">time constant<\/th>\n\t<td style=\"width:20%\"><input type=\"range\" min=\"1\" max=\"50\" value=\"10\" step=\".1\" class=\"fslider\" name=\"fslider\" id=\"tc_slider\"><\/td>\n\t<td style=\"width:15%\"><label id=\"tc_slider_display\">6<\/label><br><\/td>\n      <\/tr>\n\n      <tr>\n\t<th style=\"width:20%\">amplitude<\/th>\n\t<td style=\"width:20%\"><input type=\"range\" min=\"0\" max=\"99\" value=\"50\" step=\"1\" class=\"fslider\" name=\"fslider\" id=\"amplitude_slider\"><\/td>\n\t<td style=\"width:15%\"><label id=\"amplitude_slider_display\">50<\/label><br><\/td>\n      <\/tr>\n    \n\n    <!-- plot -->\n    <div>\n      <span id=\"v1_plot\"> \n\t<!-- <canvas id=\"canvas_r\" width=\"960\" height=\"480\"><\/canvas> -->\n\t<canvas id=\"canvas_r\" width=\"960\" height=\"960\"><\/canvas>\n      <\/span>\n    <\/div>\n\n\n\n    <!-- javascript -->\n    <script type=\"text\/javascript\">\n      'use strict';\n\n      \n      function interp(x, x0, x1, y0, y1) { \n     \t  return y0 + (x - x0)*((y1-y0)\/(x1-x0)); \n      } \n\n\n      \/\/ colormaps roughly interpolated from matplotlib \n      function cm_coolwarm (x, alpha=1.0) { \/\/x = 0 -- 1 \n     \t  var r = Math.min(Math.max(Math.round(-8.8*x**3 + 36.7*x**2 + 15.3*x + 176.0), 0), 255); \n     \t  var g = Math.min(Math.max(Math.round(9.6*x**3 + 2.7*x**2 + -63.8*x + 219.6), 0), 255); \n     \t  var b = Math.min(Math.max(Math.round(3.5*x**3 + -16.0*x**2 + 2.0*x + 247.0), 0), 255); \n\t  return \"rgba(\"+r+\",\"+g+\",\"+b+\",\"+alpha+\")\";\n      } \n\n      \/\/ function cm_plasma(x) { \/\/x = 0--1 \n      \/\/ \t  var r = Math.min(Math.max(Math.round(-104.1*x**3 + -135.0*x**2 + 462.6*x + 19.3), 0), 255); \n      \/\/ \t  var g = Math.min(Math.max(Math.round(-254.9*x**3 + 617.4*x**2 + -127.7*x + 7.0), 0), 255); \n      \/\/ \t  var b = Math.min(Math.max(Math.round(582.1*x**3 + -1023.3*x**2 + 344.7*x + 133.4), 0), 255);\n      \/\/ \t  return \"rgb(\"+r+\",\"+g+\",\"+b+\")\"; \n      \/\/ } \n\n      \n       \/\/ function cm_viridis(x) { \/\/x = 0--1 \n       \/\/ \t  var r = Math.min(Math.max(Math.round(733.3*x**3 + -542.5*x**2 + 11.2*x + 74.5), 0), 255); \n       \/\/ \t  var g = Math.min(Math.max(Math.round(-44.7*x**3 + -41.0*x**2 + 314.5*x + 4.3), 0), 255); \n       \/\/ \t  var b = Math.min(Math.max(Math.round(44.5*x**3 + -435.7*x**2 + 312.5*x + 87.4), 0), 255); \n       \/\/ \t  return \"rgb(\"+r+\",\"+g+\",\"+b+\")\"; \n       \/\/ } \n\n      function cm_cividis(x, alpha=1.0) { \/\/x = 0--1 \n     \t  var r = Math.min(Math.max(Math.round(148.3*x**3 + -237.2*x**2 + 361.6*x + -14.4), 0), 255); \n     \t  var g = Math.min(Math.max(Math.round(37.1*x**3 + -19.0*x**2 + 178.2*x + 33.8), 0), 255); \n     \t  var b = Math.min(Math.max(Math.round(-228.0*x**3 + 168.1*x**2 + 14.2*x + 98.1), 0), 255); \n\t  return \"rgba(\"+r+\",\"+g+\",\"+b+\",\"+alpha+\")\";\n       } \n\n\n       \/\/ soapbubble colors for complex magnitudes and angles  \n       function cm_soapbubble(x, y, alpha=1.0) { \/\/x = 0 -- 1, y= -pi -- pi \n     \t  var r = Math.min(Math.max(Math.round(-57.4*Math.sin(y + 4.5) +x*265.1 + -20.3), 0), 255); \n     \t  var g = Math.min(Math.max(Math.round(20.6*Math.sin(y + -1.5) +x*253.5 + -14.3), 0), 255); \n     \t  var b = Math.min(Math.max(Math.round(57.1*Math.sin(y + 3.2) +x*234.6 + -4.3), 0), 255); \n     \t  return \"rgba(\"+r+\",\"+g+\",\"+b+\",\"+alpha+\")\"; \n       }\n\n\n\n      class Oscilloscope_scrolling {\n\t  constructor (context, left, right, bottom, top, title, bg,\n\t\t       vmin=-85, vmax=50, t_size=100) {\n\t      this.left = left;\n\t      this.right = right;\n\t      this.bottom = bottom;\n\t      this.top = top;\n\t      this.ctx = context;\n\t      this.bg = bg;\n\t      this.ctx.fillStyle = bg;\n\t      this.ctx.fillRect(this.left,this.bottom,this.right-this.left,this.top-this.bottom);\n\t      this.tmin = 0;\n\t      this.tmax = 10;\n\t      this.vmin = vmin;\n\t      this.vmax = vmax;\n\t      this.speed = .01\n\t      this.t = 0;\n\t      this.v = 0;\n\t      this.t_old;\n\t      this.v_old;\n\t      this.x;\n\t      this.y;\n\t      this.noise = 0;\n\t      this.dt = .02;\n\n\t      this.t_size = t_size;\n              this.bhead = 0; \/\/ Points to the current start of the buffer\n              this.buff = new Array(t_size).fill(-65.0);\n\n\t      this.title = title;\n\t  }\n\n\t  draw_line(t0, v0, t1, v1, color, dash, lw, x0off=0, x1off=0, y0off=0, y1off=0) {\n\t      var x0 = Math.round(interp(t0, this.tmin, this.tmax, this.left, this.right)) + x0off;\n\t      var x1 = Math.round(interp(t1, this.tmin, this.tmax, this.left, this.right)) + x1off;\n\t      var y0 = Math.round(interp(v0, this.vmin, this.vmax, this.bottom, this.top)) + y0off;\n\t      var y1 = Math.round(interp(v1, this.vmin, this.vmax, this.bottom, this.top)) + y1off;\n\t      this.ctx.lineWidth = lw;\n\t      this.ctx.setLineDash(dash);\n\t      this.ctx.strokeStyle = color;\n\t      this.ctx.beginPath();\n\t      this.ctx.moveTo(x0, y0);\n\t      this.ctx.lineTo(x1, y1);\n\t      this.ctx.fillStyle = color;\n\t      this.ctx.stroke();\n\t  }\n\t  \n\t  write_label(posx, posy, symbol, aftersym, centerx, centery) {\n\t      this.ctx.font = \"12px Ariel\";\n\t      if (centery==1) this.ctx.textBaseline = \"middle\";\n\t      if (centery==0) this.ctx.textBaseline = \"bottom\";\n\t      if (centerx==1) this.ctx.textAlign = \"center\";\n\t      if (centerx==0) this.ctx.textAlign = \"right\";\n\t      this.ctx.fillText(symbol + aftersym, posx, posy);\n\t  }\n\n\t  draw_h_line(pos, start, stop, color, dash, symbol, aftersym, centerx, centery) {\n\t      this.draw_line(this.tmin, pos, this.tmax, pos, color, dash, 1.20);\n\t  }\n\t  \n\t  draw_v_line(pos, start, stop, color, dash, symbol, aftersym, centerx, centery) {\n\t      this.draw_line(pos, this.vmin, pos, this.vmax, color, dash, 1.20);\n\t  }\n\t  \n\t  draw_lines(color) {\n\t      var i, first, dash, stepsize;\n\t      stepsize = Math.pow(10, Math.floor(Math.log10(this.tmax-this.tmin)-.3));\n\t      first = Math.ceil(this.tmin\/stepsize)*stepsize;\n\t      for (i=first; i<this.tmax; i+=stepsize) {\n\t\t  this.draw_v_line(i, 0.05, 1, color, [1,4], \"\", \"\", 0, 0);\n\t      }\n\t      stepsize = Math.pow(10, Math.floor(Math.log10(this.vmax-this.vmin)-.3));\n\t      first = Math.ceil(this.vmin\/stepsize)*stepsize;\n\t      for (i=first; i<this.vmax; i+=stepsize) {\n\t       \t  if (i==0) dash=[1,1];\n\t       \t  if (i!=0) dash=[1,4];\n\t\t  this.draw_h_line(i, 0.05, 1, color, dash, \"\", \"\", 0, 0);\n\t      }\n\t  }\n\t  \n\t  draw_title() {\n\t      this.ctx.font = \"18px Arial\";\n\t      this.ctx.fillStyle = cm_cividis(0.1)\n\t      this.ctx.textAlign = 'center'; \/\/ Align text horizontally in the middle\n\t      this.ctx.textBaseline = 'top'; \/\/ Align text vertically in the middle\n\t      this.ctx.fillText(this.title, (this.right+this.left)\/2, this.top); \n\t  }\n\t  \n\t  add(value) {\n              this.buff[this.bhead] = value;\n              this.bhead = (this.bhead + 1) % this.t_size; \/\/ Move head to the next position, wrapping around if necessary\n\t  }\n\n\t  add_v(vector) {\n\t      this.t_size = vector.length;\n              this.bhead = 0; \/\/ Points to the current start of the buffer\n              this.buff = new Array(this.t_size).fill(0.0);\n\t      \n\t      for (let i=0; i<vector.length; i++) {\n\t\t  this.add(vector[i]);\n\t      }\n\t  }\n\t  \n\t  bview() {\n              return this.buff.slice(this.bhead).concat(this.buff.slice(0, this.bhead));\n\t  }\n\t  \n\t  draw() {\n\t      \/\/ fill the rectangle\n\t      this.ctx.fillStyle = this.bg;\n\t      this.ctx.fillRect(this.left,this.bottom,this.right-this.left,this.top-this.bottom);\n\n\t      \/\/ scale and draw the line\n\t      this.ctx.beginPath();\n\t      for (let i=0; i<this.t_size; i++) {\n\t\t  const x = interp(i, 0, this.t_size, this.left, this.right);\n\t\t  const ind = (this.bhead+i)%this.t_size;\n\t\t  this.ind = ind;\n\t\t  const y = interp(this.buff[ind], this.vmin, this.vmax, this.top, this.bottom);\n\t\t  this.ctx.lineTo(x, y);\n\t\t  this.x = x;\n\t\t  this.y = y;\n\t      }\n\t      this.ctx.strokeStyle = 'green';\n\t      this.ctx.lineWidth = 3;\n\t      this.ctx.setLineDash([0]);\n\t      this.ctx.stroke();\n\t      \n\t      \/\/ plot elements\n\t      this.draw_lines(\"gray\");\n\t      this.draw_title();\n\t  }\n\n\t  update(val=0.0) {\n\t      this.add(val);\n\t      this.draw();\n\t  }\n\n      }\n\n\n\t  \n\t  \n\n      function log_normal_array(size=100, mu=0, sigma=10) {\n\t  let arr = new Array(size);\n\t  let step = (Math.exp(mu + 5 * sigma) - Math.exp(mu - 5 * sigma)) \/ size;\n\t  for (let i = 0; i < size; i++) {\n              let x = Math.exp(mu - 5 * sigma) + step * i;\n              let pdfValue = (1 \/ (x * sigma * Math.sqrt(2 * Math.PI))) * \n                  Math.exp(-((Math.log(x) - mu) ** 2) \/ (2 * sigma ** 2));\n              arr[i] = pdfValue;\n\t  }\n\t  return arr;\n      }\n\n      function ln(times, tp=.10, sigma=.2) {\n\t  let arr = new Array(times.length);\n\t  \/\/ let sigma = tp\/2;\n\t  for (var i = 0; i<times.length; i++) {\n\t      arr[i] = Math.exp(-Math.pow(Math.log(times[i] \/ tp), 2) \/ (2 * Math.pow(sigma, 2)));\n\t  }\n\t  \/\/ normalize\n\t  \/\/ let sum = 0;\n\t  \/\/ for (var i = 0; i<times.length; i++) {\n\t  \/\/     sum += arr[i];\n\t  \/\/ }\n\t  \/\/ for (var i = 0; i<times.length; i++) {\n\t  \/\/     arr[i] \/= sum;\n\t  \/\/ }\n\t  return arr;\n      }\n\n      \n      function convolve_arrays(array1, array2) {\n\t  let result = new Array(array1.length + array2.length - 1).fill(0);\n\t  for (let i = 0; i < array1.length; i++) {\n              for (let j = 0; j < array2.length; j++) {\n\t\t  result[i + j] += array1[i] * array2[j];\n              }\n\t  }\n\t  return result;\n      }\n\n      function dot(array1, array2) {\n\t  if (array1.length !== array2.length) {\n              throw new Error(\"Arrays must be of the same size.\");\n\t  }\n    \n\t  let sum = 0;\n\t  for (let i = 0; i < array1.length; i++) {\n              sum += array1[i] * array2[i];\n\t  }\n\t  return sum;\n      }\n\n      function m_dot_v(matrix, vector) {\n\t  if (matrix[0].length !== vector.length) {\n              throw new Error(\"Matrix and vector dimensions do not match.\");\n\t  }\n\n\t  let result = new Array(matrix.length).fill(0);\n\t  \n\t  for (let i = 0; i < matrix.length; i++) {\n              for (let j = 0; j < vector.length; j++) {\n\t\t  result[i] += matrix[i][j] * vector[j];\n              }\n\t  }\n\n\t  return result;\n      }\n      \n      \n      class Neuron {\n\t  constructor (ctx, x, y, rad, lambda=.10, ox, oy, ow, oh, name) {\n\t      this.ctx = ctx;\n\t      this.x = x;\n\t      this.y = y;\n\t      this.rad = rad;\n\t      this.lambda = lambda;\n\t      \n\t      this.rest = -65;\n\t      this.thresh = -30;\n\t      this.v = -65;\n\n\t      this.size = 100;\n\t      this.times = new Array(this.size).fill(0);\n\t      for (var i = 0; i<(this.size-1); i++) {\n\t\t  this.times[i] = i*.01 + .001;\n\t      }\n\t      this.filter = ln(this.times, lambda);\n\n\t      this.pre_curr = 10;\n\t      this.current = new Array(this.size + this.pre_curr).fill(0);\n\t      this.ap = -1\n\n\t      this.name = name;\n\n\t      this.osc = new Oscilloscope_scrolling(this.ctx, ox, ox+ow, oy, oy + oh, this.name, 'black', -80, 50, 400);\n\n\t  }\n\n\n\t  shift_curr() {\n\t      \/\/everything gets shifted\n\t      for (let i=this.current.length-1; i>0; i--) {\n\t\t  this.current[i] = this.current[i-1];\n\t      }\n\t      this.current[0] = 0;\n\t  }\n\t      \n\t  add_curr(curr=0, index=-1) {\n\t      if (index==-1) index=this.pre_curr;\n\t      this.current[index] = this.current[index] + curr;\n\t  }\n\t      \n\t  draw() {\n\t      var c = interp(this.v, -80, 50, 0, 1);\n\t      this.ctx.beginPath();\n\t      this.ctx.fillStyle = cm_cividis(c);\n\t      this.ctx.arc(this.x, this.y, this.rad, 0, 2*Math.PI);\n\t      this.ctx.fill();\n\t      \n\t      this.ctx.font = \"18px Arial\";\n\t      this.ctx.textAlign = 'center'; \/\/ Align text horizontally in the middle\n\t      this.ctx.textBaseline = 'middle'; \/\/ Align text vertically in the middle\n\t      this.ctx.fillStyle = cm_coolwarm(.1);\n\t      this.ctx.fillText(this.name, this.x, this.y); \n\n\t  }\n\t  \n\n\t  update(current=0) {\n\t      this.draw();\n\n\t      this.shift_curr();\n\t      this.add_curr(current);\n\t      this.v = dot(this.current.slice(this.pre_curr, this.current.length), this.filter) + this.rest;\n\n\t      \/\/ if we hit, and no ap is going\n\t      if (this.v > this.thresh && this.ap==-1) {\n\t\t  const maxind = this.filter.reduce((maxIndex, currentElement, currentIndex, arr) => \n\t\t      currentElement > arr[maxIndex] ? currentIndex : maxIndex, 0);\n\t\t  this.add_curr(50, maxind + this.pre_curr)\n\t\t  this.add_curr(-30, maxind - this.pre_curr - 4)\n\t\t  this.ap = 10;\n\t\t  console.log('AP', this.name)\n\n\t      }\n\t      if (this.ap >= 0 && this.v < this.thresh)\n\t\t  this.ap -= 1;\n\n\t      this.osc.update(this.v + Math.random()*5)\n\t  }\n      }\n\n      \n      class Ears { \n          constructor (context, left, right, bottom, top) {\n\t      \/\/ canvas\n\t      this.ctx = context\n\t      \n\t      this.left = left;\n\t      this.right = right;\n\t      this.top = top;\n\t      this.bottom = bottom;\n\t      this.midcolor = cm_coolwarm(.5);\n\n\t      \/\/ ears\n\t      this.l_ear_x = left+150;\n\t      this.l_ear_y = top\/3;\n\t      this.r_ear_x = right-150;\n\t      this.r_ear_y = top\/3;\n\t      \n\t      this.ear_rad = 30;\n\t      this.ear_neuron_d = 40;\n\n\t      this.l_ear_cond = -1;\n\t      this.r_ear_cond = -1;\n\t      this.ear_cond_speed = 1;\n\t      \n\t      \n\t      \/\/ sound will be negative when no sound\n\t      this.sound = -1;\n\t      this.speed_of_sound = 2;\n\t      this.sound_dist = right-left;\n\t      this.sound_x = 0;\n\t      this.sound_y = 0;\n\t      this.sound_hit_l = false;\n\t      this.sound_hit_r = false;\n\n\t      this.l_ear_dist = -1;\n\t      this.r_ear_dist = -1;\n\n\t      \/\/ nodes\n\t      this.nnodes_slider=document.getElementById(\"nnodes_slider\");\n\t      this.nnodes_display=document.getElementById(\"nnodes_slider_display\");\n\t      \n\t      this.tc_slider=document.getElementById(\"tc_slider\");\n\t      this.tc_display=document.getElementById(\"tc_slider_display\");\n\t      \n\t      this.amplitude_slider=document.getElementById(\"amplitude_slider\");\n\t      this.amplitude_display=document.getElementById(\"amplitude_slider_display\");\n\t      \n\t      this.num_nodes = 5;\n\t      this.num_nodes_old = 5;\n\t      this.num_nodes_max = 9;\n\t      this.nodes = [];\n\t      for (var i=0; i<this.num_nodes_max; i++) {\n\t\t  var n = new Neuron(this.ctx, 0, this.l_ear_y, 0.5*this.ear_neuron_d, .1, 500,500,1,1, i+1);\n\t\t  this.nodes.push(n);\n\t      }\n\t      this.update_node_locs();\n\n\t      this.amplitude = 1;\n          }\n\n\n\t  \n\t  update_node_locs() {\n\t      for (var i=0; i<this.num_nodes; i++) {\n\t\t  var s = this.l_ear_x + 2*this.ear_rad;\n\t\t  var d = this.r_ear_x - this.l_ear_x - 4*this.ear_rad;\n\t\t  this.nodes[i].x = s + (i+1)*d\/(this.num_nodes+1);\n\n\t\t  s = (this.right-this.left)\/(this.num_nodes+1);\n\t\t  d = 60;\n\t\t  this.nodes[i].osc.left = (i+1\/2)*s-d;\n\t\t  this.nodes[i].osc.right = (i+1\/2)*s+2*d;\n\t\t  this.nodes[i].osc.bottom = 400 + (i%2)*130;\n\t\t  this.nodes[i].osc.top = 500 + (i%2)*130;\n\t      }\n\t  }\n\n\t  update_node_tcs() {\n\t      for (var i=0; i<this.num_nodes_max; i++) {\n\t  \t  this.nodes[i].lambda = this.tc\/100.;\n\t\t  this.nodes[i].filter = ln(this.nodes[i].times, this.nodes[i].lambda);\n\t      }\n\t  }\n\n\n\t  sig(x, bot=-1., top=1., mid=0., slp=4.) {\n\t      return bot + (top - bot)\/(1 + Math.exp(slp*(mid - x)));\n\t  }\n\n\t  \n\t  clicked(x, y) {\n\t      if (this.sound == -1) {\n\t\t  this.sound = 0;\n\t\t  this.sound_x = x;\n\t\t  this.sound_y = y;\n\n\t\t  this.l_ear_dist = Math.sqrt((this.l_ear_x - x)**2 + (this.l_ear_y - y)**2);\n\t\t  this.r_ear_dist = Math.sqrt((this.r_ear_x - x)**2 + (this.r_ear_y - y)**2);\n\t\t  \n\t\t  this.sound_hit_l = false;\n\t\t  this.sound_hit_r = false;\n\t      }\n\t  }\n\t  \n\t  draw() {\n\t      \/\/ erase everything\n\t      this.ctx.fillStyle = cm_coolwarm(.1);\n\t      this.ctx.fillRect(this.left,this.bottom,this.right-this.left,this.top-this.bottom);\n\n\t      this.ctx.fillStyle = cm_coolwarm(.9);\n\t      this.ctx.font = \"36px Ariel\";\n\t      this.ctx.textBaseline = \"top\";\n\t      this.ctx.textAlign = \"left\";\n\t      this.ctx.fillText(\"Click to start a soundwave\", 10,10);\n\n\t      \n\t      \/\/ draw ears\n\t      this.ctx.beginPath();\n\t      this.ctx.strokeStyle = 'black';\n\t      this.ctx.setLineDash([0]);\n\t      this.ctx.lineWidth = 2;\n\t      this.ctx.arc(this.l_ear_x - this.ear_rad, this.l_ear_y, this.ear_rad, 3*Math.PI\/2, Math.PI\/2);\n\t      this.ctx.stroke(); \n\t      this.ctx.beginPath();\n\t      this.ctx.arc(this.r_ear_x+this.ear_rad, this.r_ear_y, this.ear_rad, Math.PI\/2, 3*Math.PI\/2);\n\t      this.ctx.stroke();\n\t      \n\t      \/\/ draw axons\n\t      this.ctx.beginPath();\n\t      this.ctx.moveTo(this.l_ear_x, this.l_ear_y);\n\t      for (var i=this.l_ear_x; i<this.r_ear_x-this.ear_rad*2; i+=2) {\n\t\t  this.ctx.lineTo(i, this.sig(i, this.l_ear_y, this.l_ear_y-this.ear_neuron_d, this.l_ear_x+this.ear_rad, .2));\n\t      }\n\t      this.ctx.moveTo(this.r_ear_x, this.r_ear_y);\n\t      for (var i=this.r_ear_x; i>this.l_ear_x+this.ear_rad*2; i-=2) {\n\t\t  this.ctx.lineTo(i, this.sig(i, this.r_ear_y+this.ear_neuron_d, this.r_ear_y, this.r_ear_x-this.ear_rad, .2));\n\t      }\n\t      this.ctx.stroke();\n\n\t      \n\n\t      \/\/ nodes\n\t      this.ctx.fillStyle = cm_cividis(.1);\n\t      for (var i=0; i<this.num_nodes; i++) {\n\t\t  this.ctx.beginPath();\n\t\t  this.nodes[i].draw();\n\t\t  this.ctx.beginPath();\n\t\t  this.ctx.arc(this.nodes[i].x, this.l_ear_y, 0.60*this.ear_neuron_d, Math.PI\/2-.3, Math.PI\/2+.3);\n\t\t  this.ctx.stroke();\n\t\t  this.ctx.beginPath();\n\t\t  this.ctx.arc(this.nodes[i].x, this.l_ear_y, 0.60*this.ear_neuron_d, -Math.PI\/2-.3, -Math.PI\/2+.3);\n\t\t  this.ctx.stroke();\n\t\t  this.ctx.beginPath();\n\t\t  this.ctx.moveTo(this.nodes[i].x, this.l_ear_y + 0.6*this.ear_neuron_d);\n\t\t  this.ctx.lineTo(this.nodes[i].x, this.l_ear_y + this.ear_neuron_d);\n\t\t  this.ctx.moveTo(this.nodes[i].x, this.l_ear_y - 0.6*this.ear_neuron_d);\n\t\t  this.ctx.lineTo(this.nodes[i].x, this.l_ear_y - this.ear_neuron_d);\n\t\t  this.ctx.stroke();\n\t      }\n\n\t      \/\/ left conduction\n\t      this.ctx.fillStyle = cm_cividis(.99);\n\t      if (this.l_ear_cond>=0) {\n\t      \t  this.l_ear_cond += this.ear_cond_speed;\n\t      \t  if (this.l_ear_x + this.l_ear_cond > this.r_ear_x-this.ear_rad*2) {\n\t      \t      this.l_ear_cond = -1;\n\t      \t  } \n\t      \t  this.ctx.beginPath();\n\t      \t  this.ctx.arc(this.l_ear_x + this.l_ear_cond, this.sig(this.l_ear_x + this.l_ear_cond, this.l_ear_y, this.l_ear_y-this.ear_neuron_d, this.l_ear_x+this.ear_rad, .2), 10, 0, 2 * Math.PI);\n\t      \t  this.ctx.fill();\n\t      }\n\t      \/\/ right conduction\n\t      if (this.r_ear_cond>=0) {\n\t      \t  this.r_ear_cond += this.ear_cond_speed;\n\t      \t  if (this.r_ear_x - this.r_ear_cond < this.l_ear_x+this.ear_rad*2) {\n\t      \t      this.r_ear_cond = -1;\n\t      \t  } \n\t      \t  this.ctx.beginPath();\n\t      \t  this.ctx.arc(this.r_ear_x - this.r_ear_cond, this.sig(this.r_ear_x - this.r_ear_cond, this.r_ear_y+this.ear_neuron_d, this.r_ear_y, this.r_ear_x-this.ear_rad, .2), 10, 0, 2 * Math.PI);\n\t      \t  this.ctx.fill();\n\t      }\n\n\t      \n\t      \/\/ if sound is in the air\n\t      if (this.sound >= 0 && this.sound < this.sound_dist) {\n\t\t  this.sound += this.speed_of_sound;\n\n\t\t  this.ctx.beginPath();\n\t\t  this.ctx.strokeStyle = cm_coolwarm(.9);\n\t\t  this.ctx.strokeWidth = 5;\n\t\t  this.ctx.arc(this.sound_x, this.sound_y, this.sound+2, 0, 2 * Math.PI);\n\t\t  this.ctx.stroke();\n\n\t\t  this.ctx.beginPath();\n\t\t  this.ctx.strokeStyle = cm_coolwarm(.2, .8);\n\t\t  this.ctx.arc(this.sound_x, this.sound_y, this.sound+4, 0, 2 * Math.PI);\n\t\t  this.ctx.arc(this.sound_x, this.sound_y, this.sound, 0, 2 * Math.PI);\n\t\t  this.ctx.stroke();\n\n\t\t  if (this.sound_hit_l==false && this.sound>this.l_ear_dist) {\n\t\t      this.sound_hit_l = true;\n\t\t      this.l_ear_cond = 0;\n\t\t  }\n\t\t  if (this.sound_hit_r==false && this.sound>this.r_ear_dist) {\n\t\t      this.sound_hit_r = true;\n\t\t      this.r_ear_cond = 0;\n\t\t  }\n\t      }\n\t      else if (this.sound >= this.sound_dist){\n\t\t  this.sound = -1;\n\t      }\n\n\t  }\n\n\t  update_sliders() {\n\t      this.num_nodes = parseFloat(this.nnodes_slider.value);\n\t      this.nnodes_display.innerHTML=this.num_nodes;\n\t      this.update_node_locs();\n\n\t      this.tc = parseFloat(this.tc_slider.value);\n\t      this.tc_display.innerHTML=this.tc;\n\t      this.update_node_tcs();\n\n\t      this.amplitude = parseFloat(this.amplitude_slider.value);\n\t      this.amplitude_display.innerHTML=this.amplitude;\n\n\t  }\n\n\t       \n\t  update() {\n\t      this.update_sliders();\n\t      \n\t      this.draw();\n\t      \n\t      for (var i=0; i<this.num_nodes; i++) {\n\t\t  if (this.l_ear_cond + this.l_ear_x > this.nodes[i].x && this.l_ear_cond + this.l_ear_x <=  this.nodes[i].x + this.ear_cond_speed) {\n\t\t      this.nodes[i].update(this.amplitude);\n\t\t  }\n\t\t  else {\n\t\t      this.nodes[i].update(0);\n\t\t  }\n\t\t  if (this.r_ear_x - this.r_ear_cond < this.nodes[i].x && this.r_ear_x - this.r_ear_cond >= this.nodes[i].x - this.ear_cond_speed) {\n\t\t      this.nodes[i].update(this.amplitude);\n\t\t  }\n\t\t  else {\n\t\t      this.nodes[i].update(0);\n\t\t  }\n\t      }\n\t  }\n      }\n      \n\n      const canvas = document.getElementById('canvas_r'); \n      const ctx = canvas.getContext('2d');\n\n      var ears = new Ears(ctx, 0, 960, 0, 960);\n      \n      canvas.addEventListener('click', function(event) {\n          \/\/ Calculate click position relative to the canvas\n          const rect = canvas.getBoundingClientRect();\n          ears.clicked(event.clientX - rect.left, event.clientY - rect.top);\n    });\n      \n      const o = new Oscilloscope_scrolling(ctx, 100, 300, 700, 800,\n      \t\t\t\t\t   'abc', 'black', -3, 3);\n\n      <!-- const o2 = new Oscilloscope_scrolling(ctx, 400, 600, 700, 800, -->\n      <!-- \t\t\t\t\t    'abd', 'black', -3, 3); -->\n\n      o.add_v(ears.nodes[0].filter)\n      <!-- o2.add_v(ears.nodes[0].current) -->\n      \n      function update() {\n\t  ears.update();\n\n\t  o.add_v(ears.nodes[0].filter)\n\t  o.draw();\n\t  <!-- o2.add_v(ears.nodes[0].current) -->\n\t  <!-- o2.draw(); -->\n      }\n      \n      setInterval(update, 20);\n\n\n      \n    <\/script>\n  <\/body>\n<\/html>\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>Hearing localization num nodes 8 time constant 6 amplitude 50<\/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":5,"href":"https:\/\/faculty.fiu.edu\/~theobald\/wp-json\/wp\/v2\/pages\/4619\/revisions"}],"predecessor-version":[{"id":4624,"href":"https:\/\/faculty.fiu.edu\/~theobald\/wp-json\/wp\/v2\/pages\/4619\/revisions\/4624"}],"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}]}}