Starting interactive Python…
import%20sys%20as%20_browser_sys%0Aif%20_browser_sys.platform%20%3D%3D%20'emscripten'%3A%0A%20%20%20%20import%20re%20as%20_browser_re%0A%20%20%20%20import%20micropip%20as%20_browser_pip%0A%20%20%20%20for%20_browser_attempt%20in%20range(4)%3A%0A%20%20%20%20%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20await%20_browser_pip.install(%5B'wigglystuff%3D%3D0.5.36'%5D)%0A%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20except%20ValueError%20as%20_browser_error%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_browser_conflict%20%3D%20_browser_re.search(r'but%20(%5CS%2B)%3D%3D%5CS%2B%20is%20already%20installed'%2C%20str(_browser_error))%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20_browser_conflict%20is%20None%20or%20_browser_attempt%20%3D%3D%203%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20raise%0A%20%20%20%20%20%20%20%20%20%20%20%20_browser_pip.uninstall(_browser_conflict%5B1%5D)%0Aimport%20json%20as%20_obsidian_json%0A_obsidian_namespace%20%3D%20%7B%7D%0Aexec('%22%22%22Obsidian%20extensions%20for%20the%20pinned%20marimo%20Markdown%20parser%20(also%20runs%20in%20Pyodide).%22%22%22%5Cnimport%20html%5Cnimport%20posixpath%5Cnimport%20re%5Cnimport%20threading%5Cnimport%20unicodedata%5Cnfrom%20urllib.parse%20import%20unquote%5Cnfrom%20xml.etree%20import%20ElementTree%20as%20ET%5Cn%5Cnfrom%20markdown%20import%20Extension%2C%20Markdown%5Cnfrom%20markdown.inlinepatterns%20import%20InlineProcessor%5Cnfrom%20markdown.preprocessors%20import%20Preprocessor%5Cnfrom%20markdown.treeprocessors%20import%20Treeprocessor%5Cnfrom%20markdown.util%20import%20AtomicString%5Cn%5Cn%5Cndef%20file_key(value)%3A%5Cn%20%20%20%20value%20%3D%20unquote(value).strip().strip(%22%2F%22)%5Cn%20%20%20%20value%20%3D%20re.sub(r%22%5C%5C.(%3F%3Amarimo(%3F%3A%5C%5C.py)%3F%7Cmd%7Chtml)%24%22%2C%20%22%22%2C%20value%2C%20flags%3Dre.I)%5Cn%20%20%20%20return%20re.sub(r%22%5C%5Cs%22%2C%20%22-%22%2C%20value).replace(%22%26%22%2C%20%22-and-%22).replace(%22%25%22%2C%20%22-percent%22).replace(%22%3F%22%2C%20%22%22).lower()%5Cn%5Cn%5Cndef%20heading_slug(value%2C%20separator%3D%22-%22)%3A%5Cn%20%20%20%20%23%20Preserve%20Unicode%20letters%2C%20numbers%2C%20marks%20and%20underscores%2C%20as%20Quartz%20does.%5Cn%20%20%20%20value%20%3D%20html.unescape(re.sub(r%22%3C%5B%5E%3E%5D*%3E%22%2C%20%22%22%2C%20value)).lower()%5Cn%20%20%20%20return%20%22%22.join(c%20for%20c%20in%20value%20if%20c%20in%20%22%20-_%22%20or%20unicodedata.category(c)%5B0%5D%20in%20%22LNM%22).replace(%22%20%22%2C%20%22-%22)%5Cn%5Cn%5Cndef%20load_index(url)%3A%5Cn%20%20%20%20%23%20The%20Vault%20index%20is%20one%20shared%2C%20HTTP-cached%20file%20instead%20of%20being%20inlined%5Cn%20%20%20%20%23%20into%20every%20notebook.%20Workers%20may%20use%20synchronous%20requests%3B%20the%20page%20loader%5Cn%20%20%20%20%23%20prefetches%20this%20URL%2C%20so%20the%20first%20link%20usually%20reads%20from%20cache.%5Cn%20%20%20%20import%20json%5Cn%20%20%20%20try%3A%5Cn%20%20%20%20%20%20%20%20from%20js%20import%20console%2C%20location%5Cn%20%20%20%20%20%20%20%20from%20pyodide.http%20import%20open_url%5Cn%20%20%20%20except%20ImportError%3A%5Cn%20%20%20%20%20%20%20%20%23%20Build-time%20execution%20reads%20the%20same%20index%20from%20a%20local%20file.%5Cn%20%20%20%20%20%20%20%20import%20os%5Cn%20%20%20%20%20%20%20%20path%20%3D%20os.environ.get(%22EWAN_MARIMO_LINK_INDEX%22)%5Cn%20%20%20%20%20%20%20%20if%20not%20path%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20return%20%5B%5D%5Cn%20%20%20%20%20%20%20%20with%20open(path)%20as%20file%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20return%20json.load(file).get(%22files%22%2C%20%5B%5D)%5Cn%20%20%20%20try%3A%5Cn%20%20%20%20%20%20%20%20return%20json.load(open_url(location.origin%20%2B%20url)).get(%22files%22%2C%20%5B%5D)%5Cn%20%20%20%20except%20Exception%20as%20error%3A%5Cn%20%20%20%20%20%20%20%20console.warn(f%22%5Bewan-marimo%5D%20link%20index%20unavailable%3A%20%7Berror%7D%22)%5Cn%20%20%20%20%20%20%20%20return%20%5B%5D%5Cn%5Cn%5Cnclass%20Resolver%3A%5Cn%20%20%20%20def%20__init__(self%2C%20context)%3A%5Cn%20%20%20%20%20%20%20%20self.current%20%3D%20context.get(%22currentSlug%22%2C%20%22index%22)%5Cn%20%20%20%20%20%20%20%20self.index%20%3D%20context.get(%22index%22)%5Cn%20%20%20%20%20%20%20%20self._entries%20%3D%20context.get(%22files%22)%5Cn%20%20%20%20%20%20%20%20self._keys%20%3D%20None%5Cn%5Cn%20%20%20%20%40property%5Cn%20%20%20%20def%20entries(self)%3A%5Cn%20%20%20%20%20%20%20%20if%20self._entries%20is%20None%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20self._entries%20%3D%20load_index(self.index)%20if%20self.index%20else%20%5B%5D%5Cn%20%20%20%20%20%20%20%20return%20self._entries%5Cn%5Cn%20%20%20%20%40property%5Cn%20%20%20%20def%20keys(self)%3A%5Cn%20%20%20%20%20%20%20%20if%20self._keys%20is%20None%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20self._keys%20%3D%20%7B%7D%5Cn%20%20%20%20%20%20%20%20%20%20%20%20for%20entry%20in%20self.entries%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20name%20in%20%5Bentry%5B%22path%22%5D%2C%20entry%5B%22slug%22%5D%2C%20*entry.get(%22aliases%22%2C%20%5B%5D)%5D%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20key%20%3D%20file_key(name)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self._keys.setdefault(key%2C%20%5B%5D)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20entry%20not%20in%20self._keys%5Bkey%5D%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self._keys%5Bkey%5D.append(entry)%5Cn%20%20%20%20%20%20%20%20return%20self._keys%5Cn%5Cn%20%20%20%20def%20resolve(self%2C%20target)%3A%5Cn%20%20%20%20%20%20%20%20target%20%3D%20unquote(target).strip()%5Cn%20%20%20%20%20%20%20%20if%20re.match(r%22%5E%5Ba-zA-Z%5D%5B%5C%5Cw%2B.-%5D*%3A%22%2C%20target)%20or%20target.startswith(%22%2F%2F%22)%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20return%20target%2C%20None%5Cn%20%20%20%20%20%20%20%20name%2C%20sep%2C%20fragment%20%3D%20target.partition(%22%23%22)%5Cn%20%20%20%20%20%20%20%20key%20%3D%20file_key(name)%5Cn%20%20%20%20%20%20%20%20if%20not%20name%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20found%20%3D%20next((e%20for%20e%20in%20self.entries%20if%20e%5B%22slug%22%5D%20%3D%3D%20self.current)%2C%20None)%5Cn%20%20%20%20%20%20%20%20else%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20relative%20%3D%20file_key(posixpath.normpath(posixpath.join(posixpath.dirname(self.current)%2C%20key)))%5Cn%20%20%20%20%20%20%20%20%20%20%20%20if%20name.startswith((%22.%2F%22%2C%20%22..%2F%22))%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20matches%20%3D%20self.keys.get(relative%2C%20%5B%5D)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20else%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20matches%20%3D%20self.keys.get(key%2C%20%5B%5D)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20not%20name.startswith(%22%2F%22)%20and%20(not%20matches%20or%20%22%2F%22%20not%20in%20key)%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20matches%20%3D%20%5B*matches%2C%20*%5Be%20for%20k%2C%20es%20in%20self.keys.items()%20if%20k.endswith(%22%2F%22%20%2B%20key)%20for%20e%20in%20es%5D%5D%5Cn%20%20%20%20%20%20%20%20%20%20%20%20matches%20%3D%20list(%7Be%5B%22slug%22%5D%3A%20e%20for%20e%20in%20matches%7D.values())%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%23%20A%20note%20next%20to%20this%20notebook%20wins%20an%20otherwise%20ambiguous%20basename.%5Cn%20%20%20%20%20%20%20%20%20%20%20%20nearby%20%3D%20%5Be%20for%20e%20in%20matches%20if%20posixpath.dirname(e%5B%22slug%22%5D)%20%3D%3D%20posixpath.dirname(self.current)%5D%5Cn%20%20%20%20%20%20%20%20%20%20%20%20found%20%3D%20matches%5B0%5D%20if%20len(matches)%20%3D%3D%201%20else%20nearby%5B0%5D%20if%20len(nearby)%20%3D%3D%201%20else%20None%5Cn%20%20%20%20%20%20%20%20if%20name%20and%20found%20is%20None%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20return%20None%2C%20None%5Cn%20%20%20%20%20%20%20%20slug%20%3D%20found%5B%22slug%22%5D%20if%20found%20else%20self.current%5Cn%20%20%20%20%20%20%20%20simple%20%3D%20re.sub(r%22(%3F%3A%5E%7C%2F)index%24%22%2C%20%22%2F%22%2C%20slug).rstrip(%22%2F%22)%5Cn%20%20%20%20%20%20%20%20root%20%3D%20%22..%2F%22%20*%20self.current.count(%22%2F%22)%20or%20%22.%2F%22%5Cn%20%20%20%20%20%20%20%20href%20%3D%20root%20%2B%20simple%5Cn%20%20%20%20%20%20%20%20if%20slug.endswith(%22%2Findex%22)%20or%20slug%20%3D%3D%20%22index%22%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20href%20%3D%20href.rstrip(%22%2F%22)%20%2B%20%22%2F%22%5Cn%20%20%20%20%20%20%20%20if%20not%20name%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20href%20%3D%20%22%22%5Cn%20%20%20%20%20%20%20%20if%20sep%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20if%20name.lower().endswith(%22.pdf%22)%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20anchor%20%3D%20fragment%5Cn%20%20%20%20%20%20%20%20%20%20%20%20elif%20fragment.startswith(%22%5E%22)%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20anchor%20%3D%20fragment%5B1%3A%5D.lower()%5Cn%20%20%20%20%20%20%20%20%20%20%20%20else%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20anchor%20%3D%20(found%20or%20%7B%7D).get(%22headings%22%2C%20%7B%7D).get(fragment)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20anchor%20is%20None%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20anchor%20%3D%20heading_slug(fragment.split(%22%23%22)%5B-1%5D)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20href%20%2B%3D%20%22%23%22%20%2B%20anchor%5Cn%20%20%20%20%20%20%20%20return%20href%2C%20found%5Cn%5Cn%5Cnclass%20WikiLinks(InlineProcessor)%3A%5Cn%20%20%20%20def%20__init__(self%2C%20md%2C%20resolver)%3A%5Cn%20%20%20%20%20%20%20%20super().__init__(r%22(%3F%3C!%5C%5C%5C%5C)(!%3F)%5C%5C%5B%5C%5C%5B(%5B%5E%5C%5C%5B%5C%5C%5D%5C%5Cn%5D%2B)%5C%5C%5D%5C%5C%5D%22%2C%20md)%5Cn%20%20%20%20%20%20%20%20self.resolver%20%3D%20resolver%5Cn%5Cn%20%20%20%20def%20handleMatch(self%2C%20match%2C%20data)%3A%5Cn%20%20%20%20%20%20%20%20raw%20%3D%20match.group(2).replace(r%22%5C%5C%7C%22%2C%20%22%7C%22)%5Cn%20%20%20%20%20%20%20%20target%2C%20divider%2C%20alias%20%3D%20raw.partition(%22%7C%22)%5Cn%20%20%20%20%20%20%20%20target%20%3D%20target.strip()%5Cn%20%20%20%20%20%20%20%20label%20%3D%20alias.strip()%20if%20divider%20else%20target.rsplit(%22%2F%22%2C%201)%5B-1%5D.removesuffix(%22.md%22)%5Cn%20%20%20%20%20%20%20%20href%2C%20entry%20%3D%20self.resolver.resolve(target)%5Cn%20%20%20%20%20%20%20%20if%20href%20is%20None%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20el%20%3D%20ET.Element(%22span%22%2C%20%7B%22class%22%3A%20%22internal-link%20is-unresolved%22%2C%20%22title%22%3A%20%22Unpublished%20or%20ambiguous%20note%3A%20%22%20%2B%20target%7D)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20el.text%20%3D%20AtomicString(label)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20return%20el%2C%20match.start(0)%2C%20match.end(0)%5Cn%20%20%20%20%20%20%20%20if%20match.group(1)%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20ext%20%3D%20posixpath.splitext(target.split(%22%23%22)%5B0%5D)%5B1%5D.lower()%5Cn%20%20%20%20%20%20%20%20%20%20%20%20if%20ext%20in%20%7B%22.png%22%2C%20%22.jpg%22%2C%20%22.jpeg%22%2C%20%22.gif%22%2C%20%22.svg%22%2C%20%22.webp%22%2C%20%22.avif%22%2C%20%22.bmp%22%7D%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20el%20%3D%20ET.Element(%22img%22%2C%20%7B%22src%22%3A%20href%2C%20%22alt%22%3A%20label%2C%20%22loading%22%3A%20%22lazy%22%7D)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dimensions%20%3D%20re.fullmatch(r%22(%5C%5Cd%2B)(%3F%3Ax(%5C%5Cd%2B))%3F%22%2C%20alias.strip())%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20dimensions%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20el.set(%22width%22%2C%20dimensions%5B1%5D)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20dimensions%5B2%5D%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20el.set(%22height%22%2C%20dimensions%5B2%5D)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20elif%20ext%20in%20%7B%22.mp3%22%2C%20%22.wav%22%2C%20%22.ogg%22%2C%20%22.m4a%22%2C%20%22.flac%22%2C%20%22.mp4%22%2C%20%22.webm%22%2C%20%22.mov%22%7D%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20tag%20%3D%20%22video%22%20if%20ext%20in%20%7B%22.mp4%22%2C%20%22.webm%22%2C%20%22.mov%22%7D%20else%20%22audio%22%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20el%20%3D%20ET.Element(tag%2C%20%7B%22src%22%3A%20href%2C%20%22controls%22%3A%20%22%22%2C%20%22preload%22%3A%20%22metadata%22%7D)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20elif%20ext%20%3D%3D%20%22.pdf%22%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20el%20%3D%20ET.Element(%22iframe%22%2C%20%7B%22src%22%3A%20href%2C%20%22title%22%3A%20label%2C%20%22loading%22%3A%20%22lazy%22%7D)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20else%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20el%20%3D%20ET.Element(%22span%22%2C%20%7B%22class%22%3A%20%22marimo-obsidian-embed%22%2C%20%22data-obsidian-embed%22%3A%20href%7D)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20link%20%3D%20ET.SubElement(el%2C%20%22a%22%2C%20%7B%22href%22%3A%20href%2C%20%22class%22%3A%20%22internal%22%7D)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20link.text%20%3D%20AtomicString(label)%5Cn%20%20%20%20%20%20%20%20else%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20el%20%3D%20ET.Element(%22a%22%2C%20%7B%22href%22%3A%20href%2C%20%22class%22%3A%20%22internal%22%2C%20%22data-obsidian-resolved%22%3A%20%22true%22%7D)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20el.text%20%3D%20AtomicString(label)%5Cn%20%20%20%20%20%20%20%20return%20el%2C%20match.start(0)%2C%20match.end(0)%5Cn%5Cn%5Cnclass%20Citations(InlineProcessor)%3A%5Cn%20%20%20%20%23%20%60%5B%40key%5D%60%20groups%20were%20formatted%20at%20build%20time%20by%20the%20site%5C's%20citation%20plugin%5Cn%20%20%20%20%23%20(index.js%20scans%20the%20source%20for%20them)%3B%20unknown%20keys%20stay%20literal%2C%20as%20in%20notes.%5Cn%20%20%20%20def%20__init__(self%2C%20md%2C%20citations)%3A%5Cn%20%20%20%20%20%20%20%20super().__init__(r%22(%3F%3C!%5B%5C%5C%5C%5C%5C%5C%5B!%5D)%5C%5C%5B(%3F%3A%5B%5E%5C%5C%5B%5C%5C%5D%5C%5Cn%5D*%3F%5B%5C%5Cs%3B%5D)%3F-%3F%40%5C%5Cw%5B%5E%5C%5C%5B%5C%5C%5D%5C%5Cn%5D*%5C%5C%5D(%3F!%5B%5C%5C%5B(%5C%5C%5D%5D)%22%2C%20md)%5Cn%20%20%20%20%20%20%20%20self.citations%20%3D%20citations%5Cn%5Cn%20%20%20%20def%20handleMatch(self%2C%20match%2C%20data)%3A%5Cn%20%20%20%20%20%20%20%20cited%20%3D%20self.citations.get(match.group(0))%5Cn%20%20%20%20%20%20%20%20if%20cited%20is%20None%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20return%20None%2C%20None%2C%20None%5Cn%20%20%20%20%20%20%20%20return%20self.md.htmlStash.store(cited)%2C%20match.start(0)%2C%20match.end(0)%5Cn%5Cn%5Cnclass%20RawCode(Preprocessor)%3A%5Cn%20%20%20%20def%20run(self%2C%20lines)%3A%5Cn%20%20%20%20%20%20%20%20text%20%3D%20%22%5C%5Cn%22.join(lines)%5Cn%20%20%20%20%20%20%20%20text%20%3D%20re.sub(r%22%3C(code%7Cpre)%5C%5Cb%5B%5E%3E%5D*%3E%5B%5C%5Cs%5C%5CS%5D*%3F%3C%2F%5C%5C1%3E%22%2C%20lambda%20m%3A%20self.md.htmlStash.store(m%5B0%5D)%2C%20text%2C%20flags%3Dre.I)%5Cn%20%20%20%20%20%20%20%20return%20text.split(%22%5C%5Cn%22)%5Cn%5Cn%5Cnclass%20Comments(Preprocessor)%3A%5Cn%20%20%20%20def%20run(self%2C%20lines)%3A%5Cn%20%20%20%20%20%20%20%20%23%20Superfences%20and%20HTML%20blocks%20have%20already%20stashed%20their%20contents.%20Protect%5Cn%20%20%20%20%20%20%20%20%23%20inline%2Findented%20code%20too%3B%20comments%20may%20otherwise%20span%20paragraphs.%5Cn%20%20%20%20%20%20%20%20text%20%3D%20%22%5C%5Cn%22.join(lines)%5Cn%20%20%20%20%20%20%20%20definitions%20%3D%20%5B%5D%5Cn%20%20%20%20%20%20%20%20pattern%20%3D%20(%5Cn%20%20%20%20%20%20%20%20%20%20%20%20r%22(%3FP%3Ccode%3E(%3FP%3Cticks%3E%60%2B)%5B%5C%5Cs%5C%5CS%5D*%3F(%3FP%3Dticks)(%3F!%60)%7C%5E(%3F%3A%20%7B4%7D%7C%5C%5Ct)%5B%5E%5C%5Cn%5D*)%22%5Cn%20%20%20%20%20%20%20%20%20%20%20%20r%22%7C(%3FP%3Ccomment%3E(%3F%3C!%5C%5C%5C%5C)%25%25%5B%5C%5Cs%5C%5CS%5D*%3F(%3F%3C!%5C%5C%5C%5C)%25%25)%22%5Cn%20%20%20%20%20%20%20%20%20%20%20%20r%22%7C(%3F%3C!%5C%5C%5C%5C)%5C%5C%5E%5C%5C%5B(%3FP%3Cfootnote%3E(%3F%3A%5B%5E%5C%5C%5B%5C%5C%5D%5C%5Cn%5D%7C%5C%5C%5B%5B%5E%5C%5C%5D%5C%5Cn%5D*%5C%5C%5D)%2B)%5C%5C%5D%22%5Cn%20%20%20%20%20%20%20%20%20%20%20%20r%22%7C(%3FP%3Ctask%3E%5E%5B%20%5C%5Ct%5D*%5B-*%2B%5D%20%5C%5C%5B)%5B%5E%20%5C%5C%5DxX%5D(%3F%3D%5C%5C%5D%20)%22%5Cn%20%20%20%20%20%20%20%20)%5Cn%20%20%20%20%20%20%20%20def%20replace(match)%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20if%20match.group(%22code%22)%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20match.group(0)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20if%20match.group(%22comment%22)%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20%22%22%5Cn%20%20%20%20%20%20%20%20%20%20%20%20if%20match.group(%22task%22)%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20match.group(%22task%22)%20%2B%20%22x%22%5Cn%20%20%20%20%20%20%20%20%20%20%20%20name%20%3D%20f%22ewan-inline-%7Blen(definitions)%20%2B%201%7D%22%5Cn%20%20%20%20%20%20%20%20%20%20%20%20definitions.append(f%22%5B%5E%7Bname%7D%5D%3A%20%7Bmatch.group(%5C'footnote%5C')%7D%22)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20return%20f%22%5B%5E%7Bname%7D%5D%22%5Cn%20%20%20%20%20%20%20%20text%20%3D%20re.sub(pattern%2C%20replace%2C%20text%2C%20flags%3Dre.M)%5Cn%20%20%20%20%20%20%20%20return%20(text%20%2B%20%22%5C%5Cn%5C%5Cn%22%20%2B%20%22%5C%5Cn%22.join(definitions)).split(%22%5C%5Cn%22)%5Cn%5Cn%5Cn%5Cnclass%20Callouts(Treeprocessor)%3A%5Cn%20%20%20%20def%20run(self%2C%20root)%3A%5Cn%20%20%20%20%20%20%20%20%23%20Before%20inline%20parsing%20so%20titles%20retain%20bold%2C%20links%20and%20other%20Markdown.%5Cn%20%20%20%20%20%20%20%20for%20el%20in%20list(root.iter())%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20if%20el.tag%20!%3D%20%22blockquote%22%20or%20not%20len(el)%20or%20el%5B0%5D.tag%20!%3D%20%22p%22%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20continue%5Cn%20%20%20%20%20%20%20%20%20%20%20%20first%20%3D%20el%5B0%5D%5Cn%20%20%20%20%20%20%20%20%20%20%20%20match%20%3D%20re.match(r%22%5E%5C%5C%5B!(%5B%5C%5Cw-%5D%2B)%5C%5C%5D(%5B%2B-%5D%3F)%5B%20%5C%5Ct%5D*(%5B%5E%5C%5Cn%5D*)(%3F%3A%5C%5Cn%7C%24)%22%2C%20first.text%20or%20%22%22)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20if%20not%20match%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20continue%5Cn%20%20%20%20%20%20%20%20%20%20%20%20kind%2C%20fold%2C%20title%20%3D%20match.groups()%5Cn%20%20%20%20%20%20%20%20%20%20%20%20el.tag%20%3D%20%22details%22%20if%20fold%20else%20%22div%22%5Cn%20%20%20%20%20%20%20%20%20%20%20%20el.set(%22class%22%2C%20%22callout%22)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20el.set(%22data-callout%22%2C%20kind.lower())%5Cn%20%20%20%20%20%20%20%20%20%20%20%20if%20fold%20%3D%3D%20%22%2B%22%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20el.set(%22open%22%2C%20%22%22)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20first.text%20%3D%20(first.text%20or%20%22%22)%5Bmatch.end()%3A%5D%5Cn%20%20%20%20%20%20%20%20%20%20%20%20title_el%20%3D%20ET.Element(%22summary%22%20if%20fold%20else%20%22div%22%2C%20%7B%22class%22%3A%20%22callout-title%22%7D)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20title_el.text%20%3D%20title%20or%20kind.capitalize()%5Cn%20%20%20%20%20%20%20%20%20%20%20%20el.insert(0%2C%20title_el)%5Cn%20%20%20%20%20%20%20%20return%20root%5Cn%5Cn%5Cnclass%20ObsidianTree(Treeprocessor)%3A%5Cn%20%20%20%20def%20__init__(self%2C%20md%2C%20resolver)%3A%5Cn%20%20%20%20%20%20%20%20super().__init__(md)%5Cn%20%20%20%20%20%20%20%20self.resolver%20%3D%20resolver%5Cn%5Cn%20%20%20%20def%20run(self%2C%20root)%3A%5Cn%20%20%20%20%20%20%20%20%23%20After%20inline%20parsing%3A%20syntax%20in%20code%20and%20raw%20HTML%20remains%20literal.%5Cn%20%20%20%20%20%20%20%20for%20parent%20in%20list(root.iter())%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20if%20parent.tag%20in%20%7B%22code%22%2C%20%22pre%22%7D%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20continue%5Cn%20%20%20%20%20%20%20%20%20%20%20%20for%20i%2C%20child%20in%20enumerate(list(parent))%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20child.tag%20in%20%7B%22p%22%2C%20%22li%22%2C%20%22blockquote%22%7D%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20parts%20%3D%20%5B(child%2C%20%22text%22)%5D%20%2B%20%5B(e%2C%20%22tail%22)%20for%20e%20in%20child.iter()%5D%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20el%2C%20attr%20in%20reversed(parts)%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20text%20%3D%20getattr(el%2C%20attr)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20not%20text%20or%20not%20text.strip()%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20continue%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20marker%20%3D%20re.search(r%22(%3F%3A%5E%7C%5C%5Cs)%5C%5C%5E(%5BA-Za-z0-9-%5D%2B)%5C%5Cs*%24%22%2C%20text)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20marker%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20setattr(el%2C%20attr%2C%20text%5B%3Amarker.start()%5D)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dest%20%3D%20child%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20child.tag%20%3D%3D%20%22p%22%20and%20not%20%22%22.join(child.itertext()).strip()%20and%20i%20%3E%200%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dest%20%3D%20parent%5Bi%20-%201%5D%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20parent.remove(child)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dest.set(%22id%22%2C%20marker%5B1%5D.lower())%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%5Cn%20%20%20%20%20%20%20%20for%20el%20in%20list(root.iter())%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20if%20el.tag%20%3D%3D%20%22p%22%20and%20el.get(%22id%22)%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20Native%20marimo%20rewrites%20bare%20%3Cp%3E%20but%20every%20%3C%2Fp%3E%3B%20keep%20IDs%20valid.%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20el.tag%20%3D%20%22span%22%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20el.set(%22class%22%2C%20%22paragraph%22)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20if%20el.tag%20in%20%7B%22a%22%2C%20%22img%22%7D%20and%20not%20el.get(%22data-obsidian-resolved%22)%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20attr%20%3D%20%22href%22%20if%20el.tag%20%3D%3D%20%22a%22%20else%20%22src%22%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20url%20%3D%20el.get(attr%2C%20%22%22)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20el.tag%20%3D%3D%20%22img%22%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20alt%20%3D%20el.get(%22alt%22%2C%20%22%22)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dimensions%20%3D%20re.search(r%22%5C%5C%7C(%5C%5Cd%2B)(%3F%3Ax(%5C%5Cd%2B))%3F%24%22%2C%20alt)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20dimensions%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20el.set(%22alt%22%2C%20alt%5B%3Adimensions.start()%5D)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20el.set(%22width%22%2C%20dimensions%5B1%5D)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20dimensions%5B2%5D%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20el.set(%22height%22%2C%20dimensions%5B2%5D)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20Footnotes%20and%20generated%20TOC%20anchors%20already%20use%20rendered%20IDs.%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20not%20url%20or%20(url.startswith(%22%23%22)%20and%20(%22footnote%22%20in%20el.get(%22class%22%2C%20%22%22)%20or%20url.startswith(%22%23fn%22)))%20or%20re.match(r%22%5E%5Ba-zA-Z%5D%5B%5C%5Cw%2B.-%5D*%3A%22%2C%20url)%20or%20url.startswith(%22%2F%2F%22)%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20continue%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20href%2C%20entry%20%3D%20self.resolver.resolve(url)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20href%20is%20not%20None%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20el.set(attr%2C%20href)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20el.tag%20%3D%3D%20%22a%22%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20el.set(%22class%22%2C%20%22internal%22)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20elif%20el.tag%20%3D%3D%20%22a%22%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20el.attrib.pop(%22href%22%2C%20None)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20el.tag%20%3D%20%22span%22%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20el.set(%22class%22%2C%20%22internal-link%20is-unresolved%22)%5Cn%5Cn%20%20%20%20%20%20%20%20return%20root%5Cn%5Cn%5Cnclass%20ObsidianExtension(Extension)%3A%5Cn%20%20%20%20def%20__init__(self%2C%20resolver%2C%20citations)%3A%5Cn%20%20%20%20%20%20%20%20self.resolver%20%3D%20resolver%5Cn%20%20%20%20%20%20%20%20self.citations%20%3D%20citations%5Cn%20%20%20%20%20%20%20%20super().__init__()%5Cn%5Cn%20%20%20%20def%20extendMarkdown(self%2C%20md)%3A%5Cn%20%20%20%20%20%20%20%20md.preprocessors.register(RawCode(md)%2C%20%22obsidian-raw-code%22%2C%2024.5)%5Cn%20%20%20%20%20%20%20%20md.preprocessors.register(Comments(md)%2C%20%22obsidian-comments%22%2C%2019)%5Cn%20%20%20%20%20%20%20%20md.inlinePatterns.register(WikiLinks(md%2C%20self.resolver)%2C%20%22obsidian-wikilinks%22%2C%20181)%5Cn%20%20%20%20%20%20%20%20md.inlinePatterns.register(Citations(md%2C%20self.citations)%2C%20%22obsidian-citations%22%2C%20175)%5Cn%20%20%20%20%20%20%20%20md.treeprocessors.register(Callouts(md)%2C%20%22obsidian-callouts%22%2C%2026)%5Cn%20%20%20%20%20%20%20%20md.treeprocessors.register(ObsidianTree(md%2C%20self.resolver)%2C%20%22obsidian-tree%22%2C%206)%5Cn%5Cn%5Cndef%20install(context)%3A%5Cn%20%20%20%20import%20importlib%5Cn%20%20%20%20import%20marimo%5Cn%20%20%20%20native%20%3D%20importlib.import_module(%22marimo._output.md%22)%5Cn%20%20%20%20extensions%20%3D%20%5Be%20for%20e%20in%20native._get_extensions()%20if%20e%20not%20in%20(%22pymdownx.caret%22%2C%20%22pymdownx.critic%22)%5D%5Cn%20%20%20%20config%20%3D%20%7Bk%3A%20dict(v)%20for%20k%2C%20v%20in%20native._get_extension_configs().items()%7D%5Cn%20%20%20%20config%5B%22toc%22%5D%20%3D%20%7B%22slugify%22%3A%20heading_slug%7D%5Cn%20%20%20%20config%5B%22pymdownx.tilde%22%5D%20%3D%20%7B%22subscript%22%3A%20False%7D%5Cn%20%20%20%20config%5B%22pymdownx.superfences%22%5D%5B%22custom_fences%22%5D%20%3D%20%5B%7B%5Cn%20%20%20%20%20%20%20%20%22name%22%3A%20%22mermaid%22%2C%20%22class%22%3A%20%22mermaid%22%2C%5Cn%20%20%20%20%20%20%20%20%22format%22%3A%20lambda%20source%2C%20language%2C%20class_name%2C%20options%2C%20md%2C%20**kwargs%3A%20marimo.mermaid(source).text%2C%5Cn%20%20%20%20%7D%5D%5Cn%20%20%20%20md%20%3D%20Markdown(extensions%3D%5B*extensions%2C%20%22pymdownx.mark%22%2C%20ObsidianExtension(Resolver(context)%2C%20context.get(%22citations%22%2C%20%7B%7D))%5D%2C%20extension_configs%3Dconfig)%5Cn%20%20%20%20lock%20%3D%20threading.Lock()%5Cn%20%20%20%20native._get_markdown%20%3D%20lambda%3A%20(md%2C%20lock)%5Cn'%2C%20_obsidian_namespace)%0A_obsidian_namespace%5B'install'%5D(_obsidian_json.loads('%7B%22currentSlug%22%3A%20%22notes%2Fwearable-sensors%2Fwavelets-find-the-fall%22%2C%20%22index%22%3A%20%22%2Fstatic%2Fmarimo-links.json%3Fv%3D6dbe45cfcdf0%22%7D'))%0Aewan_obsidian_ready%20%3D%20True
ewan_obsidian_ready%0Aimport%20base64%0Aimport%20json%0Aimport%20zlib%0A%0Aimport%20anywidget%0Aimport%20marimo%20as%20mo%0Aimport%20numpy%20as%20np%0Aimport%20traitlets%0Afrom%20wigglystuff%20import%20TangleLatex%0A%0AFS%20%3D%20100%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20Hz%2C%20every%20signal%20in%20this%20notebook%0A%23%20The%20nine%20BME3740%20sample%20trials%3A%20IMU%201%20(trunk)%20acceleration%20magnitude%20in%20g%20at%20100%20Hz.%0A%23%20Written%20by%20%60scripts%2Fexport-wavelet-trials%60%20in%20Lab%2Fprojects%2Fmarimo%20from%20DATA%2Fwearable-falls%2Fbme3740-sample-v1.%0ATRIAL_DATA%20%3D%20%22eNqFfbuuLUty3K8I175z0PXKqqIpQ5AhyJDkETQuyCE14EuYGUKGwH9Xd0Vk74iz14DO6bP26tWPemZGRkb%2B9f%2F75V9%2B%2B%2Bff%2F%2FJXv%2FyXf%2F3j%2F%2F3tj3%2F3n%2F7%2Bt3%2F6p19%2B%2FeUc%2FurPf%2Fy33%2F%2F6yz%2F%2F9g%2B%2F%2FNVfXz%2F2%2BvX5p%2FRzuM5hBv6d52%2B14bDPHyfO6BOHeg51nMN1PsXCp4KrdHzq%2BHng08Klr40DLra3HFbDKbPhdws3KvoQA3cY%2BHnt%2Biw8H5csTR%2BFP8MZi2%2BMExfvPdAoTR%2Bo4N6D9%2BZ3Ib%2Fjp3WFXnpW%2FBzXxBusa2h785SGTmDDsVVwn1m0%2BdiYbMWOLhy4Mi7c2VBb33jboxden01UtdMCZ67Ql9zyPBgv%2BcJduzGaNZCOr%2ByqsqQ%2FVsODLxzuhyw%2Frue%2FG00Wy27Zzi1jc%2BSxrYYcAo0VbKyGMwdbhH2DP3IU43Ha1KHGwcJ24UAa%2BNT4g6JXxijRIRm4y%2BrsXlyjhw1N7dcoNk55z6FDzJ6Yd8YMnZhNeeDDcajwB5XtL3OE83sGX1TuuTDLF7uraHPyGdE3q8iCkW27lg5x9lezpSW%2Fw%2B84tPIU%2FVm22dLRmcsUX7Toa2w8Hid56HyZbMM5vh599S7TMucEf81u0Afp%2BvKXrlvZlfmo3hZNF4ZcvrYsbYFLx1g65vkI%2FF3Xbs7ew%2FNj3C3c9e1ZXIs3jybrEJs%2B2AMY3lxzdYxzTc%2Fz%2BHLX0oWkVl3LKhe4Ip3JEZRzr0nrTllicrxxcdGVKMI60uYyV7ecDOPrx%2BiZQPPlysEO3U2vP3Rq5pbFFQzPVm3Bx0AJXoTDZmsDlK0j0A%2Bc2UOnJtt0Tu0lbrNsTK5TnKpsErYmW2vozn2FLM85ZotegyORA4Wfqm2aa30Y3cPmI1f8YWsZv2OTtqFzFTOdlgX7lCtn1%2FNzIPemGz0bpduyVKVFW7fNstoapdNm9q6X4qec5FggOMi3Lc0cgM26k8sLL92abEmTNgQbmr2V02LoHdJssncIs0RycdMzhsypNBas8boPHjcrlt6TD9mamlVdzaUcYDAaAi%2FOV82hy%2Fa%2BdOEIGhRDd%2BZYusUuPG6uZLzmEpOCl4zFu6KduQjQAis2gTnaujY651laerokwZgNHuRv7yRoshx3Psb5F0ZDcElHm4XdanG6dzMxc9Fsui2mqT10d%2BTOyU0egzO4VrevYZGmaNoXOmKaLf1N17XAIAxeSi0tmj6XbYdoezYBBgPGUqQhxF1ty2LAicUn5O7JbsnFsMsBDx%2F4auCNAoNgYGR0focmHniSweHF0YmLYHIO3HRg3md34TCwQsRQkxzPP9DxA6vfwNPhnSr6s2Jzrvhjg7nVLz7k%2BRv6uGLENQysjv7vWE86XqNjWWm41oW3KdivGl6n8Kvnw9qn7xYmy9rHJ1uwuBe6bWHpXPm7MyPWOv281hn9%2FPcM%2FjXOSFkD5x3%2F57YMz5U6LlhPY61yXvtewco5nJ6F5Y%2Fvr%2FOXe6l%2BHuYeDM83Uc5wvLuink%2Fn9KjnJcaZ2wOjF10d18aH03WjnI6p6J%2BKyVr7uVSLY5B02CVxLfzujMR73J6nCiyhhTMULX3dV7k9kB77q%2BWinIGMwdf7Gen3I%2B%2F7xNrvM8qPe%2FI9PxuP99L6cWL6syPcJ9Rz3t1U7T7sco%2Bvcl%2F3%2BWpf7fnqbuHnt73fa239Ufo9XvqPe7cvv8YzcOt9aOPukf4j1t2f9cczXuo99O6mq%2FdDjXPnx5quP64W5zbPOLufpAU64axSgYUp6mmn0c7IvXevjhdF9xb0OcZ4rc%2FFrlLXOWBYb7RTuS99vnsOZdxNc3%2B6B9VzeOz2%2B8DpwClyNvO1MIlKcORPDGS6bOgP9g7n1Bnq%2BzTR3Yvnbs%2B2cn%2B6%2Bru83Z%2FiWrJko8Pvyb3PUz7vUdrpqvq8x30Yp5fKxiXH%2Be4eCs935ZmY9yFGOX88PVj6NfHdud1jhd6HAbuXpjdn67rUnQ6uY1yeiJKg9RYG2zzXelaR%2BwbXGTfXPje%2Fnon5vF05h4bX6QvtW%2FDr86njuWKeUzCmrnbc6XGhP9b56r7WOeCxFj0C9MuGy8HV%2BT6i4zEMcLvrtM3V8MwF5uL95vJp7iJ79L7o2p8bcf95LNHnYgNPhNfCYxa8f6nn2S%2BadtiCOxbAjm2Dj35hNEz8uuIp0UJo841bJ7DTzOXCUjzYO9jgOu22MmVj7rAuWuPii5U8V2tsKWnasTExSs%2Fh3hT%2B5t9%2FfbG5%2F%2Fzb3%2F7jfwjO5Q7IzZ825VZohZsSRxz2hZXulOEU03AE8zp4CHXqafm%2Bfpq5TWmsTjP5l3pnod5qM6guzGbtZikm%2FFfMYya4MuSJgmMsETyDltI2C32hYYYtX2gTRNtf3lG2G83UqkDQLN%2FdramufkJEzczypi1jAM0Q53XRI0pESRGUtBiX4Tqh%2BACfIHEmA2rSkCkCD6ajzZfh1J0Gc1obE8YoVVzh1zZnpyicuIgJ8NDN7SFWcVnvJyYy1PdqW4300O6guzwMr2QvdoNg%2BGBdYU6Dn2niJ5pR1NFLhNO9JoOtp%2FSmNmSOWZ029tDs12ZTI12jqc3PqXE5sF7k1WGdYdzQeCfMVdWfTutdG6F%2FmfpNHfmFJiY4nZ1Xp%2FY2Z1ub3zGvF5%2BwuEG2P%2BfekMHPV4%2BpYH3ix7safDjM0wzzc3XeJSRtw10fNkfhstZn1w1bfAlhXNa7CTAWXYq5hGHwB6Gi0GWULxmc0zncDWLi2xWF3Pi4K8Ec7dwXnHZcyKYEh9W0NTYhR%2FMYDelMRIUQX4IAVQcQfallI4LdMH350E7kjV6sagkqu9GQOyHLkfbTO2DpIO7%2BAedj33PF41q6DPeaup7lw1aZ1puPlbNI156cA6NZEEuHZNXbZL%2By79h6nJjd3M307nXXW8XCAQy5TYtz%2BfBNjHJ%2FLQr0e944DJ%2Bhys0fA%2FCxXbhh0JIheELPmusMDxmMqHCOAxYTjHDgPhXtV%2FAmddBNV%2BSaqxEeZScySPesi0naC01FjDe8csMY7vQ38JgVXd9ooKPBEj2f2aaPgYYrN9iiF8wLYAQwXRd%2F3TH9CPuy0zbjb1PNkZHABz7AqT49eLvH53CdjTzaGQ7zOj04EVGbgJNinB68PbJ%2BDueuo5wm6tjJO%2Fq6j9OhDS5TO5PjtlnhX8ONPc864jT9wNoy9lkx2Kl9wO09l1gbY7yd5wGWfQ%2BydnzJs8DcBvLxJft5jY5Tejvt2jB72jr3bu2szHWd3bNi%2B6r9LD4Vs7RiwlfY2R3w6j1lj6Owj6fQ5umr2%2FYrx8sdjyNcbz%2FtOVyPO3zF%2Bds1%2BvPp9k7XfeiPYx2tPh7y%2Fb7z8aHX8Z7r42w%2B5x3vfD1rTblbvj6ja94P9Pjl4%2FiXdTW4hsdCWMB47r3yOCkPolSe8f7cO56l8b70Y0DeV3lstfsOZT63nU%2Bc8777uMp52Plru93Mu0%2Fb%2FW738L0%2FPf7F4%2BMft%2F7ALs8TjcfHaQGfFUsJ%2FKqJXSswUsc%2B02RgCZoXTkEXj3X25HsknVHWzh72RHseb%2Fb477WdRn4AkcdBvuTQylkDaj0vfLfXWRHKsZFXA7KDNTcwKMc4Q2lcpxtngT%2FaT2%2FWUvUwjit5H47TDVe%2F9OMF3q7n%2BWM5%2FnJ5IKTnKljGItJ2P10QQAEknBAVfi8etsNxpZ9cMW87ZmrFodH9g50ytvnvtMexcnNXokVHC5d4IvxDmIEdax8BvwKrqBGxzaDfC%2BMefxeeK5bcUeHNnz64CnCPgsXvwsVyf%2BTSxjtgHYLtQqiyoWkwOzcMhFEU3l0BpzzgeV%2B612yCABjwD6L0PCXAsnpxp%2B66R40uUfCBPzbsOX1rqPtZo44Dj%2FbFfoJWHHT2%2BKpALjBMr7qWIhDsLOIwXTdnuHwDu1%2FfPGzx2Afbixg69i062phttI%2BqBjse5%2BegOC3hqIM5nG0Ez4YXwxJ2AafA7MoNZmNUccACD1u0OwpahGOHcHWEBgU33f9NaI54De7APZzITtfQ%2BMAUatyu6dnRfeCTcSaRBUArk1OvER%2FExk4rAe27Q%2BOUNG02zYqkSYQYLIvLGf28y%2BKIXU0VGjrBAUazLLraXMob4kPTdIhp8fekNRQdgotdhD4ZYlyERdpxGw6Kne8IC2orADJgh0bpZnmhuTVInvOEK1D2SBV4Yn65cvR6Ni0lg0tyhcLI3sSwslmNnqB2NnbmtMFobm%2F2%2Fm7iK0fOp6lRTVjkRNHpR6R5T4Oehjz9PfXD0PWcYzTxGYJTokzyiMb3EO0b0AptaBq1BJD4GDBj9xBXMSN8jQabjnJs%2FAeIPCZx6ChHEGVcjBq9YMOZeEvHFP40ZEA2mKa92eKLXeBSKIhYF6NqfWu87%2FV%2F1YCGm7mLej35ipjWjJIF17OlwAiCMZuxvOyVppS3nXti1zApvZm8ezX6BRaxqbAbo%2FLcBzkfksPGn6fT6SDVUCz1v%2F3%2B7%2F%2F8HxMdw1gUnEahAGj8FAgX3DSJe%2F0D5JA8FAVKVnOUsRpTwtzwVg1m0FmRwf2i%2FiCfnMySrVwfc%2BGT%2BKDfW1Q4AaiiaEB62V15oc0Otf2MciV%2B4rFqc3gTv0zIaMksDU4i7vUK0ERbst8aB4cuc%2BgWwg1l%2BYI2lABTwlA%2FQ%2By6UkWSsxLqhRelYCRcgXbZjHZwveVempM0ZP3KHWUs4Xy9MX0jaiT9RMfbLhKwya3hJ0JkyNO%2ByJAu%2FdXRR4UTEgN1NiyfK7lFXdohiQL5rjLPch3WBX0K1tgNJWQH882IYyzlK%2BRo7dZVnAQ%2F8YmNWdBsEVuGNhlRZsQHINaJh0pqqzaX8hrKLskoxHDycv9AWxy28HRHFbtCyE23t5d%2FvIWrlBzgq8nW8YLoGIf9A1MzurFshiJLYbRfW7fSsJCYSXJtklFrpLFpaB47iT3HzqVPcmk0JM0FJbPRjGrT2KSK0SfvzAIkOh0SThxGO%2BWG2ZXe%2FnKjhUGcLZ7%2BksP10x5%2FKfrHF%2BVy0C1IxgHCu3LYyyq%2FbMjk44QsF8su1HUZS9xQGiIJwFO2r7XNUKU9EBZDUuZ8GEknozPJZO8aUrGhpytLEeJmMcC46n3Z8qGG3fvGRhdO8nD9bvnm%2BMg%2BtQ3Cdg1jOrEFilEcCS3zYZc%2B1mU87e0brdGX08ytwp566XnVQgBT75Bbpu2%2FydLUEMfyG9VtC2HTYW%2BB34Trm%2BUcXJYU4vtY0VmPS74UQYOf5%2FzwClOt69ysjJkaZkm8oTLNxAi1k19zRx8l50OzdVdjvGywjY2DXtFrDZqHnDiM0hx4SsDnwayqpDIwVtL0WckmOyeuNZS1ha6%2Bkn1G9gVYIWV%2FoR4L%2BOk9aSuccy7Whw1yvJANoK0dOkhpgL%2FBVrrGgVhKPZBG6Yf5U64DhF4T1JKSPrbwl4kjBfAgEkYuLBgNVk%2BSVio4NR0gH847j31ed8I8mEADMKEvuGPXBG8tAMZjNJRKV4ruxVLOJdGc3FaVtZqbWFprUxa6SCJeSdvyYI7nnQa8uU40jN2AmAE4YFcPhCjqTNbMl4Ua6fFgWFwEuZTUODANAanvhvcDAe32uvRQsIyfodIxYs4qtzA%2BF1aoRUfsgpXGzjhfgBx45toCD%2BYe7udTPReamP23YXcQ43Juco%2FHeT6dJQE0Q%2Fqz2LFBDtwg5y0AxGe9vS8eYBSCFHiuujjY62n0hb5eFy5%2BnqV1XOM05AR5M860Z8ChgVlasR7Vfs6v9bxvq2f6VOygF0J5h2N3HxriDvVMwYIUpHPjCmpZbwuHhmsshCnOYUQlUPv0fVsHk4zrsMlmOyDgus7wuKf9A%2F1f1wk8XI%2B380Qc2hMBKPvEJtp1TilP3Kw%2B4%2BkJaJQ4PL92%2F%2FD54zw49z1g8fPn%2FPuFDxPwOgGH%2BeBW7cd4sKP24PbPKedp759VAFztgMz3Lo%2FAyYE0GjDwuMr%2BYkUGgkkddnfDpGsDcZ52hkUHmt0xmMc4lldEgC6KyVPBJAWicG%2BVmLoXOJAMe9xt3H5EGwjigGSHPLC6zmLRzocRByCZZ67dlvZz3gAGXMixLVwasHRf6ZA1vORz%2FjwrTj9xh0JvoFUE3xBHwUyajNa1vWRdu8iHw7K0sfUMoD2dHMPnxHpesEzQJxu30EFYDoh%2B4eKLZRDMwnb6aF68GQOeCu8zvkjcj05Rr1wKuT4iroNDBaJc8B4lo%2FW0%2FxAhPdckV3hXen8gFT5D6gD8eGPbzToRy7Qs8HqEvviuVQI1hNr7FgCa9ivX2IsoGciR1wCMDUJfGWxmjGMEccriKckPO%2B2GM7gHEfnYagwxIE1SFOJHz%2FrzXH9gByOPHUZGI1uVHU5TEst8yRYKwcAZiMbCy8D1tdSQGAx5iH%2FfGTEicEwHk06t5QYOYq1kKdIyZJvPIl2VBhHxc5Lb6dUS6YU5ssHZxZL1BXZuhebZWUudGDr%2FQa7rtpyzZAsqWvc%2F%2FvAP%2F%2Fs%2Fhuteh1JJHW%2F24RTI03JqkhFqblazkEBRryAhvKVgllnDU%2FNBk1wyzaIoBlim82I%2Fz6RIy4oliMI5WY3xqJSgRCEcXWjO1KtqnxOXKZosmyAyiQrEeIvl5g1D7tpU3k5YNnDXM%2BgJNeOWLk3PW19UC3HIk%2BxpbdS9bZs1yzJQTUc4%2FcqwLNLq2EFRDGiK0%2Ffi0cbWM8BmW%2BZY95uGcu0yyjAtqUeh12W8qohufk0ThGdl0HkYr7pIUHQ6ubSIV5TNRtiRS%2BEQMu1PCC%2Bdhba%2BZcpnz22LoaXzNXRa0pYuDoP4NF0yMbuP%2FKlZgekj2jQdnqa7DRbn0DDMSTN%2BZ%2F%2Beq%2F96vjYvk0iGmc%2FJx52zC6awxXVwh3JIQtlq1oCea1oMW5pVQUrNC3xRfwXKcws1euil5KxpeY4u%2BaA4XEK%2BCW9PUxGw9WZOoxK2D2GOqUhlNUJxwlmGXhBeSiBlfUhKJTo0VUUh%2BoeFkL2WXdltyl8Gkk%2FP0jeogS1WdBNNJKXEB7b8Uu5rkgmM28ifFU%2FkM%2B7hqrY9bOOmTuUgDyMTsi%2BTRG2rKvskOa2aVp3JrctCT4bDTKf2G7XSx7plY2foZYb4vi%2FKy9jBfAG%2FtW2KNM%2BM1pyG5QoCnhJfvpNx83ksidUSVA1yV6bEsln27uCaw1i9ayT1POyOaWVYpvxlFNtlMY81P2goTOMHhwWrQqnn3TOx1eB52bcqpZB0%2FctFAcZXWmem1Sfxaij7tC5LLV8anru0HTL%2Fl5tN0gcMS%2FRMcdfHSH7JMBa4i8hoWPJ9S3vn3MGMum6xt2FJJFyHNefmJ%2FS0227WlZGRdoiFtIvbnsbPsPidJXeHkpLSqcCenrlyDH9mTvI2cRSlA5u9E0nH74lfHvcphAecKxpZEkSvkiwBv25PIVL1ND1xzWpLOyfAepMLD6GsSV7cRfA1%2BEhNTNYVSs32fBCua9WkXOhJTJKXxSJi1IKcwbB3THR%2F6YY1XOrGzggJoZL8Q3eTjh3bBNtVJ90HDcQktI6hPUJ5G4yOZ55ap68Y4mjiZ3UQdcXf0OEdbzUyPVuieux3JuBHm5k3eVxSAHtXCGUw9KvKA2%2FKx8IDgCPGHNuD7gWhPIAS%2FczrxQxUckkL8phBjAaL9TzZxNCd8H0nnOWAKRTMEUZPTrTRAv5zGzoHyakAxxvQdNpOzFBdIOhVIjlIPOxkYBIdb8g%2FZdJnJMHn5Vpm6jR3o5Y2t7IMQcW7V7wNpAmwGajTx27pZE6fsdqw6rZ6ZlY9M6oCniFkWYFEVaQr3ofnZwWufwWu2gCANEy6vpB1fXbFgVkwAV2sEcwVPRzgOMTTFgcvmxOA5NPnD0p5MMvyjJh6P9c6f7z6c0qr9zXb%2FYp3t%2FdnkMz7cD0YZP1xmM8kth7y9ej1gSGfFeC%2BwDosa1iIi5h9QRSggme5D%2Fe09rM4jHLoVes6nXavqedBn5lwP1N9rrhraQcIPXfpyMk%2Bv63z4EYFUFEZp49bP5zWioSBtZDfCjrn4bKXB0zeX1b%2BAu5Gb4n40TgwVQnAewG29AikLR%2Fw9X4nQH%2FAFOO899Ww4hHSAfizkOY6EbLA8EK%2B6aI7MjoektQxDNLoCNqcYE9ZQCTbBDB5HqRNZjwgQsFABf44DghZmN6N8xAhaqCVNoB0awGzxIvOA3%2BXVnE40FiJi%2FnMTB%2FWmEdjRCzDN1XEEYguKkEt7Wsu4snzUW7TYKrvFkeJRL5c%2FYpafrAyB7N5sQdWoL7YORZ2NnobzENOzh53iR5GQLEnIRzJTbm8yfMHNCwZJzsbWxVbLmkypBQydLoEeCVZOZKNaR7lNL7S1S3ZUnAzBhyHiZ9tC4s3OT31o5rlHzXjPxSxJmj6k5F81S0D9CIDGvD4RTY7nWnyTrOHjTy4lc7LPZ9B1c1GpyWUlKyuDOG%2BvuIByW4nWzdxsGJQ5f%2F8w59%2F939%2B%2B7c%2F%2Ff53f%2Frzb%2F%2Fydy9Wef%2F7JwMrd%2F3uPv%2Fk4s%2F6AWGjIXE1s94dK%2FTcbPMB5occv22MIlNZyfFZBHxpzZ6nSo%2BP8iFnk0wgD%2FJnRlwYzUbZJal1Zc6BCR%2BlJ9zV0%2ByKs7zNZGnq3WglzZnBrsRomOAw1lfznNyhMNs2zKI6XWGrw0%2FHpqlLktgDxwbH7iiqgWOcjGGCVXTmmzOSmjaLKUaFIRAO59gAWQaKhaXNhkIA2TTGUNOX35Y%2FOasSqqat3s2w9%2BQXmjZk0nQMBFUBxZ9Qjm6M8GG53OHdv0ymzPzmaZTBbJquuoc2QMzZHN4a6xus%2FJJ1pqoG%2BFJrjfAT6GUQdfiZ3XrRUDJPbI%2F9Ib7gafdGp3eZLW%2B8pCIZgXipDF8zUGg6zDCVNVMNrTJYUGk4qbLYlHZnmm7VFoJq3L2qCSOv%2Fp7zqdd3sdOlcbJX%2FHEZYtqN3KjIkskdWgb%2B1DDDGMbyMiJi2KbrCOyI7yqIzstNB3g3S6k3bcxid5j2u2mpzWEEummv5cGXrjpjw2TMtlKlfiJFbsP6wzQCqnFumwVY7N7TGuCy3cRFDJf146cc7TBOdfMNY3xgU%2FsaPH2h1TQWE4wcJmg4TP%2B4Gsba3Jqz%2FmyWW2B6tcWXyk%2BrVfsg6zy%2BSQqaKMNL8986QcPm5DT4K5Xnug0yYzfGd5nofKpm2outmLSIqSSU%2BLAEmNCFGb3NNJaHauplYMYQOLMmtusKmy5OxphNoNQbqJhRkVk4XfCofIRmirs%2FEV6r9Zux1T8C%2F%2FMDHNvs%2BfhdOmFDeBxBEgJz3OlOVDXRyQnpeKGOx%2BzMuIUn1DA2iJNUnFkwGy8SeArT7sFtaxQqqwBwAiwXMNmwrZFwWLCFQUdsI%2BdqbcjYwRRe2OXWzjvML0rhgjuzkL%2B8yKZJWGLC%2B66JmLxiaaufXliQ71tIBVtowIUeWgB018DtBiXXAH8FBfhwP7rCpTBzF9QcDEAyAjEDrsU3h5gfeVBDko2JcZHJ1Jm7R0iSEoVYgRsZo1QxRPPVULkrJs8OPF4qKKJbhySzDeb6MeUV%2BERnija8wIlc7kl9s%2F3CDcXSsFI1MtOk1N5bpJsCBalNJNIW6JoLXy14nEjvvxYAGLK02mH8FJDMSucfIQdXDypzn9nwCdzYQAp8B%2BFrA9SpVAfAmXOJYhyApcLM%2BU6u1xextkC67D7gE9CiyjT4BejofApcd4C5G50gEw541sDz8MyJxzrE3bIK1QOK%2FBrydx1J9w1p9oB4jmTB02pgvUHOAARHhgUKgKGE6QnMF1NMdjlZMqIwaBt6mMOay0Oy3lNVlrpqWE8KRSqI4WNxwlpIQhkzvyEskepsoILeB3ISVXQTw6uR6EXsiThLk3sTyc35Y2q%2FMVW3Kn08FdlKtawRgrWnUinjKAr3MO2N3HDfLLKigGa%2FbKIZSUxi%2BKFLpIGIPpVHidcz9ZTIdYWyBcjFQ5W0GbpAZ4K7eF3JnX5t%2FaO1AEGMVjPX%2FasXk2IWwp5khPDlKG0T%2BalfiomdenvMiGWzknyWIJna1Qz5dhW5SdiUJAESt9SqHESc2DXN1WC3pdWpyFEGmtf8kJ4dKzOHmdp7Pq0v%2FczrmiOVAr4IOKG5JKHQ3wjNJ%2BRrm%2F5bZkSSuMpE9emC2kJpo%2F3GJEayllI1iLTBqTTOlKonWz15j00t1PoXwYhpiaOvS6hThesHBYKa5Xc0VSNOYz8rdLTvlg5FFtgrl%2BUQ0owmoyB13gWgGsZ%2FCRWGfk3xqsYPQ2hL5zqZOGNqL1IMg4sOFIGxYGECIDJ2WyecB%2ByGYeivK8ZXJSORwRSylgxaX0sXQdoGDQOJZgC1TCmPPGi84Xd1UsppmNWGhQNjp874IlffdgNen7b%2FZq4JI07Yt2EdNGwwDXkWsA4KxGdKkF1dEdLAIY7pUDHSWzmXbhCy6VDB7YzvnN2qYU%2Bv%2B6jB1HXWq7qPskrdvNaJgdSN79YJhZNIXxe%2Bi0HNmhO1OmtfRVil1mNlVMSwK2Z9mbAWEGoq7aK4DewRINL47urAyhGETOnV0QTxYLJNx2CiMjTMRiRY0FiqmjK3YA5OqMYsumYViRnIQFlwTFMDGlFB7A0D2Rkw68ADXgBGFwbWGkzxgMwvjPzVEPa9oJ9Vz4xbkLBiiG8BlFkIC63F%2B2A2XrgknxGryYVH6BB4RThmNSoPUbhjaPJuwX5zUdCZZjauQuukwEoPmu549sm7IyydeTF9fN1vIXiUpjuaYzHUC%2BOkYIkqQ20UMPvpfOQqQIsi1WmYPrHM%2FkdWA16HJj7Vi6hlJFYLk8vjpZ8eavwUdkXpOLQvfYoUzyXdnzI4pEUaFEN58rgsmDaUWMBPI%2BVklgjW7EyLNd87GSGUX1lqVXKbHZqdGaVquOa%2FPqTyP%2F3hz38xTpM0che9%2FKm2gaE9lm%2Bc7n1SQY2H3RRtWFYDxKpdfSga86YTagOH1cPalyLhXRUm35oWtv1luSTjraQ6ru5LLtzaFD4yWuG7y6pSYfX02%2F29Jd5CSqZKO%2BJ7XZhdLJRqtYcSZrPqPrQ1KAW9TQ10mv6T4b3ZeGQRLaW%2BzWZ4vaFeW5UxponLTg%2F%2FaF2L90UMkCQtjvoyoWSsV%2FrYgHRPnp7GlLfUBxcUpo3drAZZM9JnVfEkRQj7%2FlA1zWNuhj459mURmCgW8NF0jPqhXNVPWc1NCYxJo546dbehaCYMcm3jtmrnLVPOTsK5CbZYKv80LYRlskrLpAWs66cXf1IqY3Lkp%2Bk%2BDIPxppWAqYqR%2FyTd2ucHiWqbVW8Hm6ZOfKoTMz4I4ioU7DRwLbSUPTus%2BJzRQl8Z4aoWeDUSeTMNAa5xBv54aoitWa98h5XAml4crCoPfhh9r1oJquJE0G55Hcqpp%2FdWPkmmWzRkfph0WpJuKMX1hXA%2FODbdc%2FWtu7qGIUyQYCuBIsfupdpqqAORaF6fIu%2BUZ9BHvVRnfFkgKETumtJ9mUFJTyxUK%2BelbdqqSM4CPWmTuUnXFTskKaOliSQbJXt31wg40Z1KiqqqyyZRlRqMXXUGL0vlymoT3JDlBBosVIktSYxU932xtAFFNHtJHcODjmgcKLUnr2Fe3hKm6mhWsGupWga11nZy2YYKAqZ8JL5T2g1TKihnFpn7JropwVTITAjdmnafgZf5XSjk1UrYGq7MYJqms3DucTUf8aGknaFu7%2FLXhP9E9hjR6EgjpKu%2BUGjaFc0CGtE7N%2BghI5I0WC5G1IGjpZxi%2BBYAvFTsO62DzA1Zuuou06onAzkrcBVNGaMMY%2B8mxz5tvzSJlSmpXynpPoUclYeqqQDZaVe33cqYOkXoHqn%2Fsi1QXUxG0UvnZfKoBe6MvZM8paIrXFjkrqpt0up3HfhaPih6NYuDb629tTRrKLUhOMaX6YcM3ciy%2FNjoVj%2BiqgE8twVG1wdRlmI102xfCCFOEeFb0%2FT2u%2FQDRSQT2w6R7ISGA7U0mPl9La3Mg7%2BxMg%2FrrfSlehWZfhwq3sTSfQltkYReypebfXviCMhBdwLeXlIul9amWpuhO0z0C%2BtQwWgss3yFui40OcVAahsiRzGGeD1jvxE0pFauRcon4A6csDBfFv4dzKQHEAD15NsBhUDGaYqJWO3EdjxhtExkjQdwgUDebQzUsYJk6gBVO5W4AYNGRbGpdob8wPTpZxRVPGUF9teuc58OsDArXZ2Gn2iJDgnbuQ4GhShWbfTjKTV7ITZ14lHtUFbns4vXH3WfilLPUG0%2FxgMD9h%2Bj3EO8A4rs9%2Bn3XtXuW0bhieVp7lPuKgcd3gzT8UIndOQYVARt22g4XHzPARWJBiVxKJqfLuzot5lhjYMA1Xk2tYGw33xGRf1R5tmyVwAMHCeqFdBmGFAQKHEhwgccaanozypnLwmM3tEglw4afsdkGRiMAeUdClscqOlglwifAeubZ3u%2FGx%2BfKhjhCPwFZHAQES0om1UqhUUwgwDGIdCxEIFfA3%2BE2s2aqbfSRAxiVAXrVwrybBVvhuA0wpkXNZVphCD3wUobKh8nd5zcXqcEyjKCMotVylDmVfJQu2QHpmpoU11j6glkHUj%2FQZJPtWbh6yApT7FYNd1qGWatfE%2BqS%2BvEwYqhMr0xq5WjtkKwdA1IN7ayF7TSkpjcNJszZStHMQ7ywzz%2B3Z%2F%2F9Xf%2F9Iff%2F2UCcmkffPZuIanLts4lWYrDaktOy8cLZQHZpurFeENpjwkcdUsr%2FNIw%2FimhL7OwyocqPrV9V7Tjr7fFiX5qAQUic%2B%2B10kE%2FqYKZA16Mruiu%2Bv5uXCVmVRSheMvAx3dH2Tl%2Bntdn%2Fu6w%2BtfNhR%2BKgUDlAzHcOFUqPpfwoykcrPG92NErJqmF05rVVPlJyM6s1bBnTA0AKw5j6hcfEn5Nf9Ji6V5A1NQGejc%2FVzuQbus2SXAzbGf5cMWr2qetro0pir386vGhDrRZmT9lB48pWohm1xrAPPZfUi580Q4rX55yD90y58N%2BrXBEt3pnpjviJYQtFzwRDWf%2FWe61S3hcBrClC2saCt00c71o3NYZ3cPuE5%2BI8BbydRisGJZ42faQkoNeKNrm79q68lUrnmyAOB2m5mxi3R7XsCRmjfm9RRWapVBPGxNG2%2BV4L77OGaXVtExfnN%2ByzZdVuIryXbDhRZ0%2BFRG8DIfzoutd027zpZeNkqqk9ZfQ7s5y%2BSa1kVN3GJbpKKlVss5R1Uyitnsd8fGhhOG2dH1j8ObIW05B3h%2B0g%2Bd3gdvSrXSUPV61mde8JPe2KIiijXNbcrtTyrV8XbNJ3JdVgxP67DAZjbG%2F796vgsj8kN8U0xBh649tnBoL3LwwqGVJOaBczdLrtpdSIapa0lQovTzrboXONF%2BMEnCxX5NilGWxrOLiMD31UCEe6nWRx0Ve0%2FC6bRYYINhAKTNSyUjIDKs2R8Mqczi6qIVlrqEV8iuORnnySpV42U7mlJV0u0xEqOggj0Ksskm11BZdqKU1ySQ7ywsfyAAO%2FZYi2GSajqqlYbwAarWM%2FzdV9guIzaLcpLoQUiDP%2BDhX4Mst3CyJD8A6Frdz1hjuXbHjlYUVtiQwk9aAPO1VUZW7ISO9nuVmgrQwUShkAruYDX8cdDL5N5R26kAXmABfUAQK4pho2CNG%2BcAKqAB2uuX2elED%2BjR2gEkSoGIEqJcBpYEApSsPcMEwk1kYHN4nqgotcEBXQk6ESpdyAsnsqb2LZmpFSaPO7GlIHw7kREPbsR5QIutB9XTbQe8F2TdAWwhoXAZ4dnCxLxB1rgl9iVXpYp%2BLwS1GCnMd51p1HV%2B84eYNBKJ1yLS9H65en4e1M67zxwGRxwDHdxbAEAdTvyfCOlXBnms8Y%2FJR9txHCfS80orDF5pIjh19ok74%2FiIblY53aJpAscDumrBU7vX5yKJCVSKG4i0Di92AzzrAxxgYIwPu1hlLAcZ1NCJIDbXSUZquFIybM3RRR24gdBBAHAK50LORz0M%2FOYS%2BRL0EqmhSAIP1hUg54vzipIstkw5mykLO5MJKvEZYoTrM42uHEopTaXt%2Flf0qF5LYszJYJXO8St0kdNsVp9%2FKwWQgGBrr9OuMwx%2BPIJ50rgHR3w4YpSOlvkNzoJwwTwuUkQuIL7OKNhFLmtsVSwgkGxb2%2B5f%2BNJpgoRzVLPGUJFKGKyBjUVDtewDsIWcXbdEQ0SKllnsh5yzJfGQsVSodAJY9Ex2TH1ju3Gc5neCnYQW89yeodqBrIHaxgNpOEInXxXSKRkAYGRD8DgsK5YUzB58ZF1hz6kWdj7MmoRzezNKFgPf6wmw60%2Bme2oEpdmYulA5GOZS8Dr4g9GZPt5VJnj8HDrMpoPIBDvkiLMdqR1kzkkUpgasNKKiuq3ylHpSO3AFKmw5wpseltlOBrXBRVYRZLyRqwlBF%2BHSRl9uwswHUhvjxztpoZakYOQyVFl%2FnJ1xI4juS8JnYsivWTCqc5gHxwsohx0JYNPRSXX9rabOpdlHbL0f%2BTLyvMpkL45ACMBO01Xv%2FnZLSw90YuFmuAZWK4ZBLrxgi4Myu1nBANs5AG1US3Zm%2Bw%2B%2FA7IPWOh8ohuQRkaaPoMVFxjyUIbpEEBZTimBFFpL2MIhJ0lxUcodpAJsAysjXomQNSIZY2zZTGaqYRw1IcyEJEg%2B4NMZMaaBRZCm8KAZEMXHI7FbMjcefeJRT5iOdEkc7ej4JCveHgN7LOrtygMhaEPgAk76DtVjQ6AnXg8U%2BEXWuKKZ5P34HJRcUXpbWSmn2lclcJ%2BUFOTHQHsG8wTymTjAtg4tC49ibVsfcIJNx4h0XGgqDjuEuiLJc1%2BopqH9mllYfBJB%2FDfAAvkrRne2E%2BwjsFuh5DNYD5KSoVkltKEPiVflUY5FmeyJwTBcoM2WgvzSRkz5RNFhdlUaT2NQ2lQvq7VcpMcZ7VwwelvLrNDhYOWxpLZ%2FU3ahTpJeHppMy1Dg1WyILjfF83jvL9yodLn%2FQ1lfAbqSMUlPEhpGJtPk5NMibRgwF%2FcK3opQ2CUqb2jnJsC6ZqfYlXJ0pBZ75HRodyGbA4sjUliztqKlPo6ryQ9Y9lkyYHCBVE2I4WjJNdJjCsCauI71xe86Il6gOFWPMGEdyGrSGDpNfIllmxm3jgKIEOrkQZP8k9c6EK4sCluQo0zG4%2BPMsNLdF6%2FZFCO3SsS307VC1SWuQaJI%2F30q4ZPkSOrgrKTJf94nkVRmkGwbEmXdMR5hI2tTCeW9Bd%2BdeNaNbmNix0m7DcomTB7N7qoe%2F1B22QiaCDUuCqo6fW50oK96ZTbOrlZYtWuJ6m%2FLe0LzzZUwLet6c5RSMy9QrTepeWRlyy%2FJUlYlOqfKm8%2FhNqxoyiLhGLRtKIxtNC%2BEMo%2ByZdndXRZOlooUZieRtCNHO0BtEsx62Np9NtfI5deND2nYW3Ax9b4a6lsYH%2F%2Fvvf%2Fvj70xJ3YKDXyL5Bdm1zI99axiUZMue77TiEme6lUxf1s%2BUxmGmE7WRmXRUrSxSW1aglAT%2FEHXHl9M3pKDtUnA9pT5lbQoTnW8a4thWBHXoy7q%2B0aUZdWMZ6nkpMTfHSpdB9UKbul02Xf%2FIXMry6lOC8wmPFhUwybHhJD1QBKlBRm1%2FgnC5%2FfNT5vyY3rKrfBBADIX2l%2BYGZPXWqsqmSQMcHgpQM4YFc7cVLZ1VKgBqjdZct9m6YWXchuntD9fG8LpgS9CEbYrvWbiMWbcG1%2BaayAj%2BMv5vuBiVxkdexHl8j4Ub4S0JmZeFl7rRfLeGnEwtomjkN4fStm1vfeXuvpbKbt81R%2BJTzd%2FLJKO9JLJFucKyP1g6l1tHSBu8caqibL%2FpHNBlwhzbAoLrAw1%2FVkttsZpdP2mIT7VOqtk4RSMNMcS8yvt0fdWh23xmABg3ISMYHliKD1Rs0yq2xOSfxOJccmbO7%2FJJmb5hNQejDqvfbsrxXQ0Cxr1NzG%2BpWtpb4VOTHXKNVNK%2FqV7n%2FlZMTKmJCfYG0GzaLQv10jyzSFttH0S8tmkReUFH4418qA3wk8DQUhH5XNSbRVKHF5MIHWKcmcXSdEILIKeGStMKmVlboeoetLQoYkyXlTazsZrQV1fFdn%2F07VoBn5SPUvDX0iEyslU%2FqKt1L9Bhq8vw%2BJ3VUCxGH96q9vTmyFjlg6lpW6lHpkVh05DPLVbzNFzaLLPn1Q5%2BpfjHB%2F74stLBRXVvlqqfvzWjqxp2YUr3xUjL27NDLK%2BnmaLa5WVuLOztQ9MYZ2EVU1c6y%2BtD2tvlc8AutrZl2Zkyvs2x6dloHv83Itr6XkYhUQ4T4fqwCL5Fli05hJ%2B6V2%2FRhXho%2FehkFxTlwHFoDJFuo9%2BXJHUuCtmeukgpYZ309UyXCGUTpdEI66RrqY7pxCRiISY%2BnyV0u47KlFG%2FlPmVptUyOQDaPznyhhWUsG20N6vYpC%2FJM2Dq0%2FzOhJqVRirLPZ5gBOmr7YtWynJdHfnuHZUgkZ2fctvQHb4C8HLnAQnLiCYV0FfJVWYFyYqc%2BwvJ%2BXESd2oc8YAGpdeBipOD9Oe3MDXwPqR1Yx%2BaFZX9xuYnoKSIHxJ%2FLNTAORgsYgCXGKcL%2Btz3dnJuhpjeIQs%2FISLEGhDybMj%2B6Sga2Y%2FGQId8UO8IRRK8hE27kHE92hk1rHzYYal2LPejndV0gBo%2FQFGHZk4H4b%2FBVavQNamAnBpiM4jOVQCuHd0xy6lSeGSz65PO%2F4Qv%2B6Ms%2FpTqa4%2BIdz3S4Be0gmYcUG0edHcelHZ0dAYKJFbIUZMUnfHbSSlsCiAQ3ehMdCeQ3xAJZ6ApRPYdiVAB62yWhpqV%2BAHAhwUUdWKdnZ2fNrtsqH4X8gEQb6L%2F2YYCb0wLICWbNIRrs3oe0HwEyfDHia0i4PYMGMOjM1K7EIg%2F%2B%2BdEz81y5lXUs64NxDcGDVPohd%2BT5oimXxOHE3ysiFUCoV8Q3drA0dc6Uy%2BgO9UD0W3En2qBXPcgso9pTJs1kmoOCgJKf0KDIWD%2FBxaNgBJMIFwZDTSDjkAx5OYb8hrOvAog0xMy33cfoPGZ1NFKQt9nLUDongGB9iXhlQG0ygy6BnCc0DzWGAatluAob1pyU6IKdunUX4huAteM72kAjPyFjOPuLBv49ccG8evCeCxCtpWa30WVTKuu2pESUyGUmVA6Kenju1haXwLBXf5YWLJ3qkh27sdZ7asrnvS%2F%2FviHv%2F3Hv0w0n0bs9TJ1pr3p9CF6EMNVTtdSLZ1qhOlwjUZTOS3qoqUtwxKHJAcN1SNKfr4JhdERHIrRbjMrXUu8GAF1qrrUK7sTYlds%2BTC9rss0z6N9wIyHApcWHk57qO4PTcFEf%2B7itE5Mivl1K7Se91vyR%2Bv6aM5vcrqmqZImZT3UxO4mGuFE6da%2Bi3xaPauwFPtq7Pc0YrzcmzF56e83dUKrVXNX6bTXXhSH9x1T5s7EslQ8l8feGiJj7Gp2FdoIC1qZjucQDGdbZgLNyBSrYnEaE3ZK5GGoPHqWAzDFiPTSzWR8ub39e1L4W1TBsOzSLBXC4wcKq7%2FU267mfXPksJrKadEJUs1H2tPu18y33FJmPfm4w3WFTcPF5LCnVbi8bF6mL1oE%2FHxzOwzaDFVGqYa4mrOqhOV34jYrUWVVrzwtw%2BCQMHimeXXMZenNSwW86a1yWtZhjfeBr%2Bx9XPVX1cZENwUb3VhfmQGTvMnuC43ZVS3alyBCVZ3DXCCKLCt0RpiR%2F2rPaR2MLIJl2tJvxtnQ7hmWgLMsr2EYXF8N140PfvxSOChTDKNauG5%2BlZAM1Qt6S8wZJDI%2BFFJLYu%2Fult80P9Ddp6oHv8JEVf94WVk1k4hYpi0wtstAd5EKfMUIwmpYKqn5ldwp3zv7TbmzyhOGPrX6oUZFGOonhkNGg4YVBayetqVwQO5ml2X%2FdNtQjQ7wblZGoW6mvrIVo36lr3xkWiZSNGPnm1ZiVamFoltkYgBF7%2FrGaYsCwN2YAcMCaNVAbj%2Ffaox4gdxtp1yW%2FB6Ks5NqoFVoqksWGCxtlQo4rlItc5ti7FSlLMOXGJSoFDVpIrFWkwQ5xPbO7bZ4GEJNNO6zSeHrGobhitRiSXL6BSo4ENbkgKPA2urgp0EXF2Gu1dMzWq%2FM326Y3OTI9xRfAEJDkROGZhv1uEdGwA6JaIC7BE8K2syL0AzoUgAe6JazXA%2B1U6coZvNJC3xtKM1O0PIm0qbv5eG4tMc136TBk99KfhuInpMcuS9SzXG6GnzbVjuUGRfY2RtVuY6T0%2Bc5ZUB3ccyDAQ24iwNlvALwREChMeA2QgB57EPdCdDgAs7zIMd7HLLuAMtr4JS%2BoSPZt4gqXx1gEN15zPgOFn%2FBulGANRWwky8sO%2BXYZxV2eIOf0uCtFmh0VsgINKivNmBTBdZtxWwrcB4ueOAXQqEX4OqCtQugSAEMcvXTNRUMxALEo1ZUlDsPWSHcW5F%2FUKFNWED07tB2qOXsNxXmfIVJxFeNqyDnoIEPeI%2Fu%2FoBn7de4L3bqwMWBjeLB7eJJlX%2F4hsd6bA%2FW9qBNs54TH%2BbfU2zu0LtOvbD6o1%2FnUJ9iX%2FXHeDCB%2BjzXPhXoFg7tOZT9fPXIbT6V6sYRBxhH3uAIcdfkft9OHkCTM%2FLmOkNjjjPMJnCFiMNBjIJhsxaYxWdg9XlGARMFGimPDVTvDily6vTAyl8woid2jwnTc2LKB1JVBnppBDn6E9%2FhFCysAXwqEFENgDQxcCYkIwJqtIHlOcB9jE2%2BduMfcQokDbCiTRAiFwq0JXMWNGHYlBODdkKqfoIXPlF1cCLcMHk%2BCKoTv1sXISVwjVH6bYLTQBbuIRkcbiaETitVREjYPPNtADwmB6gZLWppmYKtKaY9lcencPLIX2lZySDEJx7jZagerBpSta1m2b1jwzVzOwEIEcKqEnTuKr2bOg3FannTwuCOdqncy96K8lzKj8kQPPl%2BTXZWMrHCxAuyqoPIhXIPYVCrD%2BVzpL03xdNNWz25p189c8WLSomSNI2KaRw9r15rUgyR2aFWkp2G%2BzbRxmGG1DalxCKKhokCFXEM%2BECkBKd714cgPjtVJ6aZRaq2uNSkT3vI6s1mBD21h6wK0%2BiWsKsfsiZvVSM5CTOeIxqCWU3NBk0qUZlG%2BXDegWXWd%2FPwuri3r5VXbYoNQeDIP28JsxmCSjuceBPd6KJJ%2Fos6bk2JqTNZJc5B7d%2FTKF0mtmn2JaN6LFFLQDKl7YzaTNF0QM2VHPX6OvukeB8QeEnTvnoWKsKUgTkqAK8lCkpZGIUzdqp9X7zumGFsWy30TXJzKSZHb0QgDmiOh26iKsPkQYbJfszxQf6WgQFallvT8yPZ7cZlGilQ%2Bzf%2F%2Fjf%2FHyeSlvs%3D%22%0ATHEME_JS%20%3D%20r%22%22%22%0Afunction%20followTheme(root%2C%20onChange)%20%7B%0A%20%20const%20apply%20%3D%20()%20%3D%3E%20%7B%0A%20%20%20%20const%20b%20%3D%20document.body%2C%20h%20%3D%20document.documentElement%3B%0A%20%20%20%20const%20dark%20%3D%20%5B%22dark%22%2C%20%22dark-theme%22%5D.some((c)%20%3D%3E%20b.classList.contains(c)%20%7C%7C%20h.classList.contains(c))%0A%20%20%20%20%20%20%7C%7C%20b.dataset.theme%20%3D%3D%3D%20%22dark%22%20%7C%7C%20h.dataset.theme%20%3D%3D%3D%20%22dark%22%20%7C%7C%20h.getAttribute(%22saved-theme%22)%20%3D%3D%3D%20%22dark%22%3B%0A%20%20%20%20root.classList.toggle(%22dark%22%2C%20dark)%3B%0A%20%20%20%20if%20(onChange)%20onChange()%3B%0A%20%20%7D%3B%0A%20%20apply()%3B%0A%20%20const%20watch%20%3D%20new%20MutationObserver(apply)%3B%0A%20%20for%20(const%20n%20of%20%5Bdocument.body%2C%20document.documentElement%5D)%0A%20%20%20%20watch.observe(n%2C%20%7B%20attributes%3A%20true%2C%20attributeFilter%3A%20%5B%22class%22%2C%20%22data-theme%22%2C%20%22saved-theme%22%5D%20%7D)%3B%0A%20%20return%20()%20%3D%3E%20watch.disconnect()%3B%0A%7D%0Aconst%20NS%20%3D%20%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3B%0Afunction%20node(tag%2C%20attrs%2C%20parent)%20%7B%0A%20%20const%20n%20%3D%20document.createElementNS(NS%2C%20tag)%3B%0A%20%20for%20(const%20k%20in%20attrs)%20n.setAttribute(k%2C%20attrs%5Bk%5D)%3B%0A%20%20if%20(parent)%20parent.appendChild(n)%3B%0A%20%20return%20n%3B%0A%7D%0A%22%22%22%0APALETTE_CSS%20%3D%20%22%22%22%0A%20%20--pos%3A%20%23899780%3B%20--neg%3A%20%23AD6E60%3B%20--accent%3A%20%23E89A39%3B%0A%7D%0A.dark%20%7B%0A%20%20--pos%3A%20%23AEBD9F%3B%20--neg%3A%20%23DB8E88%3B%20--accent%3A%20%23E0BC7E%3B%0A%22%22%22
ewan_obsidian_ready%0Amo.md(%22%22%22%0A*I%20had%20Claude%20create%20this%20marimo%20notebook%20for%20visualization%20as%20a%20test%20of%20learning%20capabilities.*%0A%22%22%22)
ewan_obsidian_ready%0Amo.md(r%22%22%22%0AA%20fall%20is%20a%20slow%20change%20of%20posture%20with%20a%20sharp%20impact%20at%20its%20end.%20A%20wavelet%20transform%20measures%0Aboth%20at%20once%3A%20it%20slides%20a%20short%20wiggle%20along%20the%20signal%20and%20records%20how%20much%20area%20the%20two%0Ashare%20at%20every%20moment%20and%20every%20frequency.%20Below%2C%20sliding%20as%20area%20(%5B%5BConvolutions%7Cconvolution%5D%5D)%2C%0Athen%20a%20wavelet%20you%20shape%2C%20then%20the%20nine%20BME3740%20sample%20trials%20from%20a%20trunk%20%5B%5BInertial%20measurement%20units%7CIMU%5D%5D.%0A%22%22%22)
ewan_obsidian_ready%0Adef%20toy_signal()%3A%0A%20%20%20%20%22%22%2212%20s%3A%20a%200.25%20Hz%20swell%20with%20a%20brief%206%20Hz%20burst%20near%20t%20%3D%208%20s.%22%22%22%0A%20%20%20%20t%20%3D%20np.arange(0%2C%2012%2C%201%20%2F%20FS)%0A%20%20%20%20slow%20%3D%20np.sin(2%20*%20np.pi%20*%200.25%20*%20t)%0A%20%20%20%20burst%20%3D%200.35%20*%20np.exp(-((t%20-%208)%20**%202)%20%2F%20(2%20*%200.25**2))%20*%20np.sin(2%20*%20np.pi%20*%206%20*%20t)%0A%20%20%20%20return%20t%2C%20slow%20%2B%20burst
ewan_obsidian_ready%0Adef%20svg_path(xs%2C%20ys%2C%20x_range%2C%20y_range%2C%20w%2C%20h%2C%20close%3DFalse)%3A%0A%20%20%20%20x0%2C%20x1%20%3D%20x_range%0A%20%20%20%20y0%2C%20y1%20%3D%20y_range%0A%20%20%20%20px%20%3D%20(np.asarray(xs)%20-%20x0)%20%2F%20(x1%20-%20x0)%20*%20w%0A%20%20%20%20py%20%3D%20h%20-%20(np.asarray(ys)%20-%20y0)%20%2F%20(y1%20-%20y0)%20*%20h%0A%20%20%20%20d%20%3D%20%22M%22%20%2B%20%22L%22.join(f%22%7Ba%3A.1f%7D%2C%7Bb%3A.1f%7D%22%20for%20a%2C%20b%20in%20zip(px%2C%20py))%0A%20%20%20%20return%20d%20%2B%20(%22Z%22%20if%20close%20else%20%22%22)
ewan_obsidian_ready%0A_t%2C%20_y%20%3D%20toy_signal()%0A_amp%20%3D%202%20*%20np.abs(np.fft.rfft(_y))%20%2F%20len(_y)%0A_f%20%3D%20np.fft.rfftfreq(len(_y)%2C%201%20%2F%20FS)%0A_keep%20%3D%20_f%20%3C%3D%2010%0A_w%2C%20_h%2C%20_sw%20%3D%20620%2C%20120%2C%20200%0A_sig%20%3D%20svg_path(_t%2C%20_y%2C%20(0%2C%2012)%2C%20(-1.45%2C%201.45)%2C%20_w%2C%20_h)%0A_spec%20%3D%20svg_path(_f%5B_keep%5D%2C%20_amp%5B_keep%5D%2C%20(0%2C%2010)%2C%20(0%2C%201.1)%2C%20_sw%2C%20_h)%0A_burst%20%3D%20_amp%5B(_f%20%3E%205)%20%26%20(_f%20%3C%207)%5D.max()%0A_bx%20%3D%206%20%2F%2010%20*%20_sw%0A_by%20%3D%20_h%20-%20_burst%20%2F%201.1%20*%20_h%0Amo.md(rf%22%22%22%0A%23%23%20Fourier%20hears%20the%20burst%20but%20not%20when%0A%0AThe%20test%20signal%20is%20a%20slow%20swell%20with%20a%20small%206%20Hz%20burst%20riding%20on%20it.%20Its%20spectrum%20shows%20both%0Afrequencies%2C%20but%20the%20burst's%20peak%20carries%20no%20clock%3A%20the%20same%20%5B%5BFourier%20Transform%7Cspectrum%5D%5D%20fits%20a%20burst%20at%20any%20time.%0A%0A%7Bmo.Html(f'''%0A%3Cdiv%20style%3D%22display%3Aflex%3Bgap%3A28px%3Bflex-wrap%3Awrap%3Balign-items%3Aflex-end%3Bcolor%3Ainherit%22%3E%0A%20%20%3Csvg%20viewBox%3D%220%200%20%7B_w%7D%20%7B_h%20%2B%2016%7D%22%20width%3D%22%7B_w%7D%22%20height%3D%22%7B_h%20%2B%2016%7D%22%20style%3D%22max-width%3A100%25%3Bheight%3Aauto%3Boverflow%3Avisible%22%3E%0A%20%20%20%20%3Cline%20x1%3D%220%22%20x2%3D%22%7B_w%7D%22%20y1%3D%22%7B_h%20%2F%202%7D%22%20y2%3D%22%7B_h%20%2F%202%7D%22%20stroke%3D%22currentColor%22%20stroke-opacity%3D%22.12%22%2F%3E%0A%20%20%20%20%3Cpath%20d%3D%22%7B_sig%7D%22%20fill%3D%22none%22%20stroke%3D%22currentColor%22%20stroke-width%3D%221.4%22%2F%3E%0A%20%20%20%20%3Ctext%20x%3D%220%22%20y%3D%22%7B_h%20%2B%2014%7D%22%20font-size%3D%2211%22%20fill%3D%22currentColor%22%20fill-opacity%3D%22.55%22%3E0%20s%3C%2Ftext%3E%0A%20%20%20%20%3Ctext%20x%3D%22%7B_w%7D%22%20y%3D%22%7B_h%20%2B%2014%7D%22%20font-size%3D%2211%22%20text-anchor%3D%22end%22%20fill%3D%22currentColor%22%20fill-opacity%3D%22.55%22%3E12%20s%3C%2Ftext%3E%0A%20%20%3C%2Fsvg%3E%0A%20%20%3Csvg%20viewBox%3D%220%200%20%7B_sw%7D%20%7B_h%20%2B%2016%7D%22%20width%3D%22%7B_sw%7D%22%20height%3D%22%7B_h%20%2B%2016%7D%22%20style%3D%22max-width%3A100%25%3Bheight%3Aauto%3Boverflow%3Avisible%22%3E%0A%20%20%20%20%3Cline%20x1%3D%220%22%20x2%3D%22%7B_sw%7D%22%20y1%3D%22%7B_h%7D%22%20y2%3D%22%7B_h%7D%22%20stroke%3D%22currentColor%22%20stroke-opacity%3D%22.2%22%2F%3E%0A%20%20%20%20%3Cpath%20d%3D%22%7B_spec%7D%22%20fill%3D%22none%22%20stroke%3D%22currentColor%22%20stroke-width%3D%221.4%22%2F%3E%0A%20%20%20%20%3Ctext%20x%3D%22%7B0.25%20%2F%2010%20*%20_sw%20%2B%206%7D%22%20y%3D%22%7B_h%20-%201%20%2F%201.1%20*%20_h%20%2B%2010%7D%22%20font-size%3D%2211%22%20fill%3D%22currentColor%22%20fill-opacity%3D%22.7%22%3E0.25%20Hz%3C%2Ftext%3E%0A%20%20%20%20%3Ctext%20x%3D%22%7B_bx%7D%22%20y%3D%22%7B_by%20-%206%7D%22%20font-size%3D%2211%22%20text-anchor%3D%22middle%22%20fill%3D%22currentColor%22%20fill-opacity%3D%22.7%22%3E6%20Hz%3C%2Ftext%3E%0A%20%20%20%20%3Ctext%20x%3D%22%7B_sw%7D%22%20y%3D%22%7B_h%20%2B%2014%7D%22%20font-size%3D%2211%22%20text-anchor%3D%22end%22%20fill%3D%22currentColor%22%20fill-opacity%3D%22.55%22%3E10%20Hz%3C%2Ftext%3E%0A%20%20%3C%2Fsvg%3E%0A%3C%2Fdiv%3E''')%7D%0A%22%22%22)
ewan_obsidian_ready%0Aclass%20Slide(anywidget.AnyWidget)%3A%0A%20%20%20%20%22%22%22signals%20%2F%20kernels%3A%20%5B%7Bname%2C%20symbol%2C%20dt%2C%20y%7D%5D%3B%20kernels%20are%20centred%2C%20odd%20length.%0A%0A%20%20%20%20The%20browser%20computes%20the%20sliding%20area%20and%20draws%20three%20rows%3A%20kernel%20over%0A%20%20%20%20signal%2C%20their%20product%2C%20and%20the%20running%20output.%20%60%60position%60%60%20(0..1)%20is%0A%20%20%20%20the%20kernel%20centre%2C%20kept%20across%20re-renders.%0A%20%20%20%20%22%22%22%0A%0A%20%20%20%20_esm%20%3D%20THEME_JS%20%2B%20r%22%22%22%0A%20%20%20%20function%20render(%7B%20model%2C%20el%20%7D)%20%7B%0A%20%20%20%20%20%20const%20root%20%3D%20document.createElement(%22div%22)%3B%0A%20%20%20%20%20%20root.className%20%3D%20%22slide%22%3B%0A%20%20%20%20%20%20const%20bar%20%3D%20document.createElement(%22div%22)%3B%0A%20%20%20%20%20%20bar.className%20%3D%20%22bar%22%3B%0A%20%20%20%20%20%20const%20play%20%3D%20document.createElement(%22button%22)%3B%0A%20%20%20%20%20%20play.type%20%3D%20%22button%22%3B%0A%20%20%20%20%20%20play.className%20%3D%20%22play%22%3B%0A%20%20%20%20%20%20const%20sigPills%20%3D%20document.createElement(%22div%22)%3B%0A%20%20%20%20%20%20const%20kerPills%20%3D%20document.createElement(%22div%22)%3B%0A%20%20%20%20%20%20sigPills.className%20%3D%20kerPills.className%20%3D%20%22pills%22%3B%0A%20%20%20%20%20%20bar.append(play%2C%20sigPills%2C%20kerPills)%3B%0A%20%20%20%20%20%20const%20svg%20%3D%20node(%22svg%22%2C%20%7B%20class%3A%20%22plot%22%2C%20tabindex%3A%20%220%22%2C%20role%3A%20%22slider%22%2C%20%22aria-label%22%3A%20%22Kernel%20position%22%20%7D)%3B%0A%20%20%20%20%20%20root.append(bar%2C%20svg)%3B%0A%20%20%20%20%20%20el.appendChild(root)%3B%0A%20%20%20%20%20%20const%20unfollow%20%3D%20followTheme(root)%3B%0A%0A%20%20%20%20%20%20const%20ROW%20%3D%20%5B150%2C%2080%2C%20104%5D%2C%20LABEL%20%3D%2022%2C%20PAD%20%3D%206%3B%0A%20%20%20%20%20%20const%20tops%20%3D%20%5BLABEL%2C%202%20*%20LABEL%20%2B%20ROW%5B0%5D%2C%203%20*%20LABEL%20%2B%20ROW%5B0%5D%20%2B%20ROW%5B1%5D%5D%3B%0A%20%20%20%20%20%20const%20H%20%3D%20tops%5B2%5D%20%2B%20ROW%5B2%5D%20%2B%204%3B%0A%20%20%20%20%20%20let%20W%20%3D%20640%2C%20S%2C%20K%2C%20out%2C%20n%20%3D%202%2C%20m%2C%20c%2C%20yMax%2C%20kScale%2C%20pMax%2C%20oLo%2C%20oHi%2C%20idx%20%3D%200%2C%20pos%20%3D%200%3B%0A%20%20%20%20%20%20let%20dyn%20%3D%20null%2C%20raf%20%3D%200%2C%20playRaf%20%3D%200%2C%20playing%20%3D%20false%2C%20dragging%20%3D%20false%2C%20lastTs%20%3D%200%3B%0A%0A%20%20%20%20%20%20const%20X%20%3D%20(i)%20%3D%3E%20PAD%20%2B%20(i%20%2F%20(n%20-%201))%20*%20(W%20-%202%20*%20PAD)%3B%0A%20%20%20%20%20%20const%20Y0%20%3D%20(v)%20%3D%3E%20tops%5B0%5D%20%2B%20ROW%5B0%5D%20%2F%202%20-%20(v%20%2F%20yMax)%20*%20(ROW%5B0%5D%20%2F%202%20-%204)%3B%0A%20%20%20%20%20%20const%20Y1%20%3D%20(v)%20%3D%3E%20tops%5B1%5D%20%2B%20ROW%5B1%5D%20%2F%202%20-%20(v%20%2F%20pMax)%20*%20(ROW%5B1%5D%20%2F%202%20-%203)%3B%0A%20%20%20%20%20%20const%20Y2%20%3D%20(v)%20%3D%3E%20tops%5B2%5D%20%2B%20((oHi%20-%20v)%20%2F%20(oHi%20-%20oLo))%20*%20ROW%5B2%5D%3B%0A%20%20%20%20%20%20const%20f1%20%3D%20(v)%20%3D%3E%20v.toFixed(1)%3B%0A%0A%20%20%20%20%20%20function%20pills(box%2C%20items%2C%20current%2C%20key)%20%7B%0A%20%20%20%20%20%20%20%20box.replaceChildren()%3B%0A%20%20%20%20%20%20%20%20box.hidden%20%3D%20items.length%20%3C%202%3B%0A%20%20%20%20%20%20%20%20for%20(const%20it%20of%20items)%20%7B%0A%20%20%20%20%20%20%20%20%20%20const%20b%20%3D%20document.createElement(%22button%22)%3B%0A%20%20%20%20%20%20%20%20%20%20b.type%20%3D%20%22button%22%3B%0A%20%20%20%20%20%20%20%20%20%20b.textContent%20%3D%20it.name%3B%0A%20%20%20%20%20%20%20%20%20%20b.setAttribute(%22aria-pressed%22%2C%20String(it.name%20%3D%3D%3D%20current))%3B%0A%20%20%20%20%20%20%20%20%20%20b.addEventListener(%22click%22%2C%20()%20%3D%3E%20%7B%20model.set(key%2C%20it.name)%3B%20model.save_changes()%3B%20%7D)%3B%0A%20%20%20%20%20%20%20%20%20%20box.appendChild(b)%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20function%20prepare()%20%7B%0A%20%20%20%20%20%20%20%20const%20sigs%20%3D%20model.get(%22signals%22)%2C%20kers%20%3D%20model.get(%22kernels%22)%3B%0A%20%20%20%20%20%20%20%20S%20%3D%20sigs.find((s)%20%3D%3E%20s.name%20%3D%3D%3D%20model.get(%22signal%22))%20%7C%7C%20sigs%5B0%5D%3B%0A%20%20%20%20%20%20%20%20K%20%3D%20kers.find((k)%20%3D%3E%20k.name%20%3D%3D%3D%20model.get(%22kernel%22))%20%7C%7C%20kers%5B0%5D%3B%0A%20%20%20%20%20%20%20%20const%20y%20%3D%20S.y%2C%20k%20%3D%20K.y%3B%0A%20%20%20%20%20%20%20%20n%20%3D%20y.length%3B%20m%20%3D%20k.length%3B%20c%20%3D%20(m%20-%201)%20%3E%3E%201%3B%0A%20%20%20%20%20%20%20%20out%20%3D%20new%20Float64Array(n)%3B%0A%20%20%20%20%20%20%20%20for%20(let%20i%20%3D%200%3B%20i%20%3C%20n%3B%20i%2B%2B)%20%7B%0A%20%20%20%20%20%20%20%20%20%20let%20s%20%3D%200%3B%0A%20%20%20%20%20%20%20%20%20%20for%20(let%20j%20%3D%200%3B%20j%20%3C%20m%3B%20j%2B%2B)%20s%20%2B%3D%20y%5BMath.min(n%20-%201%2C%20Math.max(0%2C%20i%20%2B%20j%20-%20c))%5D%20*%20k%5Bj%5D%3B%0A%20%20%20%20%20%20%20%20%20%20out%5Bi%5D%20%3D%20s%20*%20S.dt%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20yMax%20%3D%201e-9%3B%20for%20(const%20v%20of%20y)%20yMax%20%3D%20Math.max(yMax%2C%20Math.abs(v))%3B%0A%20%20%20%20%20%20%20%20let%20kMax%20%3D%201e-9%3B%20for%20(const%20v%20of%20k)%20kMax%20%3D%20Math.max(kMax%2C%20Math.abs(v))%3B%0A%20%20%20%20%20%20%20%20kScale%20%3D%20(0.92%20*%20yMax)%20%2F%20kMax%3B%0A%20%20%20%20%20%20%20%20pMax%20%3D%20yMax%20*%20kMax%3B%0A%20%20%20%20%20%20%20%20oLo%20%3D%200%3B%20oHi%20%3D%200%3B%20for%20(const%20v%20of%20out)%20%7B%20oLo%20%3D%20Math.min(oLo%2C%20v)%3B%20oHi%20%3D%20Math.max(oHi%2C%20v)%3B%20%7D%0A%20%20%20%20%20%20%20%20const%20pad%20%3D%200.08%20*%20(oHi%20-%20oLo%20%7C%7C%201)%3B%20oLo%20-%3D%20pad%3B%20oHi%20%2B%3D%20pad%3B%0A%20%20%20%20%20%20%20%20pos%20%3D%20model.get(%22position%22)%20*%20(n%20-%201)%3B%0A%20%20%20%20%20%20%20%20idx%20%3D%20Math.round(pos)%3B%0A%20%20%20%20%20%20%20%20pills(sigPills%2C%20sigs%2C%20S.name%2C%20%22signal%22)%3B%0A%20%20%20%20%20%20%20%20pills(kerPills%2C%20kers%2C%20K.name%2C%20%22kernel%22)%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20function%20line(arr%2C%20Yf%2C%20from%2C%20to)%20%7B%0A%20%20%20%20%20%20%20%20let%20d%20%3D%20%22%22%3B%0A%20%20%20%20%20%20%20%20for%20(let%20i%20%3D%20from%3B%20i%20%3C%20to%3B%20i%2B%2B)%20d%20%2B%3D%20(d%20%3F%20%22L%22%20%3A%20%22M%22)%20%2B%20f1(X(i))%20%2B%20%22%2C%22%20%2B%20f1(Yf(arr%5Bi%5D))%3B%0A%20%20%20%20%20%20%20%20return%20d%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20function%20draw()%20%7B%0A%20%20%20%20%20%20%20%20W%20%3D%20Math.max(300%2C%20Math.round(root.clientWidth%20%7C%7C%20640))%3B%0A%20%20%20%20%20%20%20%20svg.setAttribute(%22viewBox%22%2C%20%600%200%20%24%7BW%7D%20%24%7BH%7D%60)%3B%0A%20%20%20%20%20%20%20%20svg.setAttribute(%22width%22%2C%20W)%3B%0A%20%20%20%20%20%20%20%20svg.setAttribute(%22height%22%2C%20H)%3B%0A%20%20%20%20%20%20%20%20svg.replaceChildren()%3B%0A%20%20%20%20%20%20%20%20const%20s%20%3D%20S.symbol%2C%20k%20%3D%20K.symbol%3B%0A%20%20%20%20%20%20%20%20%5B%60%24%7Bs%7D(t)%2C%20%20%24%7Bk%7D(t%20%E2%88%92%20%CF%84)%60%2C%20%60%24%7Bs%7D(t)%20%C2%B7%20%24%7Bk%7D(t%20%E2%88%92%20%CF%84)%60%2C%20%60%E2%88%AB%20%24%7Bs%7D(t)%20%24%7Bk%7D(t%20%E2%88%92%20%CF%84)%20dt%60%5D.forEach((txt%2C%20r)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20node(%22text%22%2C%20%7B%20class%3A%20%22lab%22%2C%20x%3A%20PAD%2C%20y%3A%20tops%5Br%5D%20-%207%20%7D%2C%20svg).textContent%20%3D%20txt%3B%0A%20%20%20%20%20%20%20%20%7D)%3B%0A%20%20%20%20%20%20%20%20for%20(const%20yy%20of%20%5BY0(0)%2C%20Y1(0)%2C%20Y2(0)%5D)%20node(%22line%22%2C%20%7B%20class%3A%20%22axis%22%2C%20x1%3A%20PAD%2C%20x2%3A%20W%20-%20PAD%2C%20y1%3A%20yy%2C%20y2%3A%20yy%20%7D%2C%20svg)%3B%0A%20%20%20%20%20%20%20%20node(%22path%22%2C%20%7B%20class%3A%20%22sig%22%2C%20d%3A%20line(S.y%2C%20Y0%2C%200%2C%20n)%20%7D%2C%20svg)%3B%0A%20%20%20%20%20%20%20%20node(%22path%22%2C%20%7B%20class%3A%20%22faint%22%2C%20d%3A%20line(out%2C%20Y2%2C%200%2C%20n)%20%7D%2C%20svg)%3B%0A%20%20%20%20%20%20%20%20dyn%20%3D%20node(%22g%22%2C%20%7B%7D%2C%20svg)%3B%0A%20%20%20%20%20%20%20%20update()%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20function%20fill(a%2C%20b%2C%20val%2C%20Yf%2C%20target)%20%7B%0A%20%20%20%20%20%20%20%20let%20run%20%3D%20%22%22%2C%20sgn%20%3D%200%2C%20last%20%3D%200%3B%0A%20%20%20%20%20%20%20%20const%20base%20%3D%20f1(Yf(0))%3B%0A%20%20%20%20%20%20%20%20const%20flush%20%3D%20()%20%3D%3E%20%7B%20if%20(run)%20target%5Bsgn%20%3E%200%20%3F%200%20%3A%201%5D%20%2B%3D%20run%20%2B%20%22L%22%20%2B%20f1(X(last))%20%2B%20%22%2C%22%20%2B%20base%20%2B%20%22Z%22%3B%20run%20%3D%20%22%22%3B%20%7D%3B%0A%20%20%20%20%20%20%20%20for%20(let%20j%20%3D%20a%3B%20j%20%3C%20b%3B%20j%2B%2B)%20%7B%0A%20%20%20%20%20%20%20%20%20%20const%20i%20%3D%20idx%20%2B%20j%20-%20c%2C%20p%20%3D%20S.y%5Bi%5D%20*%20K.y%5Bj%5D%2C%20sg%20%3D%20p%20%3E%200%20%3F%201%20%3A%20p%20%3C%200%20%3F%20-1%20%3A%200%3B%0A%20%20%20%20%20%20%20%20%20%20if%20(sg%20!%3D%3D%20sgn)%20%7B%20flush()%3B%20sgn%20%3D%20sg%3B%20%7D%0A%20%20%20%20%20%20%20%20%20%20if%20(sg%20!%3D%3D%200)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20const%20xx%20%3D%20f1(X(i))%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20(!run)%20run%20%3D%20%22M%22%20%2B%20xx%20%2B%20%22%2C%22%20%2B%20base%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20run%20%2B%3D%20%22L%22%20%2B%20xx%20%2B%20%22%2C%22%20%2B%20f1(Yf(val(j%2C%20i)))%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20last%20%3D%20i%3B%0A%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20flush()%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20function%20update()%20%7B%0A%20%20%20%20%20%20%20%20if%20(!dyn)%20return%3B%0A%20%20%20%20%20%20%20%20dyn.replaceChildren()%3B%0A%20%20%20%20%20%20%20%20const%20a%20%3D%20Math.max(0%2C%20c%20-%20idx)%2C%20b%20%3D%20Math.min(m%2C%20n%20-%20idx%20%2B%20c)%3B%0A%20%20%20%20%20%20%20%20const%20top%20%3D%20%5B%22%22%2C%20%22%22%5D%2C%20mid%20%3D%20%5B%22%22%2C%20%22%22%5D%3B%0A%20%20%20%20%20%20%20%20fill(a%2C%20b%2C%20(j)%20%3D%3E%20K.y%5Bj%5D%20*%20kScale%2C%20Y0%2C%20top)%3B%0A%20%20%20%20%20%20%20%20fill(a%2C%20b%2C%20(j%2C%20i)%20%3D%3E%20K.y%5Bj%5D%20*%20S.y%5Bi%5D%2C%20Y1%2C%20mid)%3B%0A%20%20%20%20%20%20%20%20node(%22path%22%2C%20%7B%20class%3A%20%22pos%22%2C%20d%3A%20top%5B0%5D%20%2B%20mid%5B0%5D%20%7D%2C%20dyn)%3B%0A%20%20%20%20%20%20%20%20node(%22path%22%2C%20%7B%20class%3A%20%22neg%22%2C%20d%3A%20top%5B1%5D%20%2B%20mid%5B1%5D%20%7D%2C%20dyn)%3B%0A%20%20%20%20%20%20%20%20let%20dk%20%3D%20%22%22%2C%20dp%20%3D%20%22%22%3B%0A%20%20%20%20%20%20%20%20for%20(let%20j%20%3D%20a%3B%20j%20%3C%20b%3B%20j%2B%2B)%20%7B%0A%20%20%20%20%20%20%20%20%20%20const%20i%20%3D%20idx%20%2B%20j%20-%20c%2C%20xx%20%3D%20f1(X(i))%3B%0A%20%20%20%20%20%20%20%20%20%20dk%20%2B%3D%20(dk%20%3F%20%22L%22%20%3A%20%22M%22)%20%2B%20xx%20%2B%20%22%2C%22%20%2B%20f1(Y0(K.y%5Bj%5D%20*%20kScale))%3B%0A%20%20%20%20%20%20%20%20%20%20dp%20%2B%3D%20(dp%20%3F%20%22L%22%20%3A%20%22M%22)%20%2B%20xx%20%2B%20%22%2C%22%20%2B%20f1(Y1(K.y%5Bj%5D%20*%20S.y%5Bi%5D))%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20node(%22path%22%2C%20%7B%20class%3A%20%22ker%22%2C%20d%3A%20dk%20%7D%2C%20dyn)%3B%0A%20%20%20%20%20%20%20%20node(%22path%22%2C%20%7B%20class%3A%20%22prod%22%2C%20d%3A%20dp%20%7D%2C%20dyn)%3B%0A%20%20%20%20%20%20%20%20node(%22path%22%2C%20%7B%20class%3A%20%22out%22%2C%20d%3A%20line(out%2C%20Y2%2C%200%2C%20idx%20%2B%201)%20%7D%2C%20dyn)%3B%0A%20%20%20%20%20%20%20%20const%20x%20%3D%20X(idx)%3B%0A%20%20%20%20%20%20%20%20node(%22line%22%2C%20%7B%20class%3A%20%22cursor%22%2C%20x1%3A%20x%2C%20x2%3A%20x%2C%20y1%3A%20tops%5B0%5D%2C%20y2%3A%20tops%5B2%5D%20%2B%20ROW%5B2%5D%20%7D%2C%20dyn)%3B%0A%20%20%20%20%20%20%20%20node(%22circle%22%2C%20%7B%20class%3A%20%22dot%22%2C%20cx%3A%20x%2C%20cy%3A%20Y2(out%5Bidx%5D)%2C%20r%3A%203.5%20%7D%2C%20dyn)%3B%0A%20%20%20%20%20%20%20%20svg.setAttribute(%22aria-valuenow%22%2C%20String(idx))%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20const%20schedule%20%3D%20()%20%3D%3E%20%7B%20if%20(!raf)%20raf%20%3D%20requestAnimationFrame(()%20%3D%3E%20%7B%20raf%20%3D%200%3B%20update()%3B%20%7D)%3B%20%7D%3B%0A%20%20%20%20%20%20const%20commit%20%3D%20()%20%3D%3E%20%7B%20model.set(%22position%22%2C%20idx%20%2F%20(n%20-%201))%3B%20model.save_changes()%3B%20%7D%3B%0A%20%20%20%20%20%20const%20setPlay%20%3D%20()%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20play.textContent%20%3D%20playing%20%3F%20%22%E2%9D%9A%E2%9D%9A%22%20%3A%20%22%E2%96%B6%22%3B%0A%20%20%20%20%20%20%20%20play.setAttribute(%22aria-label%22%2C%20playing%20%3F%20%22Pause%22%20%3A%20%22Play%22)%3B%0A%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20function%20tick(ts)%20%7B%0A%20%20%20%20%20%20%20%20if%20(!playing)%20return%3B%0A%20%20%20%20%20%20%20%20const%20dms%20%3D%20lastTs%20%3F%20ts%20-%20lastTs%20%3A%2016%3B%0A%20%20%20%20%20%20%20%20lastTs%20%3D%20ts%3B%0A%20%20%20%20%20%20%20%20pos%20%3D%20Math.min(n%20-%201%2C%20pos%20%2B%20((n%20-%201)%20*%20dms)%20%2F%207000)%3B%0A%20%20%20%20%20%20%20%20idx%20%3D%20Math.round(pos)%3B%0A%20%20%20%20%20%20%20%20update()%3B%0A%20%20%20%20%20%20%20%20if%20(idx%20%3E%3D%20n%20-%201)%20%7B%20stop()%3B%20return%3B%20%7D%0A%20%20%20%20%20%20%20%20playRaf%20%3D%20requestAnimationFrame(tick)%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20function%20start()%20%7B%0A%20%20%20%20%20%20%20%20if%20(idx%20%3E%3D%20n%20-%201)%20%7B%20pos%20%3D%200%3B%20idx%20%3D%200%3B%20%7D%0A%20%20%20%20%20%20%20%20pos%20%3D%20idx%3B%20playing%20%3D%20true%3B%20lastTs%20%3D%200%3B%20setPlay()%3B%0A%20%20%20%20%20%20%20%20playRaf%20%3D%20requestAnimationFrame(tick)%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20function%20stop()%20%7B%0A%20%20%20%20%20%20%20%20if%20(!playing)%20return%3B%0A%20%20%20%20%20%20%20%20playing%20%3D%20false%3B%20cancelAnimationFrame(playRaf)%3B%20setPlay()%3B%20commit()%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20play.addEventListener(%22click%22%2C%20()%20%3D%3E%20(playing%20%3F%20stop()%20%3A%20start()))%3B%0A%0A%20%20%20%20%20%20const%20at%20%3D%20(e)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20r%20%3D%20svg.getBoundingClientRect()%3B%0A%20%20%20%20%20%20%20%20const%20px%20%3D%20(e.clientX%20-%20r.left)%20*%20(W%20%2F%20r.width)%3B%0A%20%20%20%20%20%20%20%20return%20Math.round(Math.min(1%2C%20Math.max(0%2C%20(px%20-%20PAD)%20%2F%20(W%20-%202%20*%20PAD)))%20*%20(n%20-%201))%3B%0A%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20svg.addEventListener(%22pointerdown%22%2C%20(e)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20stop()%3B%20dragging%20%3D%20true%3B%20svg.setPointerCapture(e.pointerId)%3B%0A%20%20%20%20%20%20%20%20idx%20%3D%20at(e)%3B%20pos%20%3D%20idx%3B%20schedule()%3B%0A%20%20%20%20%20%20%7D)%3B%0A%20%20%20%20%20%20svg.addEventListener(%22pointermove%22%2C%20(e)%20%3D%3E%20%7B%20if%20(dragging)%20%7B%20idx%20%3D%20at(e)%3B%20pos%20%3D%20idx%3B%20schedule()%3B%20%7D%20%7D)%3B%0A%20%20%20%20%20%20const%20end%20%3D%20()%20%3D%3E%20%7B%20if%20(dragging)%20%7B%20dragging%20%3D%20false%3B%20commit()%3B%20%7D%20%7D%3B%0A%20%20%20%20%20%20svg.addEventListener(%22pointerup%22%2C%20end)%3B%0A%20%20%20%20%20%20svg.addEventListener(%22pointercancel%22%2C%20end)%3B%0A%20%20%20%20%20%20svg.addEventListener(%22keydown%22%2C%20(e)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20step%20%3D%20Math.max(1%2C%20Math.round((n%20-%201)%20%2F%20(e.shiftKey%20%3F%2010%20%3A%20100)))%3B%0A%20%20%20%20%20%20%20%20const%20next%20%3D%20%7B%20ArrowRight%3A%20idx%20%2B%20step%2C%20ArrowLeft%3A%20idx%20-%20step%2C%20Home%3A%200%2C%20End%3A%20n%20-%201%20%7D%5Be.key%5D%3B%0A%20%20%20%20%20%20%20%20if%20(next%20%3D%3D%3D%20undefined)%20return%3B%0A%20%20%20%20%20%20%20%20e.preventDefault()%3B%20stop()%3B%0A%20%20%20%20%20%20%20%20idx%20%3D%20Math.min(n%20-%201%2C%20Math.max(0%2C%20next))%3B%20pos%20%3D%20idx%3B%20update()%3B%20commit()%3B%0A%20%20%20%20%20%20%7D)%3B%0A%0A%20%20%20%20%20%20const%20rebuild%20%3D%20()%20%3D%3E%20%7B%20prepare()%3B%20draw()%3B%20%7D%3B%0A%20%20%20%20%20%20const%20ro%20%3D%20new%20ResizeObserver(()%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20if%20(Math.abs(Math.round(root.clientWidth)%20-%20W)%20%3E%201)%20draw()%3B%0A%20%20%20%20%20%20%7D)%3B%0A%20%20%20%20%20%20ro.observe(root)%3B%0A%20%20%20%20%20%20for%20(const%20ev%20of%20%5B%22change%3Asignals%22%2C%20%22change%3Akernels%22%2C%20%22change%3Asignal%22%2C%20%22change%3Akernel%22%5D)%20model.on(ev%2C%20rebuild)%3B%0A%20%20%20%20%20%20setPlay()%3B%0A%20%20%20%20%20%20rebuild()%3B%0A%0A%20%20%20%20%20%20return%20()%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20playing%20%3D%20false%3B%0A%20%20%20%20%20%20%20%20cancelAnimationFrame(raf)%3B%20cancelAnimationFrame(playRaf)%3B%0A%20%20%20%20%20%20%20%20for%20(const%20ev%20of%20%5B%22change%3Asignals%22%2C%20%22change%3Akernels%22%2C%20%22change%3Asignal%22%2C%20%22change%3Akernel%22%5D)%20model.off(ev%2C%20rebuild)%3B%0A%20%20%20%20%20%20%20%20ro.disconnect()%3B%20unfollow()%3B%20root.remove()%3B%0A%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%7D%0A%20%20%20%20export%20default%20%7B%20render%20%7D%3B%0A%20%20%20%20%22%22%22%0A%20%20%20%20_css%20%3D%20(%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20.slide%20%7B%20color%3A%20inherit%3B%20font%3A%20inherit%3B%20font-size%3A%2013px%3B%22%22%22%0A%20%20%20%20%20%20%20%20%2B%20PALETTE_CSS%0A%20%20%20%20%20%20%20%20%2B%20%22%22%22%0A%20%20%20%20%7D%0A%20%20%20%20.slide%20.bar%20%7B%20display%3A%20flex%3B%20gap%3A%2010px%2014px%3B%20align-items%3A%20center%3B%20flex-wrap%3A%20wrap%3B%20margin-bottom%3A%204px%3B%20%7D%0A%20%20%20%20.slide%20.pills%20%7B%20display%3A%20flex%3B%20gap%3A%204px%3B%20flex-wrap%3A%20wrap%3B%20%7D%0A%20%20%20%20.slide%20.pills%5Bhidden%5D%20%7B%20display%3A%20none%3B%20%7D%0A%20%20%20%20.slide%20button%20%7B%0A%20%20%20%20%20%20font%3A%20inherit%3B%20color%3A%20inherit%3B%20background%3A%20transparent%3B%20cursor%3A%20pointer%3B%0A%20%20%20%20%20%20border%3A%201px%20solid%20color-mix(in%20srgb%2C%20currentColor%2020%25%2C%20transparent)%3B%0A%20%20%20%20%20%20border-radius%3A%20999px%3B%20padding%3A%202px%2011px%3B%20line-height%3A%201.5%3B%0A%20%20%20%20%7D%0A%20%20%20%20.slide%20.play%20%7B%20width%3A%2034px%3B%20padding%3A%202px%200%3B%20font-size%3A%2011px%3B%20%7D%0A%20%20%20%20.slide%20button%5Baria-pressed%3D%22true%22%5D%20%7B%0A%20%20%20%20%20%20background%3A%20color-mix(in%20srgb%2C%20currentColor%2011%25%2C%20transparent)%3B%0A%20%20%20%20%20%20border-color%3A%20color-mix(in%20srgb%2C%20currentColor%2045%25%2C%20transparent)%3B%0A%20%20%20%20%7D%0A%20%20%20%20.slide%20.plot%20%7B%20display%3A%20block%3B%20max-width%3A%20100%25%3B%20touch-action%3A%20none%3B%20cursor%3A%20ew-resize%3B%20user-select%3A%20none%3B%20%7D%0A%20%20%20%20.slide%20.plot%3Afocus-visible%20%7B%20outline%3A%201px%20dashed%20color-mix(in%20srgb%2C%20currentColor%2040%25%2C%20transparent)%3B%20outline-offset%3A%202px%3B%20%7D%0A%20%20%20%20.slide%20.axis%20%7B%20stroke%3A%20currentColor%3B%20stroke-opacity%3A%20.12%3B%20%7D%0A%20%20%20%20.slide%20.sig%20%7B%20fill%3A%20none%3B%20stroke%3A%20currentColor%3B%20stroke-opacity%3A%20.38%3B%20stroke-width%3A%201.3%3B%20%7D%0A%20%20%20%20.slide%20.ker%20%7B%20fill%3A%20none%3B%20stroke%3A%20currentColor%3B%20stroke-width%3A%202%3B%20%7D%0A%20%20%20%20.slide%20.prod%20%7B%20fill%3A%20none%3B%20stroke%3A%20currentColor%3B%20stroke-width%3A%201.3%3B%20%7D%0A%20%20%20%20.slide%20.pos%20%7B%20fill%3A%20var(--pos)%3B%20fill-opacity%3A%20.6%3B%20%7D%0A%20%20%20%20.slide%20.neg%20%7B%20fill%3A%20var(--neg)%3B%20fill-opacity%3A%20.6%3B%20%7D%0A%20%20%20%20.slide%20.faint%20%7B%20fill%3A%20none%3B%20stroke%3A%20currentColor%3B%20stroke-opacity%3A%20.13%3B%20stroke-width%3A%201.3%3B%20%7D%0A%20%20%20%20.slide%20.out%20%7B%20fill%3A%20none%3B%20stroke%3A%20var(--accent)%3B%20stroke-width%3A%202.2%3B%20%7D%0A%20%20%20%20.slide%20.dot%20%7B%20fill%3A%20var(--accent)%3B%20%7D%0A%20%20%20%20.slide%20.cursor%20%7B%20stroke%3A%20currentColor%3B%20stroke-opacity%3A%20.45%3B%20stroke-dasharray%3A%203%204%3B%20%7D%0A%20%20%20%20.slide%20.lab%20%7B%0A%20%20%20%20%20%20fill%3A%20currentColor%3B%20fill-opacity%3A%20.75%3B%20font-size%3A%2015px%3B%0A%20%20%20%20%20%20font-family%3A%20%22IM%20Fell%20DW%20Pica%22%2C%20%22Iowan%20Old%20Style%22%2C%20Georgia%2C%20serif%3B%20font-style%3A%20italic%3B%0A%20%20%20%20%7D%0A%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20signals%20%3D%20traitlets.List(%5B%5D).tag(sync%3DTrue)%0A%20%20%20%20kernels%20%3D%20traitlets.List(%5B%5D).tag(sync%3DTrue)%0A%20%20%20%20signal%20%3D%20traitlets.Unicode(%22%22).tag(sync%3DTrue)%0A%20%20%20%20kernel%20%3D%20traitlets.Unicode(%22%22).tag(sync%3DTrue)%0A%20%20%20%20position%20%3D%20traitlets.Float(0.5).tag(sync%3DTrue)
ewan_obsidian_ready%0Adef%20series(name%2C%20y%2C%20symbol%3D%22y%22)%3A%0A%20%20%20%20return%20%7B%22name%22%3A%20name%2C%20%22symbol%22%3A%20symbol%2C%20%22dt%22%3A%201%20%2F%20FS%2C%20%22y%22%3A%20np.round(np.asarray(y%2C%20float)%2C%205).tolist()%7D
ewan_obsidian_ready%0Adef%20centred(width_s)%3A%0A%20%20%20%20%22%22%22Odd-length%20time%20axis%2C%20centred%20on%20zero%2C%20at%20FS.%22%22%22%0A%20%20%20%20half%20%3D%20int(round(width_s%20*%20FS))%0A%20%20%20%20return%20np.arange(-half%2C%20half%20%2B%201)%20%2F%20FS
Convolution is a sliding area
Lay a kernel k k k ||(k||) over the signal y y y ||(y||) , multiply them point by point, and take the area under the
product. Green is where they agree in sign and add; red is where they disagree and subtract.
Slide k k k ||(k||) along and the areas trace the output:
( y ⋆ k ) ( τ ) = ∫ y ( t ) k ( t − τ ) d t (y \star k)(\tau) = \int y(t)\, k(t-\tau)\, dt ( y ⋆ k ) ( τ ) = ∫ y ( t ) k ( t − τ ) d t ||[(y \star k)(\tau) = \int y(t)\, k(t-\tau)\, dt||] Drag across the plot or press play. A box averages, a bump smooths, an edge kernel fires on steps.
Strictly, convolution flips k k k ||(k||) first; wavelet transforms slide it unflipped, as here.
ewan_obsidian_ready%0A_pulse_t%20%3D%20np.arange(0%2C%208%2C%201%20%2F%20FS)%0A_toy_t%2C%20_toy_y%20%3D%20toy_signal()%0A_box%20%3D%20centred(0.4)%0A_bump%2C%20_edge%20%3D%20centred(0.8)%2C%20centred(1.0)%0Aconvolution%20%3D%20Slide(%0A%20%20%20%20signals%3D%5B%0A%20%20%20%20%20%20%20%20series(%22pulse%22%2C%20((_pulse_t%20%3E%3D%203)%20%26%20(_pulse_t%20%3C%204.5)).astype(float))%2C%0A%20%20%20%20%20%20%20%20series(%22step%22%2C%20(_pulse_t%20%3E%3D%204).astype(float))%2C%0A%20%20%20%20%20%20%20%20series(%22swell%20%2B%20burst%22%2C%20_toy_y)%2C%0A%20%20%20%20%5D%2C%0A%20%20%20%20kernels%3D%5B%0A%20%20%20%20%20%20%20%20series(%22box%22%2C%20np.full(len(_box)%2C%201%20%2F%200.8)%2C%20%22k%22)%2C%0A%20%20%20%20%20%20%20%20series(%22bump%22%2C%20np.exp(-(_bump**2)%20%2F%20(2%20*%200.2**2))%20%2F%20(np.sqrt(2%20*%20np.pi)%20*%200.2)%2C%20%22k%22)%2C%0A%20%20%20%20%20%20%20%20series(%22edge%22%2C%20_edge%20*%20np.exp(-(_edge**2)%20%2F%20(2%20*%200.25**2))%20%2F%200.25**2%2C%20%22k%22)%2C%0A%20%20%20%20%5D%2C%0A%20%20%20%20signal%3D%22pulse%22%2C%0A%20%20%20%20kernel%3D%22box%22%2C%0A%20%20%20%20position%3D0.42%2C%0A)%0Amo.vstack(%5B%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Convolution%20is%20a%20sliding%20area%0A%0A%20%20%20%20Lay%20a%20kernel%20%24k%24%20over%20the%20signal%20%24y%24%2C%20multiply%20them%20point%20by%20point%2C%20and%20take%20the%20area%20under%20the%0A%20%20%20%20product.%20Green%20is%20where%20they%20agree%20in%20sign%20and%20add%3B%20red%20is%20where%20they%20disagree%20and%20subtract.%0A%20%20%20%20Slide%20%24k%24%20along%20and%20the%20areas%20trace%20the%20output%3A%0A%0A%20%20%20%20%24%24(y%20%5Cstar%20k)(%5Ctau)%20%3D%20%5Cint%20y(t)%5C%2C%20k(t-%5Ctau)%5C%2C%20dt%24%24%0A%0A%20%20%20%20Drag%20across%20the%20plot%20or%20press%20play.%20A%20box%20averages%2C%20a%20bump%20smooths%2C%20an%20edge%20kernel%20fires%20on%20steps.%0A%20%20%20%20Strictly%2C%20convolution%20flips%20%24k%24%20first%3B%20wavelet%20transforms%20slide%20it%20unflipped%2C%20as%20here.%0A%20%20%20%20%22%22%22)%2C%0A%20%20%20%20convolution%2C%0A%5D)
ewan_obsidian_ready%0Ashape%20%3D%20mo.ui.anywidget(%0A%20%20%20%20TangleLatex(%0A%20%20%20%20%20%20%20%20latex%3Dr%22%5Cbegin%7Bgathered%7D%20%5Crule%7B0pt%7D%7B1.4em%7D%5Cpsi(t)%20%3D%20%5Ccos(2%5Cpi%20%5Ccdot%20%5Ctangle%7Bf%7D%5C%2C%20t)%5C%2C%20%5Cexp%5C!%5CBig(-%5Ctfrac%7B1%7D%7B2%7D%5Cbig(t%2F%5Csigma%5Cbig)%5E%7B2%7D%5CBig)%20%5C%5C%5B6pt%5D%20%5Csigma%20%3D%20%5Cfrac%7B%5Ctangle%7Bn%7D%7D%7B2%5Cpi%20%5Ccdot%20%5Ctangle%7Bf%7D%7D%20%5Cend%7Bgathered%7D%22%2C%0A%20%20%20%20%20%20%20%20parameters%3D%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22f%22%3A%20%7B%22value%22%3A%206%2C%20%22min_value%22%3A%200.5%2C%20%22max_value%22%3A%2015%2C%20%22step%22%3A%200.25%2C%20%22digits%22%3A%202%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22display%22%3A%20%22symbol%22%2C%20%22symbol%22%3A%20%22f%22%2C%20%22label%22%3A%20%22frequency%20in%20Hz%22%2C%20%22pixels_per_step%22%3A%204%2C%20%22color%22%3A%20%7B%22light%22%3A%20%22%23C47A1F%22%2C%20%22dark%22%3A%20%22%23E0BC7E%22%7D%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22n%22%3A%20%7B%22value%22%3A%205%2C%20%22min_value%22%3A%200.5%2C%20%22max_value%22%3A%2012%2C%20%22step%22%3A%200.5%2C%20%22digits%22%3A%201%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22display%22%3A%20%22symbol%22%2C%20%22symbol%22%3A%20%22n%22%2C%20%22label%22%3A%20%22cycles%20under%20the%20envelope%22%2C%20%22pixels_per_step%22%3A%208%2C%20%22color%22%3A%20%7B%22light%22%3A%20%22%23587571%22%2C%20%22dark%22%3A%20%22%238DAEB3%22%7D%7D%2C%0A%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20theme%3Dmo.app_meta().theme%20if%20mo.app_meta().theme%20in%20(%22light%22%2C%20%22dark%22)%20else%20%22auto%22%2C%0A%20%20%20%20)%0A)
ewan_obsidian_ready%0Af_wave%20%3D%20float(shape.value%5B%22values%22%5D%5B%22f%22%5D)%0An_cycles%20%3D%20float(shape.value%5B%22values%22%5D%5B%22n%22%5D)
ewan_obsidian_ready%0Adef%20real_wavelet(f%2C%20n)%3A%0A%20%20%20%20%22%22%22cos(2%CF%80ft)%20under%20a%20Gaussian%20with%20n%2F2%CF%80%20cycles%20per%20%CF%83%2C%20cut%20at%20%C2%B13.5%CF%83.%22%22%22%0A%20%20%20%20sigma%20%3D%20n%20%2F%20(2%20*%20np.pi%20*%20f)%0A%20%20%20%20t%20%3D%20centred(3.5%20*%20sigma)%0A%20%20%20%20env%20%3D%20np.exp(-(t**2)%20%2F%20(2%20*%20sigma**2))%0A%20%20%20%20return%20env%20*%20np.cos(2%20*%20np.pi%20*%20f%20*%20t)%2C%20env
ewan_obsidian_ready%0Adef%20wavelet_preview(f%2C%20n%2C%20half%3D1.5%2C%20w%3D300%2C%20h%3D120)%3A%0A%20%20%20%20%22%22%22%CF%88%20on%20a%20fixed%20%C2%B1half%20s%20axis%2C%20so%20f%20changes%20its%20spacing%20and%20n%20its%20width.%22%22%22%0A%20%20%20%20sigma%20%3D%20n%20%2F%20(2%20*%20np.pi%20*%20f)%0A%20%20%20%20t%20%3D%20np.linspace(-half%2C%20half%2C%201200)%0A%20%20%20%20env%20%3D%20np.exp(-(t**2)%20%2F%20(2%20*%20sigma**2))%0A%20%20%20%20psi%20%3D%20env%20*%20np.cos(2%20*%20np.pi%20*%20f%20*%20t)%0A%20%20%20%20rng%2C%20mid%20%3D%20(-half%2C%20half)%2C%20(-1.1%2C%201.1)%0A%20%20%20%20line%20%3D%20svg_path(t%2C%20psi%2C%20rng%2C%20mid%2C%20w%2C%20h)%0A%20%20%20%20base%20%3D%20h%20%2F%202%0A%20%20%20%20pos%20%3D%20svg_path(np.r_%5B-half%2C%20t%2C%20half%5D%2C%20np.r_%5B0%2C%20np.maximum(psi%2C%200)%2C%200%5D%2C%20rng%2C%20mid%2C%20w%2C%20h%2C%20close%3DTrue)%0A%20%20%20%20neg%20%3D%20svg_path(np.r_%5B-half%2C%20t%2C%20half%5D%2C%20np.r_%5B0%2C%20np.minimum(psi%2C%200)%2C%200%5D%2C%20rng%2C%20mid%2C%20w%2C%20h%2C%20close%3DTrue)%0A%20%20%20%20up%20%3D%20svg_path(t%2C%20env%2C%20rng%2C%20mid%2C%20w%2C%20h)%0A%20%20%20%20down%20%3D%20svg_path(t%2C%20-env%2C%20rng%2C%20mid%2C%20w%2C%20h)%0A%20%20%20%20muted%20%3D%20'fill%3D%22currentColor%22%20fill-opacity%3D%22.5%22%20font-size%3D%2211%22'%0A%20%20%20%20return%20mo.Html(f'''%0A%20%20%20%20%3Csvg%20viewBox%3D%220%200%20%7Bw%7D%20%7Bh%20%2B%2016%7D%22%20width%3D%22%7Bw%7D%22%20height%3D%22%7Bh%20%2B%2016%7D%22%20style%3D%22max-width%3A100%25%3Bheight%3Aauto%3Bcolor%3Ainherit%3Boverflow%3Avisible%22%20aria-label%3D%22The%20wavelet%20%CF%88%22%3E%0A%20%20%20%20%20%20%3Cline%20x1%3D%220%22%20x2%3D%22%7Bw%7D%22%20y1%3D%22%7Bbase%7D%22%20y2%3D%22%7Bbase%7D%22%20stroke%3D%22currentColor%22%20stroke-opacity%3D%22.12%22%2F%3E%0A%20%20%20%20%20%20%3Cpath%20d%3D%22%7Bpos%7D%22%20fill%3D%22%23899780%22%20fill-opacity%3D%22.45%22%2F%3E%0A%20%20%20%20%20%20%3Cpath%20d%3D%22%7Bneg%7D%22%20fill%3D%22%23AD6E60%22%20fill-opacity%3D%22.45%22%2F%3E%0A%20%20%20%20%20%20%3Cpath%20d%3D%22%7Bup%7D%22%20fill%3D%22none%22%20stroke%3D%22currentColor%22%20stroke-opacity%3D%22.35%22%20stroke-dasharray%3D%223%203%22%2F%3E%0A%20%20%20%20%20%20%3Cpath%20d%3D%22%7Bdown%7D%22%20fill%3D%22none%22%20stroke%3D%22currentColor%22%20stroke-opacity%3D%22.35%22%20stroke-dasharray%3D%223%203%22%2F%3E%0A%20%20%20%20%20%20%3Cpath%20d%3D%22%7Bline%7D%22%20fill%3D%22none%22%20stroke%3D%22currentColor%22%20stroke-width%3D%221.6%22%2F%3E%0A%20%20%20%20%20%20%3Ctext%20x%3D%220%22%20y%3D%22%7Bh%20%2B%2014%7D%22%20%7Bmuted%7D%3E%E2%88%92%7Bhalf%3Ag%7D%20s%3C%2Ftext%3E%0A%20%20%20%20%20%20%3Ctext%20x%3D%22%7Bw%7D%22%20y%3D%22%7Bh%20%2B%2014%7D%22%20text-anchor%3D%22end%22%20%7Bmuted%7D%3E%7Bhalf%3Ag%7D%20s%3C%2Ftext%3E%0A%20%20%20%20%3C%2Fsvg%3E''')
ewan_obsidian_ready%0Apsi%2C%20_env%20%3D%20real_wavelet(f_wave%2C%20n_cycles)%0Aleak%20%3D%20abs(psi.sum())%20%2F%20_env.sum()%0A_share%20%3D%20f%22%7Bleak%3A.1%25%7D%22%20if%20leak%20%3E%3D%200.001%20else%20%22under%200.1%25%22%0A_why%20%3D%20(%0A%20%20%20%20%22so%20a%20slow%20wave%20passing%20under%20it%20cancels%20itself%20out%22%0A%20%20%20%20if%20leak%20%3C%200.02%0A%20%20%20%20else%20%22so%20even%20a%20flat%20offset%20registers%3B%20more%20cycles%20bring%20it%20to%20zero%22%0A)%0Amo.md(rf%22%22%22%0A%23%23%20A%20wavelet%20is%20a%20kernel%20with%20a%20frequency%0A%0AWrap%20a%20cosine%20in%20a%20Gaussian%20and%20you%20have%20a%20kernel%20that%20responds%20only%20to%20its%20own%20frequency%2C%20and%20only%0Awhere%20it%20sits.%20Drag%20%24f%24%20(frequency%2C%20Hz)%20and%20%24n%24%20(cycles%20under%20the%20envelope)%20in%20the%20equation.%0A%0A%7Bmo.hstack(%5Bshape%2C%20wavelet_preview(f_wave%2C%20n_cycles)%5D%2C%20justify%3D%22start%22%2C%20align%3D%22center%22%2C%20gap%3D1.5%2C%20wrap%3DTrue)%7D%0A%0AIts%20net%20area%20is%20%7B_share%7D%20of%20its%20envelope's%2C%20%7B_why%7D.%20Slide%20it%20over%20the%20swell%20and%20burst%3A%20near%206%20Hz%20the%0Aoutput%20stays%20flat%20across%20the%20swell%20and%20rings%20only%20at%20the%20burst.%20Drop%20%24f%24%20toward%200.25%20Hz%20and%20the%0Aswell%20takes%20over.%0A%22%22%22)
ewan_obsidian_ready%0A_t%2C%20_y%20%3D%20toy_signal()%0Awavelet_view%20%3D%20Slide(signals%3D%5Bseries(%22swell%20%2B%20burst%22%2C%20_y)%5D%2C%20kernels%3D%5B%5D%2C%20position%3D0.62)
ewan_obsidian_ready%0Awavelet_view.kernels%20%3D%20%5Bseries(%22%CF%88%22%2C%20psi%2C%20%22%CF%88%22)%5D%0Awavelet_view
ewan_obsidian_ready%0Adef%20conv_same(x%2C%20k)%3A%0A%20%20%20%20%22%22%22Linear%20convolution%2C%20output%20aligned%20with%20x%20(odd-length%20k)%2C%20via%20the%20FFT.%22%22%22%0A%20%20%20%20size%20%3D%20len(x)%20%2B%20len(k)%20-%201%0A%20%20%20%20nfft%20%3D%201%20%3C%3C%20(size%20-%201).bit_length()%0A%20%20%20%20full%20%3D%20np.fft.ifft(np.fft.fft(x%2C%20nfft)%20*%20np.fft.fft(k%2C%20nfft))%5B%3Asize%5D%0A%20%20%20%20start%20%3D%20(len(k)%20-%201)%20%2F%2F%202%0A%20%20%20%20return%20full%5Bstart%20%3A%20start%20%2B%20len(x)%5D
ewan_obsidian_ready%0Adef%20scalogram(x%2C%20freqs%2C%20n)%3A%0A%20%20%20%20%22%22%22%7Ccomplex%20Morlet%20transform%7C%2C%20scaled%20so%20a%20sinusoid%20of%20amplitude%20A%20reads%20A.%22%22%22%0A%20%20%20%20rows%20%3D%20%5B%5D%0A%20%20%20%20for%20f%20in%20freqs%3A%0A%20%20%20%20%20%20%20%20sigma%20%3D%20n%20%2F%20(2%20*%20np.pi%20*%20f)%0A%20%20%20%20%20%20%20%20t%20%3D%20centred(3.5%20*%20sigma)%0A%20%20%20%20%20%20%20%20env%20%3D%20np.exp(-(t**2)%20%2F%20(2%20*%20sigma**2))%0A%20%20%20%20%20%20%20%20w%20%3D%20env%20*%20np.exp(2j%20*%20np.pi%20*%20f%20*%20t)%20%2F%20(env.sum()%20%2F%202)%0A%20%20%20%20%20%20%20%20rows.append(np.abs(conv_same(x%2C%20np.conj(w)%5B%3A%3A-1%5D)))%0A%20%20%20%20return%20np.array(rows)
ewan_obsidian_ready%0Aclass%20Scalogram(anywidget.AnyWidget)%3A%0A%20%20%20%20%22%22%22groups%3A%20%5B%7Blabel%2C%20panels%3A%20%5B%7Btitle%2C%20y%2C%20duration%2C%20fmin%2C%20fmax%2C%20rows%2C%20cols%2C%20power%2C%20vmax%2C%20unit%7D%5D%7D%5D.%0A%0A%20%20%20%20%60%60power%60%60%20is%20base64%20uint8%2C%20rows%20ordered%20high%20to%20low%20frequency%20(log%20spaced).%0A%20%20%20%20%60%60band%60%60%20%5Blo%2C%20hi%5D%20Hz%20is%20drawn%20on%20every%20panel%3B%20lo%20%3D%3D%20hi%20draws%20one%20line.%0A%20%20%20%20%22%22%22%0A%0A%20%20%20%20_esm%20%3D%20THEME_JS%20%2B%20r%22%22%22%0A%20%20%20%20function%20render(%7B%20model%2C%20el%20%7D)%20%7B%0A%20%20%20%20%20%20const%20root%20%3D%20document.createElement(%22div%22)%3B%0A%20%20%20%20%20%20root.className%20%3D%20%22scalo%22%3B%0A%20%20%20%20%20%20el.appendChild(root)%3B%0A%20%20%20%20%20%20let%20cells%20%3D%20%5B%5D%3B%0A%20%20%20%20%20%20const%20unfollow%20%3D%20followTheme(root)%3B%0A%0A%20%20%20%20%20%20function%20build()%20%7B%0A%20%20%20%20%20%20%20%20root.replaceChildren()%3B%0A%20%20%20%20%20%20%20%20cells%20%3D%20%5B%5D%3B%0A%20%20%20%20%20%20%20%20for%20(const%20g%20of%20model.get(%22groups%22))%20%7B%0A%20%20%20%20%20%20%20%20%20%20if%20(g.label)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20const%20h%20%3D%20document.createElement(%22div%22)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20h.className%20%3D%20%22glabel%22%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20h.textContent%20%3D%20g.label%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20root.appendChild(h)%3B%0A%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20const%20grid%20%3D%20document.createElement(%22div%22)%3B%0A%20%20%20%20%20%20%20%20%20%20grid.className%20%3D%20%22grid%22%3B%0A%20%20%20%20%20%20%20%20%20%20grid.style.setProperty(%22--cols%22%2C%20model.get(%22columns%22))%3B%0A%20%20%20%20%20%20%20%20%20%20root.appendChild(grid)%3B%0A%20%20%20%20%20%20%20%20%20%20for%20(const%20p%20of%20g.panels)%20cells.push(panel(p%2C%20grid))%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20cells.forEach(paint)%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20function%20panel(p%2C%20grid)%20%7B%0A%20%20%20%20%20%20%20%20const%20box%20%3D%20document.createElement(%22div%22)%3B%0A%20%20%20%20%20%20%20%20box.className%20%3D%20%22panel%22%3B%0A%20%20%20%20%20%20%20%20const%20head%20%3D%20document.createElement(%22div%22)%3B%0A%20%20%20%20%20%20%20%20head.className%20%3D%20%22head%22%3B%0A%20%20%20%20%20%20%20%20const%20title%20%3D%20document.createElement(%22span%22)%3B%0A%20%20%20%20%20%20%20%20title.textContent%20%3D%20p.title%3B%0A%20%20%20%20%20%20%20%20const%20read%20%3D%20document.createElement(%22span%22)%3B%0A%20%20%20%20%20%20%20%20read.className%20%3D%20%22read%22%3B%0A%20%20%20%20%20%20%20%20head.append(title%2C%20read)%3B%0A%20%20%20%20%20%20%20%20const%20trace%20%3D%20node(%22svg%22%2C%20%7B%20class%3A%20%22trace%22%20%7D)%3B%0A%20%20%20%20%20%20%20%20const%20wrap%20%3D%20document.createElement(%22div%22)%3B%0A%20%20%20%20%20%20%20%20wrap.className%20%3D%20%22map%22%3B%0A%20%20%20%20%20%20%20%20const%20cv%20%3D%20document.createElement(%22div%22)%3B%0A%20%20%20%20%20%20%20%20cv.className%20%3D%20%22heat%22%3B%0A%20%20%20%20%20%20%20%20const%20over%20%3D%20node(%22svg%22%2C%20%7B%20class%3A%20%22over%22%20%7D)%3B%0A%20%20%20%20%20%20%20%20wrap.append(cv%2C%20over)%3B%0A%20%20%20%20%20%20%20%20box.append(head%2C%20trace%2C%20wrap)%3B%0A%20%20%20%20%20%20%20%20grid.appendChild(box)%3B%0A%20%20%20%20%20%20%20%20const%20bin%20%3D%20atob(p.power)%3B%0A%20%20%20%20%20%20%20%20const%20arr%20%3D%20new%20Uint8ClampedArray(bin.length)%3B%0A%20%20%20%20%20%20%20%20for%20(let%20i%20%3D%200%3B%20i%20%3C%20bin.length%3B%20i%2B%2B)%20arr%5Bi%5D%20%3D%20bin.charCodeAt(i)%3B%0A%20%20%20%20%20%20%20%20const%20off%20%3D%20document.createElement(%22canvas%22)%3B%0A%20%20%20%20%20%20%20%20off.width%20%3D%20p.cols%3B%20off.height%20%3D%20p.rows%3B%0A%20%20%20%20%20%20%20%20const%20img%20%3D%20new%20ImageData(p.cols%2C%20p.rows)%3B%0A%20%20%20%20%20%20%20%20for%20(let%20i%20%3D%200%3B%20i%20%3C%20arr.length%3B%20i%2B%2B)%20img.data%5B4%20*%20i%20%2B%203%5D%20%3D%20arr%5Bi%5D%3B%0A%20%20%20%20%20%20%20%20off.getContext(%222d%22).putImageData(img%2C%200%2C%200)%3B%0A%20%20%20%20%20%20%20%20const%20mask%20%3D%20%60url(%24%7Boff.toDataURL()%7D)%60%3B%0A%20%20%20%20%20%20%20%20cv.style.maskImage%20%3D%20cv.style.webkitMaskImage%20%3D%20mask%3B%0A%20%20%20%20%20%20%20%20const%20cell%20%3D%20%7B%20p%2C%20wrap%2C%20trace%2C%20cv%2C%20over%2C%20read%2C%20arr%2C%20w%3A%200%20%7D%3B%0A%20%20%20%20%20%20%20%20wrap.addEventListener(%22pointermove%22%2C%20(e)%20%3D%3E%20hover(cell%2C%20e))%3B%0A%20%20%20%20%20%20%20%20wrap.addEventListener(%22pointerleave%22%2C%20()%20%3D%3E%20%7B%20cell.read.textContent%20%3D%20%22%22%3B%20cell.cross%20%26%26%20(cell.cross.style.display%20%3D%20%22none%22)%3B%20%7D)%3B%0A%20%20%20%20%20%20%20%20return%20cell%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20const%20logY%20%3D%20(p%2C%20f%2C%20h)%20%3D%3E%20((Math.log(p.fmax)%20-%20Math.log(f))%20%2F%20(Math.log(p.fmax)%20-%20Math.log(p.fmin)))%20*%20h%3B%0A%0A%20%20%20%20%20%20function%20paint(cell)%20%7B%0A%20%20%20%20%20%20%20%20const%20%7B%20p%2C%20wrap%2C%20trace%2C%20cv%2C%20over%20%7D%20%3D%20cell%3B%0A%20%20%20%20%20%20%20%20const%20w%20%3D%20Math.max(80%2C%20Math.round(wrap.clientWidth%20%7C%7C%20300))%3B%0A%20%20%20%20%20%20%20%20const%20h%20%3D%20model.get(%22height%22)%2C%20th%20%3D%20model.get(%22trace_height%22)%3B%0A%20%20%20%20%20%20%20%20const%20%5Blo%2C%20hi%5D%20%3D%20model.get(%22yrange%22)%3B%0A%20%20%20%20%20%20%20%20cell.w%20%3D%20w%3B%0A%20%20%20%20%20%20%20%20trace.setAttribute(%22viewBox%22%2C%20%600%200%20%24%7Bw%7D%20%24%7Bth%7D%60)%3B%0A%20%20%20%20%20%20%20%20trace.setAttribute(%22width%22%2C%20w)%3B%20trace.setAttribute(%22height%22%2C%20th)%3B%0A%20%20%20%20%20%20%20%20let%20d%20%3D%20%22%22%3B%0A%20%20%20%20%20%20%20%20p.y.forEach((v%2C%20i)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20const%20x%20%3D%20(i%20%2F%20(p.y.length%20-%201))%20*%20w%2C%20y%20%3D%20th%20-%202%20-%20((v%20-%20lo)%20%2F%20(hi%20-%20lo))%20*%20(th%20-%204)%3B%0A%20%20%20%20%20%20%20%20%20%20d%20%2B%3D%20(d%20%3F%20%22L%22%20%3A%20%22M%22)%20%2B%20x.toFixed(1)%20%2B%20%22%2C%22%20%2B%20y.toFixed(1)%3B%0A%20%20%20%20%20%20%20%20%7D)%3B%0A%20%20%20%20%20%20%20%20trace.replaceChildren(node(%22path%22%2C%20%7B%20d%20%7D))%3B%0A%0A%20%20%20%20%20%20%20%20cv.style.height%20%3D%20h%20%2B%20%22px%22%3B%0A%0A%20%20%20%20%20%20%20%20over.setAttribute(%22viewBox%22%2C%20%600%200%20%24%7Bw%7D%20%24%7Bh%7D%60)%3B%0A%20%20%20%20%20%20%20%20over.setAttribute(%22width%22%2C%20w)%3B%20over.setAttribute(%22height%22%2C%20h)%3B%0A%20%20%20%20%20%20%20%20over.replaceChildren()%3B%0A%20%20%20%20%20%20%20%20const%20band%20%3D%20model.get(%22band%22)%3B%0A%20%20%20%20%20%20%20%20if%20(band%20%26%26%20band.length%20%3D%3D%3D%202)%20%7B%0A%20%20%20%20%20%20%20%20%20%20const%20y1%20%3D%20logY(p%2C%20Math.min(p.fmax%2C%20band%5B1%5D)%2C%20h)%2C%20y0%20%3D%20logY(p%2C%20Math.max(p.fmin%2C%20band%5B0%5D)%2C%20h)%3B%0A%20%20%20%20%20%20%20%20%20%20if%20(band%5B0%5D%20%3D%3D%3D%20band%5B1%5D)%20node(%22line%22%2C%20%7B%20class%3A%20%22band%22%2C%20x1%3A%200%2C%20x2%3A%20w%2C%20y1%2C%20y2%3A%20y1%20%7D%2C%20over)%3B%0A%20%20%20%20%20%20%20%20%20%20else%20node(%22rect%22%2C%20%7B%20class%3A%20%22band%22%2C%20x%3A%200.5%2C%20y%3A%20y1%2C%20width%3A%20w%20-%201%2C%20height%3A%20Math.max(1%2C%20y0%20-%20y1)%20%7D%2C%20over)%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20for%20(const%20f%20of%20%5B0.5%2C%202%2C%208%5D)%20%7B%0A%20%20%20%20%20%20%20%20%20%20if%20(f%20%3C%3D%20p.fmin%20%7C%7C%20f%20%3E%3D%20p.fmax)%20continue%3B%0A%20%20%20%20%20%20%20%20%20%20node(%22text%22%2C%20%7B%20class%3A%20%22tick%22%2C%20x%3A%20w%20-%203%2C%20y%3A%20logY(p%2C%20f%2C%20h)%20%2B%203.5%20%7D%2C%20over).textContent%20%3D%20f%20%2B%20%22%20Hz%22%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20cell.cross%20%3D%20node(%22line%22%2C%20%7B%20class%3A%20%22cross%22%2C%20x1%3A%200%2C%20x2%3A%200%2C%20y1%3A%200%2C%20y2%3A%20h%2C%20style%3A%20%22display%3Anone%22%20%7D%2C%20over)%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20function%20hover(cell%2C%20e)%20%7B%0A%20%20%20%20%20%20%20%20const%20%7B%20p%2C%20wrap%2C%20arr%20%7D%20%3D%20cell%3B%0A%20%20%20%20%20%20%20%20const%20r%20%3D%20wrap.getBoundingClientRect()%2C%20h%20%3D%20model.get(%22height%22)%3B%0A%20%20%20%20%20%20%20%20const%20fx%20%3D%20Math.min(1%2C%20Math.max(0%2C%20(e.clientX%20-%20r.left)%20%2F%20r.width))%3B%0A%20%20%20%20%20%20%20%20const%20fy%20%3D%20Math.min(1%2C%20Math.max(0%2C%20(e.clientY%20-%20r.top)%20%2F%20h))%3B%0A%20%20%20%20%20%20%20%20const%20col%20%3D%20Math.min(p.cols%20-%201%2C%20Math.floor(fx%20*%20p.cols))%2C%20row%20%3D%20Math.min(p.rows%20-%201%2C%20Math.floor(fy%20*%20p.rows))%3B%0A%20%20%20%20%20%20%20%20const%20f%20%3D%20Math.exp(Math.log(p.fmax)%20-%20fy%20*%20(Math.log(p.fmax)%20-%20Math.log(p.fmin)))%3B%0A%20%20%20%20%20%20%20%20const%20v%20%3D%20(arr%5Brow%20*%20p.cols%20%2B%20col%5D%20%2F%20255)%20**%202%20*%20p.vmax%3B%0A%20%20%20%20%20%20%20%20cell.read.textContent%20%3D%20%60%24%7B(fx%20*%20p.duration).toFixed(2)%7D%20s%20%C2%B7%20%24%7Bf.toFixed(1)%7D%20Hz%20%C2%B7%20%24%7Bv.toFixed(2)%7D%20%24%7Bp.unit%7D%60%3B%0A%20%20%20%20%20%20%20%20const%20x%20%3D%20fx%20*%20cell.w%3B%0A%20%20%20%20%20%20%20%20cell.cross.setAttribute(%22x1%22%2C%20x)%3B%20cell.cross.setAttribute(%22x2%22%2C%20x)%3B%0A%20%20%20%20%20%20%20%20cell.cross.style.display%20%3D%20%22%22%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20const%20ro%20%3D%20new%20ResizeObserver(()%20%3D%3E%20cells.forEach((c)%20%3D%3E%20%7B%20if%20(Math.abs(c.wrap.clientWidth%20-%20c.w)%20%3E%201)%20paint(c)%3B%20%7D))%3B%0A%20%20%20%20%20%20ro.observe(root)%3B%0A%20%20%20%20%20%20const%20repaint%20%3D%20()%20%3D%3E%20cells.forEach(paint)%3B%0A%20%20%20%20%20%20model.on(%22change%3Agroups%22%2C%20build)%3B%0A%20%20%20%20%20%20model.on(%22change%3Aband%22%2C%20repaint)%3B%0A%20%20%20%20%20%20build()%3B%0A%20%20%20%20%20%20return%20()%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20model.off(%22change%3Agroups%22%2C%20build)%3B%20model.off(%22change%3Aband%22%2C%20repaint)%3B%0A%20%20%20%20%20%20%20%20ro.disconnect()%3B%20unfollow()%3B%20root.remove()%3B%0A%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%7D%0A%20%20%20%20export%20default%20%7B%20render%20%7D%3B%0A%20%20%20%20%22%22%22%0A%20%20%20%20_css%20%3D%20(%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20.scalo%20%7B%20color%3A%20inherit%3B%20font%3A%20inherit%3B%20font-size%3A%2012px%3B%22%22%22%0A%20%20%20%20%20%20%20%20%2B%20PALETTE_CSS%0A%20%20%20%20%20%20%20%20%2B%20%22%22%22%0A%20%20%20%20%7D%0A%20%20%20%20.scalo%20.grid%20%7B%20display%3A%20grid%3B%20grid-template-columns%3A%20repeat(var(--cols)%2C%20minmax(0%2C%201fr))%3B%20gap%3A%2016px%2018px%3B%20%7D%0A%20%20%20%20%40media%20(max-width%3A%20640px)%20%7B%20.scalo%20.grid%20%7B%20grid-template-columns%3A%20repeat(min(var(--cols)%2C%202)%2C%20minmax(0%2C%201fr))%3B%20%7D%20%7D%0A%20%20%20%20.scalo%20.glabel%20%7B%20margin%3A%2014px%200%206px%3B%20opacity%3A%20.6%3B%20%7D%0A%20%20%20%20.scalo%20.glabel%3Afirst-child%20%7B%20margin-top%3A%200%3B%20%7D%0A%20%20%20%20.scalo%20.head%20%7B%20display%3A%20flex%3B%20justify-content%3A%20space-between%3B%20gap%3A%208px%3B%20min-height%3A%201.4em%3B%20%7D%0A%20%20%20%20.scalo%20.read%20%7B%20font-family%3A%20%22IBM%20Plex%20Mono%22%2C%20ui-monospace%2C%20monospace%3B%20font-variant-numeric%3A%20tabular-nums%3B%20font-size%3A%2011px%3B%20opacity%3A%20.65%3B%20white-space%3A%20nowrap%3B%20%7D%0A%20%20%20%20.scalo%20.trace%20%7B%20display%3A%20block%3B%20overflow%3A%20visible%3B%20%7D%0A%20%20%20%20.scalo%20.trace%20path%20%7B%20fill%3A%20none%3B%20stroke%3A%20currentColor%3B%20stroke-opacity%3A%20.75%3B%20stroke-width%3A%201%3B%20%7D%0A%20%20%20%20.scalo%20.map%20%7B%20position%3A%20relative%3B%20cursor%3A%20crosshair%3B%20%7D%0A%20%20%20%20.scalo%20.heat%20%7B%0A%20%20%20%20%20%20display%3A%20block%3B%20width%3A%20100%25%3B%20background%3A%20currentColor%3B%0A%20%20%20%20%20%20mask-size%3A%20100%25%20100%25%3B%20-webkit-mask-size%3A%20100%25%20100%25%3B%20mask-repeat%3A%20no-repeat%3B%20-webkit-mask-repeat%3A%20no-repeat%3B%0A%20%20%20%20%7D%0A%20%20%20%20.scalo%20.over%20%7B%20position%3A%20absolute%3B%20inset%3A%200%3B%20pointer-events%3A%20none%3B%20overflow%3A%20visible%3B%20%7D%0A%20%20%20%20.scalo%20.tick%20%7B%20text-anchor%3A%20end%3B%20fill%3A%20currentColor%3B%20fill-opacity%3A%20.55%3B%20font-size%3A%2010px%3B%20font-family%3A%20%22IBM%20Plex%20Mono%22%2C%20ui-monospace%2C%20monospace%3B%20%7D%0A%20%20%20%20.scalo%20.band%20%7B%20fill%3A%20none%3B%20stroke%3A%20var(--accent)%3B%20stroke-width%3A%201.4%3B%20%7D%0A%20%20%20%20.scalo%20.cross%20%7B%20stroke%3A%20var(--accent)%3B%20stroke-opacity%3A%20.7%3B%20%7D%0A%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20groups%20%3D%20traitlets.List(%5B%5D).tag(sync%3DTrue)%0A%20%20%20%20columns%20%3D%20traitlets.Int(1).tag(sync%3DTrue)%0A%20%20%20%20height%20%3D%20traitlets.Int(140).tag(sync%3DTrue)%0A%20%20%20%20trace_height%20%3D%20traitlets.Int(48).tag(sync%3DTrue)%0A%20%20%20%20yrange%20%3D%20traitlets.List(%5B-1.0%2C%201.0%5D).tag(sync%3DTrue)%0A%20%20%20%20band%20%3D%20traitlets.List(%5B%5D).tag(sync%3DTrue)
ewan_obsidian_ready%0Adef%20panel(title%2C%20trace%2C%20power%2C%20freqs%2C%20vmax%2C%20unit%3D%22%22%2C%20cols%3D480)%3A%0A%20%20%20%20%22%22%22Pack%20one%20scalogram%3A%20sqrt-compressed%2C%20high%20frequency%20first%2C%20uint8%20base64.%22%22%22%0A%20%20%20%20step%20%3D%20max(1%2C%20power.shape%5B1%5D%20%2F%2F%20cols)%0A%20%20%20%20p%20%3D%20power%5B%3A%3A-1%2C%20%3A%3Astep%5D%0A%20%20%20%20q%20%3D%20np.clip(np.sqrt(p%20%2F%20vmax)%2C%200%2C%201)%20*%20255%0A%20%20%20%20trace%20%3D%20np.asarray(trace)%5B%3A%3A%20max(1%2C%20len(trace)%20%2F%2F%20cols)%5D%0A%20%20%20%20return%20%7B%0A%20%20%20%20%20%20%20%20%22title%22%3A%20title%2C%20%22y%22%3A%20np.round(trace%2C%204).tolist()%2C%20%22duration%22%3A%20power.shape%5B1%5D%20%2F%20FS%2C%0A%20%20%20%20%20%20%20%20%22fmin%22%3A%20float(freqs%5B0%5D)%2C%20%22fmax%22%3A%20float(freqs%5B-1%5D)%2C%20%22rows%22%3A%20p.shape%5B0%5D%2C%20%22cols%22%3A%20p.shape%5B1%5D%2C%0A%20%20%20%20%20%20%20%20%22power%22%3A%20base64.b64encode(q.astype(np.uint8).tobytes()).decode()%2C%20%22vmax%22%3A%20float(vmax)%2C%20%22unit%22%3A%20unit%2C%0A%20%20%20%20%7D
Every frequency at once is a scalogram
Pair the cosine with a sine and take the magnitude: the ringing above becomes a smooth envelope.
Do that for every f f f ||(f||) and stack the rows. The swell and the burst separate cleanly in time and
frequency; the line marks your f f f ||(f||) . More cycles sharpen frequency and blur time.
ewan_obsidian_ready%0A_t%2C%20_y%20%3D%20toy_signal()%0A_freqs%20%3D%20np.geomspace(0.15%2C%2015%2C%2064)%0A_power%20%3D%20scalogram(_y%2C%20_freqs%2C%20n_cycles)%0Atoy_scalogram%20%3D%20Scalogram(%0A%20%20%20%20groups%3D%5B%7B%22label%22%3A%20%22%22%2C%20%22panels%22%3A%20%5Bpanel(%22%22%2C%20_y%2C%20_power%2C%20_freqs%2C%20float(_power.max()))%5D%7D%5D%2C%0A%20%20%20%20columns%3D1%2C%20height%3D170%2C%20trace_height%3D56%2C%20yrange%3D%5B-1.45%2C%201.45%5D%2C%20band%3D%5Bf_wave%2C%20f_wave%5D%2C%0A)%0Amo.vstack(%5B%0A%20%20%20%20mo.md(rf%22%22%22%0A%20%20%20%20%23%23%20Every%20frequency%20at%20once%20is%20a%20scalogram%0A%0A%20%20%20%20Pair%20the%20cosine%20with%20a%20sine%20and%20take%20the%20magnitude%3A%20the%20ringing%20above%20becomes%20a%20smooth%20envelope.%0A%20%20%20%20Do%20that%20for%20every%20%24f%24%20and%20stack%20the%20rows.%20The%20swell%20and%20the%20burst%20separate%20cleanly%20in%20time%20and%0A%20%20%20%20frequency%3B%20the%20line%20marks%20your%20%24f%24.%20More%20cycles%20sharpen%20frequency%20and%20blur%20time.%0A%20%20%20%20%22%22%22)%2C%0A%20%20%20%20toy_scalogram%2C%0A%5D)
ewan_obsidian_ready%0Adef%20load_trials()%3A%0A%20%20%20%20%22%22%22The%20nine%20BME3740%20sample%20trials%2C%20embedded%3A%20name%2C%20fall%20label%2C%20trunk%20%7Ca%7C%20in%20g%20at%20100%20Hz.%22%22%22%0A%20%20%20%20rows%20%3D%20json.loads(zlib.decompress(base64.b64decode(TRIAL_DATA)))%0A%20%20%20%20return%20%5B%7B%22name%22%3A%20r%5B%22name%22%5D%2C%20%22fall%22%3A%20r%5B%22fall%22%5D%2C%20%22mag%22%3A%20np.asarray(r%5B%22mag%22%5D)%7D%20for%20r%20in%20rows%5D
ewan_obsidian_ready%0Atrials%20%3D%20load_trials()%0Awindow%20%3D%206%20*%20FS%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%204%20s%20before%20the%20largest%20peak%2C%202%20s%20after%0Afor%20_tr%20in%20trials%3A%0A%20%20%20%20_start%20%3D%20int(np.clip(_tr%5B%22mag%22%5D.argmax()%20-%204%20*%20FS%2C%200%2C%20len(_tr%5B%22mag%22%5D)%20-%20window))%0A%20%20%20%20_tr%5B%22window%22%5D%20%3D%20slice(_start%2C%20_start%20%2B%20window)
ewan_obsidian_ready%0Aimu_view%20%3D%20Slide(%0A%20%20%20%20signals%3D%5Bseries(_tr%5B%22name%22%5D%2C%20_tr%5B%22mag%22%5D%5B_tr%5B%22window%22%5D%5D%20-%201%2C%20%22a%22)%20for%20_tr%20in%20trials%5D%2C%0A%20%20%20%20kernels%3D%5B%5D%2C%0A%20%20%20%20signal%3D%22Forward%20fall%22%2C%0A%20%20%20%20position%3D0.6%2C%0A)
A fall is a slow wave with a burst at its end
Each trial is the acceleration magnitude from the trunk sensor minus 1 g, 4 s before its largest
peak to 2 s after. (IMU 1 stays upright through the hard sit while IMU 2 ends horizontal, so IMU 1
is the trunk.) The wavelet is still yours: around 1 Hz it swells over the whole drop, around 8 Hz
it rings only at impact. Compare a fall with the hard sit.
ewan_obsidian_ready%0Aimu_view.kernels%20%3D%20%5Bseries(%22%CF%88%22%2C%20psi%2C%20%22%CF%88%22)%5D%0Amo.vstack(%5B%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20A%20fall%20is%20a%20slow%20wave%20with%20a%20burst%20at%20its%20end%0A%0A%20%20%20%20Each%20trial%20is%20the%20acceleration%20magnitude%20from%20the%20trunk%20sensor%20minus%201%20g%2C%204%20s%20before%20its%20largest%0A%20%20%20%20peak%20to%202%20s%20after.%20(IMU%201%20stays%20upright%20through%20the%20hard%20sit%20while%20IMU%202%20ends%20horizontal%2C%20so%20IMU%201%0A%20%20%20%20is%20the%20trunk.)%20The%20wavelet%20is%20still%20yours%3A%20around%201%20Hz%20it%20swells%20over%20the%20whole%20drop%2C%20around%208%20Hz%0A%20%20%20%20it%20rings%20only%20at%20impact.%20Compare%20a%20fall%20with%20the%20hard%20sit.%0A%20%20%20%20%22%22%22)%2C%0A%20%20%20%20imu_view%2C%0A%5D)
ewan_obsidian_ready%0Aband%20%3D%20mo.ui.range_slider(%0A%20%20%20%20steps%3D%5B0.3%2C%200.5%2C%200.75%2C%201%2C%201.5%2C%202%2C%203%2C%204%2C%206%2C%208%2C%2012%2C%2016%2C%2024%5D%2C%0A%20%20%20%20value%3D%5B0.5%2C%201.5%5D%2C%20label%3D%22Band%20(Hz)%22%2C%20show_value%3DTrue%2C%0A)
ewan_obsidian_ready%0Aimu_freqs%20%3D%20np.geomspace(0.3%2C%2024%2C%2060)%0Aimu_power%20%3D%20%5Bscalogram(_tr%5B%22mag%22%5D%20-%201%2C%20imu_freqs%2C%20n_cycles)%5B%3A%2C%20_tr%5B%22window%22%5D%5D%20for%20_tr%20in%20trials%5D
Every trial's scalogram on one colour scale, impacts aligned two seconds from the right edge.
Each fall darkens around 1 Hz under its impact, where the body drops; the box marks the band
scored below.
ewan_obsidian_ready%0A_vmax%20%3D%20float(np.percentile(%5Bp.max()%20for%20p%20in%20imu_power%5D%2C%2075))%0A_panels%20%3D%20%5Bpanel(_tr%5B%22name%22%5D%2C%20_tr%5B%22mag%22%5D%5B_tr%5B%22window%22%5D%5D%2C%20p%2C%20imu_freqs%2C%20_vmax%2C%20%22g%22%2C%20cols%3D300)%0A%20%20%20%20%20%20%20%20%20%20%20for%20_tr%2C%20p%20in%20zip(trials%2C%20imu_power)%5D%0A_ymax%20%3D%20max(float(_tr%5B%22mag%22%5D%5B_tr%5B%22window%22%5D%5D.max())%20for%20_tr%20in%20trials)%0A_falls%20%3D%20%5Bp%20for%20p%2C%20_tr%20in%20zip(_panels%2C%20trials)%20if%20_tr%5B%22fall%22%5D%5D%0A_rest%20%3D%20%5Bp%20for%20p%2C%20_tr%20in%20zip(_panels%2C%20trials)%20if%20not%20_tr%5B%22fall%22%5D%5D%0Atrial_scalograms%20%3D%20Scalogram(%0A%20%20%20%20groups%3D%5B%7B%22label%22%3A%20%22Falls%22%2C%20%22panels%22%3A%20_falls%7D%2C%20%7B%22label%22%3A%20%22Not%20falls%22%2C%20%22panels%22%3A%20_rest%7D%5D%2C%0A%20%20%20%20columns%3D5%2C%20height%3D96%2C%20trace_height%3D40%2C%20yrange%3D%5B0.0%2C%20min(_ymax%2C%208.0)%5D%2C%20band%3Dlist(band.value)%2C%0A)%0Amo.vstack(%5B%0A%20%20%20%20mo.md(%22%22%22%0A%20%20%20%20Every%20trial's%20scalogram%20on%20one%20colour%20scale%2C%20impacts%20aligned%20two%20seconds%20from%20the%20right%20edge.%0A%20%20%20%20Each%20fall%20darkens%20around%201%20Hz%20under%20its%20impact%2C%20where%20the%20body%20drops%3B%20the%20box%20marks%20the%20band%0A%20%20%20%20scored%20below.%0A%20%20%20%20%22%22%22)%2C%0A%20%20%20%20band%2C%0A%20%20%20%20trial_scalograms%2C%0A%5D)
ewan_obsidian_ready%0Adef%20who(names)%3A%0A%20%20%20%20if%20not%20names%3A%0A%20%20%20%20%20%20%20%20return%20%22no%20other%20trial%22%2C%20%22does%22%0A%20%20%20%20names%20%3D%20%5Bf%22the%20%7Bn.lower()%7D%22%20for%20n%20in%20names%5D%0A%20%20%20%20joined%20%3D%20names%5B0%5D%20if%20len(names)%20%3D%3D%201%20else%20%22%2C%20%22.join(names%5B%3A-1%5D)%20%2B%20%22%20and%20%22%20%2B%20names%5B-1%5D%0A%20%20%20%20return%20joined%2C%20%22does%22%20if%20len(names)%20%3D%3D%201%20else%20%22do%22
ewan_obsidian_ready%0Adef%20strip(rows%2C%20falls%2C%20names%2C%20width%3D640)%3A%0A%20%20%20%20%22%22%22Dot%20rows%20on%20their%20own%20scales%3B%20filled%20%3D%20fall.%20Labels%20only%20non-falls%20at%20or%20above%20the%20weakest%20fall.%22%22%22%0A%20%20%20%20row_h%2C%20left%2C%20right%20%3D%2070%2C%2010%2C%2030%0A%20%20%20%20parts%20%3D%20%5B%5D%0A%20%20%20%20for%20r%2C%20(title%2C%20values)%20in%20enumerate(rows)%3A%0A%20%20%20%20%20%20%20%20values%20%3D%20np.asarray(values)%0A%20%20%20%20%20%20%20%20top%20%3D%20r%20*%20row_h%0A%20%20%20%20%20%20%20%20y%20%3D%20top%20%2B%2042%0A%20%20%20%20%20%20%20%20vmax%20%3D%20values.max()%20*%201.05%0A%20%20%20%20%20%20%20%20x%20%3D%20lambda%20v%3A%20left%20%2B%20v%20%2F%20vmax%20*%20(width%20-%20left%20-%20right)%0A%20%20%20%20%20%20%20%20floor%20%3D%20values%5Bfalls%5D.min()%0A%20%20%20%20%20%20%20%20parts.append(f'%3Ctext%20x%3D%22%7Bleft%7D%22%20y%3D%22%7Btop%20%2B%2014%7D%22%20font-size%3D%2212%22%20fill%3D%22currentColor%22%20fill-opacity%3D%22.7%22%3E%7Btitle%7D%3C%2Ftext%3E')%0A%20%20%20%20%20%20%20%20parts.append(f'%3Cline%20x1%3D%22%7Bleft%7D%22%20x2%3D%22%7Bwidth%20-%20right%7D%22%20y1%3D%22%7By%7D%22%20y2%3D%22%7By%7D%22%20stroke%3D%22currentColor%22%20stroke-opacity%3D%22.15%22%2F%3E')%0A%20%20%20%20%20%20%20%20parts.append(f'%3Cline%20x1%3D%22%7Bx(floor)%3A.1f%7D%22%20x2%3D%22%7Bx(floor)%3A.1f%7D%22%20y1%3D%22%7By%20-%2014%7D%22%20y2%3D%22%7By%20%2B%2014%7D%22%20stroke%3D%22%23E89A39%22%20stroke-dasharray%3D%222%203%22%2F%3E')%0A%20%20%20%20%20%20%20%20parts.append(f'%3Ctext%20x%3D%22%7Bwidth%20-%20right%20%2B%204%7D%22%20y%3D%22%7By%20%2B%204%7D%22%20font-size%3D%2211%22%20fill%3D%22currentColor%22%20fill-opacity%3D%22.55%22%3E%7Bvmax%20%2F%201.05%3A.1f%7D%3C%2Ftext%3E')%0A%20%20%20%20%20%20%20%20for%20v%2C%20fall%2C%20name%20in%20sorted(zip(values%2C%20falls%2C%20names)%2C%20key%3Dlambda%20z%3A%20z%5B1%5D)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20fill%20%3D%20'fill%3D%22currentColor%22'%20if%20fall%20else%20'fill%3D%22none%22%20stroke%3D%22currentColor%22%20stroke-width%3D%221.4%22'%0A%20%20%20%20%20%20%20%20%20%20%20%20cy%20%3D%20y%20-%205%20if%20fall%20else%20y%20%2B%205%0A%20%20%20%20%20%20%20%20%20%20%20%20parts.append(f'%3Ccircle%20cx%3D%22%7Bx(v)%3A.1f%7D%22%20cy%3D%22%7Bcy%7D%22%20r%3D%225%22%20%7Bfill%7D%20fill-opacity%3D%22.85%22%3E%3Ctitle%3E%7Bname%7D%3A%20%7Bv%3A.2f%7D%3C%2Ftitle%3E%3C%2Fcircle%3E')%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20not%20fall%20and%20v%20%3E%3D%20floor%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20parts.append(f'%3Ctext%20x%3D%22%7Bx(v)%3A.1f%7D%22%20y%3D%22%7By%20%2B%2025%7D%22%20font-size%3D%2211%22%20text-anchor%3D%22middle%22%20fill%3D%22currentColor%22%20fill-opacity%3D%22.8%22%3E%7Bname.lower()%7D%3C%2Ftext%3E')%0A%20%20%20%20h%20%3D%20row_h%20*%20len(rows)%0A%20%20%20%20return%20mo.Html(f'%3Csvg%20viewBox%3D%220%200%20%7Bwidth%7D%20%7Bh%7D%22%20style%3D%22width%3A100%25%3Bmax-width%3A%7Bwidth%7Dpx%3Bcolor%3Ainherit%3Boverflow%3Avisible%22%3E%7B%22%22.join(parts)%7D%3C%2Fsvg%3E')
Peak acceleration confuses a hard sit with a fall
A threshold on peak acceleration is the usual first detector. Filled dots are falls; the dashed
tick is the gentlest fall. On peak acceleration the hard sit and the trick do at least as well; on the largest
0.5–1.5 Hz wavelet amplitude, the trick does. Peak acceleration (g) 7.0 Sit-pause-stand: 1.35 Hard sit: 4.95 hard sit Stand-to-lie: 2.75 Near-fall: 2.83 Trick: 6.26 trick Forward fall: 6.97 Backward fall: 3.38 Leftward fall: 3.64 Rightward fall: 4.95 Largest 0.5–1.5 Hz wavelet amplitude (g) 0.8 Sit-pause-stand: 0.11 Hard sit: 0.33 Stand-to-lie: 0.32 Near-fall: 0.20 Trick: 0.83 trick Forward fall: 0.54 Backward fall: 0.42 Leftward fall: 0.39 Rightward fall: 0.46 I chose the default band by looking at these same nine trials, from one session, so this is a
place to look, not a threshold; the trick was built to fool a detector. The test is the same band
on falls from other people. The shape is the opening example again: a slow swell with a burst at
its end, and a wavelet scores the two separately.
ewan_obsidian_ready%0A_lo%2C%20_hi%20%3D%20band.value%0A_rows%20%3D%20(imu_freqs%20%3E%3D%20_lo)%20%26%20(imu_freqs%20%3C%3D%20_hi)%0Apeak%20%3D%20np.array(%5B_tr%5B%22mag%22%5D%5B_tr%5B%22window%22%5D%5D.max()%20for%20_tr%20in%20trials%5D)%0Aband_amp%20%3D%20np.array(%5Bp%5B_rows%5D.max()%20if%20_rows.any()%20else%200.0%20for%20p%20in%20imu_power%5D)%0A_falls%20%3D%20np.array(%5B_tr%5B%22fall%22%5D%20for%20_tr%20in%20trials%5D)%0A_names%20%3D%20%5B_tr%5B%22name%22%5D%20for%20_tr%20in%20trials%5D%0A_over%20%3D%20lambda%20v%3A%20%5Bn%20for%20n%2C%20x%2C%20f%20in%20zip(_names%2C%20v%2C%20_falls)%20if%20not%20f%20and%20x%20%3E%3D%20v%5B_falls%5D.min()%5D%0A_p%2C%20_pv%20%3D%20who(_over(peak))%0A_b%2C%20_bv%20%3D%20who(_over(band_amp))%0Amo.vstack(%5B%0A%20%20%20%20mo.md(f%22%22%22%0A%20%20%20%20%23%23%20Peak%20acceleration%20confuses%20a%20hard%20sit%20with%20a%20fall%0A%0A%20%20%20%20A%20threshold%20on%20peak%20acceleration%20is%20the%20usual%20first%20detector.%20Filled%20dots%20are%20falls%3B%20the%20dashed%0A%20%20%20%20tick%20is%20the%20gentlest%20fall.%20On%20peak%20acceleration%20%7B_p%7D%20%7B_pv%7D%20at%20least%20as%20well%3B%20on%20the%20largest%0A%20%20%20%20%7B_lo%3Ag%7D%E2%80%93%7B_hi%3Ag%7D%20Hz%20wavelet%20amplitude%2C%20%7B_b%7D%20%7B_bv%7D.%0A%20%20%20%20%22%22%22)%2C%0A%20%20%20%20strip(%0A%20%20%20%20%20%20%20%20%5B(%22Peak%20acceleration%20(g)%22%2C%20peak)%2C%20(f%22Largest%20%7B_lo%3Ag%7D%E2%80%93%7B_hi%3Ag%7D%20Hz%20wavelet%20amplitude%20(g)%22%2C%20band_amp)%5D%2C%0A%20%20%20%20%20%20%20%20_falls%2C%20_names%2C%0A%20%20%20%20)%2C%0A%20%20%20%20mo.md(%22%22%22%0A%20%20%20%20I%20chose%20the%20default%20band%20by%20looking%20at%20these%20same%20nine%20trials%2C%20from%20one%20session%2C%20so%20this%20is%20a%0A%20%20%20%20place%20to%20look%2C%20not%20a%20threshold%3B%20the%20trick%20was%20built%20to%20fool%20a%20detector.%20The%20test%20is%20the%20same%20band%0A%20%20%20%20on%20falls%20from%20other%20people.%20The%20shape%20is%20the%20opening%20example%20again%3A%20a%20slow%20swell%20with%20a%20burst%20at%0A%20%20%20%20its%20end%2C%20and%20a%20wavelet%20scores%20the%20two%20separately.%0A%20%20%20%20%22%22%22)%2C%0A%5D)