fluidstudio

Programmable music notation — write music as text, compile to MIDI + WAV stems + mix
Log | Files | Refs | README

commit 0f032fbdbf9a3b875521a6c4ffa968a2abdfa76e
Author: ed <ed@brz9.dev>
Date:   Sun,  3 May 2026 16:31:51 +0200

Initial commit: FluidStudio notation tools + soundfonts

- tools/ package: parser, midi_gen, compiler, clips, fps, video, chromatic_check
- asset/soundfonts/ with INDEX.md catalog
- .flsp notation format (chromatic semitone system)
- README with full install + usage docs

Diffstat:
A.gitignore | 8++++++++
AREADME.md | 197+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aasset/soundfonts/INDEX.md | 104+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aasset/soundfonts/drums/minipops.md | 15+++++++++++++++
Aasset/soundfonts/drums/minipops.sf2 | 0
Aasset/soundfonts/misc/P5_Bass.sf2 | 0
Aasset/soundfonts/misc/P5_Brass.sf2 | 0
Aasset/soundfonts/misc/P5_Piper.sf2 | 0
Aasset/soundfonts/misc/P5_Strings.sf2 | 0
Aasset/soundfonts/misc/P5_Tablura.sf2 | 0
Aasset/soundfonts/misc/PPG_Buzzy.sf2 | 0
Aasset/soundfonts/misc/PPG_Heavy.sf2 | 0
Aasset/soundfonts/misc/PPG_Merge.sf2 | 0
Aasset/soundfonts/misc/PPG_Shapes.sf2 | 0
Aasset/soundfonts/misc/Steinway.sf2 | 0
Aasset/soundfonts/misc/slap_bass.sf2 | 0
Aasset/soundfonts/misc/steel_drum.sf2 | 0
Aasset/soundfonts/misc/strings_cello.SF2 | 0
Aasset/soundfonts/misc/strings_dark_violas.sf2 | 0
Aasset/soundfonts/misc/strings_dark_violins.sf2 | 0
Aasset/soundfonts/misc/strings_pizzicato.sf2 | 0
Aasset/soundfonts/misc/strings_staccato.sf2 | 0
Aasset/soundfonts/misc/strings_tremolo.sf2 | 0
Aasset/soundfonts/misc/tape_hiss.sf2 | 0
Aasset/soundfonts/misc/tenor_sax.sf2 | 0
Aasset/soundfonts/misc/upright_bass.sf2 | 0
Aasset/soundfonts/rkhive/classhit/classhit.sf2 | 0
Aasset/soundfonts/rkhive/classhit/rkhive.txt | 48++++++++++++++++++++++++++++++++++++++++++++++++
Aasset/soundfonts/rkhive/cryptorg/cryptorg.sf2 | 0
Aasset/soundfonts/rkhive/cryptorg/rkhive.txt | 48++++++++++++++++++++++++++++++++++++++++++++++++
Aasset/soundfonts/rkhive/deadnois/deadnois.sf2 | 0
Aasset/soundfonts/rkhive/deadnois/rkhive.txt | 48++++++++++++++++++++++++++++++++++++++++++++++++
Aasset/soundfonts/rkhive/evilhiss/evilhiss.sf2 | 0
Aasset/soundfonts/rkhive/evilhiss/rkhive.txt | 48++++++++++++++++++++++++++++++++++++++++++++++++
Aasset/soundfonts/rkhive/ghosdrum/ghosdrum.sf2 | 0
Aasset/soundfonts/rkhive/ghosdrum/rkhive.txt | 48++++++++++++++++++++++++++++++++++++++++++++++++
Aasset/soundfonts/rkhive/jrhodes3a-looped/jrhodes3a-looped.sf2 | 0
Aasset/soundfonts/rkhive/jrhodes3a-looped/rkhive.txt | 48++++++++++++++++++++++++++++++++++++++++++++++++
Aasset/soundfonts/rkhive/kelflute/kelflute.sf2 | 0
Aasset/soundfonts/rkhive/kelflute/rkhive.txt | 48++++++++++++++++++++++++++++++++++++++++++++++++
Aasset/soundfonts/rkhive/klezclar/klezclar.sf2 | 0
Aasset/soundfonts/rkhive/klezclar/rkhive.txt | 48++++++++++++++++++++++++++++++++++++++++++++++++
Aasset/soundfonts/rkhive/mchndown/mchndown.sf2 | 0
Aasset/soundfonts/rkhive/mchndown/rkhive.txt | 48++++++++++++++++++++++++++++++++++++++++++++++++
Aasset/soundfonts/rkhive/mchnup/mchnup.sf2 | 0
Aasset/soundfonts/rkhive/mchnup/rkhive.txt | 48++++++++++++++++++++++++++++++++++++++++++++++++
Aasset/soundfonts/rkhive/monk_choir/monk_choir.sf2 | 0
Aasset/soundfonts/rkhive/monk_choir/rkhive.txt | 48++++++++++++++++++++++++++++++++++++++++++++++++
Aasset/soundfonts/rkhive/phanthit/phanthit.sf2 | 0
Aasset/soundfonts/rkhive/phanthit/rkhive.txt | 48++++++++++++++++++++++++++++++++++++++++++++++++
Aasset/soundfonts/rkhive/r-brass/r-brass.sf2 | 0
Aasset/soundfonts/rkhive/r-brass/rkhive.txt | 48++++++++++++++++++++++++++++++++++++++++++++++++
Aasset/soundfonts/rkhive/r-marmba/r-marmba.sf2 | 0
Aasset/soundfonts/rkhive/r-marmba/rkhive.txt | 48++++++++++++++++++++++++++++++++++++++++++++++++
Aasset/soundfonts/rkhive/r-timp/r-timp.sf2 | 0
Aasset/soundfonts/rkhive/r-timp/rkhive.txt | 48++++++++++++++++++++++++++++++++++++++++++++++++
Aasset/soundfonts/rkhive/radio_spike/Radio Spike.SF2 | 0
Aasset/soundfonts/rkhive/radio_spike/rkhive.txt | 48++++++++++++++++++++++++++++++++++++++++++++++++
Aasset/soundfonts/rkhive/robohit/rkhive.txt | 48++++++++++++++++++++++++++++++++++++++++++++++++
Aasset/soundfonts/rkhive/robohit/robohit.sf2 | 0
Aasset/soundfonts/rkhive/sitar/rkhive.txt | 48++++++++++++++++++++++++++++++++++++++++++++++++
Aasset/soundfonts/rkhive/sitar/sitar.sf2 | 0
Aasset/soundfonts/rkhive/thxslide/rkhive.txt | 48++++++++++++++++++++++++++++++++++++++++++++++++
Aasset/soundfonts/rkhive/thxslide/thxslide.sf2 | 0
Aasset/soundfonts/rkhive/vapour/rkhive.txt | 48++++++++++++++++++++++++++++++++++++++++++++++++
Aasset/soundfonts/rkhive/vapour/vapour.sf2 | 0
Aasset/soundfonts/rkhive/water/rkhive.txt | 48++++++++++++++++++++++++++++++++++++++++++++++++
Aasset/soundfonts/rkhive/water/water.sf2 | 0
Aasset/soundfonts/rkhive/whistsolo/rkhive.txt | 48++++++++++++++++++++++++++++++++++++++++++++++++
Aasset/soundfonts/rkhive/whistsolo/whistsolo.sf2 | 0
Aasset/soundfonts/rkhive/worshchr/rkhive.txt | 48++++++++++++++++++++++++++++++++++++++++++++++++
Aasset/soundfonts/rkhive/worshchr/worshchr.sf2 | 0
Aasset/soundfonts/rkhive/zonehit/rkhive.txt | 48++++++++++++++++++++++++++++++++++++++++++++++++
Aasset/soundfonts/rkhive/zonehit/zonehit.sf2 | 0
Arequirements.txt | 3+++
Atools/__init__.py | 22++++++++++++++++++++++
Atools/chromatic_check.py | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atools/clips.py | 207+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atools/compiler.py | 178+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atools/fps.py | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atools/midi_gen.py | 121+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atools/parser.py | 360+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atools/video.py | 208+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
83 files changed, 2690 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1,8 @@ +__pycache__/ +*.pyc +.venv/ +venv/ +*.swp +*.swo +*~ +.DS_Store diff --git a/README.md b/README.md @@ -0,0 +1,197 @@ +# FluidStudio + +Programmable music notation — write music as text, compile to MIDI + WAV stems + mix. + +## Quick Start + +```bash +git clone <repo-url> fluidstudio +cd fluidstudio + +# Install system dependencies (Debian/Ubuntu) +sudo apt install fluidsynth ffmpeg + +# Create virtual environment and install Python deps +python3 -m venv .venv +source .venv/bin/activate +pip install -r requirements.txt + +# Compile a project +python -m tools.compiler projects/my_song +``` + +## Requirements + +**System packages** (Debian/Ubuntu): +- `fluidsynth` — MIDI to WAV rendering (needs a SoundFont) +- `ffmpeg` — stem mixing, audio trimming + +```bash +sudo apt install fluidsynth ffmpeg +``` + +**Python packages** (in `requirements.txt`): +- `mido` — MIDI file generation +- `numpy` — audio processing +- `Pillow` — video frame generation + +## Project Structure + +``` +fluidstudio/ +├── asset/ +│ └── soundfonts/ # SF2 soundfont files +│ ├── misc/ +│ └── rkhive/ +├── tools/ # Python tools (package) +│ ├── __init__.py +│ ├── parser.py # .flsp notation parser +│ ├── midi_gen.py # MIDI generation + FluidSynth rendering +│ ├── compiler.py # Project compiler (partition → stems → mix) +│ ├── clips.py # Per-frame CSV for video sync +│ ├── fps.py # BPM to FPS calculator +│ ├── video.py # Video generation from clips +│ └── chromatic_check.py # Chord validation utility +├── projects/ # Your songs go here +├── requirements.txt +└── README.md +``` + +## Creating a Project + +Each project lives in `projects/<song_name>/` with this structure: + +``` +projects/<song_name>/ +├── partition/ +│ ├── header.json # BPM, octave, mode, channels +│ └── <voice>.flsp # One file per stem (filename = channel name) +├── stems/ # Compiled WAV stems (generated) +├── mix/ # Mixed WAV (generated) +├── midi/ # Compiled MIDI files (generated) +└── clip/ # Per-frame CSVs for video sync (generated) +``` + +### Header Format (`partition/header.json`) + +```json +{ + "project": "my_song", + "bpm": 120, + "key": 0, + "octave": 4, + "mode": "ionian", + "channels": [ + {"name": "piano", "sf2": "rkhive/jrhodes3a-looped/jrhodes3a-looped.sf2", "octave": 5}, + {"name": "drums", "sf2": "rkhive/ghosdrum/ghosdrum.sf2", "octave": 1} + ] +} +``` + +- `key`: key root in semitones (0=C, 7=G, etc.). Combined with `mode`, suggests which chords fit well together. Since the notation is fully chromatic, any note works anywhere — this is a guide, not a restriction. +- `octave`: base octave (0 = C at MIDI 12). `octave: 4` = C4 = MIDI 60. +- `mode`: combined with `key`, determines which chords are "scale-pure". Use `chromatic_check.py` to list them. +- Per-channel `octave`: optional override. +- `sf2`: path relative to `asset/soundfonts/`. + +### Choosing Instruments + +Check `asset/soundfonts/INDEX.md` for the full catalog with notes on optimal ranges. Some SoundFonts only sound good in specific octaves (e.g. Classic Hit is best between G2–G3, Crypt Organ has chords on C0 and bass on C-2–B-1). Always verify the instrument range before assigning an octave. + +### Notation Format (`.flsp`) + +**Chromatic semitone system:** +``` +0=C 1=C# 2=D 3=D# 4=E 5=F 6=F# 7=G 8=G# 9=A 10=A# 11=B +``` + +**Rules:** +- `---` = beat divider (one section between dividers = one beat) +- Lines between dividers are subdivisions of that beat +- `- ` prefix for subdivision lines (stripped during parse) +- `//` for inline comments, `#` for full-line comments + +**Cell syntax:** `note[x][chord_type][:duration] [(v:velocity)] [(legato: on|off)]` +- `note` = chromatic semitone (0-11) +- `x` after note = play chord +- Duration: `b` = 1 beat, `b/2` = half beat, `b/3` = triplet, `2b` = 2 beats +- Velocity: `(v:NN)` — default 100 +- Legato: `(legato: on)` / `(legato: off)` — toggles portamento + +**Example (8ths subdivision):** +``` +# melody — octave 4 +- 0:b/2 (legato: on) // C, portamento on +- 4:b/2 // E +--- +- 7:b // G +--- +- 9:b/2 // A +- 11:b/2 // B +--- +- 0:b (legato: off) // C, portamento off +--- +- 0:b (v:60) // C, quiet +``` + +**Empty sections (rests):** +``` +- 0 // kick +--- +- // rest (1 beat) +--- +- 1 // snare +``` + +## Compiling + +```bash +# From project root +python -m tools.compiler projects/my_song + +# From Python +from tools.compiler import compile_project +compile_project("projects/my_song") +``` + +This generates: MIDI files → WAV stems (exact beat duration) → mixed WAV. + +## Video Sync + +```bash +# Generate per-frame CSVs +python -m tools.clips projects/my_song + +# Generate video from clips +python -m tools.video projects/my_song +``` + +## Chord Validation + +```bash +python tools/chromatic_check.py 7 misheberak +``` + +Lists all scale-pure chords per degree for a given root and mode. + +## Available Modes + +ionian, dorian, phrygian, lydian, mixolydian, aeolian, locrian, misheberak, freygish, hungarian_minor, hungarian_major, persian, whole_tone + +## Available Chord Types + +M, M6, M69, M7m, M7M, M9, Madd2, Madd9, m, m6, m7m, m7M, m9, m69, madd2, madd9, sus2, sus26, sus27, sus27M, sus4, sus46, sus47, sus47M, dimb3, dim, dim7, b5, aug, aug7, p4, TT, p5 + +## Design Philosophy + +**Simplification over special cases.** Chromatic semitones (0-11) with octave-based offset eliminates the need for a separate percussion channel type — drums use the same system with a different octave. + +**Modes as validation, not enforcement.** Modes define which chords are "scale-pure" for reference, but partitions can use any notes. + +**Unified systems.** All channels (melodic, chord, percussion) use the same notation and resolution logic. + +## Pitfalls + +- **Stem length mismatch:** `amix` with `duration=first` truncates to shortest input. All stems must be same length — loop patterns to match longest part. +- **Trim accuracy:** Use `ffmpeg -af "atrim=end=N"` (sample-accurate), not `-t` (codec-frame-accurate, ~ms off). +- **WAV for stitching:** MP3 adds padding (~0.05s). Keep stems as WAV until final delivery. diff --git a/asset/soundfonts/INDEX.md b/asset/soundfonts/INDEX.md @@ -0,0 +1,104 @@ +# Soundfont Index + +## Piano / Keys + +| Name | Path | Notes | +|------|------|-------| +| Steinway | ./misc/Steinway.sf2 | | +| jRhodes3a (looped) | ./rkhive/jrhodes3a-looped/jrhodes3a-looped.sf2 | | + +## Strings + +| Name | Path | Notes | +|------|------|-------| +| P5 Strings | ./misc/P5_Strings.sf2 | | +| Dark Violas | ./misc/strings_dark_violas.sf2 | | +| Dark Violins | ./misc/strings_dark_violins.sf2 | | +| Pizzicato | ./misc/strings_pizzicato.sf2 | | +| Staccato | ./misc/strings_staccato.sf2 | | +| Tremolo | ./misc/strings_tremolo.sf2 | | + +## Brass + +| Name | Path | Notes | +|------|------|-------| +| P5 Brass | ./misc/P5_Brass.sf2 | | +| R-Brass | ./rkhive/r-brass/r-brass.sf2 | | + +## Woodwind + +| Name | Path | Notes | +|------|------|-------| +| Tenor Sax | ./misc/tenor_sax.sf2 | | +| Kel Flute | ./rkhive/kelflute/kelflute.sf2 | | +| Klez Clarinet | ./rkhive/klezclar/klezclar.sf2 | | +| Whistle Solo | ./rkhive/whistsolo/whistsolo.sf2 | | +| P5 Piper | ./misc/P5_Piper.sf2 | | + +## Bass + +| Name | Path | Notes | +|------|------|-------| +| P5 Bass | ./misc/P5_Bass.sf2 | | +| Slap Bass | ./misc/slap_bass.sf2 | | +| Upright Bass | ./misc/upright_bass.sf2 | | +| Mech Down | ./rkhive/mchndown/mchndown.sf2 | | +| Mech Up | ./rkhive/mchnup/mchnup.sf2 | | + +## Percussion / Drums + +| Name | Path | Notes | +|------|------|-------| +| Steel Drum | ./misc/steel_drum.sf2 | | +| Ghost Drum | ./rkhive/ghosdrum/ghosdrum.sf2 | | +| R-Marimba | ./rkhive/r-marmba/r-marmba.sf2 | | +| R-Timpani | ./rkhive/r-timp/r-timp.sf2 | | + +## Hits / Impacts + +| Name | Path | Notes | +|------|------|-------| +| Classic Hit | ./rkhive/classhit/classhit.sf2 | orchestra hit, optimal between g2 and g3 | +| Phantom Hit | ./rkhive/phanthit/phanthit.sf2 | | +| Robot Hit | ./rkhive/robohit/robohit.sf2 | | +| Zone Hit | ./rkhive/zonehit/zonehit.sf2 | | + +## Organ + +| Name | Path | Notes | +|------|------|-------| +| Crypt Organ | ./rkhive/cryptorg/cryptorg.sf2 | chords on c0 and bass between c-2 and b-1 | +| Worship Choir | ./rkhive/worshchr/worshchr.sf2 | | + +## Choir / Voice + +| Name | Path | Notes | +|------|------|-------| +| Monk Choir | ./rkhive/monk_choir/monk_choir.sf2 | | + +## Synth / Electronic + +| Name | Path | Notes | +|------|------|-------| +| PPG Buzzy | ./misc/PPG_Buzzy.sf2 | | +| PPG Heavy | ./misc/PPG_Heavy.sf2 | | +| PPG Merge | ./misc/PPG_Merge.sf2 | | +| PPG Shapes | ./misc/PPG_Shapes.sf2 | | +| Vapour | ./rkhive/vapour/vapour.sf2 | | + +## FX / Noise + +| Name | Path | Notes | +|------|------|-------| +| Tape Hiss | ./misc/tape_hiss.sf2 | | +| Dead Noise | ./rkhive/deadnois/deadnois.sf2 | | +| Evil Hiss | ./rkhive/evilhiss/evilhiss.sf2 | | +| THX Slide | ./rkhive/thxslide/thxslide.sf2 | | +| Water | ./rkhive/water/water.sf2 | | + +## World / Ethnic + +| Name | Path | Notes | +|------|------|-------| +| P5 Tablura | ./misc/P5_Tablura.sf2 | | +| Sitar | ./rkhive/sitar/sitar.sf2 | | diff --git a/asset/soundfonts/drums/minipops.md b/asset/soundfonts/drums/minipops.md @@ -0,0 +1,15 @@ +# minipops sample map + +Starts at C0 (MIDI note 24). + +| Order | Instrument | +|---:|---| +| 0 | Kick | +| 1 | Snare | +| 2 | Clap | +| 3 | Tom Medium | +| 4 | Tom High | +| 5 | Wood Block | +| 6 | Wood Block Alt | +| 7 | Hihat Closed | +| 8 | Hihat Open | diff --git a/asset/soundfonts/drums/minipops.sf2 b/asset/soundfonts/drums/minipops.sf2 Binary files differ. diff --git a/asset/soundfonts/misc/P5_Bass.sf2 b/asset/soundfonts/misc/P5_Bass.sf2 Binary files differ. diff --git a/asset/soundfonts/misc/P5_Brass.sf2 b/asset/soundfonts/misc/P5_Brass.sf2 Binary files differ. diff --git a/asset/soundfonts/misc/P5_Piper.sf2 b/asset/soundfonts/misc/P5_Piper.sf2 Binary files differ. diff --git a/asset/soundfonts/misc/P5_Strings.sf2 b/asset/soundfonts/misc/P5_Strings.sf2 Binary files differ. diff --git a/asset/soundfonts/misc/P5_Tablura.sf2 b/asset/soundfonts/misc/P5_Tablura.sf2 Binary files differ. diff --git a/asset/soundfonts/misc/PPG_Buzzy.sf2 b/asset/soundfonts/misc/PPG_Buzzy.sf2 Binary files differ. diff --git a/asset/soundfonts/misc/PPG_Heavy.sf2 b/asset/soundfonts/misc/PPG_Heavy.sf2 Binary files differ. diff --git a/asset/soundfonts/misc/PPG_Merge.sf2 b/asset/soundfonts/misc/PPG_Merge.sf2 Binary files differ. diff --git a/asset/soundfonts/misc/PPG_Shapes.sf2 b/asset/soundfonts/misc/PPG_Shapes.sf2 Binary files differ. diff --git a/asset/soundfonts/misc/Steinway.sf2 b/asset/soundfonts/misc/Steinway.sf2 Binary files differ. diff --git a/asset/soundfonts/misc/slap_bass.sf2 b/asset/soundfonts/misc/slap_bass.sf2 Binary files differ. diff --git a/asset/soundfonts/misc/steel_drum.sf2 b/asset/soundfonts/misc/steel_drum.sf2 Binary files differ. diff --git a/asset/soundfonts/misc/strings_cello.SF2 b/asset/soundfonts/misc/strings_cello.SF2 Binary files differ. diff --git a/asset/soundfonts/misc/strings_dark_violas.sf2 b/asset/soundfonts/misc/strings_dark_violas.sf2 Binary files differ. diff --git a/asset/soundfonts/misc/strings_dark_violins.sf2 b/asset/soundfonts/misc/strings_dark_violins.sf2 Binary files differ. diff --git a/asset/soundfonts/misc/strings_pizzicato.sf2 b/asset/soundfonts/misc/strings_pizzicato.sf2 Binary files differ. diff --git a/asset/soundfonts/misc/strings_staccato.sf2 b/asset/soundfonts/misc/strings_staccato.sf2 Binary files differ. diff --git a/asset/soundfonts/misc/strings_tremolo.sf2 b/asset/soundfonts/misc/strings_tremolo.sf2 Binary files differ. diff --git a/asset/soundfonts/misc/tape_hiss.sf2 b/asset/soundfonts/misc/tape_hiss.sf2 Binary files differ. diff --git a/asset/soundfonts/misc/tenor_sax.sf2 b/asset/soundfonts/misc/tenor_sax.sf2 Binary files differ. diff --git a/asset/soundfonts/misc/upright_bass.sf2 b/asset/soundfonts/misc/upright_bass.sf2 Binary files differ. diff --git a/asset/soundfonts/rkhive/classhit/classhit.sf2 b/asset/soundfonts/rkhive/classhit/classhit.sf2 Binary files differ. diff --git a/asset/soundfonts/rkhive/classhit/rkhive.txt b/asset/soundfonts/rkhive/classhit/rkhive.txt @@ -0,0 +1,47 @@ +''.....................'' +: this sample came from : +''.....................'' +: : +: /=/|=| |=| /=/ : +: /=/ |=| |=| /=/ : +: \=\ |=| |=| /=/ : +: /=/|=| |=| \=\ : +: /=/ |=| |=| \=\ : +: /=/ |=| |=| \=\ : +: : +: H I V E : +: : +''.....................'' +: http://rkhive.com : +''.....................'' + +CC0 1.0 Universal (CC0 1.0) Public Domain Dedication +https://creativecommons.org/publicdomain/zero/1.0/ + +The person who associated a work with this deed has dedicated the work to the +public domain by waiving all of his or her rights to the work worldwide under +copyright law, including all related and neighboring rights, to the extent +allowed by law. + +You can copy, modify, distribute and perform the work, even for commercial +purposes, all without asking permission. + +In no way are the patent or trademark rights of any person affected by CC0, nor +are the rights that other persons may have in the work or in how the work is +used, such as publicity or privacy rights. + +Unless expressly stated otherwise, the person who associated a work with this +deed makes no warranties about the work, and disclaims liability for all uses of +the work, to the fullest extent permitted by applicable law. + +When using or citing the work, you should not imply endorsement by the author or +the affirmer. + + +This is a summary of the full CC0 Legal Code: +English https://creativecommons.org/publicdomain/zero/1.0/legalcode +français https://creativecommons.org/publicdomain/zero/1.0/legalcode.fr +Nederlands https://creativecommons.org/publicdomain/zero/1.0/legalcode.nl +polski https://creativecommons.org/publicdomain/zero/1.0/legalcode.pl +suomeksi https://creativecommons.org/publicdomain/zero/1.0/legalcode.fi +日本語 https://creativecommons.org/publicdomain/zero/1.0/legalcode.ja+ \ No newline at end of file diff --git a/asset/soundfonts/rkhive/cryptorg/cryptorg.sf2 b/asset/soundfonts/rkhive/cryptorg/cryptorg.sf2 Binary files differ. diff --git a/asset/soundfonts/rkhive/cryptorg/rkhive.txt b/asset/soundfonts/rkhive/cryptorg/rkhive.txt @@ -0,0 +1,47 @@ +''.....................'' +: this sample came from : +''.....................'' +: : +: /=/|=| |=| /=/ : +: /=/ |=| |=| /=/ : +: \=\ |=| |=| /=/ : +: /=/|=| |=| \=\ : +: /=/ |=| |=| \=\ : +: /=/ |=| |=| \=\ : +: : +: H I V E : +: : +''.....................'' +: http://rkhive.com : +''.....................'' + +CC0 1.0 Universal (CC0 1.0) Public Domain Dedication +https://creativecommons.org/publicdomain/zero/1.0/ + +The person who associated a work with this deed has dedicated the work to the +public domain by waiving all of his or her rights to the work worldwide under +copyright law, including all related and neighboring rights, to the extent +allowed by law. + +You can copy, modify, distribute and perform the work, even for commercial +purposes, all without asking permission. + +In no way are the patent or trademark rights of any person affected by CC0, nor +are the rights that other persons may have in the work or in how the work is +used, such as publicity or privacy rights. + +Unless expressly stated otherwise, the person who associated a work with this +deed makes no warranties about the work, and disclaims liability for all uses of +the work, to the fullest extent permitted by applicable law. + +When using or citing the work, you should not imply endorsement by the author or +the affirmer. + + +This is a summary of the full CC0 Legal Code: +English https://creativecommons.org/publicdomain/zero/1.0/legalcode +français https://creativecommons.org/publicdomain/zero/1.0/legalcode.fr +Nederlands https://creativecommons.org/publicdomain/zero/1.0/legalcode.nl +polski https://creativecommons.org/publicdomain/zero/1.0/legalcode.pl +suomeksi https://creativecommons.org/publicdomain/zero/1.0/legalcode.fi +日本語 https://creativecommons.org/publicdomain/zero/1.0/legalcode.ja+ \ No newline at end of file diff --git a/asset/soundfonts/rkhive/deadnois/deadnois.sf2 b/asset/soundfonts/rkhive/deadnois/deadnois.sf2 Binary files differ. diff --git a/asset/soundfonts/rkhive/deadnois/rkhive.txt b/asset/soundfonts/rkhive/deadnois/rkhive.txt @@ -0,0 +1,47 @@ +''.....................'' +: this sample came from : +''.....................'' +: : +: /=/|=| |=| /=/ : +: /=/ |=| |=| /=/ : +: \=\ |=| |=| /=/ : +: /=/|=| |=| \=\ : +: /=/ |=| |=| \=\ : +: /=/ |=| |=| \=\ : +: : +: H I V E : +: : +''.....................'' +: http://rkhive.com : +''.....................'' + +CC0 1.0 Universal (CC0 1.0) Public Domain Dedication +https://creativecommons.org/publicdomain/zero/1.0/ + +The person who associated a work with this deed has dedicated the work to the +public domain by waiving all of his or her rights to the work worldwide under +copyright law, including all related and neighboring rights, to the extent +allowed by law. + +You can copy, modify, distribute and perform the work, even for commercial +purposes, all without asking permission. + +In no way are the patent or trademark rights of any person affected by CC0, nor +are the rights that other persons may have in the work or in how the work is +used, such as publicity or privacy rights. + +Unless expressly stated otherwise, the person who associated a work with this +deed makes no warranties about the work, and disclaims liability for all uses of +the work, to the fullest extent permitted by applicable law. + +When using or citing the work, you should not imply endorsement by the author or +the affirmer. + + +This is a summary of the full CC0 Legal Code: +English https://creativecommons.org/publicdomain/zero/1.0/legalcode +français https://creativecommons.org/publicdomain/zero/1.0/legalcode.fr +Nederlands https://creativecommons.org/publicdomain/zero/1.0/legalcode.nl +polski https://creativecommons.org/publicdomain/zero/1.0/legalcode.pl +suomeksi https://creativecommons.org/publicdomain/zero/1.0/legalcode.fi +日本語 https://creativecommons.org/publicdomain/zero/1.0/legalcode.ja+ \ No newline at end of file diff --git a/asset/soundfonts/rkhive/evilhiss/evilhiss.sf2 b/asset/soundfonts/rkhive/evilhiss/evilhiss.sf2 Binary files differ. diff --git a/asset/soundfonts/rkhive/evilhiss/rkhive.txt b/asset/soundfonts/rkhive/evilhiss/rkhive.txt @@ -0,0 +1,47 @@ +''.....................'' +: this sample came from : +''.....................'' +: : +: /=/|=| |=| /=/ : +: /=/ |=| |=| /=/ : +: \=\ |=| |=| /=/ : +: /=/|=| |=| \=\ : +: /=/ |=| |=| \=\ : +: /=/ |=| |=| \=\ : +: : +: H I V E : +: : +''.....................'' +: http://rkhive.com : +''.....................'' + +CC0 1.0 Universal (CC0 1.0) Public Domain Dedication +https://creativecommons.org/publicdomain/zero/1.0/ + +The person who associated a work with this deed has dedicated the work to the +public domain by waiving all of his or her rights to the work worldwide under +copyright law, including all related and neighboring rights, to the extent +allowed by law. + +You can copy, modify, distribute and perform the work, even for commercial +purposes, all without asking permission. + +In no way are the patent or trademark rights of any person affected by CC0, nor +are the rights that other persons may have in the work or in how the work is +used, such as publicity or privacy rights. + +Unless expressly stated otherwise, the person who associated a work with this +deed makes no warranties about the work, and disclaims liability for all uses of +the work, to the fullest extent permitted by applicable law. + +When using or citing the work, you should not imply endorsement by the author or +the affirmer. + + +This is a summary of the full CC0 Legal Code: +English https://creativecommons.org/publicdomain/zero/1.0/legalcode +français https://creativecommons.org/publicdomain/zero/1.0/legalcode.fr +Nederlands https://creativecommons.org/publicdomain/zero/1.0/legalcode.nl +polski https://creativecommons.org/publicdomain/zero/1.0/legalcode.pl +suomeksi https://creativecommons.org/publicdomain/zero/1.0/legalcode.fi +日本語 https://creativecommons.org/publicdomain/zero/1.0/legalcode.ja+ \ No newline at end of file diff --git a/asset/soundfonts/rkhive/ghosdrum/ghosdrum.sf2 b/asset/soundfonts/rkhive/ghosdrum/ghosdrum.sf2 Binary files differ. diff --git a/asset/soundfonts/rkhive/ghosdrum/rkhive.txt b/asset/soundfonts/rkhive/ghosdrum/rkhive.txt @@ -0,0 +1,47 @@ +''.....................'' +: this sample came from : +''.....................'' +: : +: /=/|=| |=| /=/ : +: /=/ |=| |=| /=/ : +: \=\ |=| |=| /=/ : +: /=/|=| |=| \=\ : +: /=/ |=| |=| \=\ : +: /=/ |=| |=| \=\ : +: : +: H I V E : +: : +''.....................'' +: http://rkhive.com : +''.....................'' + +CC0 1.0 Universal (CC0 1.0) Public Domain Dedication +https://creativecommons.org/publicdomain/zero/1.0/ + +The person who associated a work with this deed has dedicated the work to the +public domain by waiving all of his or her rights to the work worldwide under +copyright law, including all related and neighboring rights, to the extent +allowed by law. + +You can copy, modify, distribute and perform the work, even for commercial +purposes, all without asking permission. + +In no way are the patent or trademark rights of any person affected by CC0, nor +are the rights that other persons may have in the work or in how the work is +used, such as publicity or privacy rights. + +Unless expressly stated otherwise, the person who associated a work with this +deed makes no warranties about the work, and disclaims liability for all uses of +the work, to the fullest extent permitted by applicable law. + +When using or citing the work, you should not imply endorsement by the author or +the affirmer. + + +This is a summary of the full CC0 Legal Code: +English https://creativecommons.org/publicdomain/zero/1.0/legalcode +français https://creativecommons.org/publicdomain/zero/1.0/legalcode.fr +Nederlands https://creativecommons.org/publicdomain/zero/1.0/legalcode.nl +polski https://creativecommons.org/publicdomain/zero/1.0/legalcode.pl +suomeksi https://creativecommons.org/publicdomain/zero/1.0/legalcode.fi +日本語 https://creativecommons.org/publicdomain/zero/1.0/legalcode.ja+ \ No newline at end of file diff --git a/asset/soundfonts/rkhive/jrhodes3a-looped/jrhodes3a-looped.sf2 b/asset/soundfonts/rkhive/jrhodes3a-looped/jrhodes3a-looped.sf2 Binary files differ. diff --git a/asset/soundfonts/rkhive/jrhodes3a-looped/rkhive.txt b/asset/soundfonts/rkhive/jrhodes3a-looped/rkhive.txt @@ -0,0 +1,47 @@ +''.....................'' +: this sample came from : +''.....................'' +: : +: /=/|=| |=| /=/ : +: /=/ |=| |=| /=/ : +: \=\ |=| |=| /=/ : +: /=/|=| |=| \=\ : +: /=/ |=| |=| \=\ : +: /=/ |=| |=| \=\ : +: : +: H I V E : +: : +''.....................'' +: http://rkhive.com : +''.....................'' + +CC0 1.0 Universal (CC0 1.0) Public Domain Dedication +https://creativecommons.org/publicdomain/zero/1.0/ + +The person who associated a work with this deed has dedicated the work to the +public domain by waiving all of his or her rights to the work worldwide under +copyright law, including all related and neighboring rights, to the extent +allowed by law. + +You can copy, modify, distribute and perform the work, even for commercial +purposes, all without asking permission. + +In no way are the patent or trademark rights of any person affected by CC0, nor +are the rights that other persons may have in the work or in how the work is +used, such as publicity or privacy rights. + +Unless expressly stated otherwise, the person who associated a work with this +deed makes no warranties about the work, and disclaims liability for all uses of +the work, to the fullest extent permitted by applicable law. + +When using or citing the work, you should not imply endorsement by the author or +the affirmer. + + +This is a summary of the full CC0 Legal Code: +English https://creativecommons.org/publicdomain/zero/1.0/legalcode +français https://creativecommons.org/publicdomain/zero/1.0/legalcode.fr +Nederlands https://creativecommons.org/publicdomain/zero/1.0/legalcode.nl +polski https://creativecommons.org/publicdomain/zero/1.0/legalcode.pl +suomeksi https://creativecommons.org/publicdomain/zero/1.0/legalcode.fi +日本語 https://creativecommons.org/publicdomain/zero/1.0/legalcode.ja+ \ No newline at end of file diff --git a/asset/soundfonts/rkhive/kelflute/kelflute.sf2 b/asset/soundfonts/rkhive/kelflute/kelflute.sf2 Binary files differ. diff --git a/asset/soundfonts/rkhive/kelflute/rkhive.txt b/asset/soundfonts/rkhive/kelflute/rkhive.txt @@ -0,0 +1,47 @@ +''.....................'' +: this sample came from : +''.....................'' +: : +: /=/|=| |=| /=/ : +: /=/ |=| |=| /=/ : +: \=\ |=| |=| /=/ : +: /=/|=| |=| \=\ : +: /=/ |=| |=| \=\ : +: /=/ |=| |=| \=\ : +: : +: H I V E : +: : +''.....................'' +: http://rkhive.com : +''.....................'' + +CC0 1.0 Universal (CC0 1.0) Public Domain Dedication +https://creativecommons.org/publicdomain/zero/1.0/ + +The person who associated a work with this deed has dedicated the work to the +public domain by waiving all of his or her rights to the work worldwide under +copyright law, including all related and neighboring rights, to the extent +allowed by law. + +You can copy, modify, distribute and perform the work, even for commercial +purposes, all without asking permission. + +In no way are the patent or trademark rights of any person affected by CC0, nor +are the rights that other persons may have in the work or in how the work is +used, such as publicity or privacy rights. + +Unless expressly stated otherwise, the person who associated a work with this +deed makes no warranties about the work, and disclaims liability for all uses of +the work, to the fullest extent permitted by applicable law. + +When using or citing the work, you should not imply endorsement by the author or +the affirmer. + + +This is a summary of the full CC0 Legal Code: +English https://creativecommons.org/publicdomain/zero/1.0/legalcode +français https://creativecommons.org/publicdomain/zero/1.0/legalcode.fr +Nederlands https://creativecommons.org/publicdomain/zero/1.0/legalcode.nl +polski https://creativecommons.org/publicdomain/zero/1.0/legalcode.pl +suomeksi https://creativecommons.org/publicdomain/zero/1.0/legalcode.fi +日本語 https://creativecommons.org/publicdomain/zero/1.0/legalcode.ja+ \ No newline at end of file diff --git a/asset/soundfonts/rkhive/klezclar/klezclar.sf2 b/asset/soundfonts/rkhive/klezclar/klezclar.sf2 Binary files differ. diff --git a/asset/soundfonts/rkhive/klezclar/rkhive.txt b/asset/soundfonts/rkhive/klezclar/rkhive.txt @@ -0,0 +1,47 @@ +''.....................'' +: this sample came from : +''.....................'' +: : +: /=/|=| |=| /=/ : +: /=/ |=| |=| /=/ : +: \=\ |=| |=| /=/ : +: /=/|=| |=| \=\ : +: /=/ |=| |=| \=\ : +: /=/ |=| |=| \=\ : +: : +: H I V E : +: : +''.....................'' +: http://rkhive.com : +''.....................'' + +CC0 1.0 Universal (CC0 1.0) Public Domain Dedication +https://creativecommons.org/publicdomain/zero/1.0/ + +The person who associated a work with this deed has dedicated the work to the +public domain by waiving all of his or her rights to the work worldwide under +copyright law, including all related and neighboring rights, to the extent +allowed by law. + +You can copy, modify, distribute and perform the work, even for commercial +purposes, all without asking permission. + +In no way are the patent or trademark rights of any person affected by CC0, nor +are the rights that other persons may have in the work or in how the work is +used, such as publicity or privacy rights. + +Unless expressly stated otherwise, the person who associated a work with this +deed makes no warranties about the work, and disclaims liability for all uses of +the work, to the fullest extent permitted by applicable law. + +When using or citing the work, you should not imply endorsement by the author or +the affirmer. + + +This is a summary of the full CC0 Legal Code: +English https://creativecommons.org/publicdomain/zero/1.0/legalcode +français https://creativecommons.org/publicdomain/zero/1.0/legalcode.fr +Nederlands https://creativecommons.org/publicdomain/zero/1.0/legalcode.nl +polski https://creativecommons.org/publicdomain/zero/1.0/legalcode.pl +suomeksi https://creativecommons.org/publicdomain/zero/1.0/legalcode.fi +日本語 https://creativecommons.org/publicdomain/zero/1.0/legalcode.ja+ \ No newline at end of file diff --git a/asset/soundfonts/rkhive/mchndown/mchndown.sf2 b/asset/soundfonts/rkhive/mchndown/mchndown.sf2 Binary files differ. diff --git a/asset/soundfonts/rkhive/mchndown/rkhive.txt b/asset/soundfonts/rkhive/mchndown/rkhive.txt @@ -0,0 +1,47 @@ +''.....................'' +: this sample came from : +''.....................'' +: : +: /=/|=| |=| /=/ : +: /=/ |=| |=| /=/ : +: \=\ |=| |=| /=/ : +: /=/|=| |=| \=\ : +: /=/ |=| |=| \=\ : +: /=/ |=| |=| \=\ : +: : +: H I V E : +: : +''.....................'' +: http://rkhive.com : +''.....................'' + +CC0 1.0 Universal (CC0 1.0) Public Domain Dedication +https://creativecommons.org/publicdomain/zero/1.0/ + +The person who associated a work with this deed has dedicated the work to the +public domain by waiving all of his or her rights to the work worldwide under +copyright law, including all related and neighboring rights, to the extent +allowed by law. + +You can copy, modify, distribute and perform the work, even for commercial +purposes, all without asking permission. + +In no way are the patent or trademark rights of any person affected by CC0, nor +are the rights that other persons may have in the work or in how the work is +used, such as publicity or privacy rights. + +Unless expressly stated otherwise, the person who associated a work with this +deed makes no warranties about the work, and disclaims liability for all uses of +the work, to the fullest extent permitted by applicable law. + +When using or citing the work, you should not imply endorsement by the author or +the affirmer. + + +This is a summary of the full CC0 Legal Code: +English https://creativecommons.org/publicdomain/zero/1.0/legalcode +français https://creativecommons.org/publicdomain/zero/1.0/legalcode.fr +Nederlands https://creativecommons.org/publicdomain/zero/1.0/legalcode.nl +polski https://creativecommons.org/publicdomain/zero/1.0/legalcode.pl +suomeksi https://creativecommons.org/publicdomain/zero/1.0/legalcode.fi +日本語 https://creativecommons.org/publicdomain/zero/1.0/legalcode.ja+ \ No newline at end of file diff --git a/asset/soundfonts/rkhive/mchnup/mchnup.sf2 b/asset/soundfonts/rkhive/mchnup/mchnup.sf2 Binary files differ. diff --git a/asset/soundfonts/rkhive/mchnup/rkhive.txt b/asset/soundfonts/rkhive/mchnup/rkhive.txt @@ -0,0 +1,47 @@ +''.....................'' +: this sample came from : +''.....................'' +: : +: /=/|=| |=| /=/ : +: /=/ |=| |=| /=/ : +: \=\ |=| |=| /=/ : +: /=/|=| |=| \=\ : +: /=/ |=| |=| \=\ : +: /=/ |=| |=| \=\ : +: : +: H I V E : +: : +''.....................'' +: http://rkhive.com : +''.....................'' + +CC0 1.0 Universal (CC0 1.0) Public Domain Dedication +https://creativecommons.org/publicdomain/zero/1.0/ + +The person who associated a work with this deed has dedicated the work to the +public domain by waiving all of his or her rights to the work worldwide under +copyright law, including all related and neighboring rights, to the extent +allowed by law. + +You can copy, modify, distribute and perform the work, even for commercial +purposes, all without asking permission. + +In no way are the patent or trademark rights of any person affected by CC0, nor +are the rights that other persons may have in the work or in how the work is +used, such as publicity or privacy rights. + +Unless expressly stated otherwise, the person who associated a work with this +deed makes no warranties about the work, and disclaims liability for all uses of +the work, to the fullest extent permitted by applicable law. + +When using or citing the work, you should not imply endorsement by the author or +the affirmer. + + +This is a summary of the full CC0 Legal Code: +English https://creativecommons.org/publicdomain/zero/1.0/legalcode +français https://creativecommons.org/publicdomain/zero/1.0/legalcode.fr +Nederlands https://creativecommons.org/publicdomain/zero/1.0/legalcode.nl +polski https://creativecommons.org/publicdomain/zero/1.0/legalcode.pl +suomeksi https://creativecommons.org/publicdomain/zero/1.0/legalcode.fi +日本語 https://creativecommons.org/publicdomain/zero/1.0/legalcode.ja+ \ No newline at end of file diff --git a/asset/soundfonts/rkhive/monk_choir/monk_choir.sf2 b/asset/soundfonts/rkhive/monk_choir/monk_choir.sf2 Binary files differ. diff --git a/asset/soundfonts/rkhive/monk_choir/rkhive.txt b/asset/soundfonts/rkhive/monk_choir/rkhive.txt @@ -0,0 +1,47 @@ +''.....................'' +: this sample came from : +''.....................'' +: : +: /=/|=| |=| /=/ : +: /=/ |=| |=| /=/ : +: \=\ |=| |=| /=/ : +: /=/|=| |=| \=\ : +: /=/ |=| |=| \=\ : +: /=/ |=| |=| \=\ : +: : +: H I V E : +: : +''.....................'' +: http://rkhive.com : +''.....................'' + +CC0 1.0 Universal (CC0 1.0) Public Domain Dedication +https://creativecommons.org/publicdomain/zero/1.0/ + +The person who associated a work with this deed has dedicated the work to the +public domain by waiving all of his or her rights to the work worldwide under +copyright law, including all related and neighboring rights, to the extent +allowed by law. + +You can copy, modify, distribute and perform the work, even for commercial +purposes, all without asking permission. + +In no way are the patent or trademark rights of any person affected by CC0, nor +are the rights that other persons may have in the work or in how the work is +used, such as publicity or privacy rights. + +Unless expressly stated otherwise, the person who associated a work with this +deed makes no warranties about the work, and disclaims liability for all uses of +the work, to the fullest extent permitted by applicable law. + +When using or citing the work, you should not imply endorsement by the author or +the affirmer. + + +This is a summary of the full CC0 Legal Code: +English https://creativecommons.org/publicdomain/zero/1.0/legalcode +français https://creativecommons.org/publicdomain/zero/1.0/legalcode.fr +Nederlands https://creativecommons.org/publicdomain/zero/1.0/legalcode.nl +polski https://creativecommons.org/publicdomain/zero/1.0/legalcode.pl +suomeksi https://creativecommons.org/publicdomain/zero/1.0/legalcode.fi +日本語 https://creativecommons.org/publicdomain/zero/1.0/legalcode.ja+ \ No newline at end of file diff --git a/asset/soundfonts/rkhive/phanthit/phanthit.sf2 b/asset/soundfonts/rkhive/phanthit/phanthit.sf2 Binary files differ. diff --git a/asset/soundfonts/rkhive/phanthit/rkhive.txt b/asset/soundfonts/rkhive/phanthit/rkhive.txt @@ -0,0 +1,47 @@ +''.....................'' +: this sample came from : +''.....................'' +: : +: /=/|=| |=| /=/ : +: /=/ |=| |=| /=/ : +: \=\ |=| |=| /=/ : +: /=/|=| |=| \=\ : +: /=/ |=| |=| \=\ : +: /=/ |=| |=| \=\ : +: : +: H I V E : +: : +''.....................'' +: http://rkhive.com : +''.....................'' + +CC0 1.0 Universal (CC0 1.0) Public Domain Dedication +https://creativecommons.org/publicdomain/zero/1.0/ + +The person who associated a work with this deed has dedicated the work to the +public domain by waiving all of his or her rights to the work worldwide under +copyright law, including all related and neighboring rights, to the extent +allowed by law. + +You can copy, modify, distribute and perform the work, even for commercial +purposes, all without asking permission. + +In no way are the patent or trademark rights of any person affected by CC0, nor +are the rights that other persons may have in the work or in how the work is +used, such as publicity or privacy rights. + +Unless expressly stated otherwise, the person who associated a work with this +deed makes no warranties about the work, and disclaims liability for all uses of +the work, to the fullest extent permitted by applicable law. + +When using or citing the work, you should not imply endorsement by the author or +the affirmer. + + +This is a summary of the full CC0 Legal Code: +English https://creativecommons.org/publicdomain/zero/1.0/legalcode +français https://creativecommons.org/publicdomain/zero/1.0/legalcode.fr +Nederlands https://creativecommons.org/publicdomain/zero/1.0/legalcode.nl +polski https://creativecommons.org/publicdomain/zero/1.0/legalcode.pl +suomeksi https://creativecommons.org/publicdomain/zero/1.0/legalcode.fi +日本語 https://creativecommons.org/publicdomain/zero/1.0/legalcode.ja+ \ No newline at end of file diff --git a/asset/soundfonts/rkhive/r-brass/r-brass.sf2 b/asset/soundfonts/rkhive/r-brass/r-brass.sf2 Binary files differ. diff --git a/asset/soundfonts/rkhive/r-brass/rkhive.txt b/asset/soundfonts/rkhive/r-brass/rkhive.txt @@ -0,0 +1,47 @@ +''.....................'' +: this sample came from : +''.....................'' +: : +: /=/|=| |=| /=/ : +: /=/ |=| |=| /=/ : +: \=\ |=| |=| /=/ : +: /=/|=| |=| \=\ : +: /=/ |=| |=| \=\ : +: /=/ |=| |=| \=\ : +: : +: H I V E : +: : +''.....................'' +: http://rkhive.com : +''.....................'' + +CC0 1.0 Universal (CC0 1.0) Public Domain Dedication +https://creativecommons.org/publicdomain/zero/1.0/ + +The person who associated a work with this deed has dedicated the work to the +public domain by waiving all of his or her rights to the work worldwide under +copyright law, including all related and neighboring rights, to the extent +allowed by law. + +You can copy, modify, distribute and perform the work, even for commercial +purposes, all without asking permission. + +In no way are the patent or trademark rights of any person affected by CC0, nor +are the rights that other persons may have in the work or in how the work is +used, such as publicity or privacy rights. + +Unless expressly stated otherwise, the person who associated a work with this +deed makes no warranties about the work, and disclaims liability for all uses of +the work, to the fullest extent permitted by applicable law. + +When using or citing the work, you should not imply endorsement by the author or +the affirmer. + + +This is a summary of the full CC0 Legal Code: +English https://creativecommons.org/publicdomain/zero/1.0/legalcode +français https://creativecommons.org/publicdomain/zero/1.0/legalcode.fr +Nederlands https://creativecommons.org/publicdomain/zero/1.0/legalcode.nl +polski https://creativecommons.org/publicdomain/zero/1.0/legalcode.pl +suomeksi https://creativecommons.org/publicdomain/zero/1.0/legalcode.fi +日本語 https://creativecommons.org/publicdomain/zero/1.0/legalcode.ja+ \ No newline at end of file diff --git a/asset/soundfonts/rkhive/r-marmba/r-marmba.sf2 b/asset/soundfonts/rkhive/r-marmba/r-marmba.sf2 Binary files differ. diff --git a/asset/soundfonts/rkhive/r-marmba/rkhive.txt b/asset/soundfonts/rkhive/r-marmba/rkhive.txt @@ -0,0 +1,47 @@ +''.....................'' +: this sample came from : +''.....................'' +: : +: /=/|=| |=| /=/ : +: /=/ |=| |=| /=/ : +: \=\ |=| |=| /=/ : +: /=/|=| |=| \=\ : +: /=/ |=| |=| \=\ : +: /=/ |=| |=| \=\ : +: : +: H I V E : +: : +''.....................'' +: http://rkhive.com : +''.....................'' + +CC0 1.0 Universal (CC0 1.0) Public Domain Dedication +https://creativecommons.org/publicdomain/zero/1.0/ + +The person who associated a work with this deed has dedicated the work to the +public domain by waiving all of his or her rights to the work worldwide under +copyright law, including all related and neighboring rights, to the extent +allowed by law. + +You can copy, modify, distribute and perform the work, even for commercial +purposes, all without asking permission. + +In no way are the patent or trademark rights of any person affected by CC0, nor +are the rights that other persons may have in the work or in how the work is +used, such as publicity or privacy rights. + +Unless expressly stated otherwise, the person who associated a work with this +deed makes no warranties about the work, and disclaims liability for all uses of +the work, to the fullest extent permitted by applicable law. + +When using or citing the work, you should not imply endorsement by the author or +the affirmer. + + +This is a summary of the full CC0 Legal Code: +English https://creativecommons.org/publicdomain/zero/1.0/legalcode +français https://creativecommons.org/publicdomain/zero/1.0/legalcode.fr +Nederlands https://creativecommons.org/publicdomain/zero/1.0/legalcode.nl +polski https://creativecommons.org/publicdomain/zero/1.0/legalcode.pl +suomeksi https://creativecommons.org/publicdomain/zero/1.0/legalcode.fi +日本語 https://creativecommons.org/publicdomain/zero/1.0/legalcode.ja+ \ No newline at end of file diff --git a/asset/soundfonts/rkhive/r-timp/r-timp.sf2 b/asset/soundfonts/rkhive/r-timp/r-timp.sf2 Binary files differ. diff --git a/asset/soundfonts/rkhive/r-timp/rkhive.txt b/asset/soundfonts/rkhive/r-timp/rkhive.txt @@ -0,0 +1,47 @@ +''.....................'' +: this sample came from : +''.....................'' +: : +: /=/|=| |=| /=/ : +: /=/ |=| |=| /=/ : +: \=\ |=| |=| /=/ : +: /=/|=| |=| \=\ : +: /=/ |=| |=| \=\ : +: /=/ |=| |=| \=\ : +: : +: H I V E : +: : +''.....................'' +: http://rkhive.com : +''.....................'' + +CC0 1.0 Universal (CC0 1.0) Public Domain Dedication +https://creativecommons.org/publicdomain/zero/1.0/ + +The person who associated a work with this deed has dedicated the work to the +public domain by waiving all of his or her rights to the work worldwide under +copyright law, including all related and neighboring rights, to the extent +allowed by law. + +You can copy, modify, distribute and perform the work, even for commercial +purposes, all without asking permission. + +In no way are the patent or trademark rights of any person affected by CC0, nor +are the rights that other persons may have in the work or in how the work is +used, such as publicity or privacy rights. + +Unless expressly stated otherwise, the person who associated a work with this +deed makes no warranties about the work, and disclaims liability for all uses of +the work, to the fullest extent permitted by applicable law. + +When using or citing the work, you should not imply endorsement by the author or +the affirmer. + + +This is a summary of the full CC0 Legal Code: +English https://creativecommons.org/publicdomain/zero/1.0/legalcode +français https://creativecommons.org/publicdomain/zero/1.0/legalcode.fr +Nederlands https://creativecommons.org/publicdomain/zero/1.0/legalcode.nl +polski https://creativecommons.org/publicdomain/zero/1.0/legalcode.pl +suomeksi https://creativecommons.org/publicdomain/zero/1.0/legalcode.fi +日本語 https://creativecommons.org/publicdomain/zero/1.0/legalcode.ja+ \ No newline at end of file diff --git a/asset/soundfonts/rkhive/radio_spike/Radio Spike.SF2 b/asset/soundfonts/rkhive/radio_spike/Radio Spike.SF2 Binary files differ. diff --git a/asset/soundfonts/rkhive/radio_spike/rkhive.txt b/asset/soundfonts/rkhive/radio_spike/rkhive.txt @@ -0,0 +1,47 @@ +''.....................'' +: this sample came from : +''.....................'' +: : +: /=/|=| |=| /=/ : +: /=/ |=| |=| /=/ : +: \=\ |=| |=| /=/ : +: /=/|=| |=| \=\ : +: /=/ |=| |=| \=\ : +: /=/ |=| |=| \=\ : +: : +: H I V E : +: : +''.....................'' +: http://rkhive.com : +''.....................'' + +CC0 1.0 Universal (CC0 1.0) Public Domain Dedication +https://creativecommons.org/publicdomain/zero/1.0/ + +The person who associated a work with this deed has dedicated the work to the +public domain by waiving all of his or her rights to the work worldwide under +copyright law, including all related and neighboring rights, to the extent +allowed by law. + +You can copy, modify, distribute and perform the work, even for commercial +purposes, all without asking permission. + +In no way are the patent or trademark rights of any person affected by CC0, nor +are the rights that other persons may have in the work or in how the work is +used, such as publicity or privacy rights. + +Unless expressly stated otherwise, the person who associated a work with this +deed makes no warranties about the work, and disclaims liability for all uses of +the work, to the fullest extent permitted by applicable law. + +When using or citing the work, you should not imply endorsement by the author or +the affirmer. + + +This is a summary of the full CC0 Legal Code: +English https://creativecommons.org/publicdomain/zero/1.0/legalcode +français https://creativecommons.org/publicdomain/zero/1.0/legalcode.fr +Nederlands https://creativecommons.org/publicdomain/zero/1.0/legalcode.nl +polski https://creativecommons.org/publicdomain/zero/1.0/legalcode.pl +suomeksi https://creativecommons.org/publicdomain/zero/1.0/legalcode.fi +日本語 https://creativecommons.org/publicdomain/zero/1.0/legalcode.ja+ \ No newline at end of file diff --git a/asset/soundfonts/rkhive/robohit/rkhive.txt b/asset/soundfonts/rkhive/robohit/rkhive.txt @@ -0,0 +1,47 @@ +''.....................'' +: this sample came from : +''.....................'' +: : +: /=/|=| |=| /=/ : +: /=/ |=| |=| /=/ : +: \=\ |=| |=| /=/ : +: /=/|=| |=| \=\ : +: /=/ |=| |=| \=\ : +: /=/ |=| |=| \=\ : +: : +: H I V E : +: : +''.....................'' +: http://rkhive.com : +''.....................'' + +CC0 1.0 Universal (CC0 1.0) Public Domain Dedication +https://creativecommons.org/publicdomain/zero/1.0/ + +The person who associated a work with this deed has dedicated the work to the +public domain by waiving all of his or her rights to the work worldwide under +copyright law, including all related and neighboring rights, to the extent +allowed by law. + +You can copy, modify, distribute and perform the work, even for commercial +purposes, all without asking permission. + +In no way are the patent or trademark rights of any person affected by CC0, nor +are the rights that other persons may have in the work or in how the work is +used, such as publicity or privacy rights. + +Unless expressly stated otherwise, the person who associated a work with this +deed makes no warranties about the work, and disclaims liability for all uses of +the work, to the fullest extent permitted by applicable law. + +When using or citing the work, you should not imply endorsement by the author or +the affirmer. + + +This is a summary of the full CC0 Legal Code: +English https://creativecommons.org/publicdomain/zero/1.0/legalcode +français https://creativecommons.org/publicdomain/zero/1.0/legalcode.fr +Nederlands https://creativecommons.org/publicdomain/zero/1.0/legalcode.nl +polski https://creativecommons.org/publicdomain/zero/1.0/legalcode.pl +suomeksi https://creativecommons.org/publicdomain/zero/1.0/legalcode.fi +日本語 https://creativecommons.org/publicdomain/zero/1.0/legalcode.ja+ \ No newline at end of file diff --git a/asset/soundfonts/rkhive/robohit/robohit.sf2 b/asset/soundfonts/rkhive/robohit/robohit.sf2 Binary files differ. diff --git a/asset/soundfonts/rkhive/sitar/rkhive.txt b/asset/soundfonts/rkhive/sitar/rkhive.txt @@ -0,0 +1,47 @@ +''.....................'' +: this sample came from : +''.....................'' +: : +: /=/|=| |=| /=/ : +: /=/ |=| |=| /=/ : +: \=\ |=| |=| /=/ : +: /=/|=| |=| \=\ : +: /=/ |=| |=| \=\ : +: /=/ |=| |=| \=\ : +: : +: H I V E : +: : +''.....................'' +: http://rkhive.com : +''.....................'' + +CC0 1.0 Universal (CC0 1.0) Public Domain Dedication +https://creativecommons.org/publicdomain/zero/1.0/ + +The person who associated a work with this deed has dedicated the work to the +public domain by waiving all of his or her rights to the work worldwide under +copyright law, including all related and neighboring rights, to the extent +allowed by law. + +You can copy, modify, distribute and perform the work, even for commercial +purposes, all without asking permission. + +In no way are the patent or trademark rights of any person affected by CC0, nor +are the rights that other persons may have in the work or in how the work is +used, such as publicity or privacy rights. + +Unless expressly stated otherwise, the person who associated a work with this +deed makes no warranties about the work, and disclaims liability for all uses of +the work, to the fullest extent permitted by applicable law. + +When using or citing the work, you should not imply endorsement by the author or +the affirmer. + + +This is a summary of the full CC0 Legal Code: +English https://creativecommons.org/publicdomain/zero/1.0/legalcode +français https://creativecommons.org/publicdomain/zero/1.0/legalcode.fr +Nederlands https://creativecommons.org/publicdomain/zero/1.0/legalcode.nl +polski https://creativecommons.org/publicdomain/zero/1.0/legalcode.pl +suomeksi https://creativecommons.org/publicdomain/zero/1.0/legalcode.fi +日本語 https://creativecommons.org/publicdomain/zero/1.0/legalcode.ja+ \ No newline at end of file diff --git a/asset/soundfonts/rkhive/sitar/sitar.sf2 b/asset/soundfonts/rkhive/sitar/sitar.sf2 Binary files differ. diff --git a/asset/soundfonts/rkhive/thxslide/rkhive.txt b/asset/soundfonts/rkhive/thxslide/rkhive.txt @@ -0,0 +1,47 @@ +''.....................'' +: this sample came from : +''.....................'' +: : +: /=/|=| |=| /=/ : +: /=/ |=| |=| /=/ : +: \=\ |=| |=| /=/ : +: /=/|=| |=| \=\ : +: /=/ |=| |=| \=\ : +: /=/ |=| |=| \=\ : +: : +: H I V E : +: : +''.....................'' +: http://rkhive.com : +''.....................'' + +CC0 1.0 Universal (CC0 1.0) Public Domain Dedication +https://creativecommons.org/publicdomain/zero/1.0/ + +The person who associated a work with this deed has dedicated the work to the +public domain by waiving all of his or her rights to the work worldwide under +copyright law, including all related and neighboring rights, to the extent +allowed by law. + +You can copy, modify, distribute and perform the work, even for commercial +purposes, all without asking permission. + +In no way are the patent or trademark rights of any person affected by CC0, nor +are the rights that other persons may have in the work or in how the work is +used, such as publicity or privacy rights. + +Unless expressly stated otherwise, the person who associated a work with this +deed makes no warranties about the work, and disclaims liability for all uses of +the work, to the fullest extent permitted by applicable law. + +When using or citing the work, you should not imply endorsement by the author or +the affirmer. + + +This is a summary of the full CC0 Legal Code: +English https://creativecommons.org/publicdomain/zero/1.0/legalcode +français https://creativecommons.org/publicdomain/zero/1.0/legalcode.fr +Nederlands https://creativecommons.org/publicdomain/zero/1.0/legalcode.nl +polski https://creativecommons.org/publicdomain/zero/1.0/legalcode.pl +suomeksi https://creativecommons.org/publicdomain/zero/1.0/legalcode.fi +日本語 https://creativecommons.org/publicdomain/zero/1.0/legalcode.ja+ \ No newline at end of file diff --git a/asset/soundfonts/rkhive/thxslide/thxslide.sf2 b/asset/soundfonts/rkhive/thxslide/thxslide.sf2 Binary files differ. diff --git a/asset/soundfonts/rkhive/vapour/rkhive.txt b/asset/soundfonts/rkhive/vapour/rkhive.txt @@ -0,0 +1,47 @@ +''.....................'' +: this sample came from : +''.....................'' +: : +: /=/|=| |=| /=/ : +: /=/ |=| |=| /=/ : +: \=\ |=| |=| /=/ : +: /=/|=| |=| \=\ : +: /=/ |=| |=| \=\ : +: /=/ |=| |=| \=\ : +: : +: H I V E : +: : +''.....................'' +: http://rkhive.com : +''.....................'' + +CC0 1.0 Universal (CC0 1.0) Public Domain Dedication +https://creativecommons.org/publicdomain/zero/1.0/ + +The person who associated a work with this deed has dedicated the work to the +public domain by waiving all of his or her rights to the work worldwide under +copyright law, including all related and neighboring rights, to the extent +allowed by law. + +You can copy, modify, distribute and perform the work, even for commercial +purposes, all without asking permission. + +In no way are the patent or trademark rights of any person affected by CC0, nor +are the rights that other persons may have in the work or in how the work is +used, such as publicity or privacy rights. + +Unless expressly stated otherwise, the person who associated a work with this +deed makes no warranties about the work, and disclaims liability for all uses of +the work, to the fullest extent permitted by applicable law. + +When using or citing the work, you should not imply endorsement by the author or +the affirmer. + + +This is a summary of the full CC0 Legal Code: +English https://creativecommons.org/publicdomain/zero/1.0/legalcode +français https://creativecommons.org/publicdomain/zero/1.0/legalcode.fr +Nederlands https://creativecommons.org/publicdomain/zero/1.0/legalcode.nl +polski https://creativecommons.org/publicdomain/zero/1.0/legalcode.pl +suomeksi https://creativecommons.org/publicdomain/zero/1.0/legalcode.fi +日本語 https://creativecommons.org/publicdomain/zero/1.0/legalcode.ja+ \ No newline at end of file diff --git a/asset/soundfonts/rkhive/vapour/vapour.sf2 b/asset/soundfonts/rkhive/vapour/vapour.sf2 Binary files differ. diff --git a/asset/soundfonts/rkhive/water/rkhive.txt b/asset/soundfonts/rkhive/water/rkhive.txt @@ -0,0 +1,47 @@ +''.....................'' +: this sample came from : +''.....................'' +: : +: /=/|=| |=| /=/ : +: /=/ |=| |=| /=/ : +: \=\ |=| |=| /=/ : +: /=/|=| |=| \=\ : +: /=/ |=| |=| \=\ : +: /=/ |=| |=| \=\ : +: : +: H I V E : +: : +''.....................'' +: http://rkhive.com : +''.....................'' + +CC0 1.0 Universal (CC0 1.0) Public Domain Dedication +https://creativecommons.org/publicdomain/zero/1.0/ + +The person who associated a work with this deed has dedicated the work to the +public domain by waiving all of his or her rights to the work worldwide under +copyright law, including all related and neighboring rights, to the extent +allowed by law. + +You can copy, modify, distribute and perform the work, even for commercial +purposes, all without asking permission. + +In no way are the patent or trademark rights of any person affected by CC0, nor +are the rights that other persons may have in the work or in how the work is +used, such as publicity or privacy rights. + +Unless expressly stated otherwise, the person who associated a work with this +deed makes no warranties about the work, and disclaims liability for all uses of +the work, to the fullest extent permitted by applicable law. + +When using or citing the work, you should not imply endorsement by the author or +the affirmer. + + +This is a summary of the full CC0 Legal Code: +English https://creativecommons.org/publicdomain/zero/1.0/legalcode +français https://creativecommons.org/publicdomain/zero/1.0/legalcode.fr +Nederlands https://creativecommons.org/publicdomain/zero/1.0/legalcode.nl +polski https://creativecommons.org/publicdomain/zero/1.0/legalcode.pl +suomeksi https://creativecommons.org/publicdomain/zero/1.0/legalcode.fi +日本語 https://creativecommons.org/publicdomain/zero/1.0/legalcode.ja+ \ No newline at end of file diff --git a/asset/soundfonts/rkhive/water/water.sf2 b/asset/soundfonts/rkhive/water/water.sf2 Binary files differ. diff --git a/asset/soundfonts/rkhive/whistsolo/rkhive.txt b/asset/soundfonts/rkhive/whistsolo/rkhive.txt @@ -0,0 +1,47 @@ +''.....................'' +: this sample came from : +''.....................'' +: : +: /=/|=| |=| /=/ : +: /=/ |=| |=| /=/ : +: \=\ |=| |=| /=/ : +: /=/|=| |=| \=\ : +: /=/ |=| |=| \=\ : +: /=/ |=| |=| \=\ : +: : +: H I V E : +: : +''.....................'' +: http://rkhive.com : +''.....................'' + +CC0 1.0 Universal (CC0 1.0) Public Domain Dedication +https://creativecommons.org/publicdomain/zero/1.0/ + +The person who associated a work with this deed has dedicated the work to the +public domain by waiving all of his or her rights to the work worldwide under +copyright law, including all related and neighboring rights, to the extent +allowed by law. + +You can copy, modify, distribute and perform the work, even for commercial +purposes, all without asking permission. + +In no way are the patent or trademark rights of any person affected by CC0, nor +are the rights that other persons may have in the work or in how the work is +used, such as publicity or privacy rights. + +Unless expressly stated otherwise, the person who associated a work with this +deed makes no warranties about the work, and disclaims liability for all uses of +the work, to the fullest extent permitted by applicable law. + +When using or citing the work, you should not imply endorsement by the author or +the affirmer. + + +This is a summary of the full CC0 Legal Code: +English https://creativecommons.org/publicdomain/zero/1.0/legalcode +français https://creativecommons.org/publicdomain/zero/1.0/legalcode.fr +Nederlands https://creativecommons.org/publicdomain/zero/1.0/legalcode.nl +polski https://creativecommons.org/publicdomain/zero/1.0/legalcode.pl +suomeksi https://creativecommons.org/publicdomain/zero/1.0/legalcode.fi +日本語 https://creativecommons.org/publicdomain/zero/1.0/legalcode.ja+ \ No newline at end of file diff --git a/asset/soundfonts/rkhive/whistsolo/whistsolo.sf2 b/asset/soundfonts/rkhive/whistsolo/whistsolo.sf2 Binary files differ. diff --git a/asset/soundfonts/rkhive/worshchr/rkhive.txt b/asset/soundfonts/rkhive/worshchr/rkhive.txt @@ -0,0 +1,47 @@ +''.....................'' +: this sample came from : +''.....................'' +: : +: /=/|=| |=| /=/ : +: /=/ |=| |=| /=/ : +: \=\ |=| |=| /=/ : +: /=/|=| |=| \=\ : +: /=/ |=| |=| \=\ : +: /=/ |=| |=| \=\ : +: : +: H I V E : +: : +''.....................'' +: http://rkhive.com : +''.....................'' + +CC0 1.0 Universal (CC0 1.0) Public Domain Dedication +https://creativecommons.org/publicdomain/zero/1.0/ + +The person who associated a work with this deed has dedicated the work to the +public domain by waiving all of his or her rights to the work worldwide under +copyright law, including all related and neighboring rights, to the extent +allowed by law. + +You can copy, modify, distribute and perform the work, even for commercial +purposes, all without asking permission. + +In no way are the patent or trademark rights of any person affected by CC0, nor +are the rights that other persons may have in the work or in how the work is +used, such as publicity or privacy rights. + +Unless expressly stated otherwise, the person who associated a work with this +deed makes no warranties about the work, and disclaims liability for all uses of +the work, to the fullest extent permitted by applicable law. + +When using or citing the work, you should not imply endorsement by the author or +the affirmer. + + +This is a summary of the full CC0 Legal Code: +English https://creativecommons.org/publicdomain/zero/1.0/legalcode +français https://creativecommons.org/publicdomain/zero/1.0/legalcode.fr +Nederlands https://creativecommons.org/publicdomain/zero/1.0/legalcode.nl +polski https://creativecommons.org/publicdomain/zero/1.0/legalcode.pl +suomeksi https://creativecommons.org/publicdomain/zero/1.0/legalcode.fi +日本語 https://creativecommons.org/publicdomain/zero/1.0/legalcode.ja+ \ No newline at end of file diff --git a/asset/soundfonts/rkhive/worshchr/worshchr.sf2 b/asset/soundfonts/rkhive/worshchr/worshchr.sf2 Binary files differ. diff --git a/asset/soundfonts/rkhive/zonehit/rkhive.txt b/asset/soundfonts/rkhive/zonehit/rkhive.txt @@ -0,0 +1,47 @@ +''.....................'' +: this sample came from : +''.....................'' +: : +: /=/|=| |=| /=/ : +: /=/ |=| |=| /=/ : +: \=\ |=| |=| /=/ : +: /=/|=| |=| \=\ : +: /=/ |=| |=| \=\ : +: /=/ |=| |=| \=\ : +: : +: H I V E : +: : +''.....................'' +: http://rkhive.com : +''.....................'' + +CC0 1.0 Universal (CC0 1.0) Public Domain Dedication +https://creativecommons.org/publicdomain/zero/1.0/ + +The person who associated a work with this deed has dedicated the work to the +public domain by waiving all of his or her rights to the work worldwide under +copyright law, including all related and neighboring rights, to the extent +allowed by law. + +You can copy, modify, distribute and perform the work, even for commercial +purposes, all without asking permission. + +In no way are the patent or trademark rights of any person affected by CC0, nor +are the rights that other persons may have in the work or in how the work is +used, such as publicity or privacy rights. + +Unless expressly stated otherwise, the person who associated a work with this +deed makes no warranties about the work, and disclaims liability for all uses of +the work, to the fullest extent permitted by applicable law. + +When using or citing the work, you should not imply endorsement by the author or +the affirmer. + + +This is a summary of the full CC0 Legal Code: +English https://creativecommons.org/publicdomain/zero/1.0/legalcode +français https://creativecommons.org/publicdomain/zero/1.0/legalcode.fr +Nederlands https://creativecommons.org/publicdomain/zero/1.0/legalcode.nl +polski https://creativecommons.org/publicdomain/zero/1.0/legalcode.pl +suomeksi https://creativecommons.org/publicdomain/zero/1.0/legalcode.fi +日本語 https://creativecommons.org/publicdomain/zero/1.0/legalcode.ja+ \ No newline at end of file diff --git a/asset/soundfonts/rkhive/zonehit/zonehit.sf2 b/asset/soundfonts/rkhive/zonehit/zonehit.sf2 Binary files differ. diff --git a/requirements.txt b/requirements.txt @@ -0,0 +1,3 @@ +mido +numpy +Pillow diff --git a/tools/__init__.py b/tools/__init__.py @@ -0,0 +1,22 @@ +""" +FluidStudio — programmable music notation tools. + +Parses .flsp notation files into MIDI events, renders via FluidSynth. + +Usage: + from tools import parse_header, parse_gondry, render_midi + header = parse_header("song.json") + events = parse_gondry("song_0_piano.flsp", header) + render_midi(events, header, "output.mid") +""" + +from .parser import parse_header, parse_gondry, GondryEvent, VoiceHeader +from .midi_gen import gondry_to_midi, render_midi, render_wav +from .compiler import compile_project + +__all__ = [ + "parse_header", "parse_gondry", + "gondry_to_midi", "render_midi", "render_wav", + "compile_project", + "GondryEvent", "VoiceHeader", +] diff --git a/tools/chromatic_check.py b/tools/chromatic_check.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +"""Chromatic check — list all valid chords per degree for a given root/mode. + +Usage: + python chromatic_check.py 7 misheberak + python chromatic_check.py 0 dorian +""" + +import sys +import os +from pathlib import Path as _Path +sys.path.insert(0, str(_Path(__file__).resolve().parent.parent)) + +from tools.parser import MODES, CHORDS + +NOTES = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"] + + +def check(root: int, mode: str): + if mode not in MODES: + print(f"Unknown mode: {mode}") + print(f"Available: {', '.join(MODES.keys())}") + return + + intervals = MODES[mode] + scale_set = set((root + i) % 12 for i in intervals) + + print(f"Root: {NOTES[root]} | Mode: {mode}") + print(f"Scale: {' '.join(NOTES[(root + i) % 12] for i in intervals)}") + print() + + for deg, offset in enumerate(intervals): + degree_root = (root + offset) % 12 + valid = [] + for chord_name, chord_intervals in CHORDS.items(): + chord_notes = [(degree_root + ci) % 12 for ci in chord_intervals] + if all(n in scale_set for n in chord_notes): + valid.append(chord_name) + + if valid: + print(f"- {deg}: {', '.join(valid)}") + else: + print(f"- {deg}: (no scale-pure chords)") + + +if __name__ == "__main__": + if len(sys.argv) < 3: + print("Usage: python chromatic_check.py <root_semitone> <mode>") + print(" root_semitone: 0=C, 1=C#, 2=D, ..., 7=G, ..., 11=B") + sys.exit(1) + + root = int(sys.argv[1]) + mode = sys.argv[2] + check(root, mode) diff --git a/tools/clips.py b/tools/clips.py @@ -0,0 +1,207 @@ +"""FluidStudio clip generator — per-frame CSV for video sync. + +Usage: + python -m gondry.clips projects/minipops_demo + python -m gondry.clips projects/minipops_demo --fps 30 + +Output: one CSV per bar in projects/<song>/clip/ +Each CSV: 1 row = 1 frame, columns = instruments, ";" delimiter, multiple notes "," separated. +""" + +import csv +import sys +from pathlib import Path + +from .parser import parse_header, parse_gondry, VoiceHeader, GondryEvent +from .fps import bpm_to_fps + + +def events_to_frames(events: list[GondryEvent], fps: int, bpm: float, + total_frames: int) -> list[list[int]]: + """Convert events to per-frame note lists. + + Returns: list of length total_frames, each entry is list of MIDI notes active at that frame. + """ + frames = [[] for _ in range(total_frames)] + sec_per_frame = 1.0 / fps + + for ev in events: + if not ev.midi_notes: + continue + + # Event timing in seconds + sec_per_beat = 60.0 / bpm + start_sec = ev.beat_position * sec_per_beat + end_sec = start_sec + (ev.duration_beats * sec_per_beat) + + # Convert to frame indices + start_frame = int(start_sec / sec_per_frame) + end_frame = int(end_sec / sec_per_frame) + + # Clamp + start_frame = max(0, min(start_frame, total_frames - 1)) + end_frame = max(0, min(end_frame, total_frames)) + + for frame_idx in range(start_frame, end_frame): + frames[frame_idx].extend(ev.midi_notes) + + return frames + + +def generate_clips(project_dir: str | Path, fps: int | None = None): + """Generate per-frame CSVs for video sync. + + Each bar gets its own CSV with all instruments as columns. + If fps not specified, auto-calculated from BPM for clean frame sync. + """ + project = Path(project_dir) + partition_dir = project / "partition" + clip_dir = project / "clip" + + clip_dir.mkdir(exist_ok=True) + + header_path = partition_dir / "header.json" + if not header_path.exists(): + raise FileNotFoundError(f"No header.json in {partition_dir}") + + header = parse_header(header_path) + bpm = header.bpm + sec_per_beat = 60.0 / bpm + + # Auto-calculate fps if not specified + if fps is None: + fps_info = bpm_to_fps(bpm) + fps = int(fps_info["fps"]) + print(f"Auto-calculated fps: {fps} (multiplier: {fps_info['multiplier']}x)") + else: + # Verify clean sync + fpm = fps * 60 + fpb = fpm / bpm + if fpb != int(fpb): + print(f"WARNING: {fps}fps at {bpm}BPM = {fpb:.2f} frames/beat (not integer)") + print(f" Consider: {int(bpm/5)}fps (1x), {int(bpm/5*2)}fps (2x), or {int(bpm/5*3)}fps (3x)") + + # Find all .flsp files + gondry_files = sorted(partition_dir.glob("*.flsp")) + if not gondry_files: + raise FileNotFoundError(f"No .flsp files in {partition_dir}") + + # Parse all voices and collect events + voices = {} # name -> list[GondryEvent] + for gondry_path in gondry_files: + voice_name = gondry_path.stem + + # Match voice to channel config + channel_config = None + for ch in header.channels: + if ch["name"] == voice_name: + channel_config = ch + break + + if channel_config is None: + for ch in header.channels: + if voice_name.startswith(ch["name"]) or ch["name"].startswith(voice_name): + channel_config = ch + break + + if channel_config is None: + print(f" SKIP {voice_name} — no matching channel in header") + continue + + voice_header = VoiceHeader( + project=header.project, + bpm=header.bpm, + octave=header.octave, + mode=header.mode, + channels=header.channels, + channel_name=voice_name, + channel_config=channel_config, + ) + + events = parse_gondry(gondry_path, voice_header) + voices[voice_name] = events + + if not voices: + print("No voices found") + return + + # Determine total duration in beats (max across all voices) + max_beat = 0.0 + for events in voices.values(): + if events: + max_beat = max(max_beat, max(e.beat_position + e.duration_beats for e in events)) + + # Calculate frames + total_beats = max_beat + frames_per_beat = fps * 60.0 / bpm + total_frames = int(total_beats * frames_per_beat) + + print(f"Project: {project}") + print(f"BPM: {bpm} | FPS: {fps} | Frames/beat: {frames_per_beat:.2f}") + print(f"Total: {total_beats:.1f} beats = {total_frames} frames") + print(f"Voices: {list(voices.keys())}") + print() + + # Convert each voice to per-frame data + voice_frames = {} + for name, events in voices.items(): + voice_frames[name] = events_to_frames(events, fps, bpm, total_frames) + + # Write one CSV per bar + beats_per_bar = 4 # assume 4/4 time + frames_per_bar = int(beats_per_bar * frames_per_beat) + num_bars = int(total_beats / beats_per_bar) + + for bar_idx in range(num_bars): + start_frame = bar_idx * frames_per_bar + end_frame = min(start_frame + frames_per_bar, total_frames) + + # Determine which voices have activity in this bar + active_voices = [] + for name in voices: + bar_frames = voice_frames[name][start_frame:end_frame] + if any(bar_frames): + active_voices.append(name) + + if not active_voices: + continue + + clip_path = clip_dir / f"bar_{bar_idx + 1:03d}.csv" + + with open(clip_path, 'w', newline='') as f: + writer = csv.writer(f, delimiter=';') + + # Header row + writer.writerow(['frame'] + active_voices) + + # Data rows + for frame_offset in range(frames_per_bar): + frame_idx = start_frame + frame_offset + if frame_idx >= total_frames: + break + + row = [frame_offset] + for name in active_voices: + notes = voice_frames[name][frame_idx] + row.append(','.join(str(n) for n in notes)) + + writer.writerow(row) + + print(f" {clip_path.name}: {len(active_voices)} voices, {end_frame - start_frame} frames") + + print(f"\n {num_bars} clips written to {clip_dir}") + + +if __name__ == "__main__": + if len(sys.argv) < 2: + print("Usage: python -m gondry.clips <project_dir> [--fps N]") + sys.exit(1) + + project_dir = sys.argv[1] + fps = None + + if '--fps' in sys.argv: + idx = sys.argv.index('--fps') + fps = int(sys.argv[idx + 1]) + + generate_clips(project_dir, fps) diff --git a/tools/compiler.py b/tools/compiler.py @@ -0,0 +1,178 @@ +"""FluidStudio project compiler. + +Usage: + from gondry.compiler import compile_project + compile_project("projects/marimba_demo") + +Or CLI: + python -m gondry.compiler projects/marimba_demo +""" + +import sys +import subprocess +import time +from datetime import datetime +from pathlib import Path + +from .parser import parse_header, parse_gondry, VoiceHeader +from .midi_gen import render_midi, render_wav + + +def compile_project(project_dir: str | Path, sf2_root: str | Path = "asset/soundfonts"): + """Compile a gondry project: partition -> stems -> mix.""" + project = Path(project_dir) + partition_dir = project / "partition" + stems_dir = project / "stems" + mix_dir = project / "mix" + midi_dir = project / "midi" + + stems_dir.mkdir(exist_ok=True) + mix_dir.mkdir(exist_ok=True) + midi_dir.mkdir(exist_ok=True) + + log_lines = [] + + def log(msg): + print(msg) + log_lines.append(msg) + + header_path = partition_dir / "header.json" + if not header_path.exists(): + raise FileNotFoundError(f"No header.json in {partition_dir}") + + header = parse_header(header_path) + bpm = header.bpm + sec_per_beat = 60.0 / bpm + sf2_root = Path(sf2_root) + + log(f"=== FluidStudio Compile === {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") + log(f"Project: {project}") + log(f"BPM: {bpm} ({sec_per_beat:.4f}s/beat)") + log(f"Octave: {header.octave} | Mode: {header.mode}") + log(f"Channels: {[ch['name'] for ch in header.channels]}") + log("") + + # Find all .flsp files + gondry_files = sorted(partition_dir.glob("*.flsp")) + if not gondry_files: + raise FileNotFoundError(f"No .flsp files in {partition_dir}") + + stem_paths = [] + max_beat_total = 0.0 + + for gondry_path in gondry_files: + voice_name = gondry_path.stem # e.g. "melody" + + # Match voice to channel config + channel_config = None + for ch in header.channels: + if ch["name"] == voice_name: + channel_config = ch + break + + if channel_config is None: + # Try partial match + for ch in header.channels: + if voice_name.startswith(ch["name"]) or ch["name"].startswith(voice_name): + channel_config = ch + break + + if channel_config is None: + log(f" SKIP {voice_name} — no matching channel in header") + continue + + # Parse + voice_header = VoiceHeader( + project=header.project, + bpm=header.bpm, + octave=header.octave, + mode=header.mode, + channels=header.channels, + channel_name=voice_name, + channel_config=channel_config, + ) + + events = parse_gondry(gondry_path, voice_header) + if not events: + log(f" SKIP {voice_name} — no events") + continue + + # Log all events + log(f" --- {voice_name} ---") + log(f" sf2: {channel_config['sf2']}") + log(f" octave: {channel_config.get('octave', header.octave)}") + log(f" events ({len(events)}):") + for ev in events: + if ev.midi_notes: + log(f" beat={ev.beat_position:.3f} dur={ev.duration_beats:.3f} notes={ev.midi_notes} vel={ev.velocity}") + else: + log(f" beat={ev.beat_position:.3f} dur={ev.duration_beats:.3f} rest") + + # Calculate exact duration from last note-off + max_beat = max(e.beat_position + e.duration_beats for e in events) + max_beat_total = max(max_beat_total, max_beat) + duration = max_beat * sec_per_beat + + # Render MIDI + midi_path = midi_dir / f"{voice_name}.mid" + render_midi(events, voice_header, midi_path) + log(f" -> {midi_path} ({duration:.4f}s)") + + # Render WAV (trimmed to exact beat duration) + sf2_path = sf2_root / channel_config["sf2"] + stem_path = stems_dir / f"{voice_name}.wav" + if not sf2_path.exists(): + log(f" WARN: SF2 not found at {sf2_path}") + + render_wav(midi_path, sf2_path, stem_path, duration=duration) + + # Verify stem has audio + result = subprocess.run( + ["ffmpeg", "-i", str(stem_path), "-af", "volumedetect", "-f", "null", "-"], + capture_output=True, text=True + ) + max_vol = "?" + for line in result.stderr.splitlines(): + if "max_volume" in line: + max_vol = line.split(":")[-1].strip() + log(f" -> {stem_path} ({duration:.4f}s, peak={max_vol})") + log("") + + stem_paths.append(stem_path) + + # Mix all stems + if stem_paths: + total_duration = max_beat_total * sec_per_beat + inputs = [] + filter_parts = [] + for i, sp in enumerate(stem_paths): + inputs += ["-i", str(sp)] + filter_parts.append(f"[{i}:a]") + + mix_path = mix_dir / "mix.wav" + filter_str = "".join(filter_parts) + f"amix=inputs={len(stem_paths)}:duration=first[mix]" + cmd = ["ffmpeg", "-y"] + inputs + [ + "-filter_complex", filter_str, + "-map", "[mix]", + str(mix_path), + ] + r = subprocess.run(cmd, capture_output=True) + if r.returncode != 0: + log(f" MIX ERROR: ffmpeg failed") + log(f" stderr: {r.stderr.decode()[-500:]}") + else: + log(f" mix: {total_duration:.4f}s -> {mix_path}") + + # Write log file + log_path = project / "compile.log" + log_path.write_text("\n".join(log_lines) + "\n") + log(f" log -> {log_path}") + + return project + + +if __name__ == "__main__": + if len(sys.argv) < 2: + print("Usage: python -m gondry.compiler <project_dir>") + sys.exit(1) + compile_project(sys.argv[1]) diff --git a/tools/fps.py b/tools/fps.py @@ -0,0 +1,61 @@ +"""BPM to FPS calculator for clean frame sync. + +Frame sync formula: +- fps × 60 = fpm (frames per minute) +- BPM = beats per minute +- frames_per_beat = fpm / BPM = (fps × 60) / BPM + +For clean sync: frames_per_beat must be integer. +Base fps = BPM / 5 (gives 12 frames/beat). +If fps < 24, double resolution to maintain fluidity. +""" + + +def bpm_to_fps(bpm: float, min_fps: int = 24) -> dict: + """Calculate clean fps from BPM. + + Returns dict with fps, frames_per_beat, and multiplier used. + """ + # Base fps for clean sync (12 frames/beat) + fps_base = bpm / 5 + + # Double resolution if below min_fps + multiplier = 1 + fps = fps_base + while fps < min_fps: + multiplier *= 2 + fps = fps_base * multiplier + + frames_per_beat = (fps * 60) / bpm + + return { + "bpm": bpm, + "fps": fps, + "frames_per_beat": int(frames_per_beat), + "multiplier": multiplier, + "fpm": int(fps * 60), + } + + +def print_fps_table(bpm_range: range = range(60, 181, 10)): + """Print fps table for common BPMs.""" + print(f"{'BPM':>5} {'FPS':>5} {'FPB':>5} {'Mult':>5} {'FPM':>8}") + print("-" * 35) + for bpm in bpm_range: + r = bpm_to_fps(bpm) + print(f"{r['bpm']:>5} {r['fps']:>5.0f} {r['frames_per_beat']:>5} {r['multiplier']:>5} {r['fpm']:>8}") + + +if __name__ == "__main__": + import sys + + if len(sys.argv) > 1: + bpm = float(sys.argv[1]) + r = bpm_to_fps(bpm) + print(f"BPM: {r['bpm']}") + print(f"FPS: {r['fps']}") + print(f"Frames/beat: {r['frames_per_beat']}") + print(f"Resolution multiplier: {r['multiplier']}x") + print(f"FPM: {r['fpm']}") + else: + print_fps_table() diff --git a/tools/midi_gen.py b/tools/midi_gen.py @@ -0,0 +1,121 @@ +"""MIDI generation and FluidSynth rendering for FluidStudio notation.""" + +import subprocess +from pathlib import Path + +import mido + +from .parser import GondryEvent, VoiceHeader + + +def gondry_to_midi(events: list[GondryEvent], header: VoiceHeader, + ticks_per_beat: int = 480) -> mido.MidiFile: + """Convert parsed Gondry events to a MIDI file. + + Args: + events: Parsed events from parse_gondry(). + header: Voice header with BPM. + ticks_per_beat: MIDI resolution (480 = standard). + """ + mid = mido.MidiFile(ticks_per_beat=ticks_per_beat) + track = mido.MidiTrack() + mid.tracks.append(track) + + # Set tempo + microseconds_per_beat = int(60_000_000 / header.bpm) + track.append(mido.MetaMessage('set_tempo', tempo=microseconds_per_beat, time=0)) + + # Track name + track.append(mido.MetaMessage('track_name', name=header.channel_name or header.project, time=0)) + + # Convert events to messages with absolute ticks, then delta + messages: list[tuple[int, mido.Message]] = [] + legato_on = False + + # Program change for all channels (including drums) + messages.append((0, mido.Message('program_change', program=0, channel=0, time=0))) + + for ev in events: + # Legato state change + if ev.legato is not None: + legato_on = ev.legato + tick = int(ev.beat_position * ticks_per_beat) + cc_val = 127 if legato_on else 0 + messages.append((tick, mido.Message('control_change', control=65, + value=cc_val, time=0))) + + if not ev.midi_notes: + continue + + start_tick = int(ev.beat_position * ticks_per_beat) + duration_tick = max(1, int(ev.duration_beats * ticks_per_beat)) + end_tick = start_tick + duration_tick + + # When legato is on, delay note_off by 1 tick so the next note_on + # fires before this note_off — creating the overlap FluidSynth needs + if legato_on: + end_tick += 1 + + for note in ev.midi_notes: + messages.append((start_tick, mido.Message('note_on', note=note, + velocity=ev.velocity, channel=0, time=0))) + messages.append((end_tick, mido.Message('note_off', note=note, + velocity=0, channel=0, time=0))) + + # Sort by tick, note_off before note_on at same tick (overlap for legato) + messages.sort(key=lambda m: (m[0], m[1].type != 'note_off')) + + # Convert to delta time + last_tick = 0 + for tick, msg in messages: + msg.time = tick - last_tick + last_tick = tick + track.append(msg) + + return mid + + +def render_midi(events: list[GondryEvent], header: VoiceHeader, + output_path: str | Path, ticks_per_beat: int = 480): + """Write events to a .mid file.""" + mid = gondry_to_midi(events, header, ticks_per_beat) + mid.save(str(output_path)) + + +def render_wav(midi_path: str | Path, sf2_path: str | Path, + output_path: str | Path, sample_rate: int = 44100, + reverb: bool = False, chorus: bool = False, + duration: float | None = None): + """Render MIDI to WAV using FluidSynth CLI. + + Args: + duration: Trim output to exact seconds (beat-accurate stems). + """ + cmd = [ + "fluidsynth", + "-ni", + ] + if not reverb: + cmd += ["-o", "synth.reverb.active=no"] + if not chorus: + cmd += ["-o", "synth.chorus.active=no"] + cmd += [ + str(sf2_path), + str(midi_path), + "-F", str(output_path), + "-r", str(sample_rate), + ] + subprocess.run(cmd, check=True, capture_output=True) + + # Trim to exact duration if requested + if duration is not None: + stem_path = Path(output_path) + trimmed = stem_path.with_suffix('.trimmed.wav') + result = subprocess.run([ + "ffmpeg", "-y", "-i", str(output_path), + "-af", f"atrim=end={duration}", + "-c:a", "pcm_s16le", str(trimmed) + ], capture_output=True, text=True) + if result.returncode != 0: + raise RuntimeError(f"ffmpeg trim failed for {output_path}: {result.stderr[-500:]}") + trimmed.rename(stem_path) diff --git a/tools/parser.py b/tools/parser.py @@ -0,0 +1,360 @@ +"""FluidStudio notation parser — .flsp files and .json headers. + +Chromatic semitone system: notes 0-11 relative to octave in header. +0 = C at that octave. Modes used for chord validation only. +""" + +import json +import re +from dataclasses import dataclass, field +from pathlib import Path + + +# ── Mode / Chord tables (for validation) ───────────────────────────── + +MODES = { + "ionian": [0, 2, 4, 5, 7, 9, 11], + "dorian": [0, 2, 3, 5, 7, 9, 10], + "phrygian": [0, 1, 3, 5, 7, 8, 10], + "lydian": [0, 2, 4, 6, 7, 9, 11], + "mixolydian": [0, 2, 4, 5, 7, 9, 10], + "aeolian": [0, 2, 3, 5, 7, 8, 10], + "locrian": [0, 1, 3, 5, 6, 8, 10], + "misheberak": [0, 2, 3, 6, 7, 9, 10], + "freygish": [0, 1, 4, 5, 7, 8, 10], + "hungarian_minor": [0, 2, 3, 6, 7, 8, 11], + "hungarian_major": [0, 3, 4, 6, 7, 9, 10], + "persian": [0, 1, 4, 5, 6, 8, 11], + "whole_tone": [0, 2, 4, 6, 8, 10], +} + +CHORDS = { + "M": [0, 4, 7], + "M6": [0, 4, 7, 9], + "M69": [0, 4, 7, 9, 14], + "M7m": [0, 4, 7, 10], + "M7M": [0, 4, 7, 11], + "M9": [0, 4, 7, 10, 14], + "Madd2": [0, 2, 4, 7], + "Madd9": [0, 4, 7, 14], + "m": [0, 3, 7], + "m6": [0, 3, 7, 9], + "m7m": [0, 3, 7, 10], + "m7M": [0, 3, 7, 11], + "m9": [0, 3, 7, 10, 14], + "m69": [0, 3, 7, 9, 14], + "madd2": [0, 2, 3, 7], + "madd9": [0, 3, 7, 14], + "sus2": [0, 2, 7], + "sus26": [0, 2, 7, 9], + "sus27": [0, 2, 7, 10], + "sus27M": [0, 2, 7, 11], + "sus4": [0, 5, 7], + "sus46": [0, 5, 7, 9], + "sus47": [0, 5, 7, 10], + "sus47M": [0, 5, 7, 11], + "dimb3": [0, 2, 6], + "dim": [0, 3, 6], + "dim7": [0, 3, 6, 9], + "b5": [0, 4, 6], + "aug": [0, 4, 8], + "aug7": [0, 4, 8, 10], + "p4": [0, 5], + "TT": [0, 6], + "p5": [0, 7], +} + + +# ── Data structures ────────────────────────────────────────────────── + +@dataclass +class VoiceHeader: + """Parsed metadata from a .json header file.""" + project: str = "" + bpm: float = 120 + key: int = 0 # key root (0=C, 7=G, etc.) — for chord suggestions + octave: int = 4 # base octave (0=C at this octave) + mode: str = "ionian" # for chord suggestions only + channels: list[dict] = field(default_factory=list) + + # Derived from filename + channel_name: str = "" + channel_config: dict = field(default_factory=dict) + + @property + def sf2(self) -> str: + return self.channel_config.get("sf2", "") + + @property + def channel_octave(self) -> int: + """Per-channel octave override (falls back to header octave).""" + return self.channel_config.get("octave", self.octave) + + +@dataclass +class GondryEvent: + """A single parsed musical event.""" + beat_position: float # absolute beat position from start + duration_beats: float # note duration in beats + midi_notes: list[int] # resolved MIDI note numbers + velocity: int # 0-127 + note: int | None = None # chromatic note (0-11, None for rest) + chord_type: str | None = None + is_chord: bool = False + legato: bool | None = None # None=keep state, True=on, False=off + octave_shift: int = 0 # per-note octave offset + + +# ── Header parser ──────────────────────────────────────────────────── + +def parse_header(path: str | Path) -> VoiceHeader: + """Parse a .json header file.""" + with open(path) as f: + data = json.load(f) + + header = VoiceHeader( + project=data.get("project", ""), + bpm=data.get("bpm", 120), + key=data.get("key", 0), + octave=data.get("octave", 4), + mode=data.get("mode", "ionian"), + channels=data.get("channels", []), + ) + return header + + +# ── Duration parser ────────────────────────────────────────────────── + +_DURATION_RE = re.compile( + r'^(\d+)?(?:b)?(?:/(\d+))?$' + # Matches: "b", "2b", "b/2", "b/3", "1b/2", "" +) + +def _parse_duration(text: str) -> float: + """Parse duration string to beats. + + "b" -> 1.0 (one beat) + "2b" -> 2.0 (two beats) + "b/2" -> 0.5 (half beat) + "b/3" -> 0.333... (triplet eighth) + "b/4" -> 0.25 + "b/8" -> 0.125 + "" -> default handled by caller + """ + if not text: + return 0.0 # caller will fill default + + text = text.strip().lstrip(':') + + # "b" alone = 1 beat + if text == 'b': + return 1.0 + + m = _DURATION_RE.match(text) + if not m: + return 1.0 # fallback + + numerator = int(m.group(1)) if m.group(1) else 1 + denominator = int(m.group(2)) if m.group(2) else 1 + + return numerator / denominator + + +# ── Velocity parser ────────────────────────────────────────────────── + +def _parse_velocity(text: str, default: int = 100) -> int: + """Extract velocity from (v:NN) notation.""" + m = re.search(r'\(v:(\d+)\)', text) + if m: + return int(m.group(1)) + return default + + +# ── Cell parser ────────────────────────────────────────────────────── + +_CELL_RE = re.compile( + r'^' + r'(?P<note>[0-9]|1[01])?' # chromatic note (0-11) + r'(?:x(?P<chord>[A-Za-z0-9]+))?' # optional x + chord type + r'(?::(?P<duration>[\w/]+))?' # optional :duration + r'(?:\s+\(v:(?P<velocity>\d+)\))?' # optional velocity + r'$' +) + +def _parse_cell(text: str, default_vel: int = 100, + default_duration: float = 0.25) -> tuple[list[int], float, int, int | None, str | None, bool, bool | None, int]: + """Parse a single cell into (midi_notes, duration, velocity, note, chord_type, is_chord, legato, octave_shift). + + Notes are chromatic semitones (0-11). Multiple notes for chords. + """ + text = text.strip() + + # Extract legato state + legato = None + if '(legato: on)' in text: + legato = True + text = text.replace('(legato: on)', '').strip() + elif '(legato: off)' in text: + legato = False + text = text.replace('(legato: off)', '').strip() + + # Extract octave shift + octave_shift = 0 + oct_match = re.search(r'\(oct\s*([+-]?\d+)\)', text) + if oct_match: + octave_shift = int(oct_match.group(1)) + text = text.replace(oct_match.group(0), '').strip() + + # Empty or rest + if not text or text == 'x': + return [], 0.0, 0, None, None, False, legato, octave_shift + + # Melodic: full syntax (now handles 0-11) + m = _CELL_RE.match(text) + if not m: + return [], 0.0, 0, None, None, False, legato, octave_shift + + note_str = m.group('note') + chord_type = m.group('chord') + dur_str = m.group('duration') + vel_str = m.group('velocity') + + if note_str is None: + return [], 0.0, 0, None, None, False, legato, octave_shift + + note = int(note_str) + is_chord = chord_type is not None + duration = _parse_duration(dur_str) if dur_str else 0.0 + if duration == 0.0: + duration = default_duration + velocity = int(vel_str) if vel_str else default_vel + + # midi_notes will be resolved later (needs octave context) + midi_notes = [] # placeholder + + return midi_notes, duration, velocity, note, chord_type, is_chord, legato, octave_shift + + +# ── Note → MIDI resolution ────────────────────────────────────────── + +def _resolve_note_to_midi(note: int, chord_type: str | None, is_chord: bool, + octave: int, octave_shift: int = 0) -> list[int]: + """Resolve a chromatic note (0-11) to MIDI note numbers. + + Args: + note: Chromatic semitone (0=C, 1=C#, ..., 11=B) + chord_type: Chord type string (e.g., "M", "m7m") or None + is_chord: Whether to play full chord voicing + octave: Base octave (0 = C at MIDI 12) + octave_shift: Per-note octave offset + """ + # MIDI note = (octave + 1) * 12 + note + # Example: octave=4, note=0 → C4 = MIDI 60 + base_midi = (octave + 1 + octave_shift) * 12 + note + + if is_chord and chord_type: + intervals = CHORDS.get(chord_type, [0]) + return [base_midi + interval for interval in intervals] + else: + return [base_midi] + + +# ── Main parser ────────────────────────────────────────────────────── + +def parse_gondry(path: str | Path, header: VoiceHeader) -> list[GondryEvent]: + """Parse a .flsp file into a list of GondryEvents. + + Each `---` line is a beat boundary. + Lines between dividers are subdivisions of that beat. + """ + with open(path) as f: + lines = f.readlines() + + events: list[GondryEvent] = [] + beat_position = 0.0 + current_section: list[str] = [] # lines between dividers + + def _process_section(section_lines: list[str], beat_pos: float): + """Process lines that fall within one beat.""" + # Empty section = one full beat of rest (don't skip!) + if not section_lines: + events.append(GondryEvent( + beat_position=beat_pos, + duration_beats=1.0, + midi_notes=[], + velocity=0, + )) + return + + subdivision = len(section_lines) # how many parts this beat is split into + ticks_per_subdivision = 1.0 / subdivision # in beats + + for i, line in enumerate(section_lines): + sub_beat_pos = beat_pos + (i * ticks_per_subdivision) + + parsed_notes, duration, velocity, note, chord_type, is_chord, legato, octave_shift = _parse_cell( + line, + default_vel=100, + default_duration=ticks_per_subdivision, + ) + + if note is not None: + # Use per-channel octave if specified, otherwise header octave + octave = header.channel_octave + midi_notes = _resolve_note_to_midi(note, chord_type, is_chord, octave, octave_shift) + else: + # rest — but still emit legato state changes + if legato is not None: + events.append(GondryEvent( + beat_position=sub_beat_pos, + duration_beats=0.0, + midi_notes=[], + velocity=0, + legato=legato, + )) + continue + + events.append(GondryEvent( + beat_position=sub_beat_pos, + duration_beats=duration, + midi_notes=midi_notes, + velocity=velocity, + note=note, + chord_type=chord_type, + is_chord=is_chord, + legato=legato, + octave_shift=octave_shift, + )) + + for line in lines: + stripped = line.strip() + + # Skip empty lines and line comments + if not stripped or stripped.startswith('#'): + continue + + # Beat divider + if stripped.startswith('---'): + _process_section(current_section, beat_position) + current_section = [] + beat_position += 1.0 + continue + + # Strip leading "- " for subdivision lines + if stripped.startswith('- '): + stripped = stripped[2:].strip() + + # Inline comments with // + if '//' in stripped: + stripped = stripped.split('//')[0].strip() + + if stripped: + current_section.append(stripped) + + # Don't forget the last section + _process_section(current_section, beat_position) + + # Sort by beat position + events.sort(key=lambda e: e.beat_position) + + return events diff --git a/tools/video.py b/tools/video.py @@ -0,0 +1,208 @@ +"""Generate video with animated disks from bass_circles clips. + +Usage: + python -m gondry.video projects/bass_circles +""" + +import csv +import sys +from pathlib import Path +from PIL import Image, ImageDraw +import random +import subprocess + +from .parser import parse_header, parse_gondry, VoiceHeader + + +def generate_video(project_dir: str, width: int = 1920, height: int = 1080): + """Generate video with animated disks from clip CSVs.""" + project = Path(project_dir) + clip_dir = project / "clip" + partition_dir = project / "partition" + render_dir = project / "render" + render_dir.mkdir(exist_ok=True) + + # Parse header and events to get note durations + header_path = partition_dir / "header.json" + header = parse_header(header_path) + bpm = header.bpm + + # Calculate fps from BPM + from .fps import bpm_to_fps + fps_info = bpm_to_fps(bpm) + fps = int(fps_info["fps"]) + frames_per_beat = fps_info["frames_per_beat"] + + print(f"BPM: {bpm} | FPS: {fps} | Frames/beat: {frames_per_beat}") + + # Parse all gondry files to get note durations + gondry_files = sorted(partition_dir.glob("*.flsp")) + note_durations = {} # (start_frame, note) -> duration_frames + + for gondry_path in gondry_files: + voice_name = gondry_path.stem + channel_config = None + for ch in header.channels: + if ch["name"] == voice_name: + channel_config = ch + break + if channel_config is None: + continue + + voice_header = VoiceHeader( + project=header.project, + bpm=header.bpm, + octave=header.octave, + mode=header.mode, + channels=header.channels, + channel_name=voice_name, + channel_config=channel_config, + ) + + events = parse_gondry(gondry_path, voice_header) + sec_per_beat = 60.0 / bpm + sec_per_frame = 1.0 / fps + + for ev in events: + if not ev.midi_notes: + continue + start_sec = ev.beat_position * sec_per_beat + duration_sec = ev.duration_beats * sec_per_beat + start_frame = int(start_sec / sec_per_frame) + duration_frames = int(duration_sec / sec_per_frame) + + for note in ev.midi_notes: + note_durations[(start_frame, note)] = duration_frames + + print(f"Note events: {len(note_durations)}") + + # Collect all frames from all clips + all_frames = [] + for clip_path in sorted(clip_dir.glob("bar_*.csv")): + with open(clip_path, 'r') as f: + reader = csv.reader(f, delimiter=';') + header_row = next(reader) + voice_names = header_row[1:] + + for row in reader: + frame_data = {} + for i, voice_name in enumerate(voice_names): + notes_str = row[i + 1] + if notes_str: + notes = [int(n) for n in notes_str.split(',')] + frame_data[voice_name] = notes + else: + frame_data[voice_name] = [] + all_frames.append(frame_data) + + print(f"Total frames: {len(all_frames)}") + + # Track active notes and their properties + active_notes = {} # note -> {start_frame, duration, color, x, y} + colors = [(255, 0, 0), (0, 255, 0), (0, 0, 255)] # red, green, blue + color_idx = 0 + + # Disk properties + disk_radius = height // 5 + center_x, center_y = width // 2, height // 2 + spread = min(width, height) // 4 + + # Generate frames + frames_dir = render_dir / "frames" + frames_dir.mkdir(exist_ok=True) + + for frame_idx, frame_data in enumerate(all_frames): + # Create black background + img = Image.new('RGB', (width, height), (0, 0, 0)) + draw = ImageDraw.Draw(img) + + # Get current notes + current_notes = set() + for voice_name, notes in frame_data.items(): + current_notes.update(notes) + + # Check for new notes + for note in current_notes: + if note not in active_notes: + # Look up actual duration + duration = note_durations.get((frame_idx, note), fps) # fallback to 1 sec + + x = center_x + random.randint(-spread, spread) + y = center_y + random.randint(-spread, spread) + active_notes[note] = { + 'start_frame': frame_idx, + 'duration': duration, + 'color': colors[color_idx % len(colors)], + 'x': x, + 'y': y, + } + color_idx += 1 + + # Draw active disks + notes_to_remove = [] + for note, props in active_notes.items(): + elapsed = frame_idx - props['start_frame'] + duration = props['duration'] + + if elapsed >= duration: + notes_to_remove.append(note) + continue + + # Calculate fade (1.0 -> 0.0) + fade = 1.0 - (elapsed / duration) + + # Calculate opacity + opacity = int(255 * fade) + + # Get color with opacity + r, g, b = props['color'] + color = (r, g, b, opacity) + + # Draw disk + x, y = props['x'], props['y'] + draw.ellipse( + [x - disk_radius, y - disk_radius, x + disk_radius, y + disk_radius], + fill=color + ) + + # Remove expired notes + for note in notes_to_remove: + del active_notes[note] + + # Save frame + frame_path = frames_dir / f"frame_{frame_idx:04d}.png" + img.save(frame_path) + + if frame_idx % 100 == 0: + print(f" Frame {frame_idx}/{len(all_frames)}") + + print(f"\nFrames saved to {frames_dir}") + + # Generate video with ffmpeg + video_path = render_dir / "bass_circles.mp4" + cmd = [ + "ffmpeg", "-y", + "-framerate", str(fps), + "-i", str(frames_dir / "frame_%04d.png"), + "-c:v", "libx264", + "-pix_fmt", "yuv420p", + "-crf", "23", + str(video_path) + ] + + print(f"\nGenerating video...") + result = subprocess.run(cmd, capture_output=True, text=True) + if result.returncode != 0: + print(f"FFmpeg error: {result.stderr[-500:]}") + return None + + print(f"Video saved to {video_path}") + return video_path + + +if __name__ == "__main__": + if len(sys.argv) < 2: + print("Usage: python -m gondry.video <project_dir>") + sys.exit(1) + + generate_video(sys.argv[1])