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 - A - D -
-
- Cymbal - C -
-
- Cowbell - F -
-
- Tambourine - B -
-
- Meow - SPACE -
-
- Piano - 0 - 1 ... - 9 -
-
- Marimba - Q - W ... - P -
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
LEFT
-
RIGHT
-
MEOW
-
-
-
1
-
2
-
3
-
4
-
5
-
-
-
6
-
7
-
8
-
9
-
0
-
-
-
-
- - - - \ 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