Merge
This commit is contained in:
104
index.html
104
index.html
@ -32,41 +32,48 @@
|
||||
<body class="bg-neutral-800 overflow-y-hidden">
|
||||
|
||||
<!-- The header is hidden on smaller devices -->
|
||||
<header class="py-2 hidden xl:block text-white bg-neutral-900">
|
||||
<header class="py-2 block text-white bg-neutral-900">
|
||||
<div class="mx-auto flex flex-wrap pl-2 py-1 flex-row items-center">
|
||||
<a class="flex title-font font-medium items-center text-black mb-0">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="w-10 h-10 text-black p-2 bg-white rounded-full" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6.042A8.967 8.967 0 006 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 016 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 016-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0018 18a8.967 8.967 0 00-6 2.292m0-14.25v14.25" />
|
||||
</svg>
|
||||
<span id="universe-viewer" class="ml-4 text-2xl text-white">Topos</span>
|
||||
<span id="universe-viewer" class="hidden xl:block ml-4 text-2xl text-white">Topos</span>
|
||||
|
||||
</a>
|
||||
<nav class="py-0 flex flex-wrap items-center text-base absolute right-0">
|
||||
<a id="play-button-1" class="mr-5">
|
||||
<nav class="py-2 flex flex-wrap items-center text-base absolute right-0">
|
||||
<a id="play-button-1" class="hover:bg-gray-800 px-2 py-2 rounded-lg">
|
||||
<svg class="w-8 h-8" fill="currentColor" viewBox="0 0 14 16">
|
||||
<path d="M0 .984v14.032a1 1 0 0 0 1.506.845l12.006-7.016a.974.974 0 0 0 0-1.69L1.506.139A1 1 0 0 0 0 .984Z"/>
|
||||
</svg>
|
||||
</a>
|
||||
<a id="pause-button-1" class="mr-5 hover:text-gray-900">
|
||||
<a id="pause-button-1" class="mr-2 hover:text-gray-900 hover:bg-gray-800 px-2 py-2 rounded-lg">
|
||||
<svg class="fill-orange-300 w-8 h-8 text-gray-800 text-white" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM9 13a1 1 0 0 1-2 0V7a1 1 0 0 1 2 0v6Zm4 0a1 1 0 0 1-2 0V7a1 1 0 0 1 2 0v6Z"/>
|
||||
</svg>
|
||||
</a>
|
||||
<a id="stop-button-1" class="mr-5 hover:text-gray-900">
|
||||
<a id="stop-button-1" class="mr-2 hover:text-gray-900 hover:bg-gray-800 px-2 py-2 rounded-lg">
|
||||
<svg class="w-8 h-8 text-gray-800 text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 12 16">
|
||||
<path d="M10.819.4a1.974 1.974 0 0 0-2.147.33l-6.5 5.773A2.014 2.014 0 0 0 2 6.7V1a1 1 0 0 0-2 0v14a1 1 0 1 0 2 0V9.3c.055.068.114.133.177.194l6.5 5.773a1.982 1.982 0 0 0 2.147.33A1.977 1.977 0 0 0 12 13.773V2.227A1.977 1.977 0 0 0 10.819.4Z"/>
|
||||
</svg>
|
||||
</a>
|
||||
<a id="clear-button-1" class="mr-5 hover:text-gray-900">
|
||||
<a id="eval-button-1" class="mr-2 hover:text-gray-900 hover:bg-gray-800 px-2 py-2 rounded-lg">
|
||||
<svg class="w-8 h-8 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 18 20">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 1v5h-5M2 19v-5h5m10-4a8 8 0 0 1-14.947 3.97M1 10a8 8 0 0 1 14.947-3.97"/>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<a id="clear-button-1" class="mr-2 hover:text-gray-900 hover:bg-gray-800 px-2 py-2 rounded-lg">
|
||||
<svg class="w-8 h-8 text-gray-800 text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 17 20">
|
||||
<path d="M7.958 19.393a7.7 7.7 0 0 1-6.715-3.439c-2.868-4.832 0-9.376.944-10.654l.091-.122a3.286 3.286 0 0 0 .765-3.288A1 1 0 0 1 4.6.8c.133.1.313.212.525.347A10.451 10.451 0 0 1 10.6 9.3c.5-1.06.772-2.213.8-3.385a1 1 0 0 1 1.592-.758c1.636 1.205 4.638 6.081 2.019 10.441a8.177 8.177 0 0 1-7.053 3.795Z"/>
|
||||
</svg>
|
||||
</a>
|
||||
<a id="doc-button-1" class="mr-5 hover:text-gray-900">
|
||||
<a id="doc-button-1" class="mr-2 hover:text-gray-900 hover:bg-gray-800 px-2 py-2 rounded-lg">
|
||||
<svg class="w-8 h-8 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 18">
|
||||
<path d="M9 1.334C7.06.594 1.646-.84.293.653a1.158 1.158 0 0 0-.293.77v13.973c0 .193.046.383.134.55.088.167.214.306.366.403a.932.932 0 0 0 .5.147c.176 0 .348-.05.5-.147 1.059-.32 6.265.851 7.5 1.65V1.334ZM19.707.653C18.353-.84 12.94.593 11 1.333V18c1.234-.799 6.436-1.968 7.5-1.65a.931.931 0 0 0 .5.147.931.931 0 0 0 .5-.148c.152-.096.279-.235.366-.403.088-.167.134-.357.134-.55V1.423a1.158 1.158 0 0 0-.293-.77Z"/>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
</nav>
|
||||
</nav>
|
||||
</div>
|
||||
@ -74,37 +81,37 @@
|
||||
|
||||
<div id="documentation" class="hidden">
|
||||
<div id="documentation-page" class="flex flex-row">
|
||||
<aside class="h-screen w-50 p-8 h-100 bg-neutral-900 text-white">
|
||||
<nav class="space-y-8 text-xl">
|
||||
<aside class="h-screen w-50 p-1 lg:p-8 h-100 bg-neutral-900 text-white">
|
||||
<nav class="space-y-8 text-xl sm:text-sm">
|
||||
<div class="space-y-2">
|
||||
<h2 class="font-semibold dark:text-gray-400">Basics</h2>
|
||||
<h2 class="font-semibold text-gray-400">Basics</h2>
|
||||
<div class="flex flex-col">
|
||||
<p rel="noopener noreferrer" id="docs_introduction" class="pl-2 pr-2 text-xl hover:bg-neutral-800 py-1 my-1 rounded-lg">Introduction </p>
|
||||
<p rel="noopener noreferrer" id="docs_interface" class="pl-2 pr-2 text-xl hover:bg-neutral-800 py-1 my-1 rounded-lg">Interface</p>
|
||||
<p rel="noopener noreferrer" id="docs_code" class="pl-2 pr-2 text-xl hover:bg-neutral-800 py-1 my-1 rounded-lg">Code</p>
|
||||
<p rel="noopener noreferrer" id="docs_time" class="pl-2 pr-2 text-xl hover:bg-neutral-800 py-1 my-1 rounded-lg">Time</p>
|
||||
<p rel="noopener noreferrer" id="docs_sound" class="pl-2 pr-2 text-xl hover:bg-neutral-800 py-1 my-1 rounded-lg">Sound</p>
|
||||
<p rel="noopener noreferrer" id="docs_midi" class="pl-2 pr-2 text-xl hover:bg-neutral-800 py-1 my-1 rounded-lg">MIDI</p>
|
||||
<p rel="noopener noreferrer" id="docs_functions" class="pl-2 pr-2 text-xl hover:bg-neutral-800 py-1 my-1 rounded-lg">Functions</p>
|
||||
<p rel="noopener noreferrer" id="docs_shortcuts" class="pl-2 pr-2 text-xl hover:bg-neutral-800 py-1 my-1 rounded-lg">Shortcuts</p>
|
||||
<p rel="noopener noreferrer" id="docs_reference" class="pl-2 pr-2 text-xl hover:bg-neutral-800 py-1 my-1 rounded-lg">Reference</p>
|
||||
<p rel="noopener noreferrer" id="docs_introduction" class="pl-2 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Introduction </p>
|
||||
<p rel="noopener noreferrer" id="docs_interface" class="pl-2 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Interface</p>
|
||||
<p rel="noopener noreferrer" id="docs_code" class="pl-2 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Code</p>
|
||||
<p rel="noopener noreferrer" id="docs_time" class="pl-2 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Time</p>
|
||||
<p rel="noopener noreferrer" id="docs_sound" class="pl-2 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Sound</p>
|
||||
<p rel="noopener noreferrer" id="docs_samples" class="pl-2 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Samples</p>
|
||||
<p rel="noopener noreferrer" id="docs_midi" class="pl-2 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">MIDI</p>
|
||||
<p rel="noopener noreferrer" id="docs_functions" class="pl-2 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Functions</p>
|
||||
<p rel="noopener noreferrer" id="docs_shortcuts" class="pl-2 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Shortcuts</p>
|
||||
<p rel="noopener noreferrer" id="docs_reference" class="pl-2 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">Reference</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<h2 class="font-semibold dark:text-gray-400">More</h2>
|
||||
<div class="flex flex-col">
|
||||
<a rel="noopener noreferrer" id="docs_about" class="pl-2 pr-2 text-xl hover:bg-neutral-800 py-1 my-1 rounded-lg">About Topos</a>
|
||||
<a rel="noopener noreferrer" id="docs_about" class="pl-2 pr-2 lg:text-xl text-sm hover:bg-neutral-800 py-1 my-1 rounded-lg">About Topos</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</aside>
|
||||
<div id="documentation-content" class="h-screen mx-16 my-8 break-words overflow-y-scroll pb-32">
|
||||
<div id="documentation-content" class="h-screen lg:mx-16 mx-2 lg:my-8 my-2 break-words overflow-y-scroll pb-32">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="app">
|
||||
|
||||
<!-- This modal is used for switching between buffers -->
|
||||
<div id="modal-buffers" class="invisible bg-neutral-900 bg-opacity-50 flex justify-center items-center absolute top-0 right-0 bottom-0 left-0">
|
||||
<div id="start-button" class="px-16 py-14 rounded-md text-center">
|
||||
@ -182,20 +189,20 @@
|
||||
<!-- This modal pops up when the page is first loaded and then disappears forever -->
|
||||
<div id="modal-container" class="motion-safe:animate-pulse flex min-h-screen flex flex-col">
|
||||
<div id="modal" class="bg-neutral-900 bg-opacity-50 flex justify-center items-center absolute top-0 right-0 bottom-0 left-0">
|
||||
<div id="start-button" class="bg-white px-12 py-12 rounded-md text-center">
|
||||
<h1 class="text-xl mb-4 font-bold text-white rounded bg-gray-800 py-4 mb-8">Topos Prototype</h1>
|
||||
<div class="flex ml-0 lg:flex-row space-y-4 lg:space-y-0 flex-col w-auto min-w-screen px-0 lg:space-x-8 space-x-0">
|
||||
<a href="https://github.com/Bubobubobubobubo/Topos" class="block max-w-sm p-6 border border-gray-200 rounded-lg shadow bg-gray-800 border-gray-700 hover:bg-gray-700">
|
||||
<h5 class="mb-2 lg:text-2xl text-xl font-bold tracking-tight text-white">GitHub</h5>
|
||||
<p class="font-normal text-white">Get involved in the dev process and or file an issue for a broken feature</p>
|
||||
<div id="start-button" class="bg-white px-2 py-2 lg:px-12 lg:py-12 rounded-md text-center">
|
||||
<h1 class="text-xl mb-4 font-bold text-white rounded bg-gray-800 py-4 mb-6">Topos Prototype</h1>
|
||||
<div class="flex ml-0 lg:flex-row landscape:space-y-0 portrait:space-y-2 lg:space-y-4 lg:space-y-0 portrait:flex-col landscape:flex-row lg:flex-col w-auto min-w-screen px-0 lg:space-x-8 landscape:space-x-2">
|
||||
<a href="https://github.com/Bubobubobubobubo/Topos" class="block max-w-sm lg:p-6 border border-gray-200 rounded-lg shadow bg-gray-800 border-gray-700 hover:bg-gray-700">
|
||||
<h5 class="sm:pt-2 mb-2 lg:text-2xl text-xl font-bold tracking-tight text-white">GitHub</h5>
|
||||
<p class="sm:pb-2 font-normal text-white">Get involved in the dev process and or file an issue for a broken feature</p>
|
||||
</a>
|
||||
<a href="https://discord.gg/aPgV7mSFZh" class="block max-w-sm p-6 border border-gray-200 rounded-lg shadow bg-gray-800 border-gray-700 hover:bg-gray-700">
|
||||
<h5 class="mb-2 lg:text-2xl text-xl font-bold tracking-tight text-white">Discord</h5>
|
||||
<p class="font-normal text-white">Join the community on the official Topos/Sardine Discord Server.</p>
|
||||
<a href="https://discord.gg/aPgV7mSFZh" class="block max-w-sm lg:p-6 border border-gray-200 rounded-lg shadow bg-gray-800 border-gray-700 hover:bg-gray-700">
|
||||
<h5 class="sm:pt-2 mb-2 lg:text-2xl text-xl font-bold tracking-tight text-white">Discord</h5>
|
||||
<p class="sm:pb-2 font-normal text-white">Join the community on the official Topos/Sardine Discord Server.</p>
|
||||
</a>
|
||||
<a href="https://topos.raphaelforment.fr" class="block max-w-sm p-6 border border-gray-200 rounded-lg shadow bg-gray-800 border-gray-800 hover:bg-gray-700">
|
||||
<h5 class="mb-2 text-xl lg:text-2xl font-bold tracking-tight text-white">Website</h5>
|
||||
<p class="font-normal text-white">Documentation, videos and more on the official website.</p>
|
||||
<a href="https://topos.raphaelforment.fr" class="block max-w-sm lg:p-6 border border-gray-200 rounded-lg shadow bg-gray-800 border-gray-800 hover:bg-gray-700">
|
||||
<h5 class="sm:pt-2 mb-2 text-xl lg:text-2xl font-bold tracking-tight text-white">Website</h5>
|
||||
<p class="sm:pb-2 font-normal text-white">Documentation, videos and more on the official website.</p>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@ -208,31 +215,31 @@
|
||||
-->
|
||||
<aside class="
|
||||
flex flex-col items-center w-14
|
||||
h-screen py-2 overflow-y-auto
|
||||
h-screen py-2 overflow-y-scroll
|
||||
border-r rtl:border-l
|
||||
rtl:border-r-0 bg-neutral-900
|
||||
dark:border-gray-700"
|
||||
dark:border-neutral-700 border-none"
|
||||
>
|
||||
<nav class="flex flex-col space-y-6">
|
||||
<a id="local-button" class="pl-2 p-1.5 text-gray-700 focus:outline-nones transition-colors duration-200 rounded-lg dark:text-gray-200 dark:hover:bg-gray-800 hover:bg-gray-100">
|
||||
<a id="local-button" class="pl-2 p-1.5 text-gray-700 focus:outline-nones transition-colors duration-200 rounded-lg dark:text-gray-200 hover:bg-gray-800">
|
||||
<svg class="w-8 h-8 text-orange-300" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M10 15a5 5 0 1 0 0-10 5 5 0 0 0 0 10Zm0-11a1 1 0 0 0 1-1V1a1 1 0 0 0-2 0v2a1 1 0 0 0 1 1Zm0 12a1 1 0 0 0-1 1v2a1 1 0 1 0 2 0v-2a1 1 0 0 0-1-1ZM4.343 5.757a1 1 0 0 0 1.414-1.414L4.343 2.929a1 1 0 0 0-1.414 1.414l1.414 1.414Zm11.314 8.486a1 1 0 0 0-1.414 1.414l1.414 1.414a1 1 0 0 0 1.414-1.414l-1.414-1.414ZM4 10a1 1 0 0 0-1-1H1a1 1 0 0 0 0 2h2a1 1 0 0 0 1-1Zm15-1h-2a1 1 0 1 0 0 2h2a1 1 0 0 0 0-2ZM4.343 14.243l-1.414 1.414a1 1 0 1 0 1.414 1.414l1.414-1.414a1 1 0 0 0-1.414-1.414ZM14.95 6.05a1 1 0 0 0 .707-.293l1.414-1.414a1 1 0 1 0-1.414-1.414l-1.414 1.414a1 1 0 0 0 .707 1.707Z"/>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<a id="global-button" class="pl-2 p-1.5 text-gray-700 focus:outline-nones transition-colors duration-200 rounded-lg dark:text-gray-200 dark:hover:bg-gray-800 hover:bg-gray-100">
|
||||
<a id="global-button" class="pl-2 p-1.5 text-gray-700 focus:outline-nones transition-colors duration-200 rounded-lg dark:text-gray-200 hover:bg-gray-800">
|
||||
<svg class="w-8 h-8 text-white" fill="currentColor" viewBox="0 0 18 20">
|
||||
<path d="M17.8 13.75a1 1 0 0 0-.859-.5A7.488 7.488 0 0 1 10.52 2a1 1 0 0 0 0-.969A1.035 1.035 0 0 0 9.687.5h-.113a9.5 9.5 0 1 0 8.222 14.247 1 1 0 0 0 .004-.997Z"/>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<a id="init-button" class="pl-2 p-1.5 text-gray-700 focus:outline-nones transition-colors duration-200 rounded-lg dark:text-gray-200 dark:hover:bg-gray-800 hover:bg-gray-100">
|
||||
<a id="init-button" class="pl-2 p-1.5 text-gray-700 focus:outline-nones transition-colors duration-200 rounded-lg dark:text-gray-200 bg-gray-800">
|
||||
<svg class="w-8 h-8 text-white " fill="none" viewBox="0 0 21 20">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6.487 1.746c0 4.192 3.592 1.66 4.592 5.754 0 .828 1 1.5 2 1.5s2-.672 2-1.5a1.5 1.5 0 0 1 1.5-1.5h1.5m-16.02.471c4.02 2.248 1.776 4.216 4.878 5.645C10.18 13.61 9 19 9 19m9.366-6h-2.287a3 3 0 0 0-3 3v2m6-8a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"/>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<a id="note-button" class="pl-2 p-1.5 text-gray-700 focus:outline-nones transition-colors duration-200 rounded-lg dark:text-gray-200 dark:hover:bg-gray-800 hover:bg-gray-100">
|
||||
<a id="note-button" class="pl-2 p-1.5 text-gray-700 focus:outline-nones transition-colors duration-200 rounded-lg dark:text-gray-200 hover:bg-gray-800">
|
||||
<svg class="w-8 h-8 text-white" fill="currentColor" viewBox="0 0 16 20">
|
||||
<path d="M16 14V2a2 2 0 0 0-2-2H2a2 2 0 0 0-2 2v15a3 3 0 0 0 3 3h12a1 1 0 0 0 0-2h-1v-2a2 2 0 0 0 2-2ZM4 2h2v12H4V2Zm8 16H3a1 1 0 0 1 0-2h9v2Z"/>
|
||||
</svg>
|
||||
@ -240,31 +247,31 @@
|
||||
|
||||
<!-- Other buttons -->
|
||||
|
||||
<a id="play-button-2" class="pl-2 p-1.5 text-gray-700 focus:outline-nones transition-colors duration-200 rounded-lg dark:text-gray-200 dark:hover:bg-gray-800 hover:bg-gray-100">
|
||||
<a id="play-button-2" class="pl-2 p-1.5 text-gray-700 focus:outline-nones transition-colors duration-200 rounded-lg dark:text-gray-200 hover:bg-gray-800 sm:landscape:hidden">
|
||||
<svg class="w-8 h-8 text-white block xl:hidden" fill="currentColor" viewBox="0 0 14 16">
|
||||
<path d="M0 .984v14.032a1 1 0 0 0 1.506.845l12.006-7.016a.974.974 0 0 0 0-1.69L1.506.139A1 1 0 0 0 0 .984Z"/>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<a id="pause-button-2" class="pl-2 p-1.5 text-gray-700 focus:outline-nones transition-colors duration-200 rounded-lg dark:text-gray-200 dark:hover:bg-gray-800 hover:bg-gray-100">
|
||||
<a id="pause-button-2" class="pl-2 p-1.5 text-gray-700 focus:outline-nones transition-colors duration-200 rounded-lg dark:text-gray-200 hover:bg-gray-800 sm:landscape:hidden">
|
||||
<svg class="fill-orange-300 w-8 h-8 block xl:hidden" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM9 13a1 1 0 0 1-2 0V7a1 1 0 0 1 2 0v6Zm4 0a1 1 0 0 1-2 0V7a1 1 0 0 1 2 0v6Z"/>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<a id="stop-button-2" class="pl-2 p-1.5 text-gray-700 focus:outline-nones transition-colors duration-200 rounded-lg dark:text-gray-200 dark:hover:bg-gray-800 hover:bg-gray-100">
|
||||
<a id="stop-button-2" class="pl-2 p-1.5 text-gray-700 focus:outline-nones transition-colors duration-200 rounded-lg dark:text-gray-200 hover:bg-gray-800 sm:landscape:hidden">
|
||||
<svg class="w-8 h-8 text-gray-800 dark:text-white block xl:hidden" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 12 16">
|
||||
<path d="M10.819.4a1.974 1.974 0 0 0-2.147.33l-6.5 5.773A2.014 2.014 0 0 0 2 6.7V1a1 1 0 0 0-2 0v14a1 1 0 1 0 2 0V9.3c.055.068.114.133.177.194l6.5 5.773a1.982 1.982 0 0 0 2.147.33A1.977 1.977 0 0 0 12 13.773V2.227A1.977 1.977 0 0 0 10.819.4Z"/>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<a id="clear-button-2" class="pl-2 p-1.5 text-gray-700 focus:outline-nones transition-colors duration-200 rounded-lg dark:text-gray-200 dark:hover:bg-gray-800 hover:bg-gray-100">
|
||||
<a id="clear-button-2" class="pl-2 p-1.5 text-gray-700 focus:outline-nones transition-colors duration-200 rounded-lg dark:text-gray-200 hover:bg-gray-800">
|
||||
<svg class="w-8 h-8 text-white block xl:hidden" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 17 20">
|
||||
<path d="M7.958 19.393a7.7 7.7 0 0 1-6.715-3.439c-2.868-4.832 0-9.376.944-10.654l.091-.122a3.286 3.286 0 0 0 .765-3.288A1 1 0 0 1 4.6.8c.133.1.313.212.525.347A10.451 10.451 0 0 1 10.6 9.3c.5-1.06.772-2.213.8-3.385a1 1 0 0 1 1.592-.758c1.636 1.205 4.638 6.081 2.019 10.441a8.177 8.177 0 0 1-7.053 3.795Z"/>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<a id="settings-button" class="p-2 text-gray-700 ml-1 absolute bottom-2 focus:outline-nones transition-colors duration-200 rounded-lg dark:text-gray-200 dark:bg-gray-800 bg-gray-100">
|
||||
<a id="settings-button" class="lg:block sm:hidden p-2 text-gray-200 ml-1 absolute bottom-2 focus:outline-nones transition-colors duration-200 rounded-lg text-gray-200 bg-gray-800">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M10.343 3.94c.09-.542.56-.94 1.11-.94h1.093c.55 0 1.02.398 1.11.94l.149.894c.07.424.384.764.78.93.398.164.855.142 1.205-.108l.737-.527a1.125 1.125 0 011.45.12l.773.774c.39.389.44 1.002.12 1.45l-.527.737c-.25.35-.272.806-.107 1.204.165.397.505.71.93.78l.893.15c.543.09.94.56.94 1.109v1.094c0 .55-.397 1.02-.94 1.11l-.893.149c-.425.07-.765.383-.93.78-.165.398-.143.854.107 1.204l.527.738c.32.447.269 1.06-.12 1.45l-.774.773a1.125 1.125 0 01-1.449.12l-.738-.527c-.35-.25-.806-.272-1.203-.107-.397.165-.71.505-.781.929l-.149.894c-.09.542-.56.94-1.11.94h-1.094c-.55 0-1.019-.398-1.11-.94l-.148-.894c-.071-.424-.384-.764-.781-.93-.398-.164-.854-.142-1.204.108l-.738.527c-.447.32-1.06.269-1.45-.12l-.773-.774a1.125 1.125 0 01-.12-1.45l.527-.737c.25-.35.273-.806.108-1.204-.165-.397-.505-.71-.93-.78l-.894-.15c-.542-.09-.94-.56-.94-1.109v-1.094c0-.55.398-1.02.94-1.11l.894-.149c.424-.07.765-.383.93-.78.165-.398.143-.854-.107-1.204l-.527-.738a1.125 1.125 0 01.12-1.45l.773-.773a1.125 1.125 0 011.45-.12l.737.527c.35.25.807.272 1.204.107.397-.165.71-.505.78-.929l.15-.894z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
@ -305,8 +312,9 @@
|
||||
|
||||
</ul>
|
||||
<!-- Here comes the editor itself -->
|
||||
<div id="editor" class="invisible relative flex flex-row flex-grow overflow-hidden overflow-y-hidden">
|
||||
</div>
|
||||
<div id="editor" class="invisible relative flex flex-row h-screen overflow-y-scroll">
|
||||
</div>
|
||||
<p id="error_line" class="hidden text-red-400 w-screen bg-neutral-900 font-mono absolute bottom-0 pl-2 py-2">Hello kids</p>
|
||||
</div>
|
||||
</div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
|
||||
@ -32,7 +32,7 @@
|
||||
"postcss": "^8.4.27",
|
||||
"showdown": "^2.1.0",
|
||||
"showdown-highlight": "^3.1.0",
|
||||
"superdough": "^0.9.3",
|
||||
"superdough": "^0.9.5",
|
||||
"tailwind-highlightjs": "^2.0.1",
|
||||
"tailwindcss": "^3.3.3",
|
||||
"tone": "^14.8.49",
|
||||
|
||||
283
src/API.ts
283
src/API.ts
@ -2,7 +2,6 @@ import { Pitch, Chord, Rest, Event, cachedPattern, seededRandom } from "zifferjs
|
||||
import { MidiConnection } from "./IO/MidiConnection";
|
||||
import { tryEvaluate } from "./Evaluator";
|
||||
import { DrunkWalk } from "./Utils/Drunk";
|
||||
import { LRUCache } from "lru-cache";
|
||||
import { scale } from "./Scales";
|
||||
import { Editor } from "./main";
|
||||
import { Sound } from "./Sound";
|
||||
@ -11,25 +10,16 @@ import {
|
||||
samples,
|
||||
initAudioOnFirstClick,
|
||||
registerSynthSounds,
|
||||
soundMap,
|
||||
// @ts-ignore
|
||||
} from "superdough";
|
||||
|
||||
// This is an LRU cache used for storing persistent patterns
|
||||
const cache = new LRUCache({ max: 1000, ttl: 1000 * 60 * 5 });
|
||||
|
||||
interface ControlChange {
|
||||
channel: number;
|
||||
control: number;
|
||||
value: number;
|
||||
}
|
||||
|
||||
interface Pattern<T> {
|
||||
pattern: any[];
|
||||
options: {
|
||||
[key: string]: T;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* This is an override of the basic "includes" method.
|
||||
*/
|
||||
@ -43,16 +33,13 @@ Array.prototype.in = function <T>(this: T[], value: T): boolean {
|
||||
};
|
||||
|
||||
async function loadSamples() {
|
||||
const ds = "https://raw.githubusercontent.com/felixroos/dough-samples/main/";
|
||||
// const ds = "https://raw.githubusercontent.com/felixroos/dough-samples/main/";
|
||||
return Promise.all([
|
||||
initAudioOnFirstClick(),
|
||||
samples("github:Bubobubobubobubo/Topos-Samples/main"),
|
||||
samples(`${ds}/tidal-drum-machines.json`),
|
||||
samples(`${ds}/piano.json`),
|
||||
samples(`${ds}/Dirt-Samples.json`),
|
||||
samples(`${ds}/EmuSP12.json`),
|
||||
samples(`${ds}/vcsl.json`),
|
||||
registerSynthSounds(),
|
||||
samples("github:tidalcycles/Dirt-Samples/master").then(() =>
|
||||
registerSynthSounds()
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
@ -67,7 +54,7 @@ export class UserAPI {
|
||||
*/
|
||||
|
||||
private variables: { [key: string]: any } = {};
|
||||
private iterators: { [key: string]: any } = {};
|
||||
private counters: { [key: string]: any } = {};
|
||||
private _drunk: DrunkWalk = new DrunkWalk(-100, 100, false);
|
||||
public randomGen = Math.random;
|
||||
public currentSeed: string|undefined = undefined;
|
||||
@ -77,7 +64,16 @@ export class UserAPI {
|
||||
load: samples;
|
||||
|
||||
constructor(public app: Editor) {
|
||||
this.load = samples("github:tidalcycles/Dirt-Samples/master");
|
||||
//this.load = samples("github:tidalcycles/Dirt-Samples/master");
|
||||
}
|
||||
|
||||
_reportError = (error: any): void => {
|
||||
console.log(error)
|
||||
if (!this.app.show_error) {
|
||||
this.app.error_line.innerHTML = error as string;
|
||||
this.app.error_line.classList.remove('hidden');
|
||||
setInterval(() => this.app.error_line.classList.add('hidden'), 2000)
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================
|
||||
@ -91,6 +87,23 @@ export class UserAPI {
|
||||
return this.app.audioContext.currentTime;
|
||||
};
|
||||
|
||||
public play = (): void => {
|
||||
this.app.setButtonHighlighting("play", true);
|
||||
this.app.clock.start();
|
||||
};
|
||||
|
||||
public pause = (): void => {
|
||||
this.app.setButtonHighlighting("pause", true);
|
||||
this.app.clock.pause();
|
||||
};
|
||||
|
||||
public stop = (): void => {
|
||||
this.app.setButtonHighlighting("stop", true);
|
||||
this.app.clock.stop();
|
||||
};
|
||||
silence = this.stop;
|
||||
hush = this.stop;
|
||||
|
||||
// =============================================================
|
||||
// Mouse functions
|
||||
// =============================================================
|
||||
@ -320,56 +333,60 @@ export class UserAPI {
|
||||
};
|
||||
|
||||
// =============================================================
|
||||
// Iterator related functions
|
||||
// Counter related functions
|
||||
// =============================================================
|
||||
|
||||
public iterator = (name: string, limit?: number, step?: number): number => {
|
||||
public counter = (
|
||||
name: string | number,
|
||||
limit?: number,
|
||||
step?: number
|
||||
): number => {
|
||||
/**
|
||||
* Returns the current value of an iterator, and increments it by the step value.
|
||||
* Returns the current value of a counter, and increments it by the step value.
|
||||
*
|
||||
* @param name - The name of the iterator
|
||||
* @param limit - The upper limit of the iterator
|
||||
* @param step - The step value of the iterator
|
||||
* @returns The current value of the iterator
|
||||
* @param name - The name of the counter
|
||||
* @param limit - The upper limit of the counter
|
||||
* @param step - The step value of the counter
|
||||
* @returns The current value of the counter
|
||||
*/
|
||||
|
||||
if (!(name in this.iterators)) {
|
||||
// Create new iterator with default step of 1
|
||||
this.iterators[name] = {
|
||||
if (!(name in this.counters)) {
|
||||
// Create new counter with default step of 1
|
||||
this.counters[name] = {
|
||||
value: 0,
|
||||
step: step ?? 1,
|
||||
limit,
|
||||
};
|
||||
} else {
|
||||
// Check if limit has changed
|
||||
if (this.iterators[name].limit !== limit) {
|
||||
if (this.counters[name].limit !== limit) {
|
||||
// Reset value to 0 and update limit
|
||||
this.iterators[name].value = 0;
|
||||
this.iterators[name].limit = limit;
|
||||
this.counters[name].value = 0;
|
||||
this.counters[name].limit = limit;
|
||||
}
|
||||
|
||||
// Check if step has changed
|
||||
if (this.iterators[name].step !== step) {
|
||||
if (this.counters[name].step !== step) {
|
||||
// Update step
|
||||
this.iterators[name].step = step ?? this.iterators[name].step;
|
||||
this.counters[name].step = step ?? this.counters[name].step;
|
||||
}
|
||||
|
||||
// Increment existing iterator by step value
|
||||
this.iterators[name].value += this.iterators[name].step;
|
||||
this.counters[name].value += this.counters[name].step;
|
||||
|
||||
// Check for limit overshoot
|
||||
if (
|
||||
this.iterators[name].limit !== undefined &&
|
||||
this.iterators[name].value > this.iterators[name].limit
|
||||
this.counters[name].limit !== undefined &&
|
||||
this.counters[name].value > this.counters[name].limit
|
||||
) {
|
||||
this.iterators[name].value = 0;
|
||||
this.counters[name].value = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Return current iterator value
|
||||
return this.iterators[name].value;
|
||||
return this.counters[name].value;
|
||||
};
|
||||
$ = this.iterator;
|
||||
$ = this.counter;
|
||||
|
||||
// =============================================================
|
||||
// Drunk mechanism
|
||||
@ -466,20 +483,11 @@ export class UserAPI {
|
||||
// Sequencer related functions
|
||||
// =============================================================
|
||||
|
||||
private _sequence_key_generator(pattern: any[]) {
|
||||
/**
|
||||
* Generates a key for the sequence function.
|
||||
*
|
||||
* @param input - The input to generate a key for
|
||||
* @returns A key for the sequence function
|
||||
*/
|
||||
// Make the pattern base64
|
||||
return btoa(JSON.stringify(pattern));
|
||||
}
|
||||
|
||||
public slice = (chunk: number): boolean => {
|
||||
const time_pos = this.epulse();
|
||||
const current_chunk = Math.floor(time_pos / chunk);
|
||||
const current_chunk = Math.floor(
|
||||
time_pos / Math.floor(chunk * this.ppqn())
|
||||
);
|
||||
return current_chunk % 2 === 0;
|
||||
};
|
||||
|
||||
@ -493,98 +501,12 @@ export class UserAPI {
|
||||
const chunk_size = args[0]; // Get the first argument (chunk size)
|
||||
const elements = args.slice(1); // Get the rest of the arguments as an array
|
||||
const timepos = this.epulse();
|
||||
const slice_count = Math.floor(timepos / chunk_size);
|
||||
const slice_count = Math.floor(
|
||||
timepos / Math.floor(chunk_size * this.ppqn())
|
||||
);
|
||||
return elements[slice_count % elements.length];
|
||||
};
|
||||
|
||||
public seqmod = (...input: any[]) => {
|
||||
if (cache.has(this._sequence_key_generator(input))) {
|
||||
let sequence = cache.get(
|
||||
this._sequence_key_generator(input)
|
||||
) as Pattern<any>;
|
||||
|
||||
sequence.options.currentIteration++;
|
||||
|
||||
if (sequence.options.currentIteration === sequence.options.nextTarget) {
|
||||
sequence.options.index++;
|
||||
sequence.options.nextTarget =
|
||||
input[sequence.options.index % input.length];
|
||||
sequence.options.currentIteration = 0;
|
||||
}
|
||||
|
||||
cache.set(this._sequence_key_generator(input), {
|
||||
pattern: input as any[],
|
||||
options: sequence.options,
|
||||
});
|
||||
|
||||
return sequence.options.currentIteration === 0;
|
||||
} else {
|
||||
let pattern_options = {
|
||||
index: -1,
|
||||
nextTarget: this.app.clock.ticks_before_new_bar,
|
||||
currentIteration: 0,
|
||||
};
|
||||
if (typeof input[input.length - 1] === "object") {
|
||||
pattern_options = {
|
||||
...input.pop(),
|
||||
...(pattern_options as object),
|
||||
};
|
||||
}
|
||||
|
||||
// pattern_options.currentIteration++;
|
||||
// TEST
|
||||
pattern_options.nextTarget = this.app.clock.ticks_before_new_bar;
|
||||
|
||||
if (pattern_options.currentIteration === pattern_options.nextTarget) {
|
||||
pattern_options.index++;
|
||||
pattern_options.nextTarget =
|
||||
input[pattern_options.index % input.length];
|
||||
pattern_options.currentIteration = 0;
|
||||
}
|
||||
|
||||
cache.set(this._sequence_key_generator(input), {
|
||||
pattern: input as any[],
|
||||
options: pattern_options,
|
||||
});
|
||||
|
||||
return pattern_options.currentIteration === 0;
|
||||
}
|
||||
};
|
||||
|
||||
public seq = (...input: any[]) => {
|
||||
/**
|
||||
* Returns a value in a sequence stored using an LRU Cache.
|
||||
* The sequence is stored in the cache with an hash identifier
|
||||
* made from a base64 encoding of the pattern. The pattern itself
|
||||
* is composed of the pattern itself (a list of arbitrary typed
|
||||
* values) and a set of options (an object) detailing how the pattern
|
||||
* should be iterated on.
|
||||
*
|
||||
* @param input - The input to generate a key for
|
||||
* Note that the last element of the input can be an object
|
||||
* containing options for the sequence function.
|
||||
* @returns A value in a sequence stored using an LRU Cache
|
||||
*/
|
||||
if (cache.has(this._sequence_key_generator(input))) {
|
||||
let sequence = cache.get(
|
||||
this._sequence_key_generator(input)
|
||||
) as Pattern<any>;
|
||||
sequence.options.index += 1;
|
||||
cache.set(this._sequence_key_generator(input), sequence);
|
||||
return sequence.pattern[sequence.options.index % sequence.pattern.length];
|
||||
} else {
|
||||
let pattern_options = { index: 0 };
|
||||
if (typeof input[input.length - 1] === "object") {
|
||||
pattern_options = { ...input.pop(), ...(pattern_options as object) };
|
||||
}
|
||||
cache.set(this._sequence_key_generator(input), {
|
||||
pattern: input as any[],
|
||||
options: pattern_options,
|
||||
});
|
||||
return cache.get(this._sequence_key_generator(input));
|
||||
}
|
||||
};
|
||||
|
||||
pick = <T>(...array: T[]): T => {
|
||||
/**
|
||||
* Returns a random element from an array.
|
||||
@ -940,19 +862,6 @@ export class UserAPI {
|
||||
return final_pulses.some((p) => p == true);
|
||||
};
|
||||
|
||||
stop = (): void => {
|
||||
/**
|
||||
* Stops the clock.
|
||||
*
|
||||
* @see silence
|
||||
* @see hush
|
||||
*/
|
||||
this.app.clock.pause();
|
||||
this.app.setButtonHighlighting("pause", true);
|
||||
};
|
||||
silence = this.stop;
|
||||
hush = this.stop;
|
||||
|
||||
prob = (p: number): boolean => {
|
||||
/**
|
||||
* Returns true p% of the time.
|
||||
@ -977,7 +886,7 @@ export class UserAPI {
|
||||
return this.randomGen() > 0.5;
|
||||
};
|
||||
|
||||
min = (...values: number[]): number => {
|
||||
public min = (...values: number[]): number => {
|
||||
/**
|
||||
* Returns the minimum value of a list of numbers.
|
||||
*
|
||||
@ -987,7 +896,7 @@ export class UserAPI {
|
||||
return Math.min(...values);
|
||||
};
|
||||
|
||||
max = (...values: number[]): number => {
|
||||
public max = (...values: number[]): number => {
|
||||
/**
|
||||
* Returns the maximum value of a list of numbers.
|
||||
*
|
||||
@ -997,6 +906,20 @@ export class UserAPI {
|
||||
return Math.max(...values);
|
||||
};
|
||||
|
||||
public mean = (...values: number[]): number => {
|
||||
/**
|
||||
* Returns the mean of a list of numbers.
|
||||
*
|
||||
* @param values - The list of numbers
|
||||
* @returns The mean value of the list of numbers
|
||||
*/
|
||||
const sum = values.reduce(
|
||||
(accumulator, currentValue) => accumulator + currentValue,
|
||||
0
|
||||
);
|
||||
return sum / values.length;
|
||||
};
|
||||
|
||||
limit = (value: number, min: number, max: number): number => {
|
||||
/**
|
||||
* Limits a value between a minimum and a maximum.
|
||||
@ -1036,30 +959,24 @@ export class UserAPI {
|
||||
};
|
||||
|
||||
public mod = (...n: number[]): boolean => {
|
||||
const results: boolean[] = n.map((value) => this.epulse() % value === 0);
|
||||
const results: boolean[] = n.map(
|
||||
(value) => this.epulse() % Math.floor(value * this.ppqn()) === 0
|
||||
);
|
||||
return results.some((value) => value === true);
|
||||
};
|
||||
|
||||
public modbar = (...n: number[]): boolean => {
|
||||
const results: boolean[] = n.map((value) => this.bar() % value === 0);
|
||||
const results: boolean[] = n.map(
|
||||
(value) => this.bar() % Math.floor(value * this.ppqn()) === 0
|
||||
);
|
||||
return results.some((value) => value === true);
|
||||
};
|
||||
|
||||
// mod = (...pulse: number[]): boolean => {
|
||||
// /**
|
||||
// * Returns true if the current pulse is a modulo of any of the given pulses.
|
||||
// *
|
||||
// * @param pulse - The pulse to check for
|
||||
// * @returns True if the current pulse is a modulo of any of the given pulses
|
||||
// */
|
||||
// return pulse.some((p) => this.app.clock.time_position.pulse % p === 0);
|
||||
// };
|
||||
|
||||
// =============================================================
|
||||
// Rythmic generators
|
||||
// =============================================================
|
||||
|
||||
euclid = (
|
||||
public euclid = (
|
||||
iterator: number,
|
||||
pulses: number,
|
||||
length: number,
|
||||
@ -1076,6 +993,7 @@ export class UserAPI {
|
||||
*/
|
||||
return this._euclidean_cycle(pulses, length, rotate)[iterator % length];
|
||||
};
|
||||
ec = this.euclid;
|
||||
|
||||
_euclidean_cycle(
|
||||
pulses: number,
|
||||
@ -1216,20 +1134,30 @@ export class UserAPI {
|
||||
return (this.triangle(freq, offset) + 1) / 2;
|
||||
};
|
||||
|
||||
square = (freq: number = 1, offset: number = 0): number => {
|
||||
square = (
|
||||
freq: number = 1,
|
||||
offset: number = 0,
|
||||
duty: number = 0.5
|
||||
): number => {
|
||||
/**
|
||||
* Returns a square wave between -1 and 1.
|
||||
* Returns a square wave with a specified duty cycle between -1 and 1.
|
||||
*
|
||||
* @returns A square wave between -1 and 1
|
||||
* @returns A square wave with a specified duty cycle between -1 and 1
|
||||
* @see saw
|
||||
* @see triangle
|
||||
* @see sine
|
||||
* @see noise
|
||||
*/
|
||||
return this.saw(freq, offset) > 0 ? 1 : -1;
|
||||
const period = 1 / freq;
|
||||
const t = (Date.now() / 1000 + offset) % period;
|
||||
return t / period < duty ? 1 : -1;
|
||||
};
|
||||
|
||||
usquare = (freq: number = 1, offset: number = 0): number => {
|
||||
usquare = (
|
||||
freq: number = 1,
|
||||
offset: number = 0,
|
||||
duty: number = 0.5
|
||||
): number => {
|
||||
/**
|
||||
* Returns a square wave between 0 and 1.
|
||||
*
|
||||
@ -1238,7 +1166,7 @@ export class UserAPI {
|
||||
* @returns A square wave between 0 and 1
|
||||
* @see square
|
||||
*/
|
||||
return (this.square(freq, offset) + 1) / 2;
|
||||
return (this.square(freq, offset, duty) + 1) / 2;
|
||||
};
|
||||
|
||||
noise = (): number => {
|
||||
@ -1268,8 +1196,9 @@ export class UserAPI {
|
||||
sound = (sound: string) => {
|
||||
return new Sound(sound, this.app);
|
||||
};
|
||||
|
||||
snd = this.sound;
|
||||
samples = samples;
|
||||
soundMap = soundMap;
|
||||
|
||||
log = console.log;
|
||||
|
||||
|
||||
@ -1,11 +1,15 @@
|
||||
const key_shortcut = (shortcut: string): string => {
|
||||
return `<kbd class="px-2 py-1.5 text-sm font-semibold text-gray-800 bg-gray-100 border border-gray-200 rounded-lg dark:bg-gray-600 dark:text-gray-100 dark:border-gray-500">${shortcut}</kbd>`;
|
||||
return `<kbd class="lg:px-2 lg:py-1.5 px-1 py-1 lg:text-sm text-xs font-semibold text-gray-800 bg-gray-100 border border-gray-200 rounded-lg dark:bg-gray-600 dark:text-gray-100 dark:border-gray-500">${shortcut}</kbd>`;
|
||||
};
|
||||
|
||||
const injectAvailableSamples = (): string => {
|
||||
return "";
|
||||
};
|
||||
|
||||
const introduction: string = `
|
||||
# Welcome
|
||||
|
||||
Welcome to the Topos documentation. This documentation companion is made to help you understand the software and the ideas behind Topos. You can summon it anytime by pressing ${key_shortcut(
|
||||
Welcome to the Topos documentation. These pages are made to help you understand the software and the ideas behind Topos. You can jump here anytime by pressing ${key_shortcut(
|
||||
"Ctrl + D"
|
||||
)}. Press again to make the documentation disappear.
|
||||
|
||||
@ -14,7 +18,11 @@ Welcome to the Topos documentation. This documentation companion is made to help
|
||||
Topos is an _algorithmic_ sequencer. Topos uses small algorithms to represent musical sequences and processes. These can be written in just a few lines of code. Topos is made to be _live-coded_. The _live coder_ strives for the constant interaction with algorithms and sound during a musical performance. Topos is aiming to be a digital playground for live algorithmic music.
|
||||
|
||||
|
||||
Topos is deeply inspired by the [Monome Teletype](https://monome.org/). The Teletype is an open source hardware module for Eurorack synthesizers. While the Teletype was initially born as an hardware module, Topos is a web-browser based software sequencer from the same family! It is a sequencer, a scriptable interface, a companion for algorithmic music-making. Topos wishes to fullfill the same goal than the Teletype, keeping the same spirit alive on the web. It is free, open-source, and made to be shared and used by everyone.
|
||||
Topos is deeply inspired by the [Monome Teletype](https://monome.org/). The Teletype is/was an open source hardware module for Eurorack synthesizers. While the Teletype was initially born as an hardware module, Topos aims to be a web-browser based software sequencer from the same family! It is a sequencer, a scriptable interface, a companion for algorithmic music-making. Topos wishes to fullfill the same goal than the Teletype, keeping the same spirit alive on the web. It is free, open-source, and made to be shared and used by everyone.
|
||||
|
||||
## How to read this documentation
|
||||
|
||||
These pages have been conceived to introduce the core concepts first before diving to the more arcane bits. You can read them in order if you just found out about this software! Later on, this documentation will only help you to refresh your memory about some function, etc...
|
||||
|
||||
## Example
|
||||
|
||||
@ -22,51 +30,53 @@ Press ${key_shortcut(
|
||||
"Ctrl + G"
|
||||
)} to switch to the global file. This is where everything starts! Evaluate the following script there by pasting and pressing ${key_shortcut(
|
||||
"Ctrl + Enter"
|
||||
)}:
|
||||
)}. You are now making music:
|
||||
|
||||
<pre><code class="language-javascript">
|
||||
if (bar() % 4 > 2 ) {
|
||||
often() && mod(48) && sound('808bd').out()
|
||||
mod(24) && euclid($('a'), 3, 8) && sound('808sd').out()
|
||||
mod(seqbeat(24,12)) && euclid($('a'), 7, 8) && sound('hh')
|
||||
.delay(0.75).delaytime(0.75)
|
||||
.speed(seqbeat(1,2,3,4)).out()
|
||||
mod(48) && sound('bd').n(6).out()
|
||||
} else {
|
||||
mod(24) && sound('hh').n(seqbeat(1,2,3,4)).end(.01).out()
|
||||
mod(48) && sound('kick').out()
|
||||
mod(24) && euclid($('ba'), 5, 8) && sound('cp').out()
|
||||
}
|
||||
bpm(80)
|
||||
mod(0.25) :: sound('sawtooth')
|
||||
.note(seqbar(
|
||||
pick(60, 67, 63) - 12, pick(60, 67, 63) - 12,
|
||||
pick(60, 67, 63) - 12 + 5, pick(60, 67, 63) - 12 + 5,
|
||||
pick(60, 67, 63) - 12 + 7, pick(60, 67, 63) - 12 + 7) + (sometimes() ? 24 : 12))
|
||||
.dur(0.1).fmi(8).fmh(4).room(0.9)
|
||||
.gain(0.75).cutoff(500 + usine(8) * 10000)
|
||||
.delay(0.5).delaytime(bpm() / 60 / 4 / 3)
|
||||
.delayfeedback(0.25)
|
||||
.out()
|
||||
mod(1) && snd('kick').out()
|
||||
mod(2) && snd('snare').out()
|
||||
mod(.5) && snd('hat').out()
|
||||
</code></pre>
|
||||
`;
|
||||
|
||||
const software_interface: string = `
|
||||
# Interface
|
||||
|
||||
The Topos interface is molded around the core concepts at play: _scripts_ and _universes_. By mastering them, you will be able to compose complex algorithmic musical compositions.
|
||||
The Topos interface is molded around the core concepts of the software: _scripts_ and _universes_. By mastering the interface, you will already understand quite a lot about Topos and how to play music with it.
|
||||
|
||||
## Scripts
|
||||
|
||||
Topos works by linking together several scripts into what is called a _universe_:
|
||||
Every Topos session is composed of several scripts. A set of scripts is called a _universe_. Every script is written using the JavaScript programming language and describes a musical or algorithmic process that takes place over time.
|
||||
|
||||
- the global script (${key_shortcut(
|
||||
"Ctrl + G"
|
||||
)}): Evaluated for every clock pulse.
|
||||
)}): **Evaluated for every clock pulse**. The central piece, acting as the conductor for all the other scripts. You can also jam directly from the global script to test your ideas before pushing them to a separate script.
|
||||
- the local scripts (${key_shortcut(
|
||||
"Ctrl + L"
|
||||
)}): Evaluated _on demand_. Local scripts are storing musical parts, logic or whatever you need!
|
||||
)}): **Evaluated on demand**. Local scripts are used to store anything too complex to sit in the global script. It can be a musical process, a whole section of your composition, a complex controller that you've built for your hardware, etc...
|
||||
- the init script (${key_shortcut(
|
||||
"Ctrl + I"
|
||||
)}): Evaluated on program load. Used to set up the software (_bpm_, etc...).
|
||||
)}): **Evaluated on program load**. Used to set up the software the session to the desired state before playing (_bpm_, etc...).
|
||||
- the note file (${key_shortcut(
|
||||
"Ctrl + N"
|
||||
)}): Not evaluated. Used to store thoughts and ideas about the music you are making.
|
||||
)}): **Not evaluated**. Used to store your thoughts or commentaries about the session you are currently playing. It is nothing more than a scratchpad really!
|
||||
|
||||
## Universes
|
||||
|
||||
A set of files is called a _universe_. Topos can store several universes and switch immediately from one to another. You can switch between universes by pressing ${key_shortcut(
|
||||
"Ctrl + B"
|
||||
)}. You can also create a new universe by entering a name that has never been used before. _Universes_ are only known by their names.
|
||||
)}. You can also create a new universe by entering a name that has never been used before. _Universes_ are only referenced by their names. Once a universe is loaded, it is not possible to call any data/code from any other universe.
|
||||
|
||||
Switching between universes will not stop the transport nor reset the clock. You are switching the context but time keeps flowing. This can be useful to prepare immediate transitions between songs and parts. Think of universes as an algorithmic set of music. All scripts in a given universe are aware about how many times they have been runned already. You can reset that value programatically.
|
||||
|
||||
@ -76,33 +86,45 @@ You can clear the current universe by pressing the flame button on the top right
|
||||
const time: string = `
|
||||
# Time
|
||||
|
||||
Time in Topos is handled by a _transport_ system. It allows you to **play**, **pause** and **reset** time. Time is quite simple to understand:
|
||||
Time in Topos can be **paused** and/or **resetted**. Musical time is flowing at a given **BPM** (_beats per minute_) like a regular drum machine. There are three core values that you will often interact with in one form or another:
|
||||
|
||||
- **bars**: how many bars have elapsed since the origin of time.
|
||||
- **beats**: how many beats have elapsed since the beginning of the bar.
|
||||
- **pulse**: how many pulses have elapsed since the last beat.
|
||||
|
||||
The **pulse** is also known as the [PPQN](https://en.wikipedia.org/wiki/Pulses_per_quarter_note). By default, Topos is using a pulses per quarter note of 48. It means that the lowest possible rhythmic value is 1/48 of a quarter note. That's plenty of time already. Music is sequenced by playing around with these core time values.
|
||||
To change the tempo, use the <icode>bpm(number)</icode> function. You can interact with time using interface buttons, keyboard shortcuts but also by using the <icode>play()</icode>, <icode>pause()</icode> and <icode>stop()</icode> functions. You will soon learn how to manipulate time to your liking for backtracking, jumping forward, etc... The traditional timeline model has little value when you can script everything.
|
||||
|
||||
**Note:** you will also learn how to manipulate time to backtrack, jump forward, etc... Your traditional timeline based playback will progressively get more spicy.
|
||||
**Note:** the <icode>bpm(number)</icode> function can serve both for getting and setting the **BPM** value.
|
||||
|
||||
## Programming with time
|
||||
## Pulses
|
||||
|
||||
To make a beat, you need a certain number of time grains or **pulses**. The **pulse** is also known as the [PPQN](https://en.wikipedia.org/wiki/Pulses_per_quarter_note). By default, Topos is using a _pulses per quarter note_ of 48. You can change it by using the <icode>ppqn(number)</icode> function. It means that the lowest possible rhythmic value is 1/48 of a quarter note. That's plenty of time already.
|
||||
|
||||
**Note:** the <icode>ppqn(number)</icode> function can serve both for getting and setting the **PPQN** value.
|
||||
|
||||
## Time Primitives
|
||||
|
||||
Every script can access the current time by using the following functions:
|
||||
|
||||
- <icode>bar(n: number)</icode>: returns the current bar since the origin of time.
|
||||
|
||||
- <icode>beat(n: number)</icode>: returns the current beat since the origin of the bar.
|
||||
- <icode>beat(n: number)</icode>: returns the current beat since the beginning of the bar.
|
||||
|
||||
- <icode>ebeat()</icode>: returns the current beat since the origin of time.
|
||||
- <icode>ebeat()</icode>: returns the current beat since the origin of time (counting from 1).
|
||||
|
||||
- <icode>pulse()</icode>: returns the current bar since the origin of the beat.
|
||||
|
||||
- <icode>epulse()</icode>: returns the current bar since the origin of time.
|
||||
- <icode>ppqn()</icode>: returns the current **PPQN** (see above).
|
||||
|
||||
## Useful basic functions
|
||||
- <icode>bpm()</icode>: returns the current **BPM** (see above).
|
||||
|
||||
Some functions are used very often as time primitives. They are used to create more complex rhythms and patterns:
|
||||
- <icode>time()</icode>: returns the current wall clock time, the real time of the system.
|
||||
|
||||
These values are **extremely useful** to craft more complex syntax or to write musical scores. However, Topos is also offering more high-level sequencing functions to make it easier to play music.
|
||||
|
||||
## Useful Basic Functions
|
||||
|
||||
Some functions can be leveraged to play rhythms without thinking too much about the clock. Learn them well:
|
||||
|
||||
- <icode>beat(...values: number[])</icode>: returns <icode>true</icode> on the given beat. You can add any number of beat values, (_e.g._ <icode>onbeat(1.2,1.5,2.3,2.5)</icode>). The function will return <icode>true</icode> only for a given pulse, which makes this function very useful for drumming.
|
||||
|
||||
@ -112,32 +134,52 @@ Some functions are used very often as time primitives. They are used to create m
|
||||
onbeat(3) && sound('sd').out()
|
||||
\`\`\`
|
||||
|
||||
- <icode>mod(...values: number[])</icode>: returns <icode>true</icode> if the current pulse is a multiple of the given value. You can add any number of values, (_e.g._ <icode>mod(12,36)</icode>).
|
||||
- <icode>mod(...values: number[])</icode>: returns <icode>true</icode> if the current pulse is a multiple of the given value. You can add any number of values, (_e.g._ <icode>mod(.25,.75)</icode>). Note that <icode>1</icode> will equal to <icode>ppqn()</icode> pulses by default. Thus, <icode>mod(.5)</icode> for a **PPQN** of 48 will be <icode>24</icode> pulses.
|
||||
|
||||
\`\`\`javascript
|
||||
mod(48) && sound('bd').out()
|
||||
mod(pick(12,24)) && sound('hh').out()
|
||||
mod(24) && sound('jvbass').out()
|
||||
mod(1) && sound('bd').out()
|
||||
mod(pick(.25,.5)) && sound('hh').out()
|
||||
mod(.5) && sound('jvbass').out()
|
||||
\`\`\`
|
||||
|
||||
- <icode>onbar(...values: number[])</icode>: returns <icode>true</icode> if the bar is currently equal to any of the specified values.
|
||||
- <icode>modbar(...values: number[])</icode>: returns <icode>true</icode> if the bar is currently a multiple of any of the specified values.
|
||||
|
||||
## Rhythm generators
|
||||
|
||||
We included a bunch of popular rhythm generators in Topos such as the euclidian rhythms algorithms or the one to generate rhythms based on a binary sequence. They all work using _iterators_ that you will gradually learn to use for iterating over lists.
|
||||
|
||||
- <icode>euclid(iterator: number, pulses: number, length: number, rotate: number): boolean</icode>: generates <icode>true</icode> or <icode>false</icode> values from an euclidian rhythm sequence. This algorithm is very popular in the electronic music making world.
|
||||
|
||||
\`\`\`javascript
|
||||
mod(.5) && euclid($(1), 5, 8) && snd('kick').out()
|
||||
mod(.5) && euclid($(2), 2, 8) && snd('sd').out()
|
||||
\`\`\`
|
||||
|
||||
- <icode>bin(iterator: number, n: number): boolean</icode>: a binary rhythm generator. It transforms the given number into its binary representation (_e.g_ <icode>34</icode> becomes <icode>100010</icode>). It then returns a boolean value based on the iterator in order to generate a rhythm.
|
||||
|
||||
|
||||
\`\`\`javascript
|
||||
mod(.5) && euclid($(1), 34) && snd('kick').out()
|
||||
mod(.5) && euclid($(2), 48) && snd('sd').out()
|
||||
\`\`\`
|
||||
|
||||
## Using time as a conditional
|
||||
|
||||
You can use the time functions as conditionals. The following example will play a pattern A for 2 bars and a pattern B for 2 bars:
|
||||
|
||||
\`\`\`javascript
|
||||
if((bar() % 4) > 1) {
|
||||
mod(48) && sound('kick').out()
|
||||
rarely() && mod(24) && sound('sd').out()
|
||||
mod(24) && sound('jvbass').freq(500).out()
|
||||
mod(1) && sound('kick').out()
|
||||
rarely() && mod(.5) && sound('sd').out()
|
||||
mod(.5) && sound('jvbass').freq(500).out()
|
||||
} else {
|
||||
mod(24) && sound('hh').out()
|
||||
mod(36) && sound('cp').out()
|
||||
mod(24) && sound('jvbass').freq(250).out()
|
||||
mod(.5) && sound('hh').out()
|
||||
mod(.75) && sound('cp').out()
|
||||
mod(.5) && sound('jvbass').freq(250).out()
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
`;
|
||||
|
||||
const midi: string = `
|
||||
@ -152,10 +194,23 @@ You can use Topos to play MIDI thanks to the [WebMIDI API](https://developer.moz
|
||||
|
||||
\`\`\`javascript
|
||||
bpm(80) // Setting a default BPM
|
||||
mod(24) && note(36 + seqbeat(0,12), {duration: 0.02})
|
||||
mod(12) && note(pick(64, 76), {duration: 0.05})
|
||||
mod(36) && note(seqbeat(64, 67, 69), {duration: 0.05})
|
||||
sometimes() && mod(12) && note(seqbeat(64, 67, 69) + 24, {duration: 0.5})
|
||||
mod(.5) && note(36 + seqbeat(0,12)).duration(0.02).out()
|
||||
mod(.25) && note(pick(64, 76)).duration(0.05).out()
|
||||
mod(.75) && note(seqbeat(64, 67, 69)).duration(0.05).out()
|
||||
sometimes() && mod(.25) && note(seqbeat(64, 67, 69) + 24).duration(0.05).out()
|
||||
\`\`\`
|
||||
|
||||
### Note chaining
|
||||
|
||||
The <icode>note(number)</icode> function can be chained to _specify_ a midi note more. For instance, you can add a duration, a velocity, a channel, etc...:
|
||||
|
||||
\`\`\`javascript
|
||||
mod(0.25) && note(60)
|
||||
.sometimes(n=>n.note(irand(40,60)))
|
||||
.duration(0.05)
|
||||
.channel(2)
|
||||
.port("bespoke")
|
||||
.out()
|
||||
\`\`\`
|
||||
|
||||
## Control and Program Changes
|
||||
@ -184,7 +239,7 @@ You can use Topos to play MIDI thanks to the [WebMIDI API](https://developer.moz
|
||||
- <icode>midi_clock()</icode>: send a MIDI Clock message. This function is used to synchronize Topos with other MIDI devices or DAWs.
|
||||
|
||||
\`\`\`javascript
|
||||
mod(12) && midi_clock() // Sending clock to MIDI device from the global buffer
|
||||
mod(.25) && midi_clock() // Sending clock to MIDI device from the global buffer
|
||||
\`\`\`
|
||||
|
||||
## MIDI Output Selection
|
||||
@ -210,8 +265,8 @@ I recommended you to run the following scripts in the global script (${key_short
|
||||
The basic function to play a sound is <icode>sound('sample/synth').out()</icode>. If the given sound exists in the database, it will be automatically queried and will start playing once loaded. To play a very basic beat, evaluate the following script:
|
||||
|
||||
\`\`\`javascript
|
||||
mod(48) && sound('bd').out()
|
||||
mod(24) && sound('hh').out()
|
||||
mod(1) && sound('bd').out()
|
||||
mod(0.5) && sound('hh').out()
|
||||
\`\`\`
|
||||
|
||||
In plain english, this translates to:
|
||||
@ -226,7 +281,7 @@ If you remove the **mod** instruction, you will end up with a deluge of kick dru
|
||||
The <icode>.n(number)</icode> method can be used to pick a sample from the currently selected sample folder. For instance, the following script will play a random sample from the _kick_ folder:
|
||||
|
||||
\`\`\`javascript
|
||||
mod(48) && sound('kick').n(pick(1,2,3,4,5,6,7,8)).out()
|
||||
mod(1) && sound('kick').n(pick(1,2,3,4,5,6,7,8)).out()
|
||||
\`\`\`
|
||||
|
||||
Don't worry about the number. If it gets too big, it will be automatically wrapped to the number of samples in the folder.
|
||||
@ -235,8 +290,8 @@ Don't worry about the number. If it gets too big, it will be automatically wrapp
|
||||
|
||||
The <icode>sound('sample_name')</icode> function can be chained to _specify_ a sound more. For instance, you can add a filter and some effects to your high-hat:
|
||||
\`\`\`javascript
|
||||
mod(24) && sound('hh')
|
||||
.speed(pick(1,2,3))
|
||||
mod(0.5) && sound('hh')
|
||||
.sometimes(s=>s.speed(pick(1,5,10)))
|
||||
.room(0.5)
|
||||
.cutoff(usine(2) * 5000)
|
||||
.out()
|
||||
@ -279,6 +334,15 @@ No sound will play until you add <icode>.out()</icode> at the end of the chain.
|
||||
| <icode>out()</icode> | Returns an object processed by the <icode>superdough</icode> function, using the current values in the <icode>values</icode> object and the <icode>pulse_duration</icode> from the <icode>app.clock</icode>. |
|
||||
`;
|
||||
|
||||
const samples: string = `
|
||||
# Audio Samples
|
||||
|
||||
## Available audio samples
|
||||
|
||||
${injectAvailableSamples()}
|
||||
|
||||
`;
|
||||
|
||||
const about: string = `
|
||||
# About Topos
|
||||
|
||||
@ -318,10 +382,113 @@ The code you enter in any of the scripts is evaluated in strict mode. This tells
|
||||
- **about errors and printing:** your code will crash! Don't worry, it will hopefully try to crash in the most gracious way possible. To check if your code is erroring, you will have to open the dev console with ${key_shortcut(
|
||||
"Ctrl + Shift + I"
|
||||
)}. You cannot directly use <icode>console.log('hello, world')</icode> in the interface. You will have to open the console as well to see your messages being printed there!
|
||||
- **about new syntax:** sometimes, we have taken liberties with the JavaScript syntax in order to make it easier/faster to write on stage. <icode>&&</icode> can also be written <icode>::</icode> or <icode>-></icode> because it is faster to type or better for the eyes!
|
||||
|
||||
## About crashes and bugs
|
||||
|
||||
Things will crash, that's also part of the show. You will learn progressively to avoid mistakes and to write safer code. Do not hesitate to kill the page or to stop the transport if you feel overwhelmed by an algorithm blowing up. There are no safeties in place to save you. This is to ensure that you have all the available possible room to write bespoke code and experiment with your ideas through code.
|
||||
`;
|
||||
|
||||
const functions: string = `
|
||||
# Functions
|
||||
|
||||
## Global Shared Variables
|
||||
|
||||
By default, each script is independant from each other. Scripts live in their own bubble and you cannot get or set variables affecting a script from any other script. **However**, everybody knows that global variables are cool and should be used everywhere. This is an incredibely powerful tool to use for radically altering a composition in a few lines of code.
|
||||
|
||||
- <icode>variable(a: number | string, b?: any)</icode>: if only one argument is provided, the value of the variable will be returned through its name, denoted by the first argument. If a second argument is used, it will be saved as a global variable under the name of the first argument.
|
||||
- <icode>delete_variable(name: string)</icode>: deletes a global variable from storage.
|
||||
- <icode>clear_variables()</icode>: clear **ALL** variables. **This is a destructive operation**!
|
||||
|
||||
## Counter and iterators
|
||||
|
||||
You will often need to use iterators and/or counters to index over data structures (getting a note from a list of notes, etc...). There are functions ready to be used for this. Each script also comes with its own iterator that you can access using the <icode>i</icode> variable. **Note:** the script iteration count is **not** resetted between sessions. It will continue to increase the more you play, even if you just picked up an old project.
|
||||
|
||||
- <icode>counter(name: number | string, limit?: number, step?: number)</icode>: reads the value of the counter <icode>name</icode>. You can also call this function using the dollar symbol: <icode>$</icode>.
|
||||
- <icode>limit?</icode>: counter upper limit before wrapping up.
|
||||
- <icode>step?</icode>: incrementor. If step is <icode>2</icode>, the iterator will go: <icode>0, 2, 4, 6</icode>, etc...
|
||||
|
||||
- <icode>drunk(n?: number)</icode>: returns the value of the internal drunk walk counter. This iterator will sometimes go up, sometimes go down. It comes with companion functions that you can use to finetune its behavior.
|
||||
- <icode>drunk_max(max: number)</icode>: sets the maximum value.
|
||||
- <icode>drunk_min(min: number)</icode>: sets the minimum value.
|
||||
- <icode>drunk_wrap(wrap: boolean)</icode>: whether to wrap the drunk walk to 0 once the upper limit is reached or not.
|
||||
|
||||
|
||||
|
||||
## Scripts
|
||||
|
||||
You can control scripts programatically. This is the core concept of Topos after all!
|
||||
|
||||
- <icode>script(...number: number[])</icode>: call one or more scripts (_e.g. <icode>script(1,2,3,4)</icode>). Once called, scripts will be evaluated once. There are nine local scripts by default. You cannot call the global script nor the initialisation script.
|
||||
|
||||
- <icode>clear_script(number)</icode>: deletes the given script.
|
||||
- <icode>copy_script(from: number, to: number)</icode>: copies a local script denoted by its number to another local script. **This is a destructive operation!**
|
||||
|
||||
## Mouse
|
||||
|
||||
You can get the current position of the mouse on the screen by using the following functions:
|
||||
|
||||
- <icode>mouseX()</icode>: the horizontal position of the mouse on the screen (as a floating point number).
|
||||
- <icode>mouseY()</icode>: the vertical position of the mouse on the screen (as a floating point number).
|
||||
|
||||
## Low Frequency Oscillators
|
||||
|
||||
Low Frequency Oscillators (_LFOs_) are an important piece in any digital audio workstation or synthesizer. Topos implements some basic waveforms you can play with to automatically modulate your paremeters.
|
||||
|
||||
- <icode>sine(freq: number = 1, offset: number= 0): number</icode>: returns a sinusoïdal oscillation between <icode>-1</icode> and <icode>1</icode>.
|
||||
- <icode>usine(freq: number = 1, offset: number= 0): number</icode>: returns a sinusoïdal oscillation between <icode>0</icode> and <icode>1</icode>. The <icode>u</icode> stands for _unipolar_.
|
||||
|
||||
\`\`\`javascript
|
||||
mod(.25) && snd('cp').speed(1 + usine(0.25) * 2).out()
|
||||
\`\`\`
|
||||
|
||||
- <icode>triangle(freq: number = 1, offset: number= 0): number</icode>: returns a triangle oscillation between <icode>-1</icode> and <icode>1</icode>.
|
||||
- <icode>utriangle(freq: number = 1, offset: number= 0): number</icode>: returns a triangle oscillation between <icode>0</icode> and <icode>1</icode>. The <icode>u</icode> stands for _unipolar_.
|
||||
|
||||
\`\`\`javascript
|
||||
mod(.25) && snd('cp').speed(1 + utriangle(0.25) * 2).out()
|
||||
\`\`\`
|
||||
|
||||
- <icode>saw(freq: number = 1, offset: number= 0): number</icode>: returns a sawtooth-like oscillation between <icode>-1</icode> and <icode>1</icode>.
|
||||
- <icode>usaw(freq: number = 1, offset: number= 0): number</icode>: returns a sawtooth-like oscillation between <icode>0</icode> and <icode>1</icode>. The <icode>u</icode> stands for _unipolar_.
|
||||
|
||||
\`\`\`javascript
|
||||
mod(.25) && snd('cp').speed(1 + usaw(0.25) * 2).out()
|
||||
\`\`\`
|
||||
|
||||
- <icode>square(freq: number = 1, offset: number= 0, duty: number = .5): number</icode>: returns a square wave oscillation between <icode>-1</icode> and <icode>1</icode>. You can also control the duty cycle using the <icode>duty</icode> parameter.
|
||||
- <icode>usquare(freq: number = 1, offset: number= 0, duty: number = .5): number</icode>: returns a square wave oscillation between <icode>0</icode> and <icode>1</icode>. The <icode>u</icode> stands for _unipolar_. You can also control the duty cycle using the <icode>duty</icode> parameter.
|
||||
|
||||
\`\`\`javascript
|
||||
mod(.25) && snd('cp').speed(1 + usquare(0.25, 0, 0.25) * 2).out()
|
||||
\`\`\`
|
||||
|
||||
- <icode>noise()</icode>: returns a random value between -1 and 1.
|
||||
|
||||
\`\`\`javascript
|
||||
mod(.25) && snd('cp').speed(1 + noise() * 2).out()
|
||||
\`\`\`
|
||||
|
||||
## Probabilities
|
||||
|
||||
There are some simple functions to play with probabilities.
|
||||
|
||||
- <icode>prob(p: number)</icode>: return <icode>true</icode> _p_% of time, <icode>false</icode> in other cases.
|
||||
- <icode>toss()</icode>: throwing a coin. Head (<icode>true</icode>) or tails (<icode>false</icode>).
|
||||
|
||||
## Math functions
|
||||
|
||||
- <icode>max(...values: number[]): number</icode>: returns the maximum value of a list of numbers.
|
||||
- <icode>min(...values: number[]): number</icode>: returns the minimum value of a list of numbers.
|
||||
- <icode>mean(...values: number[]): number</icode>: returns the arithmetic mean of a list of numbers.
|
||||
- <icode>limit(value: number, min: number, max: number): number</icode>: Limits a value between a minimum and a maximum.
|
||||
|
||||
## Delay functions
|
||||
|
||||
- <icode>delay(ms: number, func: Function): void</icode>: Delays the execution of a function by a given number of milliseconds.
|
||||
- <icode>delayr(ms: number, nb: number, func: Function): void</icode>: Delays the execution of a function by a given number of milliseconds, repeated a given number of times.
|
||||
|
||||
|
||||
`;
|
||||
|
||||
const reference: string = `
|
||||
@ -371,6 +538,7 @@ export const documentation = {
|
||||
code: code,
|
||||
time: time,
|
||||
sound: sound,
|
||||
samples: samples,
|
||||
midi: midi,
|
||||
functions: functions,
|
||||
reference: reference,
|
||||
|
||||
@ -6,6 +6,10 @@ const delay = (ms: number) =>
|
||||
setTimeout(() => reject(new Error("Operation took too long")), ms)
|
||||
);
|
||||
|
||||
const codeReplace = (code: string): string => {
|
||||
let new_code = code.replace(/->/g, "&&").replace(/::/g, "&&");
|
||||
return new_code;
|
||||
};
|
||||
|
||||
const tryCatchWrapper = (
|
||||
application: Editor,
|
||||
@ -13,9 +17,12 @@ const tryCatchWrapper = (
|
||||
): Promise<boolean> => {
|
||||
return new Promise((resolve, _) => {
|
||||
try {
|
||||
Function(`"use strict";try{${code}} catch (e) {console.log(e)};`).call(application.api);
|
||||
Function(
|
||||
`"use strict";try{${codeReplace(code)}} catch (e) {console.log(e); _reportError(e);};`
|
||||
).call(application.api);
|
||||
resolve(true);
|
||||
} catch (error) {
|
||||
application.error_line.innerHTML = error as string;
|
||||
console.log(error);
|
||||
resolve(false);
|
||||
}
|
||||
@ -31,7 +38,7 @@ const addFunctionToCache = (code: string, fn: Function) => {
|
||||
cache.delete(cache.keys().next().value);
|
||||
}
|
||||
cache.set(code, fn);
|
||||
}
|
||||
};
|
||||
|
||||
export const tryEvaluate = async (
|
||||
application: Editor,
|
||||
@ -41,7 +48,7 @@ export const tryEvaluate = async (
|
||||
try {
|
||||
code.evaluations!++;
|
||||
const candidateCode = code.candidate;
|
||||
|
||||
|
||||
if (cache.has(candidateCode)) {
|
||||
// If the code is already in cache, use it
|
||||
cache.get(candidateCode)!.call(application.api);
|
||||
@ -54,13 +61,18 @@ export const tryEvaluate = async (
|
||||
]);
|
||||
if (isCodeValid) {
|
||||
code.committed = code.candidate;
|
||||
const newFunction = new Function(`"use strict";try{${wrappedCode}} catch (e) {console.log(e)};`);
|
||||
const newFunction = new Function(
|
||||
`"use strict";try{${codeReplace(
|
||||
wrappedCode
|
||||
)}} catch (e) {console.log(e)};`
|
||||
);
|
||||
addFunctionToCache(candidateCode, newFunction);
|
||||
} else {
|
||||
await evaluate(application, code, timeout);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
application.error_line.innerHTML = error as string;
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
@ -77,6 +89,7 @@ export const evaluate = async (
|
||||
]);
|
||||
if (code.evaluations) code.evaluations++;
|
||||
} catch (error) {
|
||||
application.error_line.innerHTML = error as string;
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
19
src/Note.ts
19
src/Note.ts
@ -17,6 +17,11 @@ export class Note extends Event {
|
||||
return this;
|
||||
}
|
||||
|
||||
duration = (value: number): this => {
|
||||
this.values['duration'] = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
channel = (value: number): this => {
|
||||
this.values['channel'] = value;
|
||||
return this;
|
||||
@ -33,13 +38,19 @@ export class Note extends Event {
|
||||
}
|
||||
|
||||
modify = (func: Function): this => {
|
||||
if(typeof func === 'function') {
|
||||
const funcResult = func(this);
|
||||
if(funcResult instanceof Object) return funcResult;
|
||||
if(funcResult instanceof Object) {
|
||||
console.log("IS OBJECT?");
|
||||
return funcResult;
|
||||
|
||||
}
|
||||
else {
|
||||
func(this.values);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Add bend
|
||||
freq = (value: number): this => {
|
||||
@ -63,7 +74,11 @@ export class Note extends Event {
|
||||
const note = this.values.note ? this.values.note : 60;
|
||||
const channel = this.values.channel ? this.values.channel : 0;
|
||||
const velocity = this.values.velocity ? this.values.velocity : 100;
|
||||
const duration = this.values.duration ? this.values.duration : 0.5;
|
||||
|
||||
const duration = this.values.duration ?
|
||||
this.values.duration * Math.floor(this.app.clock.pulse_duration * this.app.api.ppqn()) :
|
||||
this.app.clock.pulse_duration * this.app.api.ppqn();
|
||||
|
||||
const bend = this.values.bend ? this.values.bend : undefined;
|
||||
|
||||
const port = this.values.port ?
|
||||
|
||||
197
src/Sound.ts
197
src/Sound.ts
@ -7,171 +7,213 @@ import {
|
||||
} from "superdough";
|
||||
|
||||
export class Sound extends Event {
|
||||
|
||||
values: { [key: string]: any }
|
||||
values: { [key: string]: any };
|
||||
|
||||
constructor(sound: string|object, public app: Editor) {
|
||||
super(app);
|
||||
if (typeof sound === 'string') this.values = { 's': sound };
|
||||
if (typeof sound === 'string') this.values = { 's': sound, 'dur': 0.5 };
|
||||
else this.values = sound;
|
||||
}
|
||||
|
||||
attack = (value: number): this => {
|
||||
this.values["attack"] = value;
|
||||
return this;
|
||||
};
|
||||
atk = this.attack;
|
||||
|
||||
decay = (value: number): this => {
|
||||
this.values["decay"] = value;
|
||||
return this;
|
||||
};
|
||||
dec = this.decay;
|
||||
|
||||
sustain = (value: number): this => {
|
||||
this.values["sustain"] = value;
|
||||
return this;
|
||||
};
|
||||
sus = this.sustain;
|
||||
|
||||
release = (value: number): this => {
|
||||
this.values["release"] = value;
|
||||
return this;
|
||||
};
|
||||
rel = this.release;
|
||||
|
||||
unit = (value: number): this => {
|
||||
this.values["unit"] = value;
|
||||
return this;
|
||||
};
|
||||
|
||||
freq = (value: number): this => {
|
||||
this.values["freq"] = value;
|
||||
return this;
|
||||
};
|
||||
|
||||
fm = (value: number | string): this => {
|
||||
if (typeof value === "number") {
|
||||
this.values["fmi"] = value;
|
||||
} else {
|
||||
let values = value.split(":");
|
||||
this.values["fmi"] = parseFloat(values[0]);
|
||||
if (values.length > 1) this.values["fmh"] = parseFloat(values[1]);
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
sound = (value: string): this => {
|
||||
this.values['s'] = value
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
snd = this.sound;
|
||||
|
||||
unit = (value: number): this => {
|
||||
this.values['unit'] = value
|
||||
fmi = (value: number): this => {
|
||||
this.values["fmi"] = value;
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
freq = (value: number): this => {
|
||||
this.values['freq'] = value
|
||||
fmh = (value: number): this => {
|
||||
this.values["fmh"] = value;
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
nudge = (value: number): this => {
|
||||
this.values['nudge'] = value
|
||||
this.values["nudge"] = value;
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
cut = (value: number): this => {
|
||||
this.values['cut'] = value
|
||||
this.values["cut"] = value;
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
loop = (value: number): this => {
|
||||
this.values['loop'] = value
|
||||
this.values["loop"] = value;
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
clip = (value: number): this => {
|
||||
this.values['clip'] = value
|
||||
this.values["clip"] = value;
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
n = (value: number): this => {
|
||||
this.values['n'] = value
|
||||
this.values["n"] = value;
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
note = (value: number): this => {
|
||||
this.values['note'] = value
|
||||
this.values["note"] = value;
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
speed = (value: number): this => {
|
||||
this.values['speed'] = value
|
||||
this.values["speed"] = value;
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
begin = (value: number): this => {
|
||||
this.values['begin'] = value
|
||||
this.values["begin"] = value;
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
end = (value: number): this => {
|
||||
this.values['end'] = value
|
||||
this.values["end"] = value;
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
gain = (value: number): this => {
|
||||
this.values['gain'] = value
|
||||
this.values["gain"] = value;
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
cutoff = (value: number): this => {
|
||||
this.values['cutoff'] = value
|
||||
this.values["cutoff"] = value;
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
resonance = (value: number): this => {
|
||||
this.values['resonance'] = value
|
||||
this.values["resonance"] = value;
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
hcutoff = (value: number): this => {
|
||||
this.values['hcutoff'] = value
|
||||
this.values["hcutoff"] = value;
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
hresonance = (value: number): this => {
|
||||
this.values['hresonance'] = value
|
||||
this.values["hresonance"] = value;
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
bandf = (value: number): this => {
|
||||
this.values['bandf'] = value
|
||||
this.values["bandf"] = value;
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
bandq = (value: number): this => {
|
||||
this.values['bandq'] = value
|
||||
this.values["bandq"] = value;
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
coarse = (value: number): this => {
|
||||
this.values['coarse'] = value
|
||||
this.values["coarse"] = value;
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
crush = (value: number): this => {
|
||||
this.values['crush'] = value
|
||||
this.values["crush"] = value;
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
shape = (value: number): this => {
|
||||
this.values['shape'] = value
|
||||
this.values["shape"] = value;
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
pan = (value: number): this => {
|
||||
this.values['pan'] = value
|
||||
this.values["pan"] = value;
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
vowel = (value: number): this => {
|
||||
this.values['vowel'] = value
|
||||
this.values["vowel"] = value;
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
delay = (value: number): this => {
|
||||
this.values['delay'] = value
|
||||
this.values["delay"] = value;
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
delayfeedback = (value: number): this => {
|
||||
this.values['delayfeedback'] = value
|
||||
this.values["delayfeedback"] = value;
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
delaytime = (value: number): this => {
|
||||
this.values['delaytime'] = value
|
||||
this.values["delaytime"] = value;
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
orbit = (value: number): this => {
|
||||
this.values['orbit'] = value
|
||||
this.values["orbit"] = value;
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
room = (value: number): this => {
|
||||
this.values['room'] = value
|
||||
this.values["room"] = value;
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
size = (value: number): this => {
|
||||
this.values['size'] = value
|
||||
this.values["size"] = value;
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
velocity = (value: number): this => {
|
||||
this.values['velocity'] = value
|
||||
this.values["velocity"] = value;
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
modify = (func: Function): this => {
|
||||
const funcResult = func(this);
|
||||
@ -180,9 +222,20 @@ export class Sound extends Event {
|
||||
func(this.values);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
dur = (value: number): this => {
|
||||
this.values["dur"] = value;
|
||||
return this;
|
||||
};
|
||||
|
||||
out = (): object => {
|
||||
return superdough(
|
||||
this.values,
|
||||
this.app.clock.pulse_duration,
|
||||
this.values.dur
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
out = (): void => {
|
||||
superdough(this.values, this.app.clock.pulse_duration);
|
||||
}
|
||||
}
|
||||
106
src/main.ts
106
src/main.ts
@ -28,20 +28,21 @@ import showdown from "showdown";
|
||||
showdown.setFlavor("github");
|
||||
import showdownHighlight from "showdown-highlight";
|
||||
const classMap = {
|
||||
h1: "text-4xl text-white ml-4 mx-4 my-4 mb-8",
|
||||
h2: "text-3xl text-white mx-4 my-4 mt-12 mb-6",
|
||||
h1: "text-white lg:text-4xl text-xl lg:ml-4 lg:mx-4 mx-2 lg:my-4 my-2 lg:mb-8 mb-4 bg-neutral-900 rounded-lg py-2 px-2",
|
||||
h2: "text-white lg:text-3xl text-xl lg:ml-4 lg:mx-4 mx-2 lg:my-4 my-2 lg:mb-8 mb-4 bg-neutral-900 rounded-lg py-2 px-2",
|
||||
ul: "text-underline",
|
||||
li: "ml-12 list-disc text-2xl text-white mx-4 my-4 leading-normal",
|
||||
p: "text-2xl text-white mx-4 my-4 leading-normal",
|
||||
a: "text-2xl text-orange-300",
|
||||
code: "my-4 block whitespace-pre overflow-x-scroll",
|
||||
icode: "my-4 text-white font-mono bg-neutral-600",
|
||||
li: "ml-12 list-disc lg:text-2xl text-base text-white lg:mx-4 mx-2 my-4 lg:pl-4 my-2 leading-normal",
|
||||
p: "lg:text-2xl text-base text-white lg:mx-4 mx-2 my-4 leading-normal",
|
||||
a: "lg:text-2xl text-base text-orange-300",
|
||||
code: "lg:my-4 sm:my-1 text-base lg:text-xl block whitespace-pre overflow-x-scroll",
|
||||
icode:
|
||||
"lg:my-4 my-1 lg:text-xl sm:text-xs text-white font-mono bg-neutral-600",
|
||||
blockquote: "text-neutral-200 border-l-4 border-neutral-500 pl-4 my-4 mx-4",
|
||||
table:
|
||||
"justify-center my-8 mx-8 text-2xl w-full text-left text-white border-collapse",
|
||||
"justify-center lg:my-8 my-2 lg:mx-8 mx-2 lg:text-2xl text-base w-full text-left text-white border-collapse",
|
||||
thead:
|
||||
"text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400",
|
||||
th: "px-6 py-6",
|
||||
th: "lg:px-6 lg:py-6 px-2 py-2",
|
||||
td: "py-2",
|
||||
tr: "py-0",
|
||||
};
|
||||
@ -101,6 +102,9 @@ export class Editor {
|
||||
documentation_button: HTMLButtonElement = document.getElementById(
|
||||
"doc-button-1"
|
||||
) as HTMLButtonElement;
|
||||
eval_button: HTMLButtonElement = document.getElementById(
|
||||
"eval-button-1"
|
||||
) as HTMLButtonElement;
|
||||
|
||||
// Script selection elements
|
||||
local_button: HTMLButtonElement = document.getElementById(
|
||||
@ -159,6 +163,10 @@ export class Editor {
|
||||
"vim-mode"
|
||||
) as HTMLButtonElement;
|
||||
|
||||
// Error line
|
||||
error_line: HTMLElement = document.getElementById("error_line") as HTMLElement
|
||||
show_error: boolean = false
|
||||
|
||||
constructor() {
|
||||
// ================================================================================
|
||||
// Loading the universe from local storage
|
||||
@ -400,6 +408,11 @@ export class Editor {
|
||||
this.showDocumentation();
|
||||
});
|
||||
|
||||
this.eval_button.addEventListener("click", () => {
|
||||
this.currentFile().candidate = this.view.state.doc.toString();
|
||||
this.flashBackground("#2d313d", 200);
|
||||
});
|
||||
|
||||
this.pause_buttons.forEach((button) => {
|
||||
button.addEventListener("click", () => {
|
||||
this.setButtonHighlighting("pause", true);
|
||||
@ -500,55 +513,31 @@ export class Editor {
|
||||
});
|
||||
tryEvaluate(this, this.universes[this.selected_universe.toString()].init);
|
||||
|
||||
// Setting up the documentation page
|
||||
document
|
||||
.getElementById("docs_introduction")!
|
||||
.addEventListener("click", () => {
|
||||
this.currentDocumentationPane = "introduction";
|
||||
this.updateDocumentationContent();
|
||||
});
|
||||
document.getElementById("docs_interface")!.addEventListener("click", () => {
|
||||
this.currentDocumentationPane = "interface";
|
||||
this.updateDocumentationContent();
|
||||
});
|
||||
document.getElementById("docs_code")!.addEventListener("click", () => {
|
||||
this.currentDocumentationPane = "code";
|
||||
this.updateDocumentationContent();
|
||||
});
|
||||
document.getElementById("docs_time")!.addEventListener("click", () => {
|
||||
this.currentDocumentationPane = "time";
|
||||
this.updateDocumentationContent();
|
||||
});
|
||||
document.getElementById("docs_sound")!.addEventListener("click", () => {
|
||||
this.currentDocumentationPane = "sound";
|
||||
this.updateDocumentationContent();
|
||||
});
|
||||
document.getElementById("docs_midi")!.addEventListener("click", () => {
|
||||
this.currentDocumentationPane = "midi";
|
||||
this.updateDocumentationContent();
|
||||
});
|
||||
[
|
||||
"introduction",
|
||||
"interface",
|
||||
"code",
|
||||
"time",
|
||||
"sound",
|
||||
"samples",
|
||||
"midi",
|
||||
"functions",
|
||||
"reference",
|
||||
"shortcuts",
|
||||
"about",
|
||||
].forEach((e) => {
|
||||
let name = `docs_` + e;
|
||||
document.getElementById(name)!
|
||||
.addEventListener("click", () => {
|
||||
this.currentDocumentationPane = e;
|
||||
this.updateDocumentationContent();
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById("docs_functions")!.addEventListener("click", () => {
|
||||
this.currentDocumentationPane = "functions";
|
||||
this.updateDocumentationContent();
|
||||
});
|
||||
document.getElementById("docs_reference")!.addEventListener("click", () => {
|
||||
this.currentDocumentationPane = "reference";
|
||||
this.updateDocumentationContent();
|
||||
});
|
||||
document.getElementById("docs_shortcuts")!.addEventListener("click", () => {
|
||||
this.currentDocumentationPane = "shortcuts";
|
||||
this.updateDocumentationContent();
|
||||
});
|
||||
document.getElementById("docs_about")!.addEventListener("click", () => {
|
||||
this.currentDocumentationPane = "about";
|
||||
this.updateDocumentationContent();
|
||||
});
|
||||
|
||||
// Passing the API to the User
|
||||
Object.entries(this.api).forEach(([name, value]) => {
|
||||
(globalThis as Record<string, any>)[name] = value;
|
||||
});
|
||||
// Passing the API to the User
|
||||
Object.entries(this.api).forEach(([name, value]) => {
|
||||
(globalThis as Record<string, any>)[name] = value;
|
||||
});
|
||||
}
|
||||
|
||||
get note_buffer() {
|
||||
@ -715,6 +704,7 @@ export class Editor {
|
||||
.querySelectorAll(possible_selectors[selector])
|
||||
.forEach((button) => {
|
||||
if (highlight) button.children[0].classList.add("fill-orange-300");
|
||||
if (highlight) button.children[0].classList.add("animate-pulse");
|
||||
});
|
||||
// All other buttons must lose the highlighting
|
||||
document
|
||||
@ -725,12 +715,14 @@ export class Editor {
|
||||
button.children[0].classList.remove("fill-orange-300");
|
||||
button.children[0].classList.remove("text-orange-300");
|
||||
button.children[0].classList.remove("bg-orange-300");
|
||||
button.children[0].classList.remove("animate-pulse");
|
||||
});
|
||||
}
|
||||
|
||||
unfocusPlayButtons() {
|
||||
document.querySelectorAll('[id^="play-button-"]').forEach((button) => {
|
||||
button.children[0].classList.remove("fill-orange-300");
|
||||
button.children[0].classList.remove("animate-pulse");
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -1267,10 +1267,10 @@ sucrase@^3.32.0:
|
||||
pirates "^4.0.1"
|
||||
ts-interface-checker "^0.1.9"
|
||||
|
||||
superdough@^0.9.3:
|
||||
version "0.9.4"
|
||||
resolved "https://registry.yarnpkg.com/superdough/-/superdough-0.9.4.tgz#cfae0bc6dfe5976ea0abb423a9cf2b3670944b86"
|
||||
integrity sha512-1wOJbnm5e/9tn9TzYuhzlxhrXPJ3m6sY21tf+nNnU9JXA0ZUAGbGyHDWTT8R/cEtKziFAmgVxdwGBFOAxgTWdw==
|
||||
superdough@^0.9.5:
|
||||
version "0.9.5"
|
||||
resolved "https://registry.yarnpkg.com/superdough/-/superdough-0.9.5.tgz#316b9fa9699cf1b86285e65c4084c4e2ea4489dd"
|
||||
integrity sha512-PcEzWaD9oQ6apDCLmQq9OtyiAVB+Ell4U3RsYTR48L6jTssZILfYEG8pzHVf06QHeCl0rhl3/QlgO5Vf9HKMkg==
|
||||
dependencies:
|
||||
nanostores "^0.8.1"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user