diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml
new file mode 100644
index 0000000..7b4cd4d
--- /dev/null
+++ b/.github/workflows/linter.yml
@@ -0,0 +1,53 @@
+name: Lint Code Base
+
+#############################
+# Start the job on all pr #
+#############################
+on:
+ pull_request:
+ branches: [master]
+
+###############
+# Set the Job #
+###############
+jobs:
+ build:
+ # Name the Job
+ name: Lint Code Base
+ # Set the agent to run on
+ runs-on: ubuntu-latest
+
+ ############################################
+ # Grant status permission for MULTI_STATUS #
+ ############################################
+ permissions:
+ contents: read
+ packages: read
+ statuses: write
+
+ ##################
+ # Load all steps #
+ ##################
+ steps:
+ ##########################
+ # Checkout the code base #
+ ##########################
+ - name: Checkout Code
+ uses: actions/checkout@v3
+ with:
+ # Full git history is needed to get a proper
+ # list of changed files within `super-linter`
+ fetch-depth: 0
+
+ ################################
+ # Run Linter against code base #
+ ################################
+ - name: Lint Code Base
+ uses: super-linter/super-linter/slim@v5
+ env:
+ VALIDATE_ALL_CODEBASE: false
+ VALIDATE_CSS: true
+ VALIDATE_HTML: true
+ VALIDATE_JAVASCRIPT_ES: true
+ DEFAULT_BRANCH: master
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
\ No newline at end of file
diff --git a/.github/workflows/manual-deploy.yml b/.github/workflows/manual-deploy.yml
new file mode 100644
index 0000000..af59b18
--- /dev/null
+++ b/.github/workflows/manual-deploy.yml
@@ -0,0 +1,37 @@
+name: Manual Deploy via FTP
+
+# Controls when the action will run. Workflow runs when manually triggered using the UI
+# or API.
+on:
+ workflow_dispatch:
+
+# A workflow run is made up of one or more jobs that can run sequentially or in parallel
+jobs:
+ # This workflow contains a single job called "deploy"
+ deploy:
+ # The type of runner that the job will run on
+ runs-on: ubuntu-latest
+
+ # Steps represent a sequence of tasks that will be executed as part of the job
+ steps:
+ - name: 🚚 Get latest code
+ uses: actions/checkout@v3
+ # Runs a single command using the runners shell
+ - name: FTP Deploy
+ # You may pin to the exact commit or the version.
+ # uses: SamKirkland/FTP-Deploy-Action@8a24039354ee91000cb948cb4a1dbdf1a1b94a3c
+ uses: SamKirkland/FTP-Deploy-Action@v4.3.4
+ with:
+ # ftp server
+ server: jvpeek.de
+ # ftp username
+ username: ${{ secrets.FTPUSER }}
+ # ftp password
+ password: ${{ secrets.FTPPASS }}
+ local-dir: ./src/
+ # protocol to deploy with - ftp, ftps, or ftps-legacy
+ protocol: ftps # optional
+ # Prints which modifications will be made with current config options, but doesnt actually make any changes
+ dry-run: false # optional
+ # Deletes ALL contents of server-dir, even items in excluded with exclude argument
+ dangerous-clean-slate: false # optional
\ No newline at end of file
diff --git a/.github/workflows/purge-cache.yml b/.github/workflows/purge-cache.yml
deleted file mode 100644
index 2c73946..0000000
--- a/.github/workflows/purge-cache.yml
+++ /dev/null
@@ -1,13 +0,0 @@
-name: Purge Cloudflare Cache
-on: push
-
-jobs:
- deploy:
- runs-on: ubuntu-latest
- steps:
-
- - name: Purge Cloudflare Cache
- uses: jakejarvis/cloudflare-purge-action@master
- env:
- CLOUDFLARE_ZONE: ${{ secrets.CLOUDFLARE_ZONE }}
- CLOUDFLARE_TOKEN: ${{ secrets.CLOUDFLARE_TOKEN }}
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..88f24d9
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,66 @@
+name: Release
+
+on:
+ push:
+ branches:
+ - 'master'
+ paths:
+ - 'src/**'
+
+
+# A workflow run is made up of one or more jobs that can run sequentially or in parallel
+jobs:
+ # This workflow contains a single job called "deploy"
+ release:
+ # The type of runner that the job will run on
+ runs-on: ubuntu-latest
+
+ # Steps represent a sequence of tasks that will be executed as part of the job
+ steps:
+ - name: 🚚 Get latest code
+ uses: actions/checkout@v3
+ with:
+ # Full git history is needed to get a proper
+ # list of changed files within `super-linter`
+ fetch-depth: 0
+ # Runs a single command using the runners shell
+ - name: FTP Deploy
+ # You may pin to the exact commit or the version.
+ # uses: SamKirkland/FTP-Deploy-Action@8a24039354ee91000cb948cb4a1dbdf1a1b94a3c
+ uses: SamKirkland/FTP-Deploy-Action@v4.3.4
+ with:
+ # ftp server
+ server: jvpeek.de
+ # ftp username
+ username: ${{ secrets.FTPUSER }}
+ # ftp password
+ password: ${{ secrets.FTPPASS }}
+ local-dir: ./src/
+ # protocol to deploy with - ftp, ftps, or ftps-legacy
+ protocol: ftps # optional
+ # Prints which modifications will be made with current config options, but doesnt actually make any changes
+ dry-run: false # optional
+ # Deletes ALL contents of server-dir, even items in excluded with exclude argument
+ dangerous-clean-slate: false # optional
+ - name: Changelog
+ uses: ardalanamini/auto-changelog@v4
+ id : changelog
+ with:
+ github-token : ${{ secrets.GITHUB_TOKEN }}
+ mention-authors : true
+ mention-new-contributors: true
+ include-compare-link : true
+ include-pr-links : true
+ include-commit-links : true
+ use-github-autolink : true
+ semver : false
+ release-name: ${{ format('v{0}', github.run_number) }}
+ - name: Create Release
+ uses: ncipollo/release-action@v1
+ env :
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ with:
+ body : |
+ ${{ steps.changelog.outputs.changelog }}
+ tag: ${{ format('v{0}', github.run_number) }}
+ commit: master
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 723ef36..67b9edd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,188 @@
-.idea
\ No newline at end of file
+# --------------------------------------------------------------------------------
+# File created using 'AnGitIgnored' extensions for Visual Studio Code:
+# https://marketplace.visualstudio.com/items?itemName=AnAppWiLos.angitignored
+#-------------------------------------------------------------------------------
+# Created by https://www.toptal.com/developers/gitignore/api/python
+# Edit at https://www.toptal.com/developers/gitignore?templates=python
+
+### Python ###
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+cover/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+.pybuilder/
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+# For a library or package, you might want to ignore these files since the code is
+# intended to run in multiple environments; otherwise, check them in:
+# .python-version
+
+# pipenv
+# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+# However, in case of collaboration, if having platform-specific dependencies or dependencies
+# having no cross-platform support, pipenv may install dependencies that don't work, or not
+# install all needed dependencies.
+#Pipfile.lock
+
+# poetry
+# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
+# This is especially recommended for binary packages to ensure reproducibility, and is more
+# commonly ignored for libraries.
+# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
+#poetry.lock
+
+# pdm
+# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
+#pdm.lock
+# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
+# in version control.
+# https://pdm.fming.dev/#use-with-ide
+.pdm.toml
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# Cython debug symbols
+cython_debug/
+
+# PyCharm
+# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
+# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
+# and can be added to the global gitignore or merged into this file. For a more nuclear
+# option (not recommended) you can uncomment the following to ignore the entire idea folder.
+#.idea/
+
+### Python Patch ###
+# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
+poetry.toml
+
+# ruff
+.ruff_cache/
+
+# LSP config files
+pyrightconfig.json
+
+# End of https://www.toptal.com/developers/gitignore/api/python
+
+# Custom rules (everything added below won't be overriden by 'Generate .gitignore File' if you use 'Update' option)
+
+src/config.js
+
+**/.env
+
+**/*.pem
diff --git a/CNAME b/CNAME
deleted file mode 100644
index 3f710da..0000000
--- a/CNAME
+++ /dev/null
@@ -1 +0,0 @@
-bongo.cat
\ No newline at end of file
diff --git a/CODEOWNERS b/CODEOWNERS
deleted file mode 100644
index 0826de3..0000000
--- a/CODEOWNERS
+++ /dev/null
@@ -1 +0,0 @@
-* @Externalizable
\ No newline at end of file
diff --git a/DRUM ADD ON.md b/DRUM ADD ON.md
new file mode 100644
index 0000000..fd9d53f
--- /dev/null
+++ b/DRUM ADD ON.md
@@ -0,0 +1,21 @@
+## New Drums in bongo cat
+This version added 4 drum sounds to the classic Bongo cat Sounds
+### How to use ?
+Drums on this Keys
+Â * X - Base drum
+Â * C - Snare drum
+Â * B - Closed Hi-Hat
+Â * N - Open Hi-hat
+### Why all this
+Standard Bongo cat had just some random and too loud percussion sounds, but some keys are still usable on the keyboard, so I had to fill it!
+### Why not all keys
+The last row of key are already used by other Bongo cat features like this:
+Â * Y as an alternative for Z conflict in DE and US layout.
+Â * V as lower Octave value for Bongo+
+Â * M as "Mau" sound in all versions Â
+### Midi Notes corresponding to Drums
+In case you use Midi files as source for your notes, here are note values of Midi channel 10 corresponding to Bongo cat:
+ * Base drum   C1  36  - X
+ * Snare drum   E1  40  - C
+Â * Closed Hi-Hat F#1 42 Â - B
+ * Open Hi-hat  A#1 46  - N
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
deleted file mode 100644
index 6e45aa2..0000000
--- a/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-MIT License
-
-Copyright (c) 2018 Eric Huber
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
diff --git a/README.md b/README.md
index dd96ebf..3fcbfa9 100644
--- a/README.md
+++ b/README.md
@@ -1,56 +1,44 @@
-# Bongo Cat
-
-
-
-Hit the bongos like Bongo Cat! https://bongo.cat
-
-## About the meme
-Bongo Cat is a meme created by [DitzyFlama](https://twitter.com/DitzyFlama) ([tweet](https://twitter.com/DitzyFlama/status/993487015499853824)) using [StrayRogue](https://twitter.com/StrayRogue)'s [drawing of a cat](https://twitter.com/StrayRogue/status/992994454058381312).
-
-## Song Examples
-Happy Birthday to You
-> 1 1 3 1 6 5\
-1 1 3 1 8 6
-
-Ode to Joy - Friedrich Schiller
-> 5 5 6 8 8 6 5 3 1 1 3 5 5 3 3\
-5 5 6 8 8 6 5 3 1 1 3 5 3 1 1\
-3 3 5 1 3 5 6 5 1 3 5 6 5 3 1 3 1
-
-In the End - Linkin Park
-> 3 0 0 6 5 5 5 5 6 3\
-0 0 6 5 5 5 5 6 3\
-C\
-Space\
-0 0 6 5 5 5 5 6 3\
-0 0 6 5 5 5 5 6 3\
-C
-
-Come as You Are - Nirvana
-> 1 1 2 3\
-6 3 6 3 3 2 1 8 1 1 8 1 2 3
-
-[Browse more songs](https://github.com/Externalizable/bongo.cat/issues?utf8=%E2%9C%93&q=is%3Aissue+label%3A%22Type%3A+Song+Submission%22+) or [submit your own](https://github.com/Externalizable/bongo.cat/issues/new)!
-
-## Featured in
-- [Tweet by StrayRogue](https://twitter.com/StrayRogue/status/1041115341290561536), the creator of the cat drawing (15th September 2018)
-- [Tweet by DitzyFlama](https://twitter.com/DitzyFlama/status/1041116096420470784), the original creator of the meme (15th September 2018)
-- [YouTubers React To Bongo Cat Meme Compilation](https://www.youtube.com/watch?v=9HuzuR48nd0) on YouTube, posted by [FBE](https://www.youtube.com/channel/UC0v-tlzsn0QZwJnkiaUSJVQ) (2nd October 2018)
-- [Bored Button](https://www.boredbutton.com/) website (Date unknown)
-
-## Permission
-StrayRogue gave written permission to Externalizable on the 15th September 2018 to use their drawing on the Bongo Cat website.
-
-## Built with
-- [lowLag.js](https://lowlag.alienbill.com/) - To play lag free sounds on a wide range of devices
-- [SoundManager 2](http://www.schillmania.com/projects/soundmanager2/) - Used by lowLag.js
-
-## Authors
-- [StrayRogue](https://twitter.com/StrayRogue) - Cat drawing
-- [DitzyFlama](https://twitter.com/DitzyFlama) - Original idea
-- [Externalizable](https://github.com/Externalizable) - Website & domain
-
-See also the list of [contributors](https://github.com/Externalizable/bongo.cat/contributors) who participated in this project.
-
-## License
-This project is licensed under the MIT License - see the [LICENSE](https://github.com/Externalizable/bongo.cat/blob/master/LICENSE) file for details
+## Add the bongo cat to your stream
+A nice little bongo cat themed drum machine.
+Be warned: This is a work in progress.
+### What does it do?
+Bongo Cat for Twitch lets the popular meme cat appear on your screen whenever someone in your Twitch chat issues a command.
+### How do i add it to my stream? (easy route, START HERE)
+ * Add a new browser source and point it to https://jvpeek.de/ext/bc/#yourchannel
+ * Set the size of the browser to 1920x1080 pixels.
+Done.
+
+### How do i install it locally? (advanced users)
+If you want to modify the sounds or the behavior you can also run it locally.
+
+#### With Docker (Docker Desktop works too)
+ * Start Http-Daemon with `docker compose up -d`. The Webserver will listen on Port 8080.
+ * Point your OBS browser source to the index.html file and add your channel name as a hash. Example:`http://localhost:8080/#jvpeek`
+
+
+ * Set the size of the browser to 1920x1080 pixels.
+
+#### With Webserver (Whitout Docker)
+ * Install a Webserver (Xampp, lamp, MAMP, Apache2, Nginx...)
+ * Configure Webroot to ./src
+ * Point your OBS browser source to the index.html file and add your channel name as a hash. Example:`http://localhost/#jvpeek`
+
+
+ * Set the size of the browser to 1920x1080 pixels.
+
+### How do i use it?
+There are two commands:
+ * `!bpm`
+Lets you choose the speed of the song.
+ * `!bongo [notes]`
+Plays back the sounds in chunks.
+Notes are divided into chunks by spaces. `12 34` would result in two chunks.
+The first one containing notes `1` and `2`, the second one containing `3` and `4`.
+### What instruments are available?
+As of now, there are the same instruments as on https://bongo.cat.
+### Can i modify this?
+Yes. And please show us the results.
+### Who did this?
+The code was written by [CodeHustle](https://twitch.tv/codehustle) and [JvPeek](https://twitch.tv/jvpeek)
+The initial compilation of sounds and images was done by [Externalizable](https://github.com/Externalizable)
+Bongo cat was drawn by [StrayRogue](https://twitter.com/StrayRogue)
diff --git a/UPDATE.md b/UPDATE.md
new file mode 100644
index 0000000..1dacf56
--- /dev/null
+++ b/UPDATE.md
@@ -0,0 +1,10 @@
+## Updates
+
+### 5. Jun 2023
+-Mark old bongo notation as deprecated legacy. bongo+ is now default.
+-Z key is read as Y key
+
+### 26. Apr 2023
+ -Add Drums to Bongocat
+
+
diff --git a/discord_bot/.example.env b/discord_bot/.example.env
new file mode 100644
index 0000000..03599dd
--- /dev/null
+++ b/discord_bot/.example.env
@@ -0,0 +1,5 @@
+DISCORD_BOT_TOKEN=your-token-here
+DISCORD_ALLOWED_ROLES="123,456"
+GITHUB_APP_ID=000000
+GITHUB_APP_KEY_FILE=your-path-to-secret-key-file-here
+GITHUB_APP_INSTALLATION_ID=111111
\ No newline at end of file
diff --git a/discord_bot/bot.py b/discord_bot/bot.py
new file mode 100644
index 0000000..21b20fa
--- /dev/null
+++ b/discord_bot/bot.py
@@ -0,0 +1,286 @@
+import os
+from os.path import isfile
+import sys
+import json
+import re
+from typing import Optional, List
+from dataclasses import dataclass
+
+import discord
+from discord import CategoryChannel, ForumChannel, Interaction, Member, Message, TextStyle, app_commands, ui
+from discord.utils import MISSING
+from dotenv import load_dotenv
+
+from github import Github, Auth
+from github.Repository import Repository
+
+intents = discord.Intents.default()
+intents.message_content = True
+client = discord.Client(intents=intents)
+tree = app_commands.CommandTree(client)
+github: Github
+repo: Repository
+allowed_roles: List[int]|None = None
+
+state_file_path = "./state.json"
+state = {}
+
+
+class SaveSongModal(ui.Modal, title="Save Song"):
+
+ song_title = ui.TextInput(label="Song title")
+ file_name = ui.TextInput(label="Command name")
+ author = ui.TextInput(label="Song author")
+ #notation = ui.Select(options=[SelectOption(label="Modern (bongo+)", value="bongo+"), SelectOption(label="legacy", value="bongo+")])
+ notation = ui.TextInput(label="Song notation")
+ notes = ui.TextInput(label="Song notes", style=TextStyle.paragraph)
+ extras = {}
+
+ def __init__(self, *, title: str = MISSING, timeout: Optional[float] = None, custom_id: str = MISSING, default_song_title: str = "", default_file_name: str = "", default_author: str = "", default_notation="bongo+", default_notes: str = "", **kwargs) -> None:
+ self.song_title.default = default_song_title
+ self.file_name.default = default_file_name
+ self.author.default = default_author
+ self.notation.default = default_notation
+ #for option in self.notation.options:
+ # if option.value == default_notation:
+ # option.default = True
+ self.notes.default = default_notes
+
+ for key, value in kwargs.items():
+ self.extras[key] = value
+
+ super().__init__(title=title, timeout=timeout, custom_id=custom_id)
+
+
+
+ async def on_submit(self, interaction: Interaction) -> None:
+ global state
+ #message = interaction.extras["message"]
+ print(self.song_title.value)
+ print(self.file_name.value)
+ print(self.author.value)
+ print(self.notation.value)
+ print(self.notes.value)
+ print(interaction.extras)
+ print(self.extras)
+ print(interaction.message)
+ file_name = self.file_name.value.strip().replace(".json", "").replace(".", "")
+ file_name = re.sub(r"\s+", "_", file_name).replace("/", "").replace("\\", "").lower()
+ file_name += ".json"
+ notation = self.notation.value
+ if notation == "modern" or notation == "default" or notation == "bongo":
+ notation = "bongo+"
+ if notation == "legacy" or notation == "old":
+ notation = "bongol"
+ if notation == "experimental" or notation == "experimentell":
+ notation = "bongox"
+ song = {"notes": self.notes.value.strip(), "author": self.author.value.strip(), "title": self.song_title.value.strip(), "notation": notation.strip()}
+ await interaction.response.send_message("Saving Song", ephemeral=True)
+ content = json.dumps(song, indent=4).strip()
+ content += "\n"
+
+ repo.create_file(f"songs/{file_name}", f"Add Song {song['title']}", content)
+
+ message : Message = self.extras["message"]
+ await message.add_reaction("💾")
+ await interaction.edit_original_response(content="Saved Song")
+ state["msgs"].add(message.id)
+ write_state()
+
+@dataclass
+class Song():
+ title: str
+ file_name: str
+ author: str
+ notation: str
+ notes: str
+
+
+def parse_song(message: discord.Message):
+
+ if not "!bongo" in message.content:
+ return None
+
+ note_begin = message.content.find("!bongo")
+ title = message.content[:note_begin].strip().replace("\n", "\\n")
+ note_begin = message.content.find(" ", note_begin+1)
+ notes = message.content[note_begin:].strip()
+
+ notation = "bongo+"
+ if "!bongol" in message.content:
+ notation = "bongol"
+ elif "!bongo+" in message.content:
+ notation = "bongo+"
+ else:
+ #find actual notation
+ notation = "bongol"
+ if "#" in notes:
+ notation = "bongo+"
+ if "^" in notes:
+ notation = "bongo+"
+ if "v" in notes:
+ notation = "bongo+"
+
+ return Song(title, title.replace(" ", "_").lower(), message.author.name, notation, notes)
+
+
+
+parsing_songs = False
+
+#@tree.command(
+# name="get_old_songs",
+# description="Parses the entire channel for old songs",
+#)
+async def get_old_songs(interaction : Interaction):
+ global parsing_songs
+ global state
+ channel = interaction.channel
+ if channel == None:
+ await interaction.response.send_message("No channel found for this command!", ephemeral=True)
+ return
+ if parsing_songs:
+ await interaction.response.send_message("Already parsing songs!", ephemeral=True)
+ return
+
+ if isinstance(channel, ForumChannel) or isinstance(channel,CategoryChannel):
+ await interaction.response.send_message("Wrong Channel!", ephemeral=True)
+ return
+
+ parsing_songs = True
+ try:
+ await interaction.response.defer(ephemeral=True, thinking=True)
+ after = state["last_time"]
+ msgs = channel.history(limit=None,after=after, oldest_first=True)
+ last_time = None
+ msg_count = 0
+ songs = []
+ state_msgs = state["msgs"]
+ async for msg in msgs:
+ if msg.id in state_msgs:
+ continue
+ state_msgs.append(msg.id)
+ msg_count += 1
+ song = parse_song(msg)
+ if song:
+ songs.append(song)
+ last_time = msg.created_at
+ if msg.edited_at:
+ last_time = msg.edited_at
+
+ state["last_time"] = last_time
+ await interaction.followup.send(f"Parsed {msg_count} messages found {len(songs)} songs!", ephemeral=True)
+ finally:
+ parsing_songs = False
+ write_state()
+
+
+@tree.context_menu(
+ name="save_song"
+)
+@app_commands.describe(message="The Message with the song to save")
+async def save_song(interaction: Interaction, message: Message):
+ global state
+ if message.id in state["msgs"]:
+ await interaction.response.send_message("Already parsed this song!", ephemeral=True)
+ return
+ if allowed_roles:
+ if not isinstance(interaction.user, Member):
+ await interaction.response.send_message("You do not have permission to save songs!", ephemeral=True)
+ return
+ allowed = False
+ for role in interaction.user.roles:
+ if role.id in allowed_roles:
+ allowed = True
+ break
+ if not allowed:
+ await interaction.response.send_message("You do not have permission to save songs!", ephemeral=True)
+ return
+
+ interaction.extras["message"] = message
+ song = parse_song(message)
+ if not song:
+ await interaction.response.send_message("No song detected!", ephemeral=True)
+ return
+ print(song)
+ print(message)
+ interaction.extras["song"] = song
+ interaction.extras["modal"] = SaveSongModal(default_song_title=song.title, default_file_name=song.file_name, default_author=song.author, default_notation=song.notation, default_notes=song.notes, message=message)
+ await interaction.response.send_modal(interaction.extras["modal"])
+
+
+@client.event
+async def on_ready():
+ print(f'We have logged in as {client.user}')
+ print("Syncing commands")
+ await tree.sync()
+ print("Commands synced")
+
+@client.event
+async def on_message(message):
+ if message.author == client.user:
+ return
+
+ if message.content.startswith('$hello'):
+ await message.channel.send('Hello!')
+
+
+def write_state():
+ global state
+ global state_file_path
+ with open(state_file_path, "w") as state_file:
+ state_data = {}
+ state_data["last_time"] = state["last_time"]
+ state_data["msgs"] = list(state["msgs"])
+ json.dump(state_data, state_file)
+
+def read_state():
+ global state
+ global state_file_path
+ if not os.path.isfile(state_file_path):
+ state = {"last_time": None, "msgs": set()}
+ return
+ with open(state_file_path, "r") as state_file:
+ state_data = json.load(state_file)
+ state["last_time"] = state_data["last_time"]
+ state["msgs"] = set(state_data)
+
+if __name__ == "__main__":
+ load_dotenv()
+ read_state()
+ token = os.environ.get("DISCORD_BOT_TOKEN")
+ if not token:
+ print("No discord token provided!")
+ sys.exit(1)
+ allowed_roles_str = os.environ.get("DISCORD_ALLOWED_ROLES")
+
+ if allowed_roles_str:
+ allowed_roles = [int(role) for role in allowed_roles_str.split(",")]
+
+ app_id = os.environ.get("GITHUB_APP_ID")
+ private_key_path = os.environ.get("GITHUB_APP_KEY_FILE")
+ installation_id = os.environ.get("GITHUB_APP_INSTALLATION_ID")
+ if not app_id:
+ print("No github app id provided!")
+ sys.exit(1)
+ if not private_key_path:
+ print("No github key provided!")
+ sys.exit(1)
+ if not os.path.isfile(private_key_path):
+ print("No github key provided!")
+ sys.exit(1)
+ private_key = ""
+ with open(private_key_path) as private_key_file:
+ private_key = private_key_file.read()
+ if not private_key:
+ print("No github key provided!")
+ sys.exit(1)
+ if not installation_id:
+ print("No github installation id provided!")
+ sys.exit(1)
+
+ installation_id = int(installation_id)
+ auth = Auth.AppAuth(app_id, private_key).get_installation_auth(installation_id)
+ github = Github(auth=auth)
+ repo = github.get_repo("awsdcrafting/bongocat-songs")
+
+ client.run(token)
diff --git a/discord_bot/requirements.txt b/discord_bot/requirements.txt
new file mode 100644
index 0000000..f890c4d
--- /dev/null
+++ b/discord_bot/requirements.txt
@@ -0,0 +1,24 @@
+aiohttp==3.9.1
+aiosignal==1.3.1
+attrs==23.2.0
+certifi==2023.11.17
+cffi==1.16.0
+charset-normalizer==3.3.2
+cryptography==41.0.7
+Deprecated==1.2.14
+discord.py==2.3.2
+frozenlist==1.4.1
+idna==3.6
+multidict==6.0.4
+pycparser==2.21
+PyGithub==2.1.1
+PyJWT==2.8.0
+PyNaCl==1.5.0
+python-dateutil==2.8.2
+python-dotenv==1.0.0
+requests==2.31.0
+six==1.16.0
+typing_extensions==4.9.0
+urllib3==2.1.0
+wrapt==1.16.0
+yarl==1.9.4
\ No newline at end of file
diff --git a/discord_bot/todos.txt b/discord_bot/todos.txt
new file mode 100644
index 0000000..84eb7cc
--- /dev/null
+++ b/discord_bot/todos.txt
@@ -0,0 +1,3 @@
+MAYBE:
+TWITCH anbindung
+
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..5ad144b
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,9 @@
+version: "3.2"
+services:
+ httpd:
+ image: httpd
+ ports:
+ - 8080:80
+ restart: unless-stopped
+ volumes:
+ - ./src:/usr/local/apache2/htdocs/
diff --git a/docs/fsm.png b/docs/fsm.png
new file mode 100644
index 0000000..734c6bc
Binary files /dev/null and b/docs/fsm.png differ
diff --git a/docs/keylayout.png b/docs/keylayout.png
new file mode 100644
index 0000000..81a1d0a
Binary files /dev/null and b/docs/keylayout.png differ
diff --git a/docs/keylayout.svg b/docs/keylayout.svg
new file mode 100644
index 0000000..77ef601
--- /dev/null
+++ b/docs/keylayout.svg
@@ -0,0 +1,282 @@
+
+
+
+
+
+
+
+
+
+ image/svg+xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/images/cat.png b/images/cat.png
deleted file mode 100644
index 0ee1cff..0000000
Binary files a/images/cat.png and /dev/null differ
diff --git a/images/paw-left.png b/images/paw-left.png
deleted file mode 100644
index 7add845..0000000
Binary files a/images/paw-left.png and /dev/null differ
diff --git a/images/paw-right.png b/images/paw-right.png
deleted file mode 100644
index 33f3b90..0000000
Binary files a/images/paw-right.png and /dev/null differ
diff --git a/index.html b/index.html
deleted file mode 100644
index 6ca5320..0000000
--- a/index.html
+++ /dev/null
@@ -1,171 +0,0 @@
-
-
-
-
- Bongo Cat
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Bongos
- Keyboard
- Meow
- Cymbal
- Marimba
- Tambourine
- Cowbell
-
-
-
-
LEFT
-
RIGHT
-
MEOW
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/js/core.js b/js/core.js
deleted file mode 100644
index fe41f61..0000000
--- a/js/core.js
+++ /dev/null
@@ -1,281 +0,0 @@
-Array.prototype.remove = function(el) {
- return this.splice(this.indexOf(el), 1);
-}
-const InstrumentEnum = Object.freeze({
- BONGO: 0,
- KEYBOARD: 1,
- MEOW: 3,
- CYMBAL: 4,
- MARIMBA: 5,
- TAMBOURINE: 6,
- COWBELL: 7
-})
-const KeyEnum = Object.freeze({
- "A": 1,
- "D": 0,
- "1": 1,
- "2": 2,
- "3": 3,
- "4": 4,
- "5": 5,
- "6": 6,
- "7": 7,
- "8": 8,
- "9": 9,
- "0": 0,
- " ": 0,
- "C": 1,
- "Q": 1,
- "W": 2,
- "E": 3,
- "R": 4,
- "T": 5,
- "Y": 6, // QWERTY-Layout
- "Z": 6, // QWERTZ-Layout
- "U": 7,
- "I": 8,
- "O": 9,
- "P": 0,
- "B": 1,
- "F": 1
-})
-const InstrumentPerKeyEnum = Object.freeze({
- "A": InstrumentEnum.BONGO,
- "D": InstrumentEnum.BONGO,
- "1": InstrumentEnum.KEYBOARD,
- "2": InstrumentEnum.KEYBOARD,
- "3": InstrumentEnum.KEYBOARD,
- "4": InstrumentEnum.KEYBOARD,
- "5": InstrumentEnum.KEYBOARD,
- "6": InstrumentEnum.KEYBOARD,
- "7": InstrumentEnum.KEYBOARD,
- "8": InstrumentEnum.KEYBOARD,
- "9": InstrumentEnum.KEYBOARD,
- "0": InstrumentEnum.KEYBOARD,
- " ": InstrumentEnum.MEOW,
- "C": InstrumentEnum.CYMBAL,
- "Q": InstrumentEnum.MARIMBA,
- "W": InstrumentEnum.MARIMBA,
- "E": InstrumentEnum.MARIMBA,
- "R": InstrumentEnum.MARIMBA,
- "T": InstrumentEnum.MARIMBA,
- "Y": InstrumentEnum.MARIMBA, // QWERTY-Layout
- "Z": InstrumentEnum.MARIMBA, // QWERTZ-Layout
- "U": InstrumentEnum.MARIMBA,
- "I": InstrumentEnum.MARIMBA,
- "O": InstrumentEnum.MARIMBA,
- "P": InstrumentEnum.MARIMBA,
- "B": InstrumentEnum.TAMBOURINE,
- "F": InstrumentEnum.COWBELL
-})
-const ClickKeyEquivalentEnum = Object.freeze({
- "1": "A",
- "2": " ",
- "3": "D"
-})
-const TapKeyEquivalentEnum = Object.freeze({
- "tap-left": {
- "BONGO": ["A"]
- },
- "tap-right": {
- "BONGO": ["D"],
- "CYMBAL": ["C"],
- "TAMBOURINE": ["B"],
- "COWBELL": ["F"]
- },
- "tap-space": {
- "MEOW": [" "]
- },
- "tap-1": {
- "KEYBOARD": ["1"],
- "MARIMBA": ["Q"]
- },
- "tap-2": {
- "KEYBOARD": ["2"],
- "MARIMBA": ["W"]
- },
- "tap-3": {
- "KEYBOARD": ["3"],
- "MARIMBA": ["E"]
- },
- "tap-4": {
- "KEYBOARD": ["4"],
- "MARIMBA": ["R"]
- },
- "tap-5": {
- "KEYBOARD": ["5"],
- "MARIMBA": ["T"]
- },
- "tap-6": {
- "KEYBOARD": ["6"],
- "MARIMBA": ["Y", "Z"]
- },
- "tap-7": {
- "KEYBOARD": ["7"],
- "MARIMBA": ["U"]
- },
- "tap-8": {
- "KEYBOARD": ["8"],
- "MARIMBA": ["I"]
- },
- "tap-9": {
- "KEYBOARD": ["9"],
- "MARIMBA": ["O"]
- },
- "tap-0": {
- "KEYBOARD": ["0"],
- "MARIMBA": ["P"]
- }
-})
-const TapKeysPerLayerEnum = Object.freeze({
- "layer-bongo": ["tap-left", "tap-right"],
- "layer-keyboard": ["tap-keys"],
- "layer-meow": ["tap-space"],
- "layer-cymbal": ["tap-right"],
- "layer-marimba": ["tap-keys"],
- "layer-tambourine": ["tap-right"],
- "layer-cowbell": ["tap-right"]
-})
-const LayersPerInstrumentEnum = Object.freeze({
- "layer-bongo": InstrumentEnum.BONGO,
- "layer-keyboard": InstrumentEnum.KEYBOARD,
- "layer-meow": InstrumentEnum.MEOW,
- "layer-cymbal": InstrumentEnum.CYMBAL,
- "layer-marimba": InstrumentEnum.MARIMBA,
- "layer-tambourine": InstrumentEnum.TAMBOURINE,
- "layer-cowbell": InstrumentEnum.COWBELL
-})
-var pressed = [];
-var currentLayer;
-var allLayers = [];
-for (var tapKeysPerInstrument of Object.values(TapKeysPerLayerEnum)) {
- allLayers.push(...tapKeysPerInstrument);
-}
-allLayers = [...new Set(allLayers)];
-$(document).ready(function() {
- lowLag.init({
- 'urlPrefix': '../sounds/',
- 'debug': 'none'
- });
- $.load("bongo", 0, 1);
- $.load("keyboard", 0, 9);
- $.load("marimba", 0, 9);
- $.loadSimple("meow");
- $.loadSimple("cymbal");
- $.loadSimple("tambourine");
- $.loadSimple("cowbell");
- $.layers("layer-bongo");
- $("select#select-instrument").on("change", function() {
- $.layers($(this).val());
- });
-});
-$.loadSimple = function(file) {
- for (i = 0; i <= 1; i++) {
- lowLag.load([file + ".mp3", file + ".wav"], file + i);
- }
-}
-$.load = function(file, start, end) {
- for (i = start; i <= end; i++) {
- lowLag.load([file + i + ".mp3", file + i + ".wav"], file + i);
- }
-}
-$.wait = function(callback, ms) {
- return window.setTimeout(callback, ms);
-}
-$.play = function(instrument, key, state) {
- var instrumentName = Object.keys(InstrumentEnum).find(k => InstrumentEnum[k] === instrument).toLowerCase();
- var commonKey = KeyEnum[key];
- var id = "#" + (instrument == InstrumentEnum.MEOW ? "mouth" : "paw-" + ((instrument == InstrumentEnum.BONGO ? commonKey : commonKey <= 5 && commonKey != 0 ? 0 : 1) == 0 ? "left" : "right"));
- if (state == true) {
- if (jQuery.inArray(commonKey, pressed) !== -1) {
- return;
- }
- pressed.push(commonKey);
- if (instrument != InstrumentEnum.MEOW) {
- $(".instruments>div").each(function(index) {
- $(this).css("visibility", ($(this).attr("id") === instrumentName) ? "visible" : "hidden");
- });
- }
- lowLag.play(instrumentName + commonKey);
- $.layers(Object.keys(LayersPerInstrumentEnum).find(k => LayersPerInstrumentEnum[k] == instrument), true);
- } else {
- pressed.remove(commonKey);
- }
- $(id).css("background-position-x", (state ? "-800px" : "0"));
-}
-$.layers = function(selectedLayer) {
- if (selectedLayer !== currentLayer) {
- currentLayer = selectedLayer;
- var layers = TapKeysPerLayerEnum[selectedLayer];
- if (layers != undefined) {
- for (var layer of allLayers) {
- $("#" + layer).css("display", layers.includes(layer) ? "inline-block" : "none");
- }
- var instrument = LayersPerInstrumentEnum[selectedLayer];
- var instrumentName = Object.keys(InstrumentEnum).find(k => InstrumentEnum[k] === instrument).toLowerCase();
- if (instrument != InstrumentEnum.MEOW) {
- $(".instruments>div").each(function(index) {
- $(this).css("visibility", ($(this).attr("id") === instrumentName) ? "visible" : "hidden");
- });
- }
- }
- var layerPerKey = Object.keys(LayersPerInstrumentEnum);
- for (var i = 0; i < layerPerKey.length; i++) {
- if (layerPerKey[i] === selectedLayer) {
- $("select#select-instrument>option:eq(" + i + ")").prop("selected", true);
- }
- }
- }
-}
-$(document).bind("contextmenu", function(e) {
- e.preventDefault();
-});
-$(document).on("mousedown mouseup", function(e) {
- if (!window.matchMedia("(max-width: 769px)").matches && !$(e.target).is("a, a *")) {
- var keyboardEquivalent = ClickKeyEquivalentEnum[e.which];
- if (keyboardEquivalent != undefined) {
- var instrument = InstrumentPerKeyEnum[keyboardEquivalent.toUpperCase()];
- var key = KeyEnum[keyboardEquivalent.toUpperCase()];
- if (instrument != undefined && key != undefined) {
- $.play(instrument, key, e.type === "mousedown");
- }
- }
- }
-});
-$(document).on("keydown keyup", function(e) {
- var instrument = InstrumentPerKeyEnum[e.key.toUpperCase()];
- var key = KeyEnum[e.key.toUpperCase()];
- if (instrument != undefined && key != undefined) {
- $.play(instrument, key, e.type === "keydown");
- }
-});
-$(document).on("touchstart touchend", function(e) {
- if (e.target.classList.contains("layer")) {
- if (e.type === "touchstart") {
- $(e.target).addClass("highlight");
- $.layers(e.target.id, true);
- } else {
- $(e.target).removeClass("highlight");
- }
- } else {
- var instrument = LayersPerInstrumentEnum[currentLayer];
- if (instrument != undefined) {
- var keys = TapKeyEquivalentEnum[e.target.id];
- if (keys != undefined) {
- var instrumentName = Object.keys(InstrumentEnum).find(k => InstrumentEnum[k] === instrument);
- if (instrumentName != undefined && keys[instrumentName] != undefined) {
- for (var key of keys[instrumentName]) {
- if (key != undefined) {
- if (e.type === "touchstart") {
- $(e.target).addClass("highlight");
- } else {
- $(e.target).removeClass("highlight");
- }
- $.play(instrument, key, e.type === "touchstart");
- }
- }
- }
- }
- }
- }
-});
\ No newline at end of file
diff --git a/js/lowLag.js b/js/lowLag.js
deleted file mode 100644
index ac958ec..0000000
--- a/js/lowLag.js
+++ /dev/null
@@ -1,373 +0,0 @@
-if (!window.console) console = {log: function() {}};
-
-var lowLag = new function(){
- this.someVariable = undefined;
- this.showNeedInit = function(){ lowLag.msg("lowLag: you must call lowLag.init() first!"); }
-
- this.load = this.showNeedInit;
- this.play = this.showNeedInit;
-
- this.useSuspension = false;
- this.suspendDelay = 10000; // ten seconds
- this.suspendTimeout = null;
- this.suspended = false;
-
- this.audioTagTimeToLive = 5000;
-
- this.sm2url = 'sm2/swf/';
-
- this.soundUrl = "";
-
- this.debug = "console";
-
- this.divLowLag = null;
- this.divDebug = null;
-
- this.createElement = function(elemType,attribs){
- var elem = document.createElement(elemType);
- if(attribs){
- for(var key in attribs){
- elem.setAttribute(key,attribs[key]);
- }
- }
- return elem;
- };
- this.safelyRemoveElement = function(elem){
- if(elem) elem.parentNode.removeChild(elem);
- };
- this.safelyRemoveElementById = function(id){
- this.safelyRemoveElement(document.getElementById(id));
- };
-
- this.ready = function ready(fn) {
- if (document.readyState != 'loading'){
- fn();
- } else if (document.addEventListener) {
- document.addEventListener('DOMContentLoaded', fn);
- } else {
- document.attachEvent('onreadystatechange', function() {
- if (document.readyState != 'loading')
- fn();
- });
- }
- };
-
-
- this.init = function(config){
- //var divLowLag = document.getElementById("lowLag");
- this.safelyRemoveElement(this.divLowLag);
- this.divLowLag = this.createElement("div",{"id":"lowLag"});
- document.body.appendChild(this.divLowLag);
-
-
-
- var force = undefined;
- if(config != undefined){
- if(config['force'] != undefined){
- force = config['force'];
- }
- if(config['audioTagTimeToLive'] != undefined){
- lowLag.audioTagTimeToLive = config['audioTagTimeToLive'];
- }
- if(config['sm2url'] != undefined){
- lowLag.sm2url = config['sm2url'];
- }
- if(config['urlPrefix'] != undefined){
- lowLag.soundUrl = config['urlPrefix'];
- }
- if(config['debug'] != undefined){
- lowLag.debug = config['debug'];
- }
- if (config['useSuspension'] != undefined) {
- lowLag.useSuspension = config['useSuspension'];
- }
- if (config['suspendDelay'] != undefined) {
- lowLag.suspendDelay = config['suspendDelay'];
- }
- }
-
- if(lowLag.debug == "screen" || lowLag.debug == "both"){
- lowLag.divDebug = lowLag.createElement("pre");
- lowLag.divLowLag.appendChild(lowLag.divDebug);
-
- }
-
-
- var format = "sm2";
- if(force != undefined) format = force;
- else {
- if(window.AudioContext || window.webkitAudioContext ) format = 'audioContext';
- else if(navigator.userAgent.indexOf("Firefox")!=-1) format = 'audioTag';
- }
- switch(format){
- case 'audioContext':
-
- this.msg("init audioContext");
- this.load= this.loadSoundAudioContext;
- this.play = this.playSoundAudioContext;
- if(!this.audioContext)
- this.audioContext = new(window.AudioContext || window.webkitAudioContext)();
- if (this.useSuspension &= ('suspend' in lowLag.audioContext && 'onended' in lowLag.audioContext.createBufferSource())) {
- this.playingQueue = [];
- this.suspendPlaybackAudioContext();
- }
- break;
- case 'audioTag':
- this.msg("init audioTag");
- this.load= this.loadSoundAudioTag;
- this.play = this.playSoundAudioTag;
- break;
-
- case 'sm2':
- this.msg("init SoundManager2");
-
- this.load = this.loadSoundSM2;
- this.play = this.playSoundSM2;
- lowLag.msg("loading SM2 from "+lowLag.sm2url);
- soundManager.setup({ url: lowLag.sm2url, useHighPerformance:true,
- onready:lowLag.sm2Ready , debugMode: true})
-
-
- break;
-
- }
-
-
- }
- this.sm2IsReady = false;
-//sm2 has a callback that tells us when it's ready, so we may need to store
-//requests to loadsound, and then call sm2 once it has told us it is set.
- this.sm2ToLoad = [];
-
- this.loadSoundSM2 = function(url,tag){
- if(lowLag.sm2IsReady){
- lowLag.loadSoundSM2ForReals(url,tag);
- } else {
- lowLag.sm2ToLoad.push([url,tag]);
- }
- }
-
- this.loadSoundSM2ForReals = function(urls,ptag){
- var tag = lowLag.getTagFromURL(urls,ptag);
- lowLag.msg('sm2 loading '+urls+' as tag ' + tag);
- var urls = lowLag.getURLArray(urls); //coerce
- for(var i = 0; i < urls.length; i++){
- var url = lowLag.soundUrl + urls[i];
- urls[i] = url;
- }
-
- soundManager.createSound({
- id: tag,
- autoLoad: true,
- url: urls
- });
- };
-
- this.sm2Ready = function(){
- lowLag.sm2IsReady = true;
- for(var i = 0 ; i < lowLag.sm2ToLoad.length; i++){
- var urlAndTag = lowLag.sm2ToLoad[i];
- lowLag.loadSoundSM2ForReals(urlAndTag[0],urlAndTag[1]);
- }
- lowLag.sm2ToLoad = [];
- }
-
- this.playSoundSM2 = function(tag){
- lowLag.msg("playSoundSM2 "+tag);
-
- soundManager.play(tag);
- }
-
-
-
-//we'll use the tag they hand us, or else the url as the tag if it's a single tag,
-//or the first url
- this.getTagFromURL = function(url,tag){
- if(tag != undefined) return tag;
- return lowLag.getSingleURL(url);
- }
- this.getSingleURL = function(urls){
- if(typeof(urls) == "string") return urls;
- return urls[0];
- }
-//coerce to be an array
- this.getURLArray = function(urls){
- if(typeof(urls) == "string") return [urls];
- return urls;
- }
-
-
-
-
-this.audioContextPendingRequest = {};
-
-
- this.audioContext = undefined;
- this.audioBuffers = {};
-
- this.loadSoundAudioContext = function(urls,tag,urlPrefix){ //urlPrefix is only set when called recursively
- if(!urlPrefix)// save the urlPrefix because lowLag.soundUrl my get overwritten if 1) chrome's async AudioContext is called and 2) the user tries to load more than one sound
- urlPrefix=lowLag.soundUrl;
- var url = lowLag.getSingleURL(urls);
- var tag = lowLag.getTagFromURL (urls,tag);
- lowLag.msg('webkit/chrome audio loading '+ urlPrefix +url+' as tag ' + tag);
- var request = new XMLHttpRequest();
- request.open('GET', urlPrefix + url, true);
- request.responseType = 'arraybuffer';
-
- // Decode asynchronously
- request.onload = function() {
- var retVal = lowLag.audioContext.decodeAudioData(request.response, successLoadAudioFile, errorLoadAudioFile);
- //Newer versions of audioContext return a promise, which could throw a DOMException
- if(retVal && typeof retVal.then == 'function'){
- retVal.then(successLoadAudioFile).catch(function(e) {
- errorLoadAudioFile(e);
- urls.shift(); //remove the first url from the array
- if(urls.length>0){
- lowLag.loadSoundAudioContext(urls,tag,urlPrefix); //try the next url
- }
- });
- }
- };
- request.send();
-
- function successLoadAudioFile (buffer) {
- lowLag.audioBuffers[tag] = buffer;
- if(lowLag.audioContextPendingRequest[tag]){ //a request might have come in, try playing it now
- lowLag.playSoundAudioContext(tag);
- }
- }
-
- function errorLoadAudioFile (e){
- lowLag.msg("Error loading webkit/chrome audio: "+e);
- }
- }
-
- this.playSoundAudioContext= function(tag){
- lowLag.msg("playSoundAudioContext "+tag);
- var buffer = lowLag.audioBuffers[tag];
- if(buffer == undefined) { //possibly not loaded; put in a request to play onload
- lowLag.audioContextPendingRequest[tag] = true;
- return;
- }
- var context = lowLag.audioContext;
- if (this.useSuspension && this.suspended) {
- this.resumePlaybackAudioContext(); // Resume playback
- }
- var source = context.createBufferSource(); // creates a sound source
- source.buffer = buffer; // tell the source which sound to play
- source.connect(context.destination); // connect the source to the context's destination (the speakers)
- if (typeof(source.noteOn) == "function") {
- source.noteOn(0); // play the source now, using noteOn
- } else {
- if (this.useSuspension) {
- this.playingQueue.push(tag);
- source.onended = function(e) {
- lowLag.hndlOnEndedAudioContext(tag, e);
- }
- }
- source.start(); // play the source now, using start
- }
- }
-
- this.hndlOnEndedAudioContext = function(tag, e){
- for (var i = 0; i < this.playingQueue.length; i++ ) {
- if (this.playingQueue[i] == tag) {
- this.playingQueue.splice(i,1);
- break;
- }
- }
- if (!this.playingQueue.length) {
- this.suspendPlaybackAudioContext();
- }
- }
-
- this.resumePlaybackAudioContext = function(){
- this.audioContext.resume();
- this.suspended = false;
- }
-
- this.suspendPlaybackAudioContext = function(){
- if (this.suspendTimeout) {
- clearTimeout(this.suspendTimeout);
- }
- this.suspendTimeout = setTimeout(function(){
- lowLag.audioContext.suspend();
- lowLag.suspended = true;
- lowLag.suspendTimeout = null;
- }, this.suspendDelay);
- }
-
-
-
-
-
-
-
- this.audioTagID = 0;
- this.audioTagNameToElement = {};
-
- this.loadSoundAudioTag = function(urls,tag){
- var id = "lowLagElem_"+lowLag.audioTagID++;
-
- var tag = lowLag.getTagFromURL(urls,tag);
-
- var urls = lowLag.getURLArray(urls);
-
-
- lowLag.audioTagNameToElement[tag] = id;
-
- lowLag.msg('audioTag loading '+urls+' as tag ' + tag);
- var audioElem = this.createElement("audio",{"id":id, "preload":"auto", "autobuffer":"autobuffer"})
-
- for(var i = 0; i < urls.length; i++){
- var url = urls[i];
- var type = "audio/"+lowLag.getExtension(url);
- var sourceElem = this.createElement("source",{"src":lowLag.soundUrl+url,"type":type});
- audioElem.appendChild(sourceElem);
- }
-
- document.body.appendChild(audioElem);
- }
-
- this.playSoundAudioTag = function(tag){
- lowLag.msg("playSoundAudioTag "+tag);
-
- var modelId = lowLag.audioTagNameToElement[tag];
- var cloneId = "lowLagCloneElem_"+lowLag.audioTagID++;
-
- var modelElem = document.getElementById(modelId);
- var cloneElem = modelElem.cloneNode(true);
- cloneElem.setAttribute("id",cloneId);
- this.divLowLag.appendChild(cloneElem);
- lowLag.msg(tag);
- if(lowLag.audioTagTimeToLive != -1){
- setTimeout(function(){
- lowLag.safelyRemoveElement(cloneElem);
- },lowLag.audioTagTimeToLive);
- }
- cloneElem.play();
-
- }
-
-
- this.getExtension = function(url){
- return url.substring(url.lastIndexOf(".")+1).toLowerCase();
-
- }
-
-
- this.msg = function(m){
- m = "-- lowLag "+m;
- if(lowLag.debug == 'both' || lowLag.debug == 'console'){
- console.log(m);
- }
- if(lowLag.divDebug){
- lowLag.divDebug.innerHTML += m+"\n";
- }
- }
-
-
-
-
-}
\ No newline at end of file
diff --git a/js/sm2/js/soundmanager2-nodebug-jsmin.js b/js/sm2/js/soundmanager2-nodebug-jsmin.js
deleted file mode 100644
index 6071ed4..0000000
--- a/js/sm2/js/soundmanager2-nodebug-jsmin.js
+++ /dev/null
@@ -1,80 +0,0 @@
-/** @license
- *
- * SoundManager 2: JavaScript Sound for the Web
- * ----------------------------------------------
- * http://schillmania.com/projects/soundmanager2/
- *
- * Copyright (c) 2007, Scott Schiller. All rights reserved.
- * Code provided under the BSD License:
- * http://schillmania.com/projects/soundmanager2/license.txt
- *
- * V2.97a.20120624
- */
-(function(ea){function Q(Q,da){function R(a){return c.preferFlash&&t&&!c.ignoreFlash&&"undefined"!==typeof c.flash[a]&&c.flash[a]}function m(a){return function(c){var d=this._t;return!d||!d._a?null:a.call(this,c)}}this.setupOptions={url:Q||null,flashVersion:8,debugMode:!0,debugFlash:!1,useConsole:!0,consoleOnly:!0,waitForWindowLoad:!1,bgColor:"#ffffff",useHighPerformance:!1,flashPollingInterval:null,html5PollingInterval:null,flashLoadTimeout:1E3,wmode:null,allowScriptAccess:"always",useFlashBlock:!1,
-useHTML5Audio:!0,html5Test:/^(probably|maybe)$/i,preferFlash:!0,noSWFCache:!1};this.defaultOptions={autoLoad:!1,autoPlay:!1,from:null,loops:1,onid3:null,onload:null,whileloading:null,onplay:null,onpause:null,onresume:null,whileplaying:null,onposition:null,onstop:null,onfailure:null,onfinish:null,multiShot:!0,multiShotEvents:!1,position:null,pan:0,stream:!0,to:null,type:null,usePolicyFile:!1,volume:100};this.flash9Options={isMovieStar:null,usePeakData:!1,useWaveformData:!1,useEQData:!1,onbufferchange:null,
-ondataerror:null};this.movieStarOptions={bufferTime:3,serverURL:null,onconnect:null,duration:null};this.audioFormats={mp3:{type:['audio/mpeg; codecs="mp3"',"audio/mpeg","audio/mp3","audio/MPA","audio/mpa-robust"],required:!0},mp4:{related:["aac","m4a"],type:['audio/mp4; codecs="mp4a.40.2"',"audio/aac","audio/x-m4a","audio/MP4A-LATM","audio/mpeg4-generic"],required:!1},ogg:{type:["audio/ogg; codecs=vorbis"],required:!1},wav:{type:['audio/wav; codecs="1"',"audio/wav","audio/wave","audio/x-wav"],required:!1}};
-this.movieID="sm2-container";this.id=da||"sm2movie";this.debugID="soundmanager-debug";this.debugURLParam=/([#?&])debug=1/i;this.versionNumber="V2.97a.20120624";this.altURL=this.movieURL=this.version=null;this.enabled=this.swfLoaded=!1;this.oMC=null;this.sounds={};this.soundIDs=[];this.didFlashBlock=this.muted=!1;this.filePattern=null;this.filePatterns={flash8:/\.mp3(\?.*)?$/i,flash9:/\.mp3(\?.*)?$/i};this.features={buffering:!1,peakData:!1,waveformData:!1,eqData:!1,movieStar:!1};this.sandbox={};var fa;
-try{fa="undefined"!==typeof Audio&&"undefined"!==typeof(new Audio).canPlayType}catch(Za){fa=!1}this.hasHTML5=fa;this.html5={usingFlash:null};this.flash={};this.ignoreFlash=this.html5Only=!1;var Ca,c=this,i=null,S,q=navigator.userAgent,h=ea,ga=h.location.href.toString(),l=document,ha,Da,ia,j,w=[],J=!1,K=!1,k=!1,s=!1,ja=!1,L,r,ka,T,la,B,C,D,Ea,ma,U,V,E,na,oa,pa,W,F,Fa,qa,Ga,X,Ha,M=null,ra=null,u,sa,G,Y,Z,H,p,N=!1,ta=!1,Ia,Ja,Ka,$=0,O=null,aa,n=null,La,ba,P,x,ua,va,Ma,o,Wa=Array.prototype.slice,z=!1,
-t,wa,Na,v,Oa,xa=q.match(/(ipad|iphone|ipod)/i),y=q.match(/msie/i),Xa=q.match(/webkit/i),ya=q.match(/safari/i)&&!q.match(/chrome/i),Pa=q.match(/opera/i),za=q.match(/(mobile|pre\/|xoom)/i)||xa,Qa=!ga.match(/usehtml5audio/i)&&!ga.match(/sm2\-ignorebadua/i)&&ya&&!q.match(/silk/i)&&q.match(/OS X 10_6_([3-7])/i),Aa="undefined"!==typeof l.hasFocus?l.hasFocus():null,ca=ya&&("undefined"===typeof l.hasFocus||!l.hasFocus()),Ra=!ca,Sa=/(mp3|mp4|mpa|m4a)/i,Ba=l.location?l.location.protocol.match(/http/i):null,
-Ta=!Ba?"http://":"",Ua=/^\s*audio\/(?:x-)?(?:mpeg4|aac|flv|mov|mp4||m4v|m4a|mp4v|3gp|3g2)\s*(?:$|;)/i,Va="mpeg4,aac,flv,mov,mp4,m4v,f4v,m4a,mp4v,3gp,3g2".split(","),Ya=RegExp("\\.("+Va.join("|")+")(\\?.*)?$","i");this.mimePattern=/^\s*audio\/(?:x-)?(?:mp(?:eg|3))\s*(?:$|;)/i;this.useAltURL=!Ba;this._global_a=null;if(za&&(c.useHTML5Audio=!0,c.preferFlash=!1,xa))z=c.ignoreFlash=!0;this.setup=function(a){"undefined"!==typeof a&&k&&n&&c.ok()&&("undefined"!==typeof a.flashVersion||"undefined"!==typeof a.url)&&
-H(u("setupLate"));ka(a);return c};this.supported=this.ok=function(){return n?k&&!s:c.useHTML5Audio&&c.hasHTML5};this.getMovie=function(a){return S(a)||l[a]||h[a]};this.createSound=function(a,e){function d(){b=Y(b);c.sounds[f.id]=new Ca(f);c.soundIDs.push(f.id);return c.sounds[f.id]}var b=null,g=null,f=null;if(!k||!c.ok())return H(void 0),!1;"undefined"!==typeof e&&(a={id:a,url:e});b=r(a);b.url=aa(b.url);f=b;if(p(f.id,!0))return c.sounds[f.id];if(ba(f))g=d(),g._setup_html5(f);else{if(8=a)return!1;for(a-=1;0<=a;a--)if(c=k[a],!c.fired&&b.position>=c.position)c.fired=!0,o++,c.method.apply(c.scope,[c.position]);return!0};this._resetOnPosition=function(b){var a,c;a=k.length;if(!a)return!1;for(a-=1;0<=a;a--)if(c=k[a],c.fired&&b<=c.position)c.fired=!1,o--;return!0};s=function(){var a=b._iO,
-c=a.from,e=a.to,d,f;f=function(){b.clearOnPosition(e,f);b.stop()};d=function(){if(null!==e&&!isNaN(e))b.onPosition(e,f)};if(null!==c&&!isNaN(c))a.position=c,a.multiShot=!1,d();return a};l=function(){var a,c=b._iO.onposition;if(c)for(a in c)if(c.hasOwnProperty(a))b.onPosition(parseInt(a,10),c[a])};q=function(){var a,c=b._iO.onposition;if(c)for(a in c)c.hasOwnProperty(a)&&b.clearOnPosition(parseInt(a,10))};h=function(){b.isHTML5&&Ia(b)};I=function(){b.isHTML5&&Ja(b)};g=function(a){a||(k=[],o=0);m=!1;
-b._hasTimer=null;b._a=null;b._html5_canplay=!1;b.bytesLoaded=null;b.bytesTotal=null;b.duration=b._iO&&b._iO.duration?b._iO.duration:null;b.durationEstimate=null;b.buffered=[];b.eqData=[];b.eqData.left=[];b.eqData.right=[];b.failures=0;b.isBuffering=!1;b.instanceOptions={};b.instanceCount=0;b.loaded=!1;b.metadata={};b.readyState=0;b.muted=!1;b.paused=!1;b.peakData={left:0,right:0};b.waveformData={left:[],right:[]};b.playState=0;b.position=null;b.id3={}};g();this._onTimer=function(a){var c,f=!1,g={};
-if(b._hasTimer||a){if(b._a&&(a||(0f.duration?b.duration:f.duration:parseInt(b.bytesTotal/b.bytesLoaded*b.duration,10),"undefined"===
-typeof b.durationEstimate)b.durationEstimate=b.duration;if(!b.isHTML5)b.buffered=[{start:0,end:b.duration}];(3!==b.readyState||b.isHTML5)&&f.whileloading&&f.whileloading.apply(b)};this._whileplaying=function(a,c,e,d,f){var g=b._iO;if(isNaN(a)||null===a)return!1;b.position=Math.max(0,a);b._processOnPosition();if(!b.isHTML5&&8j)c.flashVersion=j=9;c.version=c.versionNumber+(c.html5Only?" (HTML5-only mode)":9===j?" (AS3/Flash 9)":" (AS2/Flash 8)");8 '}if(J&&K)return!1;if(c.html5Only)return ma(),c.oMC=S(c.movieID),ia(),K=J=!0,!1;var b=e||c.url,g=c.altURL||b,f;f=pa();var h,i,j=G(),k,m=null,m=(m=l.getElementsByTagName("html")[0])&&m.dir&&m.dir.match(/rtl/i),a="undefined"===typeof a?c.id:a;ma();c.url=Ha(Ba?b:g);e=c.url;c.wmode=
-!c.wmode&&c.useHighPerformance?"transparent":c.wmode;if(null!==c.wmode&&(q.match(/msie 8/i)||!y&&!c.useHighPerformance)&&navigator.platform.match(/win32|win64/i))c.wmode=null;f={name:a,id:a,src:e,quality:"high",allowScriptAccess:c.allowScriptAccess,bgcolor:c.bgColor,pluginspage:Ta+"www.macromedia.com/go/getflashplayer",title:"JS/Flash audio component (SoundManager 2)",type:"application/x-shockwave-flash",wmode:c.wmode,hasPriority:"true"};if(c.debugFlash)f.FlashVars="debug=1";c.wmode||delete f.wmode;
-if(y)b=l.createElement("div"),i=['',d("movie",e),d("AllowScriptAccess",c.allowScriptAccess),d("quality",f.quality),c.wmode?d("wmode",c.wmode):"",d("bgcolor",c.bgColor),d("hasPriority","true"),c.debugFlash?d("FlashVars",f.FlashVars):""," "].join("");else for(h in b=l.createElement("embed"),
-f)f.hasOwnProperty(h)&&b.setAttribute(h,f[h]);qa();j=G();if(f=pa())if(c.oMC=S(c.movieID)||l.createElement("div"),c.oMC.id){k=c.oMC.className;c.oMC.className=(k?k+" ":"movieContainer")+(j?" "+j:"");c.oMC.appendChild(b);if(y)h=c.oMC.appendChild(l.createElement("div")),h.className="sm2-object-box",h.innerHTML=i;K=!0}else{c.oMC.id=c.movieID;c.oMC.className="movieContainer "+j;h=j=null;if(!c.useFlashBlock)if(c.useHighPerformance)j={position:"fixed",width:"8px",height:"8px",bottom:"0px",left:"0px",overflow:"hidden"};
-else if(j={position:"absolute",width:"6px",height:"6px",top:"-9999px",left:"-9999px"},m)j.left=Math.abs(parseInt(j.left,10))+"px";if(Xa)c.oMC.style.zIndex=1E4;if(!c.debugFlash)for(k in j)j.hasOwnProperty(k)&&(c.oMC.style[k]=j[k]);try{y||c.oMC.appendChild(b);f.appendChild(c.oMC);if(y)h=c.oMC.appendChild(l.createElement("div")),h.className="sm2-object-box",h.innerHTML=i;K=!0}catch(n){throw Error(u("domError")+" \n"+n.toString());}}return J=!0};V=function(){if(c.html5Only)return W(),!1;if(i)return!1;
-i=c.getMovie(c.id);if(!i)M?(y?c.oMC.innerHTML=ra:c.oMC.appendChild(M),M=null,J=!0):W(c.id,c.url),i=c.getMovie(c.id);"function"===typeof c.oninitmovie&&setTimeout(c.oninitmovie,1);return!0};D=function(){setTimeout(Ea,1E3)};Ea=function(){var a,e=!1;if(N)return!1;N=!0;o.remove(h,"load",D);if(ca&&!Aa)return!1;k||(a=c.getMoviePercent(),0a&&(e=!0));setTimeout(function(){a=c.getMoviePercent();if(e)return N=!1,h.setTimeout(D,1),!1;!k&&Ra&&(null===a?c.useFlashBlock||0===c.flashLoadTimeout?c.useFlashBlock&&
-sa():X(!0):0!==c.flashLoadTimeout&&X(!0))},c.flashLoadTimeout)};U=function(){if(Aa||!ca)return o.remove(h,"focus",U),!0;Aa=Ra=!0;N=!1;D();o.remove(h,"focus",U);return!0};Oa=function(){var a,e=[];if(c.useHTML5Audio&&c.hasHTML5)for(a in c.audioFormats)c.audioFormats.hasOwnProperty(a)&&e.push(a+": "+c.html5[a]+(!c.html5[a]&&t&&c.flash[a]?" (using flash)":c.preferFlash&&c.flash[a]&&t?" (preferring flash)":!c.html5[a]?" ("+(c.audioFormats[a].required?"required, ":"")+"and no flash support)":""))};L=function(a){if(k)return!1;
-if(c.html5Only)return k=!0,C(),!0;var e=!0,d;if(!c.useFlashBlock||!c.flashLoadTimeout||c.getMoviePercent())k=!0,s&&(d={type:!t&&n?"NO_FLASH":"INIT_TIMEOUT"});if(s||a){if(c.useFlashBlock&&c.oMC)c.oMC.className=G()+" "+(null===c.getMoviePercent()?"swf_timedout":"swf_error");B({type:"ontimeout",error:d,ignoreInit:!0});F(d);e=!1}s||(c.waitForWindowLoad&&!ja?o.add(h,"load",C):C());return e};Da=function(){var a,e=c.setupOptions;for(a in e)e.hasOwnProperty(a)&&("undefined"===typeof c[a]?c[a]=e[a]:c[a]!==
-e[a]&&(c.setupOptions[a]=c[a]))};ia=function(){if(k)return!1;if(c.html5Only){if(!k)o.remove(h,"load",c.beginDelayedInit),c.enabled=!0,L();return!0}V();try{i._externalInterfaceTest(!1),Fa(!0,c.flashPollingInterval||(c.useHighPerformance?10:50)),c.debugMode||i._disableDebug(),c.enabled=!0,c.html5Only||o.add(h,"unload",ha)}catch(a){return F({type:"JS_TO_FLASH_EXCEPTION",fatal:!0}),X(!0),L(),!1}L();o.remove(h,"load",c.beginDelayedInit);return!0};E=function(){if(oa)return!1;oa=!0;Da();qa();!t&&c.hasHTML5&&
-c.setup({useHTML5Audio:!0,preferFlash:!1});Ma();c.html5.usingFlash=La();n=c.html5.usingFlash;Oa();!t&&n&&c.setup({flashLoadTimeout:1});l.removeEventListener&&l.removeEventListener("DOMContentLoaded",E,!1);V();return!0};va=function(){"complete"===l.readyState&&(E(),l.detachEvent("onreadystatechange",va));return!0};na=function(){ja=!0;o.remove(h,"load",na)};wa();o.add(h,"focus",U);o.add(h,"load",D);o.add(h,"load",na);l.addEventListener?l.addEventListener("DOMContentLoaded",E,!1):l.attachEvent?l.attachEvent("onreadystatechange",
-va):F({type:"NO_DOM2_EVENTS",fatal:!0});"complete"===l.readyState&&setTimeout(E,100)}var da=null;if("undefined"===typeof SM2_DEFER||!SM2_DEFER)da=new Q;ea.SoundManager=Q;ea.soundManager=da})(window);
\ No newline at end of file
diff --git a/js/sm2/swf/soundmanager2.swf b/js/sm2/swf/soundmanager2.swf
deleted file mode 100644
index 8334e7f..0000000
Binary files a/js/sm2/swf/soundmanager2.swf and /dev/null differ
diff --git a/js/sm2/swf/soundmanager2_debug.swf b/js/sm2/swf/soundmanager2_debug.swf
deleted file mode 100644
index 4e30adb..0000000
Binary files a/js/sm2/swf/soundmanager2_debug.swf and /dev/null differ
diff --git a/js/sm2/swf/soundmanager2_flash9.swf b/js/sm2/swf/soundmanager2_flash9.swf
deleted file mode 100644
index 34d4da2..0000000
Binary files a/js/sm2/swf/soundmanager2_flash9.swf and /dev/null differ
diff --git a/js/sm2/swf/soundmanager2_flash9_debug.swf b/js/sm2/swf/soundmanager2_flash9_debug.swf
deleted file mode 100644
index ff5d695..0000000
Binary files a/js/sm2/swf/soundmanager2_flash9_debug.swf and /dev/null differ
diff --git a/js/sm2/swf/soundmanager2_flash_xdomain.zip b/js/sm2/swf/soundmanager2_flash_xdomain.zip
deleted file mode 100644
index 6d46706..0000000
Binary files a/js/sm2/swf/soundmanager2_flash_xdomain.zip and /dev/null differ
diff --git a/meta/android-chrome-192x192.png b/meta/android-chrome-192x192.png
deleted file mode 100644
index 217c7ed..0000000
Binary files a/meta/android-chrome-192x192.png and /dev/null differ
diff --git a/meta/android-chrome-384x384.png b/meta/android-chrome-384x384.png
deleted file mode 100644
index d9edd2b..0000000
Binary files a/meta/android-chrome-384x384.png and /dev/null differ
diff --git a/meta/apple-touch-icon.png b/meta/apple-touch-icon.png
deleted file mode 100644
index 0cf9ec5..0000000
Binary files a/meta/apple-touch-icon.png and /dev/null differ
diff --git a/meta/browserconfig.xml b/meta/browserconfig.xml
deleted file mode 100644
index 66f1a40..0000000
--- a/meta/browserconfig.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
- #779ecb
-
-
-
diff --git a/meta/favicon-16x16.png b/meta/favicon-16x16.png
deleted file mode 100644
index 291d0f1..0000000
Binary files a/meta/favicon-16x16.png and /dev/null differ
diff --git a/meta/favicon-32x32.png b/meta/favicon-32x32.png
deleted file mode 100644
index 7cef537..0000000
Binary files a/meta/favicon-32x32.png and /dev/null differ
diff --git a/meta/favicon.ico b/meta/favicon.ico
deleted file mode 100644
index d532e2b..0000000
Binary files a/meta/favicon.ico and /dev/null differ
diff --git a/meta/mstile-150x150.png b/meta/mstile-150x150.png
deleted file mode 100644
index 97a0893..0000000
Binary files a/meta/mstile-150x150.png and /dev/null differ
diff --git a/meta/safari-pinned-tab.svg b/meta/safari-pinned-tab.svg
deleted file mode 100644
index 686dc8c..0000000
--- a/meta/safari-pinned-tab.svg
+++ /dev/null
@@ -1,82 +0,0 @@
-
-
-
-
-Created by potrace 1.11, written by Peter Selinger 2001-2013
-
-
-
-
-
diff --git a/meta/site.webmanifest b/meta/site.webmanifest
deleted file mode 100644
index 70d35b2..0000000
--- a/meta/site.webmanifest
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "name": "",
- "short_name": "",
- "icons": [
- {
- "src": "/meta/android-chrome-192x192.png",
- "sizes": "192x192",
- "type": "image/png"
- },
- {
- "src": "/meta/android-chrome-384x384.png",
- "sizes": "384x384",
- "type": "image/png"
- }
- ],
- "theme_color": "#ffffff",
- "background_color": "#ffffff",
- "display": "standalone"
-}
diff --git a/meta/thumbnail.png b/meta/thumbnail.png
deleted file mode 100644
index c59ba38..0000000
Binary files a/meta/thumbnail.png and /dev/null differ
diff --git a/songs/africa.json b/songs/africa.json
new file mode 100644
index 0000000..0d55058
--- /dev/null
+++ b/songs/africa.json
@@ -0,0 +1,6 @@
+{
+ "notes": ",300 1#v6v3x 1#v6v3asx 1#v6v3 1#v6v3ax 1#v6v3ax v7v5#v2#asx 31#v5# ax x asx . ax ax asx . ax 1#v6v3x 1#v6v3asx 1#v6v3 1#v6v3ax 1#v6v3ax v7v5#v2#asx 31#v5# ax x asx . ax ax asx . ax 1#v6v3x 1#v6v3asx 1#v6v3 1#v6v3ax 1#v6v3ax v7v5#v2#asx 31#v5# ax x asx . ax ax asx . ax 1#v6v3x 1#v6v3asx 1#v6v3 1#v6v3ax 1#v6v3ax v7v5#v2#asx 31#v5# ax x asx . ax ax asx . ax r#2#v7v4#x r#asx r# r#ax r#1#v6#ax asx t# y#ax u2#v7v4#x asx . ax w#ax w#asx e r#ax e1#v6v3x asx e w#ax q#v7v5#ax asx q# vuax w#2#v4#x asx q# vuax q#1#v6v3ax 1#v6v3asx 1#v6v3 1#v6v3ax 1#v6v3x v7v5#v2#asx 31#v5# ax r#2#v7v4#ax r#asx r# r#ax r#1#v6#x asx t# y#ax u2#v7v4#ax asx . ax w#x w#asx e r#ax e1#v6v3ax easx . w#ax q#v7v5#x asx q# vuax w#2#v4#ax asx q# vuax q#1#v6v3x 1#v6v3asx 1#v6v3 1#v6v3ax 1#v6v3ax v7v5#v2#asx 31#v5# ax r#2#v7v4#x r#asx r# r#ax r#1#v6#ax asx t# y#ax u2#v7v4#x asx . ax w#ax w#asx e r#ax e1#v6v3x asx e w#ax q#v7v5#ax asx q# vuax 1#v6v3x v7v5#v2#asx 31#v5# ax r#2#v7v4#ax r#asx r# ax r#1#v6#x r#asx t# y#ax u2#v7v5#ax asx . ax ^w#ux ^w#uasx ^e^q# ^r#^w#ax ^r#^w#1#v6v3ax ^e^q#1#v6v3asx ^w#u1#v6v3 ^e^q#1#v6v3ax 1#v6v3x v7v5#v2#asx 2#v7v5# ax ax asx . ax x asx . ax ^y64#1#v6ax ^yasx ^y ^yax 2x ^yasx ^y ^yax ^y31#ax asx ^t# ^t#ax 5#v7v5#x asx 6 5#ax ^yeq#64#1#v6ax ^yeq#asx ^yeq# ^yr#wax ^yr#w64#1#v6x ^yr#wasx ^yr#w ^yeq#ax 2ax ^yeq#asx ^t#evu ax ^t#evu31#x asx . ax ^yeq#5#v7v5#ax ^yeq#asx ^yeq#6 ^yr#w5#ax 64#1#v6x r#wasx ^yr#w ^yeq#ax ^y64#1#v6ax eq#asx ^t#evu ^t#ax evu2x asx . ax ^yeq#31#ax ^yeq#asx ^yeq# ^yr#wax ^yr#w5#v7v5#x ^yr#wasx ^yr#w6 ^yeq#5#ax 64#1#v6ax ^yeq#asx ^t#evu ax ^t#evu2x asx ^r#q#vy ^rvuvt#ax ^t#evu31#ax asx ^yq# ^t#vuax ^r#q#vy#5#v5#x asx . ax v6#v3ax asx . ax",
+ "author": "Green",
+ "title": "Toto - Africa",
+ "notation": "bongo+"
+}
diff --git a/songs/tetris_drums.json b/songs/tetris_drums.json
new file mode 100644
index 0000000..c40d1b4
--- /dev/null
+++ b/songs/tetris_drums.json
@@ -0,0 +1,6 @@
+{
+ "notes": ",400 ^3 . 7 ^1 ^2 ^3^2 ^1 7 6 . 6 ^1 ^3 . ^2 ^1 7 . 7 ^1 ^2 . ^3 . ^1 . 6 . 6 6 76 ^2^1 ^3 ^2 . ^4 ^6 . ^5 ^4 ^3 . . ^1 ^3 . ^2 ^1 7 . 7 ^1 ^2 . ^3 . ^1 . 6 . 6 . . . ^33bx . 7v7bc ^11. ^22bx ^3^232x ^11bc 7v7. 6v6bx . 6v6bc ^11. ^33bx . ^22ncx ^11. 7v7bx . 7v7bc ^11. ^22bx .x ^33bc . ^11bx . 6v6bc . 6v6bx 6v6x 76v7v6nc ^2^121. ^33bx ^22. .bc ^44. ^66bx .x ^55bc ^44. ^33bx . .bc ^11. ^33bx . ^22ncx ^11. 7v7bx . 7v7bc ^11. ^22bx .x ^33bc . ^11bx . 6v6bc . 6v6bx .x .nc .",
+ "author": "Green",
+ "title": "Tetris Drums",
+ "notation": "bongo+"
+}
\ No newline at end of file
diff --git a/sounds/bongo0.wav b/sounds/bongo0.wav
deleted file mode 100644
index 47f621a..0000000
Binary files a/sounds/bongo0.wav and /dev/null differ
diff --git a/sounds/bongo1.wav b/sounds/bongo1.wav
deleted file mode 100644
index f9bfc31..0000000
Binary files a/sounds/bongo1.wav and /dev/null differ
diff --git a/sounds/cowbell.mp3 b/sounds/cowbell.mp3
deleted file mode 100644
index 9b75552..0000000
Binary files a/sounds/cowbell.mp3 and /dev/null differ
diff --git a/sounds/cowbell.wav b/sounds/cowbell.wav
deleted file mode 100644
index a3a38bb..0000000
Binary files a/sounds/cowbell.wav and /dev/null differ
diff --git a/sounds/cymbal.wav b/sounds/cymbal.wav
deleted file mode 100644
index a86ece1..0000000
Binary files a/sounds/cymbal.wav and /dev/null differ
diff --git a/sounds/keyboard0.wav b/sounds/keyboard0.wav
deleted file mode 100644
index f5f9a41..0000000
Binary files a/sounds/keyboard0.wav and /dev/null differ
diff --git a/sounds/keyboard1.wav b/sounds/keyboard1.wav
deleted file mode 100644
index ebc2711..0000000
Binary files a/sounds/keyboard1.wav and /dev/null differ
diff --git a/sounds/keyboard2.wav b/sounds/keyboard2.wav
deleted file mode 100644
index 06cf989..0000000
Binary files a/sounds/keyboard2.wav and /dev/null differ
diff --git a/sounds/keyboard3.wav b/sounds/keyboard3.wav
deleted file mode 100644
index f83f382..0000000
Binary files a/sounds/keyboard3.wav and /dev/null differ
diff --git a/sounds/keyboard4.wav b/sounds/keyboard4.wav
deleted file mode 100644
index c3e6a98..0000000
Binary files a/sounds/keyboard4.wav and /dev/null differ
diff --git a/sounds/keyboard5.wav b/sounds/keyboard5.wav
deleted file mode 100644
index 9f6c304..0000000
Binary files a/sounds/keyboard5.wav and /dev/null differ
diff --git a/sounds/keyboard6.wav b/sounds/keyboard6.wav
deleted file mode 100644
index 5c41a3b..0000000
Binary files a/sounds/keyboard6.wav and /dev/null differ
diff --git a/sounds/keyboard7.wav b/sounds/keyboard7.wav
deleted file mode 100644
index 3aa1841..0000000
Binary files a/sounds/keyboard7.wav and /dev/null differ
diff --git a/sounds/keyboard8.wav b/sounds/keyboard8.wav
deleted file mode 100644
index 0990c3d..0000000
Binary files a/sounds/keyboard8.wav and /dev/null differ
diff --git a/sounds/keyboard9.wav b/sounds/keyboard9.wav
deleted file mode 100644
index 4c498f1..0000000
Binary files a/sounds/keyboard9.wav and /dev/null differ
diff --git a/sounds/marimba0.wav b/sounds/marimba0.wav
deleted file mode 100644
index b48e39b..0000000
Binary files a/sounds/marimba0.wav and /dev/null differ
diff --git a/sounds/marimba1.wav b/sounds/marimba1.wav
deleted file mode 100644
index 3ba2a49..0000000
Binary files a/sounds/marimba1.wav and /dev/null differ
diff --git a/sounds/marimba2.wav b/sounds/marimba2.wav
deleted file mode 100644
index 5f8987d..0000000
Binary files a/sounds/marimba2.wav and /dev/null differ
diff --git a/sounds/marimba3.wav b/sounds/marimba3.wav
deleted file mode 100644
index 2656dda..0000000
Binary files a/sounds/marimba3.wav and /dev/null differ
diff --git a/sounds/marimba4.wav b/sounds/marimba4.wav
deleted file mode 100644
index aa879c7..0000000
Binary files a/sounds/marimba4.wav and /dev/null differ
diff --git a/sounds/marimba5.wav b/sounds/marimba5.wav
deleted file mode 100644
index d26be89..0000000
Binary files a/sounds/marimba5.wav and /dev/null differ
diff --git a/sounds/marimba6.wav b/sounds/marimba6.wav
deleted file mode 100644
index 602c9b3..0000000
Binary files a/sounds/marimba6.wav and /dev/null differ
diff --git a/sounds/marimba7.wav b/sounds/marimba7.wav
deleted file mode 100644
index c0b9342..0000000
Binary files a/sounds/marimba7.wav and /dev/null differ
diff --git a/sounds/marimba8.wav b/sounds/marimba8.wav
deleted file mode 100644
index 1fc54b9..0000000
Binary files a/sounds/marimba8.wav and /dev/null differ
diff --git a/sounds/marimba9.wav b/sounds/marimba9.wav
deleted file mode 100644
index 3a331ab..0000000
Binary files a/sounds/marimba9.wav and /dev/null differ
diff --git a/sounds/meow.wav b/sounds/meow.wav
deleted file mode 100644
index bf81522..0000000
Binary files a/sounds/meow.wav and /dev/null differ
diff --git a/sounds/tambourine.wav b/sounds/tambourine.wav
deleted file mode 100644
index df2e05b..0000000
Binary files a/sounds/tambourine.wav and /dev/null differ
diff --git a/src/bongocat.css b/src/bongocat.css
new file mode 100644
index 0000000..637c171
--- /dev/null
+++ b/src/bongocat.css
@@ -0,0 +1,132 @@
+html {
+ font-size: 10px;
+}
+
+body,html {
+ height: 1080px;
+ min-height: 1080px;
+ width: 1920px;
+ margin: 0;
+ padding: 0;
+ font-family: sans-serif;
+ overflow: hidden;
+}
+#bongocat {
+ position: absolute;
+ left: -1920px;
+ width: 1920px;
+ height: 1080px;
+ transition:left 0.5s ease-in-out;
+
+}
+#table {
+ float: left;
+ border-top: 10px solid black;
+ background: white;
+ height: 410px;
+ width: 1920px;
+ transform: rotate(14deg);
+ transform-origin: top left;
+ position: absolute;
+ left: 0px;
+ bottom: 0px;
+}
+
+#cat {
+ width: 800px;
+ height: 450px;
+ display: block;
+ position: absolute;
+ transform-origin: top left;
+ bottom: 0px;
+ left: 0px;
+}
+
+#cat div {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ top: 0px;
+ left: 0px;
+}
+#head {
+ background: url("images/cat.png");
+}
+#mouth {
+ background: url("images/mouth.png");
+}
+#paw-left {
+ background: url("images/paw-left.png");
+ background-position: left top;
+}
+#paw-right {
+ background: url("images/paw-right.png");
+
+ background-position: left top;
+}
+#nametag {
+ font-family: 'Lato', sans-serif;
+ font-weight: 300;
+ background: rgba(0,0,0,0.5);
+ font-size: 60px;
+ color: white;
+ position: absolute;
+ top: -100px;
+ left: 500px;
+ white-space: nowrap;
+ padding: 10px 15px;
+}
+#dedications {
+ font-family: 'Lato', sans-serif;
+ font-weight: 300;
+ background: rgba(0,0,0,0.3);
+ font-size: 30px;
+ color: rgba(255, 255, 255, 0.7);
+ position: absolute;
+ top: 400px;
+ left: 300px;
+ white-space: nowrap;
+ padding: 10px 15px;
+}
+#instruments {
+ width: 800px;
+ height: 450px;
+ display: block;
+ position: absolute;
+ transform-origin: top left;
+ bottom: 0px;
+ left: 0px;
+}
+
+#instruments div {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ top: 0px;
+ left: 0px;
+ visibility: hidden;
+}
+#instruments #bongo {
+ background: url("images/bongo.png");
+}
+#instruments #keyboard {
+ background: url("images/keyboard.png");
+}
+#instruments #cymbal {
+ background: url("images/cymbal.png");
+}
+#instruments #marimba {
+ background: url("images/marimba.png");
+}
+#instruments #tambourine {
+ background: url("images/tambourine.png");
+}
+#instruments #cowbell {
+ background: url("images/cowbell.png");
+}
+#instruments #TR808 {
+ background: url("images/808.png");
+}
+#instruments #nokia3210 {
+ background: url("images/NOKIA3210.png");
+}
\ No newline at end of file
diff --git a/src/bongocat.js b/src/bongocat.js
new file mode 100644
index 0000000..d80a9dc
--- /dev/null
+++ b/src/bongocat.js
@@ -0,0 +1,651 @@
+import { parseSong as parseSongBongo } from "./modules/bongo.js";
+import { extensionFeatures, experimentalFeatures } from "./experimental/bongox.js";
+import { playMidi } from "./experimental/midi.js";
+
+// ====================================================== //
+// ================== type definitions ================== //
+// ====================================================== //
+
+/**
+ * Song typedefinition
+ * @date 5/7/2023 - 2:00:19 PM
+ *
+ * @typedef Song
+ * @type {object}
+ * @property {string} notes - the notes of the song
+ * @property {string} notation - notation used for the song
+ * @property {string} performer - the name of the one playing this song
+ * @property {string} [author] - optional name of the author in case of saved song
+ * @property {string} [title] - optional title of the song in case of a saved song
+ * @property {string[]} [dedications] - optional dedications of the song
+ */
+
+/**
+ * Playback typedefinition
+ * @date 5/7/2023 - 2:00:19 PM
+ *
+ * @typedef Playback
+ * @type {object}
+ * @property {object} cmd - the command to execute
+ * @property {number} time - the time when it should be executed
+ * @property {any[]} args - the arguments for the command
+ */
+
+// ====================================================== //
+// ==================== global state ==================== //
+// ====================================================== //
+var maxBpm = 800;
+var minBpm = 50;
+
+var bpm = {};
+bpm.user = {};
+var parts = {};
+var queue = [];
+var bongoEnabled = true;
+var playing = false;
+setBPM(128);
+var githubUrls = ["https://raw.githubusercontent.com/jvpeek/twitch-bongocat/master/songs/", "https://raw.githubusercontent.com/awsdcrafting/bongocat-songs/live/songs/"];
+var stackMode = false;
+var maxSongLength = 90_000; //90 secs
+var defaultNotation = "bongo";
+var disableExperiments = false;
+var disableExtensions = false;
+
+var currentSong = null;
+var volume = 1.0;
+
+window.maxNotesPerBatch = 5;
+
+var audioContext;
+var mainGainNode;
+var oscillatorNode;
+var synthStarted = false;
+var synthDampening = 0.1;
+
+// ====================================================== //
+// ================== notation handlers ================= //
+// ====================================================== //
+var notations = {};
+
+notations["bongol"] = parseSongBongo;
+notations["legacy"] = parseSongBongo;
+notations["bongo"] = parseSongBongo;
+notations["bongo+"] = parseSongBongo;
+
+
+// ====================================================== //
+// =================== helper methods =================== //
+// ====================================================== //
+function setVolume(volumeParam) {
+ volume = Math.min(1.0, Math.max(0, Number(volumeParam)));
+ if (mainGainNode) {
+ mainGainNode.gain.value = volume * synthDampening;
+ }
+}
+
+function setMaxSongLength(maxSongLengthParam) {
+ maxSongLength = Number(maxSongLengthParam);
+ if (maxSongLength > 0) {
+ maxSongLength *= 1000;
+ } else {
+ maxSongLength = -1;
+ }
+}
+
+function clamp(value, min, max) {
+ if (value > max) {
+ return max;
+ }
+ if (value < min) {
+ return min;
+ }
+ return value;
+ //return Math.min(Math.max(value, min), max)
+}
+
+function clampBpm(bpm) {
+ return clamp(bpm, minBpm, maxBpm)
+}
+
+function setBPM(targetBPM, username) {
+ targetBPM = Number(targetBPM);
+ if (isNaN(targetBPM)) {
+ return;
+ }
+ targetBPM = clampBpm(targetBPM)
+ if (username === undefined) {
+ console.log(" current BPM: " + bpm.global + ". Target: " + Math.floor(60000 / targetBPM));
+ bpm.global = Math.floor(60000 / targetBPM);
+ } else {
+ console.log(" current BPM for " + username + ": " + bpm.user[username] + ". Target: " + Math.floor(60000 / targetBPM));
+ bpm.user[username] = Math.floor(60000 / targetBPM);
+ }
+}
+
+function getBPM(username) {
+ if (username === undefined || bpm.user[username] === undefined) {
+ return bpm.global;
+ } else {
+ return bpm.user[username];
+ }
+}
+
+function playSound(cmd, cBpm) {
+ const audio = document.querySelector(`audio[data-key="${cmd}"]`);
+ if (!audio) {
+ if (cmd != ".") {
+ console.error("No audio for ", cmd);
+ }
+ return;
+ }
+ setPaw(audio.dataset.paw, cBpm);
+ setInstrument(audio.dataset.instrument);
+
+ playMidi(cmd); // add midiplay here //Green
+
+ audio.currentTime = 0;
+ audio.volume = volume;
+ audio.play();
+}
+
+function prepareSynth(type) {
+ audioContext = new AudioContext();
+ mainGainNode = audioContext.createGain();
+ mainGainNode.connect(audioContext.destination);
+ mainGainNode.gain.value = 0;
+ oscillatorNode = audioContext.createOscillator();
+ oscillatorNode.connect(mainGainNode);
+ oscillatorNode.type = type;
+ synthStarted = false;
+ return { "ctx": audioContext, "gain": mainGainNode, "osc": oscillatorNode };
+}
+
+function playSynthSound(frequency, time) {
+ if (!audioContext || !mainGainNode || !oscillatorNode) {
+ return;
+ }
+ if (!synthStarted) {
+ oscillatorNode.start();
+ oscillatorNode.stop(maxSongLength / 1000);
+ synthStarted = true;
+ }
+ mainGainNode.gain.setValueAtTime(volume * synthDampening, time);
+ oscillatorNode.frequency.setValueAtTime(frequency, time);
+}
+
+function muteSynth(time) {
+ if (!mainGainNode) {
+ return;
+ }
+ mainGainNode.gain.setValueAtTime(0, time);
+}
+
+function introAnimation(song) {
+ let username = song.performer;
+ if (!song.author || song.performer == song.author) {
+ document.getElementById("nametag").innerHTML = username + " entered the stage";
+ }
+ else {
+ document.getElementById("nametag").innerHTML = username + " performs " + song.title + " by " + song.author;
+ }
+
+ if (song.dedications) {
+ song.dedications = song.dedications.filter((dedication, index) => {
+ return song.dedications.indexOf(dedication) === index;
+ });
+
+ document.getElementById("dedications").innerHTML = "This song is dedicated to " + song.dedications.join(", ");
+ document.getElementById("dedications").style.visibility = "visible";
+ } else {
+ document.getElementById("dedications").innerHTML = "";
+ document.getElementById("dedications").style.visibility = "hidden";
+ }
+
+ document.getElementById("bongocat").style.left = "0px";
+ playing = true;
+}
+
+function outroAnimation() {
+ document.getElementById("bongocat").style.left = "-1920px";
+ document.getElementById("dedications").style.visibility = "hidden";
+ setInstrument("none");
+ for (let id of currentSong.timeoutIDs) {
+ clearTimeout(id);
+ }
+ if (mainGainNode) {
+ mainGainNode.gain.value = 0;
+ }
+ if (oscillatorNode && synthStarted) {
+ oscillatorNode.stop();
+ synthStarted = false;
+ }
+ setTimeout(function () {
+ playing = false;
+ currentSong = null;
+ }, 1000);
+}
+
+function errorAnimation(error) {
+ document.getElementById("nametag").innerHTML = document.getElementById("nametag").innerHTML.split(" ")[0] + " crashed the cat :(";
+ document.getElementById("dedications").style.visibility = "visible";
+ document.getElementById("dedications").innerHTML = "Tag scisneromam to fix: " + error;
+ setInstrument("none");
+ for (let id of currentSong.timeoutIDs) {
+ clearTimeout(id);
+ }
+ if (mainGainNode) {
+ mainGainNode.gain.value = 0;
+ }
+ if (oscillatorNode && synthStarted) {
+ oscillatorNode.stop();
+ synthStarted = false;
+ }
+ setTimeout(outroAnimation, 5000);
+}
+
+function setInstrument(instrument) {
+ var c = document.getElementById("instruments").children;
+ for (var i = 0; i < c.length; i++) {
+ c[i].style.visibility = "hidden";
+ }
+ var newInstrument = document.getElementById(instrument);
+ if (!newInstrument) { return; }
+ newInstrument.style.visibility = "visible";
+
+}
+
+
+
+function setPaw(paw, cBpm) {
+ var currentPaw = document.getElementById(paw);
+ currentPaw.style.backgroundPosition = "top right";
+ window.setTimeout(releasePaw, 1000 * (60 / cBpm / 2), paw);
+}
+
+function releasePaw(paw) {
+
+ var currentPaw = document.getElementById(paw);
+ currentPaw.style.backgroundPosition = "top left";
+}
+
+function saveCmd(cmd) {
+ return (...args) => {
+ try {
+ cmd(...args);
+ } catch (error) {
+ errorAnimation(error);
+ console.error(error);
+ }
+ };
+}
+
+function preparePlaybackObject(cmd, time, ...args) {
+ return { time: time, cmd: saveCmd(cmd), args: args };
+}
+
+var helperMethods = { clamp, clampBpm, setBPM, getBPM, playSound, prepareSynth, playSynthSound, muteSynth, introAnimation, outroAnimation, setInstrument, setPaw, releasePaw, preparePlaybackObject };
+for (const key in helperMethods) {
+ window[key] = helperMethods[key];
+}
+window.helperMethods = helperMethods;
+console.log(helperMethods);
+
+
+// ====================================================== //
+// ================== queue management ================== //
+// ====================================================== //
+
+/**
+ * Adds a song to the queue
+ * @date 5/7/2023 - 2:01:20 PM
+ *
+ * @param {Song} song
+ */
+function addToQueue(song) {
+ //pre check for valid song
+ if (!song.notes) {
+ return;
+ }
+
+ if (!song.notation) {
+ song.notation = defaultNotation;
+ }
+ if (!song.performer) {
+ song.performer = "anonymous";
+ }
+
+ queue.push(song);
+}
+
+/**
+ * Returns the oldest song in the queue
+ * @returns {Song}
+ */
+function getFromQueue() {
+
+ var returnvalue;
+ if (stackMode) {
+ returnvalue = queue.pop();
+ } else {
+ returnvalue = queue.shift();
+ }
+ return returnvalue;
+}
+
+/**
+ * Sets the interval for the checkQueue function
+ * once per second
+ * @date 5/7/2023 - 2:10:23 PM
+ */
+function startQueue() {
+ setInterval(checkQueue, 1000);
+}
+
+
+/**
+ * Checks for new songs in the queue
+ * In case of a new song and no currently playing song the next song is retrieved from the queue
+ * This song is then parsed by the handler defined for the used notation
+ * then its played back
+ * @date 5/7/2023 - 2:10:56 PM
+ */
+function checkQueue() {
+ if (queue.length > 0 && playing == false) {
+ var song = getFromQueue();
+ let handler = notations[song.notation];
+ if (song.extension && !disableExtensions) {
+ handler = extensionFeatures[song.notation] || handler;
+ }
+ if (song.experimental && !disableExperiments) {
+ handler = experimentalFeatures[song.notation] || handler;
+ }
+ if (handler) {
+ currentSong = song;
+ currentSong.timeoutIDs = [];
+ let playbacks = handler(song);
+ introAnimation(song);
+ console.log(playbacks);
+ for (let playback of playbacks) {
+ if (maxSongLength > 0 && playback.time > maxSongLength) {
+ currentSong.timeoutIDs.push(setTimeout(outroAnimation, maxSongLength + 1000)); //1 sek
+ break;
+ }
+ currentSong.timeoutIDs.push(setTimeout(playback.cmd, playback.time, ...playback.args));
+ }
+ }
+ //addNotes(noteString, isLegacyNotation, username);
+ }
+}
+
+
+// ====================================================== //
+// ===================== remote play ==================== //
+// ====================================================== //
+async function playFromGithub(song, user) {
+ const userRegex = /@\w+/g;
+ let dedications = song.match(userRegex)?.map(s => s.replace("@", ""));
+ song = song.replaceAll(userRegex, ""); //remove usernames from string
+ song = song.trim().replaceAll(/\s+/g, "_").replace(/\.json$/, "").replaceAll(".", ""); //remove whitespaces, remove dots
+ song = song.toLowerCase();
+ song += ".json";
+
+ console.log("Playing", song, "from github for", user);
+ for (let githubUrl of githubUrls) {
+ const response = await fetch(encodeURI(githubUrl + song.trim()));
+ if (response.status != 200) {
+ continue;
+ }
+ //console.log(response)
+ const jsonData = await response.json();
+ console.log(jsonData);
+ jsonData.performer = user;
+ jsonData.dedications = dedications;
+ addToQueue(jsonData);
+ break;
+ }
+
+
+}
+
+
+
+// ====================================================== //
+// ==================== mod commands; =================== //
+// ====================================================== //
+const commands = {};
+
+function enableBongo(args) {
+ if (isSuperUser(args.tags)) {
+ console.log("aktiviere Bongo");
+ bongoEnabled = true;
+ }
+}
+commands["!enablebongo"] = enableBongo;
+
+function disableBongo(args) {
+ if (isSuperUser(args.tags)) {
+ console.log("deaktiviere Bongo");
+ bongoEnabled = false;
+ }
+}
+commands["!disablebongo"] = disableBongo;
+
+function clearQueue(args) {
+ if (isSuperUser(args.tags)) {
+ queue = [];
+ }
+}
+
+commands["!bongoclear"] = clearQueue;
+
+function skipSong(args) {
+ if (!isSuperUser(args.tags)) {
+ return;
+ }
+ if (!currentSong) {
+ return;
+ }
+
+ if (!currentSong.timeoutIDs) {
+ return;
+ }
+
+ console.log(`${args.tags.username} cleared the current song ${currentSong}`);
+
+ for (let id of currentSong.timeoutIDs) {
+ clearTimeout(id);
+ }
+
+ outroAnimation();
+}
+
+commands["!bongoskip"] = skipSong;
+
+function setVolumeCommand(args) {
+ if (!isSuperUser(args.tags)) {
+ return;
+ }
+ setVolume(args.arg);
+}
+
+commands["!bongovolume"] = setVolumeCommand;
+
+function setMaxSongLengthCommand(args) {
+ if (!isSuperUser(args.tags)) {
+ return;
+ }
+ setMaxSongLength(args.arg);
+}
+
+commands["!bongomaxsonglength"] = setMaxSongLengthCommand;
+
+// ====================================================== //
+// ==================== user commands =================== //
+// ====================================================== //
+
+function bongoDefault(args) {
+ if (!bongoEnabled) {
+ return;
+ }
+ //const notes = args.message.substr(8);
+ console.log(args);
+ const notes = args.arg;
+ console.log(`${args.tags.username} plays ${notes}. with ${defaultNotation}`);
+ let song = { performer: args.tags.username, notes: notes, notation: defaultNotation };
+ addToQueue(song);
+}
+
+function bongoPlus(args) {
+ if (!bongoEnabled) {
+ return;
+ }
+ //const notes = args.message.substr(8);
+ console.log(args);
+ const notes = args.arg;
+ console.log(`${args.tags.username} plays+ ${notes}.`);
+ let song = { performer: args.tags.username, notes: notes, notation: "bongo" };
+ addToQueue(song);
+}
+commands["!bongo"] = bongoDefault;
+commands["!bongo+"] = bongoPlus;
+
+function bongo(args) {
+ if (!bongoEnabled) {
+ return;
+ }
+ //const notes = message.substr(7);
+ const notes = args.arg;
+ console.log(`${args.tags.username} plays ${notes}.`);
+ let song = { performer: args.tags.username, notes: notes, notation: "legacy" };
+ addToQueue(song);
+}
+commands["!bongol"] = bongo;
+
+function bongox(args) {
+ let split = args.arg.indexOf(" ");
+ let experiment = args.arg.slice(0, split);
+ let notes = args.arg.slice(split + 1);
+ let username = args.tags.username;
+ let song = { performer: args.tags.username, notes: notes, notation: experiment, "experimental": true, "extension": true };
+ addToQueue(song);
+ //experimentalFeatures[experiment]?.(notes, username)
+}
+commands["!bongox"] = bongox;
+
+
+function changeBpm(args) {
+ if (!bongoEnabled) {
+ return;
+ }
+ //const targetBPM = Number(message.substr(5));
+ const targetBPM = Number(args.arg);
+ //if (targetBPM <= 600 && targetBPM > 49)
+ //{
+ console.log(`${args.tags.username} set BPM to ${targetBPM}.`);
+ setBPM(targetBPM, args.tags.username);
+ //}
+}
+commands["!bpm"] = changeBpm;
+
+function bongoPlay(args) {
+ if (!bongoEnabled) {
+ return;
+ }
+
+ playFromGithub(args.arg, args.tags.username);
+
+}
+commands["!bongoplay"] = bongoPlay;
+
+function handleCommand(message, command, arg, tags) {
+
+ let msg = message.toLowerCase();
+ let longestCmd = "";
+ for (let cmd in commands) {
+ if (msg.startsWith(cmd)) {
+ if (cmd.length > longestCmd.length) {
+ console.log(cmd, "beat", longestCmd);
+ longestCmd = cmd;
+ }
+ }
+ }
+ if (longestCmd) {
+ commands[longestCmd]?.({ message: message, command: command, arg: arg, tags: tags });
+ }
+ /*
+ let handler = commands[command]
+ if(handler) {
+ handler(arg, tags)
+ }
+ */
+}
+
+
+// ====================================================== //
+// ======================= config ======================= //
+// ====================================================== //
+let params = new URLSearchParams(document.location.search);
+
+let maxNotesPerBatch = params.get("maxNotesPerBatch");
+if (maxNotesPerBatch && Number(maxNotesPerBatch)) {
+ window.maxNotesPerBatch = Number(maxNotesPerBatch);
+}
+let maxPbmParam = params.get("maxBpm");
+if (maxPbmParam && Number(maxPbmParam)) {
+ maxBpm = Number(maxPbmParam);
+}
+let minPbmParam = params.get("minBpm");
+if (minPbmParam && Number(minPbmParam)) {
+ minBpm = Number(minPbmParam);
+}
+
+if (params.get("stackMode")) {
+ stackMode = true;
+}
+
+if (params.get("disableExperiments")) {
+ disableExperiments = true;
+}
+
+if (params.get("disableExtensions")) {
+ disableExtensions = true;
+}
+
+let maxSongLengthParam = params.get("maxSongLength");
+if (maxSongLengthParam && !isNaN(Number(maxSongLengthParam))) {
+ setMaxSongLength(maxSongLengthParam);
+}
+
+let volumeParam = params.get("volume");
+if (volumeParam && !isNaN(Number(volumeParam))) {
+ setVolume(volumeParam);
+}
+
+// ====================================================== //
+// ======================== tmijs ======================= //
+// ====================================================== //
+const channel = location.hash || params.get("channel") || 'jvpeek';
+const chatClient = new tmi.Client({
+ channels: [channel]
+});
+chatClient.connect();
+
+chatClient.on('connected', (address, port) => {
+ console.log(`Connected to ${address}:${port}, channel ${channel}.`);
+});
+function isSuperUser(tags) {
+ return tags.mod || tags.badges?.broadcaster || tags.username == "jvpeek";
+}
+chatClient.on('message', (channel, tags, message, self) => {
+
+ if (message.startsWith("!")) {
+ let args = message.split(/\s+/);
+ let cmd = args[0];
+ args = args.splice(1);
+ let arg = args.join(" ");
+ console.log(cmd, arg);
+ handleCommand(message, cmd, arg, tags);
+ }
+});
+
+startQueue();
diff --git a/src/dist/tmi.min.js b/src/dist/tmi.min.js
new file mode 100644
index 0000000..cfe8d15
--- /dev/null
+++ b/src/dist/tmi.min.js
@@ -0,0 +1 @@
+!function s(o,i,r){function a(t,e){if(!i[t]){if(!o[t]){var n="function"==typeof require&&require;if(!e&&n)return n(t,!0);if(c)return c(t,!0);throw(n=new Error("Cannot find module '"+t+"'")).code="MODULE_NOT_FOUND",n}n=i[t]={exports:{}},o[t][0].call(n.exports,function(e){return a(o[t][1][e]||e)},n,n.exports,s,o,i,r)}return i[t].exports}for(var c="function"==typeof require&&require,e=0;ee.length)&&(t=e.length);for(var n=0,s=new Array(t);n: ").concat(h)),V.hasOwn(t.tags,"username")||(t.tags.username=J),t.tags["message-type"]="whisper";J=V.channel(t.tags.username);this.emits(["whisper","message"],[[J,t.tags,h,!1]]);break;case"PRIVMSG":t.tags.username=t.prefix.split("!")[0],"jtv"===t.tags.username?(c=V.username(h.split(" ")[0]),u=h.includes("auto"),h.includes("hosting you for")?(a=V.extractNumber(h),this.emit("hosted",m,c,a,u)):h.includes("hosting you")&&this.emit("hosted",m,c,0,u)):(a=V.get(this.opts.options.messagesLogLevel,"info"),c=V.actionMessage(h),t.tags["message-type"]=c?"action":"chat",h=c?c[1]:h,V.hasOwn(t.tags,"bits")?this.emit("cheer",m,t.tags,h):(V.hasOwn(t.tags,"msg-id")?"highlighted-message"===t.tags["msg-id"]?(u=t.tags["msg-id"],this.emit("redeem",m,t.tags.username,u,t.tags,h)):"skip-subs-mode-message"===t.tags["msg-id"]&&(l=t.tags["msg-id"],this.emit("redeem",m,t.tags.username,l,t.tags,h)):V.hasOwn(t.tags,"custom-reward-id")&&(l=t.tags["custom-reward-id"],this.emit("redeem",m,t.tags.username,l,t.tags,h)),c?(this.log[a]("[".concat(m,"] *<").concat(t.tags.username,">: ").concat(h)),this.emits(["action","message"],[[m,t.tags,h,!1]])):(this.log[a]("[".concat(m,"] <").concat(t.tags.username,">: ").concat(h)),this.emits(["chat","message"],[[m,t.tags,h,!1]]))));break;default:this.log.warn("Could not parse message:\n".concat(JSON.stringify(t,null,4)))}}},n.prototype.connect=function(){var s=this;return new Promise(function(t,n){s.server=V.get(s.opts.connection.server,"irc-ws.chat.twitch.tv"),s.port=V.get(s.opts.connection.port,80),s.secure&&(s.port=443),443===s.port&&(s.secure=!0),s.reconnectTimer=s.reconnectTimer*s.reconnectDecay,s.reconnectTimer>=s.maxReconnectInterval&&(s.reconnectTimer=s.maxReconnectInterval),s._openConnection(),s.once("_promiseConnect",function(e){e?n(e):t([s.server,~~s.port])})})},n.prototype._openConnection=function(){var e="".concat(this.secure?"wss":"ws","://").concat(this.server,":").concat(this.port,"/"),t={};"agent"in this.opts.connection&&(t.agent=this.opts.connection.agent),this.ws=new r(e,"irc",t),this.ws.onmessage=this._onMessage.bind(this),this.ws.onerror=this._onError.bind(this),this.ws.onclose=this._onClose.bind(this),this.ws.onopen=this._onOpen.bind(this)},n.prototype._onOpen=function(){var n=this;this._isConnected()&&(this.log.info("Connecting to ".concat(this.server," on port ").concat(this.port,"..")),this.emit("connecting",this.server,~~this.port),this.username=V.get(this.opts.identity.username,V.justinfan()),this._getToken().then(function(e){var t=V.password(e);n.log.info("Sending authentication to server.."),n.emit("logon");e="twitch.tv/tags twitch.tv/commands";n._skipMembership||(e+=" twitch.tv/membership"),n.ws.send("CAP REQ :"+e),t?n.ws.send("PASS ".concat(t)):V.isJustinfan(n.username)&&n.ws.send("PASS SCHMOOPIIE"),n.ws.send("NICK ".concat(n.username))}).catch(function(e){n.emits(["_promiseConnect","disconnected"],[[e],["Could not get a token."]])}))},n.prototype._getToken=function(){var e,t=this.opts.identity.password;return"function"==typeof t?(e=t())instanceof Promise?e:Promise.resolve(e):Promise.resolve(t)},n.prototype._onMessage=function(e){var t=this;e.data.trim().split("\r\n").forEach(function(e){e=q.msg(e);e&&t.handleMessage(e)})},n.prototype._onError=function(){var t=this;this.moderators={},this.userstate={},this.globaluserstate={},clearInterval(this.pingLoop),clearTimeout(this.pingTimeout),clearTimeout(this._updateEmotesetsTimer),this.reason=null===this.ws?"Connection closed.":"Unable to connect.",this.emits(["_promiseConnect","disconnected"],[[this.reason]]),this.reconnect&&this.reconnections===this.maxReconnectAttempts&&(this.emit("maxreconnect"),this.log.error("Maximum reconnection attempts reached.")),this.reconnect&&!this.reconnecting&&this.reconnections<=this.maxReconnectAttempts-1&&(this.reconnecting=!0,this.reconnections=this.reconnections+1,this.log.error("Reconnecting in ".concat(Math.round(this.reconnectTimer/1e3)," seconds..")),this.emit("reconnect"),setTimeout(function(){t.reconnecting=!1,t.connect().catch(function(e){return t.log.error(e)})},this.reconnectTimer)),this.ws=null},n.prototype._onClose=function(){var t=this;this.moderators={},this.userstate={},this.globaluserstate={},clearInterval(this.pingLoop),clearTimeout(this.pingTimeout),clearTimeout(this._updateEmotesetsTimer),this.wasCloseCalled?(this.wasCloseCalled=!1,this.reason="Connection closed.",this.log.info(this.reason),this.emits(["_promiseConnect","_promiseDisconnect","disconnected"],[[this.reason],[null],[this.reason]])):(this.emits(["_promiseConnect","disconnected"],[[this.reason]]),this.reconnect&&this.reconnections===this.maxReconnectAttempts&&(this.emit("maxreconnect"),this.log.error("Maximum reconnection attempts reached.")),this.reconnect&&!this.reconnecting&&this.reconnections<=this.maxReconnectAttempts-1&&(this.reconnecting=!0,this.reconnections=this.reconnections+1,this.log.error("Could not connect to server. Reconnecting in ".concat(Math.round(this.reconnectTimer/1e3)," seconds..")),this.emit("reconnect"),setTimeout(function(){t.reconnecting=!1,t.connect().catch(function(e){return t.log.error(e)})},this.reconnectTimer))),this.ws=null},n.prototype._getPromiseDelay=function(){return this.currentLatency<=600?600:this.currentLatency+100},n.prototype._sendCommand=function(s,o,i,r){var a=this;return new Promise(function(e,t){if(!a._isConnected())return t("Not connected to server.");var n;null!==s&&"number"!=typeof s||(null===s&&(s=a._getPromiseDelay()),V.promiseDelay(s).then(function(){return t("No response from Twitch.")})),null!==o?(n=V.channel(o),a.log.info("[".concat(n,"] Executing command: ").concat(i)),a.ws.send("PRIVMSG ".concat(n," :").concat(i))):(a.log.info("Executing command: ".concat(i)),a.ws.send(i)),"function"==typeof r?r(e,t):e()})},n.prototype._sendMessage=function(c,u,l,m){var h=this;return new Promise(function(e,t){if(!h._isConnected())return t("Not connected to server.");if(V.isJustinfan(h.getUsername()))return t("Cannot send anonymous messages.");var n,s=V.channel(u);h.userstate[s]||(h.userstate[s]={}),500<=l.length&&(n=V.splitLine(l,500),l=n[0],setTimeout(function(){h._sendMessage(c,u,n[1],function(){})},350)),h.ws.send("PRIVMSG ".concat(s," :").concat(l));var o={};Object.keys(h.emotesets).forEach(function(e){return h.emotesets[e].forEach(function(e){return(V.isRegex(e.code)?q.emoteRegex:q.emoteString)(l,e.code,e.id,o)})});var i=Object.assign(h.userstate[s],q.emotes({emotes:q.transformEmotes(o)||null})),r=V.get(h.opts.options.messagesLogLevel,"info"),a=V.actionMessage(l);a?(i["message-type"]="action",h.log[r]("[".concat(s,"] *<").concat(h.getUsername(),">: ").concat(a[1])),h.emits(["action","message"],[[s,i,a[1],!0]])):(i["message-type"]="chat",h.log[r]("[".concat(s,"] <").concat(h.getUsername(),">: ").concat(l)),h.emits(["chat","message"],[[s,i,l,!0]])),"function"==typeof m?m(e,t):e()})},n.prototype._updateEmoteset=function(s){var t,o=this,e=void 0!==s;e&&(s===this.emotes?e=!1:this.emotes=s),this._skipUpdatingEmotesets?e&&this.emit("emotesets",s,{}):(t=function(){0n&&(this._events[e].warned=!0,console.error("(node) warning: possible EventEmitter memory leak detected. %d listeners added. Use emitter.setMaxListeners() to increase limit.",this._events[e].length),"function"==typeof console.trace&&console.trace()),this},o.prototype.once=function(e,t){if(!c(t))throw TypeError("listener must be a function");var n=!1;if(this._events.hasOwnProperty(e)&&"_"===e.charAt(0)){var s,o=1,i=e;for(s in this._events)this._events.hasOwnProperty(s)&&s.startsWith(i)&&o++;e+=o}function r(){"_"!==e.charAt(0)||isNaN(e.substr(e.length-1))||(e=e.substring(0,e.length-1)),this.removeListener(e,r),n||(n=!0,t.apply(this,arguments))}return r.listener=t,this.on(e,r),this},o.prototype.removeListener=function(e,t){var n,s,o,i;if(!c(t))throw TypeError("listener must be a function");if(!this._events||!this._events[e])return this;if(o=(n=this._events[e]).length,s=-1,n===t||c(n.listener)&&n.listener===t){if(delete this._events[e],this._events.hasOwnProperty(e+"2")&&"_"===e.charAt(0)){var r,a=e;for(r in this._events)this._events.hasOwnProperty(r)&&r.startsWith(a)&&(isNaN(parseInt(r.substr(r.length-1)))||(this._events[e+parseInt(r.substr(r.length-1)-1)]=this._events[r],delete this._events[r]));this._events[e]=this._events[e+"1"],delete this._events[e+"1"]}this._events.removeListener&&this.emit("removeListener",e,t)}else if(u(n)){for(i=o;0n?(t.command=e.slice(n),t):null;for(t.command=e.slice(n,s),n=s+1;32===e.charCodeAt(n);)n++;for(;n").replace(/\\"\\;/g,'"').replace(/\\'\\;/g,"'")},unescapeIRC:function(e){return e&&"string"==typeof e&&e.includes("\\")?e.replace(i,function(e,t){return t in a?a[t]:t}):e},escapeIRC:function(e){return e&&"string"==typeof e?e.replace(r,function(e,t){return t in c?"\\".concat(c[t]):t}):e},addWord:function(e,t){return e.length?e+" "+t:e+t},splitLine:function(e,t){var n=e.substring(0,t).lastIndexOf(" ");return[e.substring(0,n=-1===n?t-1:n),e.substring(n+1)]},extractNumber:function(e){for(var t=e.split(" "),n=0;n= 3)
+ {
+ name = splits.shift();
+ name = name.trim();
+ }
+
+
+ let regex = /(?:(\w)=(\d+))|(?:(\d+)?(a#|ab|a|b|h|c#|c|db|d#|d|eb|e#|e|fb|f#|f|gb|g#|g|p)(\.)?(\d)?(\.)?)/gi; //rtttl notation converted to regex
+ //groups:
+ //0 = complete capture
+ //1 = key
+ //2 = value
+ //3 = duration
+ //4 = note
+ //5 = octave
+ //6 = dot
+ let time = 1; //current time in seconds
+ let playbacks = [];
+ prepareSynth("square");
+ playbacks.push(preparePlaybackObject(setInstrument, 0, "nokia3210"));
+ while (splits.length > 0)
+ {
+ let notes = splits.shift();
+ notes = [...notes.matchAll(regex)];
+ for (let note of notes)
+ {
+ //param handling
+ if (note[1] && note[2])
+ {
+ let numberValue = Math.floor(Number(note[2]));
+ if (Number.isNaN(numberValue))
+ {
+ continue;
+ }
+ let key = note[1];
+ switch (key)
+ {
+ case "b":
+ bpm = clampBpm(numberValue);
+ break;
+ case "o":
+ octave = numberValue;
+ break;
+ case "d":
+ duration = numberValue;
+ break;
+ }
+ continue;
+ }
+ //note handling
+ let noteDuration = duration;
+ if (note[3])
+ {
+ let numberValue = Math.floor(Number(note[3]));
+ if (numberValue && !Number.isNaN(numberValue))
+ {
+ noteDuration = clamp(numberValue, 1, 64);
+ }
+ }
+ let noteLength = 240 / bpm / noteDuration;
+ if (note[5] || note[7])
+ {
+ noteLength *= 1.5;
+ }
+ let noteOctave = octave;
+ //noteOctave = 1
+ if (note[6])
+ {
+ let numberValue = Math.floor(Number(note[6]));
+ if (numberValue && !Number.isNaN(numberValue))
+ {
+ noteOctave = clamp(numberValue, 0, 8);
+ }
+ }
+ let noteStr = note[4].toUpperCase();
+ //convert alternative notation
+ switch (noteStr)
+ {
+ case "BB":
+ noteStr = "A#";
+ break;
+ case "CB":
+ noteStr = "C";
+ break;
+ case "DB":
+ noteStr = "C#";
+ break;
+ case "EB":
+ noteStr = "D#";
+ break;
+ case "FB":
+ noteStr = "E";
+ break;
+ case "E#":
+ noteStr = "F";
+ break;
+ case "GB":
+ noteStr = "F#";
+ break;
+ case "AB":
+ noteStr = "G#";
+ break;
+ }
+ if (noteStr == "P")
+ {
+
+ //playback objects do not work as intended???
+ playbacks.push(preparePlaybackObject(muteSynth, 0, time));
+ //muteSynth(time)
+ time += noteLength;
+ } else
+ {
+
+ let noteFrequency = noteFreq[noteOctave][noteStr];
+ if (noteFrequency)
+ {
+ let paws = ["paw-left", "paw-right"];
+ let rnd = 0 + (Math.random() > 0.95);
+ playbacks.push(preparePlaybackObject(setPaw, time * 1000, paws[rnd], bpm * (noteDuration / 4)));
+ //playSynthSound(noteFrequency, time)
+ playbacks.push(preparePlaybackObject(playSynthSound, 0, noteFrequency, time));
+ time += noteLength / 10 * 8;
+ //muteSynth(time)
+ playbacks.push(preparePlaybackObject(muteSynth, 0, time));
+ time += noteLength / 10 * 2;
+ }
+ }
+ }
+ }
+
+ playbacks.push(preparePlaybackObject(outroAnimation, time * 1000));
+ prepareSynth("square");
+ return playbacks;
+}
+experimentalFeatures["rtttl"] = rtttl;
+experimentalFeatures["rttl"] = rtttl;
+
+export {extensionFeatures, experimentalFeatures};
diff --git a/src/experimental/midi.js b/src/experimental/midi.js
new file mode 100644
index 0000000..d11f8dd
--- /dev/null
+++ b/src/experimental/midi.js
@@ -0,0 +1,142 @@
+let midiAccess = null;
+let midiOutput = null;
+
+function getMidiChannel(key) {
+
+ if (key === undefined || key === null) { return "0"; }
+ const s = String(key).toLowerCase().trim();
+ // enthält Ziffern 1-7 -> Kanal 1
+ if (/[1-7]/.test(s)) { return "0"; }
+
+ // enthält q, w, e, r, t, z oder u -> Kanal 2
+ if (/[qwertyu]/.test(s)) { return "1"; }
+
+ // enthält xcbn -> Kanal 10
+ if (/[asdfgxcbn]/.test(s)) { return "9"; }
+
+ // sonst Kanal 2
+ return "2"; s
+}
+
+function getMidiNote(Key) {
+ if (Key === undefined || Key === null) { return null; }
+ const s = String(Key).trim();
+ let halftone = 0;
+
+ // finde erste Ziffer 1-7
+ const m = s.match(/[1-7]/);
+ if (m) {
+ const map = { '1': '60', '2': '62', '3': '64', '4': '65', '5': '67', '6': '69', '7': '71' };
+ if (s.match(/#/)) {
+ halftone = 1;
+ }
+ if (s.match(/V/)) {
+ return Number(map[m[0]]) - 12 + halftone;
+ }
+ if (s.match(/\^/)) {
+ return Number(map[m[0]]) + 12 + halftone;
+ }
+ return map[m[0]];
+ }
+ // find first letter in QWERTZU
+ const n = s.match(/[QWERTYU]/);
+ if (n) {
+ const map = { 'Q': '60', 'W': '62', 'E': '64', 'R': '65', 'T': '67', 'Y': '69', 'U': '71' };
+ if (s.match(/#/)) {
+ halftone = 1;
+ }
+ if (s.match(/V/)) {
+ return Number(map[n[0]]) - 12 + halftone;
+ }
+ if (s.match(/\^/)) {
+ return Number(map[n[0]]) + 12 + halftone;
+ }
+ return map[n[0]];
+ }
+ // find first letter in XCBN
+ const b = s.match(/[ASDFGXCBN]/);
+ if (b) {
+ const map = { 'A': '50', 'S': '45', 'D': '56', 'F': '49', 'G': '54', 'X': '36', 'C': '40', 'B': '42', 'N': '46' };
+ return map[b[0]];
+ }
+ return null;
+}
+
+function initMidi() {
+ if (!navigator.requestMIDIAccess) {
+ console.warn("Web MIDI API wird von diesem Browser nicht unterstützt.");
+ return;
+ }
+ if (midiAccess) { return; }
+
+ navigator.requestMIDIAccess({ sysex: false })
+ .then(access => {
+ midiAccess = access;
+ updateMidiOutput();
+ midiAccess.onstatechange = () => {
+ updateMidiOutput();
+ };
+ })
+ .catch(err => {
+ console.warn("Fehler beim Initialisieren von WebMIDI:", err);
+ });
+}
+
+function updateMidiOutput() {
+ if (!midiAccess) { return; }
+ const outputs = Array.from(midiAccess.outputs.values());
+ if (outputs.length > 0) {
+ midiOutput = outputs[0];
+ console.log("MIDI output selected:", midiOutput.name || midiOutput.id);
+ }
+ else {
+ midiOutput = null;
+ console.log("Kein MIDI-Output verfügbar.");
+ }
+}
+
+/**
+ * Spielt eine MIDI-Note über Web MIDI.
+ * note kann Zahl oder string sein; channel optional (default 0).
+ * Anmerkung: NoteOff wird ~750ms nach NoteOn gesendet (wie vorher).
+ */
+function playMidi(note)
+{
+
+ const n = Number(getMidiNote(note));
+ const ch = Number(getMidiChannel(note));
+ //const ch = (typeof channel === "number" && !isNaN(channel)) ? (channel & 0x0f) : 0;
+ if (isNaN(n)) {
+ return;
+ }
+
+ if (!midiAccess) {
+ initMidi();
+ }
+
+ if (!midiOutput) {
+ // Falls noch kein Output bekannt ist, versuchen wir kurz später erneut
+ // (initialisierung asynchron). Keine Fehlerausgabe jedes Mal, nur einmal.
+ console.warn("Kein MIDI-Output bereit, versuche Initialisierung.");
+ return;
+ }
+
+ const now = window.performance.now();
+ const noteOn = 0x90 | ch;
+ const noteOff = 0x80 | ch;
+ const velocity = 127;
+
+ try {
+ // send(message, timestamp) - timestamp in DOMHighResTimeStamp (ms)
+ midiOutput.send([noteOn, n & 0x7f, velocity], now);
+ midiOutput.send([noteOff, n & 0x7f, 0], now + 750); // 0.75s wie vorher
+ console.log(`MIDI NoteOn gesendet: Note ${n}, Kanal ${ch}`);
+
+ }
+
+ catch (err) {
+ console.warn("Fehler beim Senden von MIDI-Nachrichten:", err);
+ }
+}
+
+export {playMidi};
\ No newline at end of file
diff --git a/src/images/808.png b/src/images/808.png
new file mode 100644
index 0000000..29c1eb6
Binary files /dev/null and b/src/images/808.png differ
diff --git a/src/images/NOKIA3210.png b/src/images/NOKIA3210.png
new file mode 100644
index 0000000..e9c7430
Binary files /dev/null and b/src/images/NOKIA3210.png differ
diff --git a/images/bongo.png b/src/images/bongo.png
similarity index 100%
rename from images/bongo.png
rename to src/images/bongo.png
diff --git a/src/images/cat.png b/src/images/cat.png
new file mode 100644
index 0000000..3489089
Binary files /dev/null and b/src/images/cat.png differ
diff --git a/images/cowbell.png b/src/images/cowbell.png
similarity index 100%
rename from images/cowbell.png
rename to src/images/cowbell.png
diff --git a/images/cymbal.png b/src/images/cymbal.png
similarity index 100%
rename from images/cymbal.png
rename to src/images/cymbal.png
diff --git a/images/keyboard.png b/src/images/keyboard.png
similarity index 100%
rename from images/keyboard.png
rename to src/images/keyboard.png
diff --git a/images/marimba.png b/src/images/marimba.png
similarity index 100%
rename from images/marimba.png
rename to src/images/marimba.png
diff --git a/images/mouth.png b/src/images/mouth.png
similarity index 100%
rename from images/mouth.png
rename to src/images/mouth.png
diff --git a/src/images/paw-left.png b/src/images/paw-left.png
new file mode 100644
index 0000000..b191c29
Binary files /dev/null and b/src/images/paw-left.png differ
diff --git a/src/images/paw-right.png b/src/images/paw-right.png
new file mode 100644
index 0000000..c3d73dd
Binary files /dev/null and b/src/images/paw-right.png differ
diff --git a/images/tambourine.png b/src/images/tambourine.png
similarity index 100%
rename from images/tambourine.png
rename to src/images/tambourine.png
diff --git a/src/index.html b/src/index.html
new file mode 100644
index 0000000..4df33ec
--- /dev/null
+++ b/src/index.html
@@ -0,0 +1,173 @@
+
+
+
+
+
+ JS Drum Kit
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/indexold.html b/src/indexold.html
new file mode 100644
index 0000000..80e4df8
--- /dev/null
+++ b/src/indexold.html
@@ -0,0 +1,452 @@
+
+
+
+
+
+ JS Drum Kit
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/modules/bongo.js b/src/modules/bongo.js
new file mode 100644
index 0000000..0cf1591
--- /dev/null
+++ b/src/modules/bongo.js
@@ -0,0 +1,163 @@
+
+
+/**
+ * Parses a song and returns an array of commands
+ * @date 5/7/2023 - 2:22:07 PM
+ *
+ * @param {Song} song
+ *
+ * @returns {Playback[]}
+ */
+function parseSong(song) {
+ console.log(song)
+ return addNotes(song.notes, song.notation == "bongol" || song.notation == "legacy", song.performer)
+}
+
+function addNotes(noteString, isLegacyNotation, username) {
+ console.log("legacy:", isLegacyNotation)
+ let result = []
+
+ noteString = noteString.toUpperCase().replaceAll("Z", "Y")
+
+ var noteBatches = noteString.split(" ");
+ var thisNote;
+ var thisTimer = 1000;
+ var uBPM;
+ uBPM = getBPM(username);
+
+ for (var noteBatch in noteBatches) {
+ var notes = parseNotes(noteBatches[noteBatch]).slice(0, maxNotesPerBatch);
+ if (notes.length > 0 && notes[0] == ",") {
+ let sBpm = "";
+ for (var i = 1; i <= notes.length; i++) {
+ if (isNaN(notes[i])) {
+ break;
+ }
+ sBpm += notes[i];
+ }
+ sBpm = sBpm.substring(0, 3);
+ setBPM(Number(sBpm), username);
+ uBPM = getBPM(username);
+ continue
+ }
+
+ console.log(`result notes: ${notes}`);
+
+ thisTimer += uBPM;
+ for (var noteIdx in notes) {
+ thisNote = notes[noteIdx];
+
+ if (isLegacyNotation)
+ thisNote += "L";
+
+ //setTimeout(playSound, thisTimer, thisNote);
+ result.push(preparePlaybackObject(playSound, thisTimer, thisNote, uBPM))
+ }
+ }
+ thisTimer += uBPM;
+ //setTimeout(outroAnimation, thisTimer);
+ result.push(preparePlaybackObject(outroAnimation, thisTimer))
+ return result
+}
+
+
+function parseNotes(noteBatch)
+{
+ const fsmError = 0;
+ const fsmStart = 1;
+ const fsmPitch = 2;
+ const fsmNote = 3;
+ const fsmSharp = 4;
+
+ var resultNotes = [];
+ var thisNote;
+ var state = fsmStart;
+
+ for (var i = 0; i < noteBatch.length; i++)
+ {
+ var curChar = noteBatch[i];
+
+ switch (state)
+ {
+ case fsmError: {
+ console.log("fsmError");
+
+ break;
+ }
+
+ case fsmStart: {
+ console.log("fsmStart");
+
+ thisNote = curChar;
+
+ if (curChar == "^" || curChar == "V")
+ state = fsmPitch;
+ else
+ state = fsmNote;
+ break;
+ }
+
+ case fsmPitch: {
+ console.log("fsmPitch");
+
+ if (curChar == "^" || curChar == "V" || curChar == "#")
+ state = fsmError;
+ else
+ {
+ thisNote += curChar;
+ state = fsmNote;
+ }
+
+ break;
+ }
+
+ case fsmNote: {
+ console.log("fsmNote");
+
+ if (curChar == "#")
+ {
+ thisNote += curChar;
+ state = fsmSharp;
+ }
+ else
+ {
+ resultNotes.push(thisNote);
+ state = fsmStart;
+ i--;
+ }
+
+ break;
+ }
+
+ case fsmSharp: {
+ console.log("fsmSharp");
+
+ if (curChar == "#")
+ state = fsmError;
+ else
+ {
+ resultNotes.push(thisNote);
+ state = fsmStart;
+ i--;
+ }
+
+ break;
+ }
+ }
+ }
+
+ // epsilon:
+ switch (state)
+ {
+ case fsmNote:
+ case fsmSharp: {
+ console.log("epsilon");
+ resultNotes.push(thisNote);
+ break;
+ }
+ }
+
+ return resultNotes;
+}
+
+export {parseSong};
\ No newline at end of file
diff --git a/src/sounds/bongocat/BD.mp3 b/src/sounds/bongocat/BD.mp3
new file mode 100644
index 0000000..12eed69
Binary files /dev/null and b/src/sounds/bongocat/BD.mp3 differ
diff --git a/src/sounds/bongocat/CH.mp3 b/src/sounds/bongocat/CH.mp3
new file mode 100644
index 0000000..572cc72
Binary files /dev/null and b/src/sounds/bongocat/CH.mp3 differ
diff --git a/src/sounds/bongocat/OH.mp3 b/src/sounds/bongocat/OH.mp3
new file mode 100644
index 0000000..7e8dfbe
Binary files /dev/null and b/src/sounds/bongocat/OH.mp3 differ
diff --git a/src/sounds/bongocat/SD.mp3 b/src/sounds/bongocat/SD.mp3
new file mode 100644
index 0000000..0638da6
Binary files /dev/null and b/src/sounds/bongocat/SD.mp3 differ
diff --git a/sounds/bongo0.mp3 b/src/sounds/bongocat/bongo0.mp3
similarity index 100%
rename from sounds/bongo0.mp3
rename to src/sounds/bongocat/bongo0.mp3
diff --git a/sounds/bongo1.mp3 b/src/sounds/bongocat/bongo1.mp3
similarity index 100%
rename from sounds/bongo1.mp3
rename to src/sounds/bongocat/bongo1.mp3
diff --git a/src/sounds/bongocat/cowbell.mp3 b/src/sounds/bongocat/cowbell.mp3
new file mode 100644
index 0000000..86cb936
Binary files /dev/null and b/src/sounds/bongocat/cowbell.mp3 differ
diff --git a/sounds/cymbal.mp3 b/src/sounds/bongocat/cymbal.mp3
similarity index 100%
rename from sounds/cymbal.mp3
rename to src/sounds/bongocat/cymbal.mp3
diff --git a/src/sounds/bongocat/keyboard/a3.mp3 b/src/sounds/bongocat/keyboard/a3.mp3
new file mode 100644
index 0000000..7c5a0c8
Binary files /dev/null and b/src/sounds/bongocat/keyboard/a3.mp3 differ
diff --git a/src/sounds/bongocat/keyboard/a3s.mp3 b/src/sounds/bongocat/keyboard/a3s.mp3
new file mode 100644
index 0000000..2e328db
Binary files /dev/null and b/src/sounds/bongocat/keyboard/a3s.mp3 differ
diff --git a/sounds/keyboard0.mp3 b/src/sounds/bongocat/keyboard/a4.mp3
similarity index 100%
rename from sounds/keyboard0.mp3
rename to src/sounds/bongocat/keyboard/a4.mp3
diff --git a/src/sounds/bongocat/keyboard/a4s.mp3 b/src/sounds/bongocat/keyboard/a4s.mp3
new file mode 100644
index 0000000..483677d
Binary files /dev/null and b/src/sounds/bongocat/keyboard/a4s.mp3 differ
diff --git a/src/sounds/bongocat/keyboard/a5.mp3 b/src/sounds/bongocat/keyboard/a5.mp3
new file mode 100644
index 0000000..7b39b2a
Binary files /dev/null and b/src/sounds/bongocat/keyboard/a5.mp3 differ
diff --git a/src/sounds/bongocat/keyboard/a5s.mp3 b/src/sounds/bongocat/keyboard/a5s.mp3
new file mode 100644
index 0000000..406b2fa
Binary files /dev/null and b/src/sounds/bongocat/keyboard/a5s.mp3 differ
diff --git a/src/sounds/bongocat/keyboard/b3.mp3 b/src/sounds/bongocat/keyboard/b3.mp3
new file mode 100644
index 0000000..8c3d063
Binary files /dev/null and b/src/sounds/bongocat/keyboard/b3.mp3 differ
diff --git a/src/sounds/bongocat/keyboard/b4.mp3 b/src/sounds/bongocat/keyboard/b4.mp3
new file mode 100644
index 0000000..022bcd5
Binary files /dev/null and b/src/sounds/bongocat/keyboard/b4.mp3 differ
diff --git a/src/sounds/bongocat/keyboard/b5.mp3 b/src/sounds/bongocat/keyboard/b5.mp3
new file mode 100644
index 0000000..f10afa5
Binary files /dev/null and b/src/sounds/bongocat/keyboard/b5.mp3 differ
diff --git a/src/sounds/bongocat/keyboard/c3.mp3 b/src/sounds/bongocat/keyboard/c3.mp3
new file mode 100644
index 0000000..8eddf08
Binary files /dev/null and b/src/sounds/bongocat/keyboard/c3.mp3 differ
diff --git a/src/sounds/bongocat/keyboard/c3s.mp3 b/src/sounds/bongocat/keyboard/c3s.mp3
new file mode 100644
index 0000000..a310b23
Binary files /dev/null and b/src/sounds/bongocat/keyboard/c3s.mp3 differ
diff --git a/sounds/keyboard1.mp3 b/src/sounds/bongocat/keyboard/c4.mp3
similarity index 100%
rename from sounds/keyboard1.mp3
rename to src/sounds/bongocat/keyboard/c4.mp3
diff --git a/sounds/keyboard2.mp3 b/src/sounds/bongocat/keyboard/c4s.mp3
similarity index 100%
rename from sounds/keyboard2.mp3
rename to src/sounds/bongocat/keyboard/c4s.mp3
diff --git a/src/sounds/bongocat/keyboard/c5.mp3 b/src/sounds/bongocat/keyboard/c5.mp3
new file mode 100644
index 0000000..87b32a1
Binary files /dev/null and b/src/sounds/bongocat/keyboard/c5.mp3 differ
diff --git a/src/sounds/bongocat/keyboard/c5s.mp3 b/src/sounds/bongocat/keyboard/c5s.mp3
new file mode 100644
index 0000000..55bf1ff
Binary files /dev/null and b/src/sounds/bongocat/keyboard/c5s.mp3 differ
diff --git a/src/sounds/bongocat/keyboard/d3.mp3 b/src/sounds/bongocat/keyboard/d3.mp3
new file mode 100644
index 0000000..907fd54
Binary files /dev/null and b/src/sounds/bongocat/keyboard/d3.mp3 differ
diff --git a/src/sounds/bongocat/keyboard/d3s.mp3 b/src/sounds/bongocat/keyboard/d3s.mp3
new file mode 100644
index 0000000..a447c52
Binary files /dev/null and b/src/sounds/bongocat/keyboard/d3s.mp3 differ
diff --git a/sounds/keyboard3.mp3 b/src/sounds/bongocat/keyboard/d4.mp3
similarity index 100%
rename from sounds/keyboard3.mp3
rename to src/sounds/bongocat/keyboard/d4.mp3
diff --git a/sounds/keyboard4.mp3 b/src/sounds/bongocat/keyboard/d4s.mp3
similarity index 100%
rename from sounds/keyboard4.mp3
rename to src/sounds/bongocat/keyboard/d4s.mp3
diff --git a/src/sounds/bongocat/keyboard/d5.mp3 b/src/sounds/bongocat/keyboard/d5.mp3
new file mode 100644
index 0000000..e526427
Binary files /dev/null and b/src/sounds/bongocat/keyboard/d5.mp3 differ
diff --git a/src/sounds/bongocat/keyboard/d5s.mp3 b/src/sounds/bongocat/keyboard/d5s.mp3
new file mode 100644
index 0000000..ec2b79c
Binary files /dev/null and b/src/sounds/bongocat/keyboard/d5s.mp3 differ
diff --git a/src/sounds/bongocat/keyboard/e3.mp3 b/src/sounds/bongocat/keyboard/e3.mp3
new file mode 100644
index 0000000..33c551c
Binary files /dev/null and b/src/sounds/bongocat/keyboard/e3.mp3 differ
diff --git a/sounds/keyboard5.mp3 b/src/sounds/bongocat/keyboard/e4.mp3
similarity index 100%
rename from sounds/keyboard5.mp3
rename to src/sounds/bongocat/keyboard/e4.mp3
diff --git a/src/sounds/bongocat/keyboard/e5.mp3 b/src/sounds/bongocat/keyboard/e5.mp3
new file mode 100644
index 0000000..5cc7de6
Binary files /dev/null and b/src/sounds/bongocat/keyboard/e5.mp3 differ
diff --git a/src/sounds/bongocat/keyboard/f3.mp3 b/src/sounds/bongocat/keyboard/f3.mp3
new file mode 100644
index 0000000..3a06c48
Binary files /dev/null and b/src/sounds/bongocat/keyboard/f3.mp3 differ
diff --git a/src/sounds/bongocat/keyboard/f3s.mp3 b/src/sounds/bongocat/keyboard/f3s.mp3
new file mode 100644
index 0000000..ca2a2c9
Binary files /dev/null and b/src/sounds/bongocat/keyboard/f3s.mp3 differ
diff --git a/sounds/keyboard6.mp3 b/src/sounds/bongocat/keyboard/f4.mp3
similarity index 100%
rename from sounds/keyboard6.mp3
rename to src/sounds/bongocat/keyboard/f4.mp3
diff --git a/sounds/keyboard7.mp3 b/src/sounds/bongocat/keyboard/f4s.mp3
similarity index 100%
rename from sounds/keyboard7.mp3
rename to src/sounds/bongocat/keyboard/f4s.mp3
diff --git a/src/sounds/bongocat/keyboard/f5.mp3 b/src/sounds/bongocat/keyboard/f5.mp3
new file mode 100644
index 0000000..b4d9a58
Binary files /dev/null and b/src/sounds/bongocat/keyboard/f5.mp3 differ
diff --git a/src/sounds/bongocat/keyboard/f5s.mp3 b/src/sounds/bongocat/keyboard/f5s.mp3
new file mode 100644
index 0000000..1c69f95
Binary files /dev/null and b/src/sounds/bongocat/keyboard/f5s.mp3 differ
diff --git a/src/sounds/bongocat/keyboard/g3.mp3 b/src/sounds/bongocat/keyboard/g3.mp3
new file mode 100644
index 0000000..cfd5662
Binary files /dev/null and b/src/sounds/bongocat/keyboard/g3.mp3 differ
diff --git a/src/sounds/bongocat/keyboard/g3s.mp3 b/src/sounds/bongocat/keyboard/g3s.mp3
new file mode 100644
index 0000000..41d4475
Binary files /dev/null and b/src/sounds/bongocat/keyboard/g3s.mp3 differ
diff --git a/sounds/keyboard8.mp3 b/src/sounds/bongocat/keyboard/g4.mp3
similarity index 100%
rename from sounds/keyboard8.mp3
rename to src/sounds/bongocat/keyboard/g4.mp3
diff --git a/sounds/keyboard9.mp3 b/src/sounds/bongocat/keyboard/g4s.mp3
similarity index 100%
rename from sounds/keyboard9.mp3
rename to src/sounds/bongocat/keyboard/g4s.mp3
diff --git a/src/sounds/bongocat/keyboard/g5.mp3 b/src/sounds/bongocat/keyboard/g5.mp3
new file mode 100644
index 0000000..241bb42
Binary files /dev/null and b/src/sounds/bongocat/keyboard/g5.mp3 differ
diff --git a/src/sounds/bongocat/keyboard/g5s.mp3 b/src/sounds/bongocat/keyboard/g5s.mp3
new file mode 100644
index 0000000..563d519
Binary files /dev/null and b/src/sounds/bongocat/keyboard/g5s.mp3 differ
diff --git a/src/sounds/bongocat/marimba/a3.mp3 b/src/sounds/bongocat/marimba/a3.mp3
new file mode 100644
index 0000000..fddf676
Binary files /dev/null and b/src/sounds/bongocat/marimba/a3.mp3 differ
diff --git a/src/sounds/bongocat/marimba/a3s.mp3 b/src/sounds/bongocat/marimba/a3s.mp3
new file mode 100644
index 0000000..827b3de
Binary files /dev/null and b/src/sounds/bongocat/marimba/a3s.mp3 differ
diff --git a/sounds/marimba0.mp3 b/src/sounds/bongocat/marimba/a4.mp3
similarity index 100%
rename from sounds/marimba0.mp3
rename to src/sounds/bongocat/marimba/a4.mp3
diff --git a/src/sounds/bongocat/marimba/a4s.mp3 b/src/sounds/bongocat/marimba/a4s.mp3
new file mode 100644
index 0000000..103fa5f
Binary files /dev/null and b/src/sounds/bongocat/marimba/a4s.mp3 differ
diff --git a/src/sounds/bongocat/marimba/a5.mp3 b/src/sounds/bongocat/marimba/a5.mp3
new file mode 100644
index 0000000..5bbc724
Binary files /dev/null and b/src/sounds/bongocat/marimba/a5.mp3 differ
diff --git a/src/sounds/bongocat/marimba/a5s.mp3 b/src/sounds/bongocat/marimba/a5s.mp3
new file mode 100644
index 0000000..2df661c
Binary files /dev/null and b/src/sounds/bongocat/marimba/a5s.mp3 differ
diff --git a/src/sounds/bongocat/marimba/b3.mp3 b/src/sounds/bongocat/marimba/b3.mp3
new file mode 100644
index 0000000..ef2aae9
Binary files /dev/null and b/src/sounds/bongocat/marimba/b3.mp3 differ
diff --git a/src/sounds/bongocat/marimba/b4.mp3 b/src/sounds/bongocat/marimba/b4.mp3
new file mode 100644
index 0000000..8138c37
Binary files /dev/null and b/src/sounds/bongocat/marimba/b4.mp3 differ
diff --git a/src/sounds/bongocat/marimba/b5.mp3 b/src/sounds/bongocat/marimba/b5.mp3
new file mode 100644
index 0000000..1ba3da5
Binary files /dev/null and b/src/sounds/bongocat/marimba/b5.mp3 differ
diff --git a/src/sounds/bongocat/marimba/c3.mp3 b/src/sounds/bongocat/marimba/c3.mp3
new file mode 100644
index 0000000..4c80c48
Binary files /dev/null and b/src/sounds/bongocat/marimba/c3.mp3 differ
diff --git a/src/sounds/bongocat/marimba/c3s.mp3 b/src/sounds/bongocat/marimba/c3s.mp3
new file mode 100644
index 0000000..dd35fc1
Binary files /dev/null and b/src/sounds/bongocat/marimba/c3s.mp3 differ
diff --git a/sounds/marimba1.mp3 b/src/sounds/bongocat/marimba/c4.mp3
similarity index 100%
rename from sounds/marimba1.mp3
rename to src/sounds/bongocat/marimba/c4.mp3
diff --git a/sounds/marimba2.mp3 b/src/sounds/bongocat/marimba/c4s.mp3
similarity index 100%
rename from sounds/marimba2.mp3
rename to src/sounds/bongocat/marimba/c4s.mp3
diff --git a/src/sounds/bongocat/marimba/c5.mp3 b/src/sounds/bongocat/marimba/c5.mp3
new file mode 100644
index 0000000..dda2c98
Binary files /dev/null and b/src/sounds/bongocat/marimba/c5.mp3 differ
diff --git a/src/sounds/bongocat/marimba/c5s.mp3 b/src/sounds/bongocat/marimba/c5s.mp3
new file mode 100644
index 0000000..8bd520e
Binary files /dev/null and b/src/sounds/bongocat/marimba/c5s.mp3 differ
diff --git a/src/sounds/bongocat/marimba/d3.mp3 b/src/sounds/bongocat/marimba/d3.mp3
new file mode 100644
index 0000000..bb56d3f
Binary files /dev/null and b/src/sounds/bongocat/marimba/d3.mp3 differ
diff --git a/src/sounds/bongocat/marimba/d3s.mp3 b/src/sounds/bongocat/marimba/d3s.mp3
new file mode 100644
index 0000000..1c42afc
Binary files /dev/null and b/src/sounds/bongocat/marimba/d3s.mp3 differ
diff --git a/sounds/marimba3.mp3 b/src/sounds/bongocat/marimba/d4.mp3
similarity index 100%
rename from sounds/marimba3.mp3
rename to src/sounds/bongocat/marimba/d4.mp3
diff --git a/sounds/marimba4.mp3 b/src/sounds/bongocat/marimba/d4s.mp3
similarity index 100%
rename from sounds/marimba4.mp3
rename to src/sounds/bongocat/marimba/d4s.mp3
diff --git a/src/sounds/bongocat/marimba/d5.mp3 b/src/sounds/bongocat/marimba/d5.mp3
new file mode 100644
index 0000000..f4bf05e
Binary files /dev/null and b/src/sounds/bongocat/marimba/d5.mp3 differ
diff --git a/src/sounds/bongocat/marimba/d5s.mp3 b/src/sounds/bongocat/marimba/d5s.mp3
new file mode 100644
index 0000000..47be927
Binary files /dev/null and b/src/sounds/bongocat/marimba/d5s.mp3 differ
diff --git a/src/sounds/bongocat/marimba/e3.mp3 b/src/sounds/bongocat/marimba/e3.mp3
new file mode 100644
index 0000000..3eefc23
Binary files /dev/null and b/src/sounds/bongocat/marimba/e3.mp3 differ
diff --git a/sounds/marimba5.mp3 b/src/sounds/bongocat/marimba/e4.mp3
similarity index 100%
rename from sounds/marimba5.mp3
rename to src/sounds/bongocat/marimba/e4.mp3
diff --git a/src/sounds/bongocat/marimba/e5.mp3 b/src/sounds/bongocat/marimba/e5.mp3
new file mode 100644
index 0000000..13f4a57
Binary files /dev/null and b/src/sounds/bongocat/marimba/e5.mp3 differ
diff --git a/src/sounds/bongocat/marimba/f3.mp3 b/src/sounds/bongocat/marimba/f3.mp3
new file mode 100644
index 0000000..46c36a2
Binary files /dev/null and b/src/sounds/bongocat/marimba/f3.mp3 differ
diff --git a/src/sounds/bongocat/marimba/f3s.mp3 b/src/sounds/bongocat/marimba/f3s.mp3
new file mode 100644
index 0000000..787bf12
Binary files /dev/null and b/src/sounds/bongocat/marimba/f3s.mp3 differ
diff --git a/sounds/marimba6.mp3 b/src/sounds/bongocat/marimba/f4.mp3
similarity index 100%
rename from sounds/marimba6.mp3
rename to src/sounds/bongocat/marimba/f4.mp3
diff --git a/sounds/marimba7.mp3 b/src/sounds/bongocat/marimba/f4s.mp3
similarity index 100%
rename from sounds/marimba7.mp3
rename to src/sounds/bongocat/marimba/f4s.mp3
diff --git a/src/sounds/bongocat/marimba/f5.mp3 b/src/sounds/bongocat/marimba/f5.mp3
new file mode 100644
index 0000000..07cce7f
Binary files /dev/null and b/src/sounds/bongocat/marimba/f5.mp3 differ
diff --git a/src/sounds/bongocat/marimba/f5s.mp3 b/src/sounds/bongocat/marimba/f5s.mp3
new file mode 100644
index 0000000..5418cce
Binary files /dev/null and b/src/sounds/bongocat/marimba/f5s.mp3 differ
diff --git a/src/sounds/bongocat/marimba/g3.mp3 b/src/sounds/bongocat/marimba/g3.mp3
new file mode 100644
index 0000000..825a5a8
Binary files /dev/null and b/src/sounds/bongocat/marimba/g3.mp3 differ
diff --git a/src/sounds/bongocat/marimba/g3s.mp3 b/src/sounds/bongocat/marimba/g3s.mp3
new file mode 100644
index 0000000..b4c0629
Binary files /dev/null and b/src/sounds/bongocat/marimba/g3s.mp3 differ
diff --git a/sounds/marimba8.mp3 b/src/sounds/bongocat/marimba/g4.mp3
similarity index 100%
rename from sounds/marimba8.mp3
rename to src/sounds/bongocat/marimba/g4.mp3
diff --git a/sounds/marimba9.mp3 b/src/sounds/bongocat/marimba/g4s.mp3
similarity index 100%
rename from sounds/marimba9.mp3
rename to src/sounds/bongocat/marimba/g4s.mp3
diff --git a/src/sounds/bongocat/marimba/g5.mp3 b/src/sounds/bongocat/marimba/g5.mp3
new file mode 100644
index 0000000..e279c29
Binary files /dev/null and b/src/sounds/bongocat/marimba/g5.mp3 differ
diff --git a/src/sounds/bongocat/marimba/g5s.mp3 b/src/sounds/bongocat/marimba/g5s.mp3
new file mode 100644
index 0000000..bc41c20
Binary files /dev/null and b/src/sounds/bongocat/marimba/g5s.mp3 differ
diff --git a/sounds/meow.mp3 b/src/sounds/bongocat/meow.mp3
similarity index 100%
rename from sounds/meow.mp3
rename to src/sounds/bongocat/meow.mp3
diff --git a/sounds/tambourine.mp3 b/src/sounds/bongocat/tambourine.mp3
similarity index 100%
rename from sounds/tambourine.mp3
rename to src/sounds/bongocat/tambourine.mp3
diff --git a/style/style.css b/style/style.css
deleted file mode 100644
index 9dd2273..0000000
--- a/style/style.css
+++ /dev/null
@@ -1,358 +0,0 @@
-@keyframes wave {
- 0%, 100% {
- transform: rotate(0)
- }
-
- 20%, 60% {
- transform: rotate(-25deg)
- }
-
- 40%, 80% {
- transform: rotate(10deg)
- }
-}
-
-html, body {
- overflow-x: hidden;
- margin: 0
-}
-
-body {
- position: relative;
- height: 100%;
- width: 100%;
- background-color: #fff;
- -moz-user-select: none;
- -khtml-user-select: none;
- -webkit-user-select: none;
- user-select: none;
- font-family: 'Open Sans', sans-serif;
- font-size: 14px
-}
-
-header, footer {
- position: absolute;
- text-align: center;
- width: calc(100% - 20px);
- padding: 10px;
- z-index: 500
-}
-
-header a, footer a {
- color: #000;
- text-decoration: none
-}
-
-footer {
- bottom: 0
-}
-
-footer span {
- margin: 0 7px
-}
-
-hr {
- position: relative;
- display: block;
- width: 200%;
- height: 5px;
- background: #000;
- margin: 140px 0 0 -50%;
- border: none;
- transform: rotate(13.5deg);
- z-index: 10
-}
-
-a#github {
- z-index: 1000
-}
-
-svg#github {
- fill: #000;
- color: #fff;
- position: absolute;
- top: 0;
- border: 0;
- right: 0;
- z-index: 1000
-}
-
-svg#github:hover .octo-arm {
- animation: wave 560ms ease-in-out
-}
-
-select#select-instrument {
- position: relative;
- width: 240px;
- height: 60px;
- margin: 5px 2px;
- padding: 0 15px;
- font-size: 30px;
- color: inherit;
- text-align-last: center;
- background: rgba(0, 0, 0, 0.2);
- box-shadow: inset 0 0 0 4px rgba(0, 0, 0, 0.1);
- border: 0;
- border-radius: 5px;
- transition: all .2s ease-out;
- outline: none;
- -webkit-appearance: none;
- -moz-appearance: none;
- appearance: none;
- z-index: 1000
-}
-
-select#select-instrument option {
- color: inherit;
- background: #fff;
- border: 0;
- outline: none
-}
-
-#container {
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- width: 800px;
- height: 450px
-}
-
-#container div {
- position: absolute;
- display: inline-block;
- top: 0;
- height: 100%;
- width: 100%;
- background-repeat: no-repeat
-}
-
-#touch {
- display: none
-}
-
-#keys>div {
- display: inline-block;
- padding: 0 8px
-}
-
-#keys>div span {
- display: block;
- margin: 0 4px 8px
-}
-
-#taps, #layers {
- position: absolute;
- width: 100%;
- font-size: 30px;
- text-align: center;
- color: #fff;
- z-index: 1000
-}
-
-#layers {
- top: 0
-}
-
-#taps {
- bottom: 0;
- margin-bottom: 50px
-}
-
-#taps .tap {
- position: relative;
- display: inline-block;
- width: 60px;
- height: 60px;
- margin: 5px 2px
-}
-
-#tap-left, #tap-right {
- width: calc(2.5 * 60px + 18px) !important;
- height: calc(2 * 60px + 10px) !important
-}
-
-#tap-space {
- width: calc(300px + 45px) !important;
- height: calc(2 * 60px + 10px) !important
-}
-
-.highlight {
- background: rgba(119, 158, 203, 0.6) !important;
- transition: none !important;
- transform: scale(0.95);
- transform-origin: 50% 50%
-}
-
-.key {
- display: inline-block !important;
- min-width: 35px;
- height: 35px;
- padding: 4px 0 0 6px;
- background: #f5f5f5;
- border: 2px solid #b3b3b3;
- border-radius: 6px;
- box-shadow: inset -6px -4px 0 0 #ccc;
- text-align: left;
- font-size: 15px
-}
-
-.tap {
- background: rgba(0, 0, 0, 0.2);
- box-shadow: inset 0 0 0 4px rgba(0, 0, 0, 0.1);
- width: 60px;
- height: 135px;
- border-radius: 5px;
- text-align: center;
- transition: all .2s ease-out;
- z-index: 1000
-}
-
-.tap span {
- display: block;
- position: relative;
- font-size: 30px;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- color: #fff;
- text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.2);
- pointer-events: none
-}
-
-.cat div {
- background-position-y: 0;
- background-position-x: 0
-}
-
-.cat>#head {
- background-image: url(../images/cat.png);
- z-index: 10
-}
-
-.cat>#mouth {
- background-image: url(../images/mouth.png);
- z-index: 20
-}
-
-.cat>#paw-left {
- background-image: url(../images/paw-left.png);
- z-index: 30
-}
-
-.cat>#paw-right {
- background-image: url(../images/paw-right.png);
- z-index: 30
-}
-
-.instruments {
- z-index: 20
-}
-
-.instruments>#bongo, #layer-bongo {
- background-image: url(../images/bongo.png)
-}
-
-.instruments>#keyboard, #layer-keyboard {
- background-image: url(../images/keyboard.png)
-}
-
-.instruments>#cymbal, #layer-cymbal {
- background-image: url(../images/cymbal.png)
-}
-
-.instruments>#marimba, #layer-marimba {
- background-image: url(../images/marimba.png)
-}
-
-.instruments>#tambourine, #layer-tambourine {
- background-image: url(../images/tambourine.png)
-}
-
-.instruments>#cowbell, #layer-cowbell {
- background-image: url(../images/cowbell.png)
-}
-
-.instruments>#keyboard, .instruments>#cymbal, .instruments>#marimba, .instruments>#tambourine, .instruments>#cowbell {
- visibility: hidden
-}
-
-@media (prefers-color-scheme: dark) {
- body {
- background-color: #000 !important;
- color: #fff !important
- }
-
- header a, footer a {
- color: #fff !important
- }
-
- hr {
- background: #fff !important
- }
-
- svg#github {
- fill: #fff !important;
- color: #000 !important
- }
-
- .key {
- background: #333;
- border: 2px solid #252525;
- box-shadow: inset -6px -4px 0 0 #1b1b1b
- }
-
- select#select-instrument, .tap {
- background: rgba(255, 255, 255, 0.2);
- box-shadow: inset 0 0 0 4px rgba(255, 255, 255, 0.1)
- }
-
- select#select-instrument option {
- background: #000
- }
-
- .tap span {
- text-shadow: none
- }
-
- .cat div {
- background-position-y: -450px
- }
-
- .cc-window {
- border: 1px solid rgba(255, 255, 255, 0.2) !important
- }
-}
-
-@media (prefers-reduced-motion: reduce) {
- svg#github:hover .octo-arm {
- animation: none !important
- }
-
- select#select-instrument, .tap {
- transition: none !important
- }
-
- .highlight {
- transform: scale(1) !important
- }
-}
-
-@media only screen and (max-width: 769px) {
- body {
- font-size: 13px
- }
-
- header, a#github {
- visibility: hidden
- }
-
- #touch {
- display: block
- }
-
- #container {
- transform: scale(0.6, 0.6) translate(-50%, -50%);
- transform-origin: top left
- }
-}
\ No newline at end of file