Full Code of subalterngames/cacophony for AI

main 34a997d884aa cached
162 files
656.6 KB
163.1k tokens
802 symbols
1 requests
Download .txt
Showing preview only (700K chars total). Download the full file or copy to clipboard to get everything.
Repository: subalterngames/cacophony
Branch: main
Commit: 34a997d884aa
Files: 162
Total size: 656.6 KB

Directory structure:
gitextract_lgvougrd/

├── .github/
│   └── workflows/
│       ├── build.yaml
│       └── test.yaml
├── .gitignore
├── .vscode/
│   └── settings.json
├── Cargo.toml
├── LICENSE
├── README.md
├── audio/
│   ├── Cargo.toml
│   ├── src/
│   │   ├── command.rs
│   │   ├── conn.rs
│   │   ├── decayer.rs
│   │   ├── export/
│   │   │   ├── export_setting.rs
│   │   │   ├── export_state.rs
│   │   │   ├── export_type.rs
│   │   │   ├── exportable.rs
│   │   │   ├── metadata.rs
│   │   │   └── multi_file_suffix.rs
│   │   ├── export.rs
│   │   ├── exporter.rs
│   │   ├── lib.rs
│   │   ├── midi_event_queue.rs
│   │   ├── play_state.rs
│   │   ├── player.rs
│   │   ├── program.rs
│   │   ├── synth_state.rs
│   │   ├── timed_midi_event.rs
│   │   └── types.rs
│   └── tests/
│       └── CT1MBGMRSV1.06.sf2
├── changelog.md
├── common/
│   ├── Cargo.toml
│   └── src/
│       ├── args.rs
│       ├── config.rs
│       ├── edit_mode.rs
│       ├── font.rs
│       ├── fraction.rs
│       ├── index.rs
│       ├── indexed_values.rs
│       ├── input_state.rs
│       ├── lib.rs
│       ├── midi_track.rs
│       ├── music.rs
│       ├── music_panel_field.rs
│       ├── note.rs
│       ├── open_file/
│       │   ├── child_paths.rs
│       │   ├── extension.rs
│       │   ├── file_and_directory.rs
│       │   ├── file_or_directory.rs
│       │   └── open_file_type.rs
│       ├── open_file.rs
│       ├── panel_type.rs
│       ├── paths.rs
│       ├── paths_state.rs
│       ├── piano_roll_mode.rs
│       ├── select_mode.rs
│       ├── sizes.rs
│       ├── state.rs
│       ├── time.rs
│       ├── u64_or_f32.rs
│       └── view.rs
├── data/
│   ├── CT1MBGMRSV1.06.sf2
│   ├── attribution.txt
│   ├── config.ini
│   ├── icon
│   └── text.csv
├── html/
│   ├── index.html
│   ├── install.html
│   ├── limitations.html
│   ├── manifesto.html
│   ├── me.html
│   ├── privacy.html
│   ├── roadmap.html
│   ├── style.css
│   └── user_guide.html
├── input/
│   ├── Cargo.toml
│   └── src/
│       ├── debug_input_event.rs
│       ├── input_event.rs
│       ├── keys.rs
│       ├── lib.rs
│       ├── midi_binding.rs
│       ├── midi_conn.rs
│       ├── note_on.rs
│       └── qwerty_binding.rs
├── io/
│   ├── Cargo.toml
│   └── src/
│       ├── abc123.rs
│       ├── export_panel.rs
│       ├── export_settings_panel.rs
│       ├── import_midi.rs
│       ├── io_command.rs
│       ├── lib.rs
│       ├── links_panel.rs
│       ├── music_panel.rs
│       ├── open_file_panel.rs
│       ├── panel.rs
│       ├── piano_roll/
│       │   ├── edit.rs
│       │   ├── edit_mode_deltas.rs
│       │   ├── piano_roll_panel.rs
│       │   ├── piano_roll_sub_panel.rs
│       │   ├── select.rs
│       │   ├── time.rs
│       │   └── view.rs
│       ├── piano_roll.rs
│       ├── popup.rs
│       ├── quit_panel.rs
│       ├── save.rs
│       ├── snapshot.rs
│       └── tracks_panel.rs
├── py/
│   ├── build.py
│   ├── itch_changelog.py
│   └── macroquad_icon_creator.py
├── render/
│   ├── Cargo.toml
│   └── src/
│       ├── color_key.rs
│       ├── drawable.rs
│       ├── export_panel.rs
│       ├── export_settings_panel.rs
│       ├── field_params/
│       │   ├── boolean.rs
│       │   ├── boolean_corners.rs
│       │   ├── key_input.rs
│       │   ├── key_list.rs
│       │   ├── key_list_corners.rs
│       │   ├── key_width.rs
│       │   ├── label.rs
│       │   ├── label_rectangle.rs
│       │   ├── label_ref.rs
│       │   ├── line.rs
│       │   ├── list.rs
│       │   ├── panel_background.rs
│       │   ├── rectangle.rs
│       │   ├── rectangle_pixel.rs
│       │   ├── util.rs
│       │   └── width.rs
│       ├── field_params.rs
│       ├── lib.rs
│       ├── links_panel.rs
│       ├── main_menu.rs
│       ├── music_panel.rs
│       ├── open_file_panel.rs
│       ├── page.rs
│       ├── page_position.rs
│       ├── panel.rs
│       ├── panels.rs
│       ├── piano_roll_panel/
│       │   ├── multi_track.rs
│       │   ├── piano_roll_rows.rs
│       │   ├── top_bar.rs
│       │   ├── viewable_notes.rs
│       │   └── volume.rs
│       ├── piano_roll_panel.rs
│       ├── popup.rs
│       ├── quit_panel.rs
│       ├── renderer.rs
│       ├── tracks_panel.rs
│       └── types.rs
├── src/
│   └── main.rs
├── test_files/
│   ├── child_paths/
│   │   ├── test_0.cac
│   │   ├── test_1.cac
│   │   └── test_2.CAC
│   └── ubuntu20.04/
│       └── speechd.conf
└── text/
    ├── Cargo.toml
    └── src/
        ├── lib.rs
        ├── tooltips.rs
        ├── tts.rs
        ├── tts_string.rs
        └── value_map.rs

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/build.yaml
================================================
name: build

on:
  workflow_dispatch:
    inputs:
      version:
        description: Build version
        required: True

jobs:
  build-ubuntu:
    strategy:
      matrix:
        include:
          - os: ubuntu-20.04
            version: 20
            feature: speech_dispatcher_0_9
          - os: ubuntu-22.04
            version: 22
            feature: speech_dispatcher_0_11
          - os: ubuntu-24.04
            version: 24
            feature: speech_dispatcher_0_11
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v3
      - name: apt update
        run: sudo apt-get update
      - name: install
        run: sudo apt install wget clang libspeechd-dev pkg-config libssl-dev alsa librust-alsa-sys-dev
      - name: Install Rust
        uses: dtolnay/rust-toolchain@stable
        with:
          toolchain: stable
      - name: cargo build
        run: cargo build --release --features ${{ matrix.feature }}
      - name: Create release directory
        run: mkdir -p cacophony/data
      - name: Copy executable
        run: cp target/release/cacophony cacophony/cacophony
      - name: Copy data/
        run: cp -R data cacophony
      - name: Download butler
        run: wget -O butler.zip https://broth.itch.ovh/butler/linux-amd64/LATEST/archive/default
      - name: Unzip butler
        run: unzip butler.zip
      - run: chmod +x butler
      - name: butler login
        env:
          BUTLER_API_KEY: ${{ secrets.BUTLER_API_KEY }}
        run: ./butler login
      - name: butler push
        env:
          BUTLER_API_KEY: ${{ secrets.BUTLER_API_KEY }}
        run: ./butler push cacophony subalterngames/cacophony:ubuntu${{ matrix.version }} --userversion=${{ inputs.version }}
  build-macos:
    strategy:
      matrix:
        include:
          - os: macos-12
            version: 12
          - os: macos-13
            version: 13
          - os: macos-14
            version: 14
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v3
      - name: Install Rust
        uses: dtolnay/rust-toolchain@stable
        with:
          toolchain: stable
      - name: Install cargo bundle
        run: cargo install cargo-bundle
      - name: cargo build
        run: cargo bundle --release
      - name: Download butler
        run: wget -O butler.zip https://broth.itch.ovh/butler/darwin-amd64/LATEST/archive/default
      - name: Unzip butler
        run: unzip butler.zip
      - run: chmod +x butler
      - name: butler login
        env:
          BUTLER_API_KEY: ${{ secrets.BUTLER_API_KEY }}
        run: ./butler login
      - name: butler push
        env:
          BUTLER_API_KEY: ${{ secrets.BUTLER_API_KEY }}
        run: ./butler push target/release/bundle/osx/Cacophony.app subalterngames/cacophony:macos${{ matrix.version }} --userversion=${{ inputs.version }}
  build-windows:
    name: Windows
    runs-on: windows-latest
    steps:
      - uses: actions/checkout@v3
      - name: Install Rust
        uses: dtolnay/rust-toolchain@stable
        with:
          toolchain: stable
      - name: cargo build
        run: cargo build --release
      - name: Download butler
        run: Invoke-WebRequest -Uri https://broth.itch.ovh/butler/windows-amd64/LATEST/archive/default -OutFile butler.zip
        shell: powershell
      - name: Unzip butler
        run: Expand-Archive -Path butler.zip -DestinationPath .
        shell: powershell
      - name: butler login
        env:
          BUTLER_API_KEY: ${{ secrets.BUTLER_API_KEY }}
        run: ./butler.exe login
      - name: Create release directory
        run: New-Item -Path cacophony -ItemType directory
        shell: powershell
      - name: Copy cacophony.exe
        run: Copy-Item target/release/cacophony.exe cacophony/cacophony.exe
        shell: powershell
      - name: Copy data/
        run: Copy-Item -Recurse data cacophony
        shell: powershell
      - name: butler push
        env:
          BUTLER_API_KEY: ${{ secrets.BUTLER_API_KEY }}
        run: ./butler.exe push cacophony subalterngames/cacophony:windows --userversion=${{ inputs.version }}


================================================
FILE: .github/workflows/test.yaml
================================================
name: test

on:
  push:
    branches-ignore:
      - main
    paths-ignore:
      - "doc/**"
      - ".vscode/**"
      - "html/**"
      - "data/**"
      - "promo/**"
      - "py/**"
      - "**.md"

# Make sure CI fails on all warnings, including Clippy lints
env:
  RUSTFLAGS: "-Dwarnings"

jobs:
  # Check for formatting and clippy
  lint:
    name: Lint
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      # Setup Rust toolchain
      - uses: actions-rs/toolchain@v1
        with:
          profile: minimal
          toolchain: stable
          components: rustfmt
          override: true

      # Install the required dependencies
      - name: Install test dependencies
        run: sudo apt install clang cmake speech-dispatcher libspeechd-dev pkg-config libssl-dev alsa librust-alsa-sys-dev -y

      # Cargo fmt
      - uses: actions-rs/cargo@v1
        with:
          command: fmt
          args: --all -- --check

      # Cargo clippy
      - uses: actions-rs/cargo@v1
        with:
          command: clippy
          args: --all-targets --features speech_dispatcher_0_11

  # Run test check on Linux, macOS, and Windows
  test:
    name: Test
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: true
      matrix:
        os: [ubuntu-24.04, ubuntu-22.04, ubuntu-20.04, macOS-14, macOS-13, macOS-12, windows-latest]
    steps:
      # Checkout the branch being tested
      - uses: actions/checkout@v4

      # Install rust stable
      - uses: dtolnay/rust-toolchain@master
        with:
          toolchain: stable

      # Install the required dependencies on Ubuntu
      - name: Install test dependencies
        run: sudo apt install clang cmake speech-dispatcher libspeechd-dev pkg-config libssl-dev alsa librust-alsa-sys-dev -y
        if: ${{ contains(matrix.os, 'ubuntu') }}

      # Test for Ubuntu 20.04. Copy a valid speechd.conf file and then test.
      - name: Test
        run: |
          sudo cp test_files/ubuntu20.04/speechd.conf /etc/speech-dispatcher/speechd.conf
          cargo test --all --features speech_dispatcher_0_9 -- --nocapture
        if: matrix.os == 'ubuntu-20.04'

      # Test for Ubuntu 22.04
      - name: Test
        run: cargo test --all --features speech_dispatcher_0_11 -- --nocapture
        if: matrix.os == 'ubuntu-22.04' || matrix.os == 'ubuntu-24.04'

      # Test for windows and mac
      - name: Test
        run: cargo test --all -- --nocapture
        if: ${{ !contains(matrix.os, 'ubuntu') }}


================================================
FILE: .gitignore
================================================
.idea/
*.dll
*.pyc
temp/
scratch.py
synthesizers/
*/target/
target/
ms_basic.sf3
events.txt
.DS_STORE
py/credentials/
venv/

================================================
FILE: .vscode/settings.json
================================================
{
    "rust-analyzer.linkedProjects": [
        "./audio/Cargo.toml",
        "./common/Cargo.toml",
        "./input/Cargo.toml",
        "./io/Cargo.toml",
        "./render/Cargo.toml",
        "./text/Cargo.toml",
    ],
}

================================================
FILE: Cargo.toml
================================================
[workspace]
members = ["audio", "common", "input", "io", "render", "text"]

[workspace.package]
version = "0.2.7"
authors = ["Esther Alter <subalterngames@gmail.com>"]
description = "A minimalist and ergonomic MIDI sequencer"
documentation = "https://github.com/subalterngames/cacophony"
edition = "2021"

[workspace.dependencies]
serde_json = "1.0"
rust-ini = "0.18"
directories = "5.0.1"
midir = "0.9.1"
csv = "1.2.1"
cpal = "0.13.1"
hound = "3.5.0"
chrono = "0.4.31"
vorbis-encoder = "0.1.4"
oggvorbismeta = "0.1.0"
strum = "0.24"
strum_macros = "0.24"
edit = "0.1.4"
regex = "1.9.1"
parking_lot = "0.12.1"
num-traits = "0.2.16"
webbrowser = "1.0.1"
lazy_static = "1.4.0"
midly = "0.5.3"
flacenc = "0.3.0"
metaflac = "0.2.5"
mp3lame-encoder = "0.1.4"

[workspace.dependencies.clap]
version = "4.4.7"
default-features = false
features = ["derive", "string", "env", "error-context", "help", "std", "suggestions", "usage"]

[workspace.dependencies.colorgrad]
version = "0.6.2"
default-features = false
features = []

[workspace.dependencies.id3]
version = "1.7.0"
default-features = false
features = []

[workspace.dependencies.ureq]
version = "2.9.7"
default-features = false
features = ["tls"]

[workspace.dependencies.hashbrown]
version = "0.13.2"
features = ["serde"]

[workspace.dependencies.serde]
version = "1.0.153"
default-features = false
features = ["derive"]

[workspace.dependencies.macroquad]
version = "0.4.4"
default-features = false
features = []
git = "https://github.com/not-fl3/macroquad.git"
rev = "6d9685d"

[workspace.dependencies.oxisynth]
version = "0.0.3"
features = []
git = "https://github.com/subalterngames/OxiSynth.git"
branch = "midi_event_copy_clone"

[workspace.dependencies.tts]
version = "0.26.1"
default-features = false

[features]
speech_dispatcher_0_11 = ["text/speech_dispatcher_0_11"]
speech_dispatcher_0_9 = ["text/speech_dispatcher_0_9"]

[package]
name = "cacophony"
version = "0.2.7"
authors = ["Esther Alter <subalterngames@gmail.com>"]
description = "A minimalist and ergonomic MIDI sequencer"
documentation = "https://github.com/subalterngames/cacophony"
edition = "2021"

[dependencies]
macroquad = { workspace = true }
parking_lot = { workspace = true }
ureq = { workspace = true }
regex = { workspace = true }
rust-ini = { workspace = true }
clap = { workspace = true }

[dependencies.audio]
path = "audio"

[dependencies.common]
path = "common"

[dependencies.input]
path = "input"

[dependencies.io]
path = "io"

[dependencies.render]
path = "render"

[dependencies.text]
path = "text"

[package.metadata.bundle]
name = "Cacophony"
identifier = "com.subalterngames.cacophony"
icon = ["icon/32.png", "icon/64.png", "icon/128.png", "icon/256.png"]
version = "0.2.7"
resources = ["data/*"]
copyright = "Copyright (c) Subaltern Games LLC 2023. All rights reserved."
short_description = "A minimalist and ergonomic MIDI sequencer."
long_description = """
Cacophony is a minimalist and ergonomic MIDI sequencer. It's minimalist in that it doesn't have a lot of functionality MIDI sequencers have. It's ergonomic in that there is no mouse input and a very clean interface, allowing you to juggle less inputs and avoid awkward mouse motions.

I made Cacophony because I want to make music in a very particular way that I couldn't find anywhere.

Cacophony's mascot is Casey the Transgender Cacodemon. No official artwork of Casey exists because I don't want to be cursed.
"""
deb_depends = []
osx_frameworks = []


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2023 Esther Alter

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
![Cacophony!](doc/images/banner.png)

**Cacophony is a minimalist and ergonomic MIDI sequencer.** It's minimalist in that it doesn't have a lot of functionality MIDI sequencers have. It's ergonomic in that there is no mouse input and a very clean interface, allowing you to juggle less inputs and avoid awkward mouse motions.

![Screenshot of Cacophony](doc/images/screenshot.jpg)

[Buy Cacophony](https://subalterngames.itch.io/cacophony) (or compile it yourself).

[User-end documentation.](https://subalterngames.com/cacophony)

[Discord Server](https://discord.gg/fUapDXgTYj)

## How to compile

I compile Cacophony with Rust 1.74.0 for Linux, MacOS, or Windows. Below is a list of operating systems I've tested:

Linux:

- Ubuntu 18.04 i386 with X11
- Ubuntu 18.04 x64 with X11
- Ubuntu 20.04 x64 with X11
- Ubuntu 22.04 x64 with X11
- Ubuntu 24.04 x64 with X11

MacOS:

- Catalina 10.15.7 x64
- Ventura 13.2.1 Apple Silicon

Windows:

- Windows 10 x64

### All platforms

1. Install Rust (stable)
2. Clone this repo

### Linux

#### Debian 11

1. `apt install clang cmake speech-dispatcher libspeechd-dev pkg-config libssl-dev librust-alsa-sys-dev`
2. `cargo build --release --features speech_dispatcher_0_9`

#### Debian 12

1. `apt install clang cmake speech-dispatcher libspeechd-dev pkg-config libssl-dev librust-alsa-sys-dev`
2. `cargo build --release --features speech_dispatcher_0_11`

#### Ubuntu 18

1. `apt install clang cmake speech-dispatcher libspeechd-dev pkg-config libssl-dev alsa`
2. `cargo build --release --features speech_dispatcher_0_9`

#### Ubuntu 20

1. `apt install clang cmake speech-dispatcher libspeechd-dev pkg-config libssl-dev alsa librust-alsa-sys-dev`
2. `cargo build --release --features speech_dispatcher_0_9`

#### Ubuntu 22 and 24

1. `apt install clang cmake speech-dispatcher libspeechd-dev pkg-config libssl-dev alsa librust-alsa-sys-dev`
2. `cargo build --release --features speech_dispatcher_0_11`

### MacOS

1. `cargo install cargo-bundle`
2. `cargo bundle --release`

### Windows

1. `cargo build --release`

## Set the `data` directory

Cacophony's default data directory is located at `../data`. To set the default data directory at *compile time*, set the `CACOPHONY_BUILD_DATA_DIR` enviroment variable:

```bash
export CACOPHONY_BUILD_DATA_DIR=/usr/share/cacophony
cargo build --release
```

## Tests

To test, just `cargo test --all`.

Sometimes when debugging, it's useful to create the same initial setup every time. To do this, you can pass input events in like this: `cargo run -- --events events.txt`

...where the contents of `events.txt` is something like:

```
NextPanel
AddTrack
EnableSoundFontPanel
SelectFile
```

## How to run

You can run Cacophony like any other application or you can use Rust's `cargo run` to compile and execute.

### Linux

There are two ways to run Cacophony:

1. Copy + paste `data/` into the output directory (`target/release/`). Open a terminal in `release/` and run `./Cacophony` .
2. Instead of `cargo build --release`, run `cargo run --release` Include the `--features` listed above, for example `cargo build --release --features speech_dispatcher_0_11` on Ubuntu 22

### MacOS

There are two ways to run Cacophony:

1. After compiling, double-click `Cacophony.app` (located in `./target/release/`)
2. `cargo run --release` This will compile and launch the application but it won't create a .app

### Windows

There are two ways to run Cacophony:

1. Copy + paste `data/` into the output directory (`target/release/`) and double-click `Cacophony.exe` (located in `release/`)
2. Instead of `cargo build --release`, run `cargo run --release`

## Upload

Assuming that you are Esther Alter and you have the relevant credentials on your computer, you can upload the website and create itch.io builds by doing this:

1. `cd py`
2. `py -3 build.py`


================================================
FILE: audio/Cargo.toml
================================================
[package]
name = "audio"
version.workspace = true
authors.workspace = true
description.workspace = true
documentation.workspace = true
edition.workspace = true

[dependencies]
cpal = { workspace = true }
hound = { workspace = true }
id3 = { workspace = true }
mp3lame-encoder = { workspace = true }
chrono = { workspace = true }
midly = { workspace = true }
vorbis-encoder = { workspace = true }
oggvorbismeta = { workspace = true }
oxisynth = { workspace = true }
serde = { workspace = true }
hashbrown = { workspace = true }
parking_lot = { workspace = true }
flacenc = { workspace = true }
metaflac = { workspace = true }

[dependencies.common]
path = "../common"


================================================
FILE: audio/src/command.rs
================================================
use std::path::PathBuf;

/// A command for the synthesizer.
#[derive(Debug, Eq, PartialEq, Clone)]
pub enum Command {
    /// Load a SoundFont file.
    LoadSoundFont { channel: u8, path: PathBuf },
    /// Set a program.
    SetProgram {
        channel: u8,
        path: PathBuf,
        bank_index: usize,
        preset_index: usize,
    },
    /// Set the program to None.
    UnsetProgram { channel: u8 },
    /// Set the overall gain.
    SetGain { gain: u8 },
}


================================================
FILE: audio/src/conn.rs
================================================
use crate::decayer::Decayer;
use crate::export::{ExportState, ExportType, Exportable, MultiFileSuffix};
use crate::exporter::Exporter;
use crate::play_state::PlayState;
use crate::types::SharedPlayState;
use crate::SharedExportState;
use crate::{
    midi_event_queue::MidiEventQueue, types::SharedSample, Command, Player, Program,
    SharedMidiEventQueue, SharedSynth, SynthState,
};
use common::open_file::Extension;
use common::{MidiTrack, Music, PathsState, State, Time, MAX_VOLUME};
use hashbrown::HashMap;
use oxisynth::{MidiEvent, SoundFont, SoundFontId, Synth};
use parking_lot::Mutex;
use std::fs::File;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::thread::spawn;

/// A convenient wrapper for a SoundFont.
struct SoundFontBanks {
    id: SoundFontId,
    /// The banks and their presets.
    banks: HashMap<u32, Vec<u8>>,
}

impl SoundFontBanks {
    pub fn new(font: SoundFont, synth: &mut SharedSynth) -> Self {
        let mut banks: HashMap<u32, Vec<u8>> = HashMap::new();
        (0u32..=128u32).for_each(|b| {
            let presets: Vec<u8> = (0u8..128)
                .filter(|p| font.preset(b, *p).is_some())
                .collect();
            if !presets.is_empty() {
                banks.insert(b, presets);
            }
        });
        let mut synth = synth.lock();
        let id = synth.add_font(font, true);
        Self { id, banks }
    }
}

/// The connects used by an external function.
pub struct Conn {
    /// The current export state, if any.
    pub export_state: SharedExportState,
    /// The playback framerate.
    pub framerate: f32,
    /// The audio player. This is here so we don't drop it.
    _player: Option<Player>,
    /// The most recent sample.
    /// `render::MainMenu` uses this to for its power bars.
    pub sample: SharedSample,
    /// A shared Oxisynth synthesizer.
    /// The `Conn` uses this to send MIDI events and export.
    /// The `Player` uses this to write samples to the output buffer.
    synth: SharedSynth,
    /// A queue of scheduled MIDI events.
    /// The `Conn` can add to this.
    /// The `Player` can read this and remove events.
    midi_event_queue: SharedMidiEventQueue,
    /// A HashMap of loaded SoundFonts. Key = The path to a .sf2 file.
    soundfonts: HashMap<PathBuf, SoundFontBanks>,
    /// Metadata for all SoundFont programs.
    pub state: SynthState,
    /// Export settings.
    pub exporter: Exporter,
    /// A flag that `Player` uses to decide how to write samples to the output buffer.
    pub play_state: SharedPlayState,
}

impl Default for Conn {
    fn default() -> Self {
        // Set the synthesizer.
        let mut synth = Synth::default();
        synth.set_gain(1.0);
        let synth = Arc::new(Mutex::new(synth));

        // Create other shared data.
        let midi_event_queue = Arc::new(Mutex::new(MidiEventQueue::default()));
        let sample = Arc::new(Mutex::new((0.0, 0.0)));
        let play_state = Arc::new(Mutex::new(PlayState::NotPlaying));

        // Create the player.
        let player_synth = Arc::clone(&synth);
        let player_midi_event_queue = Arc::clone(&midi_event_queue);
        let player_sample = Arc::clone(&sample);
        let player_play_state = Arc::clone(&play_state);
        let player = Player::new(
            player_midi_event_queue,
            player_synth,
            player_sample,
            player_play_state,
        );

        // Get the framerate.
        let framerate = match &player {
            Some(player) => player.framerate as f32,
            None => 0.0,
        };
        Self {
            export_state: Arc::new(Mutex::new(ExportState::NotExporting)),
            _player: player,
            framerate,
            sample,
            synth,
            midi_event_queue,
            soundfonts: HashMap::default(),
            state: SynthState::default(),
            exporter: Exporter::default(),
            play_state,
        }
    }
}

impl Conn {
    /// Do all note-on events created by user input on this app frame.
    pub fn note_ons(&mut self, state: &State, note_ons: &[[u8; 3]]) {
        if let Some(track) = state.music.get_selected_track() {
            if !note_ons.is_empty() {
                let mut synth = self.synth.lock();
                let gain = track.gain as f32 / MAX_VOLUME as f32;
                for note_on in note_ons.iter() {
                    let _ = synth.send_event(MidiEvent::NoteOn {
                        channel: track.channel,
                        key: note_on[1],
                        vel: (note_on[2] as f32 * gain) as u8,
                    });
                }
                // Play audio.
                let mut play_state = self.play_state.lock();
                *play_state = PlayState::Decaying;
            }
        }
    }

    /// Do all note-off events created by user input on this app frame.
    pub fn note_offs(&mut self, state: &State, note_offs: &[u8]) {
        if let Some(track) = state.music.get_selected_track() {
            if !note_offs.is_empty() {
                let mut synth = self.synth.lock();
                for note_off in note_offs.iter() {
                    let _ = synth.send_event(MidiEvent::NoteOff {
                        channel: track.channel,
                        key: *note_off,
                    });
                }
            }
        }
    }

    /// Execute a slice of commands sent from `io`.
    pub fn do_commands(&mut self, commands: &[Command]) {
        for command in commands.iter() {
            match command {
                Command::LoadSoundFont { channel, path } => {
                    match &self.soundfonts.get(path) {
                        // We already loaded this font.
                        Some(_) => {
                            self.set_program_default(*channel, path);
                        }
                        // Load the font.
                        None => match SoundFont::load(&mut File::open(path).unwrap()) {
                            Ok(font) => {
                                let banks = SoundFontBanks::new(font, &mut self.synth);
                                self.soundfonts.insert(path.clone(), banks);
                                // Set the default program.
                                self.set_program_default(*channel, path);
                                // Restore the other programs.
                                let programs = self.state.programs.clone();
                                for program in programs.iter().filter(|p| p.0 != channel) {
                                    if self.soundfonts.contains_key(&program.1.path) {
                                        let mut synth = self.synth.lock();
                                        synth
                                            .program_select(
                                                *program.0,
                                                self.soundfonts[&program.1.path].id,
                                                program.1.bank,
                                                program.1.preset,
                                            )
                                            .unwrap();
                                    }
                                }
                            }
                            Err(error) => {
                                panic!("Failed to load SoundFont: {:?}", error)
                            }
                        },
                    }
                }
                Command::SetProgram {
                    channel,
                    path,
                    bank_index,
                    preset_index,
                } => {
                    let soundfont = &self.soundfonts[path];
                    let banks = soundfont.banks.keys().copied().collect::<Vec<u32>>();
                    let bank = banks[*bank_index];
                    let preset = soundfont.banks[&bank][*preset_index];
                    let channel = *channel;
                    self.set_program(channel, path, bank, preset, soundfont.id);
                }
                Command::UnsetProgram { channel } => {
                    self.state.programs.remove(channel);
                }
                Command::SetGain { gain } => {
                    let mut synth = self.synth.lock();
                    synth.set_gain(*gain as f32 / MAX_VOLUME as f32);
                    self.state.gain = *gain;
                }
            }
        }
    }

    /// Start to play music if music isn't playing. Stop music if music is playing.
    pub fn set_music(&mut self, state: &State) {
        let play_state = *self.play_state.lock();
        match play_state {
            PlayState::NotPlaying | PlayState::Decaying => self.start_music(state),
            _ => self.stop_music(&state.music),
        }
    }

    pub fn exporting(&self) -> bool {
        *self.export_state.lock() != ExportState::NotExporting
    }

    /// When a new save file is loaded or a new file is opened, stop playing music if any music is playing.
    pub fn on_new_file(&mut self, state: &State) {
        let play_state = *self.play_state.lock();
        if let PlayState::Playing(_) = play_state {
            self.stop_music(&state.music);
        }
    }

    /// Schedule MIDI events and start to play music.
    fn start_music(&mut self, state: &State) {
        // Get the start time.
        let start = state
            .time
            .ppq_to_samples(state.time.playback, self.framerate);

        // Set the playback framerate.
        let mut synth = self.synth.lock();
        synth.set_sample_rate(self.framerate);
        drop(synth);

        let mut midi_event_queue = self.midi_event_queue.lock();
        // Clear the queue before adding new events.
        midi_event_queue.clear();
        // Enqueue note events.
        for track in state.music.get_playable_tracks().iter() {
            for note in track.get_playback_notes(state.time.playback) {
                // Note-on event.
                midi_event_queue.enqueue(
                    state.time.ppq_to_samples(note.start, self.framerate),
                    MidiEvent::NoteOn {
                        channel: track.channel,
                        key: note.note,
                        vel: note.velocity,
                    },
                );
                // Note-off event.
                midi_event_queue.enqueue(
                    state.time.ppq_to_samples(note.end, self.framerate),
                    MidiEvent::NoteOff {
                        channel: track.channel,
                        key: note.note,
                    },
                );
            }
        }
        // Sort the events by start time.
        midi_event_queue.sort();
        drop(midi_event_queue);

        // Play music.
        let mut play_state = self.play_state.lock();
        *play_state = PlayState::Playing(start);
    }

    /// Stop ongoing music.
    fn stop_music(&mut self, music: &Music) {
        let mut synth = self.synth.lock();
        for track in music.midi_tracks.iter() {
            if synth
                .send_event(MidiEvent::AllNotesOff {
                    channel: track.channel,
                })
                .is_ok()
                && synth
                    .send_event(MidiEvent::AllSoundOff {
                        channel: track.channel,
                    })
                    .is_ok()
            {}
        }
        drop(synth);
        // Let the audio decay.
        let mut play_state = self.play_state.lock();
        *play_state = PlayState::Decaying;
    }

    /// Set the synthesizer program to a default program.
    fn set_program_default(&mut self, channel: u8, path: &Path) {
        let soundfont = &self.soundfonts[path];
        // Get the bank info.
        let mut banks: Vec<u32> = soundfont.banks.keys().copied().collect();
        banks.sort();
        let bank = banks[0];
        let preset = soundfont.banks[&bank][0];
        // Select the default program.
        let id = self.soundfonts[path].id;
        self.set_program(channel, path, bank, preset, id);
    }

    /// Set the synthesizer program to a program.
    fn set_program(&mut self, channel: u8, path: &Path, bank: u32, preset: u8, id: SoundFontId) {
        let mut synth = self.synth.lock();
        if synth.program_select(channel, id, bank, preset).is_ok() {
            let soundfont = &self.soundfonts[path];
            // Get the bank info.
            let bank_index = soundfont.banks.keys().position(|&b| b == bank).unwrap();
            // Get the preset info.
            let preset_index = soundfont.banks[&bank]
                .iter()
                .position(|&p| p == preset)
                .unwrap();
            let preset_name = synth.channel_preset(channel).unwrap().name().to_string();
            let num_banks = soundfont.banks.len();
            let num_presets = soundfont.banks[&bank].len();
            let program = Program {
                path: path.to_path_buf(),
                num_banks,
                bank_index,
                bank,
                num_presets,
                preset_index,
                preset_name,
                preset,
            };
            // Remember the program.
            self.state.programs.insert(channel, program);
        }
    }

    pub fn start_export(&mut self, state: &State, paths_state: &PathsState) {
        let mut exportables = vec![];
        let tracks = state.music.get_playable_tracks();
        self.set_export_framerate();

        // Export each track as a separate file.
        if self.exporter.multi_file {
            for track in tracks {
                let mut events = MidiEventQueue::default();
                let mut t1 = 0;
                let gain = track.get_gain_f();
                self.enqueue_track_events(track, &state.time, &mut events, &mut t1, gain);
                events.sort();
                let suffix = Some(self.get_export_file_suffix(track));
                // Add an exportable.
                exportables.push(Exportable {
                    events,
                    total_samples: t1,
                    suffix,
                });
            }
        }
        // Export all tracks combined.
        else {
            let mut t1 = 0;
            let mut events = MidiEventQueue::default();
            for track in tracks {
                let gain = track.get_gain_f();
                self.enqueue_track_events(track, &state.time, &mut events, &mut t1, gain);
            }
            events.sort();
            // Add an exportable.
            exportables.push(Exportable {
                events,
                total_samples: t1,
                suffix: None,
            });
        }

        let export_state = Arc::clone(&self.export_state);
        let synth = Arc::clone(&self.synth);
        let exporter = self.exporter.clone();
        let path = paths_state.exports.get_path();
        let player_framerate = self.framerate;
        spawn(move || {
            Self::export(
                exportables,
                export_state,
                synth,
                exporter,
                path,
                player_framerate,
            )
        });
    }

    fn enqueue_track_events(
        &self,
        track: &MidiTrack,
        time: &Time,
        events: &mut MidiEventQueue,
        t1: &mut u64,
        gain: f32,
    ) {
        let framerate = self.exporter.framerate.get_f();
        for note in track.notes.iter() {
            // Note-on.
            events.enqueue(
                time.ppq_to_samples(note.start, framerate),
                MidiEvent::NoteOn {
                    channel: track.channel,
                    key: note.note,
                    vel: (note.velocity as f32 * gain) as u8,
                },
            );
            let end = time.ppq_to_samples(note.end, framerate);
            // This is the last known event.
            if *t1 < end {
                *t1 = end;
            }
            events.enqueue(
                end,
                MidiEvent::NoteOff {
                    channel: track.channel,
                    key: note.note,
                },
            );
        }
    }

    fn export(
        mut exportables: Vec<Exportable>,
        export_state: SharedExportState,
        synth: SharedSynth,
        exporter: Exporter,
        path: PathBuf,
        player_framerate: f32,
    ) {
        let mut decayer = Decayer::default();
        let extension: Extension = exporter.export_type.get().into();
        for exportable in exportables.iter_mut() {
            let total_samples = exportable.total_samples;
            // Get the audio buffers.
            let mut left = vec![0.0f32; total_samples as usize];
            let mut right = vec![0.0f32; total_samples as usize];
            // Set the initial wav export state.
            Self::set_export_state_wav(exportable, &export_state, 0);
            let mut synth = synth.lock();
            for t in 0..=total_samples {
                // Get and send each event at this time.
                for event in exportable.events.dequeue(t).iter() {
                    let _ = synth.send_event(*event);
                }
                // Set the export state.
                Self::set_export_state_wav(exportable, &export_state, t);
                // We are iterating to `total_samples` in order to get events at t=1.
                if t < total_samples {
                    let t = t as usize;
                    (left[t], right[t]) = synth.read_next();
                }
            }
            // Append decaying silence.
            Self::set_export_state(&export_state, ExportState::AppendingDecay);
            decayer.decaying = true;
            while decayer.decaying {
                decayer.decay_two_channels(&mut left, &mut right, &mut synth);
            }
            // Convert.
            Self::set_export_state(&export_state, ExportState::WritingToDisk);
            let filename = path.file_stem().unwrap().to_str().unwrap();
            let extension = extension.to_str(true);
            let path = match &exportable.suffix {
                Some(suffix) => path
                    .parent()
                    .unwrap()
                    .join(format!("{}_{}{}", filename, suffix, extension))
                    .to_path_buf(),
                None => path
                    .parent()
                    .unwrap()
                    .join(format!("{}{}", filename, extension))
                    .to_path_buf(),
            };
            let audio = [left, right];
            match &exporter.export_type.get() {
                ExportType::Mid => {
                    panic!("Tried exporting a .mid from the synthesizer")
                }
                // Export to a .wav file.
                ExportType::Wav => {
                    exporter.wav(&path, &audio);
                }
                ExportType::MP3 => {
                    exporter.mp3(&path, &audio);
                }
                ExportType::Ogg => {
                    exporter.ogg(&path, &audio);
                }
                ExportType::Flac => exporter.flac(&path, &audio),
            }
            // Done.
            Self::set_export_state(&export_state, ExportState::Done);
        }
        Self::set_export_state(&export_state, ExportState::NotExporting);
        synth.lock().set_sample_rate(player_framerate);
    }

    /// Set the exporter's framerate.
    fn set_export_framerate(&mut self) {
        let framerate = self.exporter.framerate.get_f();
        let mut synth = self.synth.lock();
        synth.set_sample_rate(framerate);
    }

    /// Set the number of exported wav samples.
    fn set_export_state_wav(
        exportable: &Exportable,
        export_state: &SharedExportState,
        exported_samples: u64,
    ) {
        let mut export_state = export_state.lock();
        *export_state = ExportState::WritingWav {
            total_samples: exportable.total_samples,
            exported_samples,
        }
    }

    /// Set the export state.
    fn set_export_state(export_state: &SharedExportState, state: ExportState) {
        let mut export_state = export_state.lock();
        *export_state = state;
    }

    fn get_export_file_suffix(&self, track: &MidiTrack) -> String {
        // Get the path for this track.
        match self.exporter.multi_file_suffix.get() {
            MultiFileSuffix::Channel => track.channel.to_string(),
            MultiFileSuffix::Preset => self
                .state
                .programs
                .get(&track.channel)
                .unwrap()
                .preset_name
                .clone(),
            MultiFileSuffix::ChannelAndPreset => format!(
                "{}_{}",
                track.channel,
                self.state.programs.get(&track.channel).unwrap().preset_name
            ),
        }
    }
}


================================================
FILE: audio/src/decayer.rs
================================================
use crate::SharedSynth;
use oxisynth::Synth;

/// Export this many bytes per decay chunk.
const DECAY_CHUNK_SIZE: usize = 4096;
///  Oxisynth usually doesn't zero out its audio. This is essentially an epsilon.
/// This is used to detect if the export is done.
const SILENCE: f32 = 1e-7;

/// Write audio samples during a decay.
pub(crate) struct Decayer {
    pub buffer: [f32; DECAY_CHUNK_SIZE],
    buffer_1: [f32; DECAY_CHUNK_SIZE],
    pub decaying: bool,
}

impl Default for Decayer {
    fn default() -> Self {
        Self {
            buffer: [0.0; DECAY_CHUNK_SIZE],
            buffer_1: [0.0; DECAY_CHUNK_SIZE],
            decaying: false,
        }
    }
}

impl Decayer {
    pub fn decay_shared(&mut self, synth: &SharedSynth, len: usize) {
        for sample in self.buffer[0..Self::get_len(len)].chunks_mut(2) {
            let mut synth = synth.lock();
            synth.write(sample);
        }
        self.set_decaying(len);
    }

    pub fn decay_two_channels(
        &mut self,
        left: &mut Vec<f32>,
        right: &mut Vec<f32>,
        synth: &mut Synth,
    ) {
        // Write samples.
        synth.write((self.buffer.as_mut(), self.buffer_1.as_mut()));
        left.extend(self.buffer);
        right.extend(self.buffer_1);
        self.decaying = self.buffer.iter().any(|s| s.abs() > SILENCE)
            || self.buffer_1.iter().any(|s| s.abs() > SILENCE);
    }

    fn set_decaying(&mut self, len: usize) {
        self.decaying = self.buffer[0..Self::get_len(len)]
            .iter()
            .any(|s| s.abs() > SILENCE);
    }

    fn get_len(len: usize) -> usize {
        if len <= DECAY_CHUNK_SIZE {
            len
        } else {
            DECAY_CHUNK_SIZE
        }
    }
}


================================================
FILE: audio/src/export/export_setting.rs
================================================
use serde::{Deserialize, Serialize};

/// Enum values for export settings.
#[derive(Debug, Eq, PartialEq, Copy, Clone, Default, Deserialize, Serialize)]
pub enum ExportSetting {
    #[default]
    Framerate,
    Title,
    Artist,
    Copyright,
    Album,
    TrackNumber,
    Genre,
    Comment,
    Mp3BitRate,
    Mp3Quality,
    OggQuality,
    MultiFile,
    MultiFileSuffix,
}


================================================
FILE: audio/src/export/export_state.rs
================================================
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum ExportState {
    NotExporting,
    /// Writing samples to a wav buffer.
    WritingWav {
        total_samples: u64,
        exported_samples: u64,
    },
    /// Writing decay to a wav buffer while the audio decays.
    AppendingDecay,
    /// Converting the wav buffer to another file type and write to disk.
    WritingToDisk,
    /// Done exporting.
    Done,
}


================================================
FILE: audio/src/export/export_type.rs
================================================
use common::open_file::Extension;
use serde::{Deserialize, Serialize};

/// This determines what we're exporting to.
#[derive(Debug, Eq, PartialEq, Copy, Clone, Deserialize, Serialize, Default, Hash)]
pub enum ExportType {
    #[default]
    Wav,
    Mid,
    MP3,
    Ogg,
    Flac,
}

impl From<ExportType> for Extension {
    fn from(val: ExportType) -> Self {
        match val {
            ExportType::Wav => Extension::Wav,
            ExportType::Mid => Extension::Mid,
            ExportType::MP3 => Extension::MP3,
            ExportType::Ogg => Extension::Ogg,
            ExportType::Flac => Extension::Flac,
        }
    }
}


================================================
FILE: audio/src/export/exportable.rs
================================================
use crate::midi_event_queue::MidiEventQueue;

pub(crate) struct Exportable {
    pub events: MidiEventQueue,
    pub total_samples: u64,
    pub suffix: Option<String>,
}


================================================
FILE: audio/src/export/metadata.rs
================================================
use serde::{Deserialize, Serialize};

/// The default title of the music.
pub const DEFAULT_TITLE: &str = "My Music";

/// Export metadata.
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
pub struct Metadata {
    /// The title of the music.
    pub title: String,
    /// The name of the artist.
    pub artist: Option<String>,
    /// The name of the album.
    pub album: Option<String>,
    /// The track number.
    pub track_number: Option<u32>,
    /// The genre.
    pub genre: Option<String>,
    /// Misc. comments.
    pub comment: Option<String>,
}

impl Default for Metadata {
    fn default() -> Self {
        Self {
            title: DEFAULT_TITLE.to_string(),
            artist: None,
            album: None,
            track_number: None,
            genre: None,
            comment: None,
        }
    }
}


================================================
FILE: audio/src/export/multi_file_suffix.rs
================================================
use serde::{Deserialize, Serialize};

/// How should we name files of separate tracks?
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, Deserialize, Serialize, Hash)]
pub enum MultiFileSuffix {
    /// Preset name suffix.
    Preset,
    /// Channel integer suffix.
    Channel,
    /// Channel, then preset name.
    #[default]
    ChannelAndPreset,
}


================================================
FILE: audio/src/export.rs
================================================
mod export_setting;
mod export_state;
mod export_type;
mod exportable;
mod metadata;
mod multi_file_suffix;

pub use export_setting::ExportSetting;
pub use export_state::ExportState;
pub use export_type::ExportType;
pub(crate) use exportable::Exportable;
pub use metadata::Metadata;
pub use multi_file_suffix::MultiFileSuffix;


================================================
FILE: audio/src/exporter.rs
================================================
use crate::export::{ExportSetting, ExportType, Metadata, MultiFileSuffix};
use crate::{AudioBuffer, SynthState};
use chrono::Datelike;
use chrono::Local;
use common::IndexedValues;
use common::{Index, Music, Time, U64orF32, DEFAULT_FRAMERATE, PPQ_F, PPQ_U};
use flacenc::bitsink::ByteSink;
use flacenc::component::BitRepr;
use flacenc::config::Encoder as FlacEncoder;
use flacenc::encode_with_fixed_block_size;
use flacenc::source::MemSource;
use hound::{SampleFormat, WavSpec, WavWriter};
use id3::{Tag, TagLike, Version};
use metaflac::Tag as FlacTag;
use midly::num::{u15, u24, u28, u4};
use midly::{
    write_std, Format, Header, MetaMessage, MidiMessage, Timing, Track, TrackEvent, TrackEventKind,
};
use mp3lame_encoder::*;
use oggvorbismeta::*;
use serde::{Deserialize, Serialize};
use std::fs::OpenOptions;
use std::io::Read;
use std::io::{Cursor, Write};
use std::path::Path;
use vorbis_encoder::Encoder;

/// The number of channels.
const NUM_CHANNELS: usize = 2;
/// Conversion factor for f32 to i16.
const F32_TO_I16: f32 = 32767.5;
/// An ordered list of MP3 bit rates. We can't use `IndexedValues` because this enum isn't serializable.
pub const MP3_BIT_RATES: [Bitrate; 16] = [
    Bitrate::Kbps8,
    Bitrate::Kbps16,
    Bitrate::Kbps24,
    Bitrate::Kbps32,
    Bitrate::Kbps40,
    Bitrate::Kbps48,
    Bitrate::Kbps64,
    Bitrate::Kbps80,
    Bitrate::Kbps96,
    Bitrate::Kbps112,
    Bitrate::Kbps128,
    Bitrate::Kbps160,
    Bitrate::Kbps192,
    Bitrate::Kbps224,
    Bitrate::Kbps256,
    Bitrate::Kbps320,
];
/// An ordererd list of mp3 qualities. We can't use `IndexedValues` because this enum isn't serializable.
pub const MP3_QUALITIES: [Quality; 10] = [
    Quality::Worst,
    Quality::SecondWorst,
    Quality::Ok,
    Quality::Decent,
    Quality::Good,
    Quality::Nice,
    Quality::VeryNice,
    Quality::NearBest,
    Quality::SecondBest,
    Quality::Best,
];

/// This struct contains all export settings, as well as exporter functions.
/// This struct does *not* write samples to a buffer; that's handled in the `Synthesizer`'s export functions.
/// Rather, this receives a buffer of f32 data, and then decides what to do with it based on the user-defined export settings.
///
/// There are always two copies of the same `Exporter`: One lives in the Synthesizer thread, and one lives on the main thread.
/// The user can edit the main thread `Exporter`, which is then sent to the Synthesizer thread.
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
pub struct Exporter {
    /// The framerate.
    pub framerate: U64orF32,
    /// Export metadata.
    pub metadata: Metadata,
    /// If true, write copyright info.
    pub copyright: bool,
    /// The mp3 quality index.
    pub mp3_bit_rate: Index<usize>,
    /// The mp3 quality index.
    pub mp3_quality: Index<usize>,
    /// If true, export to multiple files.
    pub multi_file: bool,
    /// Multi-file suffix setting.
    pub multi_file_suffix: IndexedValues<MultiFileSuffix, 3>,
    /// The .ogg file quality index.
    pub ogg_quality: Index<usize>,
    /// The export type.
    pub export_type: IndexedValues<ExportType, 5>,
    /// Export settings for .mid files.
    pub mid_settings: IndexedValues<ExportSetting, 3>,
    /// Export settings for .wav files.
    pub wav_settings: IndexedValues<ExportSetting, 3>,
    /// Export settings for .mp3 files.
    pub mp3_settings: IndexedValues<ExportSetting, 12>,
    /// Export settings for .ogg files.
    pub ogg_settings: IndexedValues<ExportSetting, 11>,
    /// Export settings for .flac files.
    /// Use a default if the save file is pre-0.1.3
    #[serde(default = "default_flac_settings")]
    pub flac_settings: IndexedValues<ExportSetting, 10>,
}

impl Default for Exporter {
    fn default() -> Self {
        let export_type = IndexedValues::new(
            0,
            [
                ExportType::Wav,
                ExportType::Mid,
                ExportType::MP3,
                ExportType::Ogg,
                ExportType::Flac,
            ],
        );
        let mid_settings = IndexedValues::new(
            0,
            [
                ExportSetting::Title,
                ExportSetting::Artist,
                ExportSetting::Copyright,
            ],
        );
        let wav_settings = IndexedValues::new(
            0,
            [
                ExportSetting::Framerate,
                ExportSetting::MultiFile,
                ExportSetting::MultiFileSuffix,
            ],
        );
        let mp3_settings = IndexedValues::new(
            0,
            [
                ExportSetting::Framerate,
                ExportSetting::Mp3Quality,
                ExportSetting::Mp3BitRate,
                ExportSetting::Title,
                ExportSetting::Artist,
                ExportSetting::Copyright,
                ExportSetting::Album,
                ExportSetting::TrackNumber,
                ExportSetting::Genre,
                ExportSetting::Comment,
                ExportSetting::MultiFile,
                ExportSetting::MultiFileSuffix,
            ],
        );
        let ogg_settings = IndexedValues::new(
            0,
            [
                ExportSetting::Framerate,
                ExportSetting::OggQuality,
                ExportSetting::Title,
                ExportSetting::Artist,
                ExportSetting::Copyright,
                ExportSetting::Album,
                ExportSetting::TrackNumber,
                ExportSetting::Genre,
                ExportSetting::Comment,
                ExportSetting::MultiFile,
                ExportSetting::MultiFileSuffix,
            ],
        );
        let flac_settings = default_flac_settings();
        let multi_file_suffix = IndexedValues::new(
            0,
            [
                MultiFileSuffix::ChannelAndPreset,
                MultiFileSuffix::Preset,
                MultiFileSuffix::Channel,
            ],
        );
        Self {
            framerate: U64orF32::from(DEFAULT_FRAMERATE),
            export_type,
            mp3_bit_rate: Index::new(12, MP3_BIT_RATES.len()),
            mp3_quality: Index::new(9, MP3_QUALITIES.len()),
            ogg_quality: Index::new(9, 10),
            wav_settings,
            mid_settings,
            mp3_settings,
            ogg_settings,
            flac_settings,
            multi_file_suffix,
            metadata: Metadata::default(),
            copyright: false,
            multi_file: false,
        }
    }
}

impl Exporter {
    /// Export to a .mid file.
    /// - `path` Output to this path.
    /// - `music` This is what we're saving.
    /// - `synth_state` We need this for its present names.
    /// - `text` This is is used for metadata.
    /// - `export_settings` .mid export settings.
    pub fn mid(&self, path: &Path, music: &Music, time: &Time, synth_state: &SynthState) {
        // Set the name of the music.
        let mut meta_messages = vec![MetaMessage::Text(self.metadata.title.as_bytes())];
        let mut copyright = vec![];
        // Set the tempo.
        meta_messages.push(MetaMessage::Tempo(u24::from(
            (60000000 / time.bpm.get_u()) as u32,
        )));
        // Set the time signature.
        meta_messages.push(MetaMessage::TimeSignature(4, 2, 24, 8));
        // Send copyright.
        if self.copyright {
            if let Some(artist) = &self.metadata.artist {
                copyright.append(&mut self.get_copyright(artist).as_bytes().to_vec());
                meta_messages.push(MetaMessage::Copyright(&copyright));
            }
        }

        let mut tracks = vec![];
        let mut track_0 = Track::new();
        for (i, midi_track) in music.midi_tracks.iter().enumerate() {
            if let Some(program) = synth_state.programs.get(&midi_track.channel) {
                // Get track 0 or start a new track.
                let mut track = Vec::new();

                if i == 0 {
                    for meta_message in meta_messages.iter() {
                        track_0.push(TrackEvent {
                            delta: 0.into(),
                            kind: TrackEventKind::Meta(*meta_message),
                        })
                    }
                }

                let channel = u4::from(midi_track.channel);

                // Set the program name.
                track.push(TrackEvent {
                    delta: 0.into(),
                    kind: TrackEventKind::Meta(MetaMessage::ProgramName(
                        program.preset_name.as_bytes(),
                    )),
                });
                // Change the program.
                track.push(TrackEvent {
                    delta: 0.into(),
                    kind: TrackEventKind::Midi {
                        channel,
                        message: MidiMessage::ProgramChange {
                            program: program.preset.into(),
                        },
                    },
                });

                // Iterate through the notes.
                let mut notes = midi_track.notes.clone();
                // Sort the notes by start time.
                notes.sort_by(|a, b| a.start.cmp(&b.start));
                // Get the start and end time.
                let t0 = notes.iter().map(|n| n.start).min().unwrap();
                // The delta is the first note.
                let mut dt = t0;
                let t1 = notes.iter().map(|n| n.end).max().unwrap();
                // Iterate through all pulses.
                for t in t0..t1 {
                    // Get all note-on events.
                    for note in notes.iter().filter(|n| n.start == t) {
                        let delta = Self::get_delta_time(&mut dt);
                        track.push(TrackEvent {
                            delta,
                            kind: TrackEventKind::Midi {
                                channel,
                                message: MidiMessage::NoteOn {
                                    key: note.note.into(),
                                    vel: note.velocity.into(),
                                },
                            },
                        });
                    }
                    // Get all note-off events.
                    for note in notes.iter().filter(|n| n.end == t) {
                        let delta = Self::get_delta_time(&mut dt);
                        track.push(TrackEvent {
                            delta,
                            kind: TrackEventKind::Midi {
                                channel,
                                message: MidiMessage::NoteOff {
                                    key: note.note.into(),
                                    vel: note.velocity.into(),
                                },
                            },
                        });
                    }
                }
                // End the track.
                track.push(TrackEvent {
                    delta: 0.into(),
                    kind: TrackEventKind::Meta(MetaMessage::EndOfTrack),
                });
                // Add the track.
                tracks.push(track);
            }
        }
        // Create the header.
        let header = Header::new(Format::Parallel, Timing::Metrical(u15::from(PPQ_U as u16)));
        // Write the file.
        let mut buffer: Vec<u8> = vec![];
        if let Err(error) = write_std(&header, tracks.iter(), &mut buffer) {
            panic!("Error writing {:?} {:?}", path, error);
        }
        Self::write_file(path, &buffer);
    }

    /// Export to a .wav file.
    ///
    /// - `path` The output path.
    /// - `buffer` A buffer of wav data.
    pub(crate) fn wav(&self, path: &Path, buffer: &AudioBuffer) {
        // Get the spec.
        let spec = WavSpec {
            channels: NUM_CHANNELS as u16,
            sample_rate: self.framerate.get_u() as u32,
            bits_per_sample: 16,
            sample_format: SampleFormat::Int,
        };
        // Write.
        let mut writer = WavWriter::create(path, spec).unwrap();
        let mut i16_writer = writer.get_i16_writer(buffer[0].len() as u32 * (NUM_CHANNELS as u32));
        for (l, r) in buffer[0].iter().zip(buffer[1].iter()) {
            i16_writer.write_sample(Self::to_i16(l));
            i16_writer.write_sample(Self::to_i16(r));
        }
        i16_writer.flush().unwrap();
        writer.finalize().unwrap();
    }

    /// Export to a .mp3 file.
    ///
    /// - `path` The output path.
    /// - `buffer` A buffer of wav data.
    pub(crate) fn mp3<'a, T: 'a>(&self, path: &Path, buffer: &'a [Vec<T>; NUM_CHANNELS])
    where
        mp3lame_encoder::DualPcm<'a, T>: mp3lame_encoder::EncoderInput,
    {
        // Create the encoder.
        let mut mp3_encoder = Builder::new().expect("Create LAME builder");
        mp3_encoder
            .set_num_channels(NUM_CHANNELS as u8)
            .expect("Set channels");
        mp3_encoder
            .set_sample_rate(self.framerate.get_u() as u32)
            .expect("Set sample rate");
        mp3_encoder
            .set_brate(MP3_BIT_RATES[self.mp3_bit_rate.get()])
            .expect("Set bitrate");
        mp3_encoder
            .set_quality(MP3_QUALITIES[self.mp3_quality.get()])
            .expect("Set quality");
        // Build the encoder.
        let mut mp3_encoder = mp3_encoder.build().expect("To initialize LAME encoder");
        // Get the input.
        let input = DualPcm {
            left: &buffer[0],
            right: &buffer[1],
        };
        // Get the output buffer.
        let mut mp3_out_buffer = Vec::with_capacity(max_required_buffer_size(buffer[0].len()));
        // Get the size.
        let encoded_size = mp3_encoder
            .encode(input, mp3_out_buffer.spare_capacity_mut())
            .expect("To encode");
        unsafe {
            mp3_out_buffer.set_len(mp3_out_buffer.len().wrapping_add(encoded_size));
        }
        let encoded_size = mp3_encoder
            .flush::<FlushNoGap>(mp3_out_buffer.spare_capacity_mut())
            .expect("To flush");
        unsafe {
            mp3_out_buffer.set_len(mp3_out_buffer.len().wrapping_add(encoded_size));
        }
        // Write the file.
        Self::write_file(path, &mp3_out_buffer);
        // Write the tag.
        let time = Local::now();
        let mut tag = Tag::new();
        tag.set_year(time.year());
        tag.set_title(&self.metadata.title);
        if let Some(artist) = &self.metadata.artist {
            tag.set_artist(artist);
        }
        if let Some(album) = &self.metadata.album {
            tag.set_album(album);
        }
        if let Some(genre) = &self.metadata.genre {
            tag.set_genre(genre);
        }
        if let Some(comment) = &self.metadata.comment {
            tag.set_genre(comment);
        }
        if let Some(track_number) = &self.metadata.track_number {
            tag.set_track(*track_number);
        }
        if let Err(error) = tag.write_to_path(path, Version::Id3v24) {
            panic!("Error writing ID3 tag to {:?}: {}", path, error);
        }
    }

    /// Export to an .ogg file.
    ///
    /// - `path` The output path.
    /// - `buffer` A buffer of wav data.
    pub(crate) fn ogg(&self, path: &Path, buffer: &AudioBuffer) {
        let mut samples = vec![];
        for (l, r) in buffer[0].iter().zip(buffer[1].iter()) {
            samples.push(Self::to_i16(l));
            samples.push(Self::to_i16(r));
        }
        let mut encoder = Encoder::new(
            NUM_CHANNELS as u32,
            self.framerate.get_u(),
            (self.ogg_quality.get() as f32 / 9.0) * 1.2 - 0.2,
        )
        .expect("Error creating .ogg file encoder.");
        let samples = encoder
            .encode(&samples)
            .expect("Error encoding .ogg samples.");
        // Get a cursor.
        let cursor = Cursor::new(&samples);
        // Write the comments.
        let mut comments = CommentHeader::new();
        comments.set_vendor("Ogg");
        comments.add_tag_single("title", &self.metadata.title);
        comments.add_tag_single("date", &Local::now().year().to_string());
        if let Some(artist) = &self.metadata.artist {
            comments.add_tag_single("artist", artist);
            if self.copyright {
                comments.add_tag_single("copyright", &self.get_copyright(artist));
            }
        }
        if let Some(album) = &self.metadata.album {
            comments.add_tag_single("album", album);
        }
        if let Some(genre) = &self.metadata.genre {
            comments.add_tag_single("genre", genre);
        }
        if let Some(track_number) = &self.metadata.track_number {
            comments.add_tag_single("tracknumber", &track_number.to_string());
        }
        if let Some(comment) = &self.metadata.genre {
            comments.add_tag_single("description", comment);
        }
        // Write the comments.
        let mut out = vec![];
        replace_comment_header(cursor, comments)
            .read_to_end(&mut out)
            .expect("Error reading cursor.");
        // Write the file.
        Self::write_file(path, &out);
    }

    /// Encode to flac.
    pub(crate) fn flac(&self, path: &Path, buffer: &AudioBuffer) {
        // Convert to i32.
        let mut samples = vec![];
        for (left, right) in buffer[0].iter().zip(buffer[1].iter()) {
            samples.push(Self::to_i32(left));
            samples.push(Self::to_i32(right));
        }
        let config = FlacEncoder::default();
        let source =
            MemSource::from_samples(&samples, NUM_CHANNELS, 16, self.framerate.get_u() as usize);
        match encode_with_fixed_block_size(&config, source, config.block_sizes[0]) {
            Ok(flac_stream) => {
                let mut sink = ByteSink::new();
                flac_stream.write(&mut sink).unwrap();
                // Write the file.
                Self::write_file(path, sink.as_slice());
                // Write the tag.
                let mut tag = FlacTag::read_from_path(path).unwrap();
                tag.set_vorbis("title", vec![self.metadata.title.clone()]);
                tag.set_vorbis("date", vec![Local::now().year().to_string()]);
                if let Some(artist) = &self.metadata.artist {
                    tag.set_vorbis("artist", vec![artist.clone()]);
                    if self.copyright {
                        tag.set_vorbis("copyright", vec![self.get_copyright(artist)]);
                    }
                }
                if let Some(album) = &self.metadata.album {
                    tag.set_vorbis("album", vec![album.clone()]);
                }
                if let Some(genre) = &self.metadata.genre {
                    tag.set_vorbis("genre", vec![genre.clone()]);
                }
                if let Some(track_number) = &self.metadata.track_number {
                    tag.set_vorbis("track_number", vec![track_number.to_string()]);
                }
                if let Some(comment) = &self.metadata.genre {
                    tag.set_vorbis("description", vec![comment.clone()]);
                }
                // Save the tag.
                tag.save().unwrap();
            }
            Err(error) => panic!("Error encoding flac: {:?}", error),
        }
    }

    /// Write samples to a file.
    fn write_file(path: &Path, samples: &[u8]) {
        let mut file = OpenOptions::new()
            .write(true)
            .append(false)
            .truncate(true)
            .create(true)
            .open(path)
            .expect("Error opening file {:?}");
        file.write_all(samples)
            .expect("Failed to write samples to file.");
    }

    /// Converts a PPQ value into a MIDI time delta and resets `ppq` to zero.
    fn get_delta_time(ppq: &mut u64) -> u28 {
        // Get the dt.
        let dt = (*ppq as f32 / PPQ_F) as u32;
        // Reset the PPQ value.
        *ppq = 0;
        u28::from(dt)
    }

    /// Converts an f32 sample to an i16 sample.
    fn to_i16(sample: &f32) -> i16 {
        (sample * F32_TO_I16).floor() as i16
    }

    /// Converts an f32 sample to an i32 sample.
    fn to_i32(sample: &f32) -> i32 {
        (sample * F32_TO_I16).floor() as i32
    }

    /// Returns a copyright string.
    fn get_copyright(&self, artist: &str) -> String {
        format!("Copyright {} {}", Local::now().year(), artist)
    }
}

fn default_flac_settings() -> IndexedValues<ExportSetting, 10> {
    IndexedValues::new(
        0,
        [
            ExportSetting::Framerate,
            ExportSetting::Title,
            ExportSetting::Artist,
            ExportSetting::Copyright,
            ExportSetting::Album,
            ExportSetting::TrackNumber,
            ExportSetting::Genre,
            ExportSetting::Comment,
            ExportSetting::MultiFile,
            ExportSetting::MultiFileSuffix,
        ],
    )
}


================================================
FILE: audio/src/lib.rs
================================================
//! This crate handles all audio output in Cacophony:
//!
//! - `Player` handles the cpal audio output stream.
//! - `Conn` manages the connection between external crates (command input), the synthesizer, and the audio player.
//! - `Exporter` handles all exporting to disk.
//!
//! Various data structs are shared in a Arc<Mutex<T>> format. These aren't a unified struct because they need to be locked at different times.
//!
//! As far as external crates are concerned, it's only necessary to create a new Conn: `Conn::default()`.

mod command;
mod conn;
mod decayer;
pub mod export;
pub mod exporter;
pub(crate) mod midi_event_queue;
pub mod play_state;
mod player;
mod program;
mod synth_state;
pub(crate) mod timed_midi_event;
mod types;
pub use crate::command::Command;
pub use crate::conn::Conn;
use crate::program::Program;
pub use crate::synth_state::SynthState;
pub(crate) use crate::types::{AudioBuffer, SharedMidiEventQueue, SharedSynth};
pub use crate::types::{AudioMessage, CommandsMessage, SharedExportState, SharedPlayState};
use player::Player;


================================================
FILE: audio/src/midi_event_queue.rs
================================================
use super::timed_midi_event::TimedMidiEvent;
use oxisynth::MidiEvent;

/// A queue of timed MIDI events.
#[derive(Default)]
pub(crate) struct MidiEventQueue {
    /// The events. Assume that this is sorted.
    events: Vec<TimedMidiEvent>,
}

impl MidiEventQueue {
    /// Enqueue a new MIDI event.
    ///
    /// - `time` The start time of the event in number of samples.
    /// - `event` The MIDI event.
    pub(crate) fn enqueue(&mut self, time: u64, event: MidiEvent) {
        // Add the event.
        self.events.push(TimedMidiEvent { time, event });
    }

    pub(crate) fn get_next_time(&self) -> Option<u64> {
        if self.events.is_empty() {
            None
        } else {
            Some(self.events[0].time)
        }
    }

    /// Sort the list of events by start time.
    pub(crate) fn sort(&mut self) {
        self.events.sort()
    }

    /// Dequeue any events that start at `time`.
    pub(crate) fn dequeue(&mut self, time: u64) -> Vec<MidiEvent> {
        let mut midi_events = vec![];
        while !self.events.is_empty() && self.events[0].time == time {
            midi_events.push(self.events.remove(0).event);
        }
        midi_events
    }

    /// Clear the queue.
    pub(crate) fn clear(&mut self) {
        self.events.clear()
    }
}


================================================
FILE: audio/src/play_state.rs
================================================
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum PlayState {
    /// Not playing any audio.
    NotPlaying,
    /// Playing music. There are queued events. Value: The elapsed time in samples.
    Playing(u64),
    /// There are no more events. Audio is decaying.
    Decaying,
}


================================================
FILE: audio/src/player.rs
================================================
use crate::decayer::Decayer;
use crate::play_state::PlayState;
use crate::types::SharedSample;
use crate::{SharedMidiEventQueue, SharedPlayState, SharedSynth};
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use cpal::*;
use oxisynth::Synth;

const ERROR_MESSAGE: &str = "Failed to create an audio output stream: ";

/// Try to start an audio stream and play audio.
/// Source: https://github.com/PolyMeilex/OxiSynth/blob/master/examples/real-time/src/main.rs
pub(crate) struct Player {
    /// The audio host. We don't want to drop it.
    _host: Host,
    /// The audio stream. We don't want to drop it.
    _stream: Option<Stream>,
    /// The machine's audio framerate.
    pub framerate: u32,
}

impl Player {
    pub(crate) fn new(
        midi_event_queue: SharedMidiEventQueue,
        synth: SharedSynth,
        sample: SharedSample,
        play_state: SharedPlayState,
    ) -> Option<Self> {
        // Get the host.
        let host = default_host();
        // Try to get an output device.
        match host.default_output_device() {
            None => {
                println!("{} Failed to get output device", ERROR_MESSAGE);
                None
            }
            // Try to get config info.
            Some(device) => match device.default_output_config() {
                Err(err) => {
                    println!("{} {}", ERROR_MESSAGE, err);
                    None
                }
                // We have a device and a config!
                Ok(config) => {
                    let framerate = config.sample_rate().0;
                    let stream_config: StreamConfig = config.into();
                    let channels = stream_config.channels as usize;

                    // Try to get a stream.
                    let stream = Player::run(
                        channels,
                        device,
                        stream_config,
                        midi_event_queue,
                        synth,
                        sample,
                        play_state,
                    );
                    Some(Self {
                        _host: host,
                        _stream: stream,
                        framerate,
                    })
                }
            },
        }
    }

    /// Start running the stream.
    fn run(
        channels: usize,
        device: Device,
        stream_config: StreamConfig,
        midi_event_queue: SharedMidiEventQueue,
        synth: SharedSynth,
        sample: SharedSample,
        play_state: SharedPlayState,
    ) -> Option<Stream> {
        // Define the error callback.
        let err_callback = |err| println!("Stream error: {}", err);

        let two_channels = channels == 2;
        let mut buffer = vec![0.0; 2];
        let mut sample_buffer = [0.0; 2];
        let mut decayer = Decayer::default();

        // Define the data callback used by cpal. Move `stream_send` into the closure.
        let data_callback = move |output: &mut [f32], _: &OutputCallbackInfo| {
            let ps = *play_state.lock();
            match ps {
                // Assume that there is no audio and do nothing.
                PlayState::NotPlaying => (),
                // Add decay.
                PlayState::Decaying => {
                    let len = output.len();
                    // Write the decay block.
                    decayer.decay_shared(&synth, len);
                    // Set the decay block.
                    if decayer.decaying {
                        // Copy into output.
                        if two_channels {
                            output.copy_from_slice(decayer.buffer[0..len].as_mut());
                        } else {
                            for (out_frame, in_frame) in output
                                .chunks_mut(channels)
                                .zip(decayer.buffer[0..len].chunks_mut(2))
                            {
                                for (id, sample) in out_frame.iter_mut().enumerate() {
                                    *sample = in_frame[id % 2];
                                }
                            }
                        }
                    }
                    // Done decaying.
                    else {
                        // Fill the output with silence.
                        output.iter_mut().for_each(|o| *o = 0.0);
                        let mut play_state = play_state.lock();
                        *play_state = PlayState::NotPlaying;
                    }
                }
                // Playing music.
                PlayState::Playing(time) => {
                    let len = output.len();
                    // Resize the buffers.
                    if len > buffer.len() {
                        buffer.resize(len, 0.0);
                    }
                    // Get the next sample.
                    let mut synth = synth.lock();
                    let mut midi_event_queue = midi_event_queue.lock();
                    // Iterate through the output buffer's frames.
                    let mut begin_decay = false;
                    let buffer_len = len / channels;
                    let mut t = time;
                    for frame in output.chunks_mut(channels) {
                        match midi_event_queue.get_next_time() {
                            Some(next_time) => {
                                // There are events on this frame.
                                if t == next_time {
                                    // Dequeue events.
                                    let events = midi_event_queue.dequeue(t);
                                    // Send the MIDI events to the synth.
                                    if !events.is_empty() {
                                        for event in events {
                                            if synth.send_event(event).is_ok() {}
                                        }
                                    }
                                }
                                // Add the sample.
                                // This is almost certainly more performant than the code in the `else` block.
                                if two_channels {
                                    // Get the sample.
                                    synth.write(frame);
                                }
                                // Add for more than one channel. This is slower.
                                else {
                                    synth.write(sample_buffer.as_mut_slice());
                                    for (id, sample) in frame.iter_mut().enumerate() {
                                        *sample = sample_buffer[id % 2];
                                    }
                                }
                                // Advance time.
                                t += 1;
                            }
                            // There are no more events.
                            None => {
                                begin_decay = true;
                                break;
                            }
                        }
                    }
                    if begin_decay {
                        *play_state.lock() = PlayState::Decaying;
                        Self::begin_decay(
                            buffer[0..buffer_len].as_mut(),
                            output,
                            channels,
                            two_channels,
                            &play_state,
                            &mut synth,
                        );
                    } else {
                        *play_state.lock() = PlayState::Playing(t);
                    }
                }
            }
            // Share the first sample.
            let mut sample = sample.lock();
            sample.0 = output[0];
            sample.1 = output[1]
        };

        // Build the cpal output stream from the stream config info and the callbacks.
        match device.build_output_stream(&stream_config, data_callback, err_callback) {
            // We have a stream!
            Ok(stream) => match stream.play() {
                Ok(_) => Some(stream),
                Err(_) => None,
            },
            Err(_) => None,
        }
    }

    fn begin_decay(
        buffer: &mut [f32],
        output: &mut [f32],
        channels: usize,
        two_channels: bool,
        play_state: &SharedPlayState,
        synth: &mut Synth,
    ) {
        if two_channels {
            synth.write(output);
        } else {
            // Write decay samples.
            synth.write(buffer.as_mut());
            for (out_frame, in_frame) in output.chunks_mut(channels).zip(buffer.chunks(2)) {
                for (id, sample) in out_frame.iter_mut().enumerate() {
                    *sample = in_frame[id % 2];
                }
            }
        }
        *play_state.lock() = PlayState::Decaying;
    }
}


================================================
FILE: audio/src/program.rs
================================================
use serde::{Deserialize, Serialize};
use std::path::PathBuf;

/// A channel's program.
#[derive(Serialize, Deserialize)]
pub struct Program {
    /// The path to the current track's SoundFont.
    pub path: PathBuf,
    /// The total number of banks.
    pub num_banks: usize,
    /// The index of the bank in `banks`.
    pub bank_index: usize,
    /// The actual bank value.
    pub bank: u32,
    /// The total number of presets in the bank.
    pub num_presets: usize,
    /// The preset number.
    pub preset: u8,
    /// The index of the preset in `presets`.
    pub preset_index: usize,
    /// The name of the preset.
    pub preset_name: String,
}

impl Clone for Program {
    fn clone(&self) -> Self {
        Self {
            path: self.path.clone(),
            num_banks: self.num_banks,
            bank_index: self.bank_index,
            bank: self.bank,
            num_presets: self.num_presets,
            preset: self.preset,
            preset_index: self.preset_index,
            preset_name: self.preset_name.clone(),
        }
    }
}


================================================
FILE: audio/src/synth_state.rs
================================================
use crate::Program;
use common::MAX_VOLUME;
use hashbrown::HashMap;
use serde::{Deserialize, Serialize};

/// The state of the synthesizer.
#[derive(Serialize, Deserialize)]
pub struct SynthState {
    /// The program state per channel.
    pub programs: HashMap<u8, Program>,
    /// The current gain.
    pub gain: u8,
}

impl Default for SynthState {
    fn default() -> Self {
        Self {
            programs: HashMap::new(),
            gain: MAX_VOLUME,
        }
    }
}

impl Clone for SynthState {
    fn clone(&self) -> Self {
        Self {
            programs: self.programs.clone(),
            gain: self.gain,
        }
    }
}


================================================
FILE: audio/src/timed_midi_event.rs
================================================
use oxisynth::MidiEvent;
use std::cmp::Ordering;

/// A MIDI event with a start time.
#[derive(Copy, Clone, Eq, PartialEq)]
pub(crate) struct TimedMidiEvent {
    /// The event time in number of samples.
    pub(crate) time: u64,
    /// The event.
    pub(crate) event: MidiEvent,
}

impl Ord for TimedMidiEvent {
    fn cmp(&self, other: &Self) -> Ordering {
        match self.time.cmp(&other.time) {
            Ordering::Less => Ordering::Less,
            Ordering::Greater => Ordering::Greater,
            Ordering::Equal => match (&self.event, &other.event) {
                // Two note-on events are equal.
                (
                    MidiEvent::NoteOn {
                        channel: _,
                        key: _,
                        vel: _,
                    },
                    MidiEvent::NoteOn {
                        channel: _,
                        key: _,
                        vel: _,
                    },
                ) => Ordering::Equal,
                // Two note-off events are equal.
                (
                    MidiEvent::NoteOff { channel: _, key: _ },
                    MidiEvent::NoteOff { channel: _, key: _ },
                ) => Ordering::Equal,
                // Note-off events are always before all other events.
                (MidiEvent::NoteOff { channel: _, key: _ }, _) => Ordering::Less,
                // Note-on events are always after note-offs.
                (
                    MidiEvent::NoteOn {
                        channel: _,
                        key: _,
                        vel: _,
                    },
                    MidiEvent::NoteOff { channel: _, key: _ },
                ) => Ordering::Greater,
                // Note-on events are always before all other events except note-offs.
                (
                    MidiEvent::NoteOn {
                        channel: _,
                        key: _,
                        vel: _,
                    },
                    _,
                ) => Ordering::Less,
                // All other events are equal.
                _ => Ordering::Equal,
            },
        }
    }
}

impl PartialOrd for TimedMidiEvent {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}


================================================
FILE: audio/src/types.rs
================================================
use crate::export::ExportState;
use crate::midi_event_queue::MidiEventQueue;
use crate::play_state::PlayState;
use crate::Command;
use oxisynth::Synth;
use parking_lot::Mutex;
use std::sync::Arc;

/// Type alias for an audio messages.
pub type AudioMessage = (f32, f32);
/// Type alias for a commands message.
pub type CommandsMessage = Vec<Command>;
/// Type alias for an audio buffer.
pub(crate) type AudioBuffer = [Vec<f32>; 2];
pub(crate) type SharedSynth = Arc<Mutex<Synth>>;
pub type SharedExportState = Arc<Mutex<ExportState>>;
pub(crate) type SharedMidiEventQueue = Arc<Mutex<MidiEventQueue>>;
pub type SharedPlayState = Arc<Mutex<PlayState>>;
pub(crate) type SharedSample = Arc<Mutex<AudioMessage>>;


================================================
FILE: changelog.md
================================================
# 0.2.x

## 0.2.7

- Fixed: Note-off events at the end of a track are sometimes not detected during export
- Fixed: Panic when decaying audio if the output buffer exceeds the length of the decay buffer.
- Allow alphanumeric input in `--events events.txt`.

## 0.2.6

- Fixed: If you let all notes in your music play (as opposed to stopping in the middle), it frequently becomes impossible to play or add new notes.
- Added a build env variable: Set `CACOPHONY_BUILD_DATA_DIR` to set the default path to the data directory.

## 0.2.5

- Dropped MacOS 11 builds on itch because there isn't a GitHub workflow runner anymore.
- Added MacOS 14 builds to tests and itch uploads.
- Added Ubuntu 24.04 builds to tests and itch uploads.
- Fixed: Crash if a Channel Pressure or Program Change message is sent from a MIDI controller.
- Fixed: Various TTS race conditions.
- (Backend) Added some more tests
- (Backend) Bumped ureq version (fixes a security warning)
- (Backend) Bumped Cargo.lock versions (fixes a security warning)
- (Backend) Removed audio/Cargo.lock (fixes a spurious security warning)
- (Backend) Added tests for TTS

## 0.2.4

- Fixed: If note is played via a qwerty key press, and then an octave is changed via a qwerty key press, there won't be a note-off event.
- Fixed: Cacophony can't found files (saves, soundfonts, etc.) if the file extension contains uppercase characters.
- Fixed: ChildPaths sometimes doesn't set the correct directory when moving up a directory.
- Fixed: It's possible to add notes while the music is playing.
- Fixed: When you save a new file, the panel will list children in the save directory's parent folder.
- (Backend) Fixed clippy warnings for Rust 1.78
- (Backend) The GitHub workflow for building Cacophony now uses the latest version of Rust.
- (Backend) Added tests for ChildPaths.

## 0.2.3

- Optimized text and rectangle rendering, which reduces CPU usage by around 5%.

## 0.2.2

- Fixed: There was an input bug where the play/start key (spacebar) was sometimes unresponsive for the first few presses. This is because audio was still decaying from a previous play, meaning that technically the previous play was still ongoing.
- Fixed: When a new file is created or when a new save file loaded, the app didn't reset correctly.
- Fixed: If you try to play music and there are no tracks or no playable notes, the app starts playing music and then immediately stops.
- Fixed: If you're playing music and then load a save file, the save file can't play music because the synthesizer still has MIDI events from the previous music.

## 0.2.1

- I replaced the default qwerty bindings for note input with a more "standard" layout. This information is stored in config.ini, so if you want the update, make sure to delete Documents/cacophony/config.ini if it exists (Cacophony will use the default data/config.ini instead).
- The background of the export settings panel was the same color as the text, so that it just looked like a weird gray rectangle. I fixed it.

## 0.2.0

Cacophony uses a lot of CPU resources even when it's idle. It shouldn't do that! I reduced Cacophony's CPU usage by around 50%; the exact percentage varies depending on the CPU and the OS. This update is the first big CPU optimization, and probably the most significant.

These are the optimizations:

- In `audio`, `Synthesizer` (which no longer exists) ran a very fast infinite loop to send samples to `Player`, and the loop needlessly consumed CPU resources. I replaced this loop with a bunch of `Arc<Mutex<T>>` values that are shared between `Conn` and `Player`. As a result of removing `Synthesizer` I had to reorganize all of the exporter code. There are a lot of small changes that I'm not going to list here because let's be real, no one reads verbose changelogs, but the most noticeable change is that `Exporter` is a field in `Conn` and is no longer shared (there is no `Arc<Mutex<Exporter>>` anywhere in the code). This change affects a *lot* of the codebase, but it's mostly just refactoring with zero functional differences.
- In `input`, `Input` checked for key downs and presses very inefficently. I only had to change two lines of code to make it much faster. This optimizes CPU usage by roughly 10%.
- A tiny optimization to drawing panel backgrounds. This required refactoring throughout `render`. I think that there are more tiny optimizations that could be made in `render` that cumulatively might make more of a difference.

This update doesn't have any new features or bug fixes. In the future, I'm going to reserve major releases (0.x.0) for big new features, but I had to rewrite so much of Cacophony's code, and the results are such a big improvement, that I'm making this a major release anyway.

# 0.1.x

## 0.1.4

- Fixed: Crash when setting the input beat to less than 1/8

## 0.1.3

- Added .flac exporting
- Fixed: Crash when attempting to add silence to the end of a non-wav multi-track export. I don't remember why I wanted to add silence in the first place, so I've removed it.
- Fixed: `--events [PATH]` argument doesn't work.

## 0.1.2

- Added environment variables and command line arguments:
  - Add a save file path to open it at launch, for example: `./cacophony ~/Documents/cacophony/saves/my_music.cac`
  - `CACOPHONY_DATA_DIR` or `--data_directory` to set the data directory
  - `CACOPHONY_FULLCREEN` or `--fullscreen` to enable fullscreen. 
- (Backend) Renamed feature flags `ubuntu_18_20` and `ubuntu_22` to `speech_dispatcher_0_9` and `speech_dispatcher_0_11`, respectively
- (Backend) `Paths` is now handled as a `OnceLock`
- Added missing compliation requirements in README. Added compilation instructions for Debian 11 and 12.
- Added this changelog

================================================
FILE: common/Cargo.toml
================================================
[package]
name = "common"
version.workspace = true
authors.workspace = true
description.workspace = true
documentation.workspace = true
edition.workspace = true

[dependencies]
serde = { workspace = true }
serde_json = { workspace = true }
rust-ini = { workspace = true }
directories = { workspace = true }
macroquad = { workspace = true }
hashbrown = { workspace = true }
num-traits = { workspace = true }
clap = { workspace = true }

================================================
FILE: common/src/args.rs
================================================
use crate::get_default_data_folder;
use clap::Parser;
use std::path::PathBuf;

/// Command-line arguments.
#[derive(Parser)]
#[command(author, version, about)]
pub struct Args {
    /// Open the project from disk.
    #[arg(value_name = "FILE")]
    pub file: Option<PathBuf>,
    /// Directory where Cacophony data files reside.
    ///
    /// Uses './data' if not set.
    #[arg(short, long, value_name = "DIR", env = "CACOPHONY_DATA_DIR", default_value = get_default_data_folder().into_os_string())]
    pub data_directory: PathBuf,
    /// Make the window fullscreen.
    ///
    /// Uses 'fullscreen' under '[RENDER]' in 'config.ini' if not set.
    ///
    /// Applied after displaying the splash-screen.
    #[arg(short, long, env = "CACOPHONY_FULLSCREEN")]
    pub fullscreen: bool,
    /// A path to a file of events that will be executed sequentially when the simulation starts.
    ///
    /// This is meant to be used for debugging.
    #[arg(short, long)]
    pub events: Option<PathBuf>,
}


================================================
FILE: common/src/config.rs
================================================
use crate::fraction::*;
use crate::Paths;
use ini::{Ini, Properties};
use serde_json::from_str;
use std::fmt::Display;
use std::str::FromStr;

/// Load the config file.
pub fn load() -> Ini {
    let paths = Paths::get();
    let path = if paths.user_ini_path.exists() {
        &paths.user_ini_path
    } else {
        &paths.default_ini_path
    };
    match Ini::load_from_file(path) {
        Ok(ini) => ini,
        Err(error) => panic!("Error loading confi.ini from {:?}: {}", path, error),
    }
}

/// Parse a string `value` and returns an enum of type `T`.
fn string_to_value<T>(value: &str) -> T
where
    T: FromStr,
    <T as FromStr>::Err: Display,
{
    match value.parse::<T>() {
        Ok(value) => value,
        Err(error) => panic!("Failed to parse {}", error),
    }
}

/// Parse a config key-value string pair into a value of type T.
///
/// - `properties` The `Ini` properties.
/// - `key` the key portion of the key-value pair.
pub fn parse<T>(properties: &Properties, key: &str) -> T
where
    T: FromStr,
    <T as FromStr>::Err: Display,
{
    match properties.get(key) {
        Some(value) => string_to_value(value),
        None => panic!("Missing key {}", key),
    }
}

/// Parse a 1 or 0 as a boolean.
pub fn parse_bool(properties: &Properties, key: &str) -> bool {
    match properties.get(key) {
        Some(value) => match value {
            "1" => true,
            "0" => false,
            _ => panic!("Invalid boolean value {} {}", key, value),
        },
        None => panic!("Missing key {}", key),
    }
}

/// Parse a list of fraction strings to PPQ values.
pub fn parse_fractions(properties: &Properties, key: &str) -> Vec<f32> {
    match properties.get(key) {
        Some(value) => match from_str::<Vec<&str>>(value) {
            Ok(value) => value.iter().map(|v| parse_float_kv(key, v)).collect(),
            Err(error) => panic!(
                "Error parsing list of fractions {} for key {}: {}",
                value, key, error
            ),
        },
        None => panic!("Missing key {}", key),
    }
}

/// Parse a value string as a float.
pub fn parse_float(properties: &Properties, key: &str) -> f32 {
    match properties.get(key) {
        Some(value) => parse_float_kv(key, value),
        None => panic!("Missing key {}", key),
    }
}

/// Parse a value string as a fraction.
pub fn parse_fraction(properties: &Properties, key: &str) -> Fraction {
    match properties.get(key) {
        Some(value) => parse_fraction_kv(key, value),
        None => panic!("Missing key {}", key),
    }
}

/// Parse a value string as a float.
fn parse_float_kv(key: &str, value: &str) -> f32 {
    // Is this formatted like a fraction, e.g. "1/2"?
    match value.contains('/') {
        true => {
            let nd: Vec<&str> = value.split('/').collect();
            match nd[0].parse::<f32>() {
                Ok(n) => match nd[1].parse::<f32>() {
                    Ok(d) => n / d,
                    Err(error) => panic!(
                        "Invalid denominator in fraction {} for key {}: {}",
                        value, key, error
                    ),
                },
                Err(error) => panic!(
                    "Invalid numerator in fraction {} for key {}: {}",
                    value, key, error
                ),
            }
        }
        // Is this formated like a decimal, e.g. "0.5" or "5"?
        false => match value.parse::<f32>() {
            Ok(value) => value,
            Err(error) => panic!("Invalid value {} for key {}: {}", value, key, error),
        },
    }
}

/// Parse a value string as a Fraction.
fn parse_fraction_kv(key: &str, value: &str) -> Fraction {
    // Is this formatted like a fraction, e.g. "1/2"?
    match value.contains('/') {
        true => {
            let nd: Vec<&str> = value.split('/').collect();
            match nd[0].parse::<u64>() {
                Ok(n) => match nd[1].parse::<u64>() {
                    Ok(d) => Fraction::new(n, d),
                    Err(error) => panic!(
                        "Invalid denominator in fraction {} for key {}: {}",
                        value, key, error
                    ),
                },
                Err(error) => panic!(
                    "Invalid numerator in fraction {} for key {}: {}",
                    value, key, error
                ),
            }
        }
        // Is this formated like a decimal, e.g. "0.5" or "5"?
        false => match value.parse::<u64>() {
            Ok(value) => Fraction::from(value),
            Err(error) => panic!("Invalid value {} for key {}: {}", value, key, error),
        },
    }
}


================================================
FILE: common/src/edit_mode.rs
================================================
use crate::IndexedValues;
use serde::{Deserialize, Serialize};

pub type IndexedEditModes = IndexedValues<EditMode, 3>;

#[derive(Debug, Eq, PartialEq, Hash, Copy, Clone, Default, Deserialize, Serialize)]
pub enum EditMode {
    /// Edit at a normal pace. What "normal" means is defined by the edit mode.
    #[default]
    Normal,
    /// Edit quickly; a multiple of `Normal`.
    Quick,
    /// Edit precisely. What "precisely" means is defined by the edit mode.
    Precise,
}

impl EditMode {
    pub fn indexed() -> IndexedEditModes {
        IndexedValues::new(0, [EditMode::Normal, EditMode::Quick, EditMode::Precise])
    }
}


================================================
FILE: common/src/font.rs
================================================
use crate::{get_bytes, Paths};
use ini::{Ini, Properties};
use macroquad::prelude::*;

/// Returns the font data section in the config file.
pub fn get_font_section(config: &Ini) -> &Properties {
    config.section(Some("FONTS")).unwrap()
}

/// Reads the font to a byte buffer.
pub fn get_font_bytes(config: &Ini) -> Vec<u8> {
    get_font_from_bytes(config, "font")
}

/// Returns the main font.
pub fn get_font(config: &Ini) -> Font {
    load_ttf_font_from_bytes(&get_font_bytes(config)).unwrap()
}

/// Returns the subtitle font.
pub fn get_subtitle_font(config: &Ini) -> Font {
    load_ttf_font_from_bytes(&get_font_from_bytes(config, "subtitle_font")).unwrap()
}

/// Returns the path to a font.
fn get_font_from_bytes(config: &Ini, key: &str) -> Vec<u8> {
    get_bytes(
        &Paths::get()
            .data_directory
            .join(get_font_section(config).get(key).unwrap()),
    )
}


================================================
FILE: common/src/fraction.rs
================================================
use crate::U64orF32;
use std::fmt::Display;
use std::ops::{Div, Mul};

/// A fraction has a numerator and denominator and can be multiplied or divided.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct Fraction {
    pub numerator: u64,
    pub denominator: u64,
}

impl Fraction {
    pub fn new(numerator: u64, denominator: u64) -> Self {
        Self {
            numerator,
            denominator,
        }
    }

    /// Flip the numerator and denominator.
    pub fn invert(&mut self) {
        std::mem::swap(&mut self.numerator, &mut self.denominator);
    }
}

impl From<u64> for Fraction {
    fn from(value: u64) -> Self {
        Self::new(value, 1)
    }
}

impl From<U64orF32> for Fraction {
    fn from(value: U64orF32) -> Self {
        Self::new(value.get_u(), 1)
    }
}

impl Display for Fraction {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}/{}", self.numerator, self.denominator)
    }
}

impl Mul<u64> for Fraction {
    type Output = u64;

    fn mul(self, rhs: u64) -> Self::Output {
        (rhs * self.numerator) / self.denominator
    }
}

impl Mul<Fraction> for u64 {
    type Output = u64;

    fn mul(self, rhs: Fraction) -> Self::Output {
        (self * rhs.numerator) / rhs.denominator
    }
}

impl Mul<Fraction> for Fraction {
    type Output = Fraction;

    fn mul(self, rhs: Fraction) -> Self::Output {
        Self::new(
            self.numerator * rhs.numerator,
            self.denominator * rhs.denominator,
        )
    }
}

impl Mul<Fraction> for U64orF32 {
    type Output = U64orF32;

    fn mul(self, rhs: Fraction) -> Self::Output {
        Self::from(self.get_u() * rhs)
    }
}

impl Mul<U64orF32> for Fraction {
    type Output = U64orF32;

    fn mul(self, rhs: U64orF32) -> Self::Output {
        Self::Output::from(self * rhs.get_u())
    }
}

impl Div<u64> for Fraction {
    type Output = u64;

    fn div(self, rhs: u64) -> Self::Output {
        (rhs * self.numerator) / self.denominator
    }
}

impl Div<Fraction> for u64 {
    type Output = u64;

    fn div(self, rhs: Fraction) -> Self::Output {
        (self * rhs.denominator) / rhs.numerator
    }
}

impl Div<Fraction> for Fraction {
    type Output = Fraction;

    fn div(self, rhs: Fraction) -> Self::Output {
        Self::new(
            self.numerator * rhs.denominator,
            self.denominator * rhs.numerator,
        )
    }
}

impl Div<Fraction> for U64orF32 {
    type Output = U64orF32;

    fn div(self, rhs: Fraction) -> Self::Output {
        Self::from(self.get_u() / rhs)
    }
}

impl Div<U64orF32> for Fraction {
    type Output = U64orF32;

    fn div(self, rhs: U64orF32) -> Self::Output {
        Self::Output::from(self / rhs.get_u())
    }
}

#[cfg(test)]
mod tests {
    use crate::fraction::Fraction;
    use crate::U64orF32;

    #[test]
    fn fraction() {
        // Instantiation.
        let mut fr = Fraction::from(3);
        assert_eq!(fr.numerator, 3);
        assert_eq!(fr.denominator, 1);
        fr = Fraction::new(1, 2);
        assert_eq!(fr.numerator, 1);
        assert_eq!(fr.denominator, 2);
        // Inversion.
        fr.invert();
        assert_eq!(fr.numerator, 2);
        assert_eq!(fr.denominator, 1);
        // Equality.
        fr.numerator = 3;
        fr.denominator = 5;
        // u64.
        let u = 7;
        assert_eq!(u * fr, 4);
        assert_eq!(fr * u, 4);
        assert_eq!(u / fr, 11);
        assert_eq!(fr / u, 4);
        // Fraction.
        let fr1 = Fraction::new(1, 2);
        let mut fr2 = fr * fr1;
        assert_eq!(fr2.numerator, 3);
        assert_eq!(fr2.denominator, 10);
        fr2 = fr1 * fr;
        assert_eq!(fr2.numerator, 3);
        assert_eq!(fr2.denominator, 10);
        fr2 = fr / fr1;
        assert_eq!(fr2.numerator, 6);
        assert_eq!(fr2.denominator, 5);
        fr2 = fr1 / fr;
        assert_eq!(fr2.numerator, 5);
        assert_eq!(fr2.denominator, 6);
        // U64orF32.
        let uf = U64orF32::from(7);
        assert_eq!((uf * fr).get_u(), 4);
        assert_eq!((fr * uf).get_u(), 4);
        assert_eq!((uf / fr).get_u(), 11);
        assert_eq!((fr / uf).get_u(), 4);
    }
}


================================================
FILE: common/src/index.rs
================================================
use num_traits::int::PrimInt;
use num_traits::{One, Zero};
use serde::{Deserialize, Serialize};
use std::fmt::Display;
use std::ops::{AddAssign, SubAssign};

/// An `Index` is an index in a known-length array.
/// The index can be incremented or decremented past the bounds of length, in which case it will loop to the start/end value.
/// The index can never exceed the length.
#[derive(Eq, PartialEq, Copy, Clone, Debug, Deserialize, Serialize)]
pub struct Index<T>
where
    T: PrimInt + Display + One + Zero + AddAssign + SubAssign,
{
    /// The index in the array.
    index: T,
    /// The length of the array.
    length: T,
}

impl<T> Index<T>
where
    T: PrimInt + Display + One + Zero + AddAssign + SubAssign,
{
    /// - `index` The index in the array.
    /// - `length` The size of the array.
    pub fn new(index: T, length: T) -> Self {
        Self { index, length }
    }

    /// Increment or decrement the index.
    ///
    /// If the incremented index is greater than `self.length`, `self.index` is set to 0.
    /// If the decremented index would be less than 0, `self.index` is set to `self.length - 1`.
    ///
    /// - `up` If true, increment. If false, decrement.
    pub fn increment(&mut self, up: bool) {
        let zero = T::zero();
        let one = T::one();
        if self.length == zero {
            return;
        }
        self.index = if up {
            if self.index == self.length - one {
                zero
            } else {
                self.index + one
            }
        } else if self.index == zero {
            self.length - one
        } else {
            self.index - one
        };
    }

    /// Increment or decrement the index without looping around the bounds.
    ///
    /// - `up` If true, increment. If false, decrement.
    ///
    /// Returns true if we incremented.
    pub fn increment_no_loop(&mut self, up: bool) -> bool {
        let zero = T::zero();
        let one = T::one();
        if self.length == zero {
            false
        } else if up {
            if self.index < self.length - one {
                self.index += one;
                true
            } else {
                false
            }
        } else if self.index > zero {
            self.index -= one;
            true
        } else {
            false
        }
    }

    /// Returns the index.
    pub fn get(&self) -> T {
        self.index
    }

    /// Set `self.index` to `index`. Panics if `index` is greater than or equal to `self.length`.
    pub fn set(&mut self, index: T) {
        if index >= self.length {
            panic!("Index {} exceeds length {}!", index, self.length)
        } else {
            self.index = index
        }
    }

    /// Returns the length of the value range.
    pub fn get_length(&self) -> T {
        self.length
    }
}

impl Default for Index<usize> {
    fn default() -> Self {
        Self::new(0, 0)
    }
}

#[cfg(test)]
mod tests {
    use crate::Index;

    #[test]
    fn index() {
        // Zero.
        let mut i = Index::default();
        assert_eq!(i.index, 0);
        assert_eq!(i.length, 0);
        i.increment(true);
        assert_eq!(i.index, 0);
        assert_eq!(i.length, 0);
        i.increment(false);
        assert_eq!(i.index, 0);
        assert_eq!(i.length, 0);
        i.increment_no_loop(true);
        assert_eq!(i.index, 0);
        assert_eq!(i.length, 0);
        i.increment_no_loop(false);
        assert_eq!(i.index, 0);
        assert_eq!(i.length, 0);
        // Some.
        i = Index::new(1, 9);
        assert_eq!(i.index, 1);
        assert_eq!(i.index, i.get());
        assert_eq!(i.length, 9);
        assert_eq!(i.length, i.get_length());
        i.increment(false);
        assert_eq!(i.get(), 0);
        i.increment(false);
        assert_eq!(i.get(), 8);
        i.increment(true);
        assert_eq!(i.get(), 0);
        i.increment_no_loop(false);
        assert_eq!(i.get(), 0);
    }
}


================================================
FILE: common/src/indexed_values.rs
================================================
use crate::Index;
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};

/// An `Index` with an array of values of type T and length N.
#[derive(Eq, PartialEq, Copy, Clone, Debug, Deserialize, Serialize)]
pub struct IndexedValues<T, const N: usize>
where
    [T; N]: Serialize + DeserializeOwned,
    T: Copy + Default,
{
    /// The values as an array of type `N`. These will be accessed via `index`.
    values: [T; N],
    /// The index. The length is always `N`.
    pub index: Index<usize>,
}

impl<T, const N: usize> Default for IndexedValues<T, N>
where
    [T; N]: Serialize + DeserializeOwned,
    T: Copy + Default,
{
    fn default() -> Self {
        Self {
            values: [T::default(); N],
            index: Index::default(),
        }
    }
}

impl<T, const N: usize> IndexedValues<T, N>
where
    [T; N]: Serialize + DeserializeOwned,
    T: Copy + Default,
{
    pub fn new(index: usize, values: [T; N]) -> Self {
        let index = Index::new(index, values.len());
        Self { values, index }
    }

    /// Returns the value at the index.
    pub fn get(&self) -> T {
        *self.get_ref()
    }

    /// Returns a reference to the value at the index.
    pub fn get_ref(&self) -> &T {
        &self.values[self.index.get()]
    }

    /// Returns a tuple:
    ///
    /// - A reference to the values array.
    /// - An array of booleans of whether each element is at the `self.index.get()`.
    pub fn get_values(&self) -> (&[T; N], [bool; N]) {
        let index = self.index.get();
        let mut values = [false; N];
        for (i, v) in values.iter_mut().enumerate() {
            if index == i {
                *v = true;
                break;
            }
        }
        (&self.values, values)
    }
}

#[cfg(test)]
mod tests {
    use crate::IndexedValues;

    #[test]
    fn indexed_values() {
        let mut i = IndexedValues::new(1, [0u8, 1u8, 2u8]);
        assert_eq!(i.index.get(), 1);
        assert_eq!(i.get(), 1);
        i.index.increment(false);
        assert_eq!(i.index.get(), 0);
        assert_eq!(i.get(), 0);
        i.index.increment(false);
        assert_eq!(i.index.get(), 2);
        assert_eq!(i.get(), 2);
        assert_eq!(i.get_ref(), &2)
    }
}


================================================
FILE: common/src/input_state.rs
================================================
use crate::{Index, U64orF32, MAX_VOLUME, PPQ_U};
use serde::{Deserialize, Serialize};

/// Booleans and numerical values describing the input state.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct InputState {
    /// If true, we will accept musical input.
    pub armed: bool,
    /// If true, we're inputting an alphanumeric string and we should ignore certain key bindings.
    pub alphanumeric_input: bool,
    /// The volume for all new notes.
    pub volume: Index<u8>,
    /// If true, we'll use the volume value.
    pub use_volume: bool,
    /// The input beat in PPQ.
    pub beat: U64orF32,
    /// If true, music is playing or exporting.
    #[serde(skip)]
    pub is_playing: bool,
}

impl Default for InputState {
    fn default() -> Self {
        Self {
            armed: false,
            alphanumeric_input: false,
            volume: Index::new(MAX_VOLUME, MAX_VOLUME + 1),
            use_volume: true,
            beat: U64orF32::from(PPQ_U),
            is_playing: false,
        }
    }
}


================================================
FILE: common/src/lib.rs
================================================
//! This crate contains a variety of types that are shared throughout Cacophony.
//!
//! There are two app-state-level structs defined in this crate:
//!
//! 1. `State` is *most* of the app state. It contains any data that can be placed on the undo/redo stacks. Because the undo/redo stacks contain entire `State` structs, the struct needs to be as small as possible.
//! 2. `PathsState` The state of directories, files, etc. defined by the user navigating through open-file dialogues. This isn't part of `State` because nothing here should go on the undo/redo stacks.
//!
//! There are two other state objects that aren't defined in this crate:
//!
//! - `SynthState` (defined in `audio`).
//! - `Exporter` (defined in `audio`).
//!
//! `common` is designed such that any Cacophony crate can use it, but itself does not depend on any Cacophony crates.

pub mod args;
pub mod config;
mod index;
mod input_state;
mod midi_track;
mod music;
mod note;
mod panel_type;
pub mod paths;
mod paths_state;
mod state;
pub mod time;
pub mod view;
pub use index::Index;
mod indexed_values;
pub use indexed_values::IndexedValues;
pub use input_state::InputState;
pub use midi_track::MidiTrack;
pub use music::*;
pub use note::{Note, MAX_NOTE, MIN_NOTE, NOTE_NAMES};
pub use panel_type::PanelType;
pub use paths::Paths;
pub use state::State;
use view::View;
mod edit_mode;
pub mod music_panel_field;
pub use edit_mode::*;
mod select_mode;
pub use select_mode::SelectMode;
mod piano_roll_mode;
pub use piano_roll_mode::PianoRollMode;
use std::env::current_dir;
use std::fs::{metadata, File};
use std::io::Read;
use std::option_env;
use std::path::{Path, PathBuf};
pub mod font;
pub mod open_file;
pub mod sizes;
pub use paths_state::PathsState;
mod u64_or_f32;
pub use self::time::*;
pub use u64_or_f32::*;
pub mod fraction;

/// The version that will be printed on-screen.
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
/// The maximum volume.
pub const MAX_VOLUME: u8 = 127;

/// Read bytes from a file.
pub fn get_bytes(path: &Path) -> Vec<u8> {
    let metadata = metadata(path).unwrap();
    let mut f = File::open(path).unwrap();
    let mut buffer = vec![0; metadata.len() as usize];
    f.read_exact(&mut buffer).unwrap();
    buffer
}

/// Default directory for looking at the 'data/' folder.
pub fn get_default_data_folder() -> PathBuf {
    match option_env!("CACOPHONY_BUILD_DATA_DIR") {
        Some(dir) => PathBuf::from(dir),
        None => current_dir().unwrap().join("data"),
    }
}

#[cfg(debug_assertions)]
pub fn get_test_config() -> ini::Ini {
    ini::Ini::load_from_file("../data/config.ini").unwrap()
}


================================================
FILE: common/src/midi_track.rs
================================================
use crate::{Note, MAX_VOLUME};
use serde::{Deserialize, Serialize};

/// A MIDI track has some notes.
#[derive(Debug, Deserialize, Serialize)]
pub struct MidiTrack {
    /// The channel used for audio synthesis.
    pub channel: u8,
    /// A gain value (0-127) for this track.
    pub gain: u8,
    /// The notes in the track.
    pub notes: Vec<Note>,
    /// True if the track is muted.
    pub mute: bool,
    /// True if the track is soloed.
    pub solo: bool,
}

impl MidiTrack {
    pub fn new(channel: u8) -> Self {
        Self {
            channel,
            gain: MAX_VOLUME,
            notes: vec![],
            mute: false,
            solo: false,
        }
    }

    /// Returns the end time of the track in PPQ.
    pub fn get_end(&self) -> Option<u64> {
        self.notes.iter().map(|n| n.end).max()
    }

    /// Returns the track gain as a float between 0 and 1.
    pub fn get_gain_f(&self) -> f32 {
        self.gain as f32 / MAX_VOLUME as f32
    }

    /// Returns all notes in the track that can be played (they are after t0).
    pub fn get_playback_notes(&self, start: u64) -> Vec<Note> {
        let gain = self.get_gain_f();
        let mut notes = vec![];
        for note in self.notes.iter().filter(|n| n.start >= start) {
            let mut n1 = *note;
            n1.velocity = (n1.velocity as f32 * gain) as u8;
            notes.push(n1);
        }
        notes.sort();
        notes
    }
}

impl Clone for MidiTrack {
    fn clone(&self) -> Self {
        Self {
            channel: self.channel,
            gain: self.gain,
            notes: self.notes.clone(),
            mute: self.mute,
            solo: self.solo,
        }
    }
}


================================================
FILE: common/src/music.rs
================================================
use super::midi_track::MidiTrack;
use serde::{Deserialize, Serialize};

/// Tracks, notes, and metadata.
#[derive(Clone, Default, Debug, Deserialize, Serialize)]
pub struct Music {
    /// The music tracks.
    pub midi_tracks: Vec<MidiTrack>,
    /// The index of the selected track.
    pub selected: Option<usize>,
}

impl Music {
    /// Returns the selected track, if any.
    pub fn get_selected_track(&self) -> Option<&MidiTrack> {
        match self.selected {
            Some(index) => Some(&self.midi_tracks[index]),
            None => None,
        }
    }

    /// Returns a mutable reference to the selected track, if any.
    pub fn get_selected_track_mut(&mut self) -> Option<&mut MidiTrack> {
        match self.selected {
            Some(index) => Some(&mut self.midi_tracks[index]),
            None => None,
        }
    }

    /// Returns all tracks that can be played.
    pub fn get_playable_tracks(&self) -> Vec<&MidiTrack> {
        // Get all tracks that can play music.
        let tracks = match self.midi_tracks.iter().find(|t| t.solo) {
            // Only include the solo track.
            Some(solo) => vec![solo],
            // Only include unmuted tracks.
            None => self.midi_tracks.iter().filter(|t| !t.mute).collect(),
        };
        tracks
    }
}


================================================
FILE: common/src/music_panel_field.rs
================================================
use serde::{Deserialize, Serialize};

/// Enum values defining the music panel fields.
#[derive(Debug, Default, Eq, PartialEq, Copy, Clone, Hash, Deserialize, Serialize)]
pub enum MusicPanelField {
    #[default]
    Name,
    BPM,
    Gain,
}


================================================
FILE: common/src/note.rs
================================================
use serde::ser::SerializeSeq;
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use std::fmt::{Debug, Formatter, Result};

/// The MIDI value of the highest-frequency note.
pub const MAX_NOTE: u8 = 127;
/// The MIDI value of the lowest-frequency note.
pub const MIN_NOTE: u8 = 12;
/// The MIDI value for C4.
pub const MIDDLE_C: u8 = 60;
/// The name of each note, in order.
/// Question: Why not just calculate the name from the MIDI value?
/// Answer: 1) Because I couldn't find an accurate formula. 2) This is probably slightly faster.
pub const NOTE_NAMES: [&str; 115] = [
    "G9", "F#9", "F9", "E9", "D#9", "D9", "C#9", "C9", "B9", "A#9", "A9", "G#9", "G8", "F#8", "F8",
    "E8", "D#8", "D8", "C#8", "C8", "B8", "A#8", "A8", "G#8", "G7", "F#7", "F7", "E7", "D#7", "D7",
    "C#7", "C7", "B7", "A#7", "A7", "G#7", "G6", "F#6", "F6", "E6", "D#6", "D6", "C#6", "C6", "B6",
    "A#6", "A6", "G#6", "G5", "F#5", "F5", "E5", "D#5", "D5", "C#5", "C5", "B5", "A#5", "A5",
    "G#5", "G4", "F#4", "F4", "E4", "D#4", "D4", "C#4", "C4", "B4", "A#4", "A4", "G#4", "G3",
    "F#3", "F3", "E3", "D#3", "D3", "C#3", "C3", "B3", "A#3", "A3", "G#3", "G2", "F#2", "F2", "E2",
    "D#2", "D2", "C#2", "C2", "B2", "A#2", "A2", "G#2", "G1", "F#1", "F1", "E1", "D#1", "D1",
    "C#1", "C1", "B1", "A#1", "A1", "G#1", "G0", "F#0", "F0", "E0", "D#0", "D0", "C#0",
];

/// A MIDI note with a start bar time and a duration bar time.
#[derive(Copy, Clone, PartialEq, Eq, Deserialize)]
pub struct Note {
    /// The MIDI note value.
    pub note: u8,
    /// The velocity value.
    pub velocity: u8,
    /// The start time in PPQ (pulses per quarter note).
    pub start: u64,
    /// The end time in PPQ.
    pub end: u64,
}

impl Note {
    /// Returns the duration of the note in PPQ.
    pub fn get_duration(&self) -> u64 {
        self.end - self.start
    }

    /// Adjust the start and end times by a delta (`dt`).
    pub fn set_t0_by(&mut self, dt: u64, positive: bool) {
        if positive {
            self.start += dt;
            self.end += dt;
        } else {
            self.start -= dt;
            self.end -= dt;
        }
    }

    /// Returns the name of the note.
    pub fn get_name(&self) -> &str {
        NOTE_NAMES[127 - self.note as usize]
    }
}

impl Ord for Note {
    fn cmp(&self, other: &Self) -> Ordering {
        (self.start, self.end, self.note).cmp(&(other.start, other.end, other.note))
    }
}

impl PartialOrd for Note {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl Debug for Note {
    fn fmt(&self, f: &mut Formatter) -> Result {
        write!(
            f,
            "Note {} {} {} {}",
            self.note, self.velocity, self.start, self.end
        )
    }
}

impl Serialize for Note {
    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        let mut seq = serializer.serialize_seq(Some(4)).unwrap();
        seq.serialize_element(&self.note).unwrap();
        seq.serialize_element(&self.velocity).unwrap();
        seq.serialize_element(&self.start).unwrap();
        seq.serialize_element(&self.end).unwrap();
        seq.end()
    }
}

#[cfg(test)]
mod tests {
    use crate::note::MIDDLE_C;
    use crate::{Note, MAX_VOLUME, PPQ_U};
    use serde_json::{from_str, to_string};

    #[test]
    fn note_duration() {
        let note = get_note();
        assert_eq!(note.get_duration(), PPQ_U, "{}", note.get_duration());
    }

    #[test]
    fn note_serialization() {
        let note = get_note();
        let r = to_string(&note);
        assert!(r.is_ok(), "{:?}", note);
        let s = r.unwrap();
        assert_eq!(&s, "[60,127,0,192]", "{}", s);
        let r = from_str(&s);
        assert!(r.is_ok(), "{:?}", s);
        let note: Note = r.unwrap();
        assert_eq!(note.note, MIDDLE_C, "{:?}", note);
        assert_eq!(note.velocity, MAX_VOLUME, "{:?}", note);
        assert_eq!(note.start, 0, "{:?}", note);
        assert_eq!(note.end, PPQ_U, "{:?}", note);
    }

    fn get_note() -> Note {
        Note {
            note: MIDDLE_C,
            velocity: MAX_VOLUME,
            start: 0,
            end: PPQ_U,
        }
    }
}


================================================
FILE: common/src/open_file/child_paths.rs
================================================
use super::{Extension, FileOrDirectory};
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};

/// A collection of child paths and a selected child path.
#[derive(Default, Clone, Deserialize, Serialize)]
pub struct ChildPaths {
    /// The child paths from the parent directory.
    pub children: Vec<FileOrDirectory>,
    /// The index of the selected child in `children`, if any.
    pub selected: Option<usize>,
}

impl ChildPaths {
    /// Set the child paths and selection.
    ///
    /// - `directory` The current parent directory.
    /// - `extension` The extension of valid files.
    /// - `previous_directory` This is used to set the selection to the directory we just moved up from.
    pub fn set(
        &mut self,
        directory: &Path,
        extension: &Extension,
        previous_directory: Option<PathBuf>,
    ) {
        // Get the paths.
        let children = self.get_paths_in_directory(directory, extension);
        // Get the folders. This sets the selection.
        let folders: Vec<&FileOrDirectory> = children.iter().filter(|p| !p.is_file).collect();
        // Set the selection index.
        // Try to select the previous directory.
        if let Some(previous_directory) = previous_directory {
            self.selected = children
                .iter()
                .enumerate()
                .filter(|p| {
                    p.1.stem
                        == previous_directory
                            .components()
                            .last()
                            .unwrap()
                            .as_os_str()
                            .to_str()
                            .unwrap()
                })
                .map(|p| p.0)
                .next();
        }
        // Try to select a child.
        if self.selected.is_none() {
            self.selected = if children.is_empty() {
                None
            } else {
                match (folders.is_empty(), children.iter().any(|p| p.is_file)) {
                    (true, false) => None,
                    (false, false) => Some(folders.len() - 1),
                    (_, _) => Some(0),
                }
            };
        }
        self.children = children;
    }

    /// Get the child paths of a directory.
    ///
    /// - `directory` The current parent directory.
    /// - `extension` The extension of valid files.
    fn get_paths_in_directory(
        &self,
        directory: &Path,
        extension: &Extension,
    ) -> Vec<FileOrDirectory> {
        // Find all valid paths.
        let valid_paths: Vec<PathBuf> = match directory.read_dir() {
            Ok(read) => read
                .filter(|e| e.is_ok())
                .map(|e| e.unwrap().path())
                .filter(|p| p.is_file() || p.read_dir().is_ok())
                .collect(),
            Err(_) => vec![],
        };
        // Get the files.
        let mut files: Vec<&PathBuf> = valid_paths
            .iter()
            .filter(|p| {
                p.is_file()
                    && p.extension().is_some()
                    && extension.to_str(false)
                        == p.extension().unwrap().to_str().unwrap().to_lowercase()
            })
            .collect();
        files.sort();
        // Get the directories.
        let mut folders: Vec<&PathBuf> = valid_paths.iter().filter(|p| p.is_dir()).collect();
        folders.sort();

        let mut paths: Vec<FileOrDirectory> =
            folders.iter().map(|f| FileOrDirectory::new(f)).collect();
        paths.append(&mut files.iter().map(|f| FileOrDirectory::new(f)).collect());
        paths
    }
}

#[cfg(test)]
mod tests {
    use std::{fs::canonicalize, path::PathBuf};

    use super::ChildPaths;
    use crate::open_file::{Extension, FileOrDirectory};

    #[test]
    fn test_sf2_child_paths() {
        let sf_directory = PathBuf::from("../data");
        assert!(sf_directory.exists());
        let mut child_paths = ChildPaths::default();
        child_paths.set(&sf_directory, &Extension::Sf2, None);
        assert_eq!(child_paths.children.len(), 1);
        let f = &child_paths.children[0];
        assert!(f.is_file);
        assert_eq!(f.stem, "CT1MBGMRSV1.06.sf2");
        assert!(child_paths.selected.is_some());
        assert_eq!(child_paths.selected.unwrap(), 0);
        // There shouldn't be any save files.
        child_paths.set(&sf_directory, &Extension::Cac, None);
        assert!(child_paths.children.is_empty());
        assert!(child_paths.selected.is_some());
        assert_eq!(child_paths.selected.unwrap(), 0);
        let parent_directory = canonicalize(sf_directory.parent().unwrap()).unwrap();
        assert_eq!(
            parent_directory
                .components()
                .last()
                .unwrap()
                .as_os_str()
                .to_str()
                .unwrap(),
            "cacophony"
        );
        // Set a different directory.
        child_paths.set(&parent_directory, &Extension::Sf2, None);
        assert!(!child_paths.children.is_empty());
        assert!(child_paths
            .children
            .iter()
            .filter(|c| c.is_file)
            .collect::<Vec<&FileOrDirectory>>()
            .is_empty());
        // Ignore any folders that have names beginning with a period because they won't all appear in the GitHub workflow.
        child_paths
            .children
            .retain(|f| match f.stem.chars().next() {
                Some(ch) => ch != '.',
                None => false,
            });
        assert!(child_paths.children.len() > 0);
        assert!(child_paths.selected.is_some());
        assert_eq!(child_paths.selected.unwrap(), 0);
        // Go "up" a directory.
        child_paths.set(
            &parent_directory,
            &Extension::Sf2,
            Some(sf_directory.clone()),
        );
        // Test the selection.
        assert_eq!(
            child_paths.children[child_paths.selected.unwrap()].stem,
            "data"
        );
    }

    #[test]
    fn test_cac_child_paths() {
        let cac_directory = PathBuf::from("../test_files/child_paths");
        assert!(cac_directory.exists());
        let mut child_paths = ChildPaths::default();
        child_paths.set(&cac_directory, &Extension::Cac, None);
        assert_eq!(child_paths.children.len(), 3);
        test_cac_file(&child_paths, child_paths.selected.unwrap(), "test_0.cac");
        test_cac_file(&child_paths, 1, "test_1.cac");
        test_cac_file(&child_paths, 2, "test_2.CAC");
    }

    fn test_cac_file(child_paths: &ChildPaths, index: usize, filename: &str) {
        let f = &child_paths.children[index];
        assert!(f.is_file);
        assert_eq!(f.stem, filename);
    }
}


================================================
FILE: common/src/open_file/extension.rs
================================================
use serde::{Deserialize, Serialize};

/// Enum values for file extensions used in Cacophony.
#[derive(Debug, Eq, PartialEq, Copy, Clone, Deserialize, Serialize)]
pub enum Extension {
    Cac,
    Sf2,
    Wav,
    Mid,
    MP3,
    Ogg,
    Flac,
}

impl Extension {
    /// Returns the file extension associated with the export type.
    ///
    /// - `period` If true, the extension starts with a ".", e.g. ".wav".
    pub fn to_str(&self, period: bool) -> &str {
        match self {
            Self::Cac => {
                if period {
                    ".cac"
                } else {
                    "cac"
                }
            }
            Self::Sf2 => {
                if period {
                    ".sf2"
                } else {
                    "sf2"
                }
            }
            Self::Wav => {
                if period {
                    ".wav"
                } else {
                    "wav"
                }
            }
            Self::Mid => {
                if period {
                    ".mid"
                } else {
                    "mid"
                }
            }
            Self::MP3 => {
                if period {
                    ".mp3"
                } else {
                    "mp3"
                }
            }
            Self::Ogg => {
                if period {
                    ".ogg"
                } else {
                    "ogg"
                }
            }
            Self::Flac => {
                if period {
                    ".flac"
                } else {
                    "flac"
                }
            }
        }
    }
}


================================================
FILE: common/src/open_file/file_and_directory.rs
================================================
use super::FileOrDirectory;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;

/// A directory and, optionally, a filename.
#[derive(Clone, Default, Deserialize, Serialize)]
pub struct FileAndDirectory {
    /// The file's directory.
    pub directory: FileOrDirectory,
    /// The filename, if any.
    pub filename: Option<String>,
}

impl FileAndDirectory {
    /// Create from a `PathBuf` directory; we assume here that this isn't a file.
    pub fn new_directory(directory: PathBuf) -> Self {
        Self {
            directory: FileOrDirectory::new(&directory),
            filename: None,
        }
    }

    /// Create from a `PathBuf` that may or may not be a file.
    pub fn new_path(path: PathBuf) -> Self {
        let directory = FileOrDirectory::new(path.parent().unwrap());
        let filename = Some(path.file_name().unwrap().to_str().unwrap().to_string());
        Self {
            directory,
            filename,
        }
    }

    /// Returns the path of the directory + filename.
    pub fn get_path(&self) -> PathBuf {
        match &self.filename {
            Some(filename) => self.directory.path.join(filename),
            None => panic!("No filename for: {:?}", self.directory.path),
        }
    }

    /// Returns the path of the directory + filename.
    pub fn try_get_path(&self) -> Option<PathBuf> {
        self.filename
            .as_ref()
            .map(|filename| self.directory.path.join(filename))
    }

    /// Returns the filename if there is one or an empty string if there isn't.
    pub fn get_filename(&self) -> String {
        match &self.filename {
            Some(string) => string.clone(),
            None => String::new(),
        }
    }
}


================================================
FILE: common/src/open_file/file_or_directory.rs
================================================
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};

/// Cached data for a file or directory because is_file() is a little too slow for my taste.
#[derive(Clone, Default, Deserialize, Serialize)]
pub struct FileOrDirectory {
    /// The file.
    pub path: PathBuf,
    /// If true, this is a file.
    pub is_file: bool,
    /// The top folder or the filename.
    pub stem: String,
}

impl FileOrDirectory {
    pub fn new(path: &Path) -> Self {
        let is_file = path.is_file();
        let stem = path
            .components()
            .last()
            .unwrap()
            .as_os_str()
            .to_str()
            .unwrap()
            .to_string();
        Self {
            path: path.to_path_buf(),
            is_file,
            stem,
        }
    }
}


================================================
FILE: common/src/open_file/open_file_type.rs
================================================
/// This defines the files we care about and what we can do with them.
#[derive(Debug, Eq, PartialEq, Clone, Default, Hash)]
pub enum OpenFileType {
    /// Read a save file.
    ReadSave,
    /// Read a SoundFont.
    #[default]
    SoundFont,
    /// Write a save file.
    WriteSave,
    /// Set the export path.
    Export,
    /// Import a MIDI file.
    ImportMidi,
}


================================================
FILE: common/src/open_file.rs
================================================
mod child_paths;
mod extension;
mod file_and_directory;
mod file_or_directory;
mod open_file_type;
pub use child_paths::ChildPaths;
pub use extension::Extension;
pub use file_and_directory::FileAndDirectory;
pub use file_or_directory::FileOrDirectory;
pub use open_file_type::OpenFileType;


================================================
FILE: common/src/panel_type.rs
================================================
use serde::{Deserialize, Serialize};

/// A type of panel.
#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone, Deserialize, Serialize)]
pub enum PanelType {
    MainMenu,
    Tracks,
    Music,
    PianoRoll,
    OpenFile,
    ExportState,
    ExportSettings,
    Quit,
    Links,
}


================================================
FILE: common/src/paths.rs
================================================
use directories::UserDirs;
use std::env::current_exe;
use std::fs::{copy, create_dir_all};
use std::path::{Path, PathBuf};
use std::sync::OnceLock;

const CONFIG_FILENAME: &str = "config.ini";

/// Global reference to paths.
static PATHS: OnceLock<Paths> = OnceLock::new();

/// Cached file paths. Unlike `PathsState`, this is meant to only include static data.
#[derive(Debug)]
pub struct Paths {
    /// The path to the default .ini file.
    pub default_ini_path: PathBuf,
    /// The path to the user directory.
    pub user_directory: PathBuf,
    /// The path to the user-defined .ini file. Might not exist.
    pub user_ini_path: PathBuf,
    /// The path to text.csv.
    pub text_path: PathBuf,
    /// The default SoundFont directory.
    pub soundfonts_directory: PathBuf,
    /// The default save file directory.
    pub saves_directory: PathBuf,
    /// The path to the exported audio files.
    pub export_directory: PathBuf,
    /// The path to the splash image.
    pub splash_path: PathBuf,
    /// The path to the default soundfont in data/
    pub default_soundfont_path: PathBuf,
    /// The path to the data/ directory itself.
    pub data_directory: PathBuf,
}

impl Paths {
    /// Setup the paths, needs to be be called at least once.
    pub fn init(data_directory_from_cli: &Path) {
        let data_directory = get_data_directory(data_directory_from_cli);
        PATHS.set(Self::new(&data_directory)).unwrap();
    }

    /// Returns a new `Paths` that can be used for testing.
    #[cfg(debug_assertions)]
    pub fn get_test_paths() -> Self {
        Self::new(&PathBuf::from("../data"))
    }

    /// Returns a new `Paths` object.
    fn new(data_directory: &Path) -> Self {
        let user_directory = match UserDirs::new() {
            Some(user_dirs) => match user_dirs.document_dir() {
                Some(documents) => documents.join("cacophony"),
                None => user_dirs.home_dir().join("cacophony"),
            },
            None => {
                if cfg!(windows) {
                    PathBuf::from("C:/").join("cacophony")
                } else {
                    PathBuf::from("/").join("cacophony")
                }
            }
        };
        let user_ini_path = user_directory.join(CONFIG_FILENAME);
        let default_ini_path = data_directory.join(CONFIG_FILENAME);
        let text_path = data_directory.join("text.csv");
        // Get or create the default user sub-directories.
        let soundfonts_directory = get_directory("soundfonts", &user_directory);
        let saves_directory = get_directory("saves", &user_directory);
        let export_directory = get_directory("exports", &user_directory);
        let splash_path = data_directory.join("splash.png");
        let default_soundfont_path = data_directory.join("CT1MBGMRSV1.06.sf2");
        Self {
            default_ini_path,
            user_directory,
            user_ini_path,
            text_path,
            soundfonts_directory,
            saves_directory,
            export_directory,
            splash_path,
            default_soundfont_path,
            data_directory: data_directory.to_path_buf(),
        }
    }

    /// Get a reference to the paths, panics when not initialized.
    pub fn get() -> &'static Self {
        PATHS.get().expect("Paths need to be initialzed first")
    }

    /// Create the user .ini file by copying the default .ini file.
    pub fn create_user_config(&self) {
        let path = PathBuf::from(&self.user_directory)
            .join(CONFIG_FILENAME)
            .to_str()
            .unwrap()
            .to_string();
        copy(&self.default_ini_path, path).unwrap();
    }
}

/// Returns the path to the data directory.
pub fn get_data_directory(data_directory: &Path) -> PathBuf {
    // Try to get the directory that's passed first.
    if data_directory.exists() {
        data_directory.to_path_buf()
    }
    // Maybe we're in a .app bundle.
    else if cfg!(target_os = "macos") {
        let data_directory = current_exe()
            .unwrap()
            .parent()
            .unwrap()
            .to_path_buf()
            .join("../Resources/data");
        if data_directory.exists() {
            data_directory
        } else {
            panic!("Failed to get data directory: {:?}", data_directory)
        }
    } else {
        panic!("Failed to get data directory: {:?}", data_directory)
    }
}

/// Returns a directory. Creates the directory if it doesn't exist.
fn get_directory(folder: &str, user_directory: &Path) -> PathBuf {
    let directory = user_directory.join(folder);
    if !directory.exists() {
        create_dir_all(&directory).unwrap();
    }
    directory
}


================================================
FILE: common/src/paths_state.rs
================================================
use crate::open_file::*;
use crate::{Index, Paths};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;

/// User-defined, save-file-specific, path data.
/// These paths aren't stored in `State` because:
///
/// 1. Changes to these paths should never go on the undo stack.
/// 2. This struct can be arbitrarily complex, so it shouldn't go on the undo stack.
#[derive(Deserialize, Serialize, Clone, Default)]
pub struct PathsState {
    /// When the SoundFont open-file panel is enabled, it will default to this directory.
    pub soundfonts: FileAndDirectory,
    /// When the user wants to save a file, it will be automatically written here unless they do a save-as.
    pub saves: FileAndDirectory,
    /// When the user wants to export a file, it will be exported to this path.
    pub exports: FileAndDirectory,
    /// When the user wants to import a MIDI file, this is the path.
    pub midis: FileAndDirectory,
    /// The child paths within the current working directory.
    #[serde(skip_serializing, skip_deserializing)]
    pub children: ChildPaths,
    /// The current open-file-type.
    #[serde(skip_serializing, skip_deserializing)]
    pub open_file_type: OpenFileType,
}

impl PathsState {
    pub fn new(paths: &Paths) -> Self {
        let soundfonts = FileAndDirectory::new_directory(paths.soundfonts_directory.clone());
        let saves = FileAndDirectory::new_directory(paths.saves_directory.clone());
        let exports = FileAndDirectory::new_directory(paths.export_directory.clone());
        let midis = FileAndDirectory::new_directory(paths.user_directory.clone());
        Self {
            soundfonts,
            saves,
            exports,
            midis,
            ..Default::default()
        }
    }

    /// Returns the current working directory for the open file type.
    pub fn get_directory(&self) -> &FileOrDirectory {
        match self.open_file_type {
            OpenFileType::Export => &self.exports.directory,
            OpenFileType::ReadSave | OpenFileType::WriteSave => &self.saves.directory,
            OpenFileType::SoundFont => &self.soundfonts.directory,
            OpenFileType::ImportMidi => &self.midis.directory,
        }
    }

    /// Returns a string of a given open-file-type's path's filename.
    pub fn get_filename(&self) -> Option<String> {
        match self.open_file_type {
            OpenFileType::Export => Some(self.exports.get_filename()),
            OpenFileType::WriteSave => Some(self.saves.get_filename()),
            _ => None,
        }
    }

    /// Try to go up a directory.
    pub fn up_directory(&mut self, extension: &Extension) -> bool {
        match self.open_file_type {
            OpenFileType::Export => {
                Self::up_directory_type(&mut self.exports.directory, &mut self.children, extension)
            }
            OpenFileType::ReadSave | OpenFileType::WriteSave => {
                Self::up_directory_type(&mut self.saves.directory, &mut self.children, extension)
            }
            OpenFileType::SoundFont => Self::up_directory_type(
                &mut self.soundfonts.directory,
                &mut self.children,
                extension,
            ),
            OpenFileType::ImportMidi => {
                Self::up_directory_type(&mut self.midis.directory, &mut self.children, extension)
            }
        }
    }

    /// Try to go down a directory.
    pub fn down_directory(&mut self, extension: &Extension) -> bool {
        if self.children.children.is_empty() {
            false
        } else {
            match &self.children.selected {
                Some(selected) => {
                    if self.children.children[*selected].is_file {
                        false
                    } else {
                        let cwd0 = match &self.open_file_type {
                            OpenFileType::Export => self.exports.directory.path.to_path_buf(),
                            OpenFileType::ReadSave | OpenFileType::WriteSave => {
                                self.saves.directory.path.to_path_buf()
                            }
                            OpenFileType::SoundFont => self.soundfonts.directory.path.to_path_buf(),
                            OpenFileType::ImportMidi => self.midis.directory.path.to_path_buf(),
                        };
                        let cwd1 = self.children.children[*selected].path.clone();
                        // Set the children.
                        self.children.set(&cwd1, extension, Some(cwd0));
                        // Set the directory.
                        match &self.open_file_type {
                            OpenFileType::Export => {
                                self.exports.directory = FileOrDirectory::new(&cwd1)
                            }
                            OpenFileType::ReadSave | OpenFileType::WriteSave => {
                                self.saves.directory = FileOrDirectory::new(&cwd1)
                            }
                            OpenFileType::SoundFont => {
                                self.soundfonts.directory = FileOrDirectory::new(&cwd1)
                            }
                            OpenFileType::ImportMidi => {
                                self.midis.directory = FileOrDirectory::new(&cwd1)
                            }
                        }
                        true
                    }
                }
                None => false,
            }
        }
    }

    /// Try to scroll through the children.
    pub fn scroll(&mut self, up: bool) -> bool {
        if self.children.children.is_empty() {
            false
        } else if let Some(selected) = &mut self.children.selected {
            let mut index = Index::new(*selected, self.children.children.len());
            if index.increment_no_loop(!up) {
                *selected = index.get();
                true
            } else {
                false
            }
        } else {
            false
        }
    }

    /// Set a filename.
    pub fn set_filename(&mut self, filename: &str) {
        let f = if filename.is_empty() {
            None
        } else {
            Some(filename.to_string())
        };
        match &self.open_file_type {
            OpenFileType::Export => self.exports.filename = f,
            OpenFileType::ReadSave | OpenFileType::WriteSave => self.saves.filename = f,
            OpenFileType::SoundFont => (),
            OpenFileType::ImportMidi => self.midis.filename = f,
        }
    }

    /// Returns the path of the directory + filename.
    pub fn get_path(&self) -> PathBuf {
        match &self.open_file_type {
            OpenFileType::Export => self.exports.get_path(),
            OpenFileType::ReadSave | OpenFileType::WriteSave => self.saves.get_path(),
            OpenFileType::SoundFont => self.soundfonts.get_path(),
            OpenFileType::ImportMidi => self.midis.get_path(),
        }
    }

    /// Go up a directory, given an open-file type.
    fn up_directory_type(
        directory: &mut FileOrDirectory,
        children: &mut ChildPaths,
        extension: &Extension,
    ) -> bool {
        match &directory.path.parent() {
            Some(parent) => {
                children.set(parent, extension, Some(directory.path.to_path_buf()));
                *directory = FileOrDirectory::new(parent);
                true
            }
            None => false,
        }
    }
}


================================================
FILE: common/src/piano_roll_mode.rs
================================================
use serde::{Deserialize, Serialize};

/// A sub-mode of the piano roll panel.
#[derive(Debug, Eq, PartialEq, Copy, Clone, Deserialize, Serialize, Hash)]
pub enum PianoRollMode {
    Time,
    View,
    Edit,
    Select,
}


================================================
FILE: common/src/select_mode.rs
================================================
use crate::{Music, Note};
use serde::{Deserialize, Serialize};

/// The current mode for selecting notes.
#[derive(Eq, PartialEq, Debug, Serialize, Deserialize)]
pub enum SelectMode {
    /// Select only one note. The value is an index in `track.notes`.
    Single(Option<usize>),
    /// Select many: A vec of indices.
    Many(Option<Vec<usize>>),
}

impl Clone for SelectMode {
    fn clone(&self) -> Self {
        match self {
            SelectMode::Single(index) => SelectMode::Single(*index),
            SelectMode::Many(indices) => SelectMode::Many(indices.clone()),
        }
    }
}

impl SelectMode {
    /// Converts and returns the selection as a list of indices in the selected track's `notes`.
    pub fn get_note_indices(&self) -> Option<Vec<usize>> {
        match self {
            // A single note is selected.
            SelectMode::Single(index) => index.as_ref().map(|index| vec![*index]),
            SelectMode::Many(indices) => indices.as_ref().cloned(),
        }
    }

    /// Converts and returns the selection as a list of cloned notes from the selected track's `notes`.
    ///
    /// - `music` The music.
    pub fn get_notes<'a>(&self, music: &'a Music) -> Option<Vec<&'a Note>> {
        match music.get_selected_track() {
            None => None,
            Some(track) => match self {
                SelectMode::Single(index) => index.as_ref().map(|index| vec![&track.notes[*index]]),
                SelectMode::Many(indices) => indices
                    .as_ref()
                    .map(|indices| indices.iter().map(|&i| &track.notes[i]).collect()),
            },
        }
    }

    /// Converts and returns the selection as a list of cloned notes from the selected track's `notes`.
    ///
    /// - `music` The music.
    pub fn get_notes_mut<'a>(&mut self, music: &'a mut Music) -> Option<Vec<&'a mut Note>> {
        match music.get_selected_track_mut() {
            None => None,
            Some(track) => match self {
                SelectMode::Single(index) => match index {
                    Some(index) => Some(vec![&mut track.notes[*index]]),
                    None => None,
                },
                SelectMode::Many(indices) => match indices {
                    Some(indices) => Some(
                        track
                            .notes
                            .iter_mut()
                            .enumerate()
                            .filter(|n| indices.contains(&n.0))
                            .map(|n| n.1)
                            .collect(),
                    ),
                    None => None,
                },
            },
        }
    }
}


================================================
FILE: common/src/sizes.rs
================================================
use crate::config::parse;
use crate::font::*;
use ini::Ini;
use macroquad::prelude::*;

/// The height of the main menu in grid units.
pub const MAIN_MENU_HEIGHT: u32 = 3;
/// The position of the music panel in grid units.
pub const MUSIC_PANEL_POSITION: [u32; 2] = [0, 0];
/// The height of the music panel.
pub const MUSIC_PANEL_HEIGHT: u32 = 6;
/// The height of the piano roll panel's top bar.
pub const PIANO_ROLL_PANEL_TOP_BAR_HEIGHT: u32 = 3;
/// The width of the column of note names.
pub const PIANO_ROLL_PANEL_NOTE_NAMES_WIDTH: u32 = 3;
/// The height of the piano roll volume sub-panel.
pub const PIANO_ROLL_PANEL_VOLUME_HEIGHT: u32 = 5;
/// The height of the prompt for the open-file panel.
pub const OPEN_FILE_PANEL_PROMPT_HEIGHT: u32 = 3;

/// Returns the font height.
pub fn get_font_size(config: &Ini) -> u16 {
    parse(get_font_section(config), "font_height")
}

/// Returns the size of a cell in pixels (width, height).
pub fn get_cell_size(config: &Ini) -> [f32; 2] {
    let font_size: u16 = get_font_size(config);
    let font = get_font(config);
    let size = measure_text("█", Some(&font), font_size, 1.0);
    [size.width, size.height]
}

/// Returns the window size in grid units.
pub fn get_window_grid_size(config: &Ini) -> [u32; 2] {
    let section = config.section(Some("RENDER")).unwrap();
    [
        parse(section, "window_width"),
        parse(section, "window_height"),
    ]
}

/// Returns the window size in pixels.
pub fn get_window_pixel_size(config: &Ini) -> [f32; 2] {
    let grid_size = get_window_grid_size(config);
    let cell_size = get_cell_size(config);
    [
        cell_size[0] * grid_size[0] as f32,
        cell_size[1] * grid_size[1] as f32,
    ]
}

/// Returns the size of the piano roll panel.
pub fn get_piano_roll_panel_position(config: &Ini) -> [u32; 2] {
    let tracks_panel_width = get_tracks_panel_width(config);
    [tracks_panel_width, MAIN_MENU_HEIGHT]
}

/// Returns the size of the piano roll panel.
pub fn get_piano_roll_panel_size(config: &Ini) -> [u32; 2] {
    let tracks_panel_width = get_tracks_panel_width(config);
    let window_grid_size = get_window_grid_size(config);
    [
        window_grid_size[0] - tracks_panel_width,
        window_grid_size[1] - MAIN_MENU_HEIGHT - PIANO_ROLL_PANEL_VOLUME_HEIGHT,
    ]
}

/// Returns the width of the tracks panel.
pub fn get_tracks_panel_width(config: &Ini) -> u32 {
    parse(
        config.section(Some("RENDER")).unwrap(),
        "tracks_panel_width",
    )
}

/// Returns the pixel width of all lines.
pub fn get_line_width(config: &Ini) -> f32 {
    parse(config.section(Some("RENDER")).unwrap(), "line_width")
}

/// Returns the size of the piano roll viewport.
pub fn get_viewport_size(config: &Ini) -> [u32; 2] {
    let piano_roll_panel_size = get_piano_roll_panel_size(config);
    let width = piano_roll_panel_size[0] - PIANO_ROLL_PANEL_NOTE_NAMES_WIDTH - 2;
    let height = piano_roll_panel_size[1] - PIANO_ROLL_PANEL_TOP_BAR_HEIGHT - 2;
    [width, height]
}

/// Returns the position and dimensions of the open-file panel.
pub fn get_open_file_rect(config: &Ini) -> ([u32; 2], [u32; 2]) {
    let window_grid_size = get_window_grid_size(config);
    let size = [window_grid_size[0] / 2, window_grid_size[1] / 2];
    let position = [window_grid_size[0] / 2 - size[0] / 2, MAIN_MENU_HEIGHT];
    (position, size)
}

/// Returns the position of the main menu.
pub fn get_main_menu_position(config: &Ini) -> [u32; 2] {
    let tracks_panel_width = get_tracks_panel_width(config);
    [
        MUSIC_PANEL_POSITION[0] + tracks_panel_width,
        MUSIC_PANEL_POSITION[1],
    ]
}

/// Returns the pixel width of the subtitles.
pub fn get_subtitle_width(config: &Ini) -> f32 {
    (get_main_menu_width(config) - 2) as f32 * get_cell_size(config)[0]
}

/// Returns the width of main menu.
pub fn get_main_menu_width(config: &Ini) -> u32 {
    let tracks_panel_width = get_tracks_panel_width(config);
    let window_grid_size = get_window_grid_size(config);
    window_grid_size[0] - tracks_panel_width
}


================================================
FILE: common/src/state.rs
================================================
use crate::music_panel_field::MusicPanelField;
use crate::{
    EditMode, Index, IndexedEditModes, IndexedValues, InputState, Music, PanelType, PianoRollMode,
    SelectMode, Time, View,
};
use ini::Ini;
use serde::{Deserialize, Serialize};

/// `State` contains all app data that can go on the undo/redo stacks.
/// Because the entire `State` goes on the stacks, it needs to be as small as possible.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct State {
    /// The music.
    pub music: Music,
    /// The viewport.
    pub view: View,
    /// The time state.
    pub time: Time,
    /// The input state.
    pub input: InputState,
    /// A list of all panels that need to be drawn.
    pub panels: Vec<PanelType>,
    /// The index of the focused panel.
    pub focus: Index<usize>,
    /// The currently-selected music panel field.
    pub music_panel_field: IndexedValues<MusicPanelField, 3>,
    /// The piano roll panel's current mode.
    pub piano_roll_mode: PianoRollMode,
    /// The index of the current piano roll edit mode.
    pub edit_mode: IndexedEditModes,
    /// The current selection.
    pub select_mode: SelectMode,
    /// If true, there are unsaved changes.
    #[serde(skip_serializing, skip_deserializing)]
    pub unsaved_changes: bool,
}

impl State {
    pub fn new(config: &Ini) -> State {
        let music = Music::default();
        let view = View::new(config);
        let time = Time::default();
        let input = InputState::default();
        let panels = vec![PanelType::Music, PanelType::Tracks, PanelType::PianoRoll];
        let focus = Index::new(0, panels.len());
        let music_panel_field = IndexedValues::new(
            0,
            [
                MusicPanelField::Name,
                MusicPanelField::BPM,
                MusicPanelField::Gain,
            ],
        );
        let piano_roll_mode = PianoRollMode::Time;
        let edit_mode = EditMode::indexed();
        let select_mode = SelectMode::Single(None);
        Self {
            music,
            view,
            time,
            input,
            panels,
            focus,
            music_panel_field,
            piano_roll_mode,
            edit_mode,
            select_mode,
            unsaved_changes: false,
        }
    }
}


================================================
FILE: common/src/time.rs
================================================
use crate::edit_mode::*;
use crate::U64orF32;
use serde::{Deserialize, Serialize};
use std::time::Duration;

/// The default BPM.
pub const DEFAULT_BPM: u64 = 120;
/// Converts BPM to seconds.
const BPM_TO_SECONDS: f32 = 60.0;
/// Pulses per quarter note as a u64.
pub const PPQ_U: u64 = 192;
/// Pulses per quarter note.
pub const PPQ_F: f32 = PPQ_U as f32;
/// The default framerate.
pub const DEFAULT_FRAMERATE: u64 = 44100;

/// The time state.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Time {
    /// The time defining the position of the cursor.
    pub cursor: u64,
    /// The time at which playback will start.
    pub playback: u64,
    /// The beats per minute.
    pub bpm: U64orF32,
    /// The current edit mode.
    pub mode: IndexedEditModes,
}

impl Time {
    /// Converts pulses per quarter note into seconds.
    pub fn ppq_to_seconds(&self, ppq: u64) -> f32 {
        ppq as f32 * (BPM_TO_SECONDS / (self.bpm.get_f() * PPQ_F))
    }

    /// Converts pulses per quarter note into a quantity of samples.
    pub fn ppq_to_samples(&self, ppq: u64, framerate: f32) -> u64 {
        (self.ppq_to_seconds(ppq) * framerate) as u64
    }

    /// Converts pulses per quarter note into a duration
    pub fn ppq_to_duration(&self, ppq: u64) -> Duration {
        Duration::from_secs_f32(self.ppq_to_seconds(ppq))
    }

    /// Converts a quantity of samples into pulses per quarter note.
    pub fn samples_to_ppq(&self, samples: u64, framerate: f32) -> u64 {
        ((self.bpm.get_f() * samples as f32) / (BPM_TO_SECONDS * framerate) * PPQ_F) as u64
    }

    pub fn reset(&mut self) {
        self.cursor = 0;
        self.playback = 0;
    }
}

impl Default for Time {
    fn default() -> Self {
        Self {
            cursor: 0,
            playback: 0,
            bpm: U64orF32::from(DEFAULT_BPM),
            mode: EditMode::indexed(),
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::time::*;

    #[test]
    fn time() {
        let mut time = Time::default();

        // PPQ to seconds.

        ppq_seconds(0, 0.0, &time);
        ppq_seconds(PPQ_U, 0.5, &time);
        ppq_seconds(288, 0.75, &time);

        time.bpm = U64orF32::from(60);

        ppq_seconds(0, 0.0, &time);
        ppq_seconds(PPQ_U, 1.0, &time);
        ppq_seconds(288, 1.5, &time);

        time.bpm = U64orF32::from(DEFAULT_BPM);

        let framerate: f32 = 44100.0;

        // PPQ to samples.

        ppq_samples(0, 0, framerate, &time);
        ppq_samples(PPQ_U, 22050, framerate, &time);
        ppq_samples(288, 33075, framerate, &time);

        time.bpm = U64orF32::from(60);

        ppq_samples(PPQ_U, 44100, framerate, &time);
        ppq_samples(288, 66150, framerate, &time);

        ppq_samples(PPQ_U, 48000, 48000.0, &time);
        time.bpm = U64orF32::from(DEFAULT_BPM);
        ppq_samples(PPQ_U, 24000, 48000.0, &time);

        // Samples to PPQ.
        samples_ppq(0, 0, framerate, &time);
        samples_ppq(22050, PPQ_U, framerate, &time);
        samples_ppq(44100, PPQ_U * 2, framerate, &time);
    }

    fn ppq_seconds(ppq: u64, f: f32, time: &Time) {
        let t = time.ppq_to_seconds(ppq);
        assert_eq!(t, f, "{} {}", t, f);
    }

    fn ppq_samples(ppq: u64, v: u64, framerate: f32, time: &Time) {
        let s = time.ppq_to_samples(ppq, framerate);
        assert_eq!(s, v, "{} {} {} {}", ppq, s, v, framerate)
    }

    fn samples_ppq(samples: u64, v: u64, framerate: f32, time: &Time) {
        let ppq = time.samples_to_ppq(samples, framerate);
        assert_eq!(ppq, v, "{} {} {}", ppq, v, samples);
    }
}


================================================
FILE: common/src/u64_or_f32.rs
================================================
use serde::de::{Error, Visitor};
use serde::{Deserialize, Serialize};
use std::fmt::{self, Display};

/// A value that is expressed as a u64 or an f32.
#[derive(Debug, PartialEq, Copy, Clone, Default)]
pub struct U64orF32 {
    /// The value as a u64.
    u: u64,
    /// The value as an f32.
    f: f32,
}

impl U64orF32 {
    /// Returns the value as a u64.
    pub fn get_u(&self) -> u64 {
        self.u
    }

    /// Returns the value as an f32.
    pub fn get_f(&self) -> f32 {
        self.f
    }

    pub fn set(&mut self, value: u64) {
        self.u = value;
        self.f = value as f32;
    }
}

impl Eq for U64orF32 {}

impl From<u64> for U64orF32 {
    fn from(value: u64) -> Self {
        Self {
            u: value,
            f: value as f32,
        }
    }
}

impl From<f32> for U64orF32 {
    fn from(value: f32) -> Self {
        let u = value as u64;
        Self { u, f: u as f32 }
    }
}

impl Display for U64orF32 {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.u)
    }
}

impl Serialize for U64orF32 {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        serializer.serialize_u64(self.u)
    }
}

impl<'de> Visitor<'de> for U64orF32 {
    type Value = Self;

    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str("a u64")
    }

    fn visit_u8<E>(self, value: u8) -> Result<Self::Value, E>
    where
        E: Error,
    {
        Ok(Self::from(u64::from(value)))
    }

    fn visit_u32<E>(self, value: u32) -> Result<Self::Value, E>
    where
        E: Error,
    {
        Ok(Self::from(u64::from(value)))
    }

    fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
    where
        E: Error,
    {
        Ok(Self::from(value))
    }

    fn visit_f32<E>(self, value: f32) -> Result<Self::Value, E>
    where
        E: Error,
    {
        Ok(Self::from(value))
    }
}

impl<'de> Deserialize<'de> for U64orF32 {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        deserializer.deserialize_u64(U64orF32::default())
    }
}

#[cfg(test)]
mod tests {
    use crate::U64orF32;
    use serde_json::{from_str, to_string, Error};

    #[test]
    fn set_uf() {
        uf_eq(U64orF32::from(1));
        uf_eq(U64orF32::from(1.0));
        uf_eq(U64orF32::from(-1.0));
        uf_eq(U64orF32::from(2.5));
    }

    #[test]
    fn uf_serialization() {
        let r = to_string(&U64orF32::from(5));
        assert!(r.is_ok());
        let s = r.unwrap();
        assert_eq!(&s, "5", "{}", s);
    }

    #[test]
    fn uf_deserialization() {
        deserialize_uf("5", U64orF32::from(5));
        deserialize_uf("5", U64orF32::from(5.0));
    }

    fn uf_eq(v: U64orF32) {
        assert_eq!(v.u as f32, v.f, "{:?}", v);
    }

    fn deserialize_uf(s: &str, v: U64orF32) {
        let r: Result<U64orF32, Error> = from_str(s);
        assert!(r.is_ok(), "{}", s);
        let q = r.unwrap();
        assert_eq!(q, v, "{:?} {:?}", q, v);
    }
}


================================================
FILE: common/src/view.rs
================================================
use crate::config::{parse, parse_fraction};
use crate::note::MIDDLE_C;
use crate::sizes::*;
use crate::{EditMode, Index, IndexedEditModes, U64orF32, MAX_NOTE, MIN_NOTE, PPQ_U};
use hashbrown::HashMap;
use ini::Ini;
use serde::{Deserialize, Serialize};

/// The minimum zoom time delta in PPQ.
const MIN_ZOOM: u64 = PPQ_U * 2;
const MAX_ZOOM: u64 = PPQ_U * 10000;

/// The dimensions of the piano roll viewport.
#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
pub struct View {
    /// The start and end time of the viewport in PPQ.
    pub dt: [u64; 2],
    /// The start and end note of the viewport.
    pub dn: [u8; 2],
    /// The current edit mode.
    pub mode: IndexedEditModes,
    /// If true, we're viewing a single track. If false, we're viewing multiple tracks.
    pub single_track: bool,
    /// The zoom time deltas.
    zoom_levels: Vec<u64>,
    /// The index of the current zoom level.
    zoom_index: Index<usize>,
    /// Zoom increments per edit mode.
    zoom_increments: HashMap<EditMode, usize>,
    /// The default zoom index.
    initial_zoom_index: usize,
    #[serde(skip)]
    initial_dn: [u8; 2],
}

impl View {
    pub fn new(config: &Ini) -> Self {
        // Get the width of the viewport.
        let viewport_size = get_viewport_size(config);
        let w = viewport_size[0];
        // Get the start time.
        let t0 = 0;
        // Get the end time from the size of the panel and the beats per cell.
        let t1 = t0 + w as u64 * PPQ_U;
        // Get the time delta.
        let dt = [t0, t1];
        // Get the notes delta.
        let h = viewport_size[1] as u8;
        let n0 = MIDDLE_C + h / 2;
        let n1 = n0 - h;
        let dn = [n0, n1];
        let mode = EditMode::indexed();
        let section = config.section(Some("PIANO_ROLL")).unwrap();
        let mut zoom_increment = parse_fraction(section, "zoom_increment");
        // Invert the fraction to prevent an infinite loop.
        if zoom_increment.numerator > zoom_increment.denominator {
            zoom_increment.invert();
        }
        let mut zoom_dt = U64orF32::from(t1 - t0);
        let mut zoom_levels = vec![];
        // Zoom in.
        loop {
            zoom_dt = zoom_dt * zoom_increment;
            if zoom_dt.get_u() <= MIN_ZOOM {
                break;
            }
            zoom_levels.insert(0, zoom_dt);
        }
        // Current zoom.
        zoom_dt = U64orF32::from(t1 - t0);
        let zoom_index = zoom_levels.len();
        zoom_levels.push(zoom_dt);
        // Zoom out.
        loop {
            zoom_dt = zoom_dt / zoom_increment;
            if zoom_dt.get_u() >= MAX_ZOOM {
                break;
            }
            zoom_levels.push(zoom_dt);
        }
        let zoom_levels: Vec<u64> = zoom_levels.iter().map(|z| z.get_u()).collect();
        let initial_zoom_index = zoom_index;
        let zoom_index = Index::new(zoom_index, zoom_levels.len());
        let normal_zoom = parse(section, "normal_zoom");
        let quick_zoom = parse(section, "quick_zoom");
        let precise_zoom = parse(section, "precise_zoom");
        let mut zoom_increments = HashMap::new();
        zoom_increments.insert(EditMode::Normal, normal_zoom);
        zoom_increments.insert(EditMode::Quick, quick_zoom);
        zoom_increments.insert(EditMode::Precise, precise_zoom);
        Self {
            dt,
            dn,
            mode,
            single_track: true,
            zoom_levels,
            zoom_index,
            zoom_increments,
            initial_zoom_index,
            initial_dn: dn,
        }
    }

    /// Returns the time delta from t1 to t0 in PPQ.
    pub fn get_dt(&self) -> u64 {
        self.dt[1] - self.dt[0]
    }

    /// Set the start time of the viewport.
    ///
    /// - `delta` The time delta.
    /// - `add` If true, add. If false, subtract.
    pub fn set_start_time_by(&mut self, dt: u64, add: bool) {
        let delta = self.get_dt();
        self.dt = if add {
            [self.dt[0] + dt, self.dt[1] + dt]
        } else {
            match self.dt[0].checked_sub(dt) {
                Some(t0) => [t0, t0 + delta],
                None => [0, delta],
            }
        };
    }

    /// Move `self.dn` up or down.
    ///
    /// - `dn` Move `self.dn` by this value.
    /// - `add` If true, move up. If false, move down.
    pub fn set_top_note_by(&mut self, dn: u8, add: bool) {
        self.dn = if add {
            // Don't go past n=1.
            if self.dn[0] + dn <= MAX_NOTE {
                [self.dn[0] + dn, self.dn[1] + dn]
            }
            // Snap to n=1.
            else {
                [MAX_NOTE, MAX_NOTE - self.get_dn()]
            }
        } else {
            // Don't go past n=0.
            if self.dn[1] - dn >= MIN_NOTE {
                [self.dn[0] - dn, self.dn[1] - dn]
            }
            // Snap to n=0.
            else {
                [MIN_NOTE + self.get_dn(), MIN_NOTE]
            }
        }
    }

    /// Zoom in or out. `self.dt[0]` doesn't change.
    ///
    /// - `up` If true, zoom in. If false, zoom out.
    pub fn zoom(&mut self, up: bool) {
        // Get the number of increment steps.
        let increments = self.zoom_increments[&self.mode.get()];
        // Use modulus division to get to the next valid index.
        // For example, if `increments == 2` then we need to get to an index relative to `self.initial_zoom_index` that is a multiple of 2.
        let zi = self.zoom_index.get();
        let m = (if zi > self.initial_zoom_index {
            zi - self.initial_zoom_index
        } else {
            self.initial_zoom_index - zi
        }) % increments;
        for _ in 0..m {
            if !self.zoom_index.increment_no_loop(up) {
                break;
            }
        }
        // Increment the zoom index.
        for _ in 0..increments {
            if !self.zoom_index.increment_no_loop(!up) {
                break;
            }
        }
        // Get the time delta.
        let dt = self.zoom_levels[self.zoom_index.get()];
        self.dt = [self.dt[0], self.dt[0] + dt];
    }

    /// Reset the view.
    pub fn reset(&mut self) {
        // Reset the zoom.
        self.zoom_index.set(self.initial_zoom_index);
        // Reset the time.
        let dt = self.zoom_levels[self.initial_zoom_index];
        self.dt = [0, dt];
        // Reset the notes.
        self.dn = self.initial_dn;
        // Single track view.
        self.single_track = true;
    }

    /// Returns the note delta.
    fn get_dn(&self) -> u8 {
        self.dn[0] - self.dn[1]
    }
}

#[cfg(test)]
mod tests {
    use crate::{get_test_config, EditMode, View, PPQ_U};

    const VIEW_T1: u64 = PPQ_U * 133;

    #[test]
    fn view_new() {
        let view = get_new_view();
        assert_eq!(view.dn, [75, 45], "{:?}", view.dn);
        assert_eq!(view.dt, [0, VIEW_T1], "{:?}", view.dt);
        assert_eq!(view.mode.index.get(), 0, "{}", view.mode.index.get());
        assert_eq!(view.single_track, true, "{}", view.single_track);
    }

    #[test]
    fn view_dt() {
        let mut view = get_new_view();
        view.set_start_time_by(PPQ_U, false);
        assert_eq!(view.dt, [0, VIEW_T1], "{:?}", view.dt);
        let dt = PPQ_U / 2;
        view.set_start_time_by(dt, true);
        assert_eq!(view.dt, [dt, VIEW_T1 + dt], "{:?}", view.dt);
        view.set_top_note_by(4, true);
        assert_eq!(view.dn, [79, 49], "{:?}", view.dn);
        view.set_top_note_by(4, false);
        assert_eq!(view.dn, [75, 45], "{:?}", view.dn);
        view.dt = [0, VIEW_T1];
    }

    #[test]
    fn view_dz() {
        let mut view = get_new_view();
        // Test modes.
        view.mode.index.set(0);
        assert_eq!(view.mode.get(), EditMode::Normal);
        view.mode.index.set(1);
        assert_eq!(view.mode.get(), EditMode::Quick);
        view.mode.index.set(2);
        assert_eq!(view.mode.get(), EditMode::Precise);
        // Zoom in precisely.
        view.zoom(true);
        // Reset to the default zoom.
        view.mode.index.increment(true);
        assert_eq!(view.mode.get(), EditMode::Normal);
        view.zoom(false);
        assert_eq!(view.get_dt(), VIEW_T1);
    }

    fn get_new_view() -> View {
        View::new(&get_test_config())
    }
}


================================================
FILE: data/attribution.txt
================================================
Default SoundFont: Caed's Small Trash GM by Caed. https://musical-artifacts.com/artifacts?q=Caed

================================================
FILE: data/config.ini
================================================
[FONTS]
# The path to the main font.
font = NotoSansMono-Regular.ttf
# The path to the subtitle font.
subtitle_font = NotoSansMono-ExtraBold.ttf
# The height of the font in pixels.
font_height = 20

[RENDER]
# The pixel width of all lines and rectangular borders.
line_width = 2
# If the open-file dialogue enables and everything is in mirror image, try adjusting this value (0 or 1).
flip_y = 1
# The width of the tracks panel in grid units.
tracks_panel_width = 22
# 1 for full screen, 0 for window.
fullscreen = 0
# The width of the window in grid units.
window_width = 160
# The height of the window in grid units.
window_height = 43
# In multi-track view, this is the height of each note in pixels.
multi_track_note_height = 2

[TEXT]
# This sets the column in text.csv that is used for key-value lookups.
language = en

[TEXT_TO_SPEECH]
# If 1, show subtitles. If 0, don't.
subtitles = 1
# The ID of the voice module.
# This can either be the index of a voice, e.g. 0, or a language, e.g. en-US. 
# If the language isn't available, Casey will talk in the voice at index 0.
voice_id = en-US
# As in real life, gender is optional. You can omit this line from your config.ini file.
# If voice_id is an index, e.g. 0, this is ignored. Otherwise, Casey will attempt to talk in language `voice_id` and gender `gender`.
# Valid genders are: f and m. If you enter something else, or if the language-gender pair isn't available, Casey will opt for the first voice in language `voice_id` regardless of gender.
gender = f
# The rate of speech.
rate_windows = 1
rate_macos = 0.5
rate_linux = 1

[UPDATE]
# If true, check online when the app launches to see if there is an updated version.
check_for_updates = 1

[QWERTY_BINDINGS]
# Input event bindings for a qwerty keyboard.
# Every input event must have a qwerty binding.
#
# Qwerty bindings must define the keys being pressed:
# {"keys": ["F1"]}
#
# You can optionally can define held mods: 
# {"keys": ["O"], "mods": ["LeftControl"]}
#
# You can optionally set the sensitivity, the number of frames until the app registers a repeat event:
# {"keys": ["PageUp"], "dt": 10} 
# If you don't set the dt value, key presses are detected only on press, not on held.
#
# For a list of key codes, see: <cacophony directory>/data/keycodes.txt

# Text-to-speech.
StatusTTS = {"keys": ["F1"]}
InputTTS = {"keys": ["F2"]}
AppTTS = {"keys": ["F3"]}
FileTTS = {"keys": ["F4"]}
StopTTS = {"keys": ["F5"]}

# Enable links panel.
EnableLinksPanel = {"keys": ["F9"]}

# Files.
NewFile = {"keys": ["N"], "mods": ["LeftControl"]}
OpenFile = {"keys": ["O"], "mods": ["LeftControl"]}
SaveFile = {"keys": ["S"], "mods": ["LeftControl"]}
SaveFileAs = {"keys": ["S"], "mods": ["LeftControl", "LeftShift"]}
ExportFile = {"keys": ["E"], "mods": ["LeftControl"]}
ImportMidi = {"keys": ["I"], "mods": ["LeftControl"]}
EditConfig = {"keys": ["W"], "mods": ["LeftControl"]}

# Cycle between panels.
NextPanel = {"keys": ["PageUp"], "dt": 10}
PreviousPanel = {"keys": ["PageDown"], "dt": 10}

# Alphanumeric input.
ToggleAlphanumericInput = {"keys": ["Return"]}

# Quit.
Quit = {"keys": ["Q"], "mods": ["LeftControl"]}

# Undo/redo.
Undo = {"keys": ["Z"], "mods": ["LeftControl"]}
Redo = {"keys": ["Y"], "mods": ["LeftControl"]}

# Music panel.
NextMusicPanelField = {"keys": ["Down"], "dt": 10}
PreviousMusicPanelField = {"keys": ["Up"], "dt": 10}
IncreaseMusicGain = {"keys": ["Right"], "dt": 1}
DecreaseMusicGain = {"keys": ["Left"], "dt": 1}

# Tracks panel.
AddTrack = {"keys": ["="]}
RemoveTrack = {"keys": ["-"]}
NextTrack = {"keys": ["Down"], "dt": 10}
PreviousTrack = {"keys": ["Up"], "dt": 10}
PreviousPreset = {"keys": ["["], "dt": 10}
NextPreset = {"keys": ["]"], "dt": 10}
PreviousBank = {"keys": [";"], "dt": 10}
NextBank = {"keys": ["'"], "dt": 10}
IncreaseTrackGain = {"keys": ["."], "dt": 1}
DecreaseTrackGain = {"keys": [","], "dt": 1}
EnableSoundFontPanel = {"keys": ["Return"]}
Mute = {"keys": ["M"]}
Solo = {"keys": ["S"]}

# Open file panel.
UpDirectory = {"keys": ["Left"]}
DownDirectory = {"keys": ["Right"]}
SelectFile = {"keys": ["Return"]}
NextPath = {"keys": ["Down"], "dt": 10}
PreviousPath = {"keys": ["Up"], "dt": 10}
CloseOpenFile = {"keys": ["Escape"]}
CycleExportType = {"keys": ["Tab"]}

# Export settings.
PreviousExportSetting = {"keys": ["Up"]}
NextExportSetting = {"keys": ["Down"]}
PreviousExportSettingValue = {"keys": ["Left"]}
NextExportSettingValue = {"keys": ["Right"]}
ToggleExportSettingBoolean = {"keys": ["Return"]}

# Piano roll.
PianoRollCycleMode = {"keys": ["Tab"]}
PianoRollSetTime = {"keys": ["1"]}
PianoRollSetView = {"keys": ["2"]}
PianoRollSetSelect = {"keys": ["3"]}
PianoRollSetEdit = {"keys": ["4"]}
PianoRollToggleTracks = {"keys": ["Backspace"]}
Arm = {"keys": ["Return"]}
InputBeatLeft = {"keys": ["["], "dt": 10}
InputBeatRight = {"keys": ["]"], "dt": 10}
IncreaseInputVolume = {"keys": ["'"], "dt": 1}
DecreaseInputVolume = {"keys": [";"], "dt": 1}
ToggleInputVolume = {"keys": ["Backslash"]}
PlayStop = {"keys": ["Space"]}
PianoRollPreviousTrack = {"keys": ["Up"], "dt": 5}
PianoRollNextTrack = {"keys": ["Down"], "dt": 5}

# Piano roll - view mode.
ViewLeft = {"keys": ["Left"], "dt": 5}
ViewRight = {"keys": ["Right"], "dt": 5}
ViewUp = {"keys": ["Up"], "dt": 5}
ViewDown = {"keys": ["Down"], "dt": 5}
ViewStart = {"keys": ["Home"]}
ViewEnd = {"keys": ["End"]}
ViewZoomIn = {"keys": ["Up"], "mods": ["LeftShift"], "dt": 10}
ViewZoomOut = {"keys": ["Down"], "mods": ["LeftShift"], "dt": 10}
ViewZoomDefault = {"keys": ["Home"], "mods": ["LeftShift"]}

# Piano roll - time mode.
TimeCursorLeft = {"keys": ["Left"], "dt": 5}
TimeCursorRight = {"keys": ["Right"], "dt": 5}
TimeCursorStart = {"keys": ["Home"]}
TimeCursorEnd = {"keys": ["End"]}
TimePlaybackLeft = {"keys": ["Left"], "mods": ["LeftShift"], "dt": 5}
TimePlaybackRight = {"keys": ["Right"], "mods": ["LeftShift"], "dt": 5}
TimePlaybackStart = {"keys": ["Home"], "mods": ["LeftShift"]}
TimePlaybackEnd = {"keys": ["End"], "mods": ["LeftShift"]}
TimeCursorPlayback = {"keys": ["Home"], "mods": ["LeftControl"]}
TimePlaybackCursor = {"keys": ["Home"], "mods": ["LeftControl", "LeftShift"]}
TimeCursorBeat = {"keys": ["Insert"]}
TimePlaybackBeat = {"keys": ["Insert"], "mods": ["LeftShift"]}

# Piano roll - edit mode.
EditStartLeft = {"keys": ["Left"], "dt": 5}
EditStartRight = {"keys": ["Right"], "dt": 5}
EditDurationLeft = {"keys": ["Left"], "mods": ["LeftShift"], "dt": 5}
EditDurationRight = {"keys": ["Right"], "mods": ["LeftShift"], "dt": 5}
EditPitchUp = {"keys": ["Up"], "dt": 5}
EditPitchDown = {"keys": ["Down"], "dt": 5}
EditVolumeUp = {"keys": ["Up"], "mods": ["LeftShift"], "dt": 1}
EditVolumeDown = {"keys": ["Down"], "mods": ["LeftShift"], "dt": 1}

# Piano roll - select mode.
SelectStartLeft = {"keys": ["Left"], "dt": 5}
SelectStartRight = {"keys": ["Right"], "dt": 5}
SelectEndLeft = {"keys": ["Left"], "mods": ["LeftShift"], "dt": 2}
SelectEndRight = {"keys": ["Right"], "mods": ["LeftShift"], "dt": 2}
SelectAll = {"keys": ["A"], "mods": ["LeftControl"]}
SelectNone = {"keys": ["Escape"]}

# Copy, cut, paste, delete.
CopyNotes = {"keys": ["C"], "mods": ["LeftControl"]}
CutNotes = {"keys": ["X"], "mods": ["LeftControl"]}
PasteNotes = {"keys": ["V"], "mods": ["LeftControl"]}
DeleteNotes = {"keys": ["Delete"]}

# Quit panel.
QuitPanelYes = {"keys": ["Y"]}
QuitPanelNo = {"keys": ["N"]}

# Links panel.
WebsiteUrl = {"keys": ["1"]}
DiscordUrl = {"keys": ["2"]}
GitHubUrl = {"keys": ["3"]}
CloseLinksPanel = {"keys": ["Escape"]}

# Qwerty note input.
C = {"keys": ["A"]}
CSharp = {"keys": ["W"]}
D = {"keys": ["S"]}
DSharp = {"keys": ["E"]}
E = {"keys": ["D"]}
F = {"keys": ["F"]}
FSharp = {"keys": ["T"]}
G = {"keys": ["G"]}
GSharp = {"keys": ["Y"]}
A = {"keys": ["H"]}
ASharp = {"keys": ["U"]}
B = {"keys": ["J"]}
OctaveUp = {"keys": ["Z"]}
OctaveDown = {"keys": ["X"]}

# MIDI input: two bytes, a time delta (frames, can be positive or negative), and an optional alias (used in text-to-speech).
[MIDI_BINDINGS]
# Cycle panels.
NextPanel = {"bytes": [176, 16], "dt": 10, "alias": "Knob 1"}
PreviousPanel = {"bytes": [176, 16], "dt": -10, "alias": "Knob 1"}

# Music panel.
NextMusicPanelField = {"bytes": [176, 17], "dt": 10, "alias": "Knob 2"}
PreviousMusicPanelField = {"bytes": [176, 17], "dt": -10, "alias": "Knob 2"}
IncreaseMusicGain = {"bytes": [176, 18], "dt": -1, "alias": "Knob 3"}
DecreaseMusicGain = {"bytes": [176, 18], "dt": 1, "alias": "Knob 3"}

# Tracks panel.
NextTrack = {"bytes": [176, 17], "dt": 10, "alias": "Knob 2"}
PreviousTrack = {"bytes": [176, 17], "dt": -10, "alias": "Knob 2"}
PreviousPreset = {"bytes": [176, 18], "dt": -10, "alias": "Knob 3"}
NextPreset = {"bytes": [176, 18], "dt": 10, "alias": "Knob 3"}
PreviousBank = {"bytes": [176, 19], "dt": -10, "alias": "Knob 4"}
NextBank = {"bytes": [176, 19], "dt": 10, "alias": "Knob 4"}
DecreaseTrackGain = {"bytes": [176, 20], "dt": -1, "alias": "Knob 5"}
IncreaseTrackGain = {"bytes": [176, 20], "dt": 1, "alias": "Knob 5"}

# Open file panel.
UpDirectory = {"bytes": [176, 17], "dt": 10, "alias": "Knob 2"}
DownDirectory = {"bytes": [176, 17], "dt": -10, "alias": "Knob 2"}
NextPath = {"bytes": [176, 18], "dt": -10, "alias": "Knob 3"}
PreviousPath = {"bytes": [176, 18], "dt": 10, "alias": "Knob 3"}
CycleExportType = {"bytes": [176, 19], "dt": 10, "alias": "Knob 4"}

# Export settings.
PreviousExportSetting = {"bytes": [176, 17], "dt": 10, "alias": "Knob 2"}
NextExportSetting = {"bytes": [176, 17], "dt": -10, "alias": "Knob 2"}
PreviousExportSettingValue = {"bytes": [176, 18], "dt": -10, "alias": "Knob 3"}
NextExportSettingValue = {"bytes": [176, 18], "dt": 10, "alias": "Knob 3"}

# Piano roll.
InputBeatLeft = {"bytes": [176, 21], "dt": -5, "alias": "Knob 6"}
InputBeatRight = {"bytes": [176, 21], "dt": 5, "alias": "Knob 6"}
IncreaseInputVolume = {"bytes": [176, 22], "dt": 1, "alias": "Knob 7"}
DecreaseInputVolume = {"bytes": [176, 22], "dt": -1, "alias": "Knob 7"}
PianoRollPreviousTrack = {"bytes": [176, 23], "dt": -10, "alias": "Knob 8"}
PianoRollNextTrack = {"bytes": [176, 23], "dt": 10, "alias": "Knob 8"}

# Piano roll - view mode.
ViewLeft = {"bytes": [176, 17], "dt": -5, "alias": "Knob 2"}
ViewRight = {"bytes": [176, 17], "dt": 5, "alias": "Knob 2"}
ViewUp = {"bytes": [176, 18], "dt": -2, "alias": "Knob 3"}
ViewDown = {"bytes": [176, 18], "dt": 2, "alias": "Knob 3"}
ViewZoomIn = {"bytes": [176, 19], "dt": 10, "alias": "Knob 4"}
ViewZoomOut = {"bytes": [176, 19], "dt": -10, "alias": "Knob 4"}

# Piano roll - time mode.
TimeCursorLeft = {"bytes": [176, 17], "dt": -2, "alias": "Knob 2"}
TimeCursorRight = {"bytes": [176, 17], "dt": 2, "alias": "Knob 2"}
TimePlaybackLeft = {"bytes": [176, 18], "dt": -2, "alias": "Knob 3"}
TimePlaybackRight = {"bytes": [176, 18], "dt": 2, "alias": "Knob 3"}

# Piano roll - edit mode.
EditStartLeft = {"bytes": [176, 17], "dt": 5, "alias": "Knob 2"}
EditStartRight = {"bytes": [176, 17], "dt": -5, "alias": "Knob 2"}
EditDurationLeft = {"bytes": [176, 18], "dt": -5, "alias": "Knob 3"}
EditDurationRight = {"bytes": [176, 18], "dt": 5, "alias": "Knob 3"}
EditPitchUp = {"bytes": [176, 19], "dt": -5, "alias": "Knob 4"}
EditPitchDown = {"bytes": [176, 19], "dt": 5, "alias": "Knob 4"}
EditVolumeUp = {"bytes": [176, 20], "dt": -1, "alias": "Knob 5"}
EditVolumeDown = {"bytes": [176, 20], "dt": 1, "alias": "Knob 5"}

# Piano roll - select mode.
SelectStartLeft = {"bytes": [176, 17], "dt": 5, "alias": "Knob 2"}
SelectStartRight = {"bytes": [176, 17], "dt": -5, "alias": "Knob 2"}
SelectEndLeft = {"bytes": [176, 18], "dt": -5, "alias": "Knob 3"}
SelectEndRight = {"bytes": [176, 18], "dt": 5, "alias": "Knob 3"}

[PIANO_ROLL]
# Multiply the beat by this factor to get the quick time.
quick_time_factor = 4
# In precise mode, move the view left and right by this beat length.
precise_time = 1/32
# In normal mode, move the view up and down by this many half-steps.
normal_note = 1
# In quick mode, move the viewport up and down by this many half-steps.
quick_note = 12
# In precise mode, move the view up and down by this many half-steps.
precise_note = 1
# In normal mode, edit volume by this delta.
normal_volume = 1
# In quick mode, edit volume by this delta.
quick_volume = 10
# In precise mode, edit volume by this delta.
precise_volume = 1
# The beats that the user can cycle through in piano roll mode.
beats = ["1/32", "1/16", "1/8", "1/4", "1/3", "1/2", "1", "1.5", "2", "3", "4", "5", "6", "7", "8"]
# The value of the default beat. This must exist in `beats`.
default_beat = 1
# The baseline zoom increment.
zoom_increment = 7/8
# In normal mode, increment by this factor. This must be an integer.
normal_zoom = 2
# In quick mode, increment by this factor. This must be an integer.
quick_zoom = 4
# In precise mode, increment by this factor. This must be an integer.
precise_zoom = 1

[COLOR_ALIASES]
# Add as many color aliases as you want! A color alias must have a unique key and a value formatted like [0, 255, 0].
black = [39, 41, 50]
pink = [245, 169, 184]
dusty_pink = [181, 86, 144]
white = [200, 200, 200]
blue_light = [91, 206, 250]
light_red = [224, 108, 117]
red = [166, 52, 70]
green = [53, 206, 141]
yellow_light = [229, 192, 123]
magenta = [143, 120, 221]
cyan = [86, 182, 194]
gray = [76, 82, 99]
gray_light = [92, 99, 112]
pale_red_light = [199, 106, 109]
pale_red_dark = [55, 47, 55]
khaki_light = [199, 149, 105]
khaki_dark = [63, 57, 58]
pale_yellow_light = [220, 213, 145]
pale_yellow_dark = [66, 66, 64]
sea_green_light = [146, 221, 194]
sea_green_dark = [55, 68, 71]
sky_blue_light = [146, 173, 221]
sky_blue_dark = [49, 54, 67]
magenta_light = [194, 146, 221]
magenta_dark = [54, 51, 67]
subtitle_background = [0, 0, 0]

[COLORS]
# Don't change the key names!
# You can change the values to either a color literal such as [0, 255, 0], or to one of the color aliases defined above.
Background = black
NoFocus = gray
FocusDefault = pink
Key = white
Value = blue_light
True = green
False = red
Arrow = dusty_pink
TextFieldBG = gray_light
Note = magenta
NoteSelected = dusty_pink
NotePlaying = blue_light
TimeCursor = cyan
TimePlayback = blue_light
Subtitle = dusty_pink
Separator = gray_light
TextInput = dusty_pink
SelectedNotesBackground = gray_light
Track0Focus = pale_red_light
Track0NoFocus = pale_red_dark
Track1Focus = khaki_light
Track1NoFocus = khaki_dark
Track2Focus = pale_yellow_light
Track2NoFocus = pale_yellow_dark
Track3Focus = sea_green_light
Track3NoFocus = sea_green_dark
Track4Focus = sky_blue_light
Track4NoFocus = sky_blue_dark
Track5Focus = magenta_light
Track5NoFocus = magenta_dark
SubtitleBackground = subtitle_background


================================================
FILE: data/text.csv
================================================
key,en
Space_SPOKEN,space
Apostrophe_SPOKEN,apostrophe
Comma_SPOKEN,comma
Minus_SPOKEN,minus
Period_SPOKEN,period
Slash_SPOKEN,slash
Key0_SPOKEN,0
Key1_SPOKEN,1
Key2_SPOKEN,2
Key3_SPOKEN,3
Key4_SPOKEN,4
Key5_SPOKEN,5
Key6_SPOKEN,6
Key7_SPOKEN,7
Key8_SPOKEN,8
Key9_SPOKEN,9
Semicolon_SPOKEN,semicolon
Equal_SPOKEN,equals
A_SPOKEN,a
B_SPOKEN,b
C_SPOKEN,c
D_SPOKEN,d
E_SPOKEN,e
F_SPOKEN,f
G_SPOKEN,g
H_SPOKEN,h
I_SPOKEN,i
J_SPOKEN,j
K_SPOKEN,k
L_SPOKEN,l
M_SPOKEN,m
N_SPOKEN,n
O_SPOKEN,o
P_SPOKEN,p
Q_SPOKEN,q
R_SPOKEN,r
S_SPOKEN,s
T_SPOKEN,t
U_SPOKEN,u
V_SPOKEN,v
W_SPOKEN,w
X_SPOKEN,x
Y_SPOKEN,y
Z_SPOKEN,z
LeftBracket_SPOKEN,left bracket
Backslash_SPOKEN,backslash
RightBracket_SPOKEN,right bracket
GraveAccent_SPOKEN,grave
World1_SPOKEN,world 1
World2_SPOKEN,world 2
Escape_SPOKEN,escape
Enter_SPOKEN,enter
Tab_SPOKEN,tab
Backspace_SPOKEN,backspace
Insert_SPOKEN,insert
Delete_SPOKEN,delete
Right_SPOKEN,right
Left_SPOKEN,left
Down_SPOKEN,down
Up_SPOKEN,up
PageUp_SPOKEN,page up
PageDown_SPOKEN,page down
Home_SPOKEN,home
End_SPOKEN,end
CapsLock_SPOKEN,caps lock
ScrollLock_SPOKEN,scroll lock
NumLock_SPOKEN,num lock
PrintScreen_SPOKEN,print screen
Pause_SPOKEN,pause
F1_SPOKEN,f1
F2_SPOKEN,f2
F3_SPOKEN,f3
F4_SPOKEN,f4
F5_SPOKEN,f5
F6_SPOKEN,f6
F7_SPOKEN,f7
F8_SPOKEN,f8
F9_SPOKEN,f9
F10_SPOKEN,f10
F11_SPOKEN,f11
F12_SPOKEN,f12
F13_SPOKEN,f13
F14_SPOKEN,f14
F15_SPOKEN,f15
F16_SPOKEN,f16
F17_SPOKEN,f17
F18_SPOKEN,f18
F19_SPOKEN,f19
F20_SPOKEN,f20
F21_SPOKEN,f21
F22_SPOKEN,f22
F23_SPOKEN,f23
F24_SPOKEN,f24
F25_SPOKEN,f25
Kp0_SPOKEN,keypad 0
Kp1_SPOKEN,keypad 1
Kp2_SPOKEN,keypad 2
Kp3_SPOKEN,keypad 3
Kp4_SPOKEN,keypad 4
Kp5_SPOKEN,keypad 5
Kp6_SPOKEN,keypad 6
Kp7_SPOKEN,keypad 7
Kp8_SPOKEN,keypad 8
Kp9_SPOKEN,keypad 9
KpDecimal_SPOKEN,keypad decimal
KpDivide_SPOKEN,keypad divide
KpMultiply_SPOKEN,keypad multiply
KpSubtract_SPOKEN,keypad subtract
KpAdd_SPOKEN,keypad add
KpEnter_SPOKEN,keypad enter
KpEqual_SPOKEN,keypad equal
LeftShift_SPOKEN,left shift
LeftControl_SPOKEN,left control
LeftAlt_SPOKEN,left alt
LeftSuper_SPOKEN,left super
RightShift_SPOKEN,right shift
RightControl_SPOKEN,right control
RightAlt_SPOKEN,right alt
RightSuper_SPOKEN,right super
Menu_SPOKEN,menu
Unknown_SPOKEN,unknown
Space_SEEN,Space
Apostrophe_SEEN,'
Comma_SEEN,","
Minus_SEEN,-
Period_SEEN,.
Slash_SEEN,/
Key0_SEEN,0
Key1_SEEN,1
Key2_SEEN,2
Key3_SEEN,3
Key4_SEEN,4
Key5_SEEN,5
Key6_SEEN,6
Key7_SEEN,7
Key8_SEEN,8
Key9_SEEN,9
Semicolon_SEEN,;
Equal_SEEN,=
A_SEEN,A
B_SEEN,B
C_SEEN,C
D_SEEN,D
E_SEEN,E
F_SEEN,F
G_SEEN,G
H_SEEN,H
I_SEEN,I
J_SEEN,J
K_SEEN,K
L_SEEN,L
M_SEEN,M
N_SEEN,N
O_SEEN,O
P_SEEN,P
Q_SEEN,Q
R_SEEN,R
S_SEEN,S
T_SEEN,T
U_SEEN,U
V_SEEN,V
W_SEEN,W
X_SEEN,X
Y_SEEN,Y
Z_SEEN,Z
LeftBracket_SEEN,[
Backslash_SEEN,\
RightBracket_SEEN,]
GraveAccent_SEEN,`
World1_SEEN,World 1
World2_SEEN,World 2
Escape_SEEN,Esc
Enter_SEEN,Enter
Tab_SEEN,Tab
Backspace_SEEN,Backspace
Insert_SEEN,Insert
Delete_SEEN,Delete
Right_SEEN,Right
Left_SEEN,Left
Down_SEEN,Down
Up_SEEN,Up
PageUp_SEEN,PageUp
PageDown_SEEN,PageDown
Home_SEEN,Home
End_SEEN,End
CapsLock_SEEN,CapsLock
ScrollLock_SEEN,ScrollLock
NumLock_SEEN,NumLock
PrintScreen_SEEN,PrintScreen
Pause_SEEN,Pause
F1_SEEN,F1
F2_SEEN,F2
F3_SEEN,F3
F4_SEEN,F4
F5_SEEN,F5
F6_SEEN,F6
F7_SEEN,F7
F8_SEEN,F8
F9_SEEN,F9
F10_SEEN,F10
F11_SEEN,F11
F12_SEEN,F12
F13_SEEN,F13
F14_SEEN,F14
F15_SEEN,F15
F16_SEEN,F16
F17_SEEN,F17
F18_SEEN,F18
F19_SEEN,F19
F20_SEEN,F20
F21_SEEN,F21
F22_SEEN,F22
F23_SEEN,F23
F24_SEEN,F24
F25_SEEN,F25
Kp0_SEEN,Keypad 0
Kp1_SEEN,Keypad 1
Kp2_SEEN,Keypad 2
Kp3_SEEN,Keypad 3
Kp4_SEEN,Keypad 4
Kp5_SEEN,Keypad 5
Kp6_SEEN,Keypad 6
Kp7_SEEN,Keypad 7
Kp8_SEEN,Keypad 8
Kp9_SEEN,Keypad 9
KpDecimal_SEEN,Keypad .
KpDivide_SEEN,Keypad /
KpMultiply_SEEN,Keypad *
KpSubtract_SEEN,Keypad -
KpAdd_SEEN,Keypad +
KpEnter_SEEN,Keypad Enter
KpEqual_SEEN,Keypad =
LeftShift_SEEN,LShift
LeftControl_SEEN,LCtrl
LeftAlt_SEEN,LAlt
LeftSuper_SEEN,LSuper
RightShift_SEEN,RShift
RightControl_SEEN,RCtrl
RightAlt_SEEN,RAlt
RightSuper_SEEN,RSuper
Menu_SEEN,Menu
Unknown_SEEN,?
NOTE_NAMES,"C0, C#0, D0, D#0, E0, F0, F#0, G0, G#0, A0, A#0, B0, C1, C#1, D1, D#1, E1, F1, F#1, G1, G#1, A1, A#1, B1, C2, C#2, D2, D#2, E2, F2, F#2, G2, G#2, A2, A#2, B2, C3, C#3, D3, D#3, E3, F3, F#3, G3, G#3, A3, A#3, B3, C4, C#4, D4, D#4, E4, F4, F#4, G4, G#4, A4, A#4, B4, C5, C#5, D5, D#5, E5, F5, F#5, G5, G#5, A5, A#5, B5, C6, C#6, D6, D#6, E6, F6, F#6, G6, G#6, A6, A#6, B6, C7, C#7, D7, D#7, E7, F7, F#7, G7, G#7, A7, A#7, B7, C8, C#8, D8, D#8, E8, F8, F#8, G8, G#8, A8, A#8, B8, C9, C#9, D9, D#9, E9, F9, F#9, G9"
TIME_TTS,\0 minutes and \1 seconds
TIME_TTS_HOURS,"\0 hours, \1 minutes, and \2 seconds"
OR, or 
MIDI_CONTROL,MIDI control \0 channel \1
APP_TTS_0,Hello world. I am Casey the Cacodemon.
APP_TTS_1,\0 to ask me about the panel status. \1 to ask me about the input keys. \2 to ask me about files.
APP_TTS_2,\0 to quit. 
APP_TTS_3,\0 and \1 to cycle panels. 
APP_TTS_4,\0 or \1 to undo or redo. 
APP_TTS_5,\0 to ask me to stop talking.
APP_TTS_6,\0 to open a panel with helpful website links.
FILE_TTS_0,\0 for new music.
FILE_TTS_1,\0 to open a file.
FILE_TTS_2,\0 to save. \1 to save as.
FILE_TTS_3,\0 to export.
FILE_TTS_4,\0 to import a MIDI file.
FILE_TTS_5,\0 to edit the config file.
MUSIC_PANEL_STATUS_TTS,This music is named \0. The BPM is \1. The gain is \2.
MUSIC_PANEL_INPUT_TTS,\0 and \1 to scroll.
NAME,name
BPM,BPM
GAIN,gain
MUSIC_PANEL_INPUT_TTS_BPM_ABC123,Type to set the beats per minute. \0 to finish.
MUSIC_PANEL_INPUT_TTS_BPM_NO_ABC123,\0 to enable input and then type to set the beats per minute.
MUSIC_PANEL_INPUT_TTS_BPM,Type to set the beats per minute.
MUSIC_PANEL_INPUT_TTS_GAIN,\0 and \1 to set the gain.
MUSIC_PANEL_INPUT_TTS_NAME_ABC123,Type the name of the music. \0 to finish.
MUSIC_PANEL_INPUT_TTS_NAME_NO_ABC123,\0 to enable input and then type the name of the music.
TRACKS_PANEL_STATUS_TTS_NO_SELECTION,There are no tracks.
TRACKS_PANEL_STATUS_TTS_PREFIX,Track \0 is selected.
TRACKS_PANEL_STATUS_TTS_SOUNDFONT,The preset is \0. The bank is \1. The gain is \2. The sound font is \3.
TRACKS_PANEL_STATUS_TTS_MUTED,This track is muted.
TRACKS_PANEL_STATUS_TTS_SOLOED,This track is soloed.
TRACKS_PANEL_STATUS_TTS_NO_SOUNDFONT,This track does not have a sound font.
TRACKS_PANEL_INPUT_TTS_ADD,\0 to add a track.
TRACKS_PANEL_INPUT_TTS_TRACK_PREFIX_0,\0 to remove the track. 
TRACKS_PANEL_INPUT_TTS_TRACK_PREFIX_1,\0 and \1 to scroll. 
TRACKS_PANEL_INPUT_TTS_TRACK_PREFIX_2,\0 to load a sound font.
TRACKS_PANEL_INPUT_TTS_TRACK_SUFFIX_0,\0 and \1 to set the preset.
TRACKS_PANEL_INPUT_TTS_TRACK_SUFFIX_1,\0 and \1 to set the bank.
TRACKS_PANEL_INPUT_TTS_TRACK_SUFFIX_2,\0 and \1 to set the gain.
TRACKS_PANEL_INPUT_TTS_MUTE,\0 to mute.
TRACKS_PANEL_INPUT_TTS_UNMUTE,\0 to unmute.
TRACKS_PANEL_INPUT_TTS_SOLO,\0 to solo.
TRACKS_PANEL_INPUT_TTS_UNSOLO,\0 to unsolo.
OPEN_FILE_PANEL_STATUS_TTS_CWD,The current directory is \0.
FOLDER,folder \0
FILE,file \0
OPEN_FILE_PANEL_UP,MORE ^
OPEN_FILE_PANEL_DOWN,MORE v
OPEN_FILE_PANEL_UP_DOWN,MORE ^v
OPEN_FILE_PANEL_TITLE_SOUNDFONT,Load SoundFont
OPEN_FILE_PANEL_TITLE_READ_SAVE,Load Save
OPEN_FILE_PANEL_TITLE_WRITE_SAVE,Save
OPEN_FILE_PANEL_TITLE_EXPORT,Export
OPEN_FILE_PANEL_TITLE_IMPORT_MIDI,Import MIDI
OPEN_FILE_PANEL_STATUS_TTS_SELECTION,You selected \0.
OPEN_FILE_PANEL_STATUS_TTS_NO_SELECTION,This directory is empty.
OPEN_FILE_PANEL_STATUS_TTS_EXPORT,The file type is \0.
OPEN_FILE_PANEL_INPUT_TTS_UP_DIRECTORY,\0 to go up to directory %0.
OPEN_FILE_PANEL_INPUT_TTS_SCROLL, \0 and \1 to scroll.
OPEN_FILE_PANEL_INPUT_TTS_CYCLE_EXPORT, \0 to set the export file to %0.
OPEN_FILE_PANEL_INPUT_TTS_DOWN_DIRECTORY,\0 to open folder %0.
OPEN_FILE_PANEL_INPUT_TTS_READ_SAVE,\0 to load save file %0.
OPEN_FILE_PANEL_INPUT_TTS_EXPORT,\0 to load audio file %0.
OPEN_FILE_PANEL_INPUT_TTS_SOUNDFONT,\0 to load sound font %0.
OPEN_FILE_PANEL_INPUT_TTS_WRITE_SAVE,\0 to write save file %0.
OPEN_FILE_PANEL_INPUT_TTS_IMPORT_MIDI,\0 to import MIDI file %0.
OPEN_FILE_PANEL_INPUT_TTS_CLOSE,\0 to close.
PIANO_ROLL_PANEL_TTS_NO_TRACK,You cannot use this panel until you have added a track and loaded a sound font.
PIANO_ROLL_PANEL_STATUS_TTS_MODE,The piano roll mode is \0.
PIANO_ROLL_PANEL_STATUS_TTS_SINGLE_TRACK,You are viewing track \0.
PIANO_ROLL_PANEL_STATUS_TTS_MULTI_TRACK,You are viewing multiple tracks. Track \0 is selected.
PIANO_ROLL_PANEL_STATUS_TTS_ARMED,"The track is armed. New notes will be \0 beats and volume \1."
PIANO_ROLL_PANEL_STATUS_TTS_VOLUME,\0 if you use qwerty input otherwise the MIDI velocity value.
PIANO_ROLL_PANEL_STATUS_TTS_NOT_ARMED,"The track is not armed."
PIANO_ROLL_PANEL_STATUS_TTS_PIANO_ROLL_MODE,The piano roll mode is \0.
PIANO_ROLL_PANEL_STATUS_TTS_EDIT_MODE,The edit mode is \0.
PIANO_ROLL_PANEL_STATUS_TTS_NO_SELECTION,No notes are selected.
PIANO_ROLL_PANEL_STATUS_TTS_SELECTED_SINGLE,The selected note has a pitch of \0 and starts at beat \1.
PIANO_ROLL_PANEL_STATUS_TTS_SELECTED_MANY,The selected notes start at beat \0 and end at beat \1.
PIANO_ROLL_PANEL_STATUS_TTS_TIME,"The cursor is at \0. Playback will start at \1."
PIANO_ROLL_PANEL_STATUS_TTS_VIEW,The view is from beats \0 to \1 and pitches \2 to \3.
PIANO_ROLL_PANEL_INPUT_TTS_PLAY,\0 to play music.
PIANO_ROLL_PANEL_INPUT_TTS_SINGLE_TRACK,\0 to view a single track.
PIANO_ROLL_PANEL_INPUT_TTS_MULTI_TRACK,\0 to view multiple tracks.
PIANO_ROLL_PANEL_INPUT_TTS_TRACK_SCROLL,\0 and \1 to select a track.
PIANO_ROLL_PANEL_INPUT_TTS_NOT_ARMED,\0 to arm the track.
PIANO_ROLL_PANEL_INPUT_TTS_ARMED,\0 to disarm the track. \1 and \2 to set the input beat.
PIANO_ROLL_PANEL_INPUT_TTS_NOTES,"\0, \1, \2, \3, \4, \5, \6, \7, \8, \9, \10, and \11 to play notes. \12 and \13 to change octave."
PIANO_ROLL_PANEL_INPUT_TTS_DO_NOT_USE_VOLUME,\0 and \1 to set the input volume. \2 to start using MIDI input volume instead. 
PIANO_ROLL_PANEL_INPUT_TTS_USE_VOLUME,\0 to make all new notes have the input volume value.
PIANO_ROLL_PANEL_INPUT_TTS_MODES,"\0, \1, \2, or \3 to set the mode to time, view, select, or edit."
PIANO_ROLL_PANEL_INPUT_TTS_COPY_CUT,\0 or \1 to copy or cut the selected notes.
PIANO_ROLL_PANEL_INPUT_TTS_PASTE,\0 to paste notes.
PIANO_ROLL_PANEL_INPUT_TTS_DELETE,\0 to delete the selected notes.
PIANO_ROLL_PANEL_INPUT_TTS_EDIT_MODE,\0 to set the edit mode to %0.
PIANO_ROLL_PANEL_INPUT_TTS_SELECT_SINGLE,\0 and \1 to select a different note.
PIANO_ROLL_PANEL_INPUT_TTS_SELECT_MANY,\0 and \1 to set the start of the selection. \2 and \3 to set the end of the selection.
PIANO_ROLL_PANEL_INPUT_TTS_SELECT_ALL,\0 to select all. 
PIANO_ROLL_PANEL_INPUT_TTS_DESELECT,\0 to deselect.
PIANO_ROLL_PANEL_INPUT_TTS_SELECT_CYCLE_TO_SINGLE,\0 to select only one note.
PIANO_ROLL_PANEL_INPUT_TTS_SELECT_CYCLE_TO_MANY,\0 to select multiple notes.
PIANO_ROLL_PANEL_INPUT_TTS_EDIT_0,\0 and \1 to set the pitch.
PIANO_ROLL_PANEL_INPUT_TTS_EDIT_1,\0 and \1 to set the start time. 
PIANO_ROLL_PANEL_INPUT_TTS_EDIT_2,\0 and \1 to set the duration.
PIANO_ROLL_PANEL_INPUT_TTS_EDIT_3,\0 and \1 to set the volume.
PIANO_ROLL_PANEL_INPUT_TTS_TIME_0,\0 and \1 to move the cursor.
PIANO_ROLL_PANEL_INPUT_TTS_TIME_1,\0 and \1 to set the cursor to the start and end.
PIANO_ROLL_PANEL_INPUT_TTS_TIME_2,\0 to set the cursor to the nearest beat. 
PIANO_ROLL_PANEL_INPUT_TTS_TIME_3,\0 to set the cursor to the playback time. 
PIANO_ROLL_PANEL_INPUT_TTS_TIME_4,\0 and \1 to move the playback time.
PIANO_ROLL_PANEL_INPUT_TTS_TIME_5,\0 and \1 to set the playback time to the start and end. 
PIANO_ROLL_PANEL_INPUT_TTS_TIME_6,\0 to set the playback time to the nearest beat. 
PIANO_ROLL_PANEL_INPUT_TTS_TIME_7,\0 to set the playback time to the cursor.
PIANO_ROLL_PANEL_INPUT_TTS_VIEW_SINGLE_TRACK_0,"\0, \1, \2, and \3 to move the view."
PIANO_ROLL_PANEL_INPUT_TTS_VIEW_SINGLE_TRACK_1,\0 and \1 to set the view to the start and end. 
PIANO_ROLL_PANEL_INPUT_TTS_VIEW_SINGLE_TRACk_2,\0 and \1 to zoom in and out. 
PIANO_ROLL_PANEL_INPUT_TTS_VIEW_SINGLE_TRACK_3,\0 to reset the zoom level."
PIANO_ROLL_PANEL_INPUT_TTS_VIEW_MULTI_TRACK_0,\0 and \1 to move the view.
PIANO_ROLL_PANEL_INPUT_TTS_VIEW_MULTI_TRACK_1,\0 and \1 to set the view to the start and end."
PIANO_ROLL_MODE_TIME,Time
PIANO_ROLL_MODE_VIEW,View
PIANO_ROLL_MODE_SELECT,Select
PIANO_ROLL_MODE_EDIT,Edit
FRACTION_TTS_ONE_THIRTY_SECOND,one thirty-second
FRACTION_TTS_ONE_SIXTEENTH,one sixteenth
FRACTION_TTS_ONE_EIGHTH,one eighth
FRACTION_TTS_ONE_SIXTH,one sixth
FRACTION_TTS_ONE_FOURTH,one fourth
FRACTION_TTS_ONE_THIRD,one third
FRACTION_TTS_ONE_HALF,one half
FRACTION_TTS_ONE_AND_A_HALF,one and a half
EDIT_MODE_NORMAL,Normal
EDIT_MODE_QUICK,Quick
EDIT_MODE_PRECISE,Precise
ERROR,ERROR: \0
TRUE,Y
FALSE,N
TITLE_MAIN_MENU,Cacophony
TITLE_MUSIC,Music
TITLE_TRACKS,Tracks
TITLE_PIANO_ROLL,Piano Roll
TITLE_OPEN_FILE,Open File
TITLE_EXPORT_STATE,Exporting...
TITLE_EXPORT_SETTINGS,Settings
TITLE_QUIT,Really quit?
TITLE_LINKS,Open a link in your browser
TITLE_BPM,BPM
TITLE_GAIN,Gain
MAIN_MENU_HELP,Help:
MAIN_MENU_STATUS,\0 Status
MAIN_MENU_INPUT,\0 Input
MAIN_MENU_APP,\0 App
MAIN_MENU_FILE,\0 File
MAIN_MENU_STOP,\0 Stop
MAIN_MENU_ONLINE,\0 Links
MAIN_MENU_UPDATE,Update available: v\0
TRACKS_PANEL_BANK,Bank
TRACKS_PANEL_GAIN,Gain
TRACKS_PANEL_MUTE,M
TRACKS_PANEL_SOLO,S
TRACKS_PANEL_TRACK_TITLE,Track \0
PIANO_ROLL_PANEL_TOP_BAR_ARMED,Armed
PIANO_ROLL_PANEL_TOP_BAR_BEAT,Beat
PIANO_ROLL_PANEL_TOP_BAR_USE_VOLUME,Use Volume
PIANO_ROLL_PANEL_TOP_BAR_VOLUME,Volume
PIANO_ROLL_PANEL_TOP_BAR_TIME,Time
PIANO_ROLL_PANEL_TOP_BAR_VIEW,View
PIANO_ROLL_PANEL_TOP_BAR_SELECT,Select
PIANO_ROLL_PANEL_TOP_BAR_EDIT,Edit
PIANO_ROLL_PANEL_EDIT_MODE_NORMAL,Edit Mode: Normal
PIANO_ROLL_PANEL_EDIT_MODE_QUICK,Edit Mode: Quick
PIANO_ROLL_PANEL_EDIT_MODE_PRECISE,Edit Mode: Precise
PIANO_ROLL_PANEL_EDIT_MODE_SINGLE,Edit Mode: Single
PIANO_ROLL_PANEL_EDIT_MODE_MANY,Edit Mode: Many
PIANO_ROLL_PANEL_VIEW_DT,View: \0 to \1
PIANO_ROLL_PANEL_CURSOR_TIME,Cursor: \0
PIANO_ROLL_PANEL_PLAYBACK_TIME,Playback: \0
PIANO_ROLL_PANEL_SELECTED_SINGLE,Selected: \0 at \1
PIANO_ROLL_PANEL_SELECTED_MANY,Selected: \0 to \1
PIANO_ROLL_PANEL_SELECTED_NONE,Selected: None
PIANO_ROLL_PANEL_VOLUME_TITLE,Volume
EXPORT_SETTINGS_PANEL_STATUS_TTS_FRAMERATE,Framerate is selected.
EXPORT_SETTINGS_PANEL_STATUS_TTS_TITLE_NO_ABC123,The title is %0. \0 to edit.
EXPORT_SETTINGS_PANEL_STATUS_TTS_TITLE_ABC123,The title is \0. You can edit it.
EXPORT_SETTINGS_PANEL_STATUS_TTS_ARTIST_NO_ABC123,The artist is %0. \0 to edit.
EXPORT_SETTINGS_PANEL_STATUS_TTS_ARTIST,The art
Download .txt
gitextract_lgvougrd/

├── .github/
│   └── workflows/
│       ├── build.yaml
│       └── test.yaml
├── .gitignore
├── .vscode/
│   └── settings.json
├── Cargo.toml
├── LICENSE
├── README.md
├── audio/
│   ├── Cargo.toml
│   ├── src/
│   │   ├── command.rs
│   │   ├── conn.rs
│   │   ├── decayer.rs
│   │   ├── export/
│   │   │   ├── export_setting.rs
│   │   │   ├── export_state.rs
│   │   │   ├── export_type.rs
│   │   │   ├── exportable.rs
│   │   │   ├── metadata.rs
│   │   │   └── multi_file_suffix.rs
│   │   ├── export.rs
│   │   ├── exporter.rs
│   │   ├── lib.rs
│   │   ├── midi_event_queue.rs
│   │   ├── play_state.rs
│   │   ├── player.rs
│   │   ├── program.rs
│   │   ├── synth_state.rs
│   │   ├── timed_midi_event.rs
│   │   └── types.rs
│   └── tests/
│       └── CT1MBGMRSV1.06.sf2
├── changelog.md
├── common/
│   ├── Cargo.toml
│   └── src/
│       ├── args.rs
│       ├── config.rs
│       ├── edit_mode.rs
│       ├── font.rs
│       ├── fraction.rs
│       ├── index.rs
│       ├── indexed_values.rs
│       ├── input_state.rs
│       ├── lib.rs
│       ├── midi_track.rs
│       ├── music.rs
│       ├── music_panel_field.rs
│       ├── note.rs
│       ├── open_file/
│       │   ├── child_paths.rs
│       │   ├── extension.rs
│       │   ├── file_and_directory.rs
│       │   ├── file_or_directory.rs
│       │   └── open_file_type.rs
│       ├── open_file.rs
│       ├── panel_type.rs
│       ├── paths.rs
│       ├── paths_state.rs
│       ├── piano_roll_mode.rs
│       ├── select_mode.rs
│       ├── sizes.rs
│       ├── state.rs
│       ├── time.rs
│       ├── u64_or_f32.rs
│       └── view.rs
├── data/
│   ├── CT1MBGMRSV1.06.sf2
│   ├── attribution.txt
│   ├── config.ini
│   ├── icon
│   └── text.csv
├── html/
│   ├── index.html
│   ├── install.html
│   ├── limitations.html
│   ├── manifesto.html
│   ├── me.html
│   ├── privacy.html
│   ├── roadmap.html
│   ├── style.css
│   └── user_guide.html
├── input/
│   ├── Cargo.toml
│   └── src/
│       ├── debug_input_event.rs
│       ├── input_event.rs
│       ├── keys.rs
│       ├── lib.rs
│       ├── midi_binding.rs
│       ├── midi_conn.rs
│       ├── note_on.rs
│       └── qwerty_binding.rs
├── io/
│   ├── Cargo.toml
│   └── src/
│       ├── abc123.rs
│       ├── export_panel.rs
│       ├── export_settings_panel.rs
│       ├── import_midi.rs
│       ├── io_command.rs
│       ├── lib.rs
│       ├── links_panel.rs
│       ├── music_panel.rs
│       ├── open_file_panel.rs
│       ├── panel.rs
│       ├── piano_roll/
│       │   ├── edit.rs
│       │   ├── edit_mode_deltas.rs
│       │   ├── piano_roll_panel.rs
│       │   ├── piano_roll_sub_panel.rs
│       │   ├── select.rs
│       │   ├── time.rs
│       │   └── view.rs
│       ├── piano_roll.rs
│       ├── popup.rs
│       ├── quit_panel.rs
│       ├── save.rs
│       ├── snapshot.rs
│       └── tracks_panel.rs
├── py/
│   ├── build.py
│   ├── itch_changelog.py
│   └── macroquad_icon_creator.py
├── render/
│   ├── Cargo.toml
│   └── src/
│       ├── color_key.rs
│       ├── drawable.rs
│       ├── export_panel.rs
│       ├── export_settings_panel.rs
│       ├── field_params/
│       │   ├── boolean.rs
│       │   ├── boolean_corners.rs
│       │   ├── key_input.rs
│       │   ├── key_list.rs
│       │   ├── key_list_corners.rs
│       │   ├── key_width.rs
│       │   ├── label.rs
│       │   ├── label_rectangle.rs
│       │   ├── label_ref.rs
│       │   ├── line.rs
│       │   ├── list.rs
│       │   ├── panel_background.rs
│       │   ├── rectangle.rs
│       │   ├── rectangle_pixel.rs
│       │   ├── util.rs
│       │   └── width.rs
│       ├── field_params.rs
│       ├── lib.rs
│       ├── links_panel.rs
│       ├── main_menu.rs
│       ├── music_panel.rs
│       ├── open_file_panel.rs
│       ├── page.rs
│       ├── page_position.rs
│       ├── panel.rs
│       ├── panels.rs
│       ├── piano_roll_panel/
│       │   ├── multi_track.rs
│       │   ├── piano_roll_rows.rs
│       │   ├── top_bar.rs
│       │   ├── viewable_notes.rs
│       │   └── volume.rs
│       ├── piano_roll_panel.rs
│       ├── popup.rs
│       ├── quit_panel.rs
│       ├── renderer.rs
│       ├── tracks_panel.rs
│       └── types.rs
├── src/
│   └── main.rs
├── test_files/
│   ├── child_paths/
│   │   ├── test_0.cac
│   │   ├── test_1.cac
│   │   └── test_2.CAC
│   └── ubuntu20.04/
│       └── speechd.conf
└── text/
    ├── Cargo.toml
    └── src/
        ├── lib.rs
        ├── tooltips.rs
        ├── tts.rs
        ├── tts_string.rs
        └── value_map.rs
Download .txt
SYMBOL INDEX (802 symbols across 122 files)

FILE: audio/src/command.rs
  type Command (line 5) | pub enum Command {

FILE: audio/src/conn.rs
  type SoundFontBanks (line 22) | struct SoundFontBanks {
    method new (line 29) | pub fn new(font: SoundFont, synth: &mut SharedSynth) -> Self {
  type Conn (line 46) | pub struct Conn {
    method note_ons (line 120) | pub fn note_ons(&mut self, state: &State, note_ons: &[[u8; 3]]) {
    method note_offs (line 140) | pub fn note_offs(&mut self, state: &State, note_offs: &[u8]) {
    method do_commands (line 155) | pub fn do_commands(&mut self, commands: &[Command]) {
    method set_music (line 219) | pub fn set_music(&mut self, state: &State) {
    method exporting (line 227) | pub fn exporting(&self) -> bool {
    method on_new_file (line 232) | pub fn on_new_file(&mut self, state: &State) {
    method start_music (line 240) | fn start_music(&mut self, state: &State) {
    method stop_music (line 286) | fn stop_music(&mut self, music: &Music) {
    method set_program_default (line 308) | fn set_program_default(&mut self, channel: u8, path: &Path) {
    method set_program (line 321) | fn set_program(&mut self, channel: u8, path: &Path, bank: u32, preset:...
    method start_export (line 350) | pub fn start_export(&mut self, state: &State, paths_state: &PathsState) {
    method enqueue_track_events (line 406) | fn enqueue_track_events(
    method export (line 440) | fn export(
    method set_export_framerate (line 518) | fn set_export_framerate(&mut self) {
    method set_export_state_wav (line 525) | fn set_export_state_wav(
    method set_export_state (line 538) | fn set_export_state(export_state: &SharedExportState, state: ExportSta...
    method get_export_file_suffix (line 543) | fn get_export_file_suffix(&self, track: &MidiTrack) -> String {
  method default (line 75) | fn default() -> Self {

FILE: audio/src/decayer.rs
  constant DECAY_CHUNK_SIZE (line 5) | const DECAY_CHUNK_SIZE: usize = 4096;
  constant SILENCE (line 8) | const SILENCE: f32 = 1e-7;
  type Decayer (line 11) | pub(crate) struct Decayer {
    method decay_shared (line 28) | pub fn decay_shared(&mut self, synth: &SharedSynth, len: usize) {
    method decay_two_channels (line 36) | pub fn decay_two_channels(
    method set_decaying (line 50) | fn set_decaying(&mut self, len: usize) {
    method get_len (line 56) | fn get_len(len: usize) -> usize {
  method default (line 18) | fn default() -> Self {

FILE: audio/src/export/export_setting.rs
  type ExportSetting (line 5) | pub enum ExportSetting {

FILE: audio/src/export/export_state.rs
  type ExportState (line 2) | pub enum ExportState {

FILE: audio/src/export/export_type.rs
  type ExportType (line 6) | pub enum ExportType {
  method from (line 16) | fn from(val: ExportType) -> Self {

FILE: audio/src/export/exportable.rs
  type Exportable (line 3) | pub(crate) struct Exportable {

FILE: audio/src/export/metadata.rs
  constant DEFAULT_TITLE (line 4) | pub const DEFAULT_TITLE: &str = "My Music";
  type Metadata (line 8) | pub struct Metadata {
  method default (line 24) | fn default() -> Self {

FILE: audio/src/export/multi_file_suffix.rs
  type MultiFileSuffix (line 5) | pub enum MultiFileSuffix {

FILE: audio/src/exporter.rs
  constant NUM_CHANNELS (line 29) | const NUM_CHANNELS: usize = 2;
  constant F32_TO_I16 (line 31) | const F32_TO_I16: f32 = 32767.5;
  constant MP3_BIT_RATES (line 33) | pub const MP3_BIT_RATES: [Bitrate; 16] = [
  constant MP3_QUALITIES (line 52) | pub const MP3_QUALITIES: [Quality; 10] = [
  type Exporter (line 72) | pub struct Exporter {
    method mid (line 201) | pub fn mid(&self, path: &Path, music: &Music, time: &Time, synth_state...
    method wav (line 318) | pub(crate) fn wav(&self, path: &Path, buffer: &AudioBuffer) {
    method mp3 (line 341) | pub(crate) fn mp3<'a, T: 'a>(&self, path: &Path, buffer: &'a [Vec<T>; ...
    method ogg (line 412) | pub(crate) fn ogg(&self, path: &Path, buffer: &AudioBuffer) {
    method flac (line 462) | pub(crate) fn flac(&self, path: &Path, buffer: &AudioBuffer) {
    method write_file (line 508) | fn write_file(path: &Path, samples: &[u8]) {
    method get_delta_time (line 521) | fn get_delta_time(ppq: &mut u64) -> u28 {
    method to_i16 (line 530) | fn to_i16(sample: &f32) -> i16 {
    method to_i32 (line 535) | fn to_i32(sample: &f32) -> i32 {
    method get_copyright (line 540) | fn get_copyright(&self, artist: &str) -> String {
  method default (line 106) | fn default() -> Self {
  function default_flac_settings (line 545) | fn default_flac_settings() -> IndexedValues<ExportSetting, 10> {

FILE: audio/src/midi_event_queue.rs
  type MidiEventQueue (line 6) | pub(crate) struct MidiEventQueue {
    method enqueue (line 16) | pub(crate) fn enqueue(&mut self, time: u64, event: MidiEvent) {
    method get_next_time (line 21) | pub(crate) fn get_next_time(&self) -> Option<u64> {
    method sort (line 30) | pub(crate) fn sort(&mut self) {
    method dequeue (line 35) | pub(crate) fn dequeue(&mut self, time: u64) -> Vec<MidiEvent> {
    method clear (line 44) | pub(crate) fn clear(&mut self) {

FILE: audio/src/play_state.rs
  type PlayState (line 2) | pub enum PlayState {

FILE: audio/src/player.rs
  constant ERROR_MESSAGE (line 9) | const ERROR_MESSAGE: &str = "Failed to create an audio output stream: ";
  type Player (line 13) | pub(crate) struct Player {
    method new (line 23) | pub(crate) fn new(
    method run (line 70) | fn run(
    method begin_decay (line 205) | fn begin_decay(

FILE: audio/src/program.rs
  type Program (line 6) | pub struct Program {
  method clone (line 26) | fn clone(&self) -> Self {

FILE: audio/src/synth_state.rs
  type SynthState (line 8) | pub struct SynthState {
  method default (line 16) | fn default() -> Self {
  method clone (line 25) | fn clone(&self) -> Self {

FILE: audio/src/timed_midi_event.rs
  type TimedMidiEvent (line 6) | pub(crate) struct TimedMidiEvent {
  method cmp (line 14) | fn cmp(&self, other: &Self) -> Ordering {
  method partial_cmp (line 65) | fn partial_cmp(&self, other: &Self) -> Option<Ordering> {

FILE: audio/src/types.rs
  type AudioMessage (line 10) | pub type AudioMessage = (f32, f32);
  type CommandsMessage (line 12) | pub type CommandsMessage = Vec<Command>;
  type AudioBuffer (line 14) | pub(crate) type AudioBuffer = [Vec<f32>; 2];
  type SharedSynth (line 15) | pub(crate) type SharedSynth = Arc<Mutex<Synth>>;
  type SharedExportState (line 16) | pub type SharedExportState = Arc<Mutex<ExportState>>;
  type SharedMidiEventQueue (line 17) | pub(crate) type SharedMidiEventQueue = Arc<Mutex<MidiEventQueue>>;
  type SharedPlayState (line 18) | pub type SharedPlayState = Arc<Mutex<PlayState>>;
  type SharedSample (line 19) | pub(crate) type SharedSample = Arc<Mutex<AudioMessage>>;

FILE: common/src/args.rs
  type Args (line 8) | pub struct Args {

FILE: common/src/config.rs
  function load (line 9) | pub fn load() -> Ini {
  function string_to_value (line 23) | fn string_to_value<T>(value: &str) -> T
  function parse (line 38) | pub fn parse<T>(properties: &Properties, key: &str) -> T
  function parse_bool (line 50) | pub fn parse_bool(properties: &Properties, key: &str) -> bool {
  function parse_fractions (line 62) | pub fn parse_fractions(properties: &Properties, key: &str) -> Vec<f32> {
  function parse_float (line 76) | pub fn parse_float(properties: &Properties, key: &str) -> f32 {
  function parse_fraction (line 84) | pub fn parse_fraction(properties: &Properties, key: &str) -> Fraction {
  function parse_float_kv (line 92) | fn parse_float_kv(key: &str, value: &str) -> f32 {
  function parse_fraction_kv (line 120) | fn parse_fraction_kv(key: &str, value: &str) -> Fraction {

FILE: common/src/edit_mode.rs
  type IndexedEditModes (line 4) | pub type IndexedEditModes = IndexedValues<EditMode, 3>;
  type EditMode (line 7) | pub enum EditMode {
    method indexed (line 18) | pub fn indexed() -> IndexedEditModes {

FILE: common/src/font.rs
  function get_font_section (line 6) | pub fn get_font_section(config: &Ini) -> &Properties {
  function get_font_bytes (line 11) | pub fn get_font_bytes(config: &Ini) -> Vec<u8> {
  function get_font (line 16) | pub fn get_font(config: &Ini) -> Font {
  function get_subtitle_font (line 21) | pub fn get_subtitle_font(config: &Ini) -> Font {
  function get_font_from_bytes (line 26) | fn get_font_from_bytes(config: &Ini, key: &str) -> Vec<u8> {

FILE: common/src/fraction.rs
  type Fraction (line 7) | pub struct Fraction {
    method new (line 13) | pub fn new(numerator: u64, denominator: u64) -> Self {
    method invert (line 21) | pub fn invert(&mut self) {
    method from (line 27) | fn from(value: u64) -> Self {
    method from (line 33) | fn from(value: U64orF32) -> Self {
    type Output (line 45) | type Output = u64;
    method mul (line 47) | fn mul(self, rhs: u64) -> Self::Output {
    type Output (line 61) | type Output = Fraction;
    method mul (line 63) | fn mul(self, rhs: Fraction) -> Self::Output {
    type Output (line 80) | type Output = U64orF32;
    method mul (line 82) | fn mul(self, rhs: U64orF32) -> Self::Output {
    type Output (line 88) | type Output = u64;
    method div (line 90) | fn div(self, rhs: u64) -> Self::Output {
    type Output (line 104) | type Output = Fraction;
    method div (line 106) | fn div(self, rhs: Fraction) -> Self::Output {
    type Output (line 123) | type Output = U64orF32;
    method div (line 125) | fn div(self, rhs: U64orF32) -> Self::Output {
  method fmt (line 39) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  type Output (line 53) | type Output = u64;
  function mul (line 55) | fn mul(self, rhs: Fraction) -> Self::Output {
  type Output (line 72) | type Output = U64orF32;
  method mul (line 74) | fn mul(self, rhs: Fraction) -> Self::Output {
  type Output (line 96) | type Output = u64;
  function div (line 98) | fn div(self, rhs: Fraction) -> Self::Output {
  type Output (line 115) | type Output = U64orF32;
  method div (line 117) | fn div(self, rhs: Fraction) -> Self::Output {
  function fraction (line 136) | fn fraction() {

FILE: common/src/index.rs
  type Index (line 11) | pub struct Index<T>
  function new (line 27) | pub fn new(index: T, length: T) -> Self {
  function increment (line 37) | pub fn increment(&mut self, up: bool) {
  function increment_no_loop (line 61) | pub fn increment_no_loop(&mut self, up: bool) -> bool {
  function get (line 82) | pub fn get(&self) -> T {
  function set (line 87) | pub fn set(&mut self, index: T) {
  function get_length (line 96) | pub fn get_length(&self) -> T {
  method default (line 102) | fn default() -> Self {
  function index (line 112) | fn index() {

FILE: common/src/indexed_values.rs
  type IndexedValues (line 7) | pub struct IndexedValues<T, const N: usize>
  method default (line 23) | fn default() -> Self {
  function new (line 36) | pub fn new(index: usize, values: [T; N]) -> Self {
  function get (line 42) | pub fn get(&self) -> T {
  function get_ref (line 47) | pub fn get_ref(&self) -> &T {
  function get_values (line 55) | pub fn get_values(&self) -> (&[T; N], [bool; N]) {
  function indexed_values (line 73) | fn indexed_values() {

FILE: common/src/input_state.rs
  type InputState (line 6) | pub struct InputState {
  method default (line 23) | fn default() -> Self {

FILE: common/src/lib.rs
  constant VERSION (line 61) | pub const VERSION: &str = env!("CARGO_PKG_VERSION");
  constant MAX_VOLUME (line 63) | pub const MAX_VOLUME: u8 = 127;
  function get_bytes (line 66) | pub fn get_bytes(path: &Path) -> Vec<u8> {
  function get_default_data_folder (line 75) | pub fn get_default_data_folder() -> PathBuf {
  function get_test_config (line 83) | pub fn get_test_config() -> ini::Ini {

FILE: common/src/midi_track.rs
  type MidiTrack (line 6) | pub struct MidiTrack {
    method new (line 20) | pub fn new(channel: u8) -> Self {
    method get_end (line 31) | pub fn get_end(&self) -> Option<u64> {
    method get_gain_f (line 36) | pub fn get_gain_f(&self) -> f32 {
    method get_playback_notes (line 41) | pub fn get_playback_notes(&self, start: u64) -> Vec<Note> {
  method clone (line 55) | fn clone(&self) -> Self {

FILE: common/src/music.rs
  type Music (line 6) | pub struct Music {
    method get_selected_track (line 15) | pub fn get_selected_track(&self) -> Option<&MidiTrack> {
    method get_selected_track_mut (line 23) | pub fn get_selected_track_mut(&mut self) -> Option<&mut MidiTrack> {
    method get_playable_tracks (line 31) | pub fn get_playable_tracks(&self) -> Vec<&MidiTrack> {

FILE: common/src/music_panel_field.rs
  type MusicPanelField (line 5) | pub enum MusicPanelField {

FILE: common/src/note.rs
  constant MAX_NOTE (line 7) | pub const MAX_NOTE: u8 = 127;
  constant MIN_NOTE (line 9) | pub const MIN_NOTE: u8 = 12;
  constant MIDDLE_C (line 11) | pub const MIDDLE_C: u8 = 60;
  constant NOTE_NAMES (line 15) | pub const NOTE_NAMES: [&str; 115] = [
  type Note (line 28) | pub struct Note {
    method get_duration (line 41) | pub fn get_duration(&self) -> u64 {
    method set_t0_by (line 46) | pub fn set_t0_by(&mut self, dt: u64, positive: bool) {
    method get_name (line 57) | pub fn get_name(&self) -> &str {
  method cmp (line 63) | fn cmp(&self, other: &Self) -> Ordering {
  method partial_cmp (line 69) | fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
  method fmt (line 75) | fn fmt(&self, f: &mut Formatter) -> Result {
  method serialize (line 85) | fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::E...
  function note_duration (line 105) | fn note_duration() {
  function note_serialization (line 111) | fn note_serialization() {
  function get_note (line 126) | fn get_note() -> Note {

FILE: common/src/open_file/child_paths.rs
  type ChildPaths (line 7) | pub struct ChildPaths {
    method set (line 20) | pub fn set(
    method get_paths_in_directory (line 68) | fn get_paths_in_directory(
  function test_sf2_child_paths (line 112) | fn test_sf2_child_paths() {
  function test_cac_child_paths (line 172) | fn test_cac_child_paths() {
  function test_cac_file (line 183) | fn test_cac_file(child_paths: &ChildPaths, index: usize, filename: &str) {

FILE: common/src/open_file/extension.rs
  type Extension (line 5) | pub enum Extension {
    method to_str (line 19) | pub fn to_str(&self, period: bool) -> &str {

FILE: common/src/open_file/file_and_directory.rs
  type FileAndDirectory (line 7) | pub struct FileAndDirectory {
    method new_directory (line 16) | pub fn new_directory(directory: PathBuf) -> Self {
    method new_path (line 24) | pub fn new_path(path: PathBuf) -> Self {
    method get_path (line 34) | pub fn get_path(&self) -> PathBuf {
    method try_get_path (line 42) | pub fn try_get_path(&self) -> Option<PathBuf> {
    method get_filename (line 49) | pub fn get_filename(&self) -> String {

FILE: common/src/open_file/file_or_directory.rs
  type FileOrDirectory (line 6) | pub struct FileOrDirectory {
    method new (line 16) | pub fn new(path: &Path) -> Self {

FILE: common/src/open_file/open_file_type.rs
  type OpenFileType (line 3) | pub enum OpenFileType {

FILE: common/src/panel_type.rs
  type PanelType (line 5) | pub enum PanelType {

FILE: common/src/paths.rs
  constant CONFIG_FILENAME (line 7) | const CONFIG_FILENAME: &str = "config.ini";
  type Paths (line 14) | pub struct Paths {
    method init (line 39) | pub fn init(data_directory_from_cli: &Path) {
    method get_test_paths (line 46) | pub fn get_test_paths() -> Self {
    method new (line 51) | fn new(data_directory: &Path) -> Self {
    method get (line 89) | pub fn get() -> &'static Self {
    method create_user_config (line 94) | pub fn create_user_config(&self) {
  function get_data_directory (line 105) | pub fn get_data_directory(data_directory: &Path) -> PathBuf {
  function get_directory (line 129) | fn get_directory(folder: &str, user_directory: &Path) -> PathBuf {

FILE: common/src/paths_state.rs
  type PathsState (line 12) | pub struct PathsState {
    method new (line 30) | pub fn new(paths: &Paths) -> Self {
    method get_directory (line 45) | pub fn get_directory(&self) -> &FileOrDirectory {
    method get_filename (line 55) | pub fn get_filename(&self) -> Option<String> {
    method up_directory (line 64) | pub fn up_directory(&mut self, extension: &Extension) -> bool {
    method down_directory (line 84) | pub fn down_directory(&mut self, extension: &Extension) -> bool {
    method scroll (line 128) | pub fn scroll(&mut self, up: bool) -> bool {
    method set_filename (line 145) | pub fn set_filename(&mut self, filename: &str) {
    method get_path (line 160) | pub fn get_path(&self) -> PathBuf {
    method up_directory_type (line 170) | fn up_directory_type(

FILE: common/src/piano_roll_mode.rs
  type PianoRollMode (line 5) | pub enum PianoRollMode {

FILE: common/src/select_mode.rs
  type SelectMode (line 6) | pub enum SelectMode {
    method get_note_indices (line 24) | pub fn get_note_indices(&self) -> Option<Vec<usize>> {
    method get_notes (line 35) | pub fn get_notes<'a>(&self, music: &'a Music) -> Option<Vec<&'a Note>> {
    method get_notes_mut (line 50) | pub fn get_notes_mut<'a>(&mut self, music: &'a mut Music) -> Option<Ve...
  method clone (line 14) | fn clone(&self) -> Self {

FILE: common/src/sizes.rs
  constant MAIN_MENU_HEIGHT (line 7) | pub const MAIN_MENU_HEIGHT: u32 = 3;
  constant MUSIC_PANEL_POSITION (line 9) | pub const MUSIC_PANEL_POSITION: [u32; 2] = [0, 0];
  constant MUSIC_PANEL_HEIGHT (line 11) | pub const MUSIC_PANEL_HEIGHT: u32 = 6;
  constant PIANO_ROLL_PANEL_TOP_BAR_HEIGHT (line 13) | pub const PIANO_ROLL_PANEL_TOP_BAR_HEIGHT: u32 = 3;
  constant PIANO_ROLL_PANEL_NOTE_NAMES_WIDTH (line 15) | pub const PIANO_ROLL_PANEL_NOTE_NAMES_WIDTH: u32 = 3;
  constant PIANO_ROLL_PANEL_VOLUME_HEIGHT (line 17) | pub const PIANO_ROLL_PANEL_VOLUME_HEIGHT: u32 = 5;
  constant OPEN_FILE_PANEL_PROMPT_HEIGHT (line 19) | pub const OPEN_FILE_PANEL_PROMPT_HEIGHT: u32 = 3;
  function get_font_size (line 22) | pub fn get_font_size(config: &Ini) -> u16 {
  function get_cell_size (line 27) | pub fn get_cell_size(config: &Ini) -> [f32; 2] {
  function get_window_grid_size (line 35) | pub fn get_window_grid_size(config: &Ini) -> [u32; 2] {
  function get_window_pixel_size (line 44) | pub fn get_window_pixel_size(config: &Ini) -> [f32; 2] {
  function get_piano_roll_panel_position (line 54) | pub fn get_piano_roll_panel_position(config: &Ini) -> [u32; 2] {
  function get_piano_roll_panel_size (line 60) | pub fn get_piano_roll_panel_size(config: &Ini) -> [u32; 2] {
  function get_tracks_panel_width (line 70) | pub fn get_tracks_panel_width(config: &Ini) -> u32 {
  function get_line_width (line 78) | pub fn get_line_width(config: &Ini) -> f32 {
  function get_viewport_size (line 83) | pub fn get_viewport_size(config: &Ini) -> [u32; 2] {
  function get_open_file_rect (line 91) | pub fn get_open_file_rect(config: &Ini) -> ([u32; 2], [u32; 2]) {
  function get_main_menu_position (line 99) | pub fn get_main_menu_position(config: &Ini) -> [u32; 2] {
  function get_subtitle_width (line 108) | pub fn get_subtitle_width(config: &Ini) -> f32 {
  function get_main_menu_width (line 113) | pub fn get_main_menu_width(config: &Ini) -> u32 {

FILE: common/src/state.rs
  type State (line 12) | pub struct State {
    method new (line 39) | pub fn new(config: &Ini) -> State {

FILE: common/src/time.rs
  constant DEFAULT_BPM (line 7) | pub const DEFAULT_BPM: u64 = 120;
  constant BPM_TO_SECONDS (line 9) | const BPM_TO_SECONDS: f32 = 60.0;
  constant PPQ_U (line 11) | pub const PPQ_U: u64 = 192;
  constant PPQ_F (line 13) | pub const PPQ_F: f32 = PPQ_U as f32;
  constant DEFAULT_FRAMERATE (line 15) | pub const DEFAULT_FRAMERATE: u64 = 44100;
  type Time (line 19) | pub struct Time {
    method ppq_to_seconds (line 32) | pub fn ppq_to_seconds(&self, ppq: u64) -> f32 {
    method ppq_to_samples (line 37) | pub fn ppq_to_samples(&self, ppq: u64, framerate: f32) -> u64 {
    method ppq_to_duration (line 42) | pub fn ppq_to_duration(&self, ppq: u64) -> Duration {
    method samples_to_ppq (line 47) | pub fn samples_to_ppq(&self, samples: u64, framerate: f32) -> u64 {
    method reset (line 51) | pub fn reset(&mut self) {
  method default (line 58) | fn default() -> Self {
  function time (line 73) | fn time() {
  function ppq_seconds (line 113) | fn ppq_seconds(ppq: u64, f: f32, time: &Time) {
  function ppq_samples (line 118) | fn ppq_samples(ppq: u64, v: u64, framerate: f32, time: &Time) {
  function samples_ppq (line 123) | fn samples_ppq(samples: u64, v: u64, framerate: f32, time: &Time) {

FILE: common/src/u64_or_f32.rs
  type U64orF32 (line 7) | pub struct U64orF32 {
    method get_u (line 16) | pub fn get_u(&self) -> u64 {
    method get_f (line 21) | pub fn get_f(&self) -> f32 {
    method set (line 25) | pub fn set(&mut self, value: u64) {
    method from (line 34) | fn from(value: u64) -> Self {
    method from (line 43) | fn from(value: f32) -> Self {
    type Value (line 65) | type Value = Self;
    method expecting (line 67) | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
    method visit_u8 (line 71) | fn visit_u8<E>(self, value: u8) -> Result<Self::Value, E>
    method visit_u32 (line 78) | fn visit_u32<E>(self, value: u32) -> Result<Self::Value, E>
    method visit_u64 (line 85) | fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
    method visit_f32 (line 92) | fn visit_f32<E>(self, value: f32) -> Result<Self::Value, E>
    method deserialize (line 101) | fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
  method fmt (line 50) | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
  method serialize (line 56) | fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
  function set_uf (line 115) | fn set_uf() {
  function uf_serialization (line 123) | fn uf_serialization() {
  function uf_deserialization (line 131) | fn uf_deserialization() {
  function uf_eq (line 136) | fn uf_eq(v: U64orF32) {
  function deserialize_uf (line 140) | fn deserialize_uf(s: &str, v: U64orF32) {

FILE: common/src/view.rs
  constant MIN_ZOOM (line 10) | const MIN_ZOOM: u64 = PPQ_U * 2;
  constant MAX_ZOOM (line 11) | const MAX_ZOOM: u64 = PPQ_U * 10000;
  type View (line 15) | pub struct View {
    method new (line 37) | pub fn new(config: &Ini) -> Self {
    method get_dt (line 105) | pub fn get_dt(&self) -> u64 {
    method set_start_time_by (line 113) | pub fn set_start_time_by(&mut self, dt: u64, add: bool) {
    method set_top_note_by (line 129) | pub fn set_top_note_by(&mut self, dn: u8, add: bool) {
    method zoom (line 154) | pub fn zoom(&mut self, up: bool) {
    method reset (line 182) | pub fn reset(&mut self) {
    method get_dn (line 195) | fn get_dn(&self) -> u8 {
  constant VIEW_T1 (line 204) | const VIEW_T1: u64 = PPQ_U * 133;
  function view_new (line 207) | fn view_new() {
  function view_dt (line 216) | fn view_dt() {
  function view_dz (line 231) | fn view_dz() {
  function get_new_view (line 249) | fn get_new_view() -> View {

FILE: input/src/debug_input_event.rs
  type DebugInputEvent (line 3) | pub(crate) enum DebugInputEvent {

FILE: input/src/input_event.rs
  type InputEvent (line 5) | pub enum InputEvent {

FILE: input/src/keys.rs
  constant KEYS (line 3) | pub const KEYS: [KeyCode; 121] = [
  constant MODS (line 128) | pub(crate) const MODS: [KeyCode; 7] = [
  constant ALPHANUMERIC_INPUT_MODS (line 138) | pub(crate) const ALPHANUMERIC_INPUT_MODS: [KeyCode; 2] = [KeyCode::Backs...

FILE: input/src/lib.rs
  constant MAX_OCTAVE (line 33) | const MAX_OCTAVE: u8 = 9;
  constant ALLOWED_DURING_ALPHANUMERIC_INPUT (line 35) | const ALLOWED_DURING_ALPHANUMERIC_INPUT: [InputEvent; 12] = [
  constant QWERTY_NOTE_EVENTS (line 50) | const QWERTY_NOTE_EVENTS: [(InputEvent, u8); 12] = [
  constant ILLEGAL_FILENAME_CHARACTERS (line 65) | const ILLEGAL_FILENAME_CHARACTERS: [char; 23] = [
  type Input (line 72) | pub struct Input {
    method new (line 102) | pub fn new(config: &Ini, args: &Args) -> Self {
    method update (line 169) | pub fn update(&mut self, state: &State) {
    method happened (line 313) | pub fn happened(&self, event: &InputEvent) -> bool {
    method get_bindings (line 318) | pub fn get_bindings(
    method modify_string_abc123 (line 326) | pub fn modify_string_abc123(&self, string: &mut String) -> bool {
    method modify_filename_abc123 (line 339) | pub fn modify_filename_abc123(&self, string: &mut String) -> bool {
    method is_valid_char (line 352) | fn is_valid_char(c: &char) -> bool {
    method modify_u64 (line 357) | pub fn modify_u64(&self, value: &mut u64) -> bool {
    method modify_value (line 362) | fn modify_value<T>(&self, value: &mut T, default_value: T) -> bool
    method modify_string (line 387) | fn modify_string(&self, string: &mut String, chars: &[char]) -> bool {
    method parse_qwerty_binding (line 403) | fn parse_qwerty_binding(key: &str, value: &str) -> (InputEvent, Qwerty...
    method parse_midi_binding (line 411) | fn parse_midi_binding(key: &str, value: &str) -> (InputEvent, MidiBind...
    method qwerty_note (line 425) | fn qwerty_note(&mut self, note: u8, state: &State) {
    method get_pitch (line 436) | fn get_pitch(&self, note: u8) -> u8 {
    method clear_notes_on_qwerty_octave (line 441) | fn clear_notes_on_qwerty_octave(&mut self) {
    method listen_for_note_offs (line 449) | fn listen_for_note_offs(&mut self) {

FILE: input/src/midi_binding.rs
  type MidiBinding (line 5) | pub struct MidiBinding {
    method update (line 17) | pub(crate) fn update(&mut self, buffer: &[[u8; 3]], counter: i16) -> b...

FILE: input/src/midi_conn.rs
  constant MIDI_ERROR_MESSAGE (line 6) | const MIDI_ERROR_MESSAGE: &str = "Couldn't connect to a MIDI input device";
  type MidiBuffer (line 8) | type MidiBuffer = Arc<Mutex<Vec<[u8; 3]>>>;
  type MidiConn (line 13) | pub(crate) struct MidiConn {
    method new (line 22) | pub(crate) fn new() -> Option<Self> {
    method midi_callback (line 70) | fn midi_callback(_: u64, message: &[u8], sender: &mut MidiBuffer) {
  function midi_test (line 94) | fn midi_test() {

FILE: input/src/note_on.rs
  type NoteOn (line 2) | pub(crate) struct NoteOn {
    method new (line 10) | pub(crate) fn new(note: &[u8; 3]) -> Self {

FILE: input/src/qwerty_binding.rs
  type QwertyBinding (line 9) | pub struct QwertyBinding {
    method deserialize (line 28) | pub(crate) fn deserialize(string: &str) -> Self {
    method update (line 90) | pub(crate) fn update(
  type SerializableQwertyBinding (line 129) | struct SerializableQwertyBinding {
  function keycode_from_str (line 140) | fn keycode_from_str(s: &str) -> Option<KeyCode> {

FILE: io/src/abc123.rs
  type AlphanumericModifiable (line 6) | pub(crate) trait AlphanumericModifiable {
    method is_valid (line 8) | fn is_valid(&self) -> bool;
    method modify (line 11) | fn modify(&mut self, input: &Input) -> bool;
    method is_valid (line 15) | fn is_valid(&self) -> bool {
    method modify (line 19) | fn modify(&mut self, input: &Input) -> bool {
    method is_valid (line 25) | fn is_valid(&self) -> bool {
    method modify (line 29) | fn modify(&mut self, input: &Input) -> bool {
    method is_valid (line 44) | fn is_valid(&self) -> bool {
    method modify (line 48) | fn modify(&mut self, input: &Input) -> bool {
    method is_valid (line 54) | fn is_valid(&self) -> bool {
    method modify (line 58) | fn modify(&mut self, input: &Input) -> bool {
  function update_exporter (line 74) | pub(crate) fn update_exporter<F, T>(mut f: F, input: &Input, exporter: &...
  function on_disable_exporter (line 89) | pub(crate) fn on_disable_exporter<F, T>(mut f: F, exporter: &mut Exporte...
  function update_state (line 109) | pub(crate) fn update_state<F, T>(mut f: F, state: &mut State, input: &In...
  function on_disable_state (line 122) | pub(crate) fn on_disable_state<F, T>(mut f: F, state: &mut State, defaul...

FILE: io/src/export_panel.rs
  type ExportPanel (line 7) | pub(crate) struct ExportPanel {
    method enable (line 16) | pub fn enable(&mut self, state: &mut State, panels: &[PanelType], focu...
  method update (line 25) | fn update(
  method on_disable_abc123 (line 43) | fn on_disable_abc123(&mut self, _: &mut State, _: &mut Conn) {}
  method update_abc123 (line 45) | fn update_abc123(
  method allow_alphanumeric_input (line 54) | fn allow_alphanumeric_input(&self, _: &State, _: &Conn) -> bool {
  method allow_play_music (line 58) | fn allow_play_music(&self) -> bool {

FILE: io/src/export_settings_panel.rs
  constant FRAMERATES (line 11) | const FRAMERATES: [u64; 3] = [22050, 44100, 48000];
  type ExportSettingsPanel (line 15) | pub(crate) struct ExportSettingsPanel {
    method get_status_abc123_tts (line 29) | fn get_status_abc123_tts(
    method get_status_bool_tts (line 64) | fn get_status_bool_tts(
    method get_input_scroll_tts (line 85) | fn get_input_scroll_tts(tooltips: &mut Tooltips, input: &Input, text: ...
    method get_input_abc123_tts (line 105) | fn get_input_abc123_tts(
    method get_input_lr_tts (line 138) | fn get_input_lr_tts(
    method set_framerate (line 162) | fn set_framerate(exporter: &mut Exporter, up: bool) {
    method set_track_number (line 176) | fn set_track_number(exporter: &mut Exporter, up: bool) {
    method set_index (line 195) | fn set_index<F>(mut f: F, input: &Input, exporter: &mut Exporter)
    method update_settings (line 215) | fn update_settings<F, const N: usize>(
    method update_settings_abc123 (line 497) | fn update_settings_abc123<F, const N: usize>(
    method disable_abc123 (line 520) | fn disable_abc123<F, const N: usize>(mut f: F, exporter: &mut Exporter)
    method allow_abc123 (line 545) | fn allow_abc123<F, const N: usize>(f: F, exporter: &Exporter) -> bool
  method update (line 562) | fn update(
  method update_abc123 (line 625) | fn update_abc123(
  method on_disable_abc123 (line 651) | fn on_disable_abc123(&mut self, _: &mut State, conn: &mut Conn) {
  method allow_alphanumeric_input (line 661) | fn allow_alphanumeric_input(&self, _: &State, conn: &Conn) -> bool {
  method allow_play_music (line 671) | fn allow_play_music(&self) -> bool {

FILE: io/src/import_midi.rs
  function import (line 8) | pub(crate) fn import(path: &Path, state: &mut State, conn: &mut Conn) {

FILE: io/src/io_command.rs
  type IOCommand (line 5) | pub(crate) enum IOCommand {
  type IOCommands (line 16) | pub(crate) type IOCommands = Option<Vec<IOCommand>>;

FILE: io/src/lib.rs
  constant MAX_UNDOS (line 59) | const MAX_UNDOS: usize = 100;
  type IO (line 67) | pub struct IO {
    method new (line 97) | pub fn new(config: &Ini, input: &Input, input_state: &InputState, text...
    method update (line 204) | pub fn update(
    method load_save (line 458) | pub fn load_save(
    method get_panel (line 470) | fn get_panel(&mut self, panel_type: &PanelType) -> &mut dyn Panel {
    method apply_snapshot (line 489) | fn apply_snapshot(
    method push_undo (line 546) | fn push_undo(&mut self, snapshot: Snapshot) {
  function select_track (line 559) | pub(crate) fn select_track(
  function deselect (line 583) | fn deselect(state: &mut State) {

FILE: io/src/links_panel.rs
  type LinksPanel (line 7) | pub(crate) struct LinksPanel {
    method enable (line 17) | pub fn enable(&mut self, state: &mut State) {
  method default (line 23) | fn default() -> Self {
  method update (line 48) | fn update(
  method allow_alphanumeric_input (line 98) | fn allow_alphanumeric_input(&self, _: &State, _: &Conn) -> bool {
  method allow_play_music (line 102) | fn allow_play_music(&self) -> bool {
  method on_disable_abc123 (line 106) | fn on_disable_abc123(&mut self, _: &mut State, _: &mut Conn) {}
  method update_abc123 (line 108) | fn update_abc123(

FILE: io/src/music_panel.rs
  type MusicPanel (line 8) | pub(crate) struct MusicPanel {
    method set_gain (line 14) | fn set_gain(conn: &mut Conn, up: bool) -> Option<Snapshot> {
  method update (line 30) | fn update(
  method update_abc123 (line 145) | fn update_abc123(
  method on_disable_abc123 (line 165) | fn on_disable_abc123(&mut self, state: &mut State, conn: &mut Conn) {
  method allow_alphanumeric_input (line 179) | fn allow_alphanumeric_input(&self, state: &State, _: &Conn) -> bool {
  method allow_play_music (line 187) | fn allow_play_music(&self) -> bool {

FILE: io/src/open_file_panel.rs
  type OpenFilePanel (line 12) | pub struct OpenFilePanel {
    method enable (line 21) | fn enable(
    method soundfont (line 38) | pub fn soundfont(&mut self, state: &mut State, paths_state: &mut Paths...
    method read_save (line 49) | pub fn read_save(&mut self, state: &mut State, paths_state: &mut Paths...
    method write_save (line 54) | pub fn write_save(&mut self, state: &mut State, paths_state: &mut Path...
    method export (line 59) | pub fn export(&mut self, state: &mut State, paths_state: &mut PathsSta...
    method import_midi (line 69) | pub fn import_midi(&mut self, state: &mut State, paths_state: &mut Pat...
    method get_extension (line 76) | fn get_extension(&self, paths_state: &PathsState, exporter: &Exporter)...
    method enable_as_save (line 85) | fn enable_as_save(
    method disable (line 98) | pub fn disable(&self, state: &mut State) {
  method update (line 104) | fn update(
  method on_disable_abc123 (line 371) | fn on_disable_abc123(&mut self, _: &mut State, _: &mut Conn) {}
  method update_abc123 (line 373) | fn update_abc123(
  method allow_alphanumeric_input (line 383) | fn allow_alphanumeric_input(&self, _: &State, _: &Conn) -> bool {
  method allow_play_music (line 387) | fn allow_play_music(&self) -> bool {

FILE: io/src/panel.rs
  type Panel (line 10) | pub(crate) trait Panel {
    method update (line 21) | fn update(
    method update_abc123 (line 38) | fn update_abc123(
    method on_disable_abc123 (line 49) | fn on_disable_abc123(&mut self, state: &mut State, conn: &mut Conn);
    method allow_alphanumeric_input (line 55) | fn allow_alphanumeric_input(&self, state: &State, conn: &Conn) -> bool;
    method allow_play_music (line 58) | fn allow_play_music(&self) -> bool;

FILE: io/src/piano_roll/edit.rs
  type Edit (line 10) | pub(super) struct Edit {
    method new (line 17) | pub fn new(config: &Ini) -> Self {
  method update (line 26) | fn update(
  method on_disable_abc123 (line 140) | fn on_disable_abc123(&mut self, _: &mut State, _: &mut Conn) {}
  method update_abc123 (line 142) | fn update_abc123(
  method allow_alphanumeric_input (line 151) | fn allow_alphanumeric_input(&self, _: &State, _: &Conn) -> bool {
  method allow_play_music (line 155) | fn allow_play_music(&self) -> bool {
  method get_status_tts (line 161) | fn get_status_tts(&mut self, state: &State, text: &Text) -> Vec<TtsStrin...
  method get_input_tts (line 165) | fn get_input_tts(&mut self, state: &State, input: &Input, text: &Text) -...

FILE: io/src/piano_roll/edit_mode_deltas.rs
  type EditModeDeltas (line 6) | pub(super) struct EditModeDeltas {
    method new (line 26) | pub(super) fn new(config: &Ini) -> Self {
    method get_dt (line 50) | pub(super) fn get_dt(&self, mode: &EditMode, input: &InputState) -> u64 {
    method get_dn (line 59) | pub(super) fn get_dn(&self, mode: &EditMode) -> u8 {
    method get_dv (line 68) | pub(super) fn get_dv(&self, mode: &EditMode) -> u8 {
  function edit_mode_deltas (line 83) | fn edit_mode_deltas() {

FILE: io/src/piano_roll/piano_roll_panel.rs
  constant TRACK_SCROLL_EVENTS (line 8) | const TRACK_SCROLL_EVENTS: [InputEvent; 2] = [
  type PianoRollPanel (line 15) | pub struct PianoRollPanel {
    method new (line 35) | pub fn new(beat: &u64, config: &Ini) -> Self {
    method set_input_beat (line 68) | fn set_input_beat(&mut self, up: bool, state: &mut State) -> Option<Sn...
    method set_mode (line 78) | fn set_mode(mode: PianoRollMode, state: &mut State) -> Option<Snapshot> {
    method tts_no_track (line 85) | fn tts_no_track(text: &Text) -> Vec<TtsString> {
    method get_sub_panel (line 92) | fn get_sub_panel<'a>(&'a mut self, state: &State) -> &'a mut dyn Piano...
    method copy_notes (line 102) | fn copy_notes(&mut self, state: &State) {
    method delete_notes (line 109) | fn delete_notes(state: &mut State) -> Option<Snapshot> {
  method update (line 136) | fn update(
  method on_disable_abc123 (line 496) | fn on_disable_abc123(&mut self, _: &mut State, _: &mut Conn) {}
  method update_abc123 (line 498) | fn update_abc123(
  method allow_alphanumeric_input (line 507) | fn allow_alphanumeric_input(&self, _: &State, _: &Conn) -> bool {
  method allow_play_music (line 511) | fn allow_play_music(&self) -> bool {

FILE: io/src/piano_roll/piano_roll_sub_panel.rs
  type PianoRollSubPanel (line 5) | pub(crate) trait PianoRollSubPanel {
    method get_status_tts (line 7) | fn get_status_tts(&mut self, state: &State, text: &Text) -> Vec<TtsStr...
    method get_input_tts (line 10) | fn get_input_tts(&mut self, state: &State, input: &Input, text: &Text)...
  function get_edit_mode_status_tts (line 14) | pub(crate) fn get_edit_mode_status_tts(mode: &EditMode, text: &Text) -> ...
  function get_cycle_edit_mode_input_tts (line 22) | pub(crate) fn get_cycle_edit_mode_input_tts(
  function get_no_selection_status_tts (line 40) | pub(crate) fn get_no_selection_status_tts(text: &Text) -> TtsString {

FILE: io/src/piano_roll/select.rs
  type Select (line 8) | pub(super) struct Select {
    method get_note_index_closest_to_before_cursor (line 14) | fn get_note_index_closest_to_before_cursor(notes: &[Note], time: &Time...
    method get_note_index_closest_to_after_cursor (line 24) | fn get_note_index_closest_to_after_cursor(notes: &[Note], time: &Time)...
    method get_first_selected_note (line 34) | fn get_first_selected_note<'a>(
    method get_last_selected_note (line 47) | fn get_last_selected_note<'a>(
  method update (line 61) | fn update(
  method on_disable_abc123 (line 318) | fn on_disable_abc123(&mut self, _: &mut State, _: &mut Conn) {}
  method update_abc123 (line 320) | fn update_abc123(
  method allow_alphanumeric_input (line 329) | fn allow_alphanumeric_input(&self, _: &State, _: &Conn) -> bool {
  method allow_play_music (line 333) | fn allow_play_music(&self) -> bool {
  method get_status_tts (line 339) | fn get_status_tts(&mut self, state: &State, text: &Text) -> Vec<TtsStrin...
  method get_input_tts (line 375) | fn get_input_tts(&mut self, state: &State, input: &Input, text: &Text) -...

FILE: io/src/piano_roll/time.rs
  type Time (line 6) | pub(super) struct Time {
    method new (line 13) | pub fn new(config: &Ini) -> Self {
    method get_end (line 21) | fn get_end(state: &State) -> u64 {
    method set_cursor (line 32) | fn set_cursor(&self, state: &mut State, add: bool) -> Option<Snapshot> {
    method set_playback (line 45) | fn set_playback(&self, state: &mut State, add: bool) -> Option<Snapsho...
    method get_nearest_beat (line 58) | fn get_nearest_beat(t: u64, state: &State) -> u64 {
  method update (line 64) | fn update(
  method on_disable_abc123 (line 144) | fn on_disable_abc123(&mut self, _: &mut State, _: &mut Conn) {}
  method update_abc123 (line 146) | fn update_abc123(
  method allow_alphanumeric_input (line 155) | fn allow_alphanumeric_input(&self, _: &State, _: &Conn) -> bool {
  method allow_play_music (line 159) | fn allow_play_music(&self) -> bool {
  method get_status_tts (line 165) | fn get_status_tts(&mut self, state: &State, text: &Text) -> Vec<TtsStrin...
  method get_input_tts (line 177) | fn get_input_tts(&mut self, _: &State, input: &Input, text: &Text) -> Ve...

FILE: io/src/piano_roll/view.rs
  type View (line 10) | pub(super) struct View {
    method new (line 19) | pub fn new(config: &Ini) -> Self {
    method set_top_note_by (line 30) | fn set_top_note_by(&self, state: &mut State, add: bool) -> Option<Snap...
    method set_start_time_by (line 39) | fn set_start_time_by(&self, state: &mut State, add: bool) -> Option<Sn...
    method zoom (line 49) | fn zoom(&self, state: &mut State, zoom_in: bool) -> Option<Snapshot> {
  method update (line 57) | fn update(
  method on_disable_abc123 (line 140) | fn on_disable_abc123(&mut self, _: &mut State, _: &mut Conn) {}
  method update_abc123 (line 142) | fn update_abc123(
  method allow_alphanumeric_input (line 151) | fn allow_alphanumeric_input(&self, _: &State, _: &Conn) -> bool {
  method allow_play_music (line 155) | fn allow_play_music(&self) -> bool {
  method get_status_tts (line 161) | fn get_status_tts(&mut self, state: &State, text: &Text) -> Vec<TtsStrin...
  method get_input_tts (line 175) | fn get_input_tts(&mut self, state: &State, input: &Input, text: &Text) -...

FILE: io/src/popup.rs
  type Popup (line 5) | pub(crate) struct Popup {
    method enable (line 14) | pub fn enable(&mut self, state: &mut State, panels: Vec<PanelType>) {
    method disable (line 22) | pub fn disable(&self, state: &mut State) {

FILE: io/src/quit_panel.rs
  type QuitPanel (line 6) | pub(crate) struct QuitPanel {
    method enable (line 12) | pub fn enable(&mut self, state: &mut State) {
  method update (line 18) | fn update(
  method allow_alphanumeric_input (line 44) | fn allow_alphanumeric_input(&self, _: &State, _: &Conn) -> bool {
  method allow_play_music (line 48) | fn allow_play_music(&self) -> bool {
  method on_disable_abc123 (line 52) | fn on_disable_abc123(&mut self, _: &mut State, _: &mut Conn) {}
  method update_abc123 (line 54) | fn update_abc123(

FILE: io/src/save.rs
  constant READ_ERROR (line 11) | const READ_ERROR: &str = "Error reading file: ";
  constant WRITE_ERROR (line 12) | const WRITE_ERROR: &str = "Error writing file: ";
  type Save (line 16) | pub(crate) struct Save {
    method write (line 37) | pub fn write(path: &PathBuf, state: &State, conn: &Conn, paths_state: ...
    method read (line 73) | pub fn read(path: &Path, state: &mut State, conn: &mut Conn, paths_sta...
    method fix_no_flac (line 141) | fn fix_no_flac(string: &str) -> String {
  function default_version (line 148) | fn default_version() -> String {

FILE: io/src/snapshot.rs
  type Snapshot (line 6) | pub(crate) struct Snapshot {
    method from_state_value (line 25) | pub fn from_state_value<F, T>(mut f: F, value: T, state: &mut State) -...
    method from_state (line 38) | pub fn from_state<F>(mut f: F, state: &mut State) -> Self
    method from_states (line 51) | pub fn from_states(from_state: State, to_state: &mut State) -> Self {
    method from_commands (line 64) | pub fn from_commands(
    method from_states_and_commands (line 85) | pub fn from_states_and_commands(
    method from_io_commands (line 106) | pub fn from_io_commands(io_commands: Vec<IOCommand>) -> Self {
    method from_snapshot (line 116) | pub fn from_snapshot(snapshot: &Snapshot) -> Self {

FILE: io/src/tracks_panel.rs
  constant TRACK_SCROLL_EVENTS (line 8) | const TRACK_SCROLL_EVENTS: [InputEvent; 2] = [InputEvent::PreviousTrack,...
  type TracksPanel (line 10) | pub(crate) struct TracksPanel {
    method set_preset (line 17) | fn set_preset(channel: u8, conn: &mut Conn, up: bool) -> Option<Snapsh...
    method set_bank (line 39) | fn set_bank(channel: u8, conn: &mut Conn, up: bool) -> Option<Snapshot> {
    method set_gain (line 67) | fn set_gain(state: &mut State, up: bool) -> Option<Snapshot> {
  method default (line 79) | fn default() -> Self {
  method update (line 89) | fn update(
  method on_disable_abc123 (line 344) | fn on_disable_abc123(&mut self, _: &mut State, _: &mut Conn) {}
  method update_abc123 (line 346) | fn update_abc123(
  method allow_alphanumeric_input (line 355) | fn allow_alphanumeric_input(&self, _: &State, _: &Conn) -> bool {
  method allow_play_music (line 359) | fn allow_play_music(&self) -> bool {

FILE: py/build.py
  function upload_directory (line 9) | def upload_directory(ftp: FTP, folder: str = None) -> None:
  function ftp_login (line 20) | def ftp_login() -> FTP:
  function ftp_website (line 28) | def ftp_website(ftp: FTP) -> None:
  function ftp_cwd (line 42) | def ftp_cwd(ftp: FTP, folder: str) -> None:
  function get_version (line 50) | def get_version() -> str:
  function tag (line 64) | def tag(version: str) -> None:
  function create_builds (line 70) | def create_builds(version: str) -> None:

FILE: render/src/color_key.rs
  type ColorKey (line 5) | pub enum ColorKey {

FILE: render/src/drawable.rs
  type Drawable (line 8) | pub(crate) trait Drawable {
    method update (line 16) | fn update(

FILE: render/src/export_panel.rs
  type ExportPanel (line 7) | pub(crate) struct ExportPanel {
    method new (line 17) | pub fn new(config: &Ini, renderer: &Renderer, text: &Text) -> Self {
  method update (line 55) | fn update(&self, renderer: &Renderer, _: &State, conn: &Conn, _: &Text, ...

FILE: render/src/export_settings_panel.rs
  type SeparatorLines (line 12) | struct SeparatorLines {
  type ExportSettingsPanel (line 20) | pub(crate) struct ExportSettingsPanel {
    method new (line 43) | pub fn new(config: &Ini, renderer: &Renderer, exporter: &Exporter, tex...
    method update_settings (line 158) | fn update_settings<F, const N: usize>(
    method get_separator (line 338) | fn get_separator(position: [u32; 2], width: u32, renderer: &Renderer) ...
    method draw_optional_input (line 347) | fn draw_optional_input(
    method draw_boolean (line 373) | fn draw_boolean(
  method update (line 395) | fn update(&self, renderer: &Renderer, state: &State, conn: &Conn, text: ...

FILE: render/src/field_params/boolean.rs
  type Boolean (line 8) | pub(crate) struct Boolean {
    method new (line 23) | pub fn new(key: String, position: [u32; 2], text: &Text, renderer: &Re...
    method new_from_width (line 44) | pub fn new_from_width(
    method get_boolean_label (line 65) | pub fn get_boolean_label(&self, value: &bool) -> &Label {
    method get_boolean_labels (line 70) | fn get_boolean_labels(

FILE: render/src/field_params/boolean_corners.rs
  type BooleanCorners (line 6) | pub(crate) struct BooleanCorners {
    method new (line 18) | pub fn new(

FILE: render/src/field_params/key_input.rs
  type KeyInput (line 5) | pub(crate) struct KeyInput {
    method new_from_value_width (line 21) | pub fn new_from_value_width(
    method new_from_padding (line 53) | pub fn new_from_padding(

FILE: render/src/field_params/key_list.rs
  type KeyList (line 5) | pub(crate) struct KeyList {
    method new (line 15) | pub fn new(

FILE: render/src/field_params/key_list_corners.rs
  type KeyListCorners (line 5) | pub(crate) struct KeyListCorners {
    method new (line 18) | pub fn new(

FILE: render/src/field_params/key_width.rs
  type KeyWidth (line 7) | pub(crate) struct KeyWidth {
    method new (line 18) | pub fn new(key: String, position: [u32; 2], value_width: u32, renderer...
    method new_from_width (line 31) | pub fn new_from_width(
    method get_value (line 54) | pub fn get_value<'t>(&self, value: &'t str, renderer: &Renderer) -> La...
    method get_half_width (line 64) | fn get_half_width(width: u32) -> usize {
    method get_value_width (line 73) | fn get_value_width(position: [u32; 2], width: u32, value_width: u32) -...

FILE: render/src/field_params/label.rs
  type Label (line 5) | pub(crate) struct Label {
    method new (line 13) | pub fn new(position: [u32; 2], text: String, renderer: &Renderer) -> S...

FILE: render/src/field_params/label_rectangle.rs
  type LabelRectangle (line 6) | pub(crate) struct LabelRectangle {
    method new (line 14) | pub fn new(position: [u32; 2], text: String, renderer: &Renderer) -> S...

FILE: render/src/field_params/label_ref.rs
  type LabelRef (line 5) | pub(crate) struct LabelRef<'a> {
  function new (line 13) | pub fn new(position: [u32; 2], text: &'a str, renderer: &Renderer) -> Se...

FILE: render/src/field_params/line.rs
  type Line (line 4) | pub(crate) struct Line {
    method vertical (line 11) | pub(crate) fn vertical(x: f32, y0: f32, y1: f32) -> Self {
    method horizontal (line 19) | pub(crate) fn horizontal(x0: f32, x1: f32, y: f32) -> Self {
    method vertical_line_separator (line 27) | pub(crate) fn vertical_line_separator(position: [u32; 2], renderer: &R...

FILE: render/src/field_params/list.rs
  constant LEFT_ARROW (line 5) | const LEFT_ARROW: &str = "<";
  constant RIGHT_ARROW (line 6) | const RIGHT_ARROW: &str = ">";
  type List (line 9) | pub(crate) struct List {
    method new (line 20) | pub fn new(position: [u32; 2], width: u32, renderer: &Renderer) -> Self {
    method get_value (line 36) | pub fn get_value<'t>(&self, value: &'t str, renderer: &Renderer) -> La...

FILE: render/src/field_params/panel_background.rs
  type PanelBackground (line 6) | pub(crate) struct PanelBackground {
    method new (line 16) | pub fn new(position: [u32; 2], size: [u32; 2], renderer: &Renderer) ->...
    method resize_by (line 31) | pub fn resize_by(&mut self, delta: [u32; 2], renderer: &Renderer) {

FILE: render/src/field_params/rectangle.rs
  type Rectangle (line 3) | pub(crate) struct Rectangle {
    method new (line 11) | pub fn new(position: [u32; 2], size: [u32; 2]) -> Self {

FILE: render/src/field_params/rectangle_pixel.rs
  type RectanglePixel (line 5) | pub(crate) struct RectanglePixel {
    method new_from_u (line 13) | pub fn new_from_u(position: [u32; 2], size: [u32; 2], renderer: &Rende...
    method new (line 20) | pub fn new(position: [f32; 2], size: [f32; 2]) -> Self {

FILE: render/src/field_params/util.rs
  constant KV_PADDING (line 1) | pub(crate) const KV_PADDING: u32 = 2;

FILE: render/src/field_params/width.rs
  type Width (line 7) | pub(crate) struct Width {
    method new (line 17) | pub fn new(position: [u32; 2], width: usize) -> Self {
    method to_label (line 26) | pub fn to_label<'t>(&self, value: &'t str, renderer: &Renderer) -> Lab...

FILE: render/src/lib.rs
  constant TRACK_HEIGHT_SOUNDFONT (line 46) | pub(crate) const TRACK_HEIGHT_SOUNDFONT: u32 = 4;
  constant TRACK_HEIGHT_NO_SOUNDFONT (line 47) | pub(crate) const TRACK_HEIGHT_NO_SOUNDFONT: u32 = 1;
  function draw_subtitles (line 50) | pub fn draw_subtitles(renderer: &Renderer, tts: &TTS) {
  function get_track_heights (line 57) | pub(crate) fn get_track_heights(state: &State, conn: &Conn) -> Vec<u32> {

FILE: render/src/links_panel.rs
  constant NUM_LINKS (line 6) | const NUM_LINKS: usize = 3;
  constant LABEL_COLOR (line 7) | const LABEL_COLOR: ColorKey = ColorKey::Value;
  type LinksPanel (line 10) | pub(crate) struct LinksPanel {
    method new (line 20) | pub fn new(config: &Ini, renderer: &Renderer, text: &Text, input: &Inp...
    method get_label (line 87) | fn get_label(
  method update (line 107) | fn update(&self, renderer: &Renderer, _: &State, _: &Conn, _: &Text, _: ...

FILE: render/src/main_menu.rs
  constant COLOR (line 9) | const COLOR: ColorKey = ColorKey::Key;
  constant LERP_DT (line 11) | const LERP_DT: f32 = 0.2;
  constant POWER_BAR_DELTA (line 13) | const POWER_BAR_DELTA: u64 = 5;
  type Lerp (line 17) | struct Lerp {
    method set (line 24) | pub(super) fn set(&mut self, b: f32) {
    method lerp (line 29) | pub(super) fn lerp(&mut self) -> f32 {
  type MainMenu (line 46) | pub(crate) struct MainMenu {
    method new (line 66) | pub fn new(
    method label (line 204) | fn label(key: String, x: &mut u32, y: u32, renderer: &Renderer) -> Lab...
    method label_from_key (line 211) | fn label_from_key(key: &str, x: &mut u32, y: u32, renderer: &Renderer,...
    method tooltip (line 216) | fn tooltip(
    method get_power_textures (line 234) | fn get_power_textures(
    method get_color (line 267) | fn get_color(color_key: &ColorKey, renderer: &Renderer) -> Color {
    method draw_sample_power (line 273) | fn draw_sample_power(&mut self, index: usize, renderer: &Renderer) {
    method set_lerp_target (line 289) | fn set_lerp_target(&mut self, index: usize, sample: f32) {
    method late_update (line 294) | pub fn late_update(&mut self, renderer: &Renderer, conn: &Conn) {
  method update (line 309) | fn update(&self, renderer: &Renderer, state: &State, _: &Conn, _: &Text,...

FILE: render/src/music_panel.rs
  type MusicPanel (line 5) | pub(crate) struct MusicPanel {
    method new (line 23) | pub fn new(config: &Ini, renderer: &Renderer, text: &Text) -> Self {
  method update (line 69) | fn update(&self, renderer: &Renderer, state: &State, conn: &Conn, _: &Te...

FILE: render/src/open_file_panel.rs
  constant EXTENSION_WIDTH (line 7) | const EXTENSION_WIDTH: u32 = 4;
  type OpenFilePanel (line 10) | pub(crate) struct OpenFilePanel {
    method new (line 30) | pub fn new(config: &Ini, renderer: &Renderer, text: &Text) -> Self {
    method get_scroll_label (line 154) | fn get_scroll_label(
  method update (line 169) | fn update(

FILE: render/src/page.rs
  type Page (line 4) | pub(crate) struct Page {
    method new (line 12) | pub(crate) fn new(selected: &Option<usize>, elements: &[u32], height: ...

FILE: render/src/page_position.rs
  type PagePosition (line 3) | pub(crate) enum PagePosition {

FILE: render/src/panel.rs
  type Panel (line 11) | pub(crate) struct Panel {
    method new (line 21) | pub fn new(
    method update (line 50) | pub fn update(&self, focus: bool, renderer: &Renderer) {
    method update_ex (line 60) | pub fn update_ex(&self, color: &ColorKey, renderer: &Renderer) {
    method has_focus (line 68) | pub fn has_focus(&self, state: &State) -> bool {

FILE: render/src/panels.rs
  type Panels (line 14) | pub struct Panels {
    method new (line 36) | pub fn new(
    method update (line 75) | pub fn update(
    method late_update (line 105) | pub fn late_update(&mut self, state: &State, conn: &Conn, renderer: &m...

FILE: render/src/piano_roll_panel.rs
  constant TIME_PADDING (line 18) | const TIME_PADDING: u32 = 3;
  type PianoRollPanel (line 21) | pub struct PianoRollPanel {
    method new (line 49) | pub fn new(config: &Ini, renderer: &Renderer, state: &State, text: &Te...
    method late_update (line 114) | pub fn late_update(&mut self, state: &State, renderer: &Renderer) {
    method draw_time_lines (line 119) | fn draw_time_lines(
    method get_view_dt (line 172) | fn get_view_dt(state: &State, conn: &Conn) -> [u64; 2] {
    method get_play_state (line 195) | fn get_play_state(play_state: &SharedPlayState) -> PlayState {
  method update (line 201) | fn update(&self, renderer: &Renderer, state: &State, conn: &Conn, text: ...

FILE: render/src/piano_roll_panel/multi_track.rs
  constant TRACK_COLORS_FOCUS (line 8) | const TRACK_COLORS_FOCUS: [ColorKey; 6] = [
  constant TRACK_COLORS_NO_FOCUS (line 17) | const TRACK_COLORS_NO_FOCUS: [ColorKey; 6] = [
  constant DN_F (line 26) | const DN_F: f32 = (MAX_NOTE - MIN_NOTE) as f32;
  constant DN (line 28) | const DN: [u8; 2] = [MAX_NOTE, MIN_NOTE];
  type MultiTrack (line 31) | pub(crate) struct MultiTrack {
    method new (line 43) | pub fn new(config: &Ini, renderer: &Renderer) -> Self {
    method update (line 77) | pub(crate) fn update(

FILE: render/src/piano_roll_panel/piano_roll_rows.rs
  constant BACKGROUND_COLOR (line 8) | const BACKGROUND_COLOR: ColorKey = ColorKey::Background;
  type PianoRollRows (line 10) | pub(crate) struct PianoRollRows {
    method new (line 32) | pub fn new(rect: Rectangle, state: &State, renderer: &Renderer) -> Self {
    method update (line 72) | pub fn update(&self, renderer: &Renderer) {
    method late_update (line 82) | pub fn late_update(&mut self, state: &State, renderer: &Renderer) {
    method set_row_texture (line 104) | fn set_row_texture(

FILE: render/src/piano_roll_panel/top_bar.rs
  constant PADDING (line 7) | const PADDING: u32 = 4;
  type ModesMap (line 8) | type ModesMap = HashMap<PianoRollMode, (Label, Rectangle)>;
  type TopBar (line 11) | pub(super) struct TopBar {
    method new (line 33) | pub fn new(config: &Ini, renderer: &Renderer, text: &Text) -> Self {
    method update (line 154) | pub fn update(&self, state: &State, renderer: &Renderer, text: &Text, ...
    method get_edit_mode_text (line 216) | fn get_edit_mode_text<'t>(edit_mode: &IndexedEditModes, text: &'t Text...
    method insert_mode (line 226) | fn insert_mode(

FILE: render/src/piano_roll_panel/viewable_notes.rs
  type ViewableNote (line 6) | pub(crate) struct ViewableNote<'a> {
  type ViewableNotes (line 24) | pub(crate) struct ViewableNotes<'a> {
  function new (line 40) | pub fn new(
  function new_from_track (line 68) | pub fn new_from_track(
  function get_note_w (line 142) | pub fn get_note_w(&self, note: &ViewableNote) -> f32 {
  function get_note_x (line 160) | pub fn get_note_x(t: u64, ppp: u64, x: f32, dt: &[U64orF32; 2]) -> f32 {
  function get_pulses_per_pixel (line 172) | pub fn get_pulses_per_pixel(dt: &[U64orF32; 2], w: f32) -> u64 {

FILE: render/src/piano_roll_panel/volume.rs
  constant MAX_VOLUME_F (line 5) | const MAX_VOLUME_F: f32 = MAX_VOLUME as f32;
  type Volume (line 8) | pub(crate) struct Volume {
    method new (line 20) | pub fn new(config: &Ini, text: &Text, renderer: &Renderer) -> Self {
    method update (line 59) | pub fn update(&self, notes: &ViewableNotes, renderer: &Renderer, state...
    method render_note (line 85) | fn render_note(&self, note: &ViewableNote, renderer: &Renderer) {

FILE: render/src/popup.rs
  type Popup (line 5) | pub(crate) struct Popup {
    method new (line 13) | pub(crate) fn new(panel_type: PanelType) -> Self {
    method update (line 21) | pub(crate) fn update(&self, renderer: &Renderer) {
    method late_update (line 29) | pub(crate) fn late_update(&mut self, state: &State, renderer: &mut Ren...

FILE: render/src/quit_panel.rs
  constant LABEL_PADDING (line 5) | const LABEL_PADDING: u32 = 8;
  constant LABEL_COLOR (line 6) | const LABEL_COLOR: ColorKey = ColorKey::Value;
  type QuitPanel (line 9) | pub(crate) struct QuitPanel {
    method new (line 19) | pub fn new(config: &Ini, renderer: &Renderer, text: &Text, input: &Inp...
  method update (line 53) | fn update(&self, renderer: &Renderer, _: &State, _: &Conn, _: &Text, _: ...

FILE: render/src/renderer.rs
  constant TEXTURE_COLOR (line 10) | const TEXTURE_COLOR: Color = macroquad::color::colors::WHITE;
  type Renderer (line 13) | pub struct Renderer {
    method new (line 47) | pub fn new(config: &Ini) -> Self {
    method rectangle (line 119) | pub(crate) fn rectangle(&self, rect: &Rectangle, color: &ColorKey) {
    method rectangle_pixel (line 130) | pub(crate) fn rectangle_pixel(&self, rect: &RectanglePixel, color: &Co...
    method rectangle_note (line 146) | pub(crate) fn rectangle_note(&self, position: [f32; 2], size: [f32; 2]...
    method border (line 160) | pub(crate) fn border(&self, rect: &Rectangle, color: &ColorKey) {
    method rectangle_lines (line 168) | pub(crate) fn rectangle_lines(&self, rect: &RectanglePixel, color: &Co...
    method text (line 183) | pub(crate) fn text(&self, label: &Label, text_color: &ColorKey) {
    method text_ref (line 197) | pub(crate) fn text_ref(&self, label: &LabelRef, text_color: &ColorKey) {
    method corners (line 211) | pub(crate) fn corners(&self, rect: &RectanglePixel, focus: bool) {
    method texture_pixel (line 299) | pub(crate) fn texture_pixel(
    method background (line 318) | pub(crate) fn background(&self) {
    method vertical_line_pixel (line 332) | pub(crate) fn vertical_line_pixel(&self, x: f32, top: f32, bottom: f32...
    method vertical_line (line 340) | pub(crate) fn vertical_line(&self, line: &Line, color: &ColorKey) {
    method horizontal_line (line 355) | pub(crate) fn horizontal_line(&self, line: &Line, color: &ColorKey) {
    method horizontal_line_grid (line 374) | pub(crate) fn horizontal_line_grid(
    method horizontal_line_pixel (line 395) | pub(crate) fn horizontal_line_pixel(&self, left: f32, right: f32, y: f...
    method subtitle (line 402) | pub(crate) fn subtitle(&self, text: &str) {
    method screen_capture (line 456) | pub(crate) fn screen_capture(&mut self) {
    method key_list (line 482) | pub(crate) fn key_list(&self, text: &str, key_list: &KeyList, focus: F...
    method key_list_corners (line 494) | pub(crate) fn key_list_corners(&self, text: &str, key_list: &KeyListCo...
    method list (line 510) | pub(crate) fn list(&self, text: &str, list: &List, focus: Focus) {
    method key_value (line 532) | pub(crate) fn key_value(&self, text: &str, kv: &KeyWidth, colors: [&Co...
    method key_input (line 543) | pub(crate) fn key_input(
    method boolean (line 579) | pub(crate) fn boolean(&self, value: bool, boolean: &Boolean, focus: bo...
    method boolean_corners (line 592) | pub(crate) fn boolean_corners(&self, value: bool, boolean: &BooleanCor...
    method get_value_color (line 603) | pub(crate) fn get_value_color(focus: Focus) -> ColorKey {
    method get_key_color (line 615) | pub(crate) fn get_key_color(focus: bool) -> ColorKey {
    method get_boolean_color (line 624) | pub(crate) fn get_boolean_color(focus: bool, value: bool) -> ColorKey {
    method get_color (line 635) | pub(crate) fn get_color(&self, color_key: &ColorKey) -> Color {
    method grid_to_pixel (line 642) | pub(crate) fn grid_to_pixel(&self, point: [u32; 2]) -> [f32; 2] {
    method get_border_rect (line 650) | pub(crate) fn get_border_rect(&self, position: [u32; 2], size: [u32; 2...
    method get_label_position (line 660) | pub(crate) fn get_label_position(&self, position: [u32; 2], text: &str...
    method get_text_position (line 671) | fn get_text_position(&self, position: [u32; 2], text: &str, font: &Fon...
    method parse_color (line 680) | fn parse_color(value: &str) -> Color {
    method text_sub (line 691) | fn text_sub(&self, label: &Label) {
    method text_ex (line 708) | fn text_ex(

FILE: render/src/tracks_panel.rs
  constant MUTE_OFFSET (line 5) | const MUTE_OFFSET: u32 = 6;
  type TracksPanel (line 8) | pub struct TracksPanel {
    method new (line 30) | pub fn new(config: &Ini, renderer: &Renderer, text: &Text) -> Self {
  method update (line 71) | fn update(&self, renderer: &Renderer, state: &State, conn: &Conn, text: ...

FILE: render/src/types.rs
  type Focus (line 1) | pub(crate) type Focus = [bool; 2];

FILE: src/main.rs
  constant CLEAR_COLOR (line 18) | const CLEAR_COLOR: macroquad::color::Color = macroquad::color::BLACK;
  function main (line 21) | async fn main() {
  function window_conf (line 155) | fn window_conf() -> Conf {
  function get_remote_version (line 184) | fn get_remote_version(config: &Ini) -> Option<String> {

FILE: text/src/lib.rs
  constant LANGUAGES (line 25) | const LANGUAGES: [&str; 1] = ["en"];
  constant KEYCODE_LOOKUPS (line 27) | const KEYCODE_LOOKUPS: [&str; 121] = [
  type Text (line 152) | pub struct Text {
    method new (line 170) | pub fn new(config: &Ini, paths: &Paths) -> Self {
    method get (line 213) | pub fn get(&self, key: &str) -> String {
    method get_ref (line 221) | pub fn get_ref(&self, key: &str) -> &str {
    method get_with_values (line 229) | pub fn get_with_values(&self, key: &str, values: &[&str]) -> String {
    method get_keycode (line 249) | pub fn get_keycode(&self, key: &KeyCode, spoken: bool) -> &str {
    method get_piano_roll_mode (line 263) | pub fn get_piano_roll_mode(&self, mode: &PianoRollMode) -> &str {
    method get_edit_mode (line 271) | pub fn get_edit_mode(&self, mode: &EditMode) -> &str {
    method get_boolean (line 279) | pub fn get_boolean(&self, value: &bool) -> &str {
    method get_max_boolean_length (line 284) | pub fn get_max_boolean_length(&self) -> u32 {
    method get_time (line 289) | pub fn get_time(&self, ppq: u64, time: &Time) -> String {
    method get_ppq_tts (line 314) | pub fn get_ppq_tts(&self, ppq: &u64) -> String {
    method get_error (line 334) | pub fn get_error(&self, error: &str) -> String {
    method get_note_name (line 339) | pub fn get_note_name(&self, note: u8) -> &str {
    method get_keycode_map (line 344) | fn get_keycode_map(text: &HashMap<String, String>, spoken: bool) -> Ha...
    method get_edit_mode_map (line 356) | fn get_edit_mode_map(text: &HashMap<String, String>) -> HashMap<EditMo...
    method get_piano_roll_mode_map (line 365) | fn get_piano_roll_mode_map(text: &HashMap<String, String>) -> HashMap<...
  function ppq_to_string (line 379) | pub fn ppq_to_string(ppq: u64) -> String {
  function truncate (line 403) | pub fn truncate(string: &str, length: usize, left: bool) -> &str {
  function get_file_name (line 419) | pub fn get_file_name(path: &Path) -> &str {
  function get_file_name_no_ex (line 430) | pub fn get_file_name_no_ex(path: &Path) -> &str {
  function file_name (line 446) | fn file_name() {
  function truncate_test (line 454) | fn truncate_test() {
  function ppq_fraction (line 466) | fn ppq_fraction() {

FILE: text/src/tooltips.rs
  constant NUM_REGEXES (line 7) | const NUM_REGEXES: usize = 16;
  type Regexes (line 9) | type Regexes = [Regex; NUM_REGEXES];
  type Tooltips (line 13) | pub struct Tooltips {
    method get_tooltip (line 44) | pub fn get_tooltip(
    method get_tooltip_with_values (line 81) | pub fn get_tooltip_with_values(
    method get_regexes (line 114) | fn get_regexes(prefix: &str) -> Regexes {
    method event_to_text (line 122) | fn event_to_text(
    method get_mods (line 185) | fn get_mods<'a>(qwerty: &QwertyBinding, spoken: bool, text: &'a Text) ...
    method get_keys (line 196) | fn get_keys<'a>(qwerty: &QwertyBinding, spoken: bool, text: &'a Text) ...
  method default (line 23) | fn default() -> Self {

FILE: text/src/tts.rs
  type TTS (line 7) | pub struct TTS {
    method new (line 19) | pub fn new(config: &Ini) -> Self {
    method stop (line 101) | pub fn stop(&mut self) {
    method update (line 112) | pub fn update(&mut self) {
    method get_subtitles (line 125) | pub fn get_subtitles(&self) -> Option<&str> {
    method say (line 134) | fn say(&mut self, text: &str) {
    method enqueue (line 144) | fn enqueue(&mut self, text: TtsString) {
    method enqueue (line 155) | fn enqueue(&mut self, text: &TtsString) {
    method enqueue (line 166) | fn enqueue(&mut self, text: String) {
    method enqueue (line 172) | fn enqueue(&mut self, text: &str) {
    method enqueue (line 178) | fn enqueue(&mut self, text: Vec<TtsString>) {
    method enqueue (line 191) | fn enqueue(&mut self, text: Vec<&TtsString>) {
  type Enqueable (line 205) | pub trait Enqueable<T> {
    method enqueue (line 207) | fn enqueue(&mut self, text: T);
  function test_tts (line 218) | fn test_tts() {

FILE: text/src/tts_string.rs
  type TtsString (line 3) | pub struct TtsString {
    method from (line 11) | fn from(value: String) -> Self {
    method from (line 20) | fn from(value: &str) -> Self {

FILE: text/src/value_map.rs
  type ValueMap (line 6) | pub struct ValueMap<T>
  function new (line 23) | pub fn new<const N: usize>(keys: [T; N], values: [&str; N], text: &Text)...
  function new_from_strings (line 34) | pub fn new_from_strings<const N: usize>(keys: [T; N], values: [String; N...
  function get (line 49) | pub fn get(&self, key: &T) -> &str {
Condensed preview — 162 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (705K chars).
[
  {
    "path": ".github/workflows/build.yaml",
    "chars": 4118,
    "preview": "name: build\n\non:\n  workflow_dispatch:\n    inputs:\n      version:\n        description: Build version\n        required: Tr"
  },
  {
    "path": ".github/workflows/test.yaml",
    "chars": 2503,
    "preview": "name: test\n\non:\n  push:\n    branches-ignore:\n      - main\n    paths-ignore:\n      - \"doc/**\"\n      - \".vscode/**\"\n      "
  },
  {
    "path": ".gitignore",
    "chars": 123,
    "preview": ".idea/\n*.dll\n*.pyc\ntemp/\nscratch.py\nsynthesizers/\n*/target/\ntarget/\nms_basic.sf3\nevents.txt\n.DS_STORE\npy/credentials/\nve"
  },
  {
    "path": ".vscode/settings.json",
    "chars": 226,
    "preview": "{\n    \"rust-analyzer.linkedProjects\": [\n        \"./audio/Cargo.toml\",\n        \"./common/Cargo.toml\",\n        \"./input/Ca"
  },
  {
    "path": "Cargo.toml",
    "chars": 3459,
    "preview": "[workspace]\nmembers = [\"audio\", \"common\", \"input\", \"io\", \"render\", \"text\"]\n\n[workspace.package]\nversion = \"0.2.7\"\nauthor"
  },
  {
    "path": "LICENSE",
    "chars": 1069,
    "preview": "MIT License\n\nCopyright (c) 2023 Esther Alter\n\nPermission is hereby granted, free of charge, to any person obtaining a co"
  },
  {
    "path": "README.md",
    "chars": 3841,
    "preview": "![Cacophony!](doc/images/banner.png)\n\n**Cacophony is a minimalist and ergonomic MIDI sequencer.** It's minimalist in tha"
  },
  {
    "path": "audio/Cargo.toml",
    "chars": 667,
    "preview": "[package]\nname = \"audio\"\nversion.workspace = true\nauthors.workspace = true\ndescription.workspace = true\ndocumentation.wo"
  },
  {
    "path": "audio/src/command.rs",
    "chars": 471,
    "preview": "use std::path::PathBuf;\n\n/// A command for the synthesizer.\n#[derive(Debug, Eq, PartialEq, Clone)]\npub enum Command {\n  "
  },
  {
    "path": "audio/src/conn.rs",
    "chars": 21064,
    "preview": "use crate::decayer::Decayer;\nuse crate::export::{ExportState, ExportType, Exportable, MultiFileSuffix};\nuse crate::expor"
  },
  {
    "path": "audio/src/decayer.rs",
    "chars": 1732,
    "preview": "use crate::SharedSynth;\nuse oxisynth::Synth;\n\n/// Export this many bytes per decay chunk.\nconst DECAY_CHUNK_SIZE: usize "
  },
  {
    "path": "audio/src/export/export_setting.rs",
    "chars": 384,
    "preview": "use serde::{Deserialize, Serialize};\n\n/// Enum values for export settings.\n#[derive(Debug, Eq, PartialEq, Copy, Clone, D"
  },
  {
    "path": "audio/src/export/export_state.rs",
    "chars": 421,
    "preview": "#[derive(Copy, Clone, Eq, PartialEq, Debug)]\npub enum ExportState {\n    NotExporting,\n    /// Writing samples to a wav b"
  },
  {
    "path": "audio/src/export/export_type.rs",
    "chars": 639,
    "preview": "use common::open_file::Extension;\nuse serde::{Deserialize, Serialize};\n\n/// This determines what we're exporting to.\n#[d"
  },
  {
    "path": "audio/src/export/exportable.rs",
    "chars": 171,
    "preview": "use crate::midi_event_queue::MidiEventQueue;\n\npub(crate) struct Exportable {\n    pub events: MidiEventQueue,\n    pub tot"
  },
  {
    "path": "audio/src/export/metadata.rs",
    "chars": 846,
    "preview": "use serde::{Deserialize, Serialize};\n\n/// The default title of the music.\npub const DEFAULT_TITLE: &str = \"My Music\";\n\n/"
  },
  {
    "path": "audio/src/export/multi_file_suffix.rs",
    "chars": 357,
    "preview": "use serde::{Deserialize, Serialize};\n\n/// How should we name files of separate tracks?\n#[derive(Debug, Copy, Clone, Defa"
  },
  {
    "path": "audio/src/export.rs",
    "chars": 327,
    "preview": "mod export_setting;\nmod export_state;\nmod export_type;\nmod exportable;\nmod metadata;\nmod multi_file_suffix;\n\npub use exp"
  },
  {
    "path": "audio/src/exporter.rs",
    "chars": 20969,
    "preview": "use crate::export::{ExportSetting, ExportType, Metadata, MultiFileSuffix};\nuse crate::{AudioBuffer, SynthState};\nuse chr"
  },
  {
    "path": "audio/src/lib.rs",
    "chars": 1062,
    "preview": "//! This crate handles all audio output in Cacophony:\n//!\n//! - `Player` handles the cpal audio output stream.\n//! - `Co"
  },
  {
    "path": "audio/src/midi_event_queue.rs",
    "chars": 1285,
    "preview": "use super::timed_midi_event::TimedMidiEvent;\nuse oxisynth::MidiEvent;\n\n/// A queue of timed MIDI events.\n#[derive(Defaul"
  },
  {
    "path": "audio/src/play_state.rs",
    "chars": 284,
    "preview": "#[derive(Copy, Clone, Eq, PartialEq, Debug)]\npub enum PlayState {\n    /// Not playing any audio.\n    NotPlaying,\n    ///"
  },
  {
    "path": "audio/src/player.rs",
    "chars": 8997,
    "preview": "use crate::decayer::Decayer;\nuse crate::play_state::PlayState;\nuse crate::types::SharedSample;\nuse crate::{SharedMidiEve"
  },
  {
    "path": "audio/src/program.rs",
    "chars": 1065,
    "preview": "use serde::{Deserialize, Serialize};\nuse std::path::PathBuf;\n\n/// A channel's program.\n#[derive(Serialize, Deserialize)]"
  },
  {
    "path": "audio/src/synth_state.rs",
    "chars": 648,
    "preview": "use crate::Program;\nuse common::MAX_VOLUME;\nuse hashbrown::HashMap;\nuse serde::{Deserialize, Serialize};\n\n/// The state "
  },
  {
    "path": "audio/src/timed_midi_event.rs",
    "chars": 2315,
    "preview": "use oxisynth::MidiEvent;\nuse std::cmp::Ordering;\n\n/// A MIDI event with a start time.\n#[derive(Copy, Clone, Eq, PartialE"
  },
  {
    "path": "audio/src/types.rs",
    "chars": 709,
    "preview": "use crate::export::ExportState;\nuse crate::midi_event_queue::MidiEventQueue;\nuse crate::play_state::PlayState;\nuse crate"
  },
  {
    "path": "changelog.md",
    "chars": 5718,
    "preview": "# 0.2.x\n\n## 0.2.7\n\n- Fixed: Note-off events at the end of a track are sometimes not detected during export\n- Fixed: Pani"
  },
  {
    "path": "common/Cargo.toml",
    "chars": 434,
    "preview": "[package]\nname = \"common\"\nversion.workspace = true\nauthors.workspace = true\ndescription.workspace = true\ndocumentation.w"
  },
  {
    "path": "common/src/args.rs",
    "chars": 1005,
    "preview": "use crate::get_default_data_folder;\nuse clap::Parser;\nuse std::path::PathBuf;\n\n/// Command-line arguments.\n#[derive(Pars"
  },
  {
    "path": "common/src/config.rs",
    "chars": 4656,
    "preview": "use crate::fraction::*;\nuse crate::Paths;\nuse ini::{Ini, Properties};\nuse serde_json::from_str;\nuse std::fmt::Display;\nu"
  },
  {
    "path": "common/src/edit_mode.rs",
    "chars": 634,
    "preview": "use crate::IndexedValues;\nuse serde::{Deserialize, Serialize};\n\npub type IndexedEditModes = IndexedValues<EditMode, 3>;\n"
  },
  {
    "path": "common/src/font.rs",
    "chars": 901,
    "preview": "use crate::{get_bytes, Paths};\nuse ini::{Ini, Properties};\nuse macroquad::prelude::*;\n\n/// Returns the font data section"
  },
  {
    "path": "common/src/fraction.rs",
    "chars": 4188,
    "preview": "use crate::U64orF32;\nuse std::fmt::Display;\nuse std::ops::{Div, Mul};\n\n/// A fraction has a numerator and denominator an"
  },
  {
    "path": "common/src/index.rs",
    "chars": 3962,
    "preview": "use num_traits::int::PrimInt;\nuse num_traits::{One, Zero};\nuse serde::{Deserialize, Serialize};\nuse std::fmt::Display;\nu"
  },
  {
    "path": "common/src/indexed_values.rs",
    "chars": 2242,
    "preview": "use crate::Index;\nuse serde::de::DeserializeOwned;\nuse serde::{Deserialize, Serialize};\n\n/// An `Index` with an array of"
  },
  {
    "path": "common/src/input_state.rs",
    "chars": 1026,
    "preview": "use crate::{Index, U64orF32, MAX_VOLUME, PPQ_U};\nuse serde::{Deserialize, Serialize};\n\n/// Booleans and numerical values"
  },
  {
    "path": "common/src/lib.rs",
    "chars": 2620,
    "preview": "//! This crate contains a variety of types that are shared throughout Cacophony.\n//!\n//! There are two app-state-level s"
  },
  {
    "path": "common/src/midi_track.rs",
    "chars": 1690,
    "preview": "use crate::{Note, MAX_VOLUME};\nuse serde::{Deserialize, Serialize};\n\n/// A MIDI track has some notes.\n#[derive(Debug, De"
  },
  {
    "path": "common/src/music.rs",
    "chars": 1305,
    "preview": "use super::midi_track::MidiTrack;\nuse serde::{Deserialize, Serialize};\n\n/// Tracks, notes, and metadata.\n#[derive(Clone,"
  },
  {
    "path": "common/src/music_panel_field.rs",
    "chars": 244,
    "preview": "use serde::{Deserialize, Serialize};\n\n/// Enum values defining the music panel fields.\n#[derive(Debug, Default, Eq, Part"
  },
  {
    "path": "common/src/note.rs",
    "chars": 4250,
    "preview": "use serde::ser::SerializeSeq;\nuse serde::{Deserialize, Serialize};\nuse std::cmp::Ordering;\nuse std::fmt::{Debug, Formatt"
  },
  {
    "path": "common/src/open_file/child_paths.rs",
    "chars": 6763,
    "preview": "use super::{Extension, FileOrDirectory};\nuse serde::{Deserialize, Serialize};\nuse std::path::{Path, PathBuf};\n\n/// A col"
  },
  {
    "path": "common/src/open_file/extension.rs",
    "chars": 1663,
    "preview": "use serde::{Deserialize, Serialize};\n\n/// Enum values for file extensions used in Cacophony.\n#[derive(Debug, Eq, Partial"
  },
  {
    "path": "common/src/open_file/file_and_directory.rs",
    "chars": 1721,
    "preview": "use super::FileOrDirectory;\nuse serde::{Deserialize, Serialize};\nuse std::path::PathBuf;\n\n/// A directory and, optionall"
  },
  {
    "path": "common/src/open_file/file_or_directory.rs",
    "chars": 800,
    "preview": "use serde::{Deserialize, Serialize};\nuse std::path::{Path, PathBuf};\n\n/// Cached data for a file or directory because is"
  },
  {
    "path": "common/src/open_file/open_file_type.rs",
    "chars": 374,
    "preview": "/// This defines the files we care about and what we can do with them.\n#[derive(Debug, Eq, PartialEq, Clone, Default, Ha"
  },
  {
    "path": "common/src/open_file.rs",
    "chars": 290,
    "preview": "mod child_paths;\nmod extension;\nmod file_and_directory;\nmod file_or_directory;\nmod open_file_type;\npub use child_paths::"
  },
  {
    "path": "common/src/panel_type.rs",
    "chars": 281,
    "preview": "use serde::{Deserialize, Serialize};\n\n/// A type of panel.\n#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone, Deserialize"
  },
  {
    "path": "common/src/paths.rs",
    "chars": 4704,
    "preview": "use directories::UserDirs;\nuse std::env::current_exe;\nuse std::fs::{copy, create_dir_all};\nuse std::path::{Path, PathBuf"
  },
  {
    "path": "common/src/paths_state.rs",
    "chars": 7468,
    "preview": "use crate::open_file::*;\nuse crate::{Index, Paths};\nuse serde::{Deserialize, Serialize};\nuse std::path::PathBuf;\n\n/// Us"
  },
  {
    "path": "common/src/piano_roll_mode.rs",
    "chars": 222,
    "preview": "use serde::{Deserialize, Serialize};\n\n/// A sub-mode of the piano roll panel.\n#[derive(Debug, Eq, PartialEq, Copy, Clone"
  },
  {
    "path": "common/src/select_mode.rs",
    "chars": 2669,
    "preview": "use crate::{Music, Note};\nuse serde::{Deserialize, Serialize};\n\n/// The current mode for selecting notes.\n#[derive(Eq, P"
  },
  {
    "path": "common/src/sizes.rs",
    "chars": 4046,
    "preview": "use crate::config::parse;\nuse crate::font::*;\nuse ini::Ini;\nuse macroquad::prelude::*;\n\n/// The height of the main menu "
  },
  {
    "path": "common/src/state.rs",
    "chars": 2286,
    "preview": "use crate::music_panel_field::MusicPanelField;\nuse crate::{\n    EditMode, Index, IndexedEditModes, IndexedValues, InputS"
  },
  {
    "path": "common/src/time.rs",
    "chars": 3596,
    "preview": "use crate::edit_mode::*;\nuse crate::U64orF32;\nuse serde::{Deserialize, Serialize};\nuse std::time::Duration;\n\n/// The def"
  },
  {
    "path": "common/src/u64_or_f32.rs",
    "chars": 3132,
    "preview": "use serde::de::{Error, Visitor};\nuse serde::{Deserialize, Serialize};\nuse std::fmt::{self, Display};\n\n/// A value that i"
  },
  {
    "path": "common/src/view.rs",
    "chars": 8302,
    "preview": "use crate::config::{parse, parse_fraction};\nuse crate::note::MIDDLE_C;\nuse crate::sizes::*;\nuse crate::{EditMode, Index,"
  },
  {
    "path": "data/attribution.txt",
    "chars": 96,
    "preview": "Default SoundFont: Caed's Small Trash GM by Caed. https://musical-artifacts.com/artifacts?q=Caed"
  },
  {
    "path": "data/config.ini",
    "chars": 14588,
    "preview": "[FONTS]\n# The path to the main font.\nfont = NotoSansMono-Regular.ttf\n# The path to the subtitle font.\nsubtitle_font = No"
  },
  {
    "path": "data/text.csv",
    "chars": 18727,
    "preview": "key,en\nSpace_SPOKEN,space\nApostrophe_SPOKEN,apostrophe\nComma_SPOKEN,comma\nMinus_SPOKEN,minus\nPeriod_SPOKEN,period\nSlash_"
  },
  {
    "path": "html/index.html",
    "chars": 3276,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n    <meta charset=\"utf-8\">\n\n    <title>Cacophony</title>\n    <meta name=\"description\" cont"
  },
  {
    "path": "html/install.html",
    "chars": 3058,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n    <link rel=\"stylesheet\" href=\"style.css\">\n</head>\n<body>\n\n<div class=\"sidenav\">\n    <p>"
  },
  {
    "path": "html/limitations.html",
    "chars": 2112,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n    <link rel=\"stylesheet\" href=\"style.css\">\n</head>\n<body>\n\n<div class=\"sidenav\">\n    <p>"
  },
  {
    "path": "html/manifesto.html",
    "chars": 4379,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n    <link rel=\"stylesheet\" href=\"style.css\">\n</head>\n<body>\n\n<div class=\"sidenav\">\n    <p>"
  },
  {
    "path": "html/me.html",
    "chars": 1214,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n    <link rel=\"stylesheet\" href=\"style.css\">\n</head>\n<body>\n\n<div class=\"sidenav\">\n    <p>"
  },
  {
    "path": "html/privacy.html",
    "chars": 1538,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n    <link rel=\"stylesheet\" href=\"style.css\">\n</head>\n<body>\n\n<div class=\"sidenav\">\n    <p>"
  },
  {
    "path": "html/roadmap.html",
    "chars": 1433,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n    <link rel=\"stylesheet\" href=\"style.css\">\n</head>\n<body>\n\n<div class=\"sidenav\">\n    <p>"
  },
  {
    "path": "html/style.css",
    "chars": 3271,
    "preview": "@font-face {\n    font-family: 'NotoSansMono';\n    src:\n        url('fonts/noto/NotoSansMono-Regular.ttf');\n}\n\n@font-face"
  },
  {
    "path": "html/user_guide.html",
    "chars": 7606,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n    <link rel=\"stylesheet\" href=\"style.css\">\n</head>\n<body>\n\n<div class=\"sidenav\">\n    <p>"
  },
  {
    "path": "input/Cargo.toml",
    "chars": 584,
    "preview": "[package]\nname = \"input\"\nversion.workspace = true\nauthors.workspace = true\ndescription.workspace = true\ndocumentation.wo"
  },
  {
    "path": "input/src/debug_input_event.rs",
    "chars": 112,
    "preview": "use crate::InputEvent;\n\npub(crate) enum DebugInputEvent {\n    InputEvent(InputEvent),\n    Alphanumeric(char),\n}\n"
  },
  {
    "path": "input/src/input_event.rs",
    "chars": 2887,
    "preview": "use strum_macros::EnumString;\n\n/// Input events from either a qwerty keyboard or a MIDI controller.\n#[derive(Debug, Part"
  },
  {
    "path": "input/src/keys.rs",
    "chars": 2751,
    "preview": "use macroquad::input::KeyCode;\n\npub const KEYS: [KeyCode; 121] = [\n    KeyCode::Space,\n    KeyCode::Apostrophe,\n    KeyC"
  },
  {
    "path": "input/src/lib.rs",
    "chars": 16313,
    "preview": "//! This crate handles all user input.\n//!\n//! - `InputEvent` is an enum defining an event triggered by user input, e.g."
  },
  {
    "path": "input/src/midi_binding.rs",
    "chars": 1028,
    "preview": "use serde::Deserialize;\n\n/// Bindings for MIDI input.\n#[derive(Clone, Deserialize)]\npub struct MidiBinding {\n    /// The"
  },
  {
    "path": "input/src/midi_conn.rs",
    "chars": 4887,
    "preview": "use midir::{MidiInput, MidiInputConnection, MidiInputPort};\nuse parking_lot::Mutex;\nuse std::sync::Arc;\n\n/// The MIDI co"
  },
  {
    "path": "input/src/note_on.rs",
    "chars": 371,
    "preview": "/// A note-on event. When `off` is true, the event is done.\npub(crate) struct NoteOn {\n    /// The note MIDI information"
  },
  {
    "path": "input/src/qwerty_binding.rs",
    "chars": 9752,
    "preview": "use crate::{ALPHANUMERIC_INPUT_MODS, MODS};\nuse macroquad::input::KeyCode;\nuse serde::Deserialize;\nuse serde_json::{from"
  },
  {
    "path": "io/Cargo.toml",
    "chars": 580,
    "preview": "[package]\nname = \"io\"\nversion.workspace = true\nauthors.workspace = true\ndescription.workspace = true\ndocumentation.works"
  },
  {
    "path": "io/src/abc123.rs",
    "chars": 3628,
    "preview": "use crate::panel::*;\nuse audio::exporter::Exporter;\nuse common::U64orF32;\n\n/// A type that can be modified by user alpha"
  },
  {
    "path": "io/src/export_panel.rs",
    "chars": 1420,
    "preview": "use crate::panel::*;\nuse audio::export::ExportState;\nuse common::PanelType;\n\n/// Are we done yet?\n#[derive(Default)]\npub"
  },
  {
    "path": "io/src/export_settings_panel.rs",
    "chars": 26129,
    "preview": "use crate::abc123::{on_disable_exporter, update_exporter};\nuse crate::panel::*;\nuse audio::export::{ExportSetting, Expor"
  },
  {
    "path": "io/src/import_midi.rs",
    "chars": 4767,
    "preview": "use audio::{Command, Conn};\nuse common::{MidiTrack, Music, Note, Paths, State, U64orF32};\nuse midly::{MetaMessage, MidiM"
  },
  {
    "path": "io/src/io_command.rs",
    "chars": 372,
    "preview": "use common::open_file::OpenFileType;\n\n/// Commands for the IO struct.\n#[derive(Clone)]\npub(crate) enum IOCommand {\n    /"
  },
  {
    "path": "io/src/lib.rs",
    "chars": 21851,
    "preview": "//! This crate handles essentially all of Cacophony's functionality except the rendering (see the `render` crate).\n//!\n/"
  },
  {
    "path": "io/src/links_panel.rs",
    "chars": 3106,
    "preview": "use crate::panel::*;\nuse common::PanelType;\nuse hashbrown::HashMap;\nuse webbrowser::open;\n\n/// Open a link in a browser."
  },
  {
    "path": "io/src/music_panel.rs",
    "chars": 6651,
    "preview": "use crate::abc123::{on_disable_exporter, on_disable_state, update_exporter, update_state};\nuse crate::panel::*;\nuse comm"
  },
  {
    "path": "io/src/open_file_panel.rs",
    "chars": 15656,
    "preview": "use super::import_midi::import;\nuse crate::panel::*;\nuse crate::Save;\nuse audio::export::ExportType;\nuse audio::exporter"
  },
  {
    "path": "io/src/panel.rs",
    "chars": 1974,
    "preview": "pub(crate) use crate::io_command::IOCommand;\npub(crate) use crate::popup::Popup;\npub(crate) use crate::Snapshot;\npub(cra"
  },
  {
    "path": "io/src/piano_roll/edit.rs",
    "chars": 7831,
    "preview": "use super::{\n    get_cycle_edit_mode_input_tts, get_edit_mode_status_tts, get_no_selection_status_tts,\n    EditModeDelta"
  },
  {
    "path": "io/src/piano_roll/edit_mode_deltas.rs",
    "chars": 3427,
    "preview": "use common::config::{parse, parse_float};\nuse common::{EditMode, InputState, PPQ_F};\nuse ini::Ini;\n\n/// Delta factors an"
  },
  {
    "path": "io/src/piano_roll/piano_roll_panel.rs",
    "chars": 21108,
    "preview": "use super::*;\nuse crate::panel::*;\nuse crate::select_track;\nuse common::config::parse_fractions;\nuse common::{Index, Not"
  },
  {
    "path": "io/src/piano_roll/piano_roll_sub_panel.rs",
    "chars": 1389,
    "preview": "use crate::panel::*;\nuse common::{EditMode, IndexedEditModes};\n\n/// A sub-panel (a mode) of the piano roll panel.\npub(cr"
  },
  {
    "path": "io/src/piano_roll/select.rs",
    "chars": 20336,
    "preview": "use super::{get_no_selection_status_tts, PianoRollSubPanel};\nuse crate::panel::*;\nuse common::time::Time;\nuse common::{M"
  },
  {
    "path": "io/src/piano_roll/time.rs",
    "chars": 7552,
    "preview": "use super::{get_edit_mode_status_tts, EditModeDeltas, PianoRollSubPanel};\nuse crate::panel::*;\nuse ini::Ini;\n\n/// The pi"
  },
  {
    "path": "io/src/piano_roll/view.rs",
    "chars": 7320,
    "preview": "use super::{\n    get_cycle_edit_mode_input_tts, get_edit_mode_status_tts, EditModeDeltas, PianoRollSubPanel,\n};\nuse crat"
  },
  {
    "path": "io/src/piano_roll.rs",
    "chars": 461,
    "preview": "mod edit_mode_deltas;\nmod view;\nuse edit_mode_deltas::EditModeDeltas;\nmod edit;\nmod piano_roll_panel;\nmod piano_roll_sub"
  },
  {
    "path": "io/src/popup.rs",
    "chars": 990,
    "preview": "use common::{Index, PanelType, State};\n\n/// A popup needs to store the panels that were active before it was enabled, an"
  },
  {
    "path": "io/src/quit_panel.rs",
    "chars": 1530,
    "preview": "use crate::panel::*;\nuse common::PanelType;\n\n/// Are you sure you want to quit?\n#[derive(Default)]\npub(crate) struct Qui"
  },
  {
    "path": "io/src/save.rs",
    "chars": 6227,
    "preview": "use audio::exporter::Exporter;\nuse audio::*;\nuse common::{PathsState, State};\nuse regex::Regex;\nuse serde::{Deserialize,"
  },
  {
    "path": "io/src/snapshot.rs",
    "chars": 5099,
    "preview": "use crate::{IOCommand, IOCommands, State};\nuse audio::{CommandsMessage, Conn};\n\n/// A snapshot of a state delta.\n#[deriv"
  },
  {
    "path": "io/src/tracks_panel.rs",
    "chars": 15140,
    "preview": "use crate::panel::*;\nuse crate::select_track;\nuse common::open_file::OpenFileType;\nuse common::{MidiTrack, Paths, Select"
  },
  {
    "path": "py/build.py",
    "chars": 2355,
    "preview": "from os import chdir, getcwd\nfrom subprocess import check_output, CalledProcessError, call\nfrom pathlib import Path\nfrom"
  },
  {
    "path": "py/itch_changelog.py",
    "chars": 399,
    "preview": "from pathlib import Path\nimport re\n\nversion = re.search(r'\\[workspace.package]\\nversion = \"(.*?)\"$', Path(\"../Cargo.toml"
  },
  {
    "path": "py/macroquad_icon_creator.py",
    "chars": 377,
    "preview": "from pathlib import Path\nfrom PIL import Image\nimport numpy as np\n\nicon = bytearray()\nsource: Image.Image = Image.open(s"
  },
  {
    "path": "render/Cargo.toml",
    "chars": 595,
    "preview": "[package]\nname = \"render\"\nversion.workspace = true\nauthors.workspace = true\ndescription.workspace = true\ndocumentation.w"
  },
  {
    "path": "render/src/color_key.rs",
    "chars": 656,
    "preview": "use strum_macros::EnumString;\n\n/// Enum values for color keys.\n#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone, EnumStr"
  },
  {
    "path": "render/src/drawable.rs",
    "chars": 611,
    "preview": "pub(crate) use crate::Renderer;\npub(crate) use audio::Conn;\npub(crate) use common::{PathsState, State};\npub(crate) use i"
  },
  {
    "path": "render/src/export_panel.rs",
    "chars": 2808,
    "preview": "use crate::panel::*;\nuse crate::Popup;\nuse audio::export::ExportState;\nuse macroquad::prelude::*;\n\n/// Are we done yet?\n"
  },
  {
    "path": "render/src/export_settings_panel.rs",
    "chars": 16017,
    "preview": "use crate::panel::*;\nuse crate::Focus;\nuse audio::export::{ExportSetting, ExportType, MultiFileSuffix};\nuse audio::expor"
  },
  {
    "path": "render/src/field_params/boolean.rs",
    "chars": 2806,
    "preview": "use super::util::KV_PADDING;\nuse super::Label;\nuse crate::Renderer;\nuse hashbrown::HashMap;\nuse text::Text;\n\n/// A key-b"
  },
  {
    "path": "render/src/field_params/boolean_corners.rs",
    "chars": 1216,
    "preview": "use super::{Boolean, RectanglePixel};\nuse crate::Renderer;\nuse text::Text;\n\n/// A boolean and corners all around.\npub(cr"
  },
  {
    "path": "render/src/field_params/key_input.rs",
    "chars": 2894,
    "preview": "use super::{KeyWidth, RectanglePixel};\nuse crate::Renderer;\n\n/// A key, a value, a rectangle for corners, and a rectangl"
  },
  {
    "path": "render/src/field_params/key_list.rs",
    "chars": 900,
    "preview": "use super::{Label, List};\nuse crate::Renderer;\n\n/// A key `Label` on the left and a `List` on the right.\npub(crate) stru"
  },
  {
    "path": "render/src/field_params/key_list_corners.rs",
    "chars": 1157,
    "preview": "use super::{KeyList, RectanglePixel};\nuse crate::Renderer;\n\n/// A key `Label` on the left, a `List` on the right, and co"
  },
  {
    "path": "render/src/field_params/key_width.rs",
    "chars": 2613,
    "preview": "use super::util::KV_PADDING;\nuse super::{Label, LabelRef, Width};\nuse crate::Renderer;\nuse text::truncate;\n\n/// A key la"
  },
  {
    "path": "render/src/field_params/label.rs",
    "chars": 413,
    "preview": "use crate::Renderer;\n\n/// A position and a string.\n#[derive(Clone)]\npub(crate) struct Label {\n    /// The position in gr"
  },
  {
    "path": "render/src/field_params/label_rectangle.rs",
    "chars": 536,
    "preview": "use super::{Label, RectanglePixel};\nuse crate::Renderer;\n\n/// A label and its rectangle.\n#[derive(Clone)]\npub(crate) str"
  },
  {
    "path": "render/src/field_params/label_ref.rs",
    "chars": 432,
    "preview": "use crate::Renderer;\n\n/// A position and a string.\n#[derive(Clone)]\npub(crate) struct LabelRef<'a> {\n    /// The positio"
  },
  {
    "path": "render/src/field_params/line.rs",
    "chars": 868,
    "preview": "use crate::Renderer;\n\n/// A horizontal or vertical line.\npub(crate) struct Line {\n    pub a0: f32,\n    pub a1: f32,\n    "
  },
  {
    "path": "render/src/field_params/list.rs",
    "chars": 1291,
    "preview": "use super::{Label, LabelRef, Width};\nuse crate::Renderer;\nuse text::truncate;\n\nconst LEFT_ARROW: &str = \"<\";\nconst RIGHT"
  },
  {
    "path": "render/src/field_params/panel_background.rs",
    "chars": 1278,
    "preview": "use super::{Rectangle, RectanglePixel};\nuse crate::Renderer;\n\n/// A background rectangle and a border rectangle, both in"
  },
  {
    "path": "render/src/field_params/rectangle.rs",
    "chars": 331,
    "preview": "/// A rectangle has a position and a size.\n#[derive(Clone)]\npub(crate) struct Rectangle {\n    /// The position in grid u"
  },
  {
    "path": "render/src/field_params/rectangle_pixel.rs",
    "chars": 578,
    "preview": "use crate::Renderer;\n\n/// A rectangle has a position and a size.\n#[derive(Clone)]\npub(crate) struct RectanglePixel {\n   "
  },
  {
    "path": "render/src/field_params/util.rs",
    "chars": 38,
    "preview": "pub(crate) const KV_PADDING: u32 = 2;\n"
  },
  {
    "path": "render/src/field_params/width.rs",
    "chars": 778,
    "preview": "use super::LabelRef;\nuse crate::Renderer;\nuse text::truncate;\n\n/// Not a label... but the IDEA of a label.\n/// This hold"
  },
  {
    "path": "render/src/field_params.rs",
    "chars": 824,
    "preview": "mod boolean;\nmod boolean_corners;\nmod key_input;\nmod key_list;\nmod key_list_corners;\nmod key_width;\nmod label;\nmod label"
  },
  {
    "path": "render/src/lib.rs",
    "chars": 2126,
    "preview": "//! This crate handles all rendering.\n//!\n//! The `Panels` struct reads the current state of the program and draws on th"
  },
  {
    "path": "render/src/links_panel.rs",
    "chars": 3216,
    "preview": "use crate::panel::*;\nuse crate::Popup;\nuse input::InputEvent;\nuse text::Tooltips;\n\nconst NUM_LINKS: usize = 3;\nconst LAB"
  },
  {
    "path": "render/src/main_menu.rs",
    "chars": 10247,
    "preview": "use crate::panel::*;\nuse colorgrad::Color;\nuse colorgrad::CustomGradient;\nuse input::InputEvent;\nuse macroquad::texture:"
  },
  {
    "path": "render/src/music_panel.rs",
    "chars": 4077,
    "preview": "use crate::panel::*;\nuse common::music_panel_field::*;\n\n/// The music panel.\npub(crate) struct MusicPanel {\n    /// The "
  },
  {
    "path": "render/src/open_file_panel.rs",
    "chars": 10126,
    "preview": "use crate::panel::*;\nuse crate::{Page, PagePosition, Popup};\nuse common::open_file::*;\nuse hashbrown::HashMap;\nuse text:"
  },
  {
    "path": "render/src/page.rs",
    "chars": 2149,
    "preview": "use crate::PagePosition;\n\n/// A page of elements in a scrollable context.\npub(crate) struct Page {\n    /// The indices o"
  },
  {
    "path": "render/src/page_position.rs",
    "chars": 347,
    "preview": "/// The position of a page in a scrollable context.\n#[derive(Eq, PartialEq, Hash, Copy, Clone, Debug)]\npub(crate) enum P"
  },
  {
    "path": "render/src/panel.rs",
    "chars": 2604,
    "preview": "pub(crate) use crate::drawable::*;\npub(crate) use crate::field_params::*;\npub(crate) use crate::ColorKey;\npub(crate) use"
  },
  {
    "path": "render/src/panels.rs",
    "chars": 4032,
    "preview": "use crate::export_panel::ExportPanel;\nuse crate::export_settings_panel::ExportSettingsPanel;\nuse crate::links_panel::Lin"
  },
  {
    "path": "render/src/piano_roll_panel/multi_track.rs",
    "chars": 6615,
    "preview": "use super::viewable_notes::{ViewableNote, ViewableNotes};\nuse crate::panel::*;\nuse crate::{get_track_heights, Page};\nuse"
  },
  {
    "path": "render/src/piano_roll_panel/piano_roll_rows.rs",
    "chars": 5333,
    "preview": "use super::viewable_notes::ViewableNotes;\nuse crate::panel::Rectangle;\nuse crate::{ColorKey, Renderer};\nuse common::view"
  },
  {
    "path": "render/src/piano_roll_panel/top_bar.rs",
    "chars": 7918,
    "preview": "use crate::panel::*;\nuse common::{EditMode, IndexedEditModes, PianoRollMode, SelectMode};\nuse hashbrown::HashMap;\nuse te"
  },
  {
    "path": "render/src/piano_roll_panel/viewable_notes.rs",
    "chars": 5873,
    "preview": "use crate::panel::*;\nuse audio::play_state::PlayState;\nuse common::*;\n\n/// A viewable note.\npub(crate) struct ViewableNo"
  },
  {
    "path": "render/src/piano_roll_panel/volume.rs",
    "chars": 3488,
    "preview": "use super::viewable_notes::*;\nuse crate::panel::*;\nuse common::MAX_VOLUME;\n\nconst MAX_VOLUME_F: f32 = MAX_VOLUME as f32;"
  },
  {
    "path": "render/src/piano_roll_panel.rs",
    "chars": 17391,
    "preview": "use crate::panel::*;\nuse audio::play_state::PlayState;\nuse audio::SharedPlayState;\nmod piano_roll_rows;\nuse piano_roll_r"
  },
  {
    "path": "render/src/popup.rs",
    "chars": 1261,
    "preview": "use crate::Renderer;\nuse common::{PanelType, State};\n\n/// A popup tries to capture the backround texture when its panel "
  },
  {
    "path": "render/src/quit_panel.rs",
    "chars": 1948,
    "preview": "use crate::{panel::*, popup::Popup};\nuse input::InputEvent;\nuse text::Tooltips;\n\nconst LABEL_PADDING: u32 = 8;\nconst LAB"
  },
  {
    "path": "render/src/renderer.rs",
    "chars": 24668,
    "preview": "use crate::field_params::*;\nuse crate::{ColorKey, Focus};\nuse common::config::parse_bool;\nuse common::font::{get_font, g"
  },
  {
    "path": "render/src/tracks_panel.rs",
    "chars": 7737,
    "preview": "use crate::panel::*;\nuse crate::{get_track_heights, Page, TRACK_HEIGHT_NO_SOUNDFONT, TRACK_HEIGHT_SOUNDFONT};\nuse text::"
  },
  {
    "path": "render/src/types.rs",
    "chars": 35,
    "preview": "pub(crate) type Focus = [bool; 2];\n"
  },
  {
    "path": "src/main.rs",
    "chars": 6539,
    "preview": "#![cfg_attr(not(debug_assertions), windows_subsystem = \"windows\")]\n\nuse audio::Conn;\nuse clap::Parser;\nuse common::args:"
  },
  {
    "path": "test_files/child_paths/test_0.cac",
    "chars": 3300,
    "preview": "{\"state\":{\"music\":{\"midi_tracks\":[{\"channel\":0,\"gain\":127,\"notes\":[[60,127,0,192],[62,127,192,384],[64,127,384,576]],\"mu"
  },
  {
    "path": "test_files/child_paths/test_1.cac",
    "chars": 3300,
    "preview": "{\"state\":{\"music\":{\"midi_tracks\":[{\"channel\":0,\"gain\":127,\"notes\":[[60,127,0,192],[62,127,192,384],[64,127,384,576]],\"mu"
  },
  {
    "path": "test_files/child_paths/test_2.CAC",
    "chars": 3300,
    "preview": "{\"state\":{\"music\":{\"midi_tracks\":[{\"channel\":0,\"gain\":127,\"notes\":[[60,127,0,192],[62,127,192,384],[64,127,384,576]],\"mu"
  },
  {
    "path": "test_files/ubuntu20.04/speechd.conf",
    "chars": 12541,
    "preview": "# Global configuration for Speech Dispatcher\n# ==========================================\n\n# -----SYSTEM OPTIONS-----\n\n#"
  },
  {
    "path": "text/Cargo.toml",
    "chars": 559,
    "preview": "[package]\nname = \"text\"\nversion.workspace = true\nauthors.workspace = true\ndescription.workspace = true\ndocumentation.wor"
  },
  {
    "path": "text/src/lib.rs",
    "chars": 13801,
    "preview": "//! This crate handles three related, but separate, tasks:\n//!\n//! 1. `Text` stores localized text. Throughout Cacophony"
  },
  {
    "path": "text/src/tooltips.rs",
    "chars": 7397,
    "preview": "use crate::{Text, TtsString};\nuse hashbrown::hash_map::Entry;\nuse hashbrown::HashMap;\nuse input::{Input, InputEvent, Qwe"
  },
  {
    "path": "text/src/tts.rs",
    "chars": 7909,
    "preview": "use crate::TtsString;\nuse common::config::{parse, parse_bool};\nuse ini::Ini;\nuse tts::{Gender, Tts, Voice};\n\n/// Text-to"
  },
  {
    "path": "text/src/tts_string.rs",
    "chars": 629,
    "preview": "/// A text-to-speech string is spoken via a TTS engine and displayed as subtitles.\n#[derive(Default, Clone)]\npub struct "
  },
  {
    "path": "text/src/value_map.rs",
    "chars": 1613,
    "preview": "use crate::Text;\nuse hashbrown::HashMap;\nuse std::hash::Hash;\n\n/// A map of keys of type T to value strings, and a map o"
  }
]

// ... and 3 more files (download for full content)

About this extraction

This page contains the full source code of the subalterngames/cacophony GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 162 files (656.6 KB), approximately 163.1k tokens, and a symbol index with 802 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!