Full Code of jkotlinski/lsdpatch for AI

master f6a2d01b0a1a cached
58 files
349.6 KB
84.8k tokens
636 symbols
1 requests
Download .txt
Showing preview only (370K chars total). Download the full file or copy to clipboard to get everything.
Repository: jkotlinski/lsdpatch
Branch: master
Commit: f6a2d01b0a1a
Files: 58
Total size: 349.6 KB

Directory structure:
gitextract_yezj645m/

├── .gitattributes
├── .github/
│   └── workflows/
│       └── maven.yml
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── pom.xml
└── src/
    ├── main/
    │   ├── java/
    │   │   ├── Document/
    │   │   │   ├── Document.java
    │   │   │   ├── IDocumentListener.java
    │   │   │   └── LSDSavFile.java
    │   │   ├── com/
    │   │   │   └── laszlosystems/
    │   │   │       └── libresample4j/
    │   │   │           ├── FilterKit.java
    │   │   │           ├── Resampler.java
    │   │   │           └── SampleBuffers.java
    │   │   ├── fontEditor/
    │   │   │   ├── .gitignore
    │   │   │   ├── ChangeEventListener.java
    │   │   │   ├── FontEditor.java
    │   │   │   ├── FontEditorColorSelector.java
    │   │   │   ├── FontMap.java
    │   │   │   └── TileEditor.java
    │   │   ├── kitEditor/
    │   │   │   ├── KitEditor.java
    │   │   │   ├── Sample.java
    │   │   │   ├── SamplePicker.java
    │   │   │   ├── SampleView.java
    │   │   │   ├── Sound.java
    │   │   │   ├── WaveFile.java
    │   │   │   └── sbc.java
    │   │   ├── lsdpatch/
    │   │   │   ├── LSDPatcher.java
    │   │   │   ├── MainWindow.java
    │   │   │   ├── NewVersionChecker.java
    │   │   │   ├── RomUpgradeTool.java
    │   │   │   └── WwwUtil.java
    │   │   ├── paletteEditor/
    │   │   │   ├── ColorPicker.java
    │   │   │   ├── ColorUtil.java
    │   │   │   ├── HuePanel.java
    │   │   │   ├── PaletteEditor.java
    │   │   │   ├── RGB555.java
    │   │   │   ├── SaturationBrightnessPanel.java
    │   │   │   ├── ScreenShotColors.java
    │   │   │   ├── Swatch.java
    │   │   │   ├── SwatchPair.java
    │   │   │   └── SwatchPanel.java
    │   │   ├── songManager/
    │   │   │   └── SongManager.java
    │   │   ├── structures/
    │   │   │   ├── LSDJFont.java
    │   │   │   └── ROMDataManipulator.java
    │   │   └── utils/
    │   │       ├── CommandLineFunctions.java
    │   │       ├── EditorPreferences.java
    │   │       ├── FileDialogLauncher.java
    │   │       ├── FileDrop.java
    │   │       ├── FontIO.java
    │   │       ├── GlobalHolder.java
    │   │       ├── RomUtilities.java
    │   │       └── StretchIcon.java
    │   └── resources/
    │       └── META-INF/
    │           └── MANIFEST.MF
    └── test/
        ├── java/
        │   ├── Document/
        │   │   ├── DocumentTest.java
        │   │   └── LSDSavFileTest.java
        │   └── kitEditor/
        │       └── SampleTest.java
        └── resources/
            ├── empty.lsdprj
            └── triangle_waves.lsdprj

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

================================================
FILE: .gitattributes
================================================
* text=auto


================================================
FILE: .github/workflows/maven.yml
================================================
# This workflow will build a Java project with Maven
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven

name: Java CI with Maven

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2
    - name: Set up JDK 1.8
      uses: actions/setup-java@v1
      with:
        java-version: 1.8
    - name: Build with Maven
      run: mvn -B package --file pom.xml


================================================
FILE: .gitignore
================================================
# Maven
target

# IntelliJ IDEA
.idea
.settings
*.iml

*.gb
*.sav
*.wav
*.raw
./*.lsdprj


================================================
FILE: CHANGELOG.md
================================================
# Changelog
All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
### Added
 - ROM Upgrade Tool: "Select ROM file..." button allows upgrading to arbitrary ROM files instead of only downloading latest stable or experimental from littlesounddj.com.

## [1.13.3] - 2024-09-20
### Fixed
 - Kit Editor: tiny window size on Linux.

## [1.13.1] - 2023-01-27
### Fixed
 - Palette Editor: Updated color correction to match Sameboy 0.15.x.

## [1.13.0] - 2022-04-11
### Fixed
 - Filename filtering in non-Windows file dialogs broke in 1.8.0.

### Added
 - Palette Editor: Color space combo box.
 - Palette Editor: "Reality" color space, which looks close to real Game Boy Color.

## [1.12.0] - 2021-10-01
### Added
 - Kit Editor: "Invert Polarity for GBA" preference.

### Changed
 - Kit Editor: Update dither per sample.
 - Kit Editor: Moved "half-speed" preference to menu.
 - ROM files may now use .gbc extension.

## [1.11.6] - 2021-08-18
### Fixed
 - Kit Editor: Dither preference.
 - Kit Editor: Allow filenames less than 3 characters in length.

### Changed
 - Kit Editor: Moved dither preference to main window.
 
### Added
 - Kit Editor: "Duplicate sample" and "Copy" in context menu
 - Kit Editor: Edit menu for "Trim all samples to equal length" and "Paste"

## [1.11.5] - 2021-06-16
### Changed
 - Kit Editor: Tweaked resampler lowpass-filter settings to preserve more treble.

### Added
 - Kit Editor: Preferences menu for low-pass filter and dither.

## [1.11.4] - 2021-06-01
### Fixed
 - Palette Editor: Color picker could not pick black (0,0,0) due to UI scaling bug.

### Added
 - Palette Editor: Show selected color RGB value in color picker.

## [1.11.3] - 2021-04-25
### Changed
 - Kit Editor: reverted 1.11.2 changes due to cymbal dithering problems.

## [1.11.2] - 2021-04-23
### Changed
 - Kit Editor: tweaked DC level to reduce DMG noise. works best with
   lsdj 9.2.2 and above.

## [1.11.1] - 2021-04-18
### Changed
 - Kit Editor: added internal versioning field to kits, that tells if they were
   created using lsdpatcher 1.11.1 or above. this allows adding old kits to
   lsdj 9.2.0+ without loss of quality.

## [1.11.0] - 2021-04-15
### Fixed
 - Kit Editor: sample preview playback had inverted polarity.
 - Song Manager: remember last used .lsdprj path.
 - Clearing kits would not free up space for adding .lsdprj songs.
 - Minor UI issues.

### Changed
 - Kit Editor: when creating kits, wave frames are now rotated right, so that
   the sample to be played back last is written first.
   this compensates for the Game Boy wave refresh bug that plays back samples
   in wrong order after changing wave.
   best used with Little Sound Dj 9.2.0 and above.

## [1.10.5] - 2021-03-05
### Fixed
 - Kit Editor: inversed sample polarity. broken since always.
 - Kit Editor: garbled sample names when renaming an uninitialized kit.
 - Kit Editor: slow switching to high numbered kits.
 - ROM upgrade tool would not find new versions ending with A-Z.
 - Disabled ROM upgrade if there are unsaved changes.

### Added
 - Kit Editor: F1/F2 shortcut for previous/next bank.
 - Kit Editor: Sample pad tooltip.

### Removed
 - Kit Editor: wave blending, which didn't quite work due to timing issues.

## [1.10.4] - 2021-02-22
### Fixed
 - Kit Editor: removed sample prelisten low-pass filter.

## [1.10.3] - 2021-02-12
### Fixed
 - Kit Editor: various volume and trimming errors.
 - Font Editor: avoid duplicate font names.
 - Font Editor: .png file extension got included in font name when loading a font PNG.
 - Error handling when palettes cannot be parsed.
 - .sav file not found for ROM images ending with ".gb.gb".
 - ROM upgrade did not preserve graphics characters.

### Changed
 - Subwindows are now modal.

### Added
 - Kit Editor: bank switching buttons.

## [1.10.2] - 2020-11-22
### Fixed
 - Kit Editor: when replacing samples, trim sample end to fit.

## [1.10.1] - 2020-11-10
### Fixed
 - New version check at startup.

## [1.10.0] - 2020-11-10
### Fixed
 - Kit Editor: sample duration right alignment.
 - Kit Editor: made text fields handle "enter" key.
 - Kit Editor: stop listening to keypresses when window is closed.
 - Kit Editor: make focus return to main window when bank is changed.
 - Various window resize issues.

### Added
 - Kit Editor: pitch spinner, for changing sample pitch by semitone.
 - Kit Editor: trim spinner, for reducing sample length.
 - New version check at startup.

### Changed
 - Kit Editor: now removes DC offset before resampling.
 - Kit Editor: changed "Reload samples" button to "Reload sample".
 - Kit Editor: when adding a too big sample, trim sample end to fit.
 - Kit Editor: disabled "Add sample" button when kit is full.

## [1.9.0] - 2020-10-31
### Fixed
 - Kit Editor: dramatically improved resampling using libresample4j.
 - Kit Editor: refresh sample view when the sample is reloaded.
 - Kit Editor: update "seconds free" after volume change.
 - Kit Editor: pad kit banks with "rst" instead of "nop" instructions, for crash detection.

### Added
 - Kit Editor: print sample duration in sample view.

## [1.8.1] - 2020-10-25
### Fixed
 - Kit Editor: loading kits with sample volumes stored in settings file.
 - Kit Editor: reduced wave blending noise for emulators that do not have the Game Boy wave refresh bug.
 - Palette Editor: force palette names to upper case.

## [1.8.0] - 2020-10-24
### Fixed
 - Palette Editor: avoid duplicate palette names when loading a palette.
 - Palette Editor: dragging color picker sliders is now more responsive.
 - Palette Editor: improved color picker visibility.
 - Kit Editor: update of "bytes used" field.
 - Font Editor: when loading font from .png, set font name from the file name.
 - Some file dialogs would not remember the last used directory.
 - Saving a ROM when no SAV has been loaded.
 - Switching .sav would not take effect until loading a ROM.

### Added
 - Kit Editor: MPC-like UI with pads. Play by clicking or keys 1234QWERASDFZXC. Right-click pad to rename, replace or delete sample.
 - Kit Editor: "Reload samples", "Save ROM", "Clear kit" buttons.
 - Kit Editor: automatic silence trimming.
 - Kit Editor: when saving kits, remember source sample files + volumes.
 - Font Editor: support for editing graphics characters.

### Changed
 - Kit Editor: "Add Sample" now automatically resamples, normalizes and dithers the sample. No need to prepare samples using sox anymore.
 - Kit Editor: switched to TPDF dither for improved sound.
 - Kit Editor: when adding samples, blend wave frames to reduce impact of the [Game Boy wave refresh bug](https://www.devrs.com/gb/files/gbsnd3.gif).
 - Kit Editor: volume control now adjusts sample volume instead of pre-listening volume.
 - Kit Editor: improved sound playback quality.
 - Kit Editor: click sample view to play.
 - Kit Editor: half-speed setting now also affects "Add sample".
 - Kit Editor: renamed "Export kit" to "Save kit".
 - Kit Editor: show unused space in seconds instead of bytes.
 - Palette Editor: improved mid-tone generation.
 - Various file dialog improvements.
 - Improved command line feedback.

### Removed
 - Font Editor: removed saving of .lsdfnt files, as well as loading/saving multiple fonts in one go.
 - Kit Editor: "Play sample on click" toggle.
 - Kit Editor: "Export all samples" button.

## [1.7.0] - 2020-10-06
### Fixed
 - Kit Editor: sample export broke in 1.6.0.
 - Song Editor: incorrect broken-song warnings. thx michael dufault!

### Added
 - Palette Editor: color picker!
 - Palette Editor: click in lsdj screens to select color.
 - Palette Editor: "swap color" and "clone color" buttons.
 - Palette Editor: "raw" button, which displays colors as-is.

### Changed
 - Palette Editor: switched color correction from Gambatte to Sameboy.
 - Palette Editor: updated screenshots to LSDj v8.9.0.
 - Palette Editor: create brighter mid-tones if the background is brighter than the foreground.
 - Each file extension now has its own last used load/save file path.

### Removed
 - Palette Editor: color spinners.

## [1.6.0] - 2020-10-02
### Fixed
 - Kit sample playback got stuck at times.
 - Old LSDj ROMs (like, v3) would not open.

### Added
 - Startup dialog to choose ROM, SAV and sub-tool.
 - Added song manager from LSDManager project.
 - Song manager warning on corrupted songs.
 - Song manager now saves LSDj Project files (.lsdprj) which contains both song and sample kits.
 - Upgrade ROM button, which downloads the latest ROM images from https://www.littlesounddj.com. The upgrade preserves custom kits, fonts and palettes.
 - Palette editor randomize button.

### Changed
 - Palette editor layout.

## [1.5.0] - 2020-09-13
### Changed
 - Merged LSDPatcher Redux v1.4.6. Full list of changes at [LSDPatcher Redux release page](https://github.com/Eiyeron/lsdpatch/releases).

## [1.4.2] - 2020-08-19
### Added
 - LSDj v8.8.3 support.

## [1.4.1] - 2020-07-04
### Added
 - LSDj v8.7.4 support.

## [1.4.0] - 2020-07-02
### Added
 - Palette editor "Desaturate preview" toggle.
 - Palette editor copy/paste.

## [1.3.0] - 2020-06-27
### Added
 - Allow variable number of palettes. Some LSDj versions have 6 palettes, others 7.

## [1.2.0] - 2020-03-05
### Changed
 - Java 8 now required.
 - Brighter background shade for DMG fonts.

## [1.1.6] - 2017-10-19
### Fixed
 - Recalculate ROM checksum on save.

## [1.1.5] - 2017-10-14
### Added
 - "Import kits from ROM" button.

## [1.1.4] - 2017-05-07
### Changed
 - Kit selector is now hexadecimal.
 - Brought back dot in kit list.

## [1.1.3] - 2017-01-26
### Changed
 - Made palette editor a bit bigger.

### Fixed
 - Shaded and inverted tiles would not always be generated by font editor.

## [1.1.2] - 2017-01-23
### Added
 - Load and save palettes.

### Fixed
 - When loading a ROM, palettes would be added twice.
 - Errors related to combo box in font editor.

## [1.1.1] - 2017-01-23
### Added
 - Font renaming.
 - Font editor grid.
 - Include font name in .lsdfnt

### Fixed
 - Out-of-bounds drawing in font editor.

## [1.1.0] - 2017-01-23
### Added
 - Font editor.

## [1.0.2] - 2017-01-20
### Changed
 - Made preview screens in palette editor bigger.

## [1.0.1] - 2017-01-20
### Fixed
 - Wrong behavior when loading invalid ROM images.

### Changed
 - Lowered required JRE version to 1.6.

## [1.0.0] - 2017-01-20
### Added
 - Palette editor, entered by menu option Palette->Edit Palette.

## [0.19] - 2011-08-20
### Fixed
 - Loading a long sample threw a confusing, empty error message. Thanks to Clay Morrow for reporting.


[unreleased]: https://github.com/jkotlinski/lsdpatch/compare/v1.13.3..HEAD
[1.13.3]: https://github.com/jkotlinski/lsdpatch/compare/v1.13.2..v1.13.3
[1.13.2]: https://github.com/jkotlinski/lsdpatch/compare/v1.13.1..v1.13.2
[1.13.1]: https://github.com/jkotlinski/lsdpatch/compare/v1.13.0..v1.13.1
[1.13.0]: https://github.com/jkotlinski/lsdpatch/compare/v1.12.0..v1.13.0
[1.12.0]: https://github.com/jkotlinski/lsdpatch/compare/v1.11.6..v1.12.0
[1.11.6]: https://github.com/jkotlinski/lsdpatch/compare/v1.11.5..v1.11.6
[1.11.5]: https://github.com/jkotlinski/lsdpatch/compare/v1.11.4..v1.11.5
[1.11.4]: https://github.com/jkotlinski/lsdpatch/compare/v1.11.3..v1.11.4
[1.11.3]: https://github.com/jkotlinski/lsdpatch/compare/v1.11.2..v1.11.3
[1.11.2]: https://github.com/jkotlinski/lsdpatch/compare/v1.11.1..v1.11.2
[1.11.1]: https://github.com/jkotlinski/lsdpatch/compare/v1.11.0..v1.11.1
[1.11.0]: https://github.com/jkotlinski/lsdpatch/compare/v1.10.5..v1.11.0
[1.10.5]: https://github.com/jkotlinski/lsdpatch/compare/v1.10.4..v1.10.5
[1.10.4]: https://github.com/jkotlinski/lsdpatch/compare/v1.10.3..v1.10.4
[1.10.3]: https://github.com/jkotlinski/lsdpatch/compare/v1.10.2..v1.10.3
[1.10.2]: https://github.com/jkotlinski/lsdpatch/compare/v1.10.1..v1.10.2
[1.10.1]: https://github.com/jkotlinski/lsdpatch/compare/v1.10.0..v1.10.1
[1.10.0]: https://github.com/jkotlinski/lsdpatch/compare/v1.9.0..v1.10.0
[1.9.0]: https://github.com/jkotlinski/lsdpatch/compare/v1.8.1..v1.9.0
[1.8.1]: https://github.com/jkotlinski/lsdpatch/compare/v1.8.0..v1.8.1
[1.8.0]: https://github.com/jkotlinski/lsdpatch/compare/v1.7.0..v1.8.0
[1.7.0]: https://github.com/jkotlinski/lsdpatch/compare/v1.6.0..v1.7.0
[1.6.0]: https://github.com/jkotlinski/lsdpatch/compare/v1.5.0..v1.6.0
[1.5.0]: https://github.com/jkotlinski/lsdpatch/compare/v1.4.2..v1.5.0
[1.4.2]: https://github.com/jkotlinski/lsdpatch/compare/v1.4.1..v1.4.2
[1.4.1]: https://github.com/jkotlinski/lsdpatch/compare/v1.4.0..v1.4.1
[1.4.0]: https://github.com/jkotlinski/lsdpatch/compare/v1.3.0..v1.4.0
[1.3.0]: https://github.com/jkotlinski/lsdpatch/compare/v1.2.0..v1.3.0
[1.2.0]: https://github.com/jkotlinski/lsdpatch/compare/v1.1.6..v1.2.0
[1.1.6]: https://github.com/jkotlinski/lsdpatch/compare/v1.1.5..v1.1.6
[1.1.5]: https://github.com/jkotlinski/lsdpatch/compare/v1.1.4..v1.1.5
[1.1.4]: https://github.com/jkotlinski/lsdpatch/compare/v1.1.3..v1.1.4
[1.1.3]: https://github.com/jkotlinski/lsdpatch/compare/v1.1.2..v1.1.3
[1.1.2]: https://github.com/jkotlinski/lsdpatch/compare/v1.1.1..v1.1.2
[1.1.1]: https://github.com/jkotlinski/lsdpatch/compare/v1.1.0..v1.1.1
[1.1.0]: https://github.com/jkotlinski/lsdpatch/compare/v1.0.2..v1.1.0
[1.0.2]: https://github.com/jkotlinski/lsdpatch/compare/v1.0.1..v1.0.2
[1.0.1]: https://github.com/jkotlinski/lsdpatch/compare/v1.0.0..v1.0.1
[1.0.0]: https://github.com/jkotlinski/lsdpatch/compare/v0.19...v1.0.0
[0.19]: https://github.com/jkotlinski/lsdpatch/releases/tag/v0.19


================================================
FILE: LICENSE
================================================
Copyright (C) 2001 by Johan Kotlinski, 2017 by Florian Dormont

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.

===============================================================================

Filedrop.java, Copyright (C) 2007 by Robert Harder. No rights reserved.

===============================================================================

StretchIcon.java, Copyright (C) 2016 by Darryl Burke. No rights reserved.

===============================================================================

Color correction routine from Sameboy:

MIT License

Copyright (c) 2015-2020 Lior Halphon

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.

===============================================================================

libresample4j
Copyright (c) 2009 Laszlo Systems, Inc. All Rights Reserved.

libresample4j is a Java port of Dominic Mazzoni's libresample 0.1.3,
which is in turn based on Julius Smith's Resample 1.7 library.
     http://www-ccrma.stanford.edu/~jos/resample/

License: LGPL -- see the file LICENSE.txt for more information

--

This product includes software derived from the work of
Julius Smith and Dominic Mazzoni
(http://ccrma-www.stanford.edu/~jos/resample/Free_Resampling_Software.html)

libresample 0.1.3
Copyright 2003 Dominic Mazzoni <dominic@minorninth.com>.

Resample 1.7
Copyright 1994-2002 Julius O. Smith III <jos@ccrma.stanford.edu>,
all rights reserved.


================================================
FILE: README.md
================================================
# LSDPatcher

A tool for modifying songs, samples, fonts and palettes on [Little Sound Dj][lsdj] (LSDj) ROM images and save files. Requires
[Java][java] 8+. If you have problems running the .jar on Windows, try [Jarfix][jarfix].

[Download][releases] | [Fonts][lsdfnts] | [Palettes][lsdpals]

## Building

Build using [Maven](https://maven.apache.org/): `mvn package`

![Java CI with Maven](https://github.com/jkotlinski/lsdpatch/workflows/Java%20CI%20with%20Maven/badge.svg)

[lsdj]: https://www.littlesounddj.com/
[sox]: http://sox.sourceforge.net/
[releases]: https://github.com/jkotlinski/lsdpatch/releases
[wiki]: https://github.com/jkotlinski/lsdpatch/wiki/Documentation
[jarfix]: http://johann.loefflmann.net/en/software/jarfix/index.html
[java]: http://www.java.com/
[lsdfnts]: https://github.com/psgcabal/lsdfonts
[lsdpals]: https://github.com/psgcabal/lsdpals


================================================
FILE: pom.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.littlesounddj</groupId>
    <artifactId>lsdpatch</artifactId>
    <version>1.13.3</version>
    <packaging>jar</packaging>

    <dependencies>
        <dependency>
            <groupId>com.miglayout</groupId>
            <artifactId>miglayout</artifactId>
            <version>3.7.4</version>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>5.7.0</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.0.0-M5</version>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <compilerArgument>-Xlint:deprecation</compilerArgument>
                    <compilerArgument>-XDignore.symbol.file</compilerArgument>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <groupId>org.apache.maven.plugins</groupId>
                <version>3.1.0</version>
                <executions>
                    <execution>
                        <id>make-executable-jar-with-dependencies</id>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                        <configuration>
                            <appendAssemblyId>false</appendAssemblyId>
                            <archive>
                                <manifest>
                                    <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
                                    <addClasspath>true</addClasspath>
                                    <mainClass>lsdpatch.LSDPatcher</mainClass>
                                </manifest>
                            </archive>
                            <descriptorRefs>
                                <descriptorRef>jar-with-dependencies</descriptorRef>
                            </descriptorRefs>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>

    </build>
</project>


================================================
FILE: src/main/java/Document/Document.java
================================================
package Document;

import utils.EditorPreferences;
import utils.RomUtilities;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

public class Document {
    private boolean romDirty;
    private byte[] romImage;
    private File romFile;

    private boolean savDirty;
    private LSDSavFile savFile = new LSDSavFile();

    private final List<IDocumentListener> documentListeners = new LinkedList<>();

    public void subscribe(IDocumentListener documentListener) {
        documentListeners.add(documentListener);
    }

    public File romFile() {
        return romFile;
    }

    private void publishDocumentDirty() {
        for (IDocumentListener documentListener : documentListeners) {
            documentListener.onDocumentDirty(isDirty());
        }
    }

    private void setRomDirty(boolean dirty) {
        romDirty = dirty;
        publishDocumentDirty();
    }

    private void setSavDirty(boolean dirty) {
        savDirty = dirty;
        publishDocumentDirty();
    }

    public byte[] romImage() {
        return romImage == null ? null : romImage.clone();
    }

    public void setRomImage(byte[] romImage) {
        if (Arrays.equals(romImage, this.romImage)) {
            return;
        }
        this.romImage = romImage;
        setRomDirty(true);
    }

    public void loadRomImage(String romPath) throws IOException {
        romFile = new File(romPath);
        romImage = new byte[RomUtilities.BANK_SIZE * RomUtilities.BANK_COUNT];
        setRomDirty(false);
        try {
            RandomAccessFile f = new RandomAccessFile(romFile, "r");
            f.readFully(romImage);
            f.close();
            EditorPreferences.setLastPath("gb", romPath);
        } catch (IOException ioe) {
            romImage = null;
            throw ioe;
        }
    }

    public void loadSavFile(String savPath) throws IOException {
        setSavDirty(false);
        try {
            savFile = new LSDSavFile();
            savFile.loadFromSav(savPath);
            EditorPreferences.setLastPath("sav", savPath);
        } catch (IOException e) {
            savFile = null;
            throw e;
        }
    }

    public LSDSavFile savFile() {
        if (savFile == null) {
            return null;
        }
        try {
            return savFile.clone();
        } catch (CloneNotSupportedException e) {
            return null;
        }
    }

    public void setSavFile(LSDSavFile savFile) {
        if (savFile == null) {
            this.savFile = null;
            setSavDirty(false);
            return;
        }
        if (this.savFile != null && savFile.equals(this.savFile)) {
            return;
        }
        this.savFile = savFile;
        setSavDirty(true);
    }

    public boolean isSavDirty() {
        return savDirty;
    }

    public boolean isRomDirty() {
        return romDirty;
    }

    public boolean isDirty() {
        return romDirty || savDirty;
    }

    public void setRomFile(File file) {
        romFile = file;
    }

    public void clearSavDirty() {
        setSavDirty(false);
    }

    public void clearRomDirty() {
        setRomDirty(false);
    }
}


================================================
FILE: src/main/java/Document/IDocumentListener.java
================================================
package Document;

public interface IDocumentListener {
    void onDocumentDirty(boolean dirty);
}


================================================
FILE: src/main/java/Document/LSDSavFile.java
================================================
package Document;

import utils.RomUtilities;

import java.io.*;
import java.util.*;
import javax.swing.*;

public class LSDSavFile implements Cloneable {
    final int blockSize = 0x200;
    final int bankSize = 0x8000;
    final int bankCount = 4;
    final int savFileSize = bankSize * bankCount;
    final int songCount = 0x20;
    final int fileNameLength = 8;

    final int fileNameStartPtr = 0x8000;
    final int fileVersionStartPtr = 0x8100;
    final int blockAllocTableStartPtr = 0x8141;
    final int blockStartPtr = 0x8200;
    final int activeFileSlot = 0x8140;
    final char emptySlotValue = (char) 0xff;

    boolean is64kb = false;
    boolean is64kbHasBeenSet = false;

    byte[] workRam;

    public LSDSavFile() {
        workRam = new byte[savFileSize];
    }

    public LSDSavFile clone() throws CloneNotSupportedException {
        LSDSavFile copy = (LSDSavFile)super.clone();
        copy.is64kb = is64kb;
        copy.is64kbHasBeenSet = is64kbHasBeenSet;
        copy.workRam = workRam.clone();
        return copy;
    }

    public boolean equals(LSDSavFile rhs) {
        return Arrays.equals(workRam, rhs.workRam);
    }

    private boolean isSixtyFourKbRam() {
        if (is64kbHasBeenSet) return is64kb;

        for (int i = 0; i < 0x10000; ++i) {
            if (workRam[i] != workRam[0x10000 + i]) {
                is64kb = false;
                is64kbHasBeenSet = true;
                return false;
            }
        }
        is64kb = true;
        is64kbHasBeenSet = true;
        return true;
    }

    public int totalBlockCount() {
        // FAT takes one block.
        return isSixtyFourKbRam() ? 0xbf - 0x80 : 0xbf;
    }

    public void saveAs(String filePath) throws IOException {
        try (FileOutputStream fileOutputStream = new FileOutputStream(filePath)) {
            if (isSixtyFourKbRam()) {
                System.arraycopy(workRam, 0, workRam, 65536, 0x10000);
            }
            fileOutputStream.write(workRam);
        }
    }

    public void clearSong(int index) {
        int ramPtr = blockAllocTableStartPtr;
        int block = 0;

        while (block < totalBlockCount()) {
            int tableValue = workRam[ramPtr];
            if (index == tableValue) {
                workRam[ramPtr] = (byte) emptySlotValue;
            }
            ramPtr++;
            block++;
        }

        clearFileName(index);
        clearFileVersion(index);

        if (index == getActiveFileSlot()) {
            clearActiveFileSlot();
        }
    }

    public int getBlocksUsed(int slot) {
        int ramPtr = blockAllocTableStartPtr;
        int block = 0;
        int blockCount = 0;

        while (block++ < totalBlockCount()) {
            if (slot == workRam[ramPtr++]) {
                blockCount++;
            }
        }
        return blockCount;
    }

    private void clearFileName(int index) {
        workRam[fileNameStartPtr + fileNameLength * index] = (byte) 0;
    }

    private void clearFileVersion(int index) {
        workRam[fileVersionStartPtr + index] = (byte) 0;
    }

    public int usedBlockCount() {
        return totalBlockCount() - freeBlockCount();
    }

    private byte getNewSongId() {
        for (byte slot = 0; slot < songCount; slot++) {
            if (0 == getBlocksUsed(slot)) {
                return slot;
            }
        }
        return -1;
    }

    private int getBlockIdOfFirstFreeBlock() {
        int blockAllocTableStartPtr = this.blockAllocTableStartPtr;
        int block = 0;

        while (block < totalBlockCount()) {
            int tableValue = workRam[blockAllocTableStartPtr++];
            if (tableValue < 0 || tableValue > 0x1f) {
                return block;
            }
            block++;
        }
        return -1;
    }

    /*
    public void debug_dump_fat()
    {
        int l_ram_ptr = g_block_alloc_table_start_ptr;
        int l_block = 0;

        while (l_block < getTotalBlockCount())
        {
            int l_table_value = m_work_ram[l_ram_ptr++];
            System.out.print(l_table_value + " " );
            l_block++;
        }
        System.out.println();
    }
    */

    public int freeBlockCount() {
        int ramPtr = blockAllocTableStartPtr;
        int block = 0;
        int freeBlockCount = 0;

        while (block < totalBlockCount()) {
            int tableValue = workRam[ramPtr++];
            if (tableValue < 0 || tableValue > 0x1f) {
                freeBlockCount++;
            }
            block++;
        }
        return freeBlockCount;
    }

    public void loadFromSav(String filePath) throws IOException {
        RandomAccessFile savFile = new RandomAccessFile(filePath, "r");
        savFile.readFully(workRam);
        savFile.close();

        is64kbHasBeenSet = false;
    }

    public void populateSongList(JList<String> songList) {
        String[] songStringList = new String[songCount];
        songList.removeAll();

        for (int song = 0; song < songCount; song++) {
            int blocksUsed = getBlocksUsed(song);
            String songString = song + 1 + ". ";

            if (blocksUsed > 0) {
                songString += getFileName(song);
                songString += "." + version(song);
                songString += " " + blocksUsed;
                if (!isValid(song)) {
                    songString += " \u26a0"; // warning sign
                }
            }

            songStringList[song] = songString;
        }

        songList.setListData(songStringList);
    }

    private static int convertLsdCharToAscii(int ch) {
        if (ch >= 65 && ch <= (65 + 25)) {
            //char
            return 'A' + ch - 65;
        }
        if (ch >= 48 && ch < 58) {
            //decimal number
            return '0' + ch - 48;
        }
        return 0 == ch ? 0 : ' ';
    }

    public String getFileName(int slot) {
        StringBuilder sb = new StringBuilder();
        int ramPtr = fileNameStartPtr + fileNameLength * slot;
        boolean endOfFileName = false;
        for (int fileNamePos = 0;
             fileNamePos < 8;
             fileNamePos++) {
            if (!endOfFileName) {
                char ch = (char) convertLsdCharToAscii((char)
                        workRam[ramPtr]);
                if (0 == ch) {
                    endOfFileName = true;
                } else {
                    sb.append(ch);
                }
            }
            ramPtr++;
        }
        return sb.toString();
    }

    public String version(int slot) {
        int ramPtr = fileVersionStartPtr + slot;
        String version = Integer.toHexString(workRam[ramPtr]);
        return version.substring(Math.max(version.length() - 2, 0)).toUpperCase();
    }

    public void exportSongToFile(int songId, String filePath, byte[] romImage) {
        assert (songId >= 0 && songId < 0x20);

        RandomAccessFile file;
        try {
            file = new RandomAccessFile(filePath, "rw");

            int fileNamePtr = fileNameStartPtr + songId * fileNameLength;
            file.writeByte(workRam[fileNamePtr++]);
            file.writeByte(workRam[fileNamePtr++]);
            file.writeByte(workRam[fileNamePtr++]);
            file.writeByte(workRam[fileNamePtr++]);
            file.writeByte(workRam[fileNamePtr++]);
            file.writeByte(workRam[fileNamePtr++]);
            file.writeByte(workRam[fileNamePtr++]);
            file.writeByte(workRam[fileNamePtr]);

            int fileVersionPtr = fileVersionStartPtr + songId;
            file.writeByte(workRam[fileVersionPtr]);

            writeSongBlocks(songId, file);
            writeKits(romImage, songId, file);

            file.close();
        } catch (IOException e) {
            JOptionPane.showMessageDialog(null,
                    e.getMessage(),
                    "Song export failed!",
                    JOptionPane.ERROR_MESSAGE);
        }
    }

    private void writeKits(byte[] romImage, int songId, RandomAccessFile file) throws IOException {
        TreeSet<Integer> kitsToWrite = usedKits(songId);
        while (true) {
            Integer kit = kitsToWrite.pollFirst();
            if (kit == null) {
                break;
            }
            // because legacy, kits are in banks 8-26, 32-63.
            kit += 8;
            if (kit > 26) {
                kit += 5;
            }
            int kitOffset = kit * 0x4000;
            for (int i = 0; i < 0x4000; ++i) {
                file.writeByte(romImage[kitOffset + i]);
            }
        }
    }

    TreeSet<Integer> usedKits(int songId) {
        byte[] unpackedSong = unpackSong(songId);
        assert (unpackedSong != null);
        assert (unpackedSong.length == 0x8000);

        TreeSet<Integer> kits = new TreeSet<>();
        for (int instr = 0; instr < 0x40; ++instr) {
            int instrPtr = 0x3080 + instr * 0x10;
            if (unpackedSong[instrPtr] != 2) {
                continue; // Not kit instrument.
            }
            kits.add(unpackedSong[instrPtr + 2] & 0x3f);
            kits.add(unpackedSong[instrPtr + 9] & 0x3f);
        }
        return kits;
    }

    void writeSongBlocks(int songId, RandomAccessFile file) throws IOException {
        int blockId = 0;
        int blockAllocTablePtr = blockAllocTableStartPtr;

        while (blockId < totalBlockCount()) {
            if (songId == workRam[blockAllocTablePtr++]) {
                int blockPtr = blockStartPtr + blockId * blockSize;
                for (int byteIndex = 0; byteIndex < blockSize; byteIndex++) {
                    file.writeByte(workRam[blockPtr++]);
                }
            }
            blockId++;
        }
    }

    /**
     * Decodes a song. Returns 32 kB with decoded song data, or null on failure.
     */
    private byte[] unpackSong(int songId) {
        byte[] dstBuffer = new byte[0x8000];
        int dstPos = 0;

        int blockId = 0;
        int blockAllocTablePtr = blockAllocTableStartPtr;

        while (blockId < totalBlockCount()) {
            if (songId == workRam[blockAllocTablePtr++]) {
                break;
            }
            blockId++;
        }

        int srcPtr = blockStartPtr + blockSize * blockId;

        try {
            while (true) {
                switch (workRam[srcPtr]) {
                    case (byte) 0xc0:
                        srcPtr++;
                        if (workRam[srcPtr] == (byte) 0xc0) {
                            srcPtr++;
                            dstBuffer[dstPos++] = (byte) 0xc0;
                        } else {
                            // rle
                            byte b = workRam[srcPtr++];
                            byte count = workRam[srcPtr++];
                            while (count-- != 0) {
                                dstBuffer[dstPos++] = b;
                            }
                        }
                        break;

                    case (byte) 0xe0:
                        byte count;
                        srcPtr++;
                        switch (workRam[srcPtr]) {
                            case (byte) 0xe0: // e0
                                srcPtr++;
                                dstBuffer[dstPos++] = (byte) 0xe0;
                                break;

                            case (byte) 0xff: // done!
                                return dstPos == 0x8000 ? dstBuffer : null;

                            case (byte) 0xf0: //wave
                                srcPtr++;
                                count = workRam[srcPtr++];
                                while (count-- != 0) {
                                    dstBuffer[dstPos++] = (byte) 0x8e;
                                    dstBuffer[dstPos++] = (byte) 0xcd;
                                    dstBuffer[dstPos++] = (byte) 0xcc;
                                    dstBuffer[dstPos++] = (byte) 0xbb;
                                    dstBuffer[dstPos++] = (byte) 0xaa;
                                    dstBuffer[dstPos++] = (byte) 0xa9;
                                    dstBuffer[dstPos++] = (byte) 0x99;
                                    dstBuffer[dstPos++] = (byte) 0x88;
                                    dstBuffer[dstPos++] = (byte) 0x87;
                                    dstBuffer[dstPos++] = (byte) 0x76;
                                    dstBuffer[dstPos++] = (byte) 0x66;
                                    dstBuffer[dstPos++] = (byte) 0x55;
                                    dstBuffer[dstPos++] = (byte) 0x54;
                                    dstBuffer[dstPos++] = (byte) 0x43;
                                    dstBuffer[dstPos++] = (byte) 0x32;
                                    dstBuffer[dstPos++] = (byte) 0x31;
                                }
                                break;

                            case (byte) 0xf1: //instr
                                srcPtr++;
                                count = workRam[srcPtr++];
                                while (count-- != 0) {
                                    dstBuffer[dstPos++] = (byte) 0xa8;
                                    dstBuffer[dstPos++] = 0;
                                    dstBuffer[dstPos++] = 0;
                                    dstBuffer[dstPos++] = (byte) 0xff;
                                    dstBuffer[dstPos++] = 0;
                                    dstBuffer[dstPos++] = 0;
                                    dstBuffer[dstPos++] = 3;
                                    dstBuffer[dstPos++] = 0;
                                    dstBuffer[dstPos++] = 0;
                                    dstBuffer[dstPos++] = (byte) 0xd0;
                                    dstBuffer[dstPos++] = 0;
                                    dstBuffer[dstPos++] = 0;
                                    dstBuffer[dstPos++] = 0;
                                    dstBuffer[dstPos++] = (byte) 0xf3;
                                    dstBuffer[dstPos++] = 0;
                                    dstBuffer[dstPos++] = 0;
                                }
                                break;

                            default: // block switch
                                int block = workRam[srcPtr] & 0xff;
                                srcPtr = 0x8000 + blockSize * block;
                                break;
                        }
                        break;

                    default:
                        dstBuffer[dstPos++] = workRam[srcPtr++];
                }
            }
        } catch (ArrayIndexOutOfBoundsException e) {
            return null;
        }
    }

    public boolean isValid(int songId) {
        return unpackSong(songId) != null;
    }

    static class AddSongException extends Exception {
        AddSongException(String message) {
            super(message);
        }
    }

    public void addSongFromFile(String filePath, byte[] romImage) throws Exception {
        final byte songId = getNewSongId();
        if (songId == -1) {
            throw new AddSongException("Out of song slots!");
        }

        try (FileInputStream fileInputStream = new FileInputStream(filePath)) {
            writeFileNameAndVersion(fileInputStream, songId);
            copySongToWorkRam(fileInputStream, songId);
            patchKits(fileInputStream, songId, romImage);
        } catch (Exception e) {
            clearSong(songId);
            throw e;
        }
    }

    private void writeFileNameAndVersion(FileInputStream fileInputStream, byte songId) throws IOException {
        byte[] fileName = new byte[8];
        int read = fileInputStream.read(fileName);
        assert(read == fileName.length);
        byte fileVersion = (byte)fileInputStream.read();

        int fileNamePtr = fileNameStartPtr + songId * fileNameLength;
        for (int i = 0; i < 8; ++i) {
            workRam[fileNamePtr++] = fileName[i];
        }

        int fileVersionPtr = fileVersionStartPtr + songId;
        workRam[fileVersionPtr] = fileVersion;
    }

    private void patchKits(FileInputStream fileInputStream,
                           byte songId,
                           byte[] romImage) throws IOException, AddSongException {
        ArrayList<byte[]> lsdSngKits = new ArrayList<>();
        while (true) {
            byte[] kit = new byte[0x4000];
            if (fileInputStream.read(kit) != kit.length) {
                break;
            }
            lsdSngKits.add(kit);
        }

        if (lsdSngKits.size() == 0) {
            return;
        }

        // Check if kits are already in ROM. If so, they should be reused.
        int[] newKits = new int[lsdSngKits.size()];
        for (int romKit = 0; romKit < romImage.length / 0x4000; ++romKit) {
            for (int kit = 0; kit < lsdSngKits.size(); ++kit) {
                boolean kitsAreEqual = true;
                for (int i = 0; i < 0x4000; ++i) {
                    if (lsdSngKits.get(kit)[i] != romImage[romKit * 0x4000 + i]) {
                        kitsAreEqual = false;
                        break;
                    }
                }
                if (kitsAreEqual) {
                    newKits[kit] = romKit;
                }
            }
        }

        addMissingKits(romImage, lsdSngKits, newKits);
        adjustInstruments(songId, newKits);
    }

    private List<Integer> instrumentKitLocations(int songId) {
        int songPos = 0;
        int blockId = 0;
        int blockAllocTablePtr = blockAllocTableStartPtr;
        List<Integer> instrumentKitLocations = new LinkedList<>();

        while (blockId < totalBlockCount()) {
            if (songId == workRam[blockAllocTablePtr++]) {
                break;
            }
            blockId++;
        }

        int srcPtr = blockStartPtr + blockSize * blockId;
        boolean[] isKit = new boolean[64];

        try {
            while (true) {
                switch (workRam[srcPtr]) {
                    case (byte) 0xc0:
                        srcPtr++;
                        if (workRam[srcPtr] == (byte) 0xc0) {
                            srcPtr++;
                            songPos++;
                        } else {
                            srcPtr++;
                            byte count = workRam[srcPtr++];
                            while (count-- != 0) {
                                songPos++;
                            }
                        }
                        break;

                    case (byte) 0xe0:
                        byte count;
                        srcPtr++;
                        switch (workRam[srcPtr]) {
                            case (byte) 0xe0: // e0
                                srcPtr++;
                                songPos++;
                                break;

                            case (byte) 0xff: // done!
                                assert(songPos == 0x8000);
                                return instrumentKitLocations;

                            case (byte) 0xf0: //wave
                            case (byte) 0xf1: //instr
                                srcPtr++;
                                count = workRam[srcPtr++];
                                while (count-- != 0) {
                                    songPos += 16;
                                }
                                break;

                            default: // block switch
                                int block = workRam[srcPtr] & 0xff;
                                srcPtr = 0x8000 + blockSize * block;
                                break;
                        }
                        break;

                    default:
                        // Regular byte write.
                        boolean isInstrumentWrite = songPos >= 0x3080 && songPos < 0x3480;
                        if (isInstrumentWrite) {
                            int instr = (songPos - 0x3080) / 0x10;
                            switch (songPos % 16) {
                                case 0:
                                    if (workRam[srcPtr] == 2) {
                                        isKit[instr] = true;
                                    }
                                    break;
                                case 2:
                                case 9:
                                    if (isKit[instr]) {
                                        instrumentKitLocations.add(srcPtr);
                                    }
                                    break;
                            }
                        }
                        ++songPos;
                        ++srcPtr;
                }
            }
        } catch (ArrayIndexOutOfBoundsException e) {
            return null;
        }
    }

    private void adjustInstruments(int songId, int[] newKits) {
        List<Integer> instrumentKitLocations = instrumentKitLocations(songId);
        assert(instrumentKitLocations != null);

        TreeSet<Integer> lsdSngKits = new TreeSet<>();
        for (Integer instrumentKitLocation : instrumentKitLocations) {
            int kitId = workRam[instrumentKitLocation] & 0x3f;
            lsdSngKits.add(kitId);
        }

        HashMap<Integer, Integer> kitMap = new HashMap<>();
        for (int newKit : newKits) {
            Integer oldKit = lsdSngKits.pollFirst();
            if (newKit > 26) {
                newKit -= 5;
            }
            newKit -= 8;
            kitMap.put(oldKit, newKit);
        }

        for (Integer instrumentKitLocation : instrumentKitLocations) {
            int value = workRam[instrumentKitLocation];
            int newValue = (value & ~0x3f) | kitMap.get(value & 0x3f);
            workRam[instrumentKitLocation] = (byte)newValue;
        }
    }

    private void addMissingKits(byte[] romImage, ArrayList<byte[]> lsdSngKits, int[] newKits) throws AddSongException {
        for (int kit = 0; kit < newKits.length; ++kit) {
            if (newKits[kit] != 0) {
                continue;
            }
            int newKit = findFreeKit(romImage);
            if (newKit == -1) {
                throw new AddSongException("Not enough space for kits! Remove some and try again!");
            }
            newKits[kit] = newKit;
            // Copy kit.
            // TODO: this might be a good place to swizzle old kits for improved sound quality. See sbc.java
            System.arraycopy(lsdSngKits.get(kit), 0, romImage, newKit * 0x4000, 0x4000);
        }
    }

    private int findFreeKit(byte[] romImage) {
        for (int bank = 0; bank < romImage.length / RomUtilities.BANK_SIZE; ++bank) {
            int offset = bank * RomUtilities.BANK_SIZE;
            if (romImage[offset] == -1 && romImage[offset + 1] == -1) {
                return bank;
            }
        }
        return -1;
    }

    private void copySongToWorkRam(FileInputStream fileInputStream, byte songId) throws IOException, AddSongException {
        int nextBlockIdPtr = 0;
        while (true) {
            int blockId = getBlockIdOfFirstFreeBlock();
            if (blockId == -1) {
                throw new AddSongException("Out of blocks!");
            }

            if (0 != nextBlockIdPtr) {
                //add one to compensate for unused FAT block
                workRam[nextBlockIdPtr] = (byte) (blockId + 1);
            }
            workRam[blockAllocTableStartPtr + blockId] = songId;
            int blockPtr = blockStartPtr + blockId * blockSize;
            for (int i = 0; i < blockSize; ++i) {
                workRam[blockPtr++] = (byte)fileInputStream.read();
            }
            nextBlockIdPtr = getNextBlockIdPtr(blockId);
            if (nextBlockIdPtr == -1) {
                return;
            }
        }
    }

    private void clearActiveFileSlot() {
        workRam[activeFileSlot] = (byte) 0xff;
    }

    private byte getActiveFileSlot() {
        return workRam[activeFileSlot];
    }

    /* Returns address of next block id pointer (E0 XX), if one exists in block.
     * If there is none, return -1.
     */
    private int getNextBlockIdPtr(int block) throws AddSongException {
        int ramPtr = blockStartPtr + blockSize * block;
        int byteCounter = 0;

        while (byteCounter < blockSize) {
            if (workRam[ramPtr] == (byte) 0xc0) {
                ramPtr++;
                byteCounter++;
                if (workRam[ramPtr] != (byte) 0xc0) {
                    //rle
                    ramPtr++;
                    byteCounter++;
                }
            } else if (workRam[ramPtr] == (byte) 0xe0) {
                switch (workRam[ramPtr + 1]) {
                    case (byte) 0xe0:
                        ramPtr++;
                        byteCounter++;
                        break;
                    case (byte) 0xff:
                        return -1;
                    case (byte) 0xf0: //wave
                    case (byte) 0xf1: //instr
                        ramPtr += 2;
                        byteCounter += 2;
                        break;
                    default:
                        return ramPtr + 1;
                }
            }
            ramPtr++;
            byteCounter++;
        }
        // If the pointer to next block is missing, and this is not the last
        // block of a song, the song is most likely corrupted.
        throw new AddSongException("Song corrupted!");
    }

}


================================================
FILE: src/main/java/com/laszlosystems/libresample4j/FilterKit.java
================================================
/******************************************************************************
 *
 * libresample4j
 * Copyright (c) 2009 Laszlo Systems, Inc. All Rights Reserved.
 *
 * libresample4j is a Java port of Dominic Mazzoni's libresample 0.1.3,
 * which is in turn based on Julius Smith's Resample 1.7 library.
 *      http://www-ccrma.stanford.edu/~jos/resample/
 *
 * License: LGPL -- see the file LICENSE.txt for more information
 *
 *****************************************************************************/
package com.laszlosystems.libresample4j;

/**
 * This file provides Kaiser-windowed low-pass filter support,
 * including a function to create the filter coefficients, and
 * two functions to apply the filter at a particular point.
 * 
 * <pre>
 * reference: "Digital Filters, 2nd edition"
 *            R.W. Hamming, pp. 178-179
 *
 * Izero() computes the 0th order modified bessel function of the first kind.
 *    (Needed to compute Kaiser window).
 *
 * LpFilter() computes the coeffs of a Kaiser-windowed low pass filter with
 *    the following characteristics:
 *
 *       c[]  = array in which to store computed coeffs
 *       frq  = roll-off frequency of filter
 *       N    = Half the window length in number of coeffs
 *       Beta = parameter of Kaiser window
 *       Num  = number of coeffs before 1/frq
 *
 * Beta trades the rejection of the lowpass filter against the transition
 *    width from passband to stopband.  Larger Beta means a slower
 *    transition and greater stopband rejection.  See Rabiner and Gold
 *    (Theory and Application of DSP) under Kaiser windows for more about
 *    Beta.  The following table from Rabiner and Gold gives some feel
 *    for the effect of Beta:
 *
 * All ripples in dB, width of transition band = D*N where N = window length
 *
 *               BETA    D       PB RIP   SB RIP
 *               2.120   1.50  +-0.27      -30
 *               3.384   2.23    0.0864    -40
 *               4.538   2.93    0.0274    -50
 *               5.658   3.62    0.00868   -60
 *               6.764   4.32    0.00275   -70
 *               7.865   5.0     0.000868  -80
 *               8.960   5.7     0.000275  -90
 *               10.056  6.4     0.000087  -100
 * </pre>
 */
public class FilterKit {

    // Max error acceptable in Izero
    private static final double IzeroEPSILON = 1E-21;

    private static double Izero(double x) {
        double sum, u, halfx, temp;
        int n;

        sum = u = n = 1;
        halfx = x / 2.0;
        do {
            temp = halfx / (double) n;
            n += 1;
            temp *= temp;
            u *= temp;
            sum += u;
        } while (u >= IzeroEPSILON * sum);
        return (sum);
    }

    public static void lrsLpFilter(double c[], int N, double frq, double Beta, int Num) {
        double IBeta, temp, temp1, inm1;
        int i;

        // Calculate ideal lowpass filter impulse response coefficients:
        c[0] = 2.0 * frq;
        for (i = 1; i < N; i++) {
            temp = Math.PI * (double) i / (double) Num;
            c[i] = Math.sin(2.0 * temp * frq) / temp; // Analog sinc function,
            // cutoff = frq
        }

        /*
         * Calculate and Apply Kaiser window to ideal lowpass filter. Note: last
         * window value is IBeta which is NOT zero. You're supposed to really
         * truncate the window here, not ramp it to zero. This helps reduce the
         * first sidelobe.
         */
        IBeta = 1.0 / Izero(Beta);
        inm1 = 1.0 / ((double) (N - 1));
        for (i = 1; i < N; i++) {
            temp = (double) i * inm1;
            temp1 = 1.0 - temp * temp;
            temp1 = (temp1 < 0 ? 0 : temp1); /*
                                              * make sure it's not negative
                                              * since we're taking the square
                                              * root - this happens on Pentium
                                              * 4's due to tiny roundoff errors
                                              */
            c[i] *= Izero(Beta * Math.sqrt(temp1)) * IBeta;
        }
    }

    /**
     * 
     * @param Imp impulse response
     * @param ImpD impulse response deltas
     * @param Nwing length of one wing of filter
     * @param Interp Interpolate coefs using deltas?
     * @param Xp_array Current sample array
     * @param Xp_index Current sample index
     * @param Ph Phase
     * @param Inc increment (1 for right wing or -1 for left)
     * @return
     */
    public static float lrsFilterUp(float Imp[], float ImpD[], int Nwing, boolean Interp, float[] Xp_array, int Xp_index, double Ph,
            int Inc) {
        double a = 0;
        float v, t;

        Ph *= Resampler.Npc; // Npc is number of values per 1/delta in impulse
        // response

        v = 0.0f; // The output value

        float[] Hp_array = Imp;
        int Hp_index = (int) Ph;

        float[] End_array = Imp;
        int End_index = Nwing;

        float[] Hdp_array = ImpD;
        int Hdp_index = (int) Ph;

        if (Interp) {
            // Hdp = &ImpD[(int)Ph];
            a = Ph - Math.floor(Ph); /* fractional part of Phase */
        }

        if (Inc == 1) // If doing right wing...
        { // ...drop extra coeff, so when Ph is
            End_index--; // 0.5, we don't do too many mult's
            if (Ph == 0) // If the phase is zero...
            { // ...then we've already skipped the
                Hp_index += Resampler.Npc; // first sample, so we must also
                Hdp_index += Resampler.Npc; // skip ahead in Imp[] and ImpD[]
            }
        }

        if (Interp)
            while (Hp_index < End_index) {
                t = Hp_array[Hp_index]; /* Get filter coeff */
                t += Hdp_array[Hdp_index] * a; /* t is now interp'd filter coeff */
                Hdp_index += Resampler.Npc; /* Filter coeff differences step */
                t *= Xp_array[Xp_index]; /* Mult coeff by input sample */
                v += t; /* The filter output */
                Hp_index += Resampler.Npc; /* Filter coeff step */
                Xp_index += Inc; /* Input signal step. NO CHECK ON BOUNDS */
            }
        else
            while (Hp_index < End_index) {
                t = Hp_array[Hp_index]; /* Get filter coeff */
                t *= Xp_array[Xp_index]; /* Mult coeff by input sample */
                v += t; /* The filter output */
                Hp_index += Resampler.Npc; /* Filter coeff step */
                Xp_index += Inc; /* Input signal step. NO CHECK ON BOUNDS */
            }

        return v;
    }

    /**
     * 
     * @param Imp impulse response
     * @param ImpD impulse response deltas
     * @param Nwing length of one wing of filter
     * @param Interp Interpolate coefs using deltas?
     * @param Xp_array Current sample array
     * @param Xp_index Current sample index
     * @param Ph Phase
     * @param Inc increment (1 for right wing or -1 for left)
     * @param dhb filter sampling period
     * @return
     */
    public static float lrsFilterUD(float Imp[], float ImpD[], int Nwing, boolean Interp, float[] Xp_array, int Xp_index, double Ph,
            int Inc, double dhb) {
        float a;
        float v, t;
        double Ho;

        v = 0.0f; // The output value
        Ho = Ph * dhb;

        float[] End_array = Imp;
        int End_index = Nwing;

        if (Inc == 1) // If doing right wing...
        { // ...drop extra coeff, so when Ph is
            End_index--; // 0.5, we don't do too many mult's
            if (Ph == 0) // If the phase is zero...
                Ho += dhb; // ...then we've already skipped the
        } // first sample, so we must also
        // skip ahead in Imp[] and ImpD[]

        float[] Hp_array = Imp;
        int Hp_index;

        if (Interp) {
            float[] Hdp_array = ImpD;
            int Hdp_index;

            while ((Hp_index = (int) Ho) < End_index) {
                t = Hp_array[Hp_index]; // Get IR sample
                Hdp_index = (int) Ho; // get interp bits from diff table
                a = (float) (Ho - Math.floor(Ho)); // a is logically between 0
                                                   // and 1
                t += Hdp_array[Hdp_index] * a; // t is now interp'd filter coeff
                t *= Xp_array[Xp_index]; // Mult coeff by input sample
                v += t; // The filter output
                Ho += dhb; // IR step
                Xp_index += Inc; // Input signal step. NO CHECK ON BOUNDS
            }
        } else {
            while ((Hp_index = (int) Ho) < End_index) {
                t = Hp_array[Hp_index]; // Get IR sample
                t *= Xp_array[Xp_index]; // Mult coeff by input sample
                v += t; // The filter output
                Ho += dhb; // IR step
                Xp_index += Inc; // Input signal step. NO CHECK ON BOUNDS
            }
        }

        return v;
    }

}


================================================
FILE: src/main/java/com/laszlosystems/libresample4j/Resampler.java
================================================
/******************************************************************************
 *
 * libresample4j
 * Copyright (c) 2009 Laszlo Systems, Inc. All Rights Reserved.
 *
 * libresample4j is a Java port of Dominic Mazzoni's libresample 0.1.3,
 * which is in turn based on Julius Smith's Resample 1.7 library.
 *      http://www-ccrma.stanford.edu/~jos/resample/
 *
 * License: LGPL -- see the file LICENSE.txt for more information
 *
 *****************************************************************************/
package com.laszlosystems.libresample4j;

import java.nio.FloatBuffer;

public class Resampler {

    public static class Result {
        public final int inputSamplesConsumed;
        public final int outputSamplesGenerated;

        public Result(int inputSamplesConsumed, int outputSamplesGenerated) {
            this.inputSamplesConsumed = inputSamplesConsumed;
            this.outputSamplesGenerated = outputSamplesGenerated;
        }
    }

    // number of values per 1/delta in impulse response
    protected static final int Npc = 4096;

    private final float[] Imp;
    private final float[] ImpD;
    private final float LpScl;
    private final int Nmult;
    private final int Nwing;
    private final double minFactor;
    private final double maxFactor;
    private final int XSize;
    private final float[] X;
    private int Xp; // Current "now"-sample pointer for input
    private int Xread; // Position to put new samples
    private final int Xoff;
    private final float[] Y;
    private int Yp;
    private double Time;

    public static double RollOff = 0.99; // johan
    public static double Beta = 6;

    /**
     * Clone an existing resampling session. Faster than creating one from scratch.
     *
     * @param other
     */
    public Resampler(Resampler other) {
        this.Imp = other.Imp.clone();
        this.ImpD = other.ImpD.clone();
        this.LpScl = other.LpScl;
        this.Nmult = other.Nmult;
        this.Nwing = other.Nwing;
        this.minFactor = other.minFactor;
        this.maxFactor = other.maxFactor;
        this.XSize = other.XSize;
        this.X = other.X.clone();
        this.Xp = other.Xp;
        this.Xread = other.Xread;
        this.Xoff = other.Xoff;
        this.Y = other.Y.clone();
        this.Yp = other.Yp;
        this.Time = other.Time;
    }

    /**
     * Create a new resampling session.
     *
     * @param highQuality true for better quality, slower processing time
     * @param minFactor   lower bound on resampling factor for this session
     * @param maxFactor   upper bound on resampling factor for this session
     * @throws IllegalArgumentException if minFactor or maxFactor is not
     *                                  positive, or if maxFactor is less than minFactor
     */
    public Resampler(boolean highQuality, double minFactor, double maxFactor) {
        if (minFactor <= 0.0 || maxFactor <= 0.0) {
            throw new IllegalArgumentException("minFactor and maxFactor must be positive");
        }
        if (maxFactor < minFactor) {
            throw new IllegalArgumentException("minFactor must be <= maxFactor");
        }

        this.minFactor = minFactor;
        this.maxFactor = maxFactor;
        this.Nmult = highQuality ? 35 : 11;
        this.LpScl = 1.0f;
        this.Nwing = Npc * (this.Nmult - 1) / 2; // # of filter coeffs in right wing

        double[] Imp64 = new double[this.Nwing];

        FilterKit.lrsLpFilter(Imp64, this.Nwing, 0.5 * RollOff, Beta, Npc);
        this.Imp = new float[this.Nwing];
        this.ImpD = new float[this.Nwing];

        for (int i = 0; i < this.Nwing; i++) {
            this.Imp[i] = (float) Imp64[i];
        }

        // Storing deltas in ImpD makes linear interpolation
        // of the filter coefficients faster
        for (int i = 0; i < this.Nwing - 1; i++) {
            this.ImpD[i] = this.Imp[i + 1] - this.Imp[i];
        }

        // Last coeff. not interpolated
        this.ImpD[this.Nwing - 1] = -this.Imp[this.Nwing - 1];

        // Calc reach of LP filter wing (plus some creeping room)
        int Xoff_min = (int) (((this.Nmult + 1) / 2.0) * Math.max(1.0, 1.0 / minFactor) + 10);
        int Xoff_max = (int) (((this.Nmult + 1) / 2.0) * Math.max(1.0, 1.0 / maxFactor) + 10);
        this.Xoff = Math.max(Xoff_min, Xoff_max);

        // Make the inBuffer size at least 4096, but larger if necessary
        // in order to store the minimum reach of the LP filter and then some.
        // Then allocate the buffer an extra Xoff larger so that
        // we can zero-pad up to Xoff zeros at the end when we reach the
        // end of the input samples.
        this.XSize = Math.max(2 * this.Xoff + 10, 4096);
        this.X = new float[this.XSize + this.Xoff];
        this.Xp = this.Xoff;
        this.Xread = this.Xoff;

        // Make the outBuffer long enough to hold the entire processed
        // output of one inBuffer
        int YSize = (int) (((double) this.XSize) * maxFactor + 2.0);
        this.Y = new float[YSize];
        this.Yp = 0;

        this.Time = (double) this.Xoff; // Current-time pointer for converter
    }

    public int getFilterWidth() {
        return this.Xoff;
    }

    /**
     * Process a batch of samples. There is no guarantee that the input buffer will be drained.
     *
     * @param factor    factor at which to resample this batch
     * @param buffers   sample buffer for producing input and consuming output
     * @param lastBatch true if this is known to be the last batch of samples
     * @return true iff resampling is complete (ie. no input samples consumed and no output samples produced)
     */
    public boolean process(double factor, SampleBuffers buffers, boolean lastBatch) {
        if (factor < this.minFactor || factor > this.maxFactor) {
            throw new IllegalArgumentException("factor " + factor + " is not between minFactor=" + minFactor
                    + " and maxFactor=" + maxFactor);
        }

        int outBufferLen = buffers.getOutputBufferLength();
        int inBufferLen = buffers.getInputBufferLength();

        float[] Imp = this.Imp;
        float[] ImpD = this.ImpD;
        float LpScl = this.LpScl;
        int Nwing = this.Nwing;
        boolean interpFilt = false; // TRUE means interpolate filter coeffs

        int inBufferUsed = 0;
        int outSampleCount = 0;

        // Start by copying any samples still in the Y buffer to the output
        // buffer
        if ((this.Yp != 0) && (outBufferLen - outSampleCount) > 0) {
            int len = Math.min(outBufferLen - outSampleCount, this.Yp);

            buffers.consumeOutput(this.Y, 0, len);
            //for (int i = 0; i < len; i++) {
            //    outBuffer[outBufferOffset + outSampleCount + i] = this.Y[i];
            //}

            outSampleCount += len;
            for (int i = 0; i < this.Yp - len; i++) {
                this.Y[i] = this.Y[i + len];
            }
            this.Yp -= len;
        }

        // If there are still output samples left, return now - we need
        // the full output buffer available to us...
        if (this.Yp != 0) {
            return inBufferUsed == 0 && outSampleCount == 0;
        }

        // Account for increased filter gain when using factors less than 1
        if (factor < 1) {
            LpScl = (float) (LpScl * factor);
        }

        while (true) {

            // This is the maximum number of samples we can process
            // per loop iteration

            /*
             * #ifdef DEBUG
             * printf("XSize: %d Xoff: %d Xread: %d Xp: %d lastFlag: %d\n",
             * this.XSize, this.Xoff, this.Xread, this.Xp, lastFlag); #endif
             */

            // Copy as many samples as we can from the input buffer into X
            int len = this.XSize - this.Xread;

            if (len >= inBufferLen - inBufferUsed) {
                len = inBufferLen - inBufferUsed;
            }

            buffers.produceInput(this.X, this.Xread, len);
            //for (int i = 0; i < len; i++) {
            //    this.X[this.Xread + i] = inBuffer[inBufferOffset + inBufferUsed + i];
            //}

            inBufferUsed += len;
            this.Xread += len;

            int Nx;
            if (lastBatch && (inBufferUsed == inBufferLen)) {
                // If these are the last samples, zero-pad the
                // end of the input buffer and make sure we process
                // all the way to the end
                Nx = this.Xread - this.Xoff;
                for (int i = 0; i < this.Xoff; i++) {
                    this.X[this.Xread + i] = 0;
                }
            } else {
                Nx = this.Xread - 2 * this.Xoff;
            }

            /*
             * #ifdef DEBUG fprintf(stderr, "new len=%d Nx=%d\n", len, Nx);
             * #endif
             */

            if (Nx <= 0) {
                break;
            }

            // Resample stuff in input buffer
            int Nout;
            if (factor >= 1) { // SrcUp() is faster if we can use it */
                Nout = lrsSrcUp(this.X, this.Y, factor, /* &this.Time, */Nx, Nwing, LpScl, Imp, ImpD, interpFilt);
            } else {
                Nout = lrsSrcUD(this.X, this.Y, factor, /* &this.Time, */Nx, Nwing, LpScl, Imp, ImpD, interpFilt);
            }

            /*
             * #ifdef DEBUG
             * printf("Nout: %d\n", Nout);
             * #endif
             */

            this.Time -= Nx; // Move converter Nx samples back in time
            this.Xp += Nx; // Advance by number of samples processed

            // Calc time accumulation in Time
            int Ncreep = (int) (this.Time) - this.Xoff;
            if (Ncreep != 0) {
                this.Time -= Ncreep; // Remove time accumulation
                this.Xp += Ncreep; // and add it to read pointer
            }

            // Copy part of input signal that must be re-used
            int Nreuse = this.Xread - (this.Xp - this.Xoff);

            for (int i = 0; i < Nreuse; i++) {
                this.X[i] = this.X[i + (this.Xp - this.Xoff)];
            }

            /*
            #ifdef DEBUG
            printf("New Xread=%d\n", Nreuse);
            #endif */

            this.Xread = Nreuse; // Pos in input buff to read new data into
            this.Xp = this.Xoff;

            this.Yp = Nout;

            // Copy as many samples as possible to the output buffer
            if (this.Yp != 0 && (outBufferLen - outSampleCount) > 0) {
                len = Math.min(outBufferLen - outSampleCount, this.Yp);

                buffers.consumeOutput(this.Y, 0, len);
                //for (int i = 0; i < len; i++) {
                //    outBuffer[outBufferOffset + outSampleCount + i] = this.Y[i];
                //}

                outSampleCount += len;
                for (int i = 0; i < this.Yp - len; i++) {
                    this.Y[i] = this.Y[i + len];
                }
                this.Yp -= len;
            }

            // If there are still output samples left, return now,
            //   since we need the full output buffer available
            if (this.Yp != 0) {
                break;
            }
        }

        return inBufferUsed == 0 && outSampleCount == 0;
    }

    /**
     * Process a batch of samples. Convenience method for when the input and output are both floats.
     *
     * @param factor       factor at which to resample this batch
     * @param inputBuffer  contains input samples in the range -1.0 to 1.0
     * @param outputBuffer output samples will be deposited here
     * @param lastBatch    true if this is known to be the last batch of samples
     * @return true iff resampling is complete (ie. no input samples consumed and no output samples produced)
     */
    public boolean process(double factor, final FloatBuffer inputBuffer, boolean lastBatch, final FloatBuffer outputBuffer) {
        SampleBuffers sampleBuffers = new SampleBuffers() {
            public int getInputBufferLength() {
                return inputBuffer.remaining();
            }

            public int getOutputBufferLength() {
                return outputBuffer.remaining();
            }

            public void produceInput(float[] array, int offset, int length) {
                inputBuffer.get(array, offset, length);
            }

            public void consumeOutput(float[] array, int offset, int length) {
                outputBuffer.put(array, offset, length);
            }
        };
        return process(factor, sampleBuffers, lastBatch);
    }

    /**
     * Process a batch of samples. Alternative interface if you prefer to work with arrays.
     *
     * @param factor         resampling rate for this batch
     * @param inBuffer       array containing input samples in the range -1.0 to 1.0
     * @param inBufferOffset offset into inBuffer at which to start processing
     * @param inBufferLen    number of valid elements in the inputBuffer
     * @param lastBatch      pass true if this is the last batch of samples
     * @param outBuffer      array to hold the resampled data
     * @return the number of samples consumed and generated
     */
    public Result process(double factor, float[] inBuffer, int inBufferOffset, int inBufferLen, boolean lastBatch, float[] outBuffer, int outBufferOffset, int outBufferLen) {
        FloatBuffer inputBuffer = FloatBuffer.wrap(inBuffer, inBufferOffset, inBufferLen);
        FloatBuffer outputBuffer = FloatBuffer.wrap(outBuffer, outBufferOffset, outBufferLen);

        process(factor, inputBuffer, lastBatch, outputBuffer);

        return new Result(inputBuffer.position() - inBufferOffset, outputBuffer.position() - outBufferOffset);
    }



    /*
     * Sampling rate up-conversion only subroutine; Slightly faster than
     * down-conversion;
     */
    private int lrsSrcUp(float X[], float Y[], double factor, int Nx, int Nwing, float LpScl, float Imp[],
                         float ImpD[], boolean Interp) {

        float[] Xp_array = X;
        int Xp_index;

        float[] Yp_array = Y;
        int Yp_index = 0;

        float v;

        double CurrentTime = this.Time;
        double dt; // Step through input signal
        double endTime; // When Time reaches EndTime, return to user

        dt = 1.0 / factor; // Output sampling period

        endTime = CurrentTime + Nx;
        while (CurrentTime < endTime) {
            double LeftPhase = CurrentTime - Math.floor(CurrentTime);
            double RightPhase = 1.0 - LeftPhase;

            Xp_index = (int) CurrentTime; // Ptr to current input sample
            // Perform left-wing inner product
            v = FilterKit.lrsFilterUp(Imp, ImpD, Nwing, Interp, Xp_array, Xp_index++, LeftPhase, -1);
            // Perform right-wing inner product
            v += FilterKit.lrsFilterUp(Imp, ImpD, Nwing, Interp, Xp_array, Xp_index, RightPhase, 1);

            v *= LpScl; // Normalize for unity filter gain

            Yp_array[Yp_index++] = v; // Deposit output
            CurrentTime += dt; // Move to next sample by time increment
        }

        this.Time = CurrentTime;
        return Yp_index; // Return the number of output samples
    }

    private int lrsSrcUD(float X[], float Y[], double factor, int Nx, int Nwing, float LpScl, float Imp[],
                         float ImpD[], boolean Interp) {

        float[] Xp_array = X;
        int Xp_index;

        float[] Yp_array = Y;
        int Yp_index = 0;

        float v;

        double CurrentTime = this.Time;
        double dh; // Step through filter impulse response
        double dt; // Step through input signal
        double endTime; // When Time reaches EndTime, return to user

        dt = 1.0 / factor; // Output sampling period

        dh = Math.min(Npc, factor * Npc); // Filter sampling period

        endTime = CurrentTime + Nx;
        while (CurrentTime < endTime) {
            double LeftPhase = CurrentTime - Math.floor(CurrentTime);
            double RightPhase = 1.0 - LeftPhase;

            Xp_index = (int) CurrentTime; // Ptr to current input sample
            // Perform left-wing inner product
            v = FilterKit.lrsFilterUD(Imp, ImpD, Nwing, Interp, Xp_array, Xp_index++, LeftPhase, -1, dh);
            // Perform right-wing inner product
            v += FilterKit.lrsFilterUD(Imp, ImpD, Nwing, Interp, Xp_array, Xp_index, RightPhase, 1, dh);

            v *= LpScl; // Normalize for unity filter gain

            Yp_array[Yp_index++] = v; // Deposit output

            CurrentTime += dt; // Move to next sample by time increment
        }

        this.Time = CurrentTime;
        return Yp_index; // Return the number of output samples
    }

}


================================================
FILE: src/main/java/com/laszlosystems/libresample4j/SampleBuffers.java
================================================
/******************************************************************************
 *
 * libresample4j
 * Copyright (c) 2009 Laszlo Systems, Inc. All Rights Reserved.
 *
 * libresample4j is a Java port of Dominic Mazzoni's libresample 0.1.3,
 * which is in turn based on Julius Smith's Resample 1.7 library.
 *      http://www-ccrma.stanford.edu/~jos/resample/
 *
 * License: LGPL -- see the file LICENSE.txt for more information
 *
 *****************************************************************************/
package com.laszlosystems.libresample4j;

/**
 * Callback for producing and consuming samples. Enables on-the-fly conversion between sample types
 * (signed 16-bit integers to floats, for example) and/or writing directly to an output stream.
 */
public interface SampleBuffers {
    /**
     * @return number of input samples available
     */

    int getInputBufferLength();

    /**
     * @return number of samples the output buffer has room for
     */
    int getOutputBufferLength();

    /**
     * Copy <code>length</code> samples from the input buffer to the given array, starting at the given offset.
     * Samples should be in the range -1.0f to 1.0f.
     *
     * @param array  array to hold samples from the input buffer
     * @param offset start writing samples here
     * @param length write this many samples
     */
    void produceInput(float[] array, int offset, int length);

    /**
     * Copy <code>length</code> samples from the given array to the output buffer, starting at the given offset.
     *
     * @param array  array to read from
     * @param offset start reading samples here
     * @param length read this many samples
     */
    void consumeOutput(float[] array, int offset, int length);
}


================================================
FILE: src/main/java/fontEditor/.gitignore
================================================
/ChangeEventListener.class
/FontEditor$1.class
/FontEditor$2.class
/FontEditor$3.class
/FontEditor$4.class
/FontEditor$5.class
/FontEditor$6.class
/FontEditor$7.class
/FontEditor$8.class
/FontEditor.class
/FontEditorColorSelector.class
/FontMap$TileSelectListener.class
/FontMap.class
/TileEditor$TileChangedListener.class
/TileEditor.class
/FontEditor$9.class
/ChangeEventMouseSide.class
/ChangeEventListener$ChangeEventMouseSide.class
/FontEditorColorSelector$FontEditorColorListener.class


================================================
FILE: src/main/java/fontEditor/ChangeEventListener.java
================================================
package fontEditor;

abstract class ChangeEventListener {
    public enum ChangeEventMouseSide {
        LEFT,
        RIGHT
    }

    public abstract void onChange(int color, ChangeEventMouseSide side);
}



================================================
FILE: src/main/java/fontEditor/FontEditor.java
================================================
package fontEditor;

import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.*;

import Document.Document;
import structures.LSDJFont;
import utils.FileDialogLauncher;
import utils.FontIO;
import utils.RomUtilities;

public class FontEditor extends JFrame implements FontMap.TileSelectListener, TileEditor.TileChangedListener {

    private static final long serialVersionUID = 5296681614787155252L;

    private final JCheckBox displayGfxCharacters = new JCheckBox();
    private final FontMap fontMap;
    private final TileEditor tileEditor;

    private final JComboBox<String> fontSelector;

    private byte[] romImage = null;
    private int fontOffset = -1;
    private int selectedFontOffset = -1;
    private int previousSelectedFont = -1;

    public FontEditor(JFrame parent, Document document) {
        parent.setEnabled(false);

        setTitle("Font Editor");
        setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
        setBounds(100, 100, 800, 600);
        setResizable(true);
        GridBagConstraints constraints = new GridBagConstraints();

        JMenuBar menuBar = new JMenuBar();
        setJMenuBar(menuBar);

        createFileMenu(menuBar);

        createEditMenu(menuBar);

        GridBagLayout layout = new GridBagLayout();
        JPanel contentPane = new JPanel();
        contentPane.setLayout(layout);
        setContentPane(contentPane);

        tileEditor = new TileEditor();
        tileEditor.setMinimumSize(new Dimension(240, 240));
        tileEditor.setPreferredSize(new Dimension(240, 240));
        tileEditor.setTileChangedListener(this);
        constraints.fill = GridBagConstraints.BOTH;
        constraints.gridx = 3;
        constraints.gridy = 0;
        constraints.weightx = 1;
        constraints.weighty = 1;
        constraints.gridheight = 6;
        contentPane.add(tileEditor, constraints);

        fontSelector = new JComboBox<>();
        fontSelector.setEditable(true);
        // TODO is there a way to remove the action listener implementation from this class?
        fontSelector.addItemListener(this::fontSelectorItemChanged);
        fontSelector.addActionListener(this::fontSelectorAction);
        constraints.fill = GridBagConstraints.HORIZONTAL;
        constraints.gridx = 0;
        constraints.gridy = 1;
        constraints.weightx = 0;
        constraints.weighty = 0;
        constraints.gridheight = 1;
        constraints.gridwidth = 3;
        contentPane.add(fontSelector, constraints);


        JPanel colorPanel = new JPanel();
        colorPanel.setLayout(new BoxLayout(colorPanel, BoxLayout.X_AXIS));
        FontEditorColorSelector colorSelector = new FontEditorColorSelector(colorPanel);
        colorSelector.addChangeEventListener(new ChangeEventListener() {
            @Override
            public void onChange(int color, ChangeEventMouseSide side) {
                if (side == ChangeEventMouseSide.LEFT)
                    setColor(color);
                else
                    setRightColor(color);
            }
        });

        setColor(1);
        constraints.fill = GridBagConstraints.HORIZONTAL;
        constraints.gridx = 0;
        constraints.gridy = 2;
        constraints.gridwidth = 3;
        constraints.gridheight = 1;
        contentPane.add(colorPanel, constraints);

        JPanel shiftButtonPanel = new JPanel();
        shiftButtonPanel.setLayout(new BoxLayout(shiftButtonPanel, BoxLayout.X_AXIS));

        addImageButtonToPanel(shiftButtonPanel, "/shift_up.png", "Rotate up", e -> tileEditor.shiftUp(tileEditor.getTile()));
        addImageButtonToPanel(shiftButtonPanel, "/shift_down.png", "Rotate down", e -> tileEditor.shiftDown(tileEditor.getTile()));
        addImageButtonToPanel(shiftButtonPanel, "/shift_left.png", "Rotate left", e -> tileEditor.shiftLeft(tileEditor.getTile()));
        addImageButtonToPanel(shiftButtonPanel, "/shift_right.png", "Rotate right", e -> tileEditor.shiftRight(tileEditor.getTile()));

        constraints.gridx = 0;
        constraints.gridy = 3;
        constraints.gridwidth = 3;
        constraints.gridheight = 1;
        constraints.fill = GridBagConstraints.NONE;
        contentPane.add(shiftButtonPanel, constraints);

        displayGfxCharacters.setText("Show graphics characters");
        displayGfxCharacters.setToolTipText("Changes made to graphics characters will apply to all fonts.");
        constraints.fill = GridBagConstraints.BOTH;
        constraints.gridx = 0;
        constraints.gridy = 4;
        constraints.weightx = 0;
        constraints.weighty = 0;
        constraints.gridwidth = 3;
        constraints.gridheight = 1;
        contentPane.add(displayGfxCharacters, constraints);

        fontMap = new FontMap();
        fontMap.setMinimumSize(new Dimension(128, 16 * 8 * 2));
        fontMap.setPreferredSize(new Dimension(128, 16 * 8 * 2));
        fontMap.setTileSelectListener(this);
        constraints.fill = GridBagConstraints.BOTH;
        constraints.gridx = 0;
        constraints.gridy = 5;
        constraints.ipadx = 0;
        constraints.ipady = 0;
        constraints.weightx = 0;
        constraints.weighty = 1;
        constraints.gridwidth = 3;
        constraints.gridheight = 1;
        contentPane.add(fontMap, constraints);

        setMinimumSize(layout.preferredLayoutSize(contentPane));
        pack();

        setRomImage(document.romImage());

        displayGfxCharacters.addActionListener(e -> {
            fontMap.setShowGfxCharacters(displayGfxCharacters.isSelected());
            if (!displayGfxCharacters.isSelected()) {
                tileSelected(0);
            }
        });

        addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                super.windowClosing(e);
                document.setRomImage(fontMap.romImage());
                parent.setEnabled(true);
            }
        });
    }

    private void addImageButtonToPanel(JPanel panel, String imagePath, String altText, ActionListener event) {
        BufferedImage buttonImage = loadImage(imagePath);
        JButton button = new JButton();
        setUpButtonIconOrText(button, buttonImage, altText);
        button.addActionListener(event);
        panel.add(button);
    }

    private void addMenuEntry(JMenu destination, String name, int key, ActionListener event) {
        JMenuItem newMenuEntry = new JMenuItem(name);
        newMenuEntry.setMnemonic(key);
        newMenuEntry.addActionListener(event);
        destination.add(newMenuEntry);
    }

    private void createFileMenu(JMenuBar menuBar) {
        JMenu fileMenu = new JMenu("File");
        fileMenu.setMnemonic(KeyEvent.VK_F);
        menuBar.add(fileMenu);

        addMenuEntry(fileMenu, "Load font...", KeyEvent.VK_L, e -> loadFont());
        addMenuEntry(fileMenu, "Save font...", KeyEvent.VK_S, e -> saveFont());
    }

    private void createEditMenu(JMenuBar menuBar) {
        JMenu editMenu = new JMenu("Edit");
        editMenu.setMnemonic(KeyEvent.VK_E);
        menuBar.add(editMenu);

        addMenuEntry(editMenu, "Copy Tile", KeyEvent.VK_C, e -> tileEditor.copyTile());
        addMenuEntry(editMenu, "Paste Tile", KeyEvent.VK_V, e -> tileEditor.pasteTile());
    }

    private BufferedImage loadImage(String iconPath) {
        try {
            return javax.imageio.ImageIO.read(getClass().getResource(iconPath));
        } catch (IOException e1) {
            e1.printStackTrace();
        }
        return null;
    }

    private void setUpButtonIconOrText(JButton button, BufferedImage image, String altText) {
        if (image != null)
            button.setIcon(new ImageIcon(image));
        else
            button.setText(altText);
    }

    private String getFontName(int i) {
        return RomUtilities.getFontName(romImage, i);
    }

    private void populateFontSelector() {
        fontSelector.removeAllItems();

        for (int i = 0; i < LSDJFont.FONT_COUNT; ++i) {
            char[] name = getFontName(i).toCharArray();

            // Avoids duplicate names by appending incrementing number to duplicates.
            for (int j = 0; j < i; ++j) {
                if (!getFontName(j).equals(new String(name))) {
                    continue;
                }
                char lastChar = name[name.length - 1];
                if (Character.isDigit(lastChar)) {
                    ++lastChar;
                } else {
                    lastChar = '1';
                }
                name[name.length - 1] = lastChar;
                RomUtilities.setFontName(romImage, i, new String(name));
            }

            fontSelector.addItem(new String(name));
        }
    }

    public void setRomImage(byte[] romImage) {
        this.romImage = romImage;
        fontMap.setRomImage(romImage);
        fontMap.setGfxCharOffset(RomUtilities.findGfxFontOffset(romImage));
        tileEditor.setRomImage(romImage);
        tileEditor.setGfxDataOffset(RomUtilities.findGfxFontOffset(romImage));

        fontOffset = RomUtilities.findFontOffset(romImage);
        if (fontOffset == -1) {
            System.err.println("Could not find font offset!");
        }
        int nameOffset = RomUtilities.findFontNameOffset(romImage);
        if (nameOffset == -1) {
            System.err.println("Could not find font name offset!");
        }
        populateFontSelector();
    }

    private void fontSelectorItemChanged(java.awt.event.ItemEvent e) {
        if (e.getStateChange() == java.awt.event.ItemEvent.SELECTED) {
            if (e.getItemSelectable() == fontSelector) {
                // Font changed.
                int index = fontSelector.getSelectedIndex();
                if (index != -1) {
                    previousSelectedFont = index;
                    index = (index + 1) % 3; // Adjusts for fonts being defined in wrong order.
                    selectedFontOffset = fontOffset + index * LSDJFont.FONT_SIZE + LSDJFont.FONT_HEADER_SIZE;
                    fontMap.setFontOffset(selectedFontOffset);
                    tileEditor.setFontOffset(selectedFontOffset);
                }
            }
        }
    }

    private void setColor(int color) {
        assert color >= 1 && color <= 3;
        tileEditor.setColor(color);
    }

    private void setRightColor(int color) {
        assert color >= 1 && color <= 3;
        tileEditor.setRightColor(color);
    }

    public void tileSelected(int tile) {
        tileEditor.setTile(tile);
    }

    public void tileChanged() {
        fontMap.repaint();
    }

    private void fontSelectorAction(java.awt.event.ActionEvent e) {
        switch (e.getActionCommand()) {
            case "comboBoxChanged":
                if (fontSelector.getSelectedIndex() != -1) {
                    previousSelectedFont = fontSelector.getSelectedIndex();
                }
                break;
            case "comboBoxEdited":
                String selectedItem = (String) fontSelector.getSelectedItem();
                if (fontSelector.getSelectedIndex() == -1 && selectedItem != null) {
                    int index = previousSelectedFont;
                    RomUtilities.setFontName(romImage, index, selectedItem);
                    populateFontSelector();
                    fontSelector.setSelectedIndex(index);
                    fontSelector.setSelectedIndex(index);
                } else {
                    previousSelectedFont = fontSelector.getSelectedIndex();
                }
                break;
        }
    }

    private void loadFont() {
        try {
            File f = FileDialogLauncher.load(this, "Open Font", new String[]{ "png", "lsdfnt" });
            if (f == null) {
                return;
            }

            if (f.getName().endsWith("png")) {
                importBitmap(f);
                String fontName = f.getName().replaceFirst(".png$", "").toUpperCase();
                RomUtilities.setFontName(romImage, fontSelector.getSelectedIndex(), fontName);
            } else {
                String fontName = FontIO.loadFnt(f, romImage, selectedFontOffset);
                tileEditor.generateShadedAndInvertedTiles();
                RomUtilities.setFontName(romImage, fontSelector.getSelectedIndex(), fontName);
                tileEditor.tileChanged();
                tileChanged();
            }
            // Refresh the name list.
            int previousIndex = fontSelector.getSelectedIndex();
            populateFontSelector();
            fontSelector.setSelectedIndex(previousIndex);
        } catch (IOException e) {
            JOptionPane.showMessageDialog(this, "Couldn't open fnt file.\n" + e.getMessage());
            e.printStackTrace();
        }
    }

    private void importBitmap(File bitmap) {
        try {
            BufferedImage image = ImageIO.read(bitmap);
            if (image.getWidth() != 64 && image.getHeight() != 72) {
                JOptionPane.showMessageDialog(this,
                        "Make sure your picture has the right dimensions (64 * 72 pixels).");
                return;
            }
            tileEditor.readImage(bitmap.getName(), image);
            tileEditor.tileChanged();
            tileChanged();
        } catch (IOException e) {
            JOptionPane.showMessageDialog(this, "Couldn't load the given picture.\n" + e.getMessage());
            e.printStackTrace();
        }
    }

    private void saveFont() {
        File f = FileDialogLauncher.save(this, "Export Font", "png");
        if (f == null) {
            return;
        }
        BufferedImage image = tileEditor.createImage(displayGfxCharacters.isSelected());
        try {
            ImageIO.write(image, "PNG", f);
        } catch (IOException e) {
            JOptionPane.showMessageDialog(this, "Couldn't export the font map.\n" + e.getMessage());
            e.printStackTrace();
        }
    }
}


================================================
FILE: src/main/java/fontEditor/FontEditorColorSelector.java
================================================
package fontEditor;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;

import javax.swing.JPanel;
import javax.swing.SwingUtilities;

import fontEditor.ChangeEventListener.ChangeEventMouseSide;

class FontEditorColorSelector {

    private final JPanel foregroundColorIndicator;
    private final JPanel backgroundColorIndicator;

    private final ArrayList<ChangeEventListener> listeners;

    private static class FontEditorColorListener implements MouseListener {
        final FontEditorColorSelector selector;
        final int color;

        FontEditorColorListener(FontEditorColorSelector selector, int color) {
            this.selector = selector;
            this.color = color;
        }

        @Override
        public void mouseClicked(MouseEvent e) {
        }

        @Override
        public void mouseEntered(MouseEvent e) {
        }

        @Override
        public void mouseExited(MouseEvent e) {
        }

        @Override
        public void mousePressed(MouseEvent e) {
            if (SwingUtilities.isRightMouseButton(e))
                selector.sendEvent(color, ChangeEventMouseSide.RIGHT);
            if (SwingUtilities.isLeftMouseButton(e))
                selector.sendEvent(color, ChangeEventMouseSide.LEFT);

        }

        @Override
        public void mouseReleased(MouseEvent e) {
        }
    }

    public FontEditorColorSelector(JPanel buttonPanel) {
        JPanel darkButton = new JPanel();
        darkButton.setBackground(Color.BLACK);
        darkButton.setForeground(Color.BLACK);
        darkButton.addMouseListener(new FontEditorColorListener(this, 3));

        JPanel mediumButton = new JPanel();
        mediumButton.setBackground(Color.LIGHT_GRAY);
        mediumButton.addMouseListener(new FontEditorColorListener(this, 2));

        JPanel lightButton = new JPanel();
        lightButton.setBackground(Color.WHITE);
        lightButton.addMouseListener(new FontEditorColorListener(this, 1));

        listeners = new ArrayList<>();

        JPanel indicatorContainer = new JPanel();
        indicatorContainer.setLayout(null);

        foregroundColorIndicator = new JPanel();
        foregroundColorIndicator.setBackground(Color.WHITE);
        foregroundColorIndicator.setForeground(Color.WHITE);
        foregroundColorIndicator.setPreferredSize(new Dimension(24, 24));
        foregroundColorIndicator.setBounds(8, 8, 24, 24);
        indicatorContainer.add(foregroundColorIndicator);

        backgroundColorIndicator = new JPanel();
        backgroundColorIndicator.setBackground(Color.BLACK);
        backgroundColorIndicator.setForeground(Color.BLACK);
        backgroundColorIndicator.setPreferredSize(new Dimension(24, 24));
        backgroundColorIndicator.setBounds(0, 0, 24, 24);
        indicatorContainer.add(backgroundColorIndicator);


        buttonPanel.add(indicatorContainer);
        buttonPanel.add(darkButton);
        buttonPanel.add(mediumButton);
        buttonPanel.add(lightButton);

        buttonPanel.setPreferredSize(new Dimension(200, 32));

    }

    private void sendEvent(int color, ChangeEventMouseSide side) {
        Color buttonColor = Color.RED;
        switch (color) {
            case 1:
                buttonColor = Color.WHITE;
                break;
            case 2:
                buttonColor = Color.LIGHT_GRAY;
                break;
            case 3:
                buttonColor = Color.BLACK;
                break;
        }

        for (ChangeEventListener listener : listeners) {
            listener.onChange(color, side);
        }
        if (side == ChangeEventMouseSide.LEFT)
            foregroundColorIndicator.setBackground(buttonColor);
        else
            backgroundColorIndicator.setBackground(buttonColor);
    }

    void addChangeEventListener(ChangeEventListener changeEventListener) {
        listeners.add(changeEventListener);
    }

}


================================================
FILE: src/main/java/fontEditor/FontMap.java
================================================
package fontEditor;

import java.awt.*;

import javax.swing.JPanel;

import structures.LSDJFont;

public class FontMap extends JPanel implements java.awt.event.MouseListener {
    private static final long serialVersionUID = -7745908775698863845L;
    private byte[] romImage = null;
    private int fontOffset = -1;
    private int gfxCharOffset = -1;
    private int tileZoom = 1;
    private int displayTileSize = 8;
    private boolean showGfxCharacters = false;

    public interface TileSelectListener {
        void tileSelected(int tile);
    }

    private TileSelectListener tileSelectedListener = null;

    FontMap() {
        addMouseListener(this);
    }

    void setShowGfxCharacters(boolean show) {
        showGfxCharacters = show;
        repaint();
    }

    void setTileSelectListener(TileSelectListener l) {
        tileSelectedListener = l;
    }

    int getCurrentUnscaledMapHeight () {
        return showGfxCharacters ? LSDJFont.GFX_FONT_MAP_HEIGHT : LSDJFont.FONT_MAP_HEIGHT;
    }

    int getCurrentTileNumber () {
        return showGfxCharacters ? LSDJFont.GFX_TILE_COUNT + LSDJFont.TILE_COUNT : LSDJFont.TILE_COUNT;
    }

    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        int currentHeight = getCurrentUnscaledMapHeight();

        int widthScale = getWidth() / LSDJFont.FONT_MAP_WIDTH;
        int heightScale = getHeight() / currentHeight;
        tileZoom = Math.min(widthScale, heightScale);
        tileZoom = Math.max(tileZoom, 1);
        int offsetX = (getWidth() - LSDJFont.FONT_MAP_WIDTH * tileZoom) / 2;
        int offsetY = (getHeight() - currentHeight * tileZoom) / 2;
        setPreferredSize(new Dimension(LSDJFont.FONT_MAP_WIDTH * tileZoom, currentHeight * tileZoom));

        for (int tile = 0; tile < tileCount(); ++tile) {
            paintTile(g, tile, offsetX, offsetY);
        }
    }

    private int tileCount() {
        return showGfxCharacters
                ? LSDJFont.GFX_TILE_COUNT + LSDJFont.TILE_COUNT
                : LSDJFont.TILE_COUNT;
    }

    private void switchColor(Graphics g, int c) {
        switch (c & 3) {
            case 0:
                g.setColor(Color.white);
                break;
            case 1:
                g.setColor(Color.lightGray);
                break;
            case 2:
                g.setColor(Color.darkGray);  // Not used.
                break;
            case 3:
                g.setColor(Color.black);
                break;
        }
    }

    private int getColor(int tile, int x, int y) {
        int tileOffset;
        if (tile < LSDJFont.TILE_COUNT) {
            tileOffset = fontOffset;
        } else {
            tileOffset = gfxCharOffset;
            tile -= LSDJFont.TILE_COUNT;
        }
        tileOffset += tile * 16 + y * 2;
        int xMask = 7 - x;
        int value = (romImage[tileOffset] >> xMask) & 1;
        value |= ((romImage[tileOffset + 1] >> xMask) & 1) << 1;
        return value;
    }

    private void paintTile(Graphics g, int tile, int offsetX, int offsetY) {
        displayTileSize = 8 * tileZoom;
        int x = (tile % 8) * displayTileSize;
        int y = (tile / 8) * displayTileSize;

        for (int row = 0; row < 8; ++row) {
            for (int column = 0; column < 8; ++column) {
                switchColor(g, getColor(tile, column, row));
                g.fillRect(offsetX + x + column * tileZoom, offsetY + y + row * tileZoom, tileZoom, tileZoom);
            }
        }
        if (tileZoom > 1) {
            g.setColor(new Color(0.f,0.f,0.4f,0.6f));
            g.drawRect(offsetX + x, offsetY + y, tileZoom*8, tileZoom*8);
        }
    }

    void setRomImage(byte[] romImage) {
        this.romImage = romImage;
    }

    public byte[] romImage() {
        return romImage;
    }

    void setGfxCharOffset(int gfxCharOffset) {
        this.gfxCharOffset = gfxCharOffset;
    }

    void setFontOffset(int fontOffset) {
        this.fontOffset = fontOffset;
        repaint();
    }

    public void mouseEntered(java.awt.event.MouseEvent e) {
    }

    public void mouseExited(java.awt.event.MouseEvent e) {
    }

    public void mouseReleased(java.awt.event.MouseEvent e) {
    }

    public void mousePressed(java.awt.event.MouseEvent e) {
    }

    public void mouseClicked(java.awt.event.MouseEvent e) {
        int offsetX = (getWidth() - LSDJFont.FONT_MAP_WIDTH * tileZoom) / 2;
        int offsetY = (getHeight() - getCurrentUnscaledMapHeight() * tileZoom) / 2;

        int realX = e.getX() - offsetX;
        int realY = e.getY() - offsetY;

        if (realX < 0 || realY < 0  || realX > LSDJFont.FONT_MAP_WIDTH * tileZoom || realY > getCurrentUnscaledMapHeight() * tileZoom)
            return;

        int tile = (realY / displayTileSize) * LSDJFont.FONT_NUM_TILES_X +
                realX / displayTileSize;
        if (tile < 0 || tile >= getCurrentTileNumber())
            return;

        if (tileSelectedListener != null) {
            tileSelectedListener.tileSelected(tile);
        }
    }
}


================================================
FILE: src/main/java/fontEditor/TileEditor.java
================================================
package fontEditor;

import java.awt.*;
import java.awt.image.BufferedImage;

import javax.swing.JPanel;
import javax.swing.SwingUtilities;

import structures.LSDJFont;

class TileEditor extends JPanel implements java.awt.event.MouseListener, java.awt.event.MouseMotionListener {

    private static final long serialVersionUID = 4048727729255703626L;

    public interface TileChangedListener {
        void tileChanged();
    }

    private final LSDJFont font;
    private int selectedTile = 0;
    private int color = 3;
    private int rightColor = 3;

    private int[][] clipboard = null;

    private TileChangedListener tileChangedListener;

    TileEditor() {
        font = new LSDJFont();
        addMouseListener(this);
        addMouseMotionListener(this);
    }

    void setRomImage(byte[] romImage) {
        font.setRomImage(romImage);
    }

    void setFontOffset(int offset) {
        font.setDataOffset(offset);
        repaint();
    }

    void setGfxDataOffset(int offset) {
        font.setGfxDataOffset(offset);
        repaint();
    }

    void setTile(int tile) {
        selectedTile = tile;
        repaint();
    }

    int getTile() {
        return selectedTile;
    }

    void shiftUp(int tile) {
        font.rotateTileUp(tile);
        tileChanged();
    }

    void shiftDown(int tile) {
        font.rotateTileDown(tile);
        tileChanged();
    }

    void shiftRight(int tile) {
        font.rotateTileRight(tile);
        tileChanged();
    }

    void shiftLeft(int tile) {
        font.rotateTileLeft(tile);
        tileChanged();
    }

    private int getColor(int tile, int x, int y) {
        return font.getTilePixel(tile, x, y);
    }

    private void switchColor(Graphics g, int c) {
        switch (c & 3) {
            case 0:
                g.setColor(Color.white);
                break;
            case 1:
                g.setColor(Color.lightGray);
                break;
            case 2:
                g.setColor(Color.darkGray); // Not used.
                break;
            case 3:
                g.setColor(Color.black);
                break;
        }
    }

    private int getMinimumDimension() {
        return getWidth() < getHeight() ? getWidth() : getHeight();
    }

    private void paintGrid(Graphics g) {
        g.setColor(java.awt.Color.gray);
        int minimumDimension = getMinimumDimension();
        int offsetX = (getWidth() - minimumDimension) / 2;
        int offsetY = (getHeight() - minimumDimension) / 2;
        int dx = minimumDimension / 8;
        int minimumDimensionSquare = (minimumDimension / 8) * 8;
        for (int x = dx + offsetX; x < minimumDimensionSquare + offsetX; x += dx) {
            g.drawLine(x, offsetY, x, minimumDimensionSquare + offsetY);
        }

        int dy = minimumDimension / 8;
        for (int y = dy + offsetY; y < minimumDimensionSquare + offsetY; y += dy) {
            g.drawLine(offsetX, y, offsetX + minimumDimensionSquare, y);
        }
    }

    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        int minimumDimension = getMinimumDimension();
        int offsetX = (getWidth() - minimumDimension) / 2;
        int offsetY = (getHeight() - minimumDimension) / 2;
        for (int x = 0; x < 8; ++x) {
            for (int y = 0; y < 8; ++y) {
                int color = getColor(selectedTile, x, y);
                switchColor(g, color);
                int pixelWidth = minimumDimension / 8;
                int pixelHeight = minimumDimension / 8;
                g.fillRect(offsetX + x * pixelWidth, offsetY + y * pixelHeight, pixelWidth, pixelHeight);
            }
        }

        paintGrid(g);
    }

    private void doMousePaint(java.awt.event.MouseEvent e) {
        int minimumDimension = getMinimumDimension();
        int offsetX = (getWidth() - minimumDimension) / 2;
        int offsetY = (getHeight() - minimumDimension) / 2;

        int x = ((e.getX() - offsetX) * 8) / minimumDimension;
        int y = ((e.getY() - offsetY) * 8) / minimumDimension;
        if (x < 0 || x >= 8 || y < 0 || y >= 8)
            return;
        if (SwingUtilities.isLeftMouseButton(e))
            setColor(x, y, color);
        else if (SwingUtilities.isRightMouseButton(e))
            setColor(x, y, rightColor);
        tileChanged();
    }

    private void setColor(int x, int y, int color) {
        font.setTilePixel(selectedTile, x, y, color);
    }

    public void mouseEntered(java.awt.event.MouseEvent e) {
    }

    public void mouseExited(java.awt.event.MouseEvent e) {
    }

    public void mouseReleased(java.awt.event.MouseEvent e) {
    }

    public void mousePressed(java.awt.event.MouseEvent e) {
    }

    public void mouseClicked(java.awt.event.MouseEvent e) {
        doMousePaint(e);
    }

    public void mouseMoved(java.awt.event.MouseEvent e) {
    }

    public void mouseDragged(java.awt.event.MouseEvent e) {
        doMousePaint(e);
    }

    void setColor(int color) {
        assert color >= 1 && color <= 3;
        this.color = color;
    }

    void setRightColor(int color) {
        assert color >= 1 && color <= 3;
        this.rightColor = color;
    }

    void setTileChangedListener(TileChangedListener l) {
        tileChangedListener = l;
    }

    void copyTile() {
        if (clipboard == null) {
            clipboard = new int[8][8];
        }
        for (int x = 0; x < 8; ++x) {
            for (int y = 0; y < 8; ++y) {
                clipboard[x][y] = getColor(selectedTile, x, y);
            }
        }
    }

    void generateShadedAndInvertedTiles() {
        font.generateShadedAndInvertedTiles();
    }

    void pasteTile() {
        if (clipboard == null) {
            return;
        }
        for (int x = 0; x < 8; ++x) {
            for (int y = 0; y < 8; ++y) {
                int c = clipboard[x][y];
                if (c < 3) {
                    ++c; // Adjusts from Game Boy Color to editor color.
                }
                setColor(x, y, c);
            }
        }
        tileChanged();
    }

    void tileChanged() {
        repaint();
        generateShadedAndInvertedTiles();
        tileChangedListener.tileChanged();
    }

    void readImage(String name, BufferedImage image) {
        font.loadImageData(name, image);

    }

    BufferedImage createImage(boolean includeGfxCharacters) {
        return font.saveDataToImage(includeGfxCharacters);
    }
}


================================================
FILE: src/main/java/kitEditor/KitEditor.java
================================================
package kitEditor;

import Document.Document;
import com.laszlosystems.libresample4j.Resampler;
import net.miginfocom.swing.MigLayout;
import utils.*;

import javax.sound.sampled.UnsupportedAudioFileException;
import javax.swing.*;
import javax.swing.event.MenuEvent;
import javax.swing.event.MenuListener;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Locale;

public class KitEditor extends JFrame implements SamplePicker.Listener {
    private final Document document;

    public interface Listener {
        void saveRom();
    }
    Listener listener;

    private static final long serialVersionUID = -3993608561466542956L;
    private JPanel contentPane;
    private final JComboBox<String> bankBox = new JComboBox<>();
    private final SamplePicker samplePicker = new SamplePicker();
    private final String SETTINGS_FILE_EXTENSION = ".settings";

    private static final int MAX_SAMPLES = 15;
    private static final int MAX_SAMPLE_SPACE = 0x3fa0;

    private final java.awt.event.ActionListener bankBoxListener = e -> bankBox_actionPerformed();

    private static final byte KIT_VERSION_1 = 1;

    private byte[] romImage;

    private final Sample[][] samples = new Sample[RomUtilities.BANK_COUNT][MAX_SAMPLES];
    
    private final Sample[] clipboard = new Sample[MAX_SAMPLES];

    private final JButton previousBankButton = new JButton("<");
    private final JButton nextBankButton = new JButton(">");

    private final JButton loadKitButton = new JButton();
    private final JButton saveKitButton = new JButton();
    private final JButton saveRomButton = new JButton();
    private final JButton exportSampleButton = new JButton();
    private final JButton clearKitButton = new JButton("Clear kit");
    private final JButton renameKitButton = new JButton();
    private final JTextField kitNameTextField = new JTextField();
    private final JButton reloadSampleButton = new JButton("Reload sample");
    private final JButton addSampleButton = new JButton("Add sample");
    private final JLabel kitSizeLabel = new JLabel();
    private final SampleView sampleView = new SampleView();
    private final JSpinner volumeSpinner = new JSpinner();
    private final JSpinner pitchSpinner = new JSpinner();
    private final JSpinner trimSpinner = new JSpinner();
    private final JCheckBoxMenuItem halfSpeed = new JCheckBoxMenuItem("Half-speed");
    private final JCheckBox dither = new JCheckBox("Dither", true);
    private final JMenuItem useGameBoyAdvancePolarity = new JCheckBoxMenuItem("Invert Polarity for GBA");

    public KitEditor(JFrame parent, Document document, Listener listener) {
        parent.setEnabled(false);

        romImage = document.romImage();
        this.listener = listener;
        this.document = document;
        enableEvents(AWTEvent.WINDOW_EVENT_MASK);
        jbInit();
        addMenu();
        setListeners();
        setTitle("Kit Editor");
        createSamplesFromRom();
        updateRomView();

        saveRomButton.addActionListener(e -> {
            document.setRomImage(romImage);
            listener.saveRom();
            romImage = document.romImage();
            updateButtonStates();
        });

        KeyboardFocusManager keyboardFocusManager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
        PadKeyHandler padKeyHandler = new PadKeyHandler();
        keyboardFocusManager.addKeyEventPostProcessor(padKeyHandler);

        addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                super.windowClosing(e);
                keyboardFocusManager.removeKeyEventPostProcessor(padKeyHandler);
                document.setRomImage(romImage);
                parent.setEnabled(true);
            }
        });

        add(contentPane);

        pack();

        samplePicker.grabFocus();

        setResizable(false);
        setVisible(true);
    }

    private void addEnterHandler(JSpinner spinner) {
        ((JSpinner.DefaultEditor)spinner.getEditor()).getTextField().addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                if (e.getKeyCode() == KeyEvent.VK_ENTER) {
                    requestFocus();
                }
            }
        });
    }

    private void setListeners() {
        bankBox.addActionListener(bankBoxListener);
        samplePicker.addListSelectionListener(this);
        volumeSpinner.addChangeListener(e -> onSpinnerChanged());
        addEnterHandler(volumeSpinner);
        pitchSpinner.addChangeListener(e -> onSpinnerChanged());
        addEnterHandler(pitchSpinner);
        trimSpinner.addChangeListener(e -> onSpinnerChanged());
        halfSpeed.addActionListener(e -> onHalfSpeedChanged());
        dither.addActionListener(e -> onSpinnerChanged());

        Action previousBankAction = new AbstractAction("previous bank") {
            @Override
            public void actionPerformed(ActionEvent e) {
                bankBox.setSelectedIndex(bankBox.getSelectedIndex() - 1);
            }
        };
        previousBankAction.putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke("F1"));
        previousBankButton.addActionListener(previousBankAction);
        previousBankButton.getActionMap().put("previousBankAction", previousBankAction);
        previousBankButton.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
                (KeyStroke) previousBankAction.getValue(Action.ACCELERATOR_KEY), "previousBankAction");
        previousBankButton.setToolTipText("F1");

        Action nextBankAction = new AbstractAction("next bank") {
            @Override
            public void actionPerformed(ActionEvent e) {
                bankBox.setSelectedIndex(bankBox.getSelectedIndex() + 1);
            }
        };
        nextBankAction.putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke("F2"));
        nextBankButton.addActionListener(nextBankAction);
        nextBankButton.getActionMap().put("nextBankAction", nextBankAction);
        nextBankButton.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
                (KeyStroke) nextBankAction.getValue(Action.ACCELERATOR_KEY), "nextBankAction");
        nextBankButton.setToolTipText("F2");

        loadKitButton.addActionListener(e -> loadKit());
        saveKitButton.addActionListener(e -> saveKit());
        clearKitButton.addActionListener(e -> createKit());
        renameKitButton.addActionListener(e -> renameKit(kitNameTextField.getText()));
        kitNameTextField.addActionListener(e -> {
            renameKit(kitNameTextField.getText());
            requestFocus();
        });

        exportSampleButton.addActionListener(e -> exportSample());
        addSampleButton.addActionListener(e -> addSample());
        reloadSampleButton.addActionListener(e -> reloadSample());
        reloadSampleButton.setEnabled(false);
    }

    private void onHalfSpeedChanged() {
        // Update trim accordingly.
        for (Sample s : samples[selectedBank]) {
            if (s != null) {
                if (halfSpeed.isSelected()) {
                    s.setTrim((int)Math.ceil(s.getTrim() / 2.0)); // Round upwards.
                } else {
                    s.setTrim(s.getTrim() * 2);
                }
            }
        }
        reloadAllSamples();
    }
    
    private void reloadAllSamples() {
        int index = samplePicker.getSelectedIndex();
        try {
            for (Sample s : samples[selectedBank]) {
                if (s != null) {
                    s.reload(halfSpeed.isSelected());
                }
            }
        } catch (Exception e) {
            showFileErrorMessage(e);
            e.printStackTrace();
        }
        compileKit();
        updateRomView();
        samplePicker.setSelectedIndex(index);
    }

    private void reloadSample() {
        int index = samplePicker.getSelectedIndex();
        Sample sample = samples[selectedBank][index];
        if (sample != null) {
            try {
                sample.reload(halfSpeed.isSelected());
            } catch (Exception e) {
                showFileErrorMessage(e);
                e.printStackTrace();
            }
        }
        compileKit();
        updateRomView();
        playSample();
        samplePicker.setSelectedIndex(index);
    }

    static boolean handlingSpinnerChange = false;
    private void onSpinnerChanged() {
        if (handlingSpinnerChange) {
            return;
        }
        int index = samplePicker.getSelectedIndex();
        if (index < 0) {
            return;
        }
        Sample sample = samples[selectedBank][index];
        if (sample == null || !sample.canAdjustVolume()) {
            return;
        }
        handlingSpinnerChange = true;
        sample.setVolumeDb((int)volumeSpinner.getValue());
        if ((int)pitchSpinner.getValue() != sample.getPitchSemitones()) {
            sample.setPitchSemitones((int) pitchSpinner.getValue());
            try {
                sample.reload(halfSpeed.isSelected());
            } catch (UnsupportedAudioFileException | IOException e) {
                e.printStackTrace();
                showFileErrorMessage(e);
            }
        }
        sample.setTrim((int)trimSpinner.getValue());
        sample.setDither(dither.isSelected());
        sample.processSamples();
        compileKit();
        if (bytesFree() < 0) {
            // Sample did not fit, likely due to increased volume. Trim to fit.
            int fixedTrim = sample.getTrim() - bytesFree() / 16;
            assert fixedTrim >= 0;
            trimSpinner.setValue(fixedTrim);
            sample.setTrim(fixedTrim);
            sample.processSamples();
            compileKit();
        }
        // Makes sure trim is in valid range.
        int maxTrim = maxTrim(sample);
        if ((int)trimSpinner.getValue() > maxTrim) {
            trimSpinner.setValue(maxTrim);
            sample.setTrim(maxTrim);
            sample.processSamples();
            compileKit();
        }
        samplePicker.setSelectedIndex(index);
        dither.setSelected(sample.getDither());
        Sound.stopAll();
        playSample();
        handlingSpinnerChange = false;
        updateKitSizeLabel();
    }

    private double ask(String message, double value) {
        try {
            value = Double.parseDouble(JOptionPane.showInputDialog(message, value));
        } catch (NullPointerException | NumberFormatException ignored) {
        }
        return value;
    }

    private void addMenu() {
        JMenuBar menuBar = new JMenuBar();
        JMenu preferences = new JMenu("Preferences");
        preferences.add(halfSpeed);
        useGameBoyAdvancePolarity.addActionListener(e -> reloadAllSamples());
        preferences.add(useGameBoyAdvancePolarity);
        JMenuItem lpFilter = new JMenuItem("Low-Pass Filter...");
        lpFilter.addActionListener(e -> {
            Resampler.Beta = ask("Kaiser Window Beta", Resampler.Beta);
            Resampler.RollOff = ask( "Kaiser Window Roll-Off (0-1, 1=Nyquist)", Resampler.RollOff);
        });
        preferences.add(lpFilter);

        JMenu edit = new JMenu("Edit");
        JMenuItem pasteSampleMenuItem = new JMenuItem("Paste");
        pasteSampleMenuItem.addActionListener(e -> pasteSample());
        JMenuItem trimAll = new JMenuItem("Trim all samples to fit");
        trimAll.addActionListener(e -> trimAllSamples());

        edit.add(pasteSampleMenuItem);
        edit.add(trimAll);

        edit.addMenuListener(new MenuListener() {
            @Override
            public void menuSelected(MenuEvent e) {
                trimAll.setEnabled(firstFreeSampleSlot() != 0 &&
                        firstFreeSampleSlot() != 1);
                pasteSampleMenuItem.setEnabled(firstFreeSampleSlot() >= 0 &&
                        firstFreeSampleSlot() < 15 && clipboard[0] != null);
            }

            @Override
            public void menuDeselected(MenuEvent e) { }

            @Override
            public void menuCanceled(MenuEvent e) { }
        });

        menuBar.add(preferences);        
        menuBar.add(edit);
        
        setJMenuBar(menuBar);
    }

    private void jbInit() {
        contentPane = new JPanel();
        contentPane.setLayout(new MigLayout());

        createFileDrop();

        samplePicker.setBorder(BorderFactory.createEtchedBorder());

        JPanel kitContainer = new JPanel();
        kitContainer.setLayout(new MigLayout("", "[grow,fill]", ""));
        kitContainer.add(bankBox, "grow,split 3");
        kitContainer.add(previousBankButton);
        kitContainer.add(nextBankButton, "wrap");
        kitContainer.add(samplePicker, "grow,wrap");
        kitContainer.add(kitSizeLabel, "grow, split 2");
        kitContainer.add(saveRomButton, "grow");

        loadKitButton.setText("Load kit");
        saveKitButton.setText("Save kit");
        saveRomButton.setText("Save ROM");

        kitNameTextField.setBorder(BorderFactory.createLoweredBevelBorder());

        renameKitButton.setText("Rename kit");

        exportSampleButton.setEnabled(false);
        exportSampleButton.setText("Export sample");

        addSampleButton.setEnabled(false);
        volumeSpinner.setEnabled(false);
        pitchSpinner.setEnabled(false);
        trimSpinner.setEnabled(false);
        dither.setEnabled(false);

        contentPane.add(kitContainer, "grow, cell 0 0, spany");
        contentPane.add(loadKitButton, "grow, wrap");
        contentPane.add(saveKitButton, "grow, wrap, sg button");
        contentPane.add(clearKitButton, "gaptop 5, grow, wrap, sg button");
        contentPane.add(kitNameTextField, "grow, wmin 60, split 2");
        contentPane.add(renameKitButton, "wrap 10");

        contentPane.add(exportSampleButton, "grow, wrap, sg button");
        contentPane.add(addSampleButton, "grow, span 2, wrap, sg button");
        contentPane.add(reloadSampleButton, "grow, span 2, wrap, sg button");
        contentPane.add(dither, "grow, wrap");
        contentPane.add(new JLabel("Volume (dB):"), "split 2");
        contentPane.add(volumeSpinner, "grow, wrap");
        contentPane.add(new JLabel("Pitch (semitone):"), "split 2");
        contentPane.add(pitchSpinner, "grow, wrap");
        contentPane.add(new JLabel("Trim (frames):"), "split 2");
        contentPane.add(trimSpinner, "grow, wrap");
        contentPane.add(sampleView, "grow, span 2, wmin 10, hmin 64");
        sampleView.addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                playSample();
            }
        });

        dither.setToolTipText("Removes 4-bit distortion by adding noise.");
        halfSpeed.setToolTipText("Half sample-rate, double kit length. Use with SPEED 0.5X kit setting.");
    }

    private void createFileDrop() {
        new FileDrop(contentPane, files -> {
            for (File file : files) {
                String fileName = file.getName().toLowerCase();
                if (fileName.endsWith(".wav")) {
                    if (romImage == null) {
                        JOptionPane.showMessageDialog(contentPane,
                                "Open .gb file before adding samples.",
                                "Can't add sample!",
                                JOptionPane.ERROR_MESSAGE);
                        continue;
                    }
                    addSample(file);
                } else if (fileName.endsWith(".kit")) {
                    if (romImage == null) {
                        JOptionPane.showMessageDialog(contentPane,
                                "Open .gb file before adding samples.",
                                "Can't add sample!",
                                JOptionPane.ERROR_MESSAGE);
                        continue;
                    }
                    loadKit(file);
                } else {
                    JOptionPane.showMessageDialog(contentPane,
                            "Unknown file type!",
                            "File error",
                            JOptionPane.ERROR_MESSAGE);
                    return;
                }
            }
        });
    }

    private static void unSwizzle(byte[] packedNibbles) {
        assert(packedNibbles.length % 16 == 0);

        // Rotates the wave frame left and inverts the signal. Mirrors sbc.java.
        byte[] tmpBuf = new byte[packedNibbles.length * 2];
        for (int i = 0; i < packedNibbles.length; i += 16) {
            for (int j = 0; j < 16; ++j) {
                int b = packedNibbles[i + j];
                int dst = ((2 * j + 31) % 32) + (i * 2);
                tmpBuf[dst] = (byte) ((0xf0 - (b & 0xf0)) >> 4);
                dst = 2 * (i + j);
                tmpBuf[dst] = (byte) ((0xf - (b & 0xf)) << 4);
            }
        }

        for (int i = 0; i < packedNibbles.length; ++i) {
            packedNibbles[i] = (byte)(tmpBuf[i * 2] | tmpBuf[i * 2 + 1]);
        }
    }

    private byte[] getNibbles(int index) {
        if (index < 0) {
            return null;
        }
        final int romBank = getSelectedROMBank();
        int offset = romBank * RomUtilities.BANK_SIZE + index * 2;
        int start = (0xff & romImage[offset]) | ((0xff & romImage[offset + 1]) << 8);
        int stop = (0xff & romImage[offset + 2]) | ((0xff & romImage[offset + 3]) << 8);
        if (stop <= start) {
            return null;
        }
        byte[] arr = new byte[stop - start];
        System.arraycopy(romImage,
                (romBank - 1) * RomUtilities.BANK_SIZE + start,
                arr,
                0,
                stop - start);
        if (isBankSwizzled()) {
            unSwizzle(arr);
        }
        return arr;
    }

    private boolean isBankSwizzled() {
        return bankVersion() == KIT_VERSION_1;
    }

    private byte bankVersion() {
        // This version field is new for lsdpatcher 1.11.1 and lsdj 9.2.2.
        // Old kits may have preexisting data here: it cannot be
        // assumed that all old kits are set to version 0.
        return romImage[versionOffset()];
    }

    private int versionOffset() {
        return getROMOffsetForSelectedBank() + 0x5f;
    }

    @Override
    public void playSample() {
        int index = samplePicker.getSelectedIndex();
        Sample sample = samples[selectedBank][index];
        if (sample == null) {
            return;
        }
        dither.setSelected(sample.getDither());
        byte[] nibbles = getNibbles(index);
        if (nibbles == null) {
            return;
        }
        try {
            Sound.play(nibbles, halfSpeed.isSelected());
        } catch (Exception e) {
            JOptionPane.showMessageDialog(this, e.getMessage(), "Audio error",
                    JOptionPane.INFORMATION_MESSAGE);
            e.printStackTrace();
        }
        updateSampleView();
    }

    private void updateSampleView() {
        int sampleIndex = samplePicker.getSelectedIndex();
        byte[] nibbles = getNibbles(sampleIndex);
        if (nibbles == null) {
            return;
        }
        float duration = samples[selectedBank][sampleIndex].lengthInSamples();
        duration /= halfSpeed.isSelected() ? 5734 : 11468;
        sampleView.setBufferContent(nibbles, duration);
        sampleView.repaint();
    }

    private boolean isKitBank(int a_bank) {
        int l_offset = (a_bank) * RomUtilities.BANK_SIZE;
        byte l_char_1 = romImage[l_offset++];
        byte l_char_2 = romImage[l_offset];
        return (l_char_1 == 0x60 && l_char_2 == 0x40);
    }

    private boolean isUninitializedBank(int a_bank) {
        int l_offset = (a_bank) * RomUtilities.BANK_SIZE;
        byte l_char_1 = romImage[l_offset++];
        byte l_char_2 = romImage[l_offset];
        return (l_char_1 == -1 && l_char_2 == -1);
    }

    private String getKitName(int a_bank) {
        if (isUninitializedBank(a_bank)) {
            return "Empty";
        }

        byte[] buf = new byte[6];
        int offset = (a_bank) * RomUtilities.BANK_SIZE + 0x52;
        for (int i = 0; i < 6; i++) {
            buf[i] = romImage[offset++];
        }
        return new String(buf);
    }

    private void updateRomView() {
        int tmp = bankBox.getSelectedIndex();
        bankBox.removeActionListener(bankBoxListener);
        bankBox.removeAllItems();

        int l_ui_index = 0;
        for (int bankNo = 0; bankNo < RomUtilities.BANK_COUNT; bankNo++) {
            if (isKitBank(bankNo) || isUninitializedBank(bankNo)) {
                bankBox.addItem(Integer.toHexString(++l_ui_index).toUpperCase() + ". " + getKitName(bankNo));
            }
        }
        bankBox.setSelectedIndex(tmp == -1 ? 0 : tmp);
        bankBox.addActionListener(bankBoxListener);
        updateBankView();
        updateSampleView();
    }

    private int selectedBank;

    private int getSelectedUiBank() {
        if (bankBox.getSelectedIndex() > -1) {
            selectedBank = bankBox.getSelectedIndex();
        }
        return selectedBank;
    }

    private int getSelectedROMBank() {
        int l_rom_bank = 0;
        int l_ui_bank = 0;

        for (; ; ) {
            if (isKitBank(l_rom_bank) || isUninitializedBank(l_rom_bank)) {
                if (getSelectedUiBank() == l_ui_bank) {
                    return l_rom_bank;
                }
                l_ui_bank++;
            }
            l_rom_bank++;
        }

    }

    private int getROMOffsetForSelectedBank() {
        return getSelectedROMBank() * RomUtilities.BANK_SIZE;
    }

    private void updateBankView() {
        byte[] buf = new byte[3];
        String[] s = new String[15];

        int bankOffset = getROMOffsetForSelectedBank();

        //update names
        int offset = bankOffset + 0x22;
        for (int instrNo = 0; instrNo < MAX_SAMPLES; instrNo++) {
            boolean isNull = false;
            for (int i = 0; i < 3; i++) {
                buf[i] = romImage[offset++];
                if (isNull) {
                    buf[i] = '-';
                } else {
                    if (buf[i] == 0) {
                        buf[i] = '-';
                        isNull = true;
                    }
                }
            }
            s[instrNo] = new String(buf);
        }
        samplePicker.setListData(s);

        updateKitSizeLabel();
        addSampleButton.setEnabled(firstFreeSampleSlot() != -1);

        updateButtonStates();
    }

    boolean kitTooBig() {
        return totalSampleSizeInBytes() > MAX_SAMPLE_SPACE;
    }

    private int bytesFree() {
        return MAX_SAMPLE_SPACE - totalSampleSizeInBytes();
    }

    private void updateKitSizeLabel() {
        int sampleRate = halfSpeed.isSelected() ? 5734 : 11468;
        float timeFree = (bytesFree() * 2.f) / sampleRate;
        kitSizeLabel.setText(String.format(Locale.US, "%.3f seconds free", timeFree));
        kitSizeLabel.setForeground(timeFree < 0 ? Color.red : Color.black);
    }

    private boolean isEmpty(Sample[] samples) {
        for (Sample s : samples) {
            if (s != null) {
                return false;
            }
        }
        return true;
    }

    private void bankBox_actionPerformed() {
        requestFocus();
        if (selectedBank == bankBox.getSelectedIndex()) {
            return;
        }
        selectedBank = bankBox.getSelectedIndex();
        if (isUninitializedBank(getSelectedROMBank())) {
            createKit();
        }
        if (isEmpty(samples[selectedBank])) {
            flushWavFiles();
            createSamplesFromRom();
        }
        updateBankView();
        samplePicker.setSelectedIndex(-1);
        selectionChanged();
    }

    private String getRomSampleName(int index) {
        int offset = getROMOffsetForSelectedBank() + 0x22 + index * 3;
        String name = "";
        name += (char) romImage[offset++];
        name += (char) romImage[offset++];
        name += (char) romImage[offset];
        return name;
    }

    private void createSamplesFromRom() {
        for (int sampleIt = 0; sampleIt < MAX_SAMPLES; ++sampleIt) {
            if (samples[selectedBank][sampleIt] != null) {
                continue;
            }
            byte[] nibbles = getNibbles(sampleIt);

            if (nibbles != null) {
                String name = getRomSampleName(sampleIt);
                samples[selectedBank][sampleIt] = Sample.createFromNibbles(nibbles, name);
            } else {
                samples[selectedBank][sampleIt] = null;
            }
        }
    }

    private void showFileErrorMessage(Exception e) {
        e.printStackTrace();
        JOptionPane.showMessageDialog(this,
                e.getMessage(),
                "File error",
                JOptionPane.ERROR_MESSAGE);
    }

    private void saveKit() {
        File f = FileDialogLauncher.save(this, "Save Kit", "kit");
        if (f == null) {
            return;
        }
        byte[] buf = new byte[RomUtilities.BANK_SIZE];
        int offset = getROMOffsetForSelectedBank();
        try {
            RandomAccessFile bankFile = new RandomAccessFile(f, "rw");
            for (int i = 0; i < buf.length; i++) {
                buf[i] = romImage[offset++];
            }
            bankFile.write(buf);
            bankFile.close();

            saveKitSettings(f);
        } catch (IOException e) {
            showFileErrorMessage(e);
        }
        updateRomView();
    }

    private void saveKitSettings(File kitFile) throws IOException {
        File kitSettingsFile = new File(kitFile.getAbsolutePath() + SETTINGS_FILE_EXTENSION);
        boolean hasFiles = false;
        for (Sample s : samples[selectedBank]) {
            if (s != null && s.getFile() != null) {
                hasFiles = true;
                break;
            }
        }
        if (!hasFiles) {
            return;
        }
        try (FileWriter fileWriter = new FileWriter(kitSettingsFile)) {
            for (Sample s : samples[selectedBank]) {
                if (s == null || s.getFile() == null) {
                    fileWriter.write("\n");
                    continue;
                }
                fileWriter.write(s.getFile().getAbsolutePath() +
                        "|" + s.getVolumeDb() +
                        "|" + s.getTrim() +
                        "|" + s.getPitchSemitones() +
                        "|" + s.getDither() + "\n");
            }
        }
    }

    private void loadKit() {
        File kitFile = FileDialogLauncher.load(this, "Load Sample Kit", "kit");
        if (kitFile != null) {
            loadKit(kitFile);
        }
    }

    private void loadKit(File kitFile) {
        createKit();
        renameKit(kitFile.getName());
        byte[] buf = new byte[RomUtilities.BANK_SIZE];
        int offset = getROMOffsetForSelectedBank();
        try {
            RandomAccessFile bankFile = new RandomAccessFile(kitFile, "r");
            bankFile.readFully(buf);
            if (buf[0] != 0x60 || buf[1] != 0x40) {
                JOptionPane.showMessageDialog(this,
                        "Malformed kit file!",
                        "File error",
                        JOptionPane.ERROR_MESSAGE);
                return;
            }
            for (byte aBuf : buf) {
                romImage[offset++] = aBuf;
            }
            bankFile.close();
            flushWavFiles();
            createSamplesFromRom();
            loadKitSettings(kitFile);
            updateBankView();
        } catch (IOException | UnsupportedAudioFileException e) {
            showFileErrorMessage(e);
        }
        updateRomView();
    }

    private void loadKitSettings(File kitFile) throws IOException, UnsupportedAudioFileException {
        File kitSettingsFile = new File(kitFile.getAbsolutePath() + SETTINGS_FILE_EXTENSION);
        if (!kitSettingsFile.exists()) {
            return;
        }
        try (BufferedReader fileReader = new BufferedReader(new FileReader(kitSettingsFile))) {
            for (int i = 0; i < MAX_SAMPLES; ++i) {
                String line = fileReader.readLine();
                if (line.isEmpty()) {
                    continue;
                }
                String[] chunks = line.split("\\|");
                File sampleFile = new File(chunks[0]);
                if (!sampleFile.exists()) {
                    continue;
                }
                int volume = Integer.parseInt(chunks[1]);
                int trim = 0;
                if (chunks.length > 2) {
                    trim = Integer.parseInt(chunks[2]);
                }
                int pitch = 0;
                if (chunks.length > 3) {
                    pitch = Integer.parseInt(chunks[3]);
                }
                boolean dither = true;
                if (chunks.length > 4) {
                    dither = chunks[4].equals("true");
                }
                samples[selectedBank][i] = Sample.createFromWav(
                        sampleFile,
                        dither,
                        halfSpeed.isSelected(),
                        volume,
                        trim,
                        pitch);
            }
        }
    }

    private String dropExtension(File f) {
        String ext = null;
        String s = f.getName();
        int i = s.lastIndexOf('.');

        if (i > 0 && i < s.length() - 1) {
            ext = s.substring(0, i);
        }
        return ext;
    }

    private void createKit() {
        //clear all bank
        int offset = getROMOffsetForSelectedBank();
        int max_offset = getROMOffsetForSelectedBank() + RomUtilities.BANK_SIZE;
        while (offset < max_offset) {
            romImage[offset++] = -1;
        }

        //clear kit name
        offset = getROMOffsetForSelectedBank() + 0x52;
        for (int i = 0; i < 6; i++) {
            romImage[offset++] = ' ';
        }

        //clear instrument names
        offset = getROMOffsetForSelectedBank() + 0x22;
        for (int i = 0; i < 15; i++) {
            romImage[offset++] = 0;
            romImage[offset++] = '-';
            romImage[offset++] = '-';
        }

        flushWavFiles();
        updateRomView();
    }

    private void flushWavFiles() {
        for (int i = 0; i < MAX_SAMPLES; ++i) {
            samples[selectedBank][i] = null;
        }
    }

    private void renameKit(String s) {
        s = s.toUpperCase();
        int offset = getROMOffsetForSelectedBank() + 0x52;
        for (int i = 0; i < 6; i++) {
            if (i < s.length()) {
                romImage[offset++] = (byte) s.charAt(i);
            } else {
                romImage[offset++] = ' ';
            }
        }
        compileKit();
        updateRomView();
    }

    private int firstFreeSampleSlot() {
        for (int sampleIt = 0; sampleIt < MAX_SAMPLES; ++sampleIt) {
            if (samples[selectedBank][sampleIt] == null) {
                return sampleIt;
            }
        }
        return -1;
    }

    private void addSample(File wavFile) {
        if (firstFreeSampleSlot() == -1) {
            JOptionPane.showMessageDialog(contentPane,
                    "Can't add sample, kit is full!",
                    "Kit full",
                    JOptionPane.ERROR_MESSAGE);
            return;
        }
        String sampleName = (dropExtension(wavFile).toUpperCase() + "---").substring(0,3);
        Sample sample;
        try {
            sample = Sample.createFromWav(wavFile, dither.isSelected(), halfSpeed.isSelected(), 0, 0, 0);
            int bytesFreeAfterAdd = MAX_SAMPLE_SPACE - totalSampleSizeInBytes() - sample.lengthInBytes();
            if (bytesFreeAfterAdd < 0) {
                int trim = -bytesFreeAfterAdd / 16;
                sample.setTrim(trim);
                sample.reload(halfSpeed.isSelected());
                JOptionPane.showMessageDialog(this,
                        "Trimmed sample to fit.",
                        "Kit full!",
                        JOptionPane.INFORMATION_MESSAGE);
            }
        } catch (Exception e) {
            showFileErrorMessage(e);
            return;
        }

        int index = firstFreeSampleSlot();
        assert index != -1;
        samples[selectedBank][index] = sample;
        renameSample(index, sampleName);
        compileKit();
        updateRomView();
        samplePicker.setSelectedIndex(index);
        playSample();
        updateButtonStates();
    }

    private void renameSample(int sampleIndex, String sampleName) {
        samples[selectedBank][sampleIndex].setName(sampleName);
        int offset = getROMOffsetForSelectedBank() + 0x22 + sampleIndex * 3;
        for (int i = 0; i < 3; ++i) {
            if (i < sampleName.length()) {
                romImage[offset] = (byte) sampleName.toUpperCase().charAt(i);
            } else {
                romImage[offset] = '-';
            }
            offset++;
        }

    }

    private void addSample() {
        File f = FileDialogLauncher.load(this, "Load Sample", "wav");
        if (f != null) {
            addSample(f);
        }
    }

    private void compileKit() {
        if (kitTooBig()) {
            return;
        }

        byte[] newSamples = new byte[RomUtilities.BANK_SIZE];
        int[] lengths = new int[15];
        sbc.compile(newSamples, samples[selectedBank], lengths, useGameBoyAdvancePolarity.isSelected());

        //copy sampledata to ROM image
        int offset = getROMOffsetForSelectedBank() + 0x60;
        int offset2 = 0x60;
        System.arraycopy(newSamples, offset2, romImage, offset, RomUtilities.BANK_SIZE - offset2);

        //update samplelength info in rom image
        int bankOffset = 0x4060;
        offset = getROMOffsetForSelectedBank();
        romImage[offset++] = 0x60;
        romImage[offset++] = 0x40;
        for (int i = 0; i < 15; i++) {
            bankOffset += lengths[i];
            if (lengths[i] != 0) {
                romImage[offset++] = (byte) (bankOffset & 0xff);
                romImage[offset++] = (byte) (bankOffset >> 8);
            } else {
                romImage[offset++] = 0;
                romImage[offset++] = 0;
            }
        }

        // Resets forced loop data.
        romImage[getROMOffsetForSelectedBank() + 0x5c] = 0;
        romImage[getROMOffsetForSelectedBank() + 0x5d] = 0;

        // Version number.
        romImage[versionOffset()] = KIT_VERSION_1;
    }

    private int totalSampleSizeInBytes() {
        int total = 0;
        for (Sample s : samples[selectedBank]) {
            total += s == null ? 0 : s.lengthInBytes();
        }
        return total;
    }

    private void dropSample() {
        ArrayList<Integer> indices = samplePicker.getSelectedIndices();
        for (int indexIt = 0; indexIt < indices.size(); ++indexIt) {
            // Assumes that indices are sorted...
            int index = indices.get(indexIt);

            // Moves up samples.
            if (14 - index >= 0) {
                System.arraycopy(samples[selectedBank],
                        index + 1,
                        samples[selectedBank],
                        index,
                        14 - index);
            }
            samples[selectedBank][14] = null;

            // Moves up instr names.
            int offset = getROMOffsetForSelectedBank() + 0x22 + index * 3;
            int i;
            for (i = offset; i < getROMOffsetForSelectedBank() + 0x22 + 14 * 3; i += 3) {
                romImage[i] = romImage[i + 3];
                romImage[i + 1] = romImage[i + 4];
                romImage[i + 2] = romImage[i + 5];
            }
            romImage[i] = 0;
            romImage[i + 1] = '-';
            romImage[i + 2] = '-';

            // Adjusts indices.
            for (int indexIt2 = indexIt + 1; indexIt2 < indices.size(); ++indexIt2) {
                indices.set(indexIt2, indices.get(indexIt2) - 1);
            }
        }
        compileKit();
        updateBankView();
    }

    private void exportSample() {
        File f = FileDialogLauncher.save(this, "Save Sample", "wav");
        if (f != null) {
            try {
                WaveFile.write(samples[selectedBank][samplePicker.getSelectedIndex()].workSampleData(), f);
            } catch (IOException e) {
                showFileErrorMessage(e);
            }
        }
    }

    @Override
    public void selectionChanged() {
        updateButtonStates();
    }

    private int maxTrim(Sample sample) {
        int maxTrim = sample.untrimmedLengthInSamples() / 32 - 1;
        return Math.max(0, maxTrim);
    }

    private int modelMaxTrim;
    private void updateTrimModel(Sample sample) {
        if (sample == null) {
            return;
        }
        int maxTrim = maxTrim(sample);
        if (modelMaxTrim == maxTrim) {
            return;
        }
        int trim = sample.getTrim();
        assert trim >= 0;
        trim = Math.min(trim, maxTrim);
        trimSpinner.setModel(new SpinnerNumberModel(trim, 0, maxTrim, 1));
        addEnterHandler(trimSpinner);
        modelMaxTrim = maxTrim;
    }

    private void updateButtonStates() {
        int index = samplePicker.getSelectedIndex();
        exportSampleButton.setEnabled(getNibbles(index) != null);
        Sample sample = index >= 0 ? samples[selectedBank][index] : null;
        boolean enableVolume = sample != null && sample.canAdjustVolume();
        handlingSpinnerChange = true;
        dither.setEnabled(enableVolume);
        volumeSpinner.setEnabled(enableVolume);
        volumeSpinner.setValue(enableVolume ? sample.getVolumeDb() : 0);
        pitchSpinner.setEnabled(enableVolume);
        pitchSpinner.setValue(enableVolume ? sample.getPitchSemitones() : 0);
        trimSpinner.setEnabled(enableVolume && sample.untrimmedLengthInSamples() > 32);
        if (trimSpinner.isEnabled()) {
            updateTrimModel(sample);
            trimSpinner.setValue(enableVolume ? sample.getTrim() : 0);
        }
        handlingSpinnerChange = false;
        reloadSampleButton.setEnabled(enableVolume);
        addSampleButton.setEnabled(totalSampleSizeInBytes() < MAX_SAMPLE_SPACE);
        saveRomButton.setEnabled(!kitTooBig() &&
                (!Arrays.equals(document.romImage(), romImage) ||
                        document.isRomDirty()));

        previousBankButton.setEnabled(selectedBank > 0);
        nextBankButton.setEnabled(selectedBank + 1 < bankBox.getItemCount());
    }

    private void trimAllSamples() {
        int numberOfSamples = firstFreeSampleSlot() > 1 ? firstFreeSampleSlot() : MAX_SAMPLES;
        int samplesToTrim = 0, usedBytes = 0;
        for (int sampleIt = 0; sampleIt < numberOfSamples; ++sampleIt) {
            Sample sample = samples[selectedBank][sampleIt];
            if (sample != null) {
                samplesToTrim = sample.canAdjustVolume() ? samplesToTrim + 1 : samplesToTrim;
                usedBytes = sample.canAdjustVolume() ? usedBytes : usedBytes + sample.untrimmedLengthInBytes();
            }
        }
        if (samplesToTrim < 1) {
            JOptionPane.showMessageDialog(this,
                "No samples to trim",
                "No samples to trim",
                JOptionPane.INFORMATION_MESSAGE);
            return;
        }
        int equalSampleLength = (MAX_SAMPLE_SPACE - usedBytes)  / samplesToTrim;
        // first calculate if any samples are shorter than equal length and add
        int addLength = 0, sampleCount = 0;
        for (int sampleIt = 0; sampleIt < MAX_SAMPLES; ++sampleIt) {
            Sample sample = samples[selectedBank][sampleIt];
            if (sample != null && sample.canAdjustVolume()) {
                if (sample.untrimmedLengthInBytes() < equalSampleLength) {
                    addLength += equalSampleLength - sample.untrimmedLengthInBytes();
                    ++sampleCount;
                }
            }
        }
        int maxEqualSampleLength = samplesToTrim > sampleCount
            ? equalSampleLength + addLength / (samplesToTrim - sampleCount)
            : equalSampleLength;
        for (int sampleIt = 0; sampleIt < MAX_SAMPLES; ++sampleIt) {
            Sample sample = samples[selectedBank][sampleIt];
            if (sample != null && sample.canAdjustVolume()) {
                int trim = sample.untrimmedLengthInBytes() > maxEqualSampleLength 
                    ? (int)Math.round((sample.untrimmedLengthInBytes() - maxEqualSampleLength) / 16.0)
                    : 0;
                sample.setTrim(trim);
            }
        }
        samplePicker.setSelectedIndex(numberOfSamples - 1);
        Sample sample = samples[selectedBank][numberOfSamples - 1];
        if (bytesFree() < 0) {
            // adjust last sample to account for rounding
            int fixedTrim = sample.getTrim() - bytesFree() / 16;
            assert fixedTrim > 0;
            trimSpinner.setValue(fixedTrim);
            sample.setTrim(fixedTrim);
            sample.processSamples();
            compileKit();
        }
        // Makes sure trim is in valid range.
        int maxTrim = maxTrim(sample);
        if ((int)trimSpinner.getValue() > maxTrim) {
            trimSpinner.setValue(maxTrim);
            sample.setTrim(maxTrim);
            sample.processSamples();
            compileKit();
        }
        updateButtonStates();
        reloadAllSamples();
        updateRomView();
        JOptionPane.showMessageDialog(this,
                "Trimmed all samples to fit.",
                "Done",
                JOptionPane.INFORMATION_MESSAGE);
    }

    private void duplicateSample(Sample sample) {
        if (sample == null) {
          return;
        }
        int sampleSlot = firstFreeSampleSlot();
        Sample dupeSample;
        if (sampleSlot != -1) {
            // copy sample data
            try {
            dupeSample = Sample.dupeSample(sample);
            dupeSample.reload(halfSpeed.isSelected());
            } catch (Exception e) {
                showFileErrorMessage(e);
                return;
            }
            samples[selectedBank][sampleSlot] = dupeSample;
            renameSample(sampleSlot, sample.getName());
            if (bytesFree() < 0 && sample.canAdjustVolume()) {
                int fixedTrim = dupeSample.getTrim() - bytesFree() / 16;
                assert fixedTrim > 0;
                dupeSample.setTrim(fixedTrim);
            } else if (bytesFree() < 0) {
                samples[selectedBank][sampleSlot] = null;
                JOptionPane.showMessageDialog(contentPane,
                        "Can't add sample, kit is full!",
                        "Kit full",
                        JOptionPane.ERROR_MESSAGE);
                return;
            }
        } else {
            JOptionPane.showMessageDialog(contentPane,
                    "Can't add sample, kit is full!",
                    "Kit full",
                    JOptionPane.ERROR_MESSAGE);
            return;
        } 
        reloadAllSamples();
        updateRomView();
        samplePicker.setSelectedIndex(sampleSlot);
        playSample();
        updateButtonStates();
    }
    
    private void pasteSample() {
        for (Sample sample : clipboard) {
            if (sample != null) {
                duplicateSample(sample);
            }
        }
    }

    @Override
    public void dupeSample() {
        int index = samplePicker.getSelectedIndex();
        duplicateSample(samples[selectedBank][index]);
    }

    @Override
    public void deleteSample() {
        dropSample();
    }

    @Override
    public void replaceSample() {
        File wavFile = FileDialogLauncher.load(this, "Load Sample", "wav");
        if (wavFile == null) {
            return;
        }
        try {
            final int index = samplePicker.getSelectedIndex();
            Sample newSample = Sample.createFromWav(wavFile, dither.isSelected(), halfSpeed.isSelected(), 0, 0, 0);
            Sample existingSample = samples[selectedBank][index];
            int bytesFreeAfterAdd = bytesFree() - newSample.lengthInBytes() + existingSample.lengthInBytes();
            if (bytesFreeAfterAdd < 0) {
                int trim = -bytesFreeAfterAdd / 16;
                newSample.setTrim(trim);
                newSample.reload(halfSpeed.isSelected());
                JOptionPane.showMessageDialog(this,
                        "Trimmed sample to fit.",
                        "Kit full!",
                        JOptionPane.INFORMATION_MESSAGE);
            }
            samples[selectedBank][index] = newSample;
            renameSample(index, dropExtension(wavFile).toUpperCase());
            compileKit();
            updateRomView();
            playSample();
        } catch (IOException | UnsupportedAudioFileException e) {
            e.printStackTrace();
            showFileErrorMessage(e);
        }
    }

    @Override
    public void renameSample(String s) {
        renameSample(samplePicker.getSelectedIndex(), s);
        updateRomView();
    }

    @Override
    public void copySample() {
        ArrayList<Integer> indices = samplePicker.getSelectedIndices();
        for (int index = 0; index < clipboard.length; ++index) {
            clipboard[index] = index < indices.size() ? samples[selectedBank][indices.get(index)] : null;
        }
    }

    private class PadKeyHandler implements KeyEventPostProcessor {
        @Override
        public boolean postProcessKeyEvent(KeyEvent e) {
            // Makes sure we do nothing if a text field has focus.
            if (e.getID() != KeyEvent.KEY_TYPED || e.isConsumed()) {
                return false;
            }
            if (e.getModifiersEx() != 0) {
                return false;
            }
            String playChars = "1234qwerasdfzxc";
            int index = playChars.indexOf(Character.toLowerCase(e.getKeyChar()));
            if (index == -1) {
                return false;
            }
            samplePicker.setSelectedIndex(index);
            playSample();
            return true;
        }
    }
}


================================================
FILE: src/main/java/kitEditor/Sample.java
================================================
package kitEditor;

import java.io.*;
import java.util.Random;
import javax.sound.sampled.*;

class Sample {
    private File file;
    private String name;
    private short[] originalSamples;
    private short[] processedSamples;
    private int untrimmedLengthInSamples = -1;
    private int readPos;
    private int volumeDb = 0;
    private int pitchSemitones = 0;
    private int trim = 0;
    private boolean dither = true;

    public Sample(short[] iBuf, String iName) {
        if (iBuf != null) {
            for (int j : iBuf) {
                assert (j >= Short.MIN_VALUE);
                assert (j <= Short.MAX_VALUE);
            }
            processedSamples = iBuf;
        }
        name = iName;
    }

    public Sample(Sample s) {
        file = s.file;
        name = s.name;
        originalSamples = s.originalSamples;
        processedSamples = s.processedSamples;
        untrimmedLengthInSamples = s.untrimmedLengthInSamples;
        readPos = s.readPos;
        volumeDb = s.volumeDb;
        pitchSemitones = s.pitchSemitones;
        trim = s.trim;
        dither = s.dither;
    }

    public String getName() {
        return name;
    }

    public void setName(String sampleName) {
        name = sampleName.toUpperCase().substring(0,3);
    }

    public int lengthInSamples() {
        return processedSamples.length;
    }

    public int untrimmedLengthInSamples() {
        return untrimmedLengthInSamples == -1 ? lengthInSamples() : untrimmedLengthInSamples;
    }

    public int untrimmedLengthInBytes() {
        int l = untrimmedLengthInSamples() / 2;
        l -= l % 0x10;
        return l;
    }

    public short[] workSampleData() {
        return (originalSamples != null ? originalSamples : processedSamples).clone();
    }

    public int lengthInBytes() {
        int l = lengthInSamples() / 2;
        l -= l % 0x10;
        return l;
    }

    public void seekStart() {
        readPos = 0;
    }

    public short read() {
        return processedSamples[readPos++];
    }

    public boolean canAdjustVolume() {
        return originalSamples != null;
    }



    // ------------------

    static Sample createFromNibbles(byte[] nibbles, String name) {
        short[] buf = new short[nibbles.length * 2];

        for (int i = 0; i < nibbles.length; ++i) {
            int n = nibbles[i];
            buf[i * 2] = (short)(n & 0xf0);
            buf[i * 2 + 1] = (short)((n & 0xf) << 4);
        }
        for (int bufIt = 0; bufIt < buf.length; ++bufIt) {
            short s = (byte)(buf[bufIt] - 0x80);
            s *= 256;
            buf[bufIt] = s;
        }
        return new Sample(buf, name);
    }

    // ------------------

    public static Sample createFromWav(File file,
                                       boolean dither,
                                       boolean halfSpeed,
                                       int volumeDb,
                                       int trim,
                                       int pitch)
            throws IOException, UnsupportedAudioFileException {
        Sample s = new Sample(null, file.getName().split("\\.")[0]);
        s.file = file;
        s.dither = dither;
        s.volumeDb = volumeDb;
        s.trim = trim;
        s.pitchSemitones = pitch;
        s.reload(halfSpeed);
        return s;
    }

    public static Sample dupeSample(Sample sample) {
      return new Sample(sample);
    }

    public void reload(boolean halfSpeed) throws IOException, UnsupportedAudioFileException {
        if (file == null) {
            return;
        }
        double outFactor = Math.pow(2.0, -pitchSemitones / 12.0);
        originalSamples = readSamples(file, halfSpeed, outFactor);
        processSamples();
    }

    public void processSamples() {
        int[] intBuffer = toIntBuffer(originalSamples);
        normalize(intBuffer);
        intBuffer = trim(intBuffer);
        if (dither) {
            dither(intBuffer);
        }
        processedSamples = toShortBuffer(intBuffer);
}

    private int[] trim(int[] intBuffer) {
        int headPos = headPos(intBuffer);
        int tailPos = tailPos(intBuffer);
        if (headPos > tailPos) {
            return new int[0];
        }
        untrimmedLengthInSamples = tailPos + 1 - headPos;
        tailPos = Math.max(headPos, tailPos - trim * 32);
        int[] newBuffer = new int[tailPos + 1 - headPos];
        System.arraycopy(intBuffer, headPos, newBuffer, 0, newBuffer.length);

        if (newBuffer.length >= 32) {
            return newBuffer;
        }

        // Extends to 32 samples.
        int[] zeroPadded = new int[32];
        System.arraycopy(newBuffer, 0, zeroPadded, 0, newBuffer.length);
        return zeroPadded;
    }

    final int SILENCE_THRESHOLD = Short.MAX_VALUE / 16;

    private int headPos(int[] buf) {
        int i;
        for (i = 0; i < buf.length; ++i) {
            if (Math.abs(buf[i]) >= SILENCE_THRESHOLD) {
                break;
            }
        }
        return i;
    }

    private int tailPos(int[] buf) {
        int i;
        for (i = buf.length - 1; i >= 0; --i) {
            if (Math.abs(buf[i]) >= SILENCE_THRESHOLD) {
                break;
            }
        }
        return i;
    }

    private short[] toShortBuffer(int[] intBuffer) {
        short[] shortBuffer = new short[intBuffer.length];
        for (int i = 0; i < intBuffer.length; ++i) {
            int s = intBuffer[i];
            s = Math.max(Short.MIN_VALUE, Math.min(Short.MAX_VALUE, s));
            shortBuffer[i] = (short)s;
        }
        return shortBuffer;
    }

    private int[] toIntBuffer(short[] shortBuffer) {
        int[] intBuffer = new int[shortBuffer.length];
        for (int i = 0; i < shortBuffer.length; ++i) {
            intBuffer[i] = shortBuffer[i];
        }
        return intBuffer;
    }

    private static short[] readSamples(File file, boolean halfSpeed, double outRateFactor) throws UnsupportedAudioFileException, IOException {
        AudioInputStream ais = AudioSystem.getAudioInputStream(file);
        float inSampleRate = ais.getFormat().getSampleRate();
        AudioFormat outFormat = new AudioFormat(inSampleRate, 16, 1, true, false);
        AudioInputStream convertedAis = AudioSystem.getAudioInputStream(outFormat, ais);
        byte[] b = new byte[convertedAis.available()];
        int read = convertedAis.read(b);
        assert read == b.length;
        short[] samples = new short[b.length / 2];
        for (int i = 0; i < samples.length; ++i) {
            samples[i] = (short) ((b[i * 2 + 1] * 256) + ((short)b[i * 2] & 0xff));
        }
        convertedAis.close();
        ais.close();

        double outSampleRate = halfSpeed ? 5734 : 11468;
        outSampleRate *= outRateFactor;
        return Sound.resample(inSampleRate, outSampleRate, samples);
    }

    // Adds triangular probability density function dither noise.
    private void dither(int[] samples) {
        Random random = new Random();
        float state = random.nextFloat();
        for (int i = 0; i < samples.length; ++i) {
            int value = samples[i];
            float r = state;
            state = random.nextFloat();
            int noiseLevel = 256 * 16;
            value += (r - state) * noiseLevel;
            samples[i] = value;
        }
    }

    private void normalize(int[] samples) {
        double peak = Double.MIN_VALUE;
        for (int sample : samples) {
            double s = sample;
            s = s < 0 ? s / Short.MIN_VALUE : s / Short.MAX_VALUE;
            peak = Math.max(s, peak);
        }
        if (peak == 0) {
            return;
        }
        double volumeAdjust = Math.pow(10, volumeDb / 20.0);
        for (int i = 0; i < samples.length; ++i) {
            samples[i] = (int)((samples[i] * volumeAdjust) / peak);
        }
    }

    public int getVolumeDb() {
        return volumeDb;
    }

    public void setVolumeDb(int value) {
        volumeDb = value;
    }

    public File getFile() {
        return file;
    }

    public void setTrim(int value) {
        assert value >= 0;
        trim = value;
    }

    public int getTrim() {
        return trim;
    }

    public void setPitchSemitones(int value) {
        pitchSemitones = value;
    }

    public int getPitchSemitones() {
        return pitchSemitones;
    }

    public void setDither(boolean value) {
        dither = value;
    }

    public boolean getDither() {
        return dither;
    }
}


================================================
FILE: src/main/java/kitEditor/SamplePicker.java
================================================
package kitEditor;

import net.miginfocom.swing.MigLayout;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.TreeSet;

import static java.awt.event.InputEvent.SHIFT_DOWN_MASK;

public class SamplePicker extends JPanel {
    private Listener listener;

    interface Listener {
        void selectionChanged();
        void playSample();
        void deleteSample();
        void dupeSample();
        void replaceSample();
        void renameSample(String s);
        void copySample();
    }

    class Pad extends JToggleButton {
        int id;
        Pad(int id) {
            this.id = id;
            setPreferredSize(new Dimension(64, 64));
            addMouseListener(new MouseAdapter() {
                @Override
                public void mousePressed(MouseEvent e) {
                    super.mouseClicked(e);
                    showPopup(e);
                }

                @Override
                public void mouseReleased(MouseEvent e) {
                    super.mouseClicked(e);
                    showPopup(e);
                }

                private void showPopup(MouseEvent e) {
                    if (!e.isPopupTrigger() || getText().equals("---")) {
                        return;
                    }
                    JPopupMenu menu = new JPopupMenu();
                    JMenuItem copy = new JMenuItem("Copy");
                    menu.add(copy);
                    copy.addActionListener(e1 -> listener.copySample());
                    JMenuItem rename = new JMenuItem("Rename...");
                    menu.add(rename);
                    rename.addActionListener(e1 -> {
                        String name = JOptionPane.showInputDialog("Enter new sample name");
                        if (name != null) {
                            listener.renameSample(name);
                        }
                    });
                    JMenuItem replace = new JMenuItem("Replace...");
                    menu.add(replace);
                    replace.addActionListener(e1 -> listener.replaceSample());
                    JMenuItem duplicate = new JMenuItem("Duplicate");
                    menu.add(duplicate);
                    duplicate.addActionListener(e1 -> listener.dupeSample());
                    JMenuItem delete = new JMenuItem("Delete");
                    menu.add(delete);
                    delete.addActionListener(e1 -> listener.deleteSample());
                    menu.show(e.getComponent(), e.getX(), e.getY());
                }
            });
        }
    }

    private final ArrayList<Pad> pads;
    private final TreeSet<Integer> selectedIndices = new TreeSet<>();

    SamplePicker() {
        setLayout(new MigLayout());
        pads = new ArrayList<>();
        for (int i = 0; i < 15; i++) {
            Pad pad = createPad();
            pad.addMouseListener(new MouseAdapter() {
                @Override
                public void mousePressed(MouseEvent e) {
                    super.mousePressed(e);
                    if ((e.getModifiersEx() & SHIFT_DOWN_MASK) == 0) {
                        selectedIndices.clear();
                        for (int i = 0; i < 15; ++i) {
                            JToggleButton button = pads.get(i);
                            button.setSelected(false);
                        }
                    }

                    Pad sender = (Pad)e.getSource();
                    int min = Math.min(sender.id, selectedIndices.isEmpty() ? Integer.MAX_VALUE : selectedIndices.first());
                    int max = Math.max(sender.id, selectedIndices.isEmpty() ? Integer.MIN_VALUE : selectedIndices.last());
                    for (int i = 0; i < 15; ++i) {
                        if (i >= min && i <= max) {
                            selectedIndices.add(i);
                            pads.get(i).setSelected(true);
                        }
                    }
                    listener.selectionChanged();
                    if (e.getButton() == MouseEvent.BUTTON1) {
                        listener.playSample();
                    }
                }
            });
        }
    }

    @Override
    public void grabFocus() {
        pads.get(0).grabFocus();
    }

    private Pad createPad() {
        Pad pad = new Pad(pads.size());
        pad.setToolTipText("Play by keys: 1234 QWER ASDF ZXC");
        pads.add(pad);
        add(pad, (pads.size() % 4) == 0 ? "wrap, sg button" : "");
        return pad;
    }

    public void setListData(String[] listData) {
        assert listData.length == pads.size();
        for (int i = 0; i < pads.size(); ++i) {
            pads.get(i).setText(listData[i]);
        }
    }

    public void setSelectedIndex(int selectedIndex) {
        selectedIndices.clear();
        for (int i = 0; i < 15; ++i) {
            JToggleButton button = pads.get(i);
            button.setSelected(false);
        }
        if (selectedIndex == -1) {
            return;
        }
        selectedIndices.add(selectedIndex);
        pads.get(selectedIndex).setSelected(true);
        listener.selectionChanged();
    }

    public int getSelectedIndex() {
        for (Pad pad : pads) {
            if (pad.isSelected()) {
                return pad.id;
            }
        }
        return selectedIndices.isEmpty() ? -1 : selectedIndices.first();
    }

    public ArrayList<Integer> getSelectedIndices() {
        return new ArrayList<>(selectedIndices);
    }

    public void addListSelectionListener(Listener listener) {
        this.listener = listener;
    }
}


================================================
FILE: src/main/java/kitEditor/SampleView.java
================================================
package kitEditor;

import java.awt.*;
import java.awt.geom.GeneralPath;
import java.util.Locale;

public class SampleView extends Canvas {
    private byte[] buf;
    private float duration;

    public void setBufferContent(byte[] newBuffer, float duration) {
        buf = newBuffer;
        setBackground(Color.black);
        this.duration = duration;
    }

    @Override
    public void paint(Graphics gg) {
        Graphics2D g = (Graphics2D) gg;
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);

        double w = g.getClipBounds().getWidth();
        double h = g.getClipBounds().getHeight();

        if (buf == null) {
            return;
        }

        GeneralPath gp = new GeneralPath();
        gp.moveTo(0, h / 2);
        for (int it = 0; it < buf.length; ++it) {
            // Only draws every second sample. This is probably OK.
            double val = buf[it] & 0xf;
            val -= 7.5;
            val /= 7.5;
            gp.lineTo(it * w / (buf.length - 1), h * (1 - val) / 2);
        }
        g.setColor(Color.YELLOW);
        g.draw(gp);

        drawDuration(g, (int) w, (int) h);
    }

    private void drawDuration(Graphics2D g, int w, int h) {
        String durationText = String.format(Locale.US, "%.3fs", duration);
        int x = -g.getFontMetrics().stringWidth(durationText) - 1;
        int y = -2;
        g.setColor(Color.BLACK);
        g.drawString(durationText, w + x, h + y);
        --x;
        --y;
        g.setColor(Color.WHITE);
        g.drawString(durationText, w + x, h + y);
    }
}


================================================
FILE: src/main/java/kitEditor/Sound.java
================================================
// Copyright (C) 2001, Johan Kotlinski

package kitEditor;

import com.laszlosystems.libresample4j.Resampler;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.ArrayList;
import javax.sound.sampled.*;

public class Sound {

    private static final ArrayList<Clip> clipPool = new ArrayList<>();
    private static final int PLAYBACK_RATE = 48000;

    private static short[] unpackNibbles(byte[] gbSample) {
        byte[] waveData = new byte[gbSample.length * 2];
        int src = 0;
        int dst = 0;

        while (src < gbSample.length) {
            byte sample = gbSample[src++];
            waveData[dst++] = (byte) (0xf0 & sample);
            waveData[dst++] = (byte) ((0x0f & sample) << 4);
        }

        short[] s = new short[waveData.length];
        for (int i = 0; i < s.length; ++i) {
            int v = waveData[i] & 0xf0;
            v -= 0x78;
            v *= Short.MAX_VALUE;
            v /= 0x78;
            s[i] = (short)v;
        }
        return s;
    }

    private static Clip getClip() throws LineUnavailableException {
        for (Clip clip : clipPool) {
            if (!clip.isRunning()) {
                clip.close();
                return clip;
            }
        }
        Clip newClip = AudioSystem.getClip();
        clipPool.add(newClip);
        return newClip;
    }

    static void play(byte[] gbSample, boolean halfSpeed) throws LineUnavailableException, IOException {
        final int sampleRate = halfSpeed ? 5734 : 11468;
        byte[] b = toByteArray(resampleForPlayback(sampleRate, unpackNibbles(gbSample)));
        Clip clip = getClip();
        clip.open(new AudioInputStream(new ByteArrayInputStream(b),
                new AudioFormat(PLAYBACK_RATE, 16, 1, true, false),
                b.length / 2));
        clip.start();
    }

    private static short[] resampleForPlayback(int srcRate, short[] src) {
        short[] dst = new short[Sound.PLAYBACK_RATE * src.length / srcRate];
        // Nearest neighbor resampling is good for emulating Game Boy sound.
        for (int i = 0; i < dst.length; ++i) {
            dst[i] = src[i * srcRate / Sound.PLAYBACK_RATE];
        }
        return dst;
    }

    private static byte[] toByteArray(short[] waveData) {
        byte[] b = new byte[waveData.length * 2];
        for (int i = 0; i < waveData.length; ++i) {
            b[i * 2] = (byte)(waveData[i] & 0xff);
            b[i * 2 + 1] = (byte)(waveData[i] >> 8);
        }
        return b;
    }

    static void stopAll() {
        for (Clip clip : clipPool) {
            clip.stop();
        }
    }

    public static short[] resample(double inSampleRate, double outSampleRate, short[] samples) {
        if (inSampleRate == outSampleRate) {
            return samples;
        }
        float[] inBuf = new float[samples.length];
        float dcOffset = 0;
        for (int i = 0; i < inBuf.length; ++i) {
            inBuf[i] = (float) samples[i] / -Short.MIN_VALUE;
            dcOffset += inBuf[i] / inBuf.length;
        }

        // Removes DC offset.
        for (int i = 0; i < inBuf.length; ++i) {
            inBuf[i] -= dcOffset;
        }

        double factor = outSampleRate / inSampleRate;
        float[] outBuf = new float[(int)(inBuf.length * factor + 1)];
        Resampler resampler = new Resampler(true, factor, factor);
        Resampler.Result result = resampler.process(factor, inBuf, 0, inBuf.length, true, outBuf, 0, outBuf.length);

        // avoid clipping
        float peak = 0;
        for (float v : outBuf) {
            peak = Math.max(peak, Math.abs(v));
        }
        if (peak > 1) {
            for (int i = 0; i < outBuf.length; ++i) {
                outBuf[i] /= peak;
            }
        }

        short[] finalBuf = new short[result.outputSamplesGenerated];
        for (int i = 0; i < finalBuf.length; ++i) {
            finalBuf[i] = (short)(outBuf[i] * Short.MAX_VALUE);
        }
        return finalBuf;
    }

}


================================================
FILE: src/main/java/kitEditor/WaveFile.java
================================================
package kitEditor;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;

public class WaveFile {
    public static void write(short[] pcm, File f) throws IOException {
        RandomAccessFile wavFile = new RandomAccessFile(f, "rw");

        int payloadSize = pcm.length * 2;
        int fileSize = pcm.length * 2 + 0x2c;
        int waveSize = fileSize - 8;

        byte[] header = {
                0x52, 0x49, 0x46, 0x46,  // RIFF
                (byte) waveSize,
                (byte) (waveSize >> 8),
                (byte) (waveSize >> 16),
                (byte) (waveSize >> 24),
                0x57, 0x41, 0x56, 0x45,  // WAVE
                // --- fmt chunk
                0x66, 0x6D, 0x74, 0x20,  // fmt
                16, 0, 0, 0,  // fmt size
                1, 0,  // pcm
                1, 0,  // channel count
                (byte) 0xcc, 0x2c, 0, 0,  // freq (11468 hz)
                (byte) 0xcc, 0x2c, 0, 0,  // avg. bytes/sec
                1, 0,  // block align
                16, 0,  // bits per sample
                // --- data chunk
                0x64, 0x61, 0x74, 0x61,  // data
                (byte) payloadSize,
                (byte) (payloadSize >> 8),
                (byte) (payloadSize >> 16),
                (byte) (payloadSize >> 24)
        };

        wavFile.write(header);

        byte[] byteBuffer = new byte[pcm.length * 2];
        int dst = 0;
        for (short sample : pcm) {
            byteBuffer[dst++] = (byte) sample;
            byteBuffer[dst++] = (byte) (sample >> 8);
        }
        wavFile.write(byteBuffer);
        wavFile.close();
    }
}


================================================
FILE: src/main/java/kitEditor/sbc.java
================================================
package kitEditor;

// Sample bank creator.

class sbc {

    public static void compile(byte[] dst, Sample[] samples, int[] byteLength, boolean gameBoyAdvancePolarity) {
        int offset = 0x60; //don't overwrite sample bank info!
        for (int sampleIt = 0; sampleIt < samples.length; sampleIt++) {
            Sample sample = samples[sampleIt];
            if (sample == null) {
                break;
            }

            sample.seekStart();
            int sampleLength = sample.lengthInSamples();

            int addedBytes = 0;
            int[] outputBuffer = new int[32];
            int outputCounter = 0;
            for (int i = 0; i < sampleLength; i++) {
                int s = sample.read();
                s = (int)(Math.round((double)s / (256 * 16) + 7.5));
                s = Math.min(0xf, Math.max(0, s));
                if (!gameBoyAdvancePolarity) {
                    // Use DMG polarity, where 0xf = -1.0 and 0 = 1.0.
                    s = 0xf - s;
                }

                // Starting from LSDj 9.2.0, first sample is skipped to compensate for wave refresh bug.
                // This rotates the wave frame rightwards.
                outputBuffer[(outputCounter + 1) % 32] = s;

                if (outputCounter == 31) {
                    for (int j = 0; j != 32; j += 2) {
                        dst[offset++] = (byte) (outputBuffer[j] * 0x10 + outputBuffer[j + 1]);
                    }
                    outputCounter = -1;
                    addedBytes += 0x10;
                }
                outputCounter++;
            }

            byteLength[sampleIt] = addedBytes;
        }
        while (offset < 0x4000) {
            dst[offset++] = -1; // rst opcode
        }
    }
}


================================================
FILE: src/main/java/lsdpatch/LSDPatcher.java
================================================
// Copyright (C) 2001, Johan Kotlinski

package lsdpatch;

import utils.CommandLineFunctions;
import utils.GlobalHolder;

import javax.swing.*;
import java.awt.*;
import java.awt.font.TextAttribute;
import java.util.HashMap;
import java.util.prefs.Preferences;

public class LSDPatcher {
    private static void initUi() {
        JFrame frame = new MainWindow();
        // Validate frames that have preset sizes
        // Pack frames that have useful preferred size info, e.g. from their layout
        frame.pack();
        frame.validate();

        // Center the window
        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
        Dimension frameSize = frame.getSize();
        if (frameSize.height > screenSize.height) {
            frameSize.height = screenSize.height;
        }
        if (frameSize.width > screenSize.width) {
            frameSize.width = screenSize.width;
        }
        frame.setLocation((screenSize.width - frameSize.width) / 2, (screenSize.height - frameSize.height) / 2);
        frame.setVisible(true);
    }

    private static void usage() {
        System.out.printf("LSDJPatcher v%s\n\n", NewVersionChecker.getCurrentVersion());
        System.out.println("java -jar LSDJPatcher.jar");
        System.out.println(" Opens the GUI.\n");

        System.out.println("java -jar LSDJPatcher.jar fnt2png [--extended] <fntfile> <pngfile>");
        System.out.println(" Exports the font file into a PNG\n");

        System.out.println("java -jar LSDJPatcher.jar png2fnt <font title> <pngfile> <fntfile>");
        System.out.println(" Converts the PNG into a font with given name.\n");

        System.out.println("java -jar LSDJPatcher.jar romfnt2png [--extended] <romFile> <fontIndex>");
        System.out.println(" Extracts the nth font from the given rom into a png named like the font.\n");

        System.out.println("java -jar LSDJPatcher.jar png2romfnt <romFile> <pngfile> <index> <fontname>");
        System.out.println(" Imports the PNG into the rom with given name.\n");

        System.out.println("java -jar LSDJPatcher.jar clone <inRomFile> <outRomlFile>");
        System.out.println(" Clones all customizations from a ROM file to another.\n");

    }

    public static void main(String[] args) {
        if (args.length >= 1) {
            processArguments(args);
            return;
        }
        try {
            // Use the system's UI look when applicable
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
            System.setProperty("apple.laf.useScreenMenuBar", "true");

            // Use font anti-aliasing when applicable
            System.setProperty("awt.useSystemAAFontSettings","on");
            System.setProperty("swing.aatext", "true");
            useJLabelFontForMenus();

        } catch (Exception e) {
            e.printStackTrace();
        }
        Preferences preferences = Preferences.userRoot().node(LSDPatcher.class.getName());
        GlobalHolder.set(preferences, Preferences.class);

        initUi();
    }

    private static void useJLabelFontForMenus() {
        // On some systems, the default font given to menus is a bit wonky with anti-aliasing. Using the one given
        // to JLabels will give a better result.
        Font systemFont = new JLabel().getFont();
        HashMap<TextAttribute, Object> attributes = new HashMap<>(systemFont.getAttributes());
        attributes.put(TextAttribute.WEIGHT, TextAttribute.WEIGHT_SEMIBOLD);
        attributes.put(TextAttribute.SIZE, systemFont.getSize());
        Font selectedFont = Font.getFont(attributes);
        UIManager.put("Menu.font", selectedFont);
        UIManager.put("MenuBar.font", selectedFont);
        UIManager.put("MenuItem.font", selectedFont);
    }

    private static void processArguments(String[] args) {
        String command = args[0].toLowerCase();

        boolean includeGfxCharacters = false;
        if(args.length > 2 && args[1].equalsIgnoreCase("--extended")) {
            includeGfxCharacters = true;
        }

        if (command.compareTo("fnt2png") == 0 && args.length == 3) {
            CommandLineFunctions.fontToPng(args[1], args[2]);
        } else if (command.compareTo("fnt2png") == 0 && args.length == 4 && includeGfxCharacters) {
            CommandLineFunctions.fontToPng(args[2], args[3]);
        } else if (command.compareTo("png2fnt") == 0 && args.length == 4) {
            CommandLineFunctions.pngToFont(args[1], args[2], args[3]);
        } else if (command.compareTo("romfnt2png") == 0 && args.length == 3) {
            // -1 to allow 1-3 range instead of 0-2
            CommandLineFunctions.extractFontToPng(args[1], Integer.parseInt(args[2]) - 1, false);
        } else if (command.compareTo("romfnt2png") == 0 && args.length == 4 && includeGfxCharacters) {
            // -1 to allow 1-3 range instead of 0-2
            CommandLineFunctions.extractFontToPng(args[2], Integer.parseInt(args[3]) - 1, true);
        } else if (command.compareTo("png2romfnt") == 0 && args.length == 5) {
            // -1 to allow 1-3 range instead of 0-2
            CommandLineFunctions.loadPngToRom(args[1], args[2], Integer.parseInt(args[3]) - 1, args[4]);
        } else if (command.compareTo("clone") == 0 && args.length == 3) {
            // -1 to allow 1-3 range instead of 0-2
            CommandLineFunctions.copyAllCustomizations(args[1], args[2]);
        } else {
            usage();
        }
    }

}


================================================
FILE: src/main/java/lsdpatch/MainWindow.java
================================================
// Copyright (C) 2020, Johan Kotlinski

package lsdpatch;

import Document.*;
import fontEditor.FontEditor;
import kitEditor.KitEditor;
import net.miginfocom.swing.MigLayout;
import paletteEditor.PaletteEditor;
import songManager.SongManager;
import utils.EditorPreferences;
import utils.FileDialogLauncher;
import utils.RomUtilities;

import javax.swing.*;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

public class MainWindow extends JFrame implements IDocumentListener, KitEditor.Listener {
    JTextField romTextField = new JTextField();
    JTextField savTextField = new JTextField();

    JButton upgradeRomButton = new JButton("Upgrade ROM");
    JButton songManagerButton = new JButton("Songs");
    JButton editKitsButton = new JButton("Sample Kits");
    JButton editFontsButton = new JButton("Fonts");
    JButton editPalettesButton = new JButton("Palettes");
    JButton saveButton = new JButton("Save...");

    MainWindow() {
        document.subscribe(this);

        updateTitle();
        JPanel panel = new JPanel();
        getContentPane().add(panel);
        MigLayout rootLayout = new MigLayout("wrap 6");
        panel.setLayout(rootLayout);

        addSelectors(panel);

        panel.add(new JSeparator(), "span 5");

        upgradeRomButton.addActionListener(e -> openRomUpgradeTool());
        panel.add(upgradeRomButton);

        songManagerButton.addActionListener(e -> openSongManager());
        panel.add(songManagerButton);

        editKitsButton.addActionListener(e ->
                new KitEditor(this, document, this).setLocationRelativeTo(this));
        panel.add(editKitsButton);

        editFontsButton.addActionListener(e -> openFontEditor());
        panel.add(editFontsButton);

        editPalettesButton.addActionListener(e -> openPaletteEditor());
        panel.add(editPalettesButton, "grow x");

        setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
        addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                if (!document.isDirty() || JOptionPane.showConfirmDialog(null,
                        "Quit without saving changes?",
                        "Unsaved changes",
                        JOptionPane.OK_CANCEL_OPTION,
                        JOptionPane.WARNING_MESSAGE) == JOptionPane.OK_OPTION) {
                    setDefaultCloseOperation(EXIT_ON_CLOSE);
                }
                super.windowClosing(e);
            }
        });

        setResizable(false);

        NewVersionChecker.checkGithub(this);
    }

    private void openRomUpgradeTool() {
        RomUpgradeTool romUpgradeTool = new RomUpgradeTool(this, document);
        romUpgradeTool.setLocationRelativeTo(this);
        romUpgradeTool.setVisible(true);
    }

    private void openSongManager() {
        SongManager savManager = new SongManager(this, document);
        savManager.setLocationRelativeTo(this);
        savManager.setVisible(true);
    }

    private void openPaletteEditor() {
        PaletteEditor editor = new PaletteEditor(this, document);
        editor.setLocationRelativeTo(this);
        editor.setVisible(true);
    }

    private void openFontEditor() {
        FontEditor fontEditor = new FontEditor(this, document);
        fontEditor.setLocationRelativeTo(this);
        fontEditor.setVisible(true);
    }

    final Document document = new Document();

    private void addSelectors(JPanel panel) {
        romTextField.setMinimumSize(new Dimension(300, 0));
        romTextField.setText(EditorPreferences.lastPath("gb"));
        romTextField.setEditable(false);
        panel.add(romTextField, "span 4, grow x");

        JButton browseRomButton = new JButton("Browse...");
        browseRomButton.addActionListener(e -> onBrowseRomButtonPress());
        panel.add(browseRomButton);

        saveButton.setEnabled(false);
        saveButton.addActionListener(e -> onSave(true));
        panel.add(saveButton, "span 1 4, grow y");

        savTextField.setMinimumSize(new Dimension(300, 0));
        savTextField.setEditable(false);
        savTextField.setText(EditorPreferences.lastPath("sav"));
        panel.add(savTextField, "span 4, grow x");

        JButton browseSavButton = new JButton("Browse...");
        browseSavButton.addActionListener(e -> onBrowseSavButtonPress());
        panel.add(browseSavButton);

        try {
            document.loadRomImage(EditorPreferences.lastPath("gb"));
        } catch (IOException e) {
            resetRomTextField();
        }
        try {
            document.loadSavFile(EditorPreferences.lastPath("sav"));
        } catch (IOException e) {
            resetSavTextField();
        }
        updateButtonsFromTextFields();
    }

    private void resetRomTextField() {
        romTextField.setText("Select LSDj ROM file -->");
    }

    private void resetSavTextField() {
        savTextField.setText("Select LSDj .sav file -->");
    }

    private void onBrowseRomButtonPress() {
        if (document.isDirty() && JOptionPane.showConfirmDialog(null,
                "Load new ROM without saving changes?",
                "Unsaved changes",
                JOptionPane.OK_CANCEL_OPTION,
                JOptionPane.WARNING_MESSAGE) == JOptionPane.CANCEL_OPTION) {
            return;
        }

        File romFile = FileDialogLauncher.load(this,
                "Select LSDj ROM Image",
                new String[]{ "gb", "gbc" });
        if (romFile == null) {
            return;
        }

        String romPath = romFile.getAbsolutePath();
        try {
            document.loadRomImage(romPath);
            romTextField.setText(romPath);
        } catch (IOException e) {
            resetRomTextField();
            e.printStackTrace();
            JOptionPane.showMessageDialog(this,
                    e.getMessage() == null ? "Could not load " + romPath : e.getMessage(),
                    "ROM load failed!",
                    JOptionPane.ERROR_MESSAGE);
        }

        String savPath = romPath
                .replaceFirst(".gbc$", ".sav")
                .replaceFirst(".gb$", ".sav");
        try {
            document.loadSavFile(savPath);
            savTextField.setText(savPath);
            EditorPreferences.setLastPath("sav", savPath);
        } catch (IOException e) {
            resetSavTextField();
            e.printStackTrace();
        }
        updateButtonsFromTextFields();
    }

    private void onBrowseSavButtonPress() {
        if (document.isSavDirty() && JOptionPane.showConfirmDialog(null,
                "Load new .sav without saving changes?",
                "Unsaved changes",
                JOptionPane.OK_CANCEL_OPTION,
                JOptionPane.WARNING_MESSAGE) == JOptionPane.CANCEL_OPTION) {
            return;
        }

        File savFile = FileDialogLauncher.load(this, "Load Save File", "sav");
        if (savFile == null) {
            return;
        }
        try {
            document.loadSavFile(savFile.getAbsolutePath());
            savTextField.setText(savFile.getAbsolutePath());
        } catch (IOException e) {
            resetSavTextField();
            JOptionPane.showMessageDialog(this,
                    e.getMessage(),
                    ".sav load failed!",
                    JOptionPane.ERROR_MESSAGE);
            e.printStackTrace();
        }
        updateButtonsFromTextFields();
    }

    void updateButtonsFromTextFields() {
        byte[] romImage = document.romImage();
        boolean romOk = romImage != null;
        String savPath = savTextField.getText();
        boolean savPathOk = savPath.endsWith(".sav") && new File(savPath).exists();
        boolean foundPalettes = romOk && RomUtilities.validatePaletteData(romImage);

        romTextField.setBackground(romOk ? Color.white : Color.pink);
        savTextField.setBackground(savPathOk ? Color.white : Color.pink);

        editKitsButton.setEnabled(romOk);
        editFontsButton.setEnabled(romOk && foundPalettes);
        editPalettesButton.setEnabled(romOk && foundPalettes);
        upgradeRomButton.setEnabled(romOk && foundPalettes);
        songManagerButton.setEnabled(savPathOk && romOk);
    }

    public void onDocumentDirty(boolean dirty) {
        updateTitle();
        upgradeRomButton.setEnabled(!dirty);
        saveButton.setEnabled(dirty);
    }

    private void updateTitle() {
        String title = "LSDPatcher v" + NewVersionChecker.getCurrentVersion();
        if (document.romImage() != null) {
            title = title + " - " + document.romFile().getName();
            if (document.isDirty()) {
                title = title + '*';
            }
        }
        setTitle(title);
    }

    private void onSave(boolean saveSavFile) {
        File f = FileDialogLauncher.save(this,
                "Save ROM Image",
                new String[]{ "gb", "gbc" });
        if (f == null) {
            return;
        }
        String romPath = f.getAbsolutePath();

        try (FileOutputStream fileOutputStream = new FileOutputStream(romPath)) {
            byte[] romImage = document.romImage();
            RomUtilities.fixChecksum(romImage);
            fileOutputStream.write(romImage);
            fileOutputStream.close();
            if (document.savFile() != null && saveSavFile) {
                String savPath = romPath
                        .replace(".gbc", ".sav")
                        .replace(".gb", ".sav");
                document.savFile().saveAs(savPath);
                savTextField.setText(savPath);
                document.loadSavFile(savPath);
                document.clearSavDirty();
                EditorPreferences.setLastPath("sav", savPath);
            }
            romTextField.setText(romPath);
            document.setRomFile(new File(romPath));
            document.clearRomDirty();
            EditorPreferences.setLastPath("gb", romPath);
            saveButton.setEnabled(false);
        } catch (IOException e) {
            JOptionPane.showMessageDialog(this,
                    e.getMessage(),
                    "File save failed!",
                    JOptionPane.ERROR_MESSAGE);
        }
    }

    @Override
    public void saveRom() {
        onSave(false);
    }
}


================================================
FILE: src/main/java/lsdpatch/NewVersionChecker.java
================================================
package lsdpatch;

import utils.GlobalHolder;

import javax.swing.*;
import java.io.IOException;
import java.net.URL;

public class NewVersionChecker {
    public static String getCurrentVersion() {
        String version = GlobalHolder.class.getPackage().getImplementationVersion();
        if (version == null) {
            return "DEV";
        }
        return version;
    }

    public static void checkGithub(JFrame parent) {
        String currentVersion = getCurrentVersion();
        if (currentVersion.equals("DEV")) {
            return;
        }
        String response;
        try {
            String apiPath = "https://api.github.com/repos/jkotlinski/lsdpatch/releases/latest";
            response = WwwUtil.fetchWwwPage(new URL(apiPath));
        } catch (IOException e) {
            return;
        }
        if (response.contains("\"v" + currentVersion + '"')) {
            return;
        }
        if (JOptionPane.showConfirmDialog(parent,
                "A new LSDPatcher release is available. Do you want to see it?",
                "Version upgrade",
               JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) {
            WwwUtil.openInBrowser("https://github.com/jkotlinski/lsdpatch/releases");
        }
    }
}

================================================
FILE: src/main/java/lsdpatch/RomUpgradeTool.java
================================================
// Copyright (C) 2020, Johan Kotlinski

package lsdpatch;

import Document.Document;
import net.miginfocom.swing.MigLayout;
import structures.LSDJFont;
import utils.RomUtilities;

import javax.swing.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.*;
import java.net.URL;
import java.util.regex.MatchResult;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipInputStream;

public class RomUpgradeTool extends JFrame {
    final String changeLogPath = "https://www.littlesounddj.com/lsd/latest/CHANGELOG.txt";
    final String licensePath = "https://www.littlesounddj.com/lsd/latest/rom_images/LICENSE.txt";
    final String developPath = "https://www.littlesounddj.com/lsd/latest/rom_images/develop/";
    final String stablePath = "https://www.littlesounddj.com/lsd/latest/rom_images/stable/";
    final String arduinoBoyPath = "https://www.littlesounddj.com/lsd/latest/rom_images/arduinoboy/";

    priva
Download .txt
gitextract_yezj645m/

├── .gitattributes
├── .github/
│   └── workflows/
│       └── maven.yml
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── pom.xml
└── src/
    ├── main/
    │   ├── java/
    │   │   ├── Document/
    │   │   │   ├── Document.java
    │   │   │   ├── IDocumentListener.java
    │   │   │   └── LSDSavFile.java
    │   │   ├── com/
    │   │   │   └── laszlosystems/
    │   │   │       └── libresample4j/
    │   │   │           ├── FilterKit.java
    │   │   │           ├── Resampler.java
    │   │   │           └── SampleBuffers.java
    │   │   ├── fontEditor/
    │   │   │   ├── .gitignore
    │   │   │   ├── ChangeEventListener.java
    │   │   │   ├── FontEditor.java
    │   │   │   ├── FontEditorColorSelector.java
    │   │   │   ├── FontMap.java
    │   │   │   └── TileEditor.java
    │   │   ├── kitEditor/
    │   │   │   ├── KitEditor.java
    │   │   │   ├── Sample.java
    │   │   │   ├── SamplePicker.java
    │   │   │   ├── SampleView.java
    │   │   │   ├── Sound.java
    │   │   │   ├── WaveFile.java
    │   │   │   └── sbc.java
    │   │   ├── lsdpatch/
    │   │   │   ├── LSDPatcher.java
    │   │   │   ├── MainWindow.java
    │   │   │   ├── NewVersionChecker.java
    │   │   │   ├── RomUpgradeTool.java
    │   │   │   └── WwwUtil.java
    │   │   ├── paletteEditor/
    │   │   │   ├── ColorPicker.java
    │   │   │   ├── ColorUtil.java
    │   │   │   ├── HuePanel.java
    │   │   │   ├── PaletteEditor.java
    │   │   │   ├── RGB555.java
    │   │   │   ├── SaturationBrightnessPanel.java
    │   │   │   ├── ScreenShotColors.java
    │   │   │   ├── Swatch.java
    │   │   │   ├── SwatchPair.java
    │   │   │   └── SwatchPanel.java
    │   │   ├── songManager/
    │   │   │   └── SongManager.java
    │   │   ├── structures/
    │   │   │   ├── LSDJFont.java
    │   │   │   └── ROMDataManipulator.java
    │   │   └── utils/
    │   │       ├── CommandLineFunctions.java
    │   │       ├── EditorPreferences.java
    │   │       ├── FileDialogLauncher.java
    │   │       ├── FileDrop.java
    │   │       ├── FontIO.java
    │   │       ├── GlobalHolder.java
    │   │       ├── RomUtilities.java
    │   │       └── StretchIcon.java
    │   └── resources/
    │       └── META-INF/
    │           └── MANIFEST.MF
    └── test/
        ├── java/
        │   ├── Document/
        │   │   ├── DocumentTest.java
        │   │   └── LSDSavFileTest.java
        │   └── kitEditor/
        │       └── SampleTest.java
        └── resources/
            ├── empty.lsdprj
            └── triangle_waves.lsdprj
Download .txt
SYMBOL INDEX (636 symbols across 47 files)

FILE: src/main/java/Document/Document.java
  class Document (line 13) | public class Document {
    method subscribe (line 23) | public void subscribe(IDocumentListener documentListener) {
    method romFile (line 27) | public File romFile() {
    method publishDocumentDirty (line 31) | private void publishDocumentDirty() {
    method setRomDirty (line 37) | private void setRomDirty(boolean dirty) {
    method setSavDirty (line 42) | private void setSavDirty(boolean dirty) {
    method romImage (line 47) | public byte[] romImage() {
    method setRomImage (line 51) | public void setRomImage(byte[] romImage) {
    method loadRomImage (line 59) | public void loadRomImage(String romPath) throws IOException {
    method loadSavFile (line 74) | public void loadSavFile(String savPath) throws IOException {
    method savFile (line 86) | public LSDSavFile savFile() {
    method setSavFile (line 97) | public void setSavFile(LSDSavFile savFile) {
    method isSavDirty (line 110) | public boolean isSavDirty() {
    method isRomDirty (line 114) | public boolean isRomDirty() {
    method isDirty (line 118) | public boolean isDirty() {
    method setRomFile (line 122) | public void setRomFile(File file) {
    method clearSavDirty (line 126) | public void clearSavDirty() {
    method clearRomDirty (line 130) | public void clearRomDirty() {

FILE: src/main/java/Document/IDocumentListener.java
  type IDocumentListener (line 3) | public interface IDocumentListener {
    method onDocumentDirty (line 4) | void onDocumentDirty(boolean dirty);

FILE: src/main/java/Document/LSDSavFile.java
  class LSDSavFile (line 9) | public class LSDSavFile implements Cloneable {
    method LSDSavFile (line 29) | public LSDSavFile() {
    method clone (line 33) | public LSDSavFile clone() throws CloneNotSupportedException {
    method equals (line 41) | public boolean equals(LSDSavFile rhs) {
    method isSixtyFourKbRam (line 45) | private boolean isSixtyFourKbRam() {
    method totalBlockCount (line 60) | public int totalBlockCount() {
    method saveAs (line 65) | public void saveAs(String filePath) throws IOException {
    method clearSong (line 74) | public void clearSong(int index) {
    method getBlocksUsed (line 95) | public int getBlocksUsed(int slot) {
    method clearFileName (line 108) | private void clearFileName(int index) {
    method clearFileVersion (line 112) | private void clearFileVersion(int index) {
    method usedBlockCount (line 116) | public int usedBlockCount() {
    method getNewSongId (line 120) | private byte getNewSongId() {
    method getBlockIdOfFirstFreeBlock (line 129) | private int getBlockIdOfFirstFreeBlock() {
    method freeBlockCount (line 159) | public int freeBlockCount() {
    method loadFromSav (line 174) | public void loadFromSav(String filePath) throws IOException {
    method populateSongList (line 182) | public void populateSongList(JList<String> songList) {
    method convertLsdCharToAscii (line 205) | private static int convertLsdCharToAscii(int ch) {
    method getFileName (line 217) | public String getFileName(int slot) {
    method version (line 238) | public String version(int slot) {
    method exportSongToFile (line 244) | public void exportSongToFile(int songId, String filePath, byte[] romIm...
    method writeKits (line 276) | private void writeKits(byte[] romImage, int songId, RandomAccessFile f...
    method usedKits (line 295) | TreeSet<Integer> usedKits(int songId) {
    method writeSongBlocks (line 312) | void writeSongBlocks(int songId, RandomAccessFile file) throws IOExcep...
    method unpackSong (line 330) | private byte[] unpackSong(int songId) {
    method isValid (line 438) | public boolean isValid(int songId) {
    class AddSongException (line 442) | static class AddSongException extends Exception {
      method AddSongException (line 443) | AddSongException(String message) {
    method addSongFromFile (line 448) | public void addSongFromFile(String filePath, byte[] romImage) throws E...
    method writeFileNameAndVersion (line 464) | private void writeFileNameAndVersion(FileInputStream fileInputStream, ...
    method patchKits (line 479) | private void patchKits(FileInputStream fileInputStream,
    method instrumentKitLocations (line 516) | private List<Integer> instrumentKitLocations(int songId) {
    method adjustInstruments (line 606) | private void adjustInstruments(int songId, int[] newKits) {
    method addMissingKits (line 633) | private void addMissingKits(byte[] romImage, ArrayList<byte[]> lsdSngK...
    method findFreeKit (line 649) | private int findFreeKit(byte[] romImage) {
    method copySongToWorkRam (line 659) | private void copySongToWorkRam(FileInputStream fileInputStream, byte s...
    method clearActiveFileSlot (line 683) | private void clearActiveFileSlot() {
    method getActiveFileSlot (line 687) | private byte getActiveFileSlot() {
    method getNextBlockIdPtr (line 694) | private int getNextBlockIdPtr(int block) throws AddSongException {

FILE: src/main/java/com/laszlosystems/libresample4j/FilterKit.java
  class FilterKit (line 56) | public class FilterKit {
    method Izero (line 61) | private static double Izero(double x) {
    method lrsLpFilter (line 77) | public static void lrsLpFilter(double c[], int N, double frq, double B...
    method lrsFilterUp (line 122) | public static float lrsFilterUp(float Imp[], float ImpD[], int Nwing, ...
    method lrsFilterUD (line 191) | public static float lrsFilterUD(float Imp[], float ImpD[], int Nwing, ...

FILE: src/main/java/com/laszlosystems/libresample4j/Resampler.java
  class Resampler (line 17) | public class Resampler {
    class Result (line 19) | public static class Result {
      method Result (line 23) | public Result(int inputSamplesConsumed, int outputSamplesGenerated) {
    method Resampler (line 56) | public Resampler(Resampler other) {
    method Resampler (line 83) | public Resampler(boolean highQuality, double minFactor, double maxFact...
    method getFilterWidth (line 140) | public int getFilterWidth() {
    method process (line 152) | public boolean process(double factor, SampleBuffers buffers, boolean l...
    method process (line 322) | public boolean process(double factor, final FloatBuffer inputBuffer, b...
    method process (line 354) | public Result process(double factor, float[] inBuffer, int inBufferOff...
    method lrsSrcUp (line 369) | private int lrsSrcUp(float X[], float Y[], double factor, int Nx, int ...
    method lrsSrcUD (line 407) | private int lrsSrcUD(float X[], float Y[], double factor, int Nx, int ...

FILE: src/main/java/com/laszlosystems/libresample4j/SampleBuffers.java
  type SampleBuffers (line 19) | public interface SampleBuffers {
    method getInputBufferLength (line 24) | int getInputBufferLength();
    method getOutputBufferLength (line 29) | int getOutputBufferLength();
    method produceInput (line 39) | void produceInput(float[] array, int offset, int length);
    method consumeOutput (line 48) | void consumeOutput(float[] array, int offset, int length);

FILE: src/main/java/fontEditor/ChangeEventListener.java
  class ChangeEventListener (line 3) | abstract class ChangeEventListener {
    type ChangeEventMouseSide (line 4) | public enum ChangeEventMouseSide {
    method onChange (line 9) | public abstract void onChange(int color, ChangeEventMouseSide side);

FILE: src/main/java/fontEditor/FontEditor.java
  class FontEditor (line 23) | public class FontEditor extends JFrame implements FontMap.TileSelectList...
    method FontEditor (line 38) | public FontEditor(JFrame parent, Document document) {
    method addImageButtonToPanel (line 170) | private void addImageButtonToPanel(JPanel panel, String imagePath, Str...
    method addMenuEntry (line 178) | private void addMenuEntry(JMenu destination, String name, int key, Act...
    method createFileMenu (line 185) | private void createFileMenu(JMenuBar menuBar) {
    method createEditMenu (line 194) | private void createEditMenu(JMenuBar menuBar) {
    method loadImage (line 203) | private BufferedImage loadImage(String iconPath) {
    method setUpButtonIconOrText (line 212) | private void setUpButtonIconOrText(JButton button, BufferedImage image...
    method getFontName (line 219) | private String getFontName(int i) {
    method populateFontSelector (line 223) | private void populateFontSelector() {
    method setRomImage (line 248) | public void setRomImage(byte[] romImage) {
    method fontSelectorItemChanged (line 266) | private void fontSelectorItemChanged(java.awt.event.ItemEvent e) {
    method setColor (line 282) | private void setColor(int color) {
    method setRightColor (line 287) | private void setRightColor(int color) {
    method tileSelected (line 292) | public void tileSelected(int tile) {
    method tileChanged (line 296) | public void tileChanged() {
    method fontSelectorAction (line 300) | private void fontSelectorAction(java.awt.event.ActionEvent e) {
    method loadFont (line 322) | private void loadFont() {
    method importBitmap (line 350) | private void importBitmap(File bitmap) {
    method saveFont (line 367) | private void saveFont() {

FILE: src/main/java/fontEditor/FontEditorColorSelector.java
  class FontEditorColorSelector (line 14) | class FontEditorColorSelector {
    class FontEditorColorListener (line 21) | private static class FontEditorColorListener implements MouseListener {
      method FontEditorColorListener (line 25) | FontEditorColorListener(FontEditorColorSelector selector, int color) {
      method mouseClicked (line 30) | @Override
      method mouseEntered (line 34) | @Override
      method mouseExited (line 38) | @Override
      method mousePressed (line 42) | @Override
      method mouseReleased (line 51) | @Override
    method FontEditorColorSelector (line 56) | public FontEditorColorSelector(JPanel buttonPanel) {
    method sendEvent (line 99) | private void sendEvent(int color, ChangeEventMouseSide side) {
    method addChangeEventListener (line 122) | void addChangeEventListener(ChangeEventListener changeEventListener) {

FILE: src/main/java/fontEditor/FontMap.java
  class FontMap (line 9) | public class FontMap extends JPanel implements java.awt.event.MouseListe...
    type TileSelectListener (line 18) | public interface TileSelectListener {
      method tileSelected (line 19) | void tileSelected(int tile);
    method FontMap (line 24) | FontMap() {
    method setShowGfxCharacters (line 28) | void setShowGfxCharacters(boolean show) {
    method setTileSelectListener (line 33) | void setTileSelectListener(TileSelectListener l) {
    method getCurrentUnscaledMapHeight (line 37) | int getCurrentUnscaledMapHeight () {
    method getCurrentTileNumber (line 41) | int getCurrentTileNumber () {
    method paintComponent (line 45) | public void paintComponent(Graphics g) {
    method tileCount (line 62) | private int tileCount() {
    method switchColor (line 68) | private void switchColor(Graphics g, int c) {
    method getColor (line 85) | private int getColor(int tile, int x, int y) {
    method paintTile (line 100) | private void paintTile(Graphics g, int tile, int offsetX, int offsetY) {
    method setRomImage (line 117) | void setRomImage(byte[] romImage) {
    method romImage (line 121) | public byte[] romImage() {
    method setGfxCharOffset (line 125) | void setGfxCharOffset(int gfxCharOffset) {
    method setFontOffset (line 129) | void setFontOffset(int fontOffset) {
    method mouseEntered (line 134) | public void mouseEntered(java.awt.event.MouseEvent e) {
    method mouseExited (line 137) | public void mouseExited(java.awt.event.MouseEvent e) {
    method mouseReleased (line 140) | public void mouseReleased(java.awt.event.MouseEvent e) {
    method mousePressed (line 143) | public void mousePressed(java.awt.event.MouseEvent e) {
    method mouseClicked (line 146) | public void mouseClicked(java.awt.event.MouseEvent e) {

FILE: src/main/java/fontEditor/TileEditor.java
  class TileEditor (line 11) | class TileEditor extends JPanel implements java.awt.event.MouseListener,...
    type TileChangedListener (line 15) | public interface TileChangedListener {
      method tileChanged (line 16) | void tileChanged();
    method TileEditor (line 28) | TileEditor() {
    method setRomImage (line 34) | void setRomImage(byte[] romImage) {
    method setFontOffset (line 38) | void setFontOffset(int offset) {
    method setGfxDataOffset (line 43) | void setGfxDataOffset(int offset) {
    method setTile (line 48) | void setTile(int tile) {
    method getTile (line 53) | int getTile() {
    method shiftUp (line 57) | void shiftUp(int tile) {
    method shiftDown (line 62) | void shiftDown(int tile) {
    method shiftRight (line 67) | void shiftRight(int tile) {
    method shiftLeft (line 72) | void shiftLeft(int tile) {
    method getColor (line 77) | private int getColor(int tile, int x, int y) {
    method switchColor (line 81) | private void switchColor(Graphics g, int c) {
    method getMinimumDimension (line 98) | private int getMinimumDimension() {
    method paintGrid (line 102) | private void paintGrid(Graphics g) {
    method paintComponent (line 119) | public void paintComponent(Graphics g) {
    method doMousePaint (line 137) | private void doMousePaint(java.awt.event.MouseEvent e) {
    method setColor (line 153) | private void setColor(int x, int y, int color) {
    method mouseEntered (line 157) | public void mouseEntered(java.awt.event.MouseEvent e) {
    method mouseExited (line 160) | public void mouseExited(java.awt.event.MouseEvent e) {
    method mouseReleased (line 163) | public void mouseReleased(java.awt.event.MouseEvent e) {
    method mousePressed (line 166) | public void mousePressed(java.awt.event.MouseEvent e) {
    method mouseClicked (line 169) | public void mouseClicked(java.awt.event.MouseEvent e) {
    method mouseMoved (line 173) | public void mouseMoved(java.awt.event.MouseEvent e) {
    method mouseDragged (line 176) | public void mouseDragged(java.awt.event.MouseEvent e) {
    method setColor (line 180) | void setColor(int color) {
    method setRightColor (line 185) | void setRightColor(int color) {
    method setTileChangedListener (line 190) | void setTileChangedListener(TileChangedListener l) {
    method copyTile (line 194) | void copyTile() {
    method generateShadedAndInvertedTiles (line 205) | void generateShadedAndInvertedTiles() {
    method pasteTile (line 209) | void pasteTile() {
    method tileChanged (line 225) | void tileChanged() {
    method readImage (line 231) | void readImage(String name, BufferedImage image) {
    method createImage (line 236) | BufferedImage createImage(boolean includeGfxCharacters) {

FILE: src/main/java/kitEditor/KitEditor.java
  class KitEditor (line 19) | public class KitEditor extends JFrame implements SamplePicker.Listener {
    type Listener (line 22) | public interface Listener {
      method saveRom (line 23) | void saveRom();
    method KitEditor (line 67) | public KitEditor(JFrame parent, Document document, Listener listener) {
    method addEnterHandler (line 112) | private void addEnterHandler(JSpinner spinner) {
    method setListeners (line 123) | private void setListeners() {
    method onHalfSpeedChanged (line 175) | private void onHalfSpeedChanged() {
    method reloadAllSamples (line 189) | private void reloadAllSamples() {
    method reloadSample (line 206) | private void reloadSample() {
    method onSpinnerChanged (line 224) | private void onSpinnerChanged() {
    method ask (line 276) | private double ask(String message, double value) {
    method addMenu (line 284) | private void addMenu() {
    method jbInit (line 328) | private void jbInit() {
    method createFileDrop (line 391) | private void createFileDrop() {
    method unSwizzle (line 424) | private static void unSwizzle(byte[] packedNibbles) {
    method getNibbles (line 444) | private byte[] getNibbles(int index) {
    method isBankSwizzled (line 467) | private boolean isBankSwizzled() {
    method bankVersion (line 471) | private byte bankVersion() {
    method versionOffset (line 478) | private int versionOffset() {
    method playSample (line 482) | @Override
    method updateSampleView (line 504) | private void updateSampleView() {
    method isKitBank (line 516) | private boolean isKitBank(int a_bank) {
    method isUninitializedBank (line 523) | private boolean isUninitializedBank(int a_bank) {
    method getKitName (line 530) | private String getKitName(int a_bank) {
    method updateRomView (line 543) | private void updateRomView() {
    method getSelectedUiBank (line 562) | private int getSelectedUiBank() {
    method getSelectedROMBank (line 569) | private int getSelectedROMBank() {
    method getROMOffsetForSelectedBank (line 585) | private int getROMOffsetForSelectedBank() {
    method updateBankView (line 589) | private void updateBankView() {
    method kitTooBig (line 620) | boolean kitTooBig() {
    method bytesFree (line 624) | private int bytesFree() {
    method updateKitSizeLabel (line 628) | private void updateKitSizeLabel() {
    method isEmpty (line 635) | private boolean isEmpty(Sample[] samples) {
    method bankBox_actionPerformed (line 644) | private void bankBox_actionPerformed() {
    method getRomSampleName (line 662) | private String getRomSampleName(int index) {
    method createSamplesFromRom (line 671) | private void createSamplesFromRom() {
    method showFileErrorMessage (line 687) | private void showFileErrorMessage(Exception e) {
    method saveKit (line 695) | private void saveKit() {
    method saveKitSettings (line 717) | private void saveKitSettings(File kitFile) throws IOException {
    method loadKit (line 744) | private void loadKit() {
    method loadKit (line 751) | private void loadKit(File kitFile) {
    method loadKitSettings (line 780) | private void loadKitSettings(File kitFile) throws IOException, Unsuppo...
    method dropExtension (line 820) | private String dropExtension(File f) {
    method createKit (line 831) | private void createKit() {
    method flushWavFiles (line 857) | private void flushWavFiles() {
    method renameKit (line 863) | private void renameKit(String s) {
    method firstFreeSampleSlot (line 877) | private int firstFreeSampleSlot() {
    method addSample (line 886) | private void addSample(File wavFile) {
    method renameSample (line 924) | private void renameSample(int sampleIndex, String sampleName) {
    method addSample (line 938) | private void addSample() {
    method compileKit (line 945) | private void compileKit() {
    method totalSampleSizeInBytes (line 983) | private int totalSampleSizeInBytes() {
    method dropSample (line 991) | private void dropSample() {
    method exportSample (line 1028) | private void exportSample() {
    method selectionChanged (line 1039) | @Override
    method maxTrim (line 1044) | private int maxTrim(Sample sample) {
    method updateTrimModel (line 1050) | private void updateTrimModel(Sample sample) {
    method updateButtonStates (line 1066) | private void updateButtonStates() {
    method trimAllSamples (line 1093) | private void trimAllSamples() {
    method duplicateSample (line 1162) | private void duplicateSample(Sample sample) {
    method pasteSample (line 1205) | private void pasteSample() {
    method dupeSample (line 1213) | @Override
    method deleteSample (line 1219) | @Override
    method replaceSample (line 1224) | @Override
    method renameSample (line 1255) | @Override
    method copySample (line 1261) | @Override
    class PadKeyHandler (line 1269) | private class PadKeyHandler implements KeyEventPostProcessor {
      method postProcessKeyEvent (line 1270) | @Override

FILE: src/main/java/kitEditor/Sample.java
  class Sample (line 7) | class Sample {
    method Sample (line 19) | public Sample(short[] iBuf, String iName) {
    method Sample (line 30) | public Sample(Sample s) {
    method getName (line 43) | public String getName() {
    method setName (line 47) | public void setName(String sampleName) {
    method lengthInSamples (line 51) | public int lengthInSamples() {
    method untrimmedLengthInSamples (line 55) | public int untrimmedLengthInSamples() {
    method untrimmedLengthInBytes (line 59) | public int untrimmedLengthInBytes() {
    method workSampleData (line 65) | public short[] workSampleData() {
    method lengthInBytes (line 69) | public int lengthInBytes() {
    method seekStart (line 75) | public void seekStart() {
    method read (line 79) | public short read() {
    method canAdjustVolume (line 83) | public boolean canAdjustVolume() {
    method createFromNibbles (line 91) | static Sample createFromNibbles(byte[] nibbles, String name) {
    method createFromWav (line 109) | public static Sample createFromWav(File file,
    method dupeSample (line 126) | public static Sample dupeSample(Sample sample) {
    method reload (line 130) | public void reload(boolean halfSpeed) throws IOException, UnsupportedA...
    method processSamples (line 139) | public void processSamples() {
    method trim (line 149) | private int[] trim(int[] intBuffer) {
    method headPos (line 172) | private int headPos(int[] buf) {
    method tailPos (line 182) | private int tailPos(int[] buf) {
    method toShortBuffer (line 192) | private short[] toShortBuffer(int[] intBuffer) {
    method toIntBuffer (line 202) | private int[] toIntBuffer(short[] shortBuffer) {
    method readSamples (line 210) | private static short[] readSamples(File file, boolean halfSpeed, doubl...
    method dither (line 231) | private void dither(int[] samples) {
    method normalize (line 244) | private void normalize(int[] samples) {
    method getVolumeDb (line 260) | public int getVolumeDb() {
    method setVolumeDb (line 264) | public void setVolumeDb(int value) {
    method getFile (line 268) | public File getFile() {
    method setTrim (line 272) | public void setTrim(int value) {
    method getTrim (line 277) | public int getTrim() {
    method setPitchSemitones (line 281) | public void setPitchSemitones(int value) {
    method getPitchSemitones (line 285) | public int getPitchSemitones() {
    method setDither (line 289) | public void setDither(boolean value) {
    method getDither (line 293) | public boolean getDither() {

FILE: src/main/java/kitEditor/SamplePicker.java
  class SamplePicker (line 13) | public class SamplePicker extends JPanel {
    type Listener (line 16) | interface Listener {
      method selectionChanged (line 17) | void selectionChanged();
      method playSample (line 18) | void playSample();
      method deleteSample (line 19) | void deleteSample();
      method dupeSample (line 20) | void dupeSample();
      method replaceSample (line 21) | void replaceSample();
      method renameSample (line 22) | void renameSample(String s);
      method copySample (line 23) | void copySample();
    class Pad (line 26) | class Pad extends JToggleButton {
      method Pad (line 28) | Pad(int id) {
    method SamplePicker (line 78) | SamplePicker() {
    method grabFocus (line 113) | @Override
    method createPad (line 118) | private Pad createPad() {
    method setListData (line 126) | public void setListData(String[] listData) {
    method setSelectedIndex (line 133) | public void setSelectedIndex(int selectedIndex) {
    method getSelectedIndex (line 147) | public int getSelectedIndex() {
    method getSelectedIndices (line 156) | public ArrayList<Integer> getSelectedIndices() {
    method addListSelectionListener (line 160) | public void addListSelectionListener(Listener listener) {

FILE: src/main/java/kitEditor/SampleView.java
  class SampleView (line 7) | public class SampleView extends Canvas {
    method setBufferContent (line 11) | public void setBufferContent(byte[] newBuffer, float duration) {
    method paint (line 17) | @Override
    method drawDuration (line 45) | private void drawDuration(Graphics2D g, int w, int h) {

FILE: src/main/java/kitEditor/Sound.java
  class Sound (line 12) | public class Sound {
    method unpackNibbles (line 17) | private static short[] unpackNibbles(byte[] gbSample) {
    method getClip (line 39) | private static Clip getClip() throws LineUnavailableException {
    method play (line 51) | static void play(byte[] gbSample, boolean halfSpeed) throws LineUnavai...
    method resampleForPlayback (line 61) | private static short[] resampleForPlayback(int srcRate, short[] src) {
    method toByteArray (line 70) | private static byte[] toByteArray(short[] waveData) {
    method stopAll (line 79) | static void stopAll() {
    method resample (line 85) | public static short[] resample(double inSampleRate, double outSampleRa...

FILE: src/main/java/kitEditor/WaveFile.java
  class WaveFile (line 7) | public class WaveFile {
    method write (line 8) | public static void write(short[] pcm, File f) throws IOException {

FILE: src/main/java/kitEditor/sbc.java
  class sbc (line 5) | class sbc {
    method compile (line 7) | public static void compile(byte[] dst, Sample[] samples, int[] byteLen...

FILE: src/main/java/lsdpatch/LSDPatcher.java
  class LSDPatcher (line 14) | public class LSDPatcher {
    method initUi (line 15) | private static void initUi() {
    method usage (line 35) | private static void usage() {
    method main (line 57) | public static void main(String[] args) {
    method useJLabelFontForMenus (line 81) | private static void useJLabelFontForMenus() {
    method processArguments (line 94) | private static void processArguments(String[] args) {

FILE: src/main/java/lsdpatch/MainWindow.java
  class MainWindow (line 23) | public class MainWindow extends JFrame implements IDocumentListener, Kit...
    method MainWindow (line 34) | MainWindow() {
    method openRomUpgradeTool (line 83) | private void openRomUpgradeTool() {
    method openSongManager (line 89) | private void openSongManager() {
    method openPaletteEditor (line 95) | private void openPaletteEditor() {
    method openFontEditor (line 101) | private void openFontEditor() {
    method addSelectors (line 109) | private void addSelectors(JPanel panel) {
    method resetRomTextField (line 145) | private void resetRomTextField() {
    method resetSavTextField (line 149) | private void resetSavTextField() {
    method onBrowseRomButtonPress (line 153) | private void onBrowseRomButtonPress() {
    method onBrowseSavButtonPress (line 196) | private void onBrowseSavButtonPress() {
    method updateButtonsFromTextFields (line 223) | void updateButtonsFromTextFields() {
    method onDocumentDirty (line 240) | public void onDocumentDirty(boolean dirty) {
    method updateTitle (line 246) | private void updateTitle() {
    method onSave (line 257) | private void onSave(boolean saveSavFile) {
    method saveRom (line 294) | @Override

FILE: src/main/java/lsdpatch/NewVersionChecker.java
  class NewVersionChecker (line 9) | public class NewVersionChecker {
    method getCurrentVersion (line 10) | public static String getCurrentVersion() {
    method checkGithub (line 18) | public static void checkGithub(JFrame parent) {

FILE: src/main/java/lsdpatch/RomUpgradeTool.java
  class RomUpgradeTool (line 20) | public class RomUpgradeTool extends JFrame {
    method RomUpgradeTool (line 32) | RomUpgradeTool(JFrame parent, Document document) {
    method versionCompare (line 77) | private boolean versionCompare(String localVersion, String remoteVersi...
    method upgrade (line 83) | private void upgrade(String basePath) {
    method localVersion (line 124) | private String localVersion() {
    method fetchLatestRemoteVersion (line 140) | private String fetchLatestRemoteVersion(String basePath) throws IOExce...
    method importAll (line 152) | private void importAll() {
    method importPalettes (line 183) | private boolean importPalettes() {
    method importFonts (line 232) | private boolean importFonts() {
    method isKitBank (line 273) | private boolean isKitBank(int a_bank) {
    method isEmptyKitBank (line 280) | private boolean isEmptyKitBank(int a_bank) {
    method importKits (line 287) | private int importKits() {
    method upgradeFromSelectedFile (line 318) | private void upgradeFromSelectedFile() {

FILE: src/main/java/lsdpatch/WwwUtil.java
  class WwwUtil (line 12) | public class WwwUtil {
    method fetchWwwPage (line 13) | static String fetchWwwPage(URL url) throws IOException {
    method openInBrowser (line 29) | static void openInBrowser(String path) {

FILE: src/main/java/paletteEditor/ColorPicker.java
  class ColorPicker (line 8) | public class ColorPicker extends JPanel implements HuePanel.Listener, Sa...
    type Listener (line 9) | interface Listener {
      method colorChanged (line 10) | void colorChanged(int r, int g, int b);
    method ColorPicker (line 18) | public ColorPicker() {
    method setColor (line 31) | public void setColor(RGB555 rgb) {
    method subscribe (line 49) | public void subscribe(Listener listener) {
    method broadcastColor (line 53) | private void broadcastColor() {
    method hueChanged (line 69) | @Override
    method saturationBrightnessChanged (line 74) | @Override

FILE: src/main/java/paletteEditor/ColorUtil.java
  class ColorUtil (line 6) | public class ColorUtil {
    type ColorSpace (line 12) | enum ColorSpace {
    method setColorSpace (line 19) | public static void setColorSpace(ColorSpace colorSpace_) {
    method to8bit (line 23) | public static int to8bit(int color) {
    method colorCorrect (line 32) | public static int colorCorrect(java.awt.Color c) {
    method colorCorrect (line 36) | public static int colorCorrect(int r, int g, int b) {

FILE: src/main/java/paletteEditor/HuePanel.java
  class HuePanel (line 11) | class HuePanel extends JPanel implements MouseListener, MouseMotionListe...
    type Listener (line 17) | public interface Listener {
      method hueChanged (line 18) | void hueChanged();
    method HuePanel (line 21) | HuePanel() {
    method setHue (line 27) | void setHue(float hue) {
    method paintComponent (line 34) | @Override
    method hue (line 59) | public float hue() {
    method mouseClicked (line 69) | @Override
    method mousePressed (line 73) | @Override
    method mouseReleased (line 79) | @Override
    method mouseEntered (line 84) | @Override
    method mouseExited (line 88) | @Override
    method mouseDragged (line 92) | @Override
    method mouseMoved (line 101) | @Override
    method subscribe (line 105) | public void subscribe(Listener listener) {

FILE: src/main/java/paletteEditor/PaletteEditor.java
  class PaletteEditor (line 19) | public class PaletteEditor extends JFrame implements SwatchPair.Listener {
    method setupMenuBar (line 46) | private void setupMenuBar() {
    method PaletteEditor (line 80) | public PaletteEditor(JFrame parent, Document document) {
    method songImagePressed (line 201) | private void songImagePressed(MouseEvent e) {
    method instrImagePressed (line 205) | private void instrImagePressed(MouseEvent e) {
    method selectColor (line 209) | private void selectColor(int rgb) {
    method setRomImage (line 249) | private void setRomImage(byte[] romImage) {
    method selectedPalette (line 262) | private int selectedPalette() {
    method color (line 270) | private java.awt.Color color(int offset) {
    method selectedPaletteOffset (line 284) | private int selectedPaletteOffset() {
    method updateRomFromSwatches (line 288) | private void updateRomFromSwatches() {
    method firstColor (line 292) | private java.awt.Color firstColor(int colorSet) {
    method secondColor (line 299) | private java.awt.Color secondColor(int colorSet) {
    method midColor (line 306) | private java.awt.Color midColor(int colorSet) {
    method paletteName (line 313) | private String paletteName(int palette) {
    method setPaletteName (line 324) | private void setPaletteName(int palette, String name) {
    method populatePaletteSelector (line 345) | private void populatePaletteSelector() {
    method modifyUsingPalette (line 355) | private java.awt.image.BufferedImage modifyUsingPalette(java.awt.image...
    method updateSongAndInstrScreens (line 439) | private void updateSongAndInstrScreens() {
    method updateSwatches (line 444) | private void updateSwatches(int colorSetIndex, SwatchPair swatchPair) {
    method updateAllSwatches (line 450) | private void updateAllSwatches() {
    method swatchSelected (line 463) | @Override
    method swatchChanged (line 476) | @Override
    method savePalette (line 487) | private void savePalette(String path) {
    method loadPalette (line 510) | private void loadPalette(java.io.File file) {
    method showOpenDialog (line 534) | private void showOpenDialog() {
    method showSaveDialog (line 541) | private void showSaveDialog() {
    method copyPalette (line 548) | private void copyPalette() {
    method areDuplicateNames (line 558) | private boolean areDuplicateNames() {
    method addNumberToPaletteName (line 570) | private void addNumberToPaletteName(int paletteIndex) {
    method onPaletteSelected (line 582) | private void onPaletteSelected() {
    method onPaletteRenamed (line 590) | private void onPaletteRenamed() {
    method pastePalette (line 600) | private void pastePalette() {

FILE: src/main/java/paletteEditor/RGB555.java
  class RGB555 (line 3) | public class RGB555 {
    method RGB555 (line 8) | public RGB555() {
    method RGB555 (line 14) | public RGB555(int r, int g, int b) {
    method setR (line 20) | public void setR(int r) {
    method setG (line 25) | public void setG(int g) {
    method setB (line 30) | public void setB(int b) {
    method r (line 35) | public int r() {
    method g (line 39) | public int g() {
    method b (line 43) | public int b() {

FILE: src/main/java/paletteEditor/SaturationBrightnessPanel.java
  class SaturationBrightnessPanel (line 10) | public class SaturationBrightnessPanel extends JPanel implements HuePane...
    method printRGB555 (line 12) | public void printRGB555(RGB555 rgb555) {
    type Listener (line 16) | interface Listener {
      method saturationBrightnessChanged (line 17) | void saturationBrightnessChanged();
    method SaturationBrightnessPanel (line 28) | public SaturationBrightnessPanel(HuePanel huePanel) {
    method setSaturationBrightness (line 36) | public void setSaturationBrightness(float saturation, float brightness) {
    method subscribe (line 45) | public void subscribe(Listener listener) {
    method hueChanged (line 49) | @Override
    method saturation (line 54) | public float saturation() {
    method brightness (line 58) | public float brightness() {
    method paintComponent (line 62) | @Override
    method mouseClicked (line 99) | @Override
    method mousePressed (line 103) | @Override
    method mouseReleased (line 109) | @Override
    method mouseEntered (line 114) | @Override
    method mouseExited (line 118) | @Override
    method mouseDragged (line 122) | @Override
    method mouseMoved (line 132) | @Override

FILE: src/main/java/paletteEditor/ScreenShotColors.java
  class ScreenShotColors (line 3) | public class ScreenShotColors {

FILE: src/main/java/paletteEditor/Swatch.java
  class Swatch (line 8) | public class Swatch extends JPanel {
    method rgb (line 9) | public RGB555 rgb() {
    type Listener (line 13) | public interface Listener {
      method swatchChanged (line 14) | void swatchChanged();
    method Swatch (line 19) | public Swatch() {
    method r (line 24) | public int r() {
    method g (line 28) | public int g() {
    method b (line 32) | public int b() {
    method setRGB (line 36) | public void setRGB(int r, int g, int b) {
    method randomize (line 49) | public void randomize(Random rand) {
    method addListener (line 53) | public void addListener(Listener listener) {
    method deselect (line 57) | public void deselect() {

FILE: src/main/java/paletteEditor/SwatchPair.java
  class SwatchPair (line 9) | class SwatchPair implements Swatch.Listener {
    type Listener (line 10) | public interface Listener {
      method swatchSelected (line 11) | void swatchSelected(Swatch swatch);
      method swatchChanged (line 12) | void swatchChanged();
    method addListener (line 15) | public void addListener(Listener listener) {
    method swatchChanged (line 18) | @Override
    method SwatchPair (line 28) | public SwatchPair() {
    method registerToPanel (line 35) | public void registerToPanel(JPanel panel, String entryName) {
    method selectBackground (line 42) | public void selectBackground() {
    method selectForeground (line 46) | public void selectForeground() {
    method select (line 50) | private void select(Swatch swatch) {
    method createSwatches (line 56) | private void createSwatches() {
    method setColors (line 72) | public void setColors(Color foregroundColor, Color backgroundColor) {
    method randomize (line 81) | public void randomize(Random rand) {
    method findMidTone (line 86) | private RGB555 findMidTone(RGB555 bg, RGB555 fg) {
    method midToneTarget (line 95) | private Color midToneTarget(RGB555 bg, RGB555 fg) {
    method findBestRgb (line 111) | private RGB555 findBestRgb(RGB555 start, Color target) {
    method add (line 127) | private void add(TreeMap<Double, RGB555> map, Color target, RGB555 sta...
    method diff (line 138) | private static double diff(Color target, int r, int g, int b) {
    method writeToRom (line 158) | public void writeToRom(byte[] romImage, int offset) {
    method deselect (line 181) | public void deselect() {

FILE: src/main/java/paletteEditor/SwatchPanel.java
  class SwatchPanel (line 10) | public class SwatchPanel extends JPanel implements SwatchPair.Listener {
    method SwatchPanel (line 24) | public SwatchPanel() {
    type CommandState (line 48) | enum CommandState {
    method swapStart (line 54) | private void swapStart() {
    method cloneStart (line 61) | private void cloneStart() {
    method updateCursor (line 69) | private void updateCursor() {
    method addListener (line 75) | public void addListener(SwatchPair.Listener listener) {
    method add (line 79) | public void add(SwatchPair swatchPair, String swatchPairName) {
    method randomize (line 85) | public void randomize() {
    method handleClone (line 91) | private void handleClone(Swatch swatch) {
    method handleSwap (line 100) | private void handleSwap(Swatch swatch) {
    method swatchSelected (line 113) | @Override
    method swatchChanged (line 130) | @Override
    method writeToRom (line 137) | public void writeToRom(byte[] romImage, int selectedPaletteOffset) {

FILE: src/main/java/songManager/SongManager.java
  class SongManager (line 20) | public class SongManager extends JFrame implements ListSelectionListener {
    method SongManager (line 32) | public SongManager(JFrame parent, Document document) {
    method clearSlotButton_actionPerformed (line 83) | public void clearSlotButton_actionPerformed() {
    method updateRamUsageIndicator (line 97) | private void updateRamUsageIndicator() {
    method exportLsdSngButton_actionPerformed (line 104) | public void exportLsdSngButton_actionPerformed() {
    method addLsdSngButton_actionPerformed (line 170) | public void addLsdSngButton_actionPerformed() {
    method valueChanged (line 207) | @Override

FILE: src/main/java/structures/LSDJFont.java
  class LSDJFont (line 11) | public class LSDJFont extends ROMDataManipulator {
    method setGfxDataOffset (line 29) | public void setGfxDataOffset(int gfxDataOffset) {
    method getTileDataLocation (line 33) | private int getTileDataLocation(int index) {
    method getGfxTileDataLocation (line 46) | private int getGfxTileDataLocation(int index) {
    method getPixel (line 55) | public int getPixel(int x, int y) {
    method getTilePixel (line 71) | public int getTilePixel(int tile, int localX, int localY) {
    method setPixel (line 75) | private void setPixel(int x, int y, int color) {
    method setTilePixel (line 95) | public void setTilePixel(int tile, int localX, int localY, int color) {
    method rotateTileUp (line 100) | public void rotateTileUp(int tile) {
    method rotateTileDown (line 114) | public void rotateTileDown(int tile) {
    method rotateTileRight (line 128) | public void rotateTileRight(int tile) {
    method rotateTileLeft (line 137) | public void rotateTileLeft(int tile) {
    method generateShadedAndInvertedTiles (line 150) | public void generateShadedAndInvertedTiles() {
    method generateInvertedTileVariant (line 157) | public void generateInvertedTileVariant(int index) {
    method generateShadedTileVariant (line 171) | public void generateShadedTileVariant(int index) {
    method grayIndexToColor (line 189) | private int grayIndexToColor(int index) {
    method loadImageData (line 204) | public String loadImageData(String name, BufferedImage image) {
    method saveDataToImage (line 236) | public BufferedImage saveDataToImage(Boolean includeGfxCharacters) {

FILE: src/main/java/structures/ROMDataManipulator.java
  class ROMDataManipulator (line 6) | public abstract class ROMDataManipulator {
    method setRomImage (line 11) | public void setRomImage(byte[] romImage) {
    method getDataOffset (line 15) | public int getDataOffset() {
    method setDataOffset (line 19) | public  void setDataOffset(int dataOffset) {

FILE: src/main/java/utils/CommandLineFunctions.java
  class CommandLineFunctions (line 14) | public class CommandLineFunctions {
    method pngToFont (line 15) | public static void pngToFont(String name, String pngFile, String fntFi...
    method fontToPng (line 38) | public static void fontToPng(String fntFile, String pngFile) {
    method extractFontToPng (line 53) | public static void extractFontToPng(String romFileName, int numFont, b...
    method loadPngToRom (line 80) | public static void loadPngToRom(String romFileName, String imageFileNa...
    method isRomBankAKit (line 111) | private static boolean isRomBankAKit(int bankIndex, byte[] romImage) {
    method isRomBankEmpty (line 119) | private static boolean isRomBankEmpty(int bankIndex, byte[] romImage) {
    method clearKitBank (line 126) | private static void clearKitBank(int bankIndex, byte[] romImage) {
    method copyAllCustomizations (line 133) | public static void copyAllCustomizations(String originFileName, String...

FILE: src/main/java/utils/EditorPreferences.java
  class EditorPreferences (line 7) | public class EditorPreferences {
    method userDir (line 8) | private static String userDir() {
    method getKey (line 12) | public static String getKey(String name, String defaultValue) {
    method putKey (line 16) | public static void putKey(String name, String value) {
    method lastPath (line 20) | public static String lastPath(String extension) {
    method lastDirectory (line 24) | public static String lastDirectory(String extension) {
    method setLastPath (line 28) | public static void setLastPath(String extension, String value) {
    method clearAll (line 32) | public static void clearAll() {

FILE: src/main/java/utils/FileDialogLauncher.java
  class CustomFileFilter (line 7) | class CustomFileFilter implements java.io.FilenameFilter {
    method CustomFileFilter (line 8) | CustomFileFilter(String[] fileExtensions) {
    method accept (line 12) | @Override
  class FileDialogLauncher (line 25) | public class FileDialogLauncher {
    method load (line 26) | public static File load(JFrame parent, String title, String fileExtens...
    method load (line 30) | public static File load(JFrame parent, String title, String[] fileExte...
    method save (line 34) | public static File save(JFrame parent, String title, String fileExtens...
    method save (line 38) | public static File save(JFrame parent, String title, String[] fileExte...
    method setFilenameFilter (line 42) | private static void setFilenameFilter(FileDialog fileDialog, String[] ...
    method open (line 56) | private static File open(JFrame parent, String title, String[] fileExt...

FILE: src/main/java/utils/FileDrop.java
  class FileDrop (line 49) | public class FileDrop
    method FileDrop (line 71) | public FileDrop(
    method FileDrop (line 94) | public FileDrop(
    method FileDrop (line 119) | public FileDrop(
    method FileDrop (line 149) | public FileDrop(
    method FileDrop (line 172) | public FileDrop(
    method FileDrop (line 198) | public FileDrop(
    method FileDrop (line 226) | public FileDrop(
    method FileDrop (line 257) | public FileDrop(
    method supportsDnD (line 421) | private static boolean supportsDnD()
    method createFileArray (line 441) | private static File[] createFileArray(BufferedReader bReader, PrintStr...
    method makeDropTarget (line 467) | private void makeDropTarget( final java.io.PrintStream out, final java...
    method isDragOk (line 514) | private boolean isDragOk( final java.io.PrintStream out, final java.aw...
    method log (line 548) | private static void log( java.io.PrintStream out, String message )
    method remove (line 567) | public static boolean remove( java.awt.Component c)
    method remove (line 583) | public static boolean remove( java.io.PrintStream out, java.awt.Compon...
    type Listener (line 620) | public static interface Listener {
      method filesDropped (line 628) | public abstract void filesDropped( java.io.File[] files );
    class Event (line 649) | public static class Event extends java.util.EventObject {
      method Event (line 662) | public Event( java.io.File[] files, Object source ) {
      method getFiles (line 674) | public java.io.File[] getFiles() {
    class TransferableObject (line 727) | public static class TransferableObject implements java.awt.datatransfe...
      method TransferableObject (line 768) | public TransferableObject( Object data )
      method TransferableObject (line 785) | public TransferableObject( Fetcher fetcher )
      method TransferableObject (line 804) | public TransferableObject( Class dataClass, Fetcher fetcher )
      method getCustomDataFlavor (line 817) | public java.awt.datatransfer.DataFlavor getCustomDataFlavor()
      method getTransferDataFlavors (line 835) | public java.awt.datatransfer.DataFlavor[] getTransferDataFlavors()
      method getTransferData (line 863) | public Object getTransferData( java.awt.datatransfer.DataFlavor flav...
      method isDataFlavorSupported (line 889) | public boolean isDataFlavorSupported( java.awt.datatransfer.DataFlav...
      type Fetcher (line 919) | public static interface Fetcher
        method getObject (line 928) | public abstract Object getObject();

FILE: src/main/java/utils/FontIO.java
  class FontIO (line 9) | public class FontIO {
    method loadFnt (line 11) | static void loadFnt(File file, byte[] array) throws IOException {
    method loadFnt (line 15) | public static String loadFnt(File file, byte[] array, int arrayOffset)...
    method saveFnt (line 33) | static void saveFnt(File file, String fontName, byte[] array) throws I...
    method saveFnt (line 37) | public static void saveFnt(File file, String fontName, byte[] array, i...

FILE: src/main/java/utils/GlobalHolder.java
  class GlobalHolder (line 18) | public class GlobalHolder {
    method lazyInstantiation (line 21) | private static void lazyInstantiation() {
    method set (line 26) | public static void set(Object object) {
    method set (line 31) | public static <C> void set(C object, Class<C> cls) {
    method set (line 36) | public static <C> void set(C object, Class<C> cls, String namespace) {
    method set (line 41) | public static void set(Object object, String namespace) {
    method get (line 46) | @SuppressWarnings("unchecked")
    method get (line 52) | @SuppressWarnings("unchecked")
    method release (line 58) | public static <C> C release(Class<C> cls) {

FILE: src/main/java/utils/RomUtilities.java
  class RomUtilities (line 5) | public class RomUtilities {
    method findGrayscalePaletteNames (line 14) | private static int findGrayscalePaletteNames(byte[] romImage)
    method findScreenBackgroundData (line 39) | private static int findScreenBackgroundData(byte[] romImage)
    method getNumberOfPalettes (line 75) | public static int getNumberOfPalettes(byte[] romImage)
    method findPaletteOffset (line 91) | public static int findPaletteOffset(byte[] romImage) {
    method findPaletteNameOffset (line 103) | public static int findPaletteNameOffset(byte[] romImage) {
    method findGfxFontOffset (line 115) | public static int findGfxFontOffset(byte[] romImage) {
    method findFontOffset (line 124) | public static int findFontOffset(byte[] romImage) {
    method findFontNameOffset (line 131) | public static int findFontNameOffset(byte[] romImage) {
    method getFontName (line 141) | public static String getFontName(byte[] romImage, int font) {
    method setFontName (line 151) | public static void setFontName(byte[] romImage, int fontIndex, String ...
    method fixChecksum (line 164) | public static void fixChecksum(byte[] romImage) {
    method validatePaletteData (line 183) | public static boolean validatePaletteData(byte[] romImage) {

FILE: src/main/java/utils/StretchIcon.java
  class StretchIcon (line 28) | public class StretchIcon extends ImageIcon
    method StretchIcon (line 47) | public StretchIcon(byte[] imageData)
    method StretchIcon (line 61) | public StretchIcon(byte[] imageData, boolean proportionate)
    method StretchIcon (line 75) | public StretchIcon(byte[] imageData, String description)
    method StretchIcon (line 91) | public StretchIcon(byte[] imageData, String description, boolean propo...
    method StretchIcon (line 104) | public StretchIcon(Image image)
    method StretchIcon (line 118) | public StretchIcon(Image image, boolean proportionate)
    method StretchIcon (line 132) | public StretchIcon(Image image, String description)
    method StretchIcon (line 147) | public StretchIcon(Image image, String description, boolean proportion...
    method StretchIcon (line 160) | public StretchIcon(String filename)
    method StretchIcon (line 174) | public StretchIcon(String filename, boolean proportionate)
    method StretchIcon (line 188) | public StretchIcon(String filename, String description)
    method StretchIcon (line 203) | public StretchIcon(String filename, String description, boolean propor...
    method StretchIcon (line 216) | public StretchIcon(URL location)
    method StretchIcon (line 230) | public StretchIcon(URL location, boolean proportionate)
    method StretchIcon (line 244) | public StretchIcon(URL location, String description)
    method StretchIcon (line 259) | public StretchIcon(URL location, String description, boolean proportio...
    method paintIcon (line 281) | @Override
    method getIconWidth (line 323) | @Override
    method getIconHeight (line 334) | @Override

FILE: src/test/java/Document/DocumentTest.java
  class DocumentTest (line 11) | class DocumentTest {
    method savFile (line 13) | @Test
    method setSavFile (line 19) | @Test

FILE: src/test/java/Document/LSDSavFileTest.java
  class LSDSavFileTest (line 12) | class LSDSavFileTest {
    method createLsdSavFile (line 15) | @BeforeEach
    method isValid_addSongsUntilOutOfBlocks (line 22) | @Test
    method testClone (line 42) | @Test
    method saveAs (line 50) | @Test

FILE: src/test/java/kitEditor/SampleTest.java
  class SampleTest (line 11) | class SampleTest {
    method createFromWav (line 13) | @Test
    method decreaseVolume (line 40) | @Test
    method trim (line 64) | @Test
    method pitch (line 84) | @Test
Condensed preview — 58 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (373K chars).
[
  {
    "path": ".gitattributes",
    "chars": 12,
    "preview": "* text=auto\n"
  },
  {
    "path": ".github/workflows/maven.yml",
    "chars": 542,
    "preview": "# This workflow will build a Java project with Maven\n# For more information see: https://help.github.com/actions/languag"
  },
  {
    "path": ".gitignore",
    "chars": 89,
    "preview": "# Maven\ntarget\n\n# IntelliJ IDEA\n.idea\n.settings\n*.iml\n\n*.gb\n*.sav\n*.wav\n*.raw\n./*.lsdprj\n"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 13660,
    "preview": "# Changelog\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Changel"
  },
  {
    "path": "LICENSE",
    "chars": 3344,
    "preview": "Copyright (C) 2001 by Johan Kotlinski, 2017 by Florian Dormont\n\nPermission is hereby granted, free of charge, to any per"
  },
  {
    "path": "README.md",
    "chars": 870,
    "preview": "# LSDPatcher\n\nA tool for modifying songs, samples, fonts and palettes on [Little Sound Dj][lsdj] (LSDj) ROM images and s"
  },
  {
    "path": "pom.xml",
    "chars": 3127,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www"
  },
  {
    "path": "src/main/java/Document/Document.java",
    "chars": 3257,
    "preview": "package Document;\n\nimport utils.EditorPreferences;\nimport utils.RomUtilities;\n\nimport java.io.File;\nimport java.io.IOExc"
  },
  {
    "path": "src/main/java/Document/IDocumentListener.java",
    "chars": 99,
    "preview": "package Document;\n\npublic interface IDocumentListener {\n    void onDocumentDirty(boolean dirty);\n}\n"
  },
  {
    "path": "src/main/java/Document/LSDSavFile.java",
    "chars": 25446,
    "preview": "package Document;\n\nimport utils.RomUtilities;\n\nimport java.io.*;\nimport java.util.*;\nimport javax.swing.*;\n\npublic class"
  },
  {
    "path": "src/main/java/com/laszlosystems/libresample4j/FilterKit.java",
    "chars": 8986,
    "preview": "/******************************************************************************\n *\n * libresample4j\n * Copyright (c) 200"
  },
  {
    "path": "src/main/java/com/laszlosystems/libresample4j/Resampler.java",
    "chars": 16760,
    "preview": "/******************************************************************************\n *\n * libresample4j\n * Copyright (c) 200"
  },
  {
    "path": "src/main/java/com/laszlosystems/libresample4j/SampleBuffers.java",
    "chars": 1744,
    "preview": "/******************************************************************************\n *\n * libresample4j\n * Copyright (c) 200"
  },
  {
    "path": "src/main/java/fontEditor/.gitignore",
    "chars": 492,
    "preview": "/ChangeEventListener.class\n/FontEditor$1.class\n/FontEditor$2.class\n/FontEditor$3.class\n/FontEditor$4.class\n/FontEditor$5"
  },
  {
    "path": "src/main/java/fontEditor/ChangeEventListener.java",
    "chars": 208,
    "preview": "package fontEditor;\n\nabstract class ChangeEventListener {\n    public enum ChangeEventMouseSide {\n        LEFT,\n        R"
  },
  {
    "path": "src/main/java/fontEditor/FontEditor.java",
    "chars": 14211,
    "preview": "package fontEditor;\n\nimport java.awt.Dimension;\nimport java.awt.GridBagConstraints;\nimport java.awt.GridBagLayout;\nimpor"
  },
  {
    "path": "src/main/java/fontEditor/FontEditorColorSelector.java",
    "chars": 3991,
    "preview": "package fontEditor;\n\nimport java.awt.Color;\nimport java.awt.Dimension;\nimport java.awt.event.MouseEvent;\nimport java.awt"
  },
  {
    "path": "src/main/java/fontEditor/FontMap.java",
    "chars": 5047,
    "preview": "package fontEditor;\n\nimport java.awt.*;\n\nimport javax.swing.JPanel;\n\nimport structures.LSDJFont;\n\npublic class FontMap e"
  },
  {
    "path": "src/main/java/fontEditor/TileEditor.java",
    "chars": 6445,
    "preview": "package fontEditor;\n\nimport java.awt.*;\nimport java.awt.image.BufferedImage;\n\nimport javax.swing.JPanel;\nimport javax.sw"
  },
  {
    "path": "src/main/java/kitEditor/KitEditor.java",
    "chars": 46201,
    "preview": "package kitEditor;\n\nimport Document.Document;\nimport com.laszlosystems.libresample4j.Resampler;\nimport net.miginfocom.sw"
  },
  {
    "path": "src/main/java/kitEditor/Sample.java",
    "chars": 8491,
    "preview": "package kitEditor;\n\nimport java.io.*;\nimport java.util.Random;\nimport javax.sound.sampled.*;\n\nclass Sample {\n    private"
  },
  {
    "path": "src/main/java/kitEditor/SamplePicker.java",
    "chars": 5616,
    "preview": "package kitEditor;\n\nimport net.miginfocom.swing.MigLayout;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.awt.eve"
  },
  {
    "path": "src/main/java/kitEditor/SampleView.java",
    "chars": 1611,
    "preview": "package kitEditor;\n\nimport java.awt.*;\nimport java.awt.geom.GeneralPath;\nimport java.util.Locale;\n\npublic class SampleVi"
  },
  {
    "path": "src/main/java/kitEditor/Sound.java",
    "chars": 3990,
    "preview": "// Copyright (C) 2001, Johan Kotlinski\n\npackage kitEditor;\n\nimport com.laszlosystems.libresample4j.Resampler;\n\nimport ja"
  },
  {
    "path": "src/main/java/kitEditor/WaveFile.java",
    "chars": 1647,
    "preview": "package kitEditor;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.RandomAccessFile;\n\npublic class Wave"
  },
  {
    "path": "src/main/java/kitEditor/sbc.java",
    "chars": 1751,
    "preview": "package kitEditor;\n\n// Sample bank creator.\n\nclass sbc {\n\n    public static void compile(byte[] dst, Sample[] samples, i"
  },
  {
    "path": "src/main/java/lsdpatch/LSDPatcher.java",
    "chars": 5472,
    "preview": "// Copyright (C) 2001, Johan Kotlinski\n\npackage lsdpatch;\n\nimport utils.CommandLineFunctions;\nimport utils.GlobalHolder;"
  },
  {
    "path": "src/main/java/lsdpatch/MainWindow.java",
    "chars": 10380,
    "preview": "// Copyright (C) 2020, Johan Kotlinski\n\npackage lsdpatch;\n\nimport Document.*;\nimport fontEditor.FontEditor;\nimport kitEd"
  },
  {
    "path": "src/main/java/lsdpatch/NewVersionChecker.java",
    "chars": 1257,
    "preview": "package lsdpatch;\n\nimport utils.GlobalHolder;\n\nimport javax.swing.*;\nimport java.io.IOException;\nimport java.net.URL;\n\np"
  },
  {
    "path": "src/main/java/lsdpatch/RomUpgradeTool.java",
    "chars": 15089,
    "preview": "// Copyright (C) 2020, Johan Kotlinski\n\npackage lsdpatch;\n\nimport Document.Document;\nimport net.miginfocom.swing.MigLayo"
  },
  {
    "path": "src/main/java/lsdpatch/WwwUtil.java",
    "chars": 910,
    "preview": "package lsdpatch;\n\nimport java.awt.*;\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.InputStr"
  },
  {
    "path": "src/main/java/paletteEditor/ColorPicker.java",
    "chars": 2031,
    "preview": "package paletteEditor;\n\nimport net.miginfocom.swing.MigLayout;\n\nimport javax.swing.*;\nimport java.awt.*;\n\npublic class C"
  },
  {
    "path": "src/main/java/paletteEditor/ColorUtil.java",
    "chars": 2090,
    "preview": "package paletteEditor;\n\nimport static java.lang.Math.pow;\nimport static java.lang.Math.round;\n\npublic class ColorUtil {\n"
  },
  {
    "path": "src/main/java/paletteEditor/HuePanel.java",
    "chars": 2833,
    "preview": "package paletteEditor;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.awt.event.MouseEvent;\nimport java.awt.event"
  },
  {
    "path": "src/main/java/paletteEditor/PaletteEditor.java",
    "chars": 22570,
    "preview": "package paletteEditor;\n\nimport java.awt.*;\n\nimport java.awt.color.ColorSpace;\nimport java.awt.event.*;\nimport java.awt.i"
  },
  {
    "path": "src/main/java/paletteEditor/RGB555.java",
    "chars": 667,
    "preview": "package paletteEditor;\n\npublic class RGB555 {\n    int r;\n    int g;\n    int b;\n\n    public RGB555() {\n        r = -1;\n  "
  },
  {
    "path": "src/main/java/paletteEditor/SaturationBrightnessPanel.java",
    "chars": 3901,
    "preview": "package paletteEditor;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.awt.event.MouseEvent;\nimport java.awt.event"
  },
  {
    "path": "src/main/java/paletteEditor/ScreenShotColors.java",
    "chars": 885,
    "preview": "package paletteEditor;\n\npublic class ScreenShotColors {\n        static final public int NORMAL_BG = 0xff1a4577;\n        "
  },
  {
    "path": "src/main/java/paletteEditor/Swatch.java",
    "chars": 1440,
    "preview": "package paletteEditor;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.util.LinkedList;\nimport java.util.Random;\n\n"
  },
  {
    "path": "src/main/java/paletteEditor/SwatchPair.java",
    "chars": 6293,
    "preview": "package paletteEditor;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.awt.event.MouseAdapter;\nimport java.awt.eve"
  },
  {
    "path": "src/main/java/paletteEditor/SwatchPanel.java",
    "chars": 4593,
    "preview": "package paletteEditor;\n\nimport net.miginfocom.swing.MigLayout;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.uti"
  },
  {
    "path": "src/main/java/songManager/SongManager.java",
    "chars": 8508,
    "preview": "package songManager;\n\nimport Document.Document;\nimport Document.LSDSavFile;\nimport net.miginfocom.swing.MigLayout;\nimpor"
  },
  {
    "path": "src/main/java/structures/LSDJFont.java",
    "chars": 9928,
    "preview": "package structures;\n\nimport java.awt.Color;\nimport java.awt.image.BufferedImage;\n\n/**\n * Helper class to access and mani"
  },
  {
    "path": "src/main/java/structures/ROMDataManipulator.java",
    "chars": 492,
    "preview": "package structures;\n\n/**\n * Base class to centralize the concept of having an access to a ROM's binary data for edition."
  },
  {
    "path": "src/main/java/utils/CommandLineFunctions.java",
    "chars": 10212,
    "preview": "package utils;\n\nimport java.awt.image.BufferedImage;\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.Ran"
  },
  {
    "path": "src/main/java/utils/EditorPreferences.java",
    "chars": 1092,
    "preview": "package utils;\n\nimport java.io.File;\nimport java.util.prefs.BackingStoreException;\nimport java.util.prefs.Preferences;\n\n"
  },
  {
    "path": "src/main/java/utils/FileDialogLauncher.java",
    "chars": 3588,
    "preview": "package utils;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.io.File;\n\nclass CustomFileFilter implements java.io"
  },
  {
    "path": "src/main/java/utils/FileDrop.java",
    "chars": 37533,
    "preview": "package utils;\n\nimport java.awt.datatransfer.DataFlavor;\nimport java.io.BufferedReader;\nimport java.io.File;\nimport java"
  },
  {
    "path": "src/main/java/utils/FontIO.java",
    "chars": 1538,
    "preview": "package utils;\n\nimport java.io.File;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\n\nimport structures.LSD"
  },
  {
    "path": "src/main/java/utils/GlobalHolder.java",
    "chars": 1993,
    "preview": "package utils;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * Singletons is an useful template but it does hav"
  },
  {
    "path": "src/main/java/utils/RomUtilities.java",
    "chars": 6394,
    "preview": "package utils;\n\nimport structures.LSDJFont;\n\npublic class RomUtilities {\n    public static final int BANK_COUNT = 64;\n  "
  },
  {
    "path": "src/main/java/utils/StretchIcon.java",
    "chars": 11337,
    "preview": "package utils;\n\nimport java.awt.Component;\nimport java.awt.Container;\nimport java.awt.Graphics;\nimport java.awt.Graphics"
  },
  {
    "path": "src/main/resources/META-INF/MANIFEST.MF",
    "chars": 55,
    "preview": "Manifest-Version: 1.0\nMain-Class: lsdpatch.LSDPatcher\n\n"
  },
  {
    "path": "src/test/java/Document/DocumentTest.java",
    "chars": 1591,
    "preview": "package Document;\n\nimport org.junit.jupiter.api.Assertions;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.File;\nimp"
  },
  {
    "path": "src/test/java/Document/LSDSavFileTest.java",
    "chars": 1813,
    "preview": "package Document;\n\nimport org.junit.jupiter.api.Assertions;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.ju"
  },
  {
    "path": "src/test/java/kitEditor/SampleTest.java",
    "chars": 3902,
    "preview": "package kitEditor;\n\nimport org.junit.jupiter.api.Assertions;\nimport org.junit.jupiter.api.Test;\n\nimport javax.sound.samp"
  }
]

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

About this extraction

This page contains the full source code of the jkotlinski/lsdpatch GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 58 files (349.6 KB), approximately 84.8k tokens, and a symbol index with 636 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!