├── .github
├── FUNDING.yml
├── dependabot.yml
└── workflows
│ └── build.yml
├── src
└── main
│ ├── resources
│ ├── icon.png
│ ├── win32-x86-64
│ │ ├── bass.dll
│ │ ├── bassmix.dll
│ │ ├── mp3lame.dll
│ │ └── LICENSE_BASS.txt
│ ├── fonts
│ │ ├── Roboto-Regular.ttf
│ │ └── OFL.txt
│ ├── linux-x86-64
│ │ ├── libbass.so
│ │ ├── libbassmix.so
│ │ ├── libmp3lame.so
│ │ └── LICENSE_BASS.txt
│ ├── noteblock_sounds
│ │ ├── bd.ogg
│ │ ├── bit.ogg
│ │ ├── hat.ogg
│ │ ├── banjo.ogg
│ │ ├── bass.ogg
│ │ ├── bell.ogg
│ │ ├── flute.ogg
│ │ ├── harp.ogg
│ │ ├── pling.ogg
│ │ ├── snare.ogg
│ │ ├── cow_bell.ogg
│ │ ├── guitar.ogg
│ │ ├── icechime.ogg
│ │ ├── xylobone.ogg
│ │ ├── didgeridoo.ogg
│ │ └── iron_xylophone.ogg
│ └── textures
│ │ └── note_block.png
│ └── java
│ └── net
│ └── raphimc
│ └── noteblocktool
│ ├── elements
│ ├── NewLineLabel.java
│ ├── drag
│ │ ├── DragTableModel.java
│ │ ├── DragTableDropTargetListener.java
│ │ └── DragTable.java
│ ├── instruments
│ │ ├── InstrumentsModel.java
│ │ └── InstrumentsTable.java
│ ├── VerticalFileChooser.java
│ ├── formatter
│ │ ├── IntFormatterFactory.java
│ │ └── DoubleFormatterFactory.java
│ ├── TextOverlayPanel.java
│ └── FastScrollPane.java
│ ├── util
│ ├── IOUtil.java
│ ├── jna
│ │ ├── COMObject.java
│ │ ├── Ole32.java
│ │ └── VTableHandler.java
│ ├── filefilter
│ │ ├── SingleFileFilter.java
│ │ └── NoteBlockFileFilter.java
│ ├── MinecraftOctaveClamp.java
│ └── SoundFileUtil.java
│ ├── Main.java
│ ├── frames
│ ├── visualizer
│ │ ├── ExtendedThinGL.java
│ │ ├── ExtendedRenderer2D.java
│ │ └── VisualizerWindow.java
│ ├── edittabs
│ │ ├── EditTab.java
│ │ ├── CustomInstrumentsTab.java
│ │ ├── InstrumentsTab.java
│ │ ├── ResamplingTab.java
│ │ ├── NotesTab.java
│ │ └── MetadataTab.java
│ └── EditFrame.java
│ └── audio
│ ├── player
│ ├── impl
│ │ ├── RealtimeSongPlayer.java
│ │ ├── ProgressSongRenderer.java
│ │ ├── RealtimeSongRenderer.java
│ │ └── SongRenderer.java
│ └── AudioSystemSongPlayer.java
│ ├── library
│ ├── LameLibrary.java
│ ├── BassMixLibrary.java
│ ├── XAudio2Library.java
│ └── BassLibrary.java
│ ├── system
│ ├── AudioSystem.java
│ └── impl
│ │ ├── AudioMixerAudioSystem.java
│ │ ├── XAudio2AudioSystem.java
│ │ └── BassAudioSystem.java
│ └── SoundMap.java
├── img
├── NoteBlockTool Main GUI.png
├── NoteBlockTool Song Editor.png
└── NoteBlockTool Song Player.png
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── settings.gradle
├── .gitignore
├── .idea
└── copyright
│ ├── profiles_settings.xml
│ └── LGPL_3_0.xml
├── gradlew.bat
├── README.md
├── LICENSE
└── gradlew
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | ko_fi: "rk_01"
2 |
--------------------------------------------------------------------------------
/src/main/resources/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RaphiMC/NoteBlockTool/HEAD/src/main/resources/icon.png
--------------------------------------------------------------------------------
/img/NoteBlockTool Main GUI.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RaphiMC/NoteBlockTool/HEAD/img/NoteBlockTool Main GUI.png
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RaphiMC/NoteBlockTool/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/img/NoteBlockTool Song Editor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RaphiMC/NoteBlockTool/HEAD/img/NoteBlockTool Song Editor.png
--------------------------------------------------------------------------------
/img/NoteBlockTool Song Player.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RaphiMC/NoteBlockTool/HEAD/img/NoteBlockTool Song Player.png
--------------------------------------------------------------------------------
/src/main/resources/win32-x86-64/bass.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RaphiMC/NoteBlockTool/HEAD/src/main/resources/win32-x86-64/bass.dll
--------------------------------------------------------------------------------
/src/main/resources/fonts/Roboto-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RaphiMC/NoteBlockTool/HEAD/src/main/resources/fonts/Roboto-Regular.ttf
--------------------------------------------------------------------------------
/src/main/resources/linux-x86-64/libbass.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RaphiMC/NoteBlockTool/HEAD/src/main/resources/linux-x86-64/libbass.so
--------------------------------------------------------------------------------
/src/main/resources/noteblock_sounds/bd.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RaphiMC/NoteBlockTool/HEAD/src/main/resources/noteblock_sounds/bd.ogg
--------------------------------------------------------------------------------
/src/main/resources/noteblock_sounds/bit.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RaphiMC/NoteBlockTool/HEAD/src/main/resources/noteblock_sounds/bit.ogg
--------------------------------------------------------------------------------
/src/main/resources/noteblock_sounds/hat.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RaphiMC/NoteBlockTool/HEAD/src/main/resources/noteblock_sounds/hat.ogg
--------------------------------------------------------------------------------
/src/main/resources/textures/note_block.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RaphiMC/NoteBlockTool/HEAD/src/main/resources/textures/note_block.png
--------------------------------------------------------------------------------
/src/main/resources/win32-x86-64/bassmix.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RaphiMC/NoteBlockTool/HEAD/src/main/resources/win32-x86-64/bassmix.dll
--------------------------------------------------------------------------------
/src/main/resources/win32-x86-64/mp3lame.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RaphiMC/NoteBlockTool/HEAD/src/main/resources/win32-x86-64/mp3lame.dll
--------------------------------------------------------------------------------
/src/main/resources/linux-x86-64/libbassmix.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RaphiMC/NoteBlockTool/HEAD/src/main/resources/linux-x86-64/libbassmix.so
--------------------------------------------------------------------------------
/src/main/resources/linux-x86-64/libmp3lame.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RaphiMC/NoteBlockTool/HEAD/src/main/resources/linux-x86-64/libmp3lame.so
--------------------------------------------------------------------------------
/src/main/resources/noteblock_sounds/banjo.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RaphiMC/NoteBlockTool/HEAD/src/main/resources/noteblock_sounds/banjo.ogg
--------------------------------------------------------------------------------
/src/main/resources/noteblock_sounds/bass.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RaphiMC/NoteBlockTool/HEAD/src/main/resources/noteblock_sounds/bass.ogg
--------------------------------------------------------------------------------
/src/main/resources/noteblock_sounds/bell.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RaphiMC/NoteBlockTool/HEAD/src/main/resources/noteblock_sounds/bell.ogg
--------------------------------------------------------------------------------
/src/main/resources/noteblock_sounds/flute.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RaphiMC/NoteBlockTool/HEAD/src/main/resources/noteblock_sounds/flute.ogg
--------------------------------------------------------------------------------
/src/main/resources/noteblock_sounds/harp.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RaphiMC/NoteBlockTool/HEAD/src/main/resources/noteblock_sounds/harp.ogg
--------------------------------------------------------------------------------
/src/main/resources/noteblock_sounds/pling.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RaphiMC/NoteBlockTool/HEAD/src/main/resources/noteblock_sounds/pling.ogg
--------------------------------------------------------------------------------
/src/main/resources/noteblock_sounds/snare.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RaphiMC/NoteBlockTool/HEAD/src/main/resources/noteblock_sounds/snare.ogg
--------------------------------------------------------------------------------
/src/main/resources/noteblock_sounds/cow_bell.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RaphiMC/NoteBlockTool/HEAD/src/main/resources/noteblock_sounds/cow_bell.ogg
--------------------------------------------------------------------------------
/src/main/resources/noteblock_sounds/guitar.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RaphiMC/NoteBlockTool/HEAD/src/main/resources/noteblock_sounds/guitar.ogg
--------------------------------------------------------------------------------
/src/main/resources/noteblock_sounds/icechime.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RaphiMC/NoteBlockTool/HEAD/src/main/resources/noteblock_sounds/icechime.ogg
--------------------------------------------------------------------------------
/src/main/resources/noteblock_sounds/xylobone.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RaphiMC/NoteBlockTool/HEAD/src/main/resources/noteblock_sounds/xylobone.ogg
--------------------------------------------------------------------------------
/src/main/resources/noteblock_sounds/didgeridoo.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RaphiMC/NoteBlockTool/HEAD/src/main/resources/noteblock_sounds/didgeridoo.ogg
--------------------------------------------------------------------------------
/src/main/resources/noteblock_sounds/iron_xylophone.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RaphiMC/NoteBlockTool/HEAD/src/main/resources/noteblock_sounds/iron_xylophone.ogg
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.daemon=true
2 | org.gradle.parallel=true
3 | org.gradle.configuration-cache=true
4 |
5 | java_version=17
6 | maven_group=net.raphimc
7 | maven_name=NoteBlockTool
8 | maven_version=1.2.3-SNAPSHOT
9 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | mavenCentral()
4 | gradlePluginPortal()
5 | }
6 | }
7 |
8 | plugins {
9 | id "org.gradle.toolchains.foojay-resolver-convention" version "1.0.0"
10 | }
11 |
12 | rootProject.name = "NoteBlockTool"
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled class file
2 | *.class
3 |
4 | # Log file
5 | *.log
6 | *.log.gz
7 |
8 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
9 | hs_err_pid*
10 |
11 | # Project files
12 | /.idea/*
13 | !/.idea/copyright/
14 | /.gradle/
15 | /buildSrc/.gradle/
16 | build/
17 | out/
18 | /run/
19 |
--------------------------------------------------------------------------------
/.idea/copyright/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionSha256Sum=72f44c9f8ebcb1af43838f45ee5c4aa9c5444898b3468ab3f4af7b6076c5bc3f
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
5 | networkTimeout=10000
6 | validateDistributionUrl=true
7 | zipStoreBase=GRADLE_USER_HOME
8 | zipStorePath=wrapper/dists
9 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | registries:
3 | maven-central:
4 | type: "maven-repository"
5 | url: "https://repo.maven.apache.org/maven2/"
6 | updates:
7 | - package-ecosystem: "gradle"
8 | directory: "/"
9 | schedule:
10 | interval: "daily"
11 | registries:
12 | - "maven-central"
13 | - package-ecosystem: "github-actions"
14 | directory: "/"
15 | schedule:
16 | interval: "daily"
17 |
--------------------------------------------------------------------------------
/.idea/copyright/LGPL_3_0.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: "Build"
2 | on: ["push", "pull_request", "workflow_dispatch"]
3 |
4 | permissions:
5 | contents: "read"
6 |
7 | jobs:
8 | build:
9 | runs-on: "ubuntu-24.04"
10 | if: "${{ github.event_name != 'pull_request' || github.repository != github.event.pull_request.head.repo.full_name }}"
11 | steps:
12 | - name: "Checkout repository"
13 | uses: "actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8" # v6.0.1
14 | with:
15 | persist-credentials: false
16 | - name: "Set up Gradle"
17 | uses: "gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2" # v5.0.0
18 | - name: "Set up Java 25"
19 | uses: "actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e" # v5.1.0
20 | with:
21 | distribution: "temurin"
22 | java-version: 25
23 | check-latest: true
24 | - name: "Build with Gradle"
25 | run: "./gradlew build"
26 | - name: "Upload artifacts to GitHub"
27 | uses: "actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f" # v6.0.0
28 | with:
29 | name: "Artifacts"
30 | path: "build/libs/"
31 |
--------------------------------------------------------------------------------
/src/main/java/net/raphimc/noteblocktool/elements/NewLineLabel.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of NoteBlockTool - https://github.com/RaphiMC/NoteBlockTool
3 | * Copyright (C) 2022-2025 RK_01/RaphiMC and contributors
4 | *
5 | * This program is free software; you can redistribute it and/or
6 | * modify it under the terms of the GNU Lesser General Public
7 | * License as published by the Free Software Foundation; either
8 | * version 3 of the License, or (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package net.raphimc.noteblocktool.elements;
19 |
20 | import javax.swing.*;
21 |
22 | public class NewLineLabel extends JLabel {
23 |
24 | public NewLineLabel(final String text) {
25 | super("" + text.replace("\n", " ") + "");
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/net/raphimc/noteblocktool/util/IOUtil.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of NoteBlockTool - https://github.com/RaphiMC/NoteBlockTool
3 | * Copyright (C) 2022-2025 RK_01/RaphiMC and contributors
4 | *
5 | * This program is free software; you can redistribute it and/or
6 | * modify it under the terms of the GNU Lesser General Public
7 | * License as published by the Free Software Foundation; either
8 | * version 3 of the License, or (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package net.raphimc.noteblocktool.util;
19 |
20 | import com.google.common.io.ByteStreams;
21 |
22 | import java.io.IOException;
23 | import java.io.InputStream;
24 |
25 | public class IOUtil {
26 |
27 | public static byte[] readFully(final InputStream inputStream) throws IOException {
28 | try (inputStream) {
29 | return ByteStreams.toByteArray(inputStream);
30 | }
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/net/raphimc/noteblocktool/elements/drag/DragTableModel.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of NoteBlockTool - https://github.com/RaphiMC/NoteBlockTool
3 | * Copyright (C) 2022-2025 RK_01/RaphiMC and contributors
4 | *
5 | * This program is free software; you can redistribute it and/or
6 | * modify it under the terms of the GNU Lesser General Public
7 | * License as published by the Free Software Foundation; either
8 | * version 3 of the License, or (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package net.raphimc.noteblocktool.elements.drag;
19 |
20 | import javax.swing.table.DefaultTableModel;
21 |
22 | public class DragTableModel extends DefaultTableModel {
23 |
24 | public DragTableModel(final String... columns) {
25 | super(columns, 0);
26 | }
27 |
28 | @Override
29 | public boolean isCellEditable(int row, int column) {
30 | return false;
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/net/raphimc/noteblocktool/elements/instruments/InstrumentsModel.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of NoteBlockTool - https://github.com/RaphiMC/NoteBlockTool
3 | * Copyright (C) 2022-2025 RK_01/RaphiMC and contributors
4 | *
5 | * This program is free software; you can redistribute it and/or
6 | * modify it under the terms of the GNU Lesser General Public
7 | * License as published by the Free Software Foundation; either
8 | * version 3 of the License, or (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package net.raphimc.noteblocktool.elements.instruments;
19 |
20 | import javax.swing.table.DefaultTableModel;
21 |
22 | public class InstrumentsModel extends DefaultTableModel {
23 |
24 | public InstrumentsModel(final String... columns) {
25 | super(columns, 0);
26 | }
27 |
28 | @Override
29 | public boolean isCellEditable(int row, int column) {
30 | return column == 1;
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/net/raphimc/noteblocktool/util/jna/COMObject.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of NoteBlockTool - https://github.com/RaphiMC/NoteBlockTool
3 | * Copyright (C) 2022-2025 RK_01/RaphiMC and contributors
4 | *
5 | * This program is free software; you can redistribute it and/or
6 | * modify it under the terms of the GNU Lesser General Public
7 | * License as published by the Free Software Foundation; either
8 | * version 3 of the License, or (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package net.raphimc.noteblocktool.util.jna;
19 |
20 | import com.sun.jna.Pointer;
21 |
22 | public abstract class COMObject extends VTableHandler {
23 |
24 | public COMObject() {
25 | }
26 |
27 | public COMObject(final Pointer pvInstance) {
28 | super(pvInstance);
29 | }
30 |
31 | public int AddRef() {
32 | return this.getVtableFunction(1).invokeInt(new Object[]{this.getPointer()});
33 | }
34 |
35 | public int Release() {
36 | return this.getVtableFunction(2).invokeInt(new Object[]{this.getPointer()});
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/net/raphimc/noteblocktool/Main.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of NoteBlockTool - https://github.com/RaphiMC/NoteBlockTool
3 | * Copyright (C) 2022-2025 RK_01/RaphiMC and contributors
4 | *
5 | * This program is free software; you can redistribute it and/or
6 | * modify it under the terms of the GNU Lesser General Public
7 | * License as published by the Free Software Foundation; either
8 | * version 3 of the License, or (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package net.raphimc.noteblocktool;
19 |
20 | import com.formdev.flatlaf.FlatDarkLaf;
21 | import net.raphimc.noteblocktool.frames.ListFrame;
22 |
23 | import javax.swing.*;
24 |
25 | public class Main {
26 |
27 | public static void main(String[] args) {
28 | FlatDarkLaf.setup();
29 | UIManager.getLookAndFeelDefaults().put("TextComponent.arc", 5);
30 | UIManager.getLookAndFeelDefaults().put("Button.arc", 5);
31 | ToolTipManager.sharedInstance().setInitialDelay(100);
32 | ToolTipManager.sharedInstance().setDismissDelay(10_000);
33 |
34 | new ListFrame();
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/net/raphimc/noteblocktool/elements/VerticalFileChooser.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of NoteBlockTool - https://github.com/RaphiMC/NoteBlockTool
3 | * Copyright (C) 2022-2025 RK_01/RaphiMC and contributors
4 | *
5 | * This program is free software; you can redistribute it and/or
6 | * modify it under the terms of the GNU Lesser General Public
7 | * License as published by the Free Software Foundation; either
8 | * version 3 of the License, or (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package net.raphimc.noteblocktool.elements;
19 |
20 | import javax.swing.*;
21 | import java.awt.*;
22 |
23 | public class VerticalFileChooser extends JFileChooser {
24 |
25 | public VerticalFileChooser() {
26 | this.changeListToVertical(this);
27 | }
28 |
29 | private void changeListToVertical(final JComponent component) {
30 | for (Component c : component.getComponents()) {
31 | if (c instanceof JList>) {
32 | JList> list = (JList>) c;
33 | list.setLayoutOrientation(JList.VERTICAL);
34 | }
35 | if (c instanceof JComponent) {
36 | this.changeListToVertical((JComponent) c);
37 | }
38 | }
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/java/net/raphimc/noteblocktool/util/filefilter/SingleFileFilter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of NoteBlockTool - https://github.com/RaphiMC/NoteBlockTool
3 | * Copyright (C) 2022-2025 RK_01/RaphiMC and contributors
4 | *
5 | * This program is free software; you can redistribute it and/or
6 | * modify it under the terms of the GNU Lesser General Public
7 | * License as published by the Free Software Foundation; either
8 | * version 3 of the License, or (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package net.raphimc.noteblocktool.util.filefilter;
19 |
20 | import javax.swing.filechooser.FileFilter;
21 | import java.io.File;
22 |
23 | public class SingleFileFilter extends FileFilter {
24 |
25 | private final String extension;
26 |
27 | public SingleFileFilter(final String extension) {
28 | this.extension = extension.toLowerCase();
29 | }
30 |
31 | @Override
32 | public boolean accept(File f) {
33 | if (f.isDirectory()) return true;
34 | if (!f.isFile()) return false;
35 | return f.getName().toLowerCase().endsWith("." + this.extension);
36 | }
37 |
38 | @Override
39 | public String getDescription() {
40 | return this.extension + " File";
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/java/net/raphimc/noteblocktool/util/jna/Ole32.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of NoteBlockTool - https://github.com/RaphiMC/NoteBlockTool
3 | * Copyright (C) 2022-2025 RK_01/RaphiMC and contributors
4 | *
5 | * This program is free software; you can redistribute it and/or
6 | * modify it under the terms of the GNU Lesser General Public
7 | * License as published by the Free Software Foundation; either
8 | * version 3 of the License, or (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package net.raphimc.noteblocktool.util.jna;
19 |
20 | import com.sun.jna.Library;
21 | import com.sun.jna.Native;
22 | import com.sun.jna.Pointer;
23 |
24 | public interface Ole32 extends Library {
25 |
26 | Ole32 INSTANCE = loadNative();
27 |
28 | int COINIT_MULTITHREADED = 0;
29 |
30 | static Ole32 loadNative() {
31 | try {
32 | return Native.load("Ole32", Ole32.class);
33 | } catch (Throwable ignored) {
34 | }
35 | return null;
36 | }
37 |
38 | static boolean isLoaded() {
39 | return INSTANCE != null;
40 | }
41 |
42 | int CoInitialize(final Pointer reserved);
43 |
44 | int CoInitializeEx(final Pointer reserved, final int dwCoInit);
45 |
46 | void CoUninitialize();
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/net/raphimc/noteblocktool/util/jna/VTableHandler.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of NoteBlockTool - https://github.com/RaphiMC/NoteBlockTool
3 | * Copyright (C) 2022-2025 RK_01/RaphiMC and contributors
4 | *
5 | * This program is free software; you can redistribute it and/or
6 | * modify it under the terms of the GNU Lesser General Public
7 | * License as published by the Free Software Foundation; either
8 | * version 3 of the License, or (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package net.raphimc.noteblocktool.util.jna;
19 |
20 | import com.sun.jna.Function;
21 | import com.sun.jna.Native;
22 | import com.sun.jna.Pointer;
23 | import com.sun.jna.PointerType;
24 |
25 | public abstract class VTableHandler extends PointerType {
26 |
27 | public VTableHandler() {
28 | }
29 |
30 | public VTableHandler(final Pointer pvInstance) {
31 | super(pvInstance);
32 | }
33 |
34 | public Function getVtableFunction(final int index) {
35 | final Pointer vtblPtr = this.getPointer().getPointer(0);
36 | return Function.getFunction(vtblPtr.getPointer((long) index * Native.POINTER_SIZE));
37 | }
38 |
39 | public void setVtableFunction(final int index, final Pointer function) {
40 | final Pointer vtblPtr = this.getPointer().getPointer(0);
41 | vtblPtr.setPointer((long) index * Native.POINTER_SIZE, function);
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/net/raphimc/noteblocktool/frames/visualizer/ExtendedThinGL.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of NoteBlockTool - https://github.com/RaphiMC/NoteBlockTool
3 | * Copyright (C) 2022-2025 RK_01/RaphiMC and contributors
4 | *
5 | * This program is free software; you can redistribute it and/or
6 | * modify it under the terms of the GNU Lesser General Public
7 | * License as published by the Free Software Foundation; either
8 | * version 3 of the License, or (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package net.raphimc.noteblocktool.frames.visualizer;
19 |
20 | import net.raphimc.thingl.ThinGL;
21 | import net.raphimc.thingl.implementation.window.WindowInterface;
22 | import net.raphimc.thingl.renderer.impl.Renderer2D;
23 |
24 | public class ExtendedThinGL extends ThinGL {
25 |
26 | public static ExtendedThinGL get() {
27 | return (ExtendedThinGL) ThinGL.get();
28 | }
29 |
30 | public static ExtendedRenderer2D renderer2D() {
31 | return get().getRenderer2D();
32 | }
33 |
34 | public ExtendedThinGL(final WindowInterface windowInterface) {
35 | super(windowInterface);
36 | }
37 |
38 | @Override
39 | public ExtendedRenderer2D getRenderer2D() {
40 | return (ExtendedRenderer2D) super.getRenderer2D();
41 | }
42 |
43 | @Override
44 | protected Renderer2D createRenderer2D() {
45 | return new ExtendedRenderer2D();
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/net/raphimc/noteblocktool/util/MinecraftOctaveClamp.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of NoteBlockTool - https://github.com/RaphiMC/NoteBlockTool
3 | * Copyright (C) 2022-2025 RK_01/RaphiMC and contributors
4 | *
5 | * This program is free software; you can redistribute it and/or
6 | * modify it under the terms of the GNU Lesser General Public
7 | * License as published by the Free Software Foundation; either
8 | * version 3 of the License, or (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package net.raphimc.noteblocktool.util;
19 |
20 | import net.raphimc.noteblocklib.format.minecraft.MinecraftDefinitions;
21 | import net.raphimc.noteblocklib.model.note.Note;
22 |
23 | public enum MinecraftOctaveClamp {
24 |
25 | NONE {
26 | @Override
27 | public void correctNote(final Note note) {
28 | }
29 | },
30 | INSTRUMENT_SHIFT {
31 | @Override
32 | public void correctNote(final Note note) {
33 | MinecraftDefinitions.instrumentShiftNote(note);
34 | MinecraftDefinitions.clampNoteKey(note);
35 | }
36 | },
37 | TRANSPOSE {
38 | @Override
39 | public void correctNote(final Note note) {
40 | MinecraftDefinitions.transposeNoteKey(note);
41 | }
42 | },
43 | CLAMP {
44 | @Override
45 | public void correctNote(final Note note) {
46 | MinecraftDefinitions.clampNoteKey(note);
47 | }
48 | };
49 |
50 | public abstract void correctNote(final Note note);
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/src/main/java/net/raphimc/noteblocktool/audio/player/impl/RealtimeSongPlayer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of NoteBlockTool - https://github.com/RaphiMC/NoteBlockTool
3 | * Copyright (C) 2022-2025 RK_01/RaphiMC and contributors
4 | *
5 | * This program is free software; you can redistribute it and/or
6 | * modify it under the terms of the GNU Lesser General Public
7 | * License as published by the Free Software Foundation; either
8 | * version 3 of the License, or (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package net.raphimc.noteblocktool.audio.player.impl;
19 |
20 | import net.raphimc.noteblocklib.model.song.Song;
21 | import net.raphimc.noteblocktool.audio.player.AudioSystemSongPlayer;
22 | import net.raphimc.noteblocktool.audio.system.AudioSystem;
23 |
24 | import java.util.Map;
25 | import java.util.function.Function;
26 |
27 | public class RealtimeSongPlayer extends AudioSystemSongPlayer {
28 |
29 | public RealtimeSongPlayer(final Song song, final Function, AudioSystem> audioSystemSupplier) {
30 | super(song, audioSystemSupplier);
31 | if (this.getAudioSystem().getLoopbackAudioFormat() != null) {
32 | throw new IllegalArgumentException("AudioSystem isn't configured for realtime playback");
33 | }
34 | }
35 |
36 | @Override
37 | public void stop() {
38 | super.stop();
39 | this.getAudioSystem().stopAllSounds();
40 | }
41 |
42 | @Override
43 | public void setPaused(final boolean paused) {
44 | super.setPaused(paused);
45 | if (paused) {
46 | this.getAudioSystem().stopAllSounds();
47 | }
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/src/main/java/net/raphimc/noteblocktool/elements/formatter/IntFormatterFactory.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of NoteBlockTool - https://github.com/RaphiMC/NoteBlockTool
3 | * Copyright (C) 2022-2025 RK_01/RaphiMC and contributors
4 | *
5 | * This program is free software; you can redistribute it and/or
6 | * modify it under the terms of the GNU Lesser General Public
7 | * License as published by the Free Software Foundation; either
8 | * version 3 of the License, or (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package net.raphimc.noteblocktool.elements.formatter;
19 |
20 | import javax.swing.*;
21 | import java.text.ParseException;
22 |
23 | public class IntFormatterFactory extends JFormattedTextField.AbstractFormatterFactory {
24 |
25 | private final String prefix;
26 |
27 | public IntFormatterFactory(final String prefix) {
28 | this.prefix = prefix;
29 | }
30 |
31 | @Override
32 | public JFormattedTextField.AbstractFormatter getFormatter(JFormattedTextField tf) {
33 | return new JFormattedTextField.AbstractFormatter() {
34 | @Override
35 | public Object stringToValue(String text) throws ParseException {
36 | try {
37 | text = text.replace(prefix, "");
38 | return Integer.parseInt(text);
39 | } catch (Throwable t) {
40 | throw new ParseException("Invalid number", 0);
41 | }
42 | }
43 |
44 | @Override
45 | public String valueToString(Object value) throws ParseException {
46 | return value + prefix;
47 | }
48 | };
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/src/main/java/net/raphimc/noteblocktool/audio/player/impl/ProgressSongRenderer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of NoteBlockTool - https://github.com/RaphiMC/NoteBlockTool
3 | * Copyright (C) 2022-2025 RK_01/RaphiMC and contributors
4 | *
5 | * This program is free software; you can redistribute it and/or
6 | * modify it under the terms of the GNU Lesser General Public
7 | * License as published by the Free Software Foundation; either
8 | * version 3 of the License, or (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package net.raphimc.noteblocktool.audio.player.impl;
19 |
20 | import it.unimi.dsi.fastutil.floats.FloatConsumer;
21 | import net.raphimc.noteblocklib.model.note.Note;
22 | import net.raphimc.noteblocklib.model.song.Song;
23 | import net.raphimc.noteblocktool.audio.system.AudioSystem;
24 |
25 | import java.util.List;
26 | import java.util.Map;
27 | import java.util.function.Function;
28 |
29 | public class ProgressSongRenderer extends SongRenderer {
30 |
31 | private final FloatConsumer progressConsumer;
32 | private final int noteCount;
33 | private int processedNotes;
34 |
35 | public ProgressSongRenderer(final Song song, final FloatConsumer progressConsumer, final Function, AudioSystem> audioSystemSupplier) {
36 | super(song, audioSystemSupplier);
37 | this.noteCount = song.getNotes().getNoteCount();
38 | this.progressConsumer = progressConsumer;
39 | }
40 |
41 | @Override
42 | protected void playNotes(final List notes) {
43 | super.playNotes(notes);
44 | this.processedNotes += notes.size();
45 | this.progressConsumer.accept(((float) this.processedNotes / this.noteCount) * 100F);
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/net/raphimc/noteblocktool/elements/TextOverlayPanel.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of NoteBlockTool - https://github.com/RaphiMC/NoteBlockTool
3 | * Copyright (C) 2022-2025 RK_01/RaphiMC and contributors
4 | *
5 | * This program is free software; you can redistribute it and/or
6 | * modify it under the terms of the GNU Lesser General Public
7 | * License as published by the Free Software Foundation; either
8 | * version 3 of the License, or (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package net.raphimc.noteblocktool.elements;
19 |
20 | import net.lenni0451.commons.swing.components.OverlayPanel;
21 |
22 | import java.awt.*;
23 |
24 | public class TextOverlayPanel extends OverlayPanel {
25 |
26 | private String text;
27 |
28 | public TextOverlayPanel(final String text) {
29 | super(new Color(50, 50, 50, 150));
30 | this.text = text;
31 |
32 | this.setOpaque(false);
33 | }
34 |
35 | @Override
36 | protected void paintOverlay(Graphics g) {
37 | g.setColor(Color.WHITE);
38 | final FontMetrics metrics = g.getFontMetrics();
39 | final String[] lines = this.text.split("\n");
40 | int y = ((this.getHeight() - metrics.getHeight()) / 2) + metrics.getAscent();
41 | for (int i = 0; i < lines.length; i++) {
42 | if (i == 1) g.setColor(Color.GRAY);
43 | String line = lines[i];
44 | int x = (this.getWidth() - metrics.stringWidth(line)) / 2;
45 | g.drawString(line, x, y);
46 | y += metrics.getHeight();
47 | }
48 | }
49 |
50 | public void setText(final String text) {
51 | this.text = text;
52 | this.invalidate();
53 | this.repaint();
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/src/main/java/net/raphimc/noteblocktool/elements/instruments/InstrumentsTable.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of NoteBlockTool - https://github.com/RaphiMC/NoteBlockTool
3 | * Copyright (C) 2022-2025 RK_01/RaphiMC and contributors
4 | *
5 | * This program is free software; you can redistribute it and/or
6 | * modify it under the terms of the GNU Lesser General Public
7 | * License as published by the Free Software Foundation; either
8 | * version 3 of the License, or (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package net.raphimc.noteblocktool.elements.instruments;
19 |
20 | import net.raphimc.noteblocklib.format.minecraft.MinecraftInstrument;
21 |
22 | import javax.swing.*;
23 | import javax.swing.table.DefaultTableModel;
24 |
25 | public class InstrumentsTable extends JTable {
26 |
27 | public InstrumentsTable(final boolean addEmptyEntry) {
28 | super(new InstrumentsModel("Original", "Replacement"));
29 | this.getTableHeader().setReorderingAllowed(false);
30 |
31 | JComboBox instruments = new JComboBox<>();
32 | if (addEmptyEntry) instruments.addItem(null);
33 | for (MinecraftInstrument instrument : MinecraftInstrument.values()) instruments.addItem(instrument);
34 | this.getColumnModel().getColumn(1).setCellEditor(new DefaultCellEditor(instruments));
35 | }
36 |
37 | public void addRow(final String original, final MinecraftInstrument instrument) {
38 | ((DefaultTableModel) this.getModel()).addRow(new Object[]{
39 | original,
40 | instrument
41 | });
42 | }
43 |
44 | @Override
45 | public Class> getColumnClass(int column) {
46 | if (column == 1) return JComboBox.class;
47 | else return super.getColumnClass(column);
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/src/main/java/net/raphimc/noteblocktool/elements/FastScrollPane.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of NoteBlockTool - https://github.com/RaphiMC/NoteBlockTool
3 | * Copyright (C) 2022-2025 RK_01/RaphiMC and contributors
4 | *
5 | * This program is free software; you can redistribute it and/or
6 | * modify it under the terms of the GNU Lesser General Public
7 | * License as published by the Free Software Foundation; either
8 | * version 3 of the License, or (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package net.raphimc.noteblocktool.elements;
19 |
20 | import javax.swing.*;
21 | import javax.swing.border.Border;
22 | import java.awt.*;
23 |
24 | public class FastScrollPane extends JScrollPane {
25 |
26 | private final Border defaultBorder = this.getBorder();
27 |
28 | public FastScrollPane() {
29 | }
30 |
31 | public FastScrollPane(final Component view) {
32 | super(view);
33 | }
34 |
35 | {
36 | this.setBorder(BorderFactory.createEmptyBorder());
37 | }
38 |
39 | public FastScrollPane setDefaultBorder() {
40 | this.setBorder(this.defaultBorder);
41 | return this;
42 | }
43 |
44 | public Border getDefaultBorder() {
45 | return this.defaultBorder;
46 | }
47 |
48 | @Override
49 | public JScrollBar createVerticalScrollBar() {
50 | JScrollBar scrollBar = super.createVerticalScrollBar();
51 | scrollBar.setUnitIncrement(16);
52 | scrollBar.setBlockIncrement(16);
53 | return scrollBar;
54 | }
55 |
56 | @Override
57 | public JScrollBar createHorizontalScrollBar() {
58 | JScrollBar scrollBar = super.createHorizontalScrollBar();
59 | scrollBar.setUnitIncrement(16);
60 | scrollBar.setBlockIncrement(16);
61 | return scrollBar;
62 | }
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/src/main/java/net/raphimc/noteblocktool/frames/visualizer/ExtendedRenderer2D.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of NoteBlockTool - https://github.com/RaphiMC/NoteBlockTool
3 | * Copyright (C) 2022-2025 RK_01/RaphiMC and contributors
4 | *
5 | * This program is free software; you can redistribute it and/or
6 | * modify it under the terms of the GNU Lesser General Public
7 | * License as published by the Free Software Foundation; either
8 | * version 3 of the License, or (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package net.raphimc.noteblocktool.frames.visualizer;
19 |
20 | import net.lenni0451.commons.color.Color;
21 | import net.raphimc.thingl.drawbuilder.databuilder.holder.VertexDataHolder;
22 | import net.raphimc.thingl.renderer.impl.Renderer2D;
23 | import net.raphimc.thingl.resource.image.texture.Texture2D;
24 | import org.joml.Matrix4f;
25 |
26 | public class ExtendedRenderer2D extends Renderer2D {
27 |
28 | public void gradientColorizedTexture(final Matrix4f positionMatrix, final Texture2D texture, final float x, final float y, final float width, final float height, final Color color1, final Color color2) {
29 | final VertexDataHolder vertexDataHolder = this.targetMultiDrawBatchDataHolder.getVertexDataHolder(this.colorizedTextureQuad.apply(texture.getGlId()));
30 | vertexDataHolder.putVector3f(positionMatrix, x, y + height, 0F).putColor(color2).putTextureCoord(0F, 1F).endVertex();
31 | vertexDataHolder.putVector3f(positionMatrix, x + width, y + height, 0F).putColor(color2).putTextureCoord(1F, 1F).endVertex();
32 | vertexDataHolder.putVector3f(positionMatrix, x + width, y, 0F).putColor(color1).putTextureCoord(1F, 0F).endVertex();
33 | vertexDataHolder.putVector3f(positionMatrix, x, y, 0F).putColor(color1).putTextureCoord(0F, 0F).endVertex();
34 | this.drawIfNotBuffering();
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/net/raphimc/noteblocktool/audio/library/LameLibrary.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of NoteBlockTool - https://github.com/RaphiMC/NoteBlockTool
3 | * Copyright (C) 2022-2025 RK_01/RaphiMC and contributors
4 | *
5 | * This program is free software; you can redistribute it and/or
6 | * modify it under the terms of the GNU Lesser General Public
7 | * License as published by the Free Software Foundation; either
8 | * version 3 of the License, or (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package net.raphimc.noteblocktool.audio.library;
19 |
20 | import com.sun.jna.Library;
21 | import com.sun.jna.Native;
22 | import com.sun.jna.Pointer;
23 |
24 | import java.util.HashMap;
25 | import java.util.Map;
26 |
27 | public interface LameLibrary extends Library {
28 |
29 | LameLibrary INSTANCE = loadNative();
30 |
31 | static LameLibrary loadNative() {
32 | try {
33 | final Map options = new HashMap<>();
34 | options.put(Library.OPTION_STRING_ENCODING, "UTF-8");
35 | return Native.load("mp3lame", LameLibrary.class, options);
36 | } catch (Throwable ignored) {
37 | }
38 | return null;
39 | }
40 |
41 | static boolean isLoaded() {
42 | return INSTANCE != null;
43 | }
44 |
45 | String get_lame_version();
46 |
47 | Pointer lame_init();
48 |
49 | int lame_set_in_samplerate(final Pointer lame, final int in_samplerate);
50 |
51 | int lame_set_num_channels(final Pointer lame, final int num_channels);
52 |
53 | int lame_init_params(final Pointer lame);
54 |
55 | int lame_encode_buffer_interleaved_ieee_float(final Pointer lame, final float[] pcm, final int num_samples, final byte[] mp3buf, final int mp3buf_size);
56 |
57 | int lame_encode_flush(final Pointer lame, final byte[] mp3buf, final int mp3buf_size);
58 |
59 | int lame_close(final Pointer lame);
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/src/main/java/net/raphimc/noteblocktool/elements/formatter/DoubleFormatterFactory.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of NoteBlockTool - https://github.com/RaphiMC/NoteBlockTool
3 | * Copyright (C) 2022-2025 RK_01/RaphiMC and contributors
4 | *
5 | * This program is free software; you can redistribute it and/or
6 | * modify it under the terms of the GNU Lesser General Public
7 | * License as published by the Free Software Foundation; either
8 | * version 3 of the License, or (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package net.raphimc.noteblocktool.elements.formatter;
19 |
20 | import javax.swing.*;
21 | import java.text.DecimalFormat;
22 | import java.text.ParseException;
23 |
24 | public class DoubleFormatterFactory extends JFormattedTextField.AbstractFormatterFactory {
25 |
26 | private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("#.##");
27 |
28 | static {
29 | DECIMAL_FORMAT.setGroupingUsed(false);
30 | }
31 |
32 |
33 | private final String prefix;
34 |
35 | public DoubleFormatterFactory(final String prefix) {
36 | this.prefix = prefix;
37 | }
38 |
39 | @Override
40 | public JFormattedTextField.AbstractFormatter getFormatter(JFormattedTextField tf) {
41 | return new JFormattedTextField.AbstractFormatter() {
42 | @Override
43 | public Object stringToValue(String text) throws ParseException {
44 | try {
45 | text = text.replace(prefix, "");
46 | return Double.parseDouble(text);
47 | } catch (Throwable t) {
48 | throw new ParseException("Invalid number", 0);
49 | }
50 | }
51 |
52 | @Override
53 | public String valueToString(Object value) throws ParseException {
54 | return DECIMAL_FORMAT.format(value) + prefix;
55 | }
56 | };
57 | }
58 |
59 | }
60 |
--------------------------------------------------------------------------------
/src/main/java/net/raphimc/noteblocktool/util/filefilter/NoteBlockFileFilter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of NoteBlockTool - https://github.com/RaphiMC/NoteBlockTool
3 | * Copyright (C) 2022-2025 RK_01/RaphiMC and contributors
4 | *
5 | * This program is free software; you can redistribute it and/or
6 | * modify it under the terms of the GNU Lesser General Public
7 | * License as published by the Free Software Foundation; either
8 | * version 3 of the License, or (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package net.raphimc.noteblocktool.util.filefilter;
19 |
20 | import net.raphimc.noteblocklib.format.SongFormat;
21 |
22 | import javax.swing.filechooser.FileFilter;
23 | import java.io.File;
24 | import java.util.Arrays;
25 | import java.util.List;
26 | import java.util.stream.Collectors;
27 |
28 | public class NoteBlockFileFilter extends FileFilter {
29 |
30 | private final List extensions;
31 | private final String description;
32 |
33 | public NoteBlockFileFilter() {
34 | this.extensions = Arrays.stream(SongFormat.values()).flatMap(format -> format.getExtensions().stream()).collect(Collectors.toList());
35 | this.description = "NoteBlockTool Song Files (" + this.extensions.stream().map(s -> "*." + s).collect(Collectors.joining(", ")) + ")";
36 | }
37 |
38 | public NoteBlockFileFilter(final SongFormat songFormat) {
39 | this.extensions = songFormat.getExtensions();
40 | this.description = songFormat.getName().toUpperCase() + " Song File (" + this.extensions.stream().map(s -> "*." + s).collect(Collectors.joining(", ")) + ")";
41 | }
42 |
43 | @Override
44 | public boolean accept(File f) {
45 | if (f.isDirectory()) return true;
46 | if (!f.isFile()) return false;
47 | final String extension = f.getName().substring(f.getName().lastIndexOf(".") + 1);
48 | return this.extensions.contains(extension.toLowerCase());
49 | }
50 |
51 | @Override
52 | public String getDescription() {
53 | return this.description;
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/src/main/java/net/raphimc/noteblocktool/frames/edittabs/EditTab.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of NoteBlockTool - https://github.com/RaphiMC/NoteBlockTool
3 | * Copyright (C) 2022-2025 RK_01/RaphiMC and contributors
4 | *
5 | * This program is free software; you can redistribute it and/or
6 | * modify it under the terms of the GNU Lesser General Public
7 | * License as published by the Free Software Foundation; either
8 | * version 3 of the License, or (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package net.raphimc.noteblocktool.frames.edittabs;
19 |
20 | import net.lenni0451.commons.swing.components.ScrollPaneSizedPanel;
21 | import net.lenni0451.commons.swing.layouts.VerticalLayout;
22 | import net.raphimc.noteblocklib.model.song.Song;
23 | import net.raphimc.noteblocktool.elements.FastScrollPane;
24 | import net.raphimc.noteblocktool.frames.ListFrame;
25 |
26 | import javax.swing.*;
27 | import java.awt.*;
28 | import java.util.List;
29 |
30 | public abstract class EditTab extends JPanel {
31 |
32 | private final String title;
33 | protected final List songs;
34 | private final JPanel center;
35 |
36 | public EditTab(final String title, final List songs) {
37 | this.title = title;
38 | this.songs = songs;
39 |
40 | this.setLayout(new BorderLayout());
41 | JScrollPane scrollPane = new FastScrollPane().setDefaultBorder();
42 | this.center = new ScrollPaneSizedPanel(scrollPane);
43 | this.center.setLayout(new VerticalLayout(5, 5));
44 | scrollPane.setViewportView(this.center);
45 | this.add(scrollPane, BorderLayout.CENTER);
46 | }
47 |
48 | public String getTitle() {
49 | return this.title;
50 | }
51 |
52 | public void init() {
53 | this.initComponents(this.center);
54 | }
55 |
56 | protected abstract void initComponents(final JPanel center);
57 |
58 | public abstract void apply(final Song song);
59 |
60 | protected final JLabel html(final String... lines) {
61 | return new JLabel("" + String.join(" ", lines) + "");
62 | }
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/src/main/java/net/raphimc/noteblocktool/audio/system/AudioSystem.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of NoteBlockTool - https://github.com/RaphiMC/NoteBlockTool
3 | * Copyright (C) 2022-2025 RK_01/RaphiMC and contributors
4 | *
5 | * This program is free software; you can redistribute it and/or
6 | * modify it under the terms of the GNU Lesser General Public
7 | * License as published by the Free Software Foundation; either
8 | * version 3 of the License, or (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package net.raphimc.noteblocktool.audio.system;
19 |
20 | import net.raphimc.audiomixer.util.PcmFloatAudioFormat;
21 |
22 | import java.util.Map;
23 |
24 | public abstract class AudioSystem implements AutoCloseable {
25 |
26 | private final int maxSounds;
27 | private final PcmFloatAudioFormat loopbackAudioFormat;
28 |
29 | public AudioSystem(final Map soundData, final int maxSounds) {
30 | this.maxSounds = maxSounds;
31 | this.loopbackAudioFormat = null;
32 | }
33 |
34 | public AudioSystem(final Map soundData, final int maxSounds, final PcmFloatAudioFormat loopbackAudioFormat) {
35 | if (loopbackAudioFormat == null) {
36 | throw new IllegalArgumentException("Loopback audio format cannot be null");
37 | }
38 | this.maxSounds = maxSounds;
39 | this.loopbackAudioFormat = loopbackAudioFormat;
40 | }
41 |
42 | public void preTick() {
43 | }
44 |
45 | public abstract void playSound(final String sound, final float pitch, final float volume, final float panning);
46 |
47 | public abstract void stopAllSounds();
48 |
49 | public abstract float[] render(final int frameCount);
50 |
51 | @Override
52 | public abstract void close();
53 |
54 | public abstract void setMasterVolume(final float volume);
55 |
56 | public abstract Integer getPlayingSounds();
57 |
58 | public int getMaxSounds() {
59 | return this.maxSounds;
60 | }
61 |
62 | public abstract Float getCpuLoad();
63 |
64 | public PcmFloatAudioFormat getLoopbackAudioFormat() {
65 | return this.loopbackAudioFormat;
66 | }
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/src/main/java/net/raphimc/noteblocktool/audio/player/impl/RealtimeSongRenderer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of NoteBlockTool - https://github.com/RaphiMC/NoteBlockTool
3 | * Copyright (C) 2022-2025 RK_01/RaphiMC and contributors
4 | *
5 | * This program is free software; you can redistribute it and/or
6 | * modify it under the terms of the GNU Lesser General Public
7 | * License as published by the Free Software Foundation; either
8 | * version 3 of the License, or (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package net.raphimc.noteblocktool.audio.player.impl;
19 |
20 | import net.raphimc.audiomixer.util.SourceDataLineWriter;
21 | import net.raphimc.noteblocklib.model.song.Song;
22 | import net.raphimc.noteblocktool.audio.system.AudioSystem;
23 |
24 | import javax.sound.sampled.AudioFormat;
25 | import java.util.Map;
26 | import java.util.function.Function;
27 |
28 | public class RealtimeSongRenderer extends SongRenderer {
29 |
30 | private final SourceDataLineWriter sourceDataLineWriter;
31 |
32 | public RealtimeSongRenderer(final Song song, final Function, AudioSystem> audioSystemSupplier) {
33 | super(song, audioSystemSupplier);
34 | try {
35 | final AudioFormat audioFormat = new AudioFormat(this.getAudioSystem().getLoopbackAudioFormat().getSampleRate(), Short.SIZE, this.getAudioSystem().getLoopbackAudioFormat().getChannels(), true, false);
36 | this.sourceDataLineWriter = new SourceDataLineWriter(javax.sound.sampled.AudioSystem.getSourceDataLine(audioFormat), 50, this::renderTick);
37 | this.sourceDataLineWriter.start();
38 | } catch (Throwable e) {
39 | throw new RuntimeException("Failed to open SourceDataLine", e);
40 | }
41 | }
42 |
43 | @Override
44 | public void stop() {
45 | super.stop();
46 | this.getAudioSystem().stopAllSounds();
47 | }
48 |
49 | @Override
50 | public void setPaused(final boolean paused) {
51 | super.setPaused(paused);
52 | if (paused) {
53 | this.getAudioSystem().stopAllSounds();
54 | }
55 | }
56 |
57 | @Override
58 | public void close() {
59 | this.sourceDataLineWriter.close();
60 | super.close();
61 | }
62 |
63 | @Override
64 | protected Float getAudioRendererCpuLoad() {
65 | return this.sourceDataLineWriter.getCpuLoad();
66 | }
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/src/main/resources/linux-x86-64/LICENSE_BASS.txt:
--------------------------------------------------------------------------------
1 | Licence
2 | =======
3 | BASS is free for non-commercial use. If you are a non-commercial entity
4 | (eg. an individual) and you are not making any money from your product
5 | (through sales/advertising/etc), then you can use BASS in it for free.
6 | If you wish to use BASS in commercial products, then please also see the
7 | next section.
8 |
9 | TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, BASS IS PROVIDED
10 | "AS IS", WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
11 | INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY
12 | AND/OR FITNESS FOR A PARTICULAR PURPOSE. THE AUTHORS SHALL NOT BE HELD
13 | LIABLE FOR ANY DAMAGE THAT MAY RESULT FROM THE USE OF BASS. YOU USE
14 | BASS ENTIRELY AT YOUR OWN RISK.
15 |
16 | Usage of BASS indicates that you agree to the above conditions.
17 |
18 | All trademarks and other registered names contained in the BASS
19 | package are the property of their respective owners.
20 |
21 | Commercial licensing
22 | --------------------
23 | BASS is available for use in your commercial products. The licence
24 | types available are as follows:
25 |
26 | SHAREWARE: Allows the usage of BASS in an unlimited number of your
27 | shareware ("try before you buy") products, which must sell for no more
28 | than 40 Euros each. Non-shareware products are also permitted, but the
29 | product price limit is 10 Euros in that case. The price limit can be
30 | raised by purchasing duplicate licences, eg. 2 licences doubles it. If
31 | you are an individual (not a corporation) making and selling your own
32 | software, this is the licence for you.
33 |
34 | SINGLE COMMERCIAL: Allows the usage of BASS in one commercial product.
35 |
36 | UNLIMITED COMMERCIAL: Allows the usage of BASS in an unlimited number
37 | of your commercial products. This licence is on a per-site basis, eg.
38 | if you are creating products with BASS at 2 sites/locations, then 2
39 | licences are required.
40 |
41 | Please note the products must be end-user products, eg. not components
42 | used by other products.
43 |
44 | These licences only cover your own software, not the publishing of
45 | other's software. If you publish other's software, its developers (or
46 | the software itself) will need to be licensed to use BASS.
47 |
48 | These licences are on a per-platform basis, with reductions available
49 | when licensing for multiple platforms. In all cases there are no royalties
50 | to pay, and you can use future BASS updates without further cost.
51 |
52 | These licences do not allow reselling/sublicensing of BASS. For example,
53 | if a product is a development system, the users of said product are not
54 | licensed to use BASS in their productions; they will need their own
55 | licences.
56 |
57 | If the standard licences do not meet your requirements, or if you have
58 | any questions, please get in touch (email: bass@un4seen.com).
59 |
60 | Visit the BASS website for the latest pricing:
61 |
62 | www.un4seen.com
--------------------------------------------------------------------------------
/src/main/resources/win32-x86-64/LICENSE_BASS.txt:
--------------------------------------------------------------------------------
1 | Licence
2 | =======
3 | BASS is free for non-commercial use. If you are a non-commercial entity
4 | (eg. an individual) and you are not making any money from your product
5 | (through sales/advertising/etc), then you can use BASS in it for free.
6 | If you wish to use BASS in commercial products, then please also see the
7 | next section.
8 |
9 | TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, BASS IS PROVIDED
10 | "AS IS", WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
11 | INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY
12 | AND/OR FITNESS FOR A PARTICULAR PURPOSE. THE AUTHORS SHALL NOT BE HELD
13 | LIABLE FOR ANY DAMAGE THAT MAY RESULT FROM THE USE OF BASS. YOU USE
14 | BASS ENTIRELY AT YOUR OWN RISK.
15 |
16 | Usage of BASS indicates that you agree to the above conditions.
17 |
18 | All trademarks and other registered names contained in the BASS
19 | package are the property of their respective owners.
20 |
21 | Commercial licensing
22 | --------------------
23 | BASS is available for use in your commercial products. The licence
24 | types available are as follows:
25 |
26 | SHAREWARE: Allows the usage of BASS in an unlimited number of your
27 | shareware ("try before you buy") products, which must sell for no more
28 | than 40 Euros each. Non-shareware products are also permitted, but the
29 | product price limit is 10 Euros in that case. The price limit can be
30 | raised by purchasing duplicate licences, eg. 2 licences doubles it. If
31 | you are an individual (not a corporation) making and selling your own
32 | software, this is the licence for you.
33 |
34 | SINGLE COMMERCIAL: Allows the usage of BASS in one commercial product.
35 |
36 | UNLIMITED COMMERCIAL: Allows the usage of BASS in an unlimited number
37 | of your commercial products. This licence is on a per-site basis, eg.
38 | if you are creating products with BASS at 2 sites/locations, then 2
39 | licences are required.
40 |
41 | Please note the products must be end-user products, eg. not components
42 | used by other products.
43 |
44 | These licences only cover your own software, not the publishing of
45 | other's software. If you publish other's software, its developers (or
46 | the software itself) will need to be licensed to use BASS.
47 |
48 | These licences are on a per-platform basis, with reductions available
49 | when licensing for multiple platforms. In all cases there are no royalties
50 | to pay, and you can use future BASS updates without further cost.
51 |
52 | These licences do not allow reselling/sublicensing of BASS. For example,
53 | if a product is a development system, the users of said product are not
54 | licensed to use BASS in their productions; they will need their own
55 | licences.
56 |
57 | If the standard licences do not meet your requirements, or if you have
58 | any questions, please get in touch (email: bass@un4seen.com).
59 |
60 | Visit the BASS website for the latest pricing:
61 |
62 | www.un4seen.com
--------------------------------------------------------------------------------
/src/main/java/net/raphimc/noteblocktool/frames/edittabs/CustomInstrumentsTab.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of NoteBlockTool - https://github.com/RaphiMC/NoteBlockTool
3 | * Copyright (C) 2022-2025 RK_01/RaphiMC and contributors
4 | *
5 | * This program is free software; you can redistribute it and/or
6 | * modify it under the terms of the GNU Lesser General Public
7 | * License as published by the Free Software Foundation; either
8 | * version 3 of the License, or (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package net.raphimc.noteblocktool.frames.edittabs;
19 |
20 | import net.raphimc.noteblocklib.format.minecraft.MinecraftInstrument;
21 | import net.raphimc.noteblocklib.format.nbs.model.NbsCustomInstrument;
22 | import net.raphimc.noteblocklib.model.song.Song;
23 | import net.raphimc.noteblocklib.util.SongUtil;
24 | import net.raphimc.noteblocktool.elements.FastScrollPane;
25 | import net.raphimc.noteblocktool.elements.instruments.InstrumentsTable;
26 | import net.raphimc.noteblocktool.frames.ListFrame;
27 |
28 | import javax.swing.*;
29 | import java.util.HashMap;
30 | import java.util.List;
31 | import java.util.Map;
32 | import java.util.Set;
33 |
34 | public class CustomInstrumentsTab extends EditTab {
35 |
36 | private InstrumentsTable table;
37 | private Set usedInstruments;
38 |
39 | public CustomInstrumentsTab(final List songs) {
40 | super("Custom Instruments", songs);
41 | }
42 |
43 | @Override
44 | protected void initComponents(JPanel center) {
45 | this.removeAll();
46 |
47 | this.table = new InstrumentsTable(true);
48 | this.add(new FastScrollPane(this.table));
49 | this.usedInstruments = SongUtil.getUsedNbsCustomInstruments(this.songs.get(0).song());
50 | for (NbsCustomInstrument customInstrument : this.usedInstruments) {
51 | this.table.addRow(customInstrument.getNameOr("No Name") + " (" + customInstrument.getSoundFilePathOr("No Sound File") + ")", null);
52 | }
53 | }
54 |
55 | @Override
56 | public void apply(final Song song) {
57 | Map replacements = new HashMap<>();
58 | int i = 0;
59 | for (NbsCustomInstrument customInstrument : this.usedInstruments) {
60 | MinecraftInstrument replacement = (MinecraftInstrument) this.table.getValueAt(i, 1);
61 | replacements.put(customInstrument, replacement);
62 | i++;
63 | }
64 | song.getNotes().forEach(note -> {
65 | MinecraftInstrument replacement = replacements.get(note.getInstrument());
66 | if (replacement != null) note.setInstrument(replacement);
67 | });
68 | }
69 |
70 | }
71 |
--------------------------------------------------------------------------------
/src/main/java/net/raphimc/noteblocktool/frames/edittabs/InstrumentsTab.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of NoteBlockTool - https://github.com/RaphiMC/NoteBlockTool
3 | * Copyright (C) 2022-2025 RK_01/RaphiMC and contributors
4 | *
5 | * This program is free software; you can redistribute it and/or
6 | * modify it under the terms of the GNU Lesser General Public
7 | * License as published by the Free Software Foundation; either
8 | * version 3 of the License, or (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package net.raphimc.noteblocktool.frames.edittabs;
19 |
20 | import net.raphimc.noteblocklib.format.minecraft.MinecraftInstrument;
21 | import net.raphimc.noteblocklib.model.song.Song;
22 | import net.raphimc.noteblocklib.util.SongUtil;
23 | import net.raphimc.noteblocktool.elements.FastScrollPane;
24 | import net.raphimc.noteblocktool.elements.instruments.InstrumentsTable;
25 | import net.raphimc.noteblocktool.frames.ListFrame;
26 |
27 | import javax.swing.*;
28 | import java.util.*;
29 |
30 | public class InstrumentsTab extends EditTab {
31 |
32 | private InstrumentsTable table;
33 | private Set usedInstruments;
34 |
35 | public InstrumentsTab(final List songs) {
36 | super("Instruments", songs);
37 | }
38 |
39 | @Override
40 | protected void initComponents(JPanel center) {
41 | this.removeAll();
42 |
43 | this.table = new InstrumentsTable(true);
44 | this.add(new FastScrollPane(this.table));
45 | this.usedInstruments = this.songs.stream()
46 | .map(song -> SongUtil.getUsedVanillaInstruments(song.song()))
47 | .reduce(EnumSet.noneOf(MinecraftInstrument.class), (a, b) -> {
48 | a.addAll(b);
49 | return a;
50 | });
51 | for (MinecraftInstrument instrument : this.usedInstruments) this.table.addRow(instrument.name(), instrument);
52 | }
53 |
54 | @Override
55 | public void apply(final Song song) {
56 | Map replacements = new HashMap<>();
57 | int i = 0;
58 | for (MinecraftInstrument instrument : this.usedInstruments) {
59 | MinecraftInstrument replacement = (MinecraftInstrument) this.table.getValueAt(i, 1);
60 | replacements.put(instrument, replacement);
61 | i++;
62 | }
63 | song.getNotes().forEach(note -> {
64 | MinecraftInstrument replacement = replacements.get(note.getInstrument());
65 | if (replacement != null) note.setInstrument(replacement);
66 | });
67 | song.getNotes().removeIf(note -> replacements.containsKey(note.getInstrument()) && replacements.get(note.getInstrument()) == null);
68 | }
69 |
70 | }
71 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 | @rem SPDX-License-Identifier: Apache-2.0
17 | @rem
18 |
19 | @if "%DEBUG%"=="" @echo off
20 | @rem ##########################################################################
21 | @rem
22 | @rem Gradle startup script for Windows
23 | @rem
24 | @rem ##########################################################################
25 |
26 | @rem Set local scope for the variables with windows NT shell
27 | if "%OS%"=="Windows_NT" setlocal
28 |
29 | set DIRNAME=%~dp0
30 | if "%DIRNAME%"=="" set DIRNAME=.
31 | @rem This is normally unused
32 | set APP_BASE_NAME=%~n0
33 | set APP_HOME=%DIRNAME%
34 |
35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
37 |
38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
40 |
41 | @rem Find java.exe
42 | if defined JAVA_HOME goto findJavaFromJavaHome
43 |
44 | set JAVA_EXE=java.exe
45 | %JAVA_EXE% -version >NUL 2>&1
46 | if %ERRORLEVEL% equ 0 goto execute
47 |
48 | echo. 1>&2
49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
50 | echo. 1>&2
51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
52 | echo location of your Java installation. 1>&2
53 |
54 | goto fail
55 |
56 | :findJavaFromJavaHome
57 | set JAVA_HOME=%JAVA_HOME:"=%
58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
59 |
60 | if exist "%JAVA_EXE%" goto execute
61 |
62 | echo. 1>&2
63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
64 | echo. 1>&2
65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
66 | echo location of your Java installation. 1>&2
67 |
68 | goto fail
69 |
70 | :execute
71 | @rem Setup the command line
72 |
73 |
74 |
75 | @rem Execute Gradle
76 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
77 |
78 | :end
79 | @rem End local scope for the variables with windows NT shell
80 | if %ERRORLEVEL% equ 0 goto mainEnd
81 |
82 | :fail
83 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
84 | rem the _cmd.exe /c_ return code!
85 | set EXIT_CODE=%ERRORLEVEL%
86 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
87 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
88 | exit /b %EXIT_CODE%
89 |
90 | :mainEnd
91 | if "%OS%"=="Windows_NT" endlocal
92 |
93 | :omega
94 |
--------------------------------------------------------------------------------
/src/main/java/net/raphimc/noteblocktool/audio/player/impl/SongRenderer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of NoteBlockTool - https://github.com/RaphiMC/NoteBlockTool
3 | * Copyright (C) 2022-2025 RK_01/RaphiMC and contributors
4 | *
5 | * This program is free software; you can redistribute it and/or
6 | * modify it under the terms of the GNU Lesser General Public
7 | * License as published by the Free Software Foundation; either
8 | * version 3 of the License, or (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package net.raphimc.noteblocktool.audio.player.impl;
19 |
20 | import net.raphimc.audiomixer.util.GrowableArray;
21 | import net.raphimc.audiomixer.util.MathUtil;
22 | import net.raphimc.noteblocklib.model.song.Song;
23 | import net.raphimc.noteblocktool.audio.player.AudioSystemSongPlayer;
24 | import net.raphimc.noteblocktool.audio.system.AudioSystem;
25 |
26 | import java.util.Map;
27 | import java.util.function.Function;
28 |
29 | public class SongRenderer extends AudioSystemSongPlayer {
30 |
31 | private boolean isRunning;
32 |
33 | public SongRenderer(final Song song, final Function, AudioSystem> audioSystemSupplier) {
34 | super(song, audioSystemSupplier);
35 | this.setCustomScheduler(null);
36 | if (this.getAudioSystem().getLoopbackAudioFormat() == null) {
37 | throw new IllegalArgumentException("AudioSystem isn't configured for loopback rendering");
38 | }
39 | }
40 |
41 | @Override
42 | public void start(final int delay, final int tick) {
43 | super.start(delay, tick);
44 | this.isRunning = true;
45 | }
46 |
47 | @Override
48 | public void stop() {
49 | this.isRunning = false;
50 | super.stop();
51 | }
52 |
53 | @Override
54 | public boolean isRunning() {
55 | return this.isRunning;
56 | }
57 |
58 | public float[] renderTick() {
59 | if (this.isRunning()) {
60 | this.tick();
61 | }
62 | return this.getAudioSystem().render(MathUtil.millisToFrameCount(this.getAudioSystem().getLoopbackAudioFormat(), 1000F / this.getCurrentTicksPerSecond()));
63 | }
64 |
65 | public float[] renderSong() throws InterruptedException {
66 | final GrowableArray samples = new GrowableArray(MathUtil.millisToSampleCount(this.getAudioSystem().getLoopbackAudioFormat(), (this.getSong().getLengthInSeconds() + 5) * 1000));
67 | this.start();
68 | while (this.isRunning()) {
69 | samples.add(this.renderTick());
70 | if (Thread.currentThread().isInterrupted()) {
71 | throw new InterruptedException();
72 | }
73 | }
74 | samples.add(this.getAudioSystem().render(MathUtil.millisToFrameCount(this.getAudioSystem().getLoopbackAudioFormat(), 3000)));
75 | return samples.getArray();
76 | }
77 |
78 | }
79 |
--------------------------------------------------------------------------------
/src/main/java/net/raphimc/noteblocktool/frames/visualizer/VisualizerWindow.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of NoteBlockTool - https://github.com/RaphiMC/NoteBlockTool
3 | * Copyright (C) 2022-2025 RK_01/RaphiMC and contributors
4 | *
5 | * This program is free software; you can redistribute it and/or
6 | * modify it under the terms of the GNU Lesser General Public
7 | * License as published by the Free Software Foundation; either
8 | * version 3 of the License, or (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package net.raphimc.noteblocktool.frames.visualizer;
19 |
20 | import net.lenni0451.commons.color.Color;
21 | import net.raphimc.noteblocktool.audio.player.AudioSystemSongPlayer;
22 | import net.raphimc.thingl.implementation.application.GLFWApplicationRunner;
23 | import org.joml.Matrix4fStack;
24 | import org.lwjgl.glfw.GLFW;
25 |
26 | import java.util.concurrent.CancellationException;
27 |
28 | public class VisualizerWindow extends GLFWApplicationRunner {
29 |
30 | private final DropRenderer dropRenderer;
31 | private final Runnable openCallback;
32 | private final Runnable closeCallback;
33 |
34 | public VisualizerWindow(final AudioSystemSongPlayer songPlayer, final Runnable openCallback, final Runnable closeCallback) {
35 | super(new Configuration()
36 | .setUseSeparateThreads(true)
37 | .setWindowTitle("NoteBlockTool Song Visualizer - " + songPlayer.getSong().getTitleOrFileNameOr("No Title"))
38 | );
39 |
40 | this.dropRenderer = new DropRenderer(songPlayer);
41 | this.openCallback = openCallback;
42 | this.closeCallback = closeCallback;
43 |
44 | this.launch();
45 | this.launchFuture.join();
46 | }
47 |
48 | public void close() {
49 | this.windowInterface.runOnWindowThread(() -> GLFW.glfwSetWindowShouldClose(this.window, true));
50 | try {
51 | this.freeFuture.join();
52 | } catch (CancellationException ignored) {
53 | }
54 | }
55 |
56 | @Override
57 | protected void setWindowHints() {
58 | super.setWindowHints();
59 | GLFW.glfwWindowHint(GLFW.GLFW_FOCUS_ON_SHOW, GLFW.GLFW_FALSE);
60 | }
61 |
62 | @Override
63 | protected void createWindow() {
64 | super.createWindow();
65 | this.openCallback.run();
66 | }
67 |
68 | @Override
69 | protected void initThinGL() {
70 | this.thinGL = new ExtendedThinGL(this.windowInterface);
71 | }
72 |
73 | @Override
74 | protected void init() {
75 | super.init();
76 | this.dropRenderer.init();
77 | this.mainFramebuffer.setClearColor(Color.GRAY);
78 | }
79 |
80 | @Override
81 | protected void render(final Matrix4fStack positionMatrix) {
82 | this.dropRenderer.render(positionMatrix);
83 | }
84 |
85 | @Override
86 | protected void freeGL() {
87 | this.dropRenderer.free();
88 | super.freeGL();
89 | }
90 |
91 | @Override
92 | protected void freeWindowSystem() {
93 | super.freeWindowSystem();
94 | if (this.closeCallback != null) {
95 | this.closeCallback.run();
96 | }
97 | }
98 |
99 | }
100 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # NoteBlockTool
2 | Tool for importing, exporting, batch manipulating and playing Minecraft note block songs.
3 |
4 | To download the latest stable version, go to [GitHub Releases](https://github.com/RaphiMC/NoteBlockTool/releases/latest).
5 | To download the latest dev version, go to [GitHub Actions](https://github.com/RaphiMC/NoteBlockTool/actions/workflows/build.yml) or [Lenni0451's Jenkins](https://build.lenni0451.net/job/NoteBlockTool/).
6 |
7 | Using it is very simple, just run the jar file, and it will start a user interface. Detailed instructions can be found in the [Usage](#usage) section.
8 |
9 | ## Features
10 | - Reads .nbs, .mcsp2, .mid, .txt and .notebot files
11 | - Can export all of the above as .nbs, .mp3, .wav and .aif files
12 | - Work with multiple songs at once (Batch processing)
13 | - High performance and accurate song player
14 | - Lag free playback of very large songs
15 | - Supports all NBS features including custom instruments
16 | - Good MIDI importer
17 | - Supports most MIDI files
18 | - Supports velocity and panning
19 | - Can handle Black MIDI files
20 | - Supports all NBS versions
21 | - Version 0 - 5
22 | - Supports undocumented features like Tempo Changers
23 | - Many tools for manipulating songs
24 | - Optimize songs for use in Minecraft (Transposing, Resampling)
25 | - Resampling songs with a different TPS
26 | - NBS metadata editor
27 | - Instrument replacement
28 | - Note deduplication (Useful for Black MIDI files)
29 | - Removal of quiet notes (Useful for Black MIDI files)
30 | - Very fast and efficient
31 | - Imports and manipulates hundreds of songs in seconds
32 | - Can export songs as audio files at multiple times the speed of playback
33 |
34 | ### Limitations
35 | - NBS layers are not preserved when doing anything other than editing the metadata
36 | - All editing is done on intermediary song objects which do not have layers
37 |
38 | ## Usage
39 | ### Importing
40 | After downloading and running the jar file, you will be greeted with a user interface:
41 | 
42 | The table shows all currently loaded songs. Songs can be dragged and dropped into the table to load them or you can use the "Add" button to open a file dialog.
43 | ### Editing
44 | To start editing songs, select one or multiple songs in the table and click the "Edit" button. This will open the song editor:
45 | 
46 | By default, everything is disabled or does nothing. To enable a feature, click the corresponding checkbox or configure its settings.
47 | After you are done editing, click the "Save" button to apply the edits to the selected songs or the "Preview" button to listen to the edits before saving.
48 | ### Playing
49 | To play a song, select it in the table and click the "Play" button. This will open the song player:
50 | 
51 | The song player features all the essential controls for playing a song.
52 | ### Exporting
53 | To export the edited songs, select them in the table and click the "Export" button. This will open the export dialog where you can select a folder to save all the songs to.
54 |
55 | ## Using it in your application
56 | NoteBlockTool uses [NoteBlockLib](https://github.com/RaphiMC/NoteBlockLib) for most of its functionality. For more information on how to use NoteBlockLib in your application, check out [NoteBlockLib](https://github.com/RaphiMC/NoteBlockLib).
57 |
58 | ## Contact
59 | If you encounter any issues, please report them on the
60 | [issue tracker](https://github.com/RaphiMC/NoteBlockTool/issues).
61 | If you just want to talk or need help using NoteBlockTool feel free to join my
62 | [Discord](https://discord.gg/dCzT9XHEWu).
63 |
--------------------------------------------------------------------------------
/src/main/java/net/raphimc/noteblocktool/frames/edittabs/ResamplingTab.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of NoteBlockTool - https://github.com/RaphiMC/NoteBlockTool
3 | * Copyright (C) 2022-2025 RK_01/RaphiMC and contributors
4 | *
5 | * This program is free software; you can redistribute it and/or
6 | * modify it under the terms of the GNU Lesser General Public
7 | * License as published by the Free Software Foundation; either
8 | * version 3 of the License, or (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package net.raphimc.noteblocktool.frames.edittabs;
19 |
20 | import net.lenni0451.commons.swing.GBC;
21 | import net.raphimc.noteblocklib.model.song.Song;
22 | import net.raphimc.noteblocklib.util.SongResampler;
23 | import net.raphimc.noteblocktool.elements.formatter.DoubleFormatterFactory;
24 | import net.raphimc.noteblocktool.frames.ListFrame;
25 |
26 | import javax.swing.*;
27 | import java.awt.*;
28 | import java.util.List;
29 |
30 | public class ResamplingTab extends EditTab {
31 |
32 | private JCheckBox changeTempoEnabled;
33 | private JSpinner changeTempoSpinner;
34 | private JCheckBox precomputeTempoEvents;
35 |
36 | public ResamplingTab(final List songs) {
37 | super("Resampling", songs);
38 | }
39 |
40 | @Override
41 | protected void initComponents(JPanel center) {
42 | JPanel resampling = new JPanel();
43 | resampling.setLayout(new GridBagLayout());
44 | resampling.setBorder(BorderFactory.createTitledBorder("Change tempo"));
45 | center.add(resampling);
46 | GBC.create(resampling).grid(0, 0).insets(5, 5, 0, 5).width(2).anchor(GBC.LINE_START).add(new JCheckBox("Enabled"), checkBox -> {
47 | this.changeTempoEnabled = checkBox;
48 | });
49 | GBC.create(resampling).grid(0, 1).insets(5, 5, 5, 5).anchor(GBC.LINE_START).add(html("New tempo:"));
50 | GBC.create(resampling).grid(1, 1).insets(5, 5, 5, 5).weightx(1).fill(GBC.HORIZONTAL).add(new JSpinner(new SpinnerNumberModel(20D, 5D, 100D, 1D)), spinner -> {
51 | this.changeTempoSpinner = spinner;
52 | ((JSpinner.DefaultEditor) spinner.getEditor()).getTextField().setFormatterFactory(new DoubleFormatterFactory(" TPS"));
53 | });
54 |
55 | JPanel nbsTempoChanger = new JPanel();
56 | nbsTempoChanger.setLayout(new GridBagLayout());
57 | nbsTempoChanger.setBorder(BorderFactory.createTitledBorder("NBS tempo changer"));
58 | center.add(nbsTempoChanger);
59 | GBC.create(nbsTempoChanger).grid(0, 0).insets(5, 5, 0, 5).anchor(GBC.LINE_START).add(new JCheckBox("Precompute NBS tempo changer"), checkBox -> {
60 | this.precomputeTempoEvents = checkBox;
61 | });
62 | GBC.create(nbsTempoChanger).grid(0, 1).insets(5, 5, 5, 5).weightx(1).fill(GBC.HORIZONTAL).add(html("Converts a song with dynamic tempo changes into one with a static tempo. This allows the song to be played in players which don't support dynamic tempo changes."));
63 | }
64 |
65 | @Override
66 | public void apply(final Song song) {
67 | if (this.precomputeTempoEvents.isSelected()) {
68 | SongResampler.precomputeTempoEvents(song);
69 | }
70 | if (this.changeTempoEnabled.isSelected()) {
71 | SongResampler.changeTickSpeed(song, ((Double) this.changeTempoSpinner.getValue()).floatValue());
72 | }
73 | }
74 |
75 | }
76 |
--------------------------------------------------------------------------------
/src/main/java/net/raphimc/noteblocktool/elements/drag/DragTableDropTargetListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of NoteBlockTool - https://github.com/RaphiMC/NoteBlockTool
3 | * Copyright (C) 2022-2025 RK_01/RaphiMC and contributors
4 | *
5 | * This program is free software; you can redistribute it and/or
6 | * modify it under the terms of the GNU Lesser General Public
7 | * License as published by the Free Software Foundation; either
8 | * version 3 of the License, or (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package net.raphimc.noteblocktool.elements.drag;
19 |
20 | import net.raphimc.noteblocktool.elements.TextOverlayPanel;
21 |
22 | import javax.swing.*;
23 | import java.awt.datatransfer.DataFlavor;
24 | import java.awt.datatransfer.Transferable;
25 | import java.awt.dnd.*;
26 | import java.io.File;
27 | import java.util.List;
28 | import java.util.function.Consumer;
29 |
30 | public class DragTableDropTargetListener implements DropTargetListener {
31 |
32 | private final JFrame frame;
33 | private final Consumer fileConsumer;
34 | private TextOverlayPanel textOverlayPanel;
35 |
36 | public DragTableDropTargetListener(final JFrame frame, final Consumer fileConsumer) {
37 | this.frame = frame;
38 | this.fileConsumer = fileConsumer;
39 | }
40 |
41 | private boolean isSupported(final Transferable transferable) {
42 | return transferable.isDataFlavorSupported(DataFlavor.javaFileListFlavor);
43 | }
44 |
45 | private void showOverlay() {
46 | this.textOverlayPanel = new TextOverlayPanel("Drop files here to add them");
47 | this.frame.setGlassPane(this.textOverlayPanel);
48 | this.textOverlayPanel.setVisible(true);
49 | }
50 |
51 | private void hideOverlay() {
52 | if (this.textOverlayPanel != null) {
53 | this.textOverlayPanel.setVisible(false);
54 | this.frame.setGlassPane(new JPanel());
55 | }
56 | }
57 |
58 | @Override
59 | public void dragEnter(DropTargetDragEvent event) {
60 | if (this.isSupported(event.getTransferable())) {
61 | event.acceptDrag(DnDConstants.ACTION_COPY);
62 | this.showOverlay();
63 | } else {
64 | event.rejectDrag();
65 | }
66 | }
67 |
68 | @Override
69 | public void dragOver(DropTargetDragEvent event) {
70 | if (this.isSupported(event.getTransferable())) {
71 | event.acceptDrag(DnDConstants.ACTION_COPY);
72 | } else {
73 | event.rejectDrag();
74 | }
75 | }
76 |
77 | @Override
78 | public void dropActionChanged(DropTargetDragEvent event) {
79 | if (this.isSupported(event.getTransferable())) {
80 | event.acceptDrag(DnDConstants.ACTION_COPY);
81 | } else {
82 | event.rejectDrag();
83 | }
84 | }
85 |
86 | @Override
87 | public void dragExit(DropTargetEvent event) {
88 | this.hideOverlay();
89 | }
90 |
91 | @Override
92 | public void drop(DropTargetDropEvent event) {
93 | this.hideOverlay();
94 | if (!this.isSupported(event.getTransferable())) {
95 | event.rejectDrop();
96 | return;
97 | }
98 |
99 | event.acceptDrop(DnDConstants.ACTION_COPY);
100 | try {
101 | final List files = (List) event.getTransferable().getTransferData(DataFlavor.javaFileListFlavor);
102 | this.fileConsumer.accept(files.toArray(new File[0]));
103 | } catch (Exception e) {
104 | e.printStackTrace();
105 | }
106 | event.dropComplete(true);
107 | }
108 |
109 | }
110 |
--------------------------------------------------------------------------------
/src/main/java/net/raphimc/noteblocktool/util/SoundFileUtil.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of NoteBlockTool - https://github.com/RaphiMC/NoteBlockTool
3 | * Copyright (C) 2022-2025 RK_01/RaphiMC and contributors
4 | *
5 | * This program is free software; you can redistribute it and/or
6 | * modify it under the terms of the GNU Lesser General Public
7 | * License as published by the Free Software Foundation; either
8 | * version 3 of the License, or (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package net.raphimc.noteblocktool.util;
19 |
20 | import net.raphimc.audiomixer.io.ogg.OggVorbisInputStream;
21 | import org.lwjgl.PointerBuffer;
22 | import org.lwjgl.stb.STBVorbis;
23 | import org.lwjgl.system.MemoryStack;
24 | import org.lwjgl.system.MemoryUtil;
25 |
26 | import javax.sound.sampled.AudioFormat;
27 | import javax.sound.sampled.AudioInputStream;
28 | import javax.sound.sampled.AudioSystem;
29 | import javax.sound.sampled.UnsupportedAudioFileException;
30 | import java.io.BufferedInputStream;
31 | import java.io.ByteArrayInputStream;
32 | import java.io.IOException;
33 | import java.io.InputStream;
34 | import java.nio.ByteBuffer;
35 | import java.nio.IntBuffer;
36 | import java.util.Arrays;
37 |
38 | public class SoundFileUtil {
39 |
40 | private static final byte[] OGG_MAGIC = new byte[]{(byte) 'O', (byte) 'g', (byte) 'g', (byte) 'S'};
41 |
42 | public static AudioInputStream readAudioFile(final InputStream inputStream) throws UnsupportedAudioFileException, IOException {
43 | final BufferedInputStream bis = new BufferedInputStream(inputStream);
44 | final byte[] magic = new byte[4];
45 | bis.mark(magic.length);
46 | bis.read(magic);
47 | bis.reset();
48 | if (Arrays.equals(magic, OGG_MAGIC)) {
49 | final byte[] data = IOUtil.readFully(bis);
50 | try {
51 | final ByteBuffer dataBuffer = MemoryUtil.memAlloc(data.length).put(data).flip();
52 | try (MemoryStack memoryStack = MemoryStack.stackPush()) {
53 | final IntBuffer channels = memoryStack.callocInt(1);
54 | final IntBuffer sampleRate = memoryStack.callocInt(1);
55 | final PointerBuffer samples = memoryStack.callocPointer(1);
56 |
57 | final int samplesCount = STBVorbis.stb_vorbis_decode_memory(dataBuffer, channels, sampleRate, samples);
58 | if (samplesCount == -1) {
59 | MemoryUtil.memFree(dataBuffer);
60 | throw new RuntimeException("Failed to decode ogg file");
61 | }
62 |
63 | final ByteBuffer samplesBuffer = samples.getByteBuffer(samplesCount * Short.BYTES);
64 | final byte[] samplesArray = new byte[samplesCount * Short.BYTES];
65 | samplesBuffer.get(samplesArray);
66 |
67 | MemoryUtil.memFree(dataBuffer);
68 | MemoryUtil.memFree(samplesBuffer);
69 |
70 | final AudioFormat audioFormat = new AudioFormat(sampleRate.get(), Short.SIZE, channels.get(), true, false);
71 | return new AudioInputStream(new ByteArrayInputStream(samplesArray), audioFormat, samplesArray.length);
72 | }
73 | } catch (Throwable e) { // Fallback if natives aren't available or if STB Vorbis fails to parse the file
74 | System.err.println("Failed to decode ogg file using STB Vorbis, falling back to JOrbis");
75 | e.printStackTrace();
76 | return OggVorbisInputStream.createAudioInputStream(new ByteArrayInputStream(data));
77 | }
78 | } else {
79 | return AudioSystem.getAudioInputStream(bis);
80 | }
81 | }
82 |
83 | }
84 |
--------------------------------------------------------------------------------
/src/main/resources/fonts/OFL.txt:
--------------------------------------------------------------------------------
1 | Copyright 2011 The Roboto Project Authors (https://github.com/googlefonts/roboto-classic)
2 |
3 | This Font Software is licensed under the SIL Open Font License, Version 1.1.
4 | This license is copied below, and is also available with a FAQ at:
5 | https://openfontlicense.org
6 |
7 |
8 | -----------------------------------------------------------
9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
10 | -----------------------------------------------------------
11 |
12 | PREAMBLE
13 | The goals of the Open Font License (OFL) are to stimulate worldwide
14 | development of collaborative font projects, to support the font creation
15 | efforts of academic and linguistic communities, and to provide a free and
16 | open framework in which fonts may be shared and improved in partnership
17 | with others.
18 |
19 | The OFL allows the licensed fonts to be used, studied, modified and
20 | redistributed freely as long as they are not sold by themselves. The
21 | fonts, including any derivative works, can be bundled, embedded,
22 | redistributed and/or sold with any software provided that any reserved
23 | names are not used by derivative works. The fonts and derivatives,
24 | however, cannot be released under any other type of license. The
25 | requirement for fonts to remain under this license does not apply
26 | to any document created using the fonts or their derivatives.
27 |
28 | DEFINITIONS
29 | "Font Software" refers to the set of files released by the Copyright
30 | Holder(s) under this license and clearly marked as such. This may
31 | include source files, build scripts and documentation.
32 |
33 | "Reserved Font Name" refers to any names specified as such after the
34 | copyright statement(s).
35 |
36 | "Original Version" refers to the collection of Font Software components as
37 | distributed by the Copyright Holder(s).
38 |
39 | "Modified Version" refers to any derivative made by adding to, deleting,
40 | or substituting -- in part or in whole -- any of the components of the
41 | Original Version, by changing formats or by porting the Font Software to a
42 | new environment.
43 |
44 | "Author" refers to any designer, engineer, programmer, technical
45 | writer or other person who contributed to the Font Software.
46 |
47 | PERMISSION & CONDITIONS
48 | Permission is hereby granted, free of charge, to any person obtaining
49 | a copy of the Font Software, to use, study, copy, merge, embed, modify,
50 | redistribute, and sell modified and unmodified copies of the Font
51 | Software, subject to the following conditions:
52 |
53 | 1) Neither the Font Software nor any of its individual components,
54 | in Original or Modified Versions, may be sold by itself.
55 |
56 | 2) Original or Modified Versions of the Font Software may be bundled,
57 | redistributed and/or sold with any software, provided that each copy
58 | contains the above copyright notice and this license. These can be
59 | included either as stand-alone text files, human-readable headers or
60 | in the appropriate machine-readable metadata fields within text or
61 | binary files as long as those fields can be easily viewed by the user.
62 |
63 | 3) No Modified Version of the Font Software may use the Reserved Font
64 | Name(s) unless explicit written permission is granted by the corresponding
65 | Copyright Holder. This restriction only applies to the primary font name as
66 | presented to the users.
67 |
68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
69 | Software shall not be used to promote, endorse or advertise any
70 | Modified Version, except to acknowledge the contribution(s) of the
71 | Copyright Holder(s) and the Author(s) or with their explicit written
72 | permission.
73 |
74 | 5) The Font Software, modified or unmodified, in part or in whole,
75 | must be distributed entirely under this license, and must not be
76 | distributed under any other license. The requirement for fonts to
77 | remain under this license does not apply to any document created
78 | using the Font Software.
79 |
80 | TERMINATION
81 | This license becomes null and void if any of the above conditions are
82 | not met.
83 |
84 | DISCLAIMER
85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
93 | OTHER DEALINGS IN THE FONT SOFTWARE.
94 |
--------------------------------------------------------------------------------
/src/main/java/net/raphimc/noteblocktool/audio/player/AudioSystemSongPlayer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of NoteBlockTool - https://github.com/RaphiMC/NoteBlockTool
3 | * Copyright (C) 2022-2025 RK_01/RaphiMC and contributors
4 | *
5 | * This program is free software; you can redistribute it and/or
6 | * modify it under the terms of the GNU Lesser General Public
7 | * License as published by the Free Software Foundation; either
8 | * version 3 of the License, or (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package net.raphimc.noteblocktool.audio.player;
19 |
20 | import net.raphimc.noteblocklib.format.minecraft.MinecraftInstrument;
21 | import net.raphimc.noteblocklib.format.nbs.model.NbsCustomInstrument;
22 | import net.raphimc.noteblocklib.model.note.Note;
23 | import net.raphimc.noteblocklib.model.song.Song;
24 | import net.raphimc.noteblocklib.player.SongPlayer;
25 | import net.raphimc.noteblocktool.audio.SoundMap;
26 | import net.raphimc.noteblocktool.audio.system.AudioSystem;
27 |
28 | import java.io.File;
29 | import java.util.ArrayList;
30 | import java.util.List;
31 | import java.util.Map;
32 | import java.util.function.Function;
33 |
34 | public abstract class AudioSystemSongPlayer extends SongPlayer implements AutoCloseable {
35 |
36 | private final AudioSystem audioSystem;
37 | private long lastTickTime;
38 | private float cpuLoad;
39 |
40 | public AudioSystemSongPlayer(final Song song, final Function, AudioSystem> audioSystemSupplier) {
41 | super(song);
42 | this.audioSystem = audioSystemSupplier.apply(SoundMap.loadSoundData(song));
43 | }
44 |
45 | @Override
46 | public void setTick(final int tick) {
47 | super.setTick(tick);
48 | this.lastTickTime = System.nanoTime();
49 | }
50 |
51 | @Override
52 | protected void tick() {
53 | final long startTime = System.nanoTime();
54 | super.tick();
55 | this.lastTickTime = System.nanoTime();
56 | final float neededMillis = (this.lastTickTime - startTime) / 1_000_000F;
57 | final float availableMillis = 1000F / this.getCurrentTicksPerSecond();
58 | this.cpuLoad = (neededMillis / availableMillis) * 100F;
59 | }
60 |
61 | @Override
62 | protected boolean shouldTick() {
63 | this.audioSystem.preTick();
64 | return super.shouldTick();
65 | }
66 |
67 | @Override
68 | protected void playNotes(final List notes) {
69 | for (Note note : notes) {
70 | if (note.getInstrument() instanceof MinecraftInstrument instrument) {
71 | this.audioSystem.playSound(SoundMap.INSTRUMENT_SOUNDS.get(instrument), note.getPitch(), note.getVolume(), note.getPanning());
72 | } else if (note.getInstrument() instanceof NbsCustomInstrument instrument) {
73 | this.audioSystem.playSound(instrument.getSoundFilePathOr("").replace(File.separatorChar, '/'), note.getPitch(), note.getVolume(), note.getPanning());
74 | } else {
75 | throw new IllegalArgumentException("Unsupported instrument type: " + note.getInstrument().getClass().getName());
76 | }
77 | }
78 | }
79 |
80 | @Override
81 | public void close() {
82 | this.stop();
83 | this.audioSystem.close();
84 | }
85 |
86 | public AudioSystem getAudioSystem() {
87 | return this.audioSystem;
88 | }
89 |
90 | public long getLastTickTime() {
91 | return this.lastTickTime;
92 | }
93 |
94 | public float getCpuLoad() {
95 | return this.cpuLoad;
96 | }
97 |
98 | public List getStatusLines() {
99 | final List statusLines = new ArrayList<>();
100 | if (this.audioSystem.getPlayingSounds() != null) {
101 | statusLines.add("Sounds: " + this.audioSystem.getPlayingSounds() + " / " + this.audioSystem.getMaxSounds());
102 | }
103 | if (this.getAudioRendererCpuLoad() != null) {
104 | statusLines.add("Audio Renderer CPU Load: " + this.getAudioRendererCpuLoad().intValue() + "%");
105 | }
106 | statusLines.add("Song Player CPU Load: " + (int) this.getCpuLoad() + "%");
107 | return statusLines;
108 | }
109 |
110 | protected Float getAudioRendererCpuLoad() {
111 | return this.audioSystem.getCpuLoad();
112 | }
113 |
114 | }
115 |
--------------------------------------------------------------------------------
/src/main/java/net/raphimc/noteblocktool/frames/edittabs/NotesTab.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of NoteBlockTool - https://github.com/RaphiMC/NoteBlockTool
3 | * Copyright (C) 2022-2025 RK_01/RaphiMC and contributors
4 | *
5 | * This program is free software; you can redistribute it and/or
6 | * modify it under the terms of the GNU Lesser General Public
7 | * License as published by the Free Software Foundation; either
8 | * version 3 of the License, or (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package net.raphimc.noteblocktool.frames.edittabs;
19 |
20 | import net.lenni0451.commons.swing.GBC;
21 | import net.raphimc.noteblocklib.model.song.Song;
22 | import net.raphimc.noteblocktool.elements.formatter.IntFormatterFactory;
23 | import net.raphimc.noteblocktool.frames.ListFrame;
24 | import net.raphimc.noteblocktool.util.MinecraftOctaveClamp;
25 |
26 | import javax.swing.*;
27 | import java.awt.*;
28 | import java.util.List;
29 |
30 | public class NotesTab extends EditTab {
31 |
32 | private JComboBox octaveClamp;
33 | private JSpinner volumeSpinner;
34 | private JCheckBox removeDoubleNotes;
35 |
36 | public NotesTab(final List songs) {
37 | super("Notes", songs);
38 | }
39 |
40 | @Override
41 | protected void initComponents(JPanel center) {
42 | JPanel octaveClamp = new JPanel();
43 | octaveClamp.setLayout(new GridBagLayout());
44 | octaveClamp.setBorder(BorderFactory.createTitledBorder("Minecraft Octave Clamp"));
45 | center.add(octaveClamp);
46 | GBC.create(octaveClamp).grid(0, 0).insets(5, 5, 0, 5).weightx(1).fill(GBC.HORIZONTAL).add(new JComboBox<>(MinecraftOctaveClamp.values()), comboBox -> {
47 | this.octaveClamp = comboBox;
48 | });
49 | GBC.create(octaveClamp).grid(0, 1).insets(5, 5, 5, 5).width(2).weightx(1).fill(GBC.HORIZONTAL).add(html(
50 | "NONE: Don't change the key of the note.",
51 | "INSTRUMENT_SHIFT: \"Transposes\" the key of the note by shifting the instrument to a higher or lower sounding one. This often sounds the best of the three methods as it keeps the musical key the same and only changes the instrument.",
52 | "TRANSPOSE: Transposes the key of the note to fall within minecraft octave range. Any key below 33 will be transposed up an octave, and any key above 57 will be transposed down an octave.",
53 | "CLAMP: Clamps the key of the note to fall within minecraft octave range. Any key below 33 will be set to 33, and any key above 57 will be set to 57."
54 | ));
55 |
56 | JPanel volume = new JPanel();
57 | volume.setLayout(new GridBagLayout());
58 | volume.setBorder(BorderFactory.createTitledBorder("Volume"));
59 | center.add(volume);
60 | GBC.create(volume).grid(0, 0).insets(5, 5, 5, 5).anchor(GBC.LINE_START).add(html("Remove notes quieter than:"));
61 | GBC.create(volume).grid(1, 0).insets(5, 5, 5, 5).weightx(1).fill(GBC.HORIZONTAL).add(new JSpinner(new SpinnerNumberModel(0, 0, 100, 1)), spinner -> {
62 | this.volumeSpinner = spinner;
63 | ((JSpinner.DefaultEditor) spinner.getEditor()).getTextField().setFormatterFactory(new IntFormatterFactory("%"));
64 | });
65 |
66 | JPanel removeDoubleNotes = new JPanel();
67 | removeDoubleNotes.setLayout(new GridBagLayout());
68 | removeDoubleNotes.setBorder(BorderFactory.createTitledBorder("Deduplication"));
69 | center.add(removeDoubleNotes);
70 | GBC.create(removeDoubleNotes).grid(0, 0).insets(5, 5, 0, 5).anchor(GBC.LINE_START).add(new JCheckBox("Remove duplicate notes"), checkBox -> {
71 | this.removeDoubleNotes = checkBox;
72 | });
73 | GBC.create(removeDoubleNotes).grid(0, 1).insets(5, 5, 5, 5).weightx(1).fill(GBC.HORIZONTAL).add(html("Removes notes which would be played multiple times in the same tick. Useful when handling large MIDI files with a lot of duplicate notes or when downsampling the tick speed of the song."));
74 | }
75 |
76 | @Override
77 | public void apply(final Song song) {
78 | song.getNotes().forEach(note -> ((MinecraftOctaveClamp) this.octaveClamp.getSelectedItem()).correctNote(note));
79 | song.getNotes().removeSilentNotes((int) this.volumeSpinner.getValue() / 100F);
80 | if (this.removeDoubleNotes.isSelected()) {
81 | song.getNotes().removeDoubleNotes();
82 | }
83 | }
84 |
85 | }
86 |
--------------------------------------------------------------------------------
/src/main/java/net/raphimc/noteblocktool/audio/library/BassMixLibrary.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of NoteBlockTool - https://github.com/RaphiMC/NoteBlockTool
3 | * Copyright (C) 2022-2025 RK_01/RaphiMC and contributors
4 | *
5 | * This program is free software; you can redistribute it and/or
6 | * modify it under the terms of the GNU Lesser General Public
7 | * License as published by the Free Software Foundation; either
8 | * version 3 of the License, or (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package net.raphimc.noteblocktool.audio.library;
19 |
20 | import com.sun.jna.Library;
21 | import com.sun.jna.Native;
22 |
23 | import java.util.HashMap;
24 | import java.util.Map;
25 |
26 | public interface BassMixLibrary extends Library {
27 |
28 | BassMixLibrary INSTANCE = loadNative();
29 |
30 | // Additional BASS_SetConfig options
31 | int BASS_CONFIG_MIXER_BUFFER = 0x10601;
32 | int BASS_CONFIG_MIXER_POSEX = 0x10602;
33 | int BASS_CONFIG_SPLIT_BUFFER = 0x10610;
34 |
35 | // BASS_Mixer_StreamCreate flags
36 | int BASS_MIXER_RESUME = 0x1000;// resume stalled immediately upon new/unpaused source
37 | int BASS_MIXER_POSEX = 0x2000;// enable BASS_Mixer_ChannelGetPositionEx support
38 | int BASS_MIXER_NOSPEAKER = 0x4000;// ignore speaker arrangement
39 | int BASS_MIXER_QUEUE = 0x8000;// queue sources
40 | int BASS_MIXER_END = 0x10000;// end the stream when there are no sources
41 | int BASS_MIXER_NONSTOP = 0x20000;// don't stall when there are no sources
42 |
43 | // BASS_Mixer_StreamAddChannel/Ex flags
44 | int BASS_MIXER_CHAN_ABSOLUTE = 0x1000;// start is an absolute position
45 | int BASS_MIXER_CHAN_BUFFER = 0x2000;// buffer data for BASS_Mixer_ChannelGetData/Level
46 | int BASS_MIXER_CHAN_LIMIT = 0x4000;// limit mixer processing to the amount available from this source
47 | int BASS_MIXER_CHAN_MATRIX = 0x10000;// matrix mixing
48 | int BASS_MIXER_CHAN_PAUSE = 0x20000;// don't process the source
49 | int BASS_MIXER_CHAN_DOWNMIX = 0x400000; // downmix to stereo/mono
50 | int BASS_MIXER_CHAN_NORAMPIN = 0x800000; // don't ramp-in the start
51 | int BASS_MIXER_BUFFER = BASS_MIXER_CHAN_BUFFER;
52 | int BASS_MIXER_LIMIT = BASS_MIXER_CHAN_LIMIT;
53 | int BASS_MIXER_MATRIX = BASS_MIXER_CHAN_MATRIX;
54 | int BASS_MIXER_PAUSE = BASS_MIXER_CHAN_PAUSE;
55 | int BASS_MIXER_DOWNMIX = BASS_MIXER_CHAN_DOWNMIX;
56 | int BASS_MIXER_NORAMPIN = BASS_MIXER_CHAN_NORAMPIN;
57 |
58 | // Mixer attributes
59 | int BASS_ATTRIB_MIXER_LATENCY = 0x15000;
60 | int BASS_ATTRIB_MIXER_THREADS = 0x15001;
61 | int BASS_ATTRIB_MIXER_VOL = 0x15002;
62 |
63 | // Additional BASS_Mixer_ChannelIsActive return values
64 | int BASS_ACTIVE_WAITING = 5;
65 | int BASS_ACTIVE_QUEUED = 6;
66 |
67 | // BASS_Split_StreamCreate flags
68 | int BASS_SPLIT_SLAVE = 0x1000;// only read buffered data
69 | int BASS_SPLIT_POS = 0x2000;
70 |
71 | // Splitter attributes
72 | int BASS_ATTRIB_SPLIT_ASYNCBUFFER = 0x15010;
73 | int BASS_ATTRIB_SPLIT_ASYNCPERIOD = 0x15011;
74 |
75 | // Envelope types
76 | int BASS_MIXER_ENV_FREQ = 1;
77 | int BASS_MIXER_ENV_VOL = 2;
78 | int BASS_MIXER_ENV_PAN = 3;
79 | int BASS_MIXER_ENV_LOOP = 0x10000; // flag: loop
80 | int BASS_MIXER_ENV_REMOVE = 0x20000; // flag: remove at end
81 |
82 | // Additional sync types
83 | int BASS_SYNC_MIXER_ENVELOPE = 0x10200;
84 | int BASS_SYNC_MIXER_ENVELOPE_NODE = 0x10201;
85 | int BASS_SYNC_MIXER_QUEUE = 0x10202;
86 |
87 | // Additional BASS_Mixer_ChannelSetPosition flag
88 | int BASS_POS_MIXER_RESET = 0x10000; // flag: clear mixer's playback buffer
89 |
90 | // Additional BASS_Mixer_ChannelGetPosition mode
91 | int BASS_POS_MIXER_DELAY = 5;
92 |
93 | // BASS_CHANNELINFO types
94 | int BASS_CTYPE_STREAM_MIXER = 0x10800;
95 | int BASS_CTYPE_STREAM_SPLIT = 0x10801;
96 |
97 | static BassMixLibrary loadNative() {
98 | try {
99 | final Map options = new HashMap<>();
100 | options.put(Library.OPTION_STRING_ENCODING, "UTF-8");
101 | return Native.load("bassmix", BassMixLibrary.class, options);
102 | } catch (Throwable ignored) {
103 | }
104 | return null;
105 | }
106 |
107 | static boolean isLoaded() {
108 | return INSTANCE != null;
109 | }
110 |
111 | int BASS_Mixer_StreamCreate(final int freq, final int chans, final int flags);
112 |
113 | boolean BASS_Mixer_StreamAddChannel(final int handle, final int channel, final int flags);
114 |
115 | }
116 |
--------------------------------------------------------------------------------
/src/main/java/net/raphimc/noteblocktool/audio/SoundMap.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of NoteBlockTool - https://github.com/RaphiMC/NoteBlockTool
3 | * Copyright (C) 2022-2025 RK_01/RaphiMC and contributors
4 | *
5 | * This program is free software; you can redistribute it and/or
6 | * modify it under the terms of the GNU Lesser General Public
7 | * License as published by the Free Software Foundation; either
8 | * version 3 of the License, or (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package net.raphimc.noteblocktool.audio;
19 |
20 | import net.raphimc.noteblocklib.format.minecraft.MinecraftInstrument;
21 | import net.raphimc.noteblocklib.format.nbs.model.NbsCustomInstrument;
22 | import net.raphimc.noteblocklib.model.song.Song;
23 | import net.raphimc.noteblocklib.util.SongUtil;
24 | import net.raphimc.noteblocktool.util.IOUtil;
25 |
26 | import java.io.File;
27 | import java.net.URL;
28 | import java.nio.file.Files;
29 | import java.util.EnumMap;
30 | import java.util.HashMap;
31 | import java.util.Map;
32 |
33 | public class SoundMap {
34 |
35 | public static final Map INSTRUMENT_SOUNDS = new EnumMap<>(MinecraftInstrument.class);
36 | private static final Map ALL_SOUND_LOCATIONS = new HashMap<>();
37 |
38 | static {
39 | INSTRUMENT_SOUNDS.put(MinecraftInstrument.HARP, "harp.ogg");
40 | INSTRUMENT_SOUNDS.put(MinecraftInstrument.BASS, "bass.ogg");
41 | INSTRUMENT_SOUNDS.put(MinecraftInstrument.BASS_DRUM, "bd.ogg");
42 | INSTRUMENT_SOUNDS.put(MinecraftInstrument.SNARE, "snare.ogg");
43 | INSTRUMENT_SOUNDS.put(MinecraftInstrument.HAT, "hat.ogg");
44 | INSTRUMENT_SOUNDS.put(MinecraftInstrument.GUITAR, "guitar.ogg");
45 | INSTRUMENT_SOUNDS.put(MinecraftInstrument.FLUTE, "flute.ogg");
46 | INSTRUMENT_SOUNDS.put(MinecraftInstrument.BELL, "bell.ogg");
47 | INSTRUMENT_SOUNDS.put(MinecraftInstrument.CHIME, "icechime.ogg");
48 | INSTRUMENT_SOUNDS.put(MinecraftInstrument.XYLOPHONE, "xylobone.ogg");
49 | INSTRUMENT_SOUNDS.put(MinecraftInstrument.IRON_XYLOPHONE, "iron_xylophone.ogg");
50 | INSTRUMENT_SOUNDS.put(MinecraftInstrument.COW_BELL, "cow_bell.ogg");
51 | INSTRUMENT_SOUNDS.put(MinecraftInstrument.DIDGERIDOO, "didgeridoo.ogg");
52 | INSTRUMENT_SOUNDS.put(MinecraftInstrument.BIT, "bit.ogg");
53 | INSTRUMENT_SOUNDS.put(MinecraftInstrument.BANJO, "banjo.ogg");
54 | INSTRUMENT_SOUNDS.put(MinecraftInstrument.PLING, "pling.ogg");
55 |
56 | reload(null);
57 | }
58 |
59 | public static void reload(final File customSoundsFolder) {
60 | ALL_SOUND_LOCATIONS.clear();
61 | for (Map.Entry entry : INSTRUMENT_SOUNDS.entrySet()) {
62 | ALL_SOUND_LOCATIONS.put(entry.getValue(), SoundMap.class.getResource("/noteblock_sounds/" + entry.getValue()));
63 | }
64 |
65 | if (customSoundsFolder != null && customSoundsFolder.exists() && customSoundsFolder.isDirectory()) {
66 | try {
67 | Files.walk(customSoundsFolder.toPath()).forEach(path -> {
68 | try {
69 | if (Files.isDirectory(path)) return;
70 |
71 | final String fileName = customSoundsFolder.toPath().relativize(path).toString();
72 | if (fileName.endsWith(".ogg") || fileName.endsWith(".wav")) {
73 | ALL_SOUND_LOCATIONS.put(fileName.replace(File.separatorChar, '/'), path.toUri().toURL());
74 | }
75 | } catch (Throwable e) {
76 | throw new RuntimeException("Error while loading custom sound sample", e);
77 | }
78 | });
79 | } catch (Throwable e) {
80 | throw new RuntimeException("Failed to load custom sound samples", e);
81 | }
82 | }
83 | }
84 |
85 | public static Map loadSoundData(final Song song) {
86 | try {
87 | final Map soundData = new HashMap<>();
88 | for (MinecraftInstrument instrument : SongUtil.getUsedVanillaInstruments(song)) {
89 | final String sound = INSTRUMENT_SOUNDS.get(instrument);
90 | if (sound != null && ALL_SOUND_LOCATIONS.containsKey(sound)) {
91 | soundData.put(sound, IOUtil.readFully(ALL_SOUND_LOCATIONS.get(sound).openStream()));
92 | }
93 | }
94 | for (NbsCustomInstrument customInstrument : SongUtil.getUsedNbsCustomInstruments(song)) {
95 | final String fileName = customInstrument.getSoundFilePathOr("").replace(File.separatorChar, '/');
96 | if (ALL_SOUND_LOCATIONS.containsKey(fileName)) {
97 | soundData.put(fileName, IOUtil.readFully(ALL_SOUND_LOCATIONS.get(fileName).openStream()));
98 | }
99 | }
100 | return soundData;
101 | } catch (Throwable e) {
102 | throw new RuntimeException("Failed to load sound samples", e);
103 | }
104 | }
105 |
106 | }
107 |
--------------------------------------------------------------------------------
/src/main/java/net/raphimc/noteblocktool/elements/drag/DragTable.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of NoteBlockTool - https://github.com/RaphiMC/NoteBlockTool
3 | * Copyright (C) 2022-2025 RK_01/RaphiMC and contributors
4 | *
5 | * This program is free software; you can redistribute it and/or
6 | * modify it under the terms of the GNU Lesser General Public
7 | * License as published by the Free Software Foundation; either
8 | * version 3 of the License, or (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package net.raphimc.noteblocktool.elements.drag;
19 |
20 | import net.raphimc.noteblocklib.util.SongUtil;
21 | import net.raphimc.noteblocktool.frames.ListFrame;
22 |
23 | import javax.swing.*;
24 | import javax.swing.table.TableModel;
25 | import javax.swing.table.TableRowSorter;
26 | import java.awt.event.MouseEvent;
27 | import java.util.ArrayList;
28 | import java.util.Comparator;
29 | import java.util.List;
30 |
31 | public class DragTable extends JTable {
32 |
33 | public DragTable() {
34 | super(new DragTableModel("Path", "Title", "Author", "Length", "Notes", "Tempo", "Minecraft compatible"));
35 |
36 | this.getTableHeader().setReorderingAllowed(false);
37 | this.getColumnModel().getColumn(1).setPreferredWidth(250);
38 | this.getColumnModel().getColumn(3).setPreferredWidth(25);
39 | this.getColumnModel().getColumn(4).setPreferredWidth(25);
40 | this.getColumnModel().getColumn(5).setPreferredWidth(25);
41 |
42 | TableRowSorter sorter = new TableRowSorter<>(this.getModel());
43 | List sortKeys = new ArrayList<>();
44 | for (int i = 0; i < 6; i++) sortKeys.add(new RowSorter.SortKey(i, SortOrder.UNSORTED));
45 | sorter.setSortKeys(sortKeys);
46 | sorter.setComparator(4, Comparator.comparingInt(o -> (int) o));
47 | this.setRowSorter(sorter);
48 | }
49 |
50 | public void addRow(final ListFrame.LoadedSong song) {
51 | ((DragTableModel) this.getModel()).addRow(new Object[]{
52 | song,
53 | song.song().getTitleOrFileNameOr("No Title"),
54 | song.song().getAuthorOr("Unknown"),
55 | song.song().getHumanReadableLength(),
56 | song.song().getNotes().getNoteCount(),
57 | song.song().getTempoEvents().getHumanReadableTempoRange(),
58 | this.isSchematicCompatible(song)
59 | });
60 | }
61 |
62 | public void refreshRow(final ListFrame.LoadedSong song) {
63 | for (int i = 0; i < this.getModel().getRowCount(); i++) {
64 | if (this.getModel().getValueAt(i, 0) == song) {
65 | this.getModel().setValueAt(song.song().getTitleOrFileNameOr("No Title"), i, 1);
66 | this.getModel().setValueAt(song.song().getAuthorOr("Unknown"), i, 2);
67 | this.getModel().setValueAt(song.song().getHumanReadableLength(), i, 3);
68 | this.getModel().setValueAt(song.song().getNotes().getNoteCount(), i, 4);
69 | this.getModel().setValueAt(song.song().getTempoEvents().getHumanReadableTempoRange(), i, 5);
70 | this.getModel().setValueAt(this.isSchematicCompatible(song), i, 6);
71 | break;
72 | }
73 | }
74 | }
75 |
76 | @Override
77 | public String getToolTipText(MouseEvent event) {
78 | int row = this.rowAtPoint(event.getPoint());
79 | int column = this.columnAtPoint(event.getPoint());
80 | if (row < 0) return null;
81 | if (column == 0) return this.getModel().getValueAt(row, column).toString();
82 | if (column == 6) {
83 | CompatibilityResult result = (CompatibilityResult) this.getModel().getValueAt(row, column);
84 | return result.getTooltip();
85 | }
86 | return null;
87 | }
88 |
89 | private CompatibilityResult isSchematicCompatible(final ListFrame.LoadedSong song) {
90 | final CompatibilityResult result = new CompatibilityResult();
91 |
92 | final float[] tempoRange = song.song().getTempoEvents().getTempoRange();
93 | if (tempoRange[0] != tempoRange[1] || (tempoRange[0] != 2.5F && tempoRange[0] != 5F && tempoRange[0] != 10F)) {
94 | result.add("The tempo must be 2.5, 5 or 10 TPS");
95 | }
96 |
97 | if (SongUtil.hasOutsideMinecraftOctaveRangeNotes(song.song())) {
98 | result.add("The song contains notes which are outside of the Minecraft octave range");
99 | }
100 |
101 | if (!SongUtil.getUsedNbsCustomInstruments(song.song()).isEmpty()) {
102 | result.add("The song contains notes with custom instruments");
103 | }
104 |
105 | return result;
106 | }
107 |
108 |
109 | private static class CompatibilityResult {
110 | private final List reasons;
111 |
112 | private CompatibilityResult() {
113 | this.reasons = new ArrayList<>();
114 | }
115 |
116 | private void add(final String reason) {
117 | this.reasons.add(reason);
118 | }
119 |
120 | private String getTooltip() {
121 | if (this.reasons.isEmpty()) return null;
122 | return String.join("\n", this.reasons);
123 | }
124 |
125 | @Override
126 | public String toString() {
127 | return this.reasons.isEmpty() ? "Yes" : "No";
128 | }
129 | }
130 |
131 | }
132 |
--------------------------------------------------------------------------------
/src/main/java/net/raphimc/noteblocktool/frames/EditFrame.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of NoteBlockTool - https://github.com/RaphiMC/NoteBlockTool
3 | * Copyright (C) 2022-2025 RK_01/RaphiMC and contributors
4 | *
5 | * This program is free software; you can redistribute it and/or
6 | * modify it under the terms of the GNU Lesser General Public
7 | * License as published by the Free Software Foundation; either
8 | * version 3 of the License, or (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package net.raphimc.noteblocktool.frames;
19 |
20 | import net.raphimc.noteblocklib.model.song.Song;
21 | import net.raphimc.noteblocklib.util.SongUtil;
22 | import net.raphimc.noteblocktool.frames.edittabs.*;
23 |
24 | import javax.swing.*;
25 | import java.awt.*;
26 | import java.util.List;
27 | import java.util.function.Consumer;
28 |
29 | public class EditFrame extends JFrame {
30 |
31 | private final List songs;
32 | private final Consumer songRefreshConsumer;
33 | private NotesTab notesTab;
34 | private ResamplingTab resamplingTab;
35 | private InstrumentsTab instrumentsTab;
36 | private CustomInstrumentsTab customInstrumentsTab;
37 | private MetadataTab metadataTab;
38 |
39 | public EditFrame(final List songs, final Consumer songRefreshConsumer) {
40 | this.songs = songs;
41 | this.songRefreshConsumer = songRefreshConsumer;
42 |
43 | this.setTitle("NoteBlockTool Editor");
44 | this.setIconImage(new ImageIcon(this.getClass().getResource("/icon.png")).getImage());
45 | this.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
46 | this.setSize(500, 400);
47 | this.setLocationRelativeTo(null);
48 |
49 | this.initComponents();
50 |
51 | this.setMinimumSize(this.getSize());
52 | this.setVisible(true);
53 | }
54 |
55 | private void initComponents() {
56 | JPanel root = new JPanel();
57 | root.setLayout(new BorderLayout());
58 | this.setContentPane(root);
59 |
60 | { //Center Panel
61 | JTabbedPane tabs = new JTabbedPane();
62 | root.add(tabs, BorderLayout.CENTER);
63 |
64 | this.notesTab = new NotesTab(this.songs);
65 | this.resamplingTab = new ResamplingTab(this.songs);
66 | this.instrumentsTab = new InstrumentsTab(this.songs);
67 | this.customInstrumentsTab = new CustomInstrumentsTab(this.songs);
68 | this.metadataTab = new MetadataTab(this.songs);
69 | tabs.addTab(this.notesTab.getTitle(), this.notesTab);
70 | tabs.addTab(this.resamplingTab.getTitle(), this.resamplingTab);
71 | tabs.addTab(this.instrumentsTab.getTitle(), this.instrumentsTab);
72 | tabs.addTab(this.customInstrumentsTab.getTitle(), this.customInstrumentsTab);
73 | tabs.addTab(this.metadataTab.getTitle(), this.metadataTab);
74 | if (this.songs.size() != 1) {
75 | int metadataTabIndex = tabs.indexOfTab(this.metadataTab.getTitle());
76 | tabs.setEnabledAt(metadataTabIndex, false);
77 | tabs.setToolTipTextAt(metadataTabIndex, "This tab is only available when editing a single song");
78 | }
79 | if (this.songs.size() != 1 || SongUtil.getUsedNbsCustomInstruments(this.songs.get(0).song()).isEmpty()) {
80 | int customInstrumentsTabIndex = tabs.indexOfTab(this.customInstrumentsTab.getTitle());
81 | tabs.removeTabAt(customInstrumentsTabIndex);
82 | this.customInstrumentsTab = null;
83 | }
84 | for (int i = 0; i < tabs.getTabCount(); i++) {
85 | EditTab tab = (EditTab) tabs.getComponentAt(i);
86 | tab.init();
87 | }
88 | }
89 | { //South Panel
90 | JPanel south = new JPanel();
91 | south.setLayout(new FlowLayout(FlowLayout.RIGHT));
92 | this.add(south, BorderLayout.SOUTH);
93 |
94 | JButton apply = new JButton("Save");
95 | apply.addActionListener(e -> {
96 | for (ListFrame.LoadedSong loadedSong : this.songs) {
97 | final Song song = loadedSong.song();
98 | this.resamplingTab.apply(song);
99 | this.instrumentsTab.apply(song);
100 | if (this.customInstrumentsTab != null) {
101 | this.customInstrumentsTab.apply(song);
102 | }
103 | this.notesTab.apply(song);
104 | this.metadataTab.apply(song);
105 | }
106 | JOptionPane.showMessageDialog(this, "Saved all changes", "Saved", JOptionPane.INFORMATION_MESSAGE);
107 | for (ListFrame.LoadedSong song : this.songs) this.songRefreshConsumer.accept(song);
108 | });
109 | south.add(apply);
110 | JButton preview = new JButton("Preview");
111 | preview.addActionListener(e -> {
112 | final Song song = this.songs.get(0).song().copy();
113 | this.resamplingTab.apply(song);
114 | this.instrumentsTab.apply(song);
115 | if (this.customInstrumentsTab != null) {
116 | this.customInstrumentsTab.apply(song);
117 | }
118 | this.notesTab.apply(song);
119 | SongPlayerFrame.open(song);
120 | });
121 | if (this.songs.size() != 1) {
122 | preview.setEnabled(false);
123 | preview.setToolTipText("Preview is only available for one song at a time");
124 | }
125 | south.add(preview);
126 | }
127 | }
128 |
129 | }
130 |
--------------------------------------------------------------------------------
/src/main/java/net/raphimc/noteblocktool/audio/system/impl/AudioMixerAudioSystem.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of NoteBlockTool - https://github.com/RaphiMC/NoteBlockTool
3 | * Copyright (C) 2022-2025 RK_01/RaphiMC and contributors
4 | *
5 | * This program is free software; you can redistribute it and/or
6 | * modify it under the terms of the GNU Lesser General Public
7 | * License as published by the Free Software Foundation; either
8 | * version 3 of the License, or (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package net.raphimc.noteblocktool.audio.system.impl;
19 |
20 | import net.raphimc.audiomixer.NormalizedAudioMixer;
21 | import net.raphimc.audiomixer.SourceDataLineAudioMixer;
22 | import net.raphimc.audiomixer.io.AudioIO;
23 | import net.raphimc.audiomixer.pcmsource.impl.MonoStaticPcmSource;
24 | import net.raphimc.audiomixer.sound.impl.mix.MixSound;
25 | import net.raphimc.audiomixer.sound.impl.mix.ThreadedChannelMixSound;
26 | import net.raphimc.audiomixer.sound.impl.pcm.OptimizedMonoSound;
27 | import net.raphimc.audiomixer.util.PcmFloatAudioFormat;
28 | import net.raphimc.noteblocktool.audio.system.AudioSystem;
29 | import net.raphimc.noteblocktool.util.SoundFileUtil;
30 |
31 | import javax.sound.sampled.AudioFormat;
32 | import java.io.ByteArrayInputStream;
33 | import java.util.HashMap;
34 | import java.util.Map;
35 |
36 | public class AudioMixerAudioSystem extends AudioSystem {
37 |
38 | private static final AudioFormat PLAYBACK_AUDIO_FORMAT = new AudioFormat(48000, Short.SIZE, 2, true, false);
39 |
40 | private final Map sounds = new HashMap<>();
41 | private final NormalizedAudioMixer audioMixer;
42 | private final MixSound masterMixSound;
43 |
44 | public AudioMixerAudioSystem(final Map soundData, final int maxSounds, final boolean threaded) {
45 | super(soundData, maxSounds);
46 | try {
47 | for (Map.Entry entry : soundData.entrySet()) {
48 | this.sounds.put(entry.getKey(), AudioIO.readSamples(SoundFileUtil.readAudioFile(new ByteArrayInputStream(entry.getValue())), new PcmFloatAudioFormat(PLAYBACK_AUDIO_FORMAT.getSampleRate(), 1)));
49 | }
50 | } catch (Throwable e) {
51 | throw new RuntimeException("Failed to load sound samples", e);
52 | }
53 | try {
54 | this.audioMixer = new SourceDataLineAudioMixer(javax.sound.sampled.AudioSystem.getSourceDataLine(PLAYBACK_AUDIO_FORMAT));
55 | if (threaded) {
56 | this.masterMixSound = new ThreadedChannelMixSound(Math.max(1, Runtime.getRuntime().availableProcessors() - 2));
57 | this.audioMixer.playSound(this.masterMixSound);
58 | } else {
59 | this.masterMixSound = this.audioMixer.getMasterMixSound();
60 | }
61 | this.masterMixSound.setMaxSounds(maxSounds);
62 | } catch (Throwable e) {
63 | throw new RuntimeException("Failed to initialize AudioMixer", e);
64 | }
65 | }
66 |
67 | public AudioMixerAudioSystem(final Map soundData, final int maxSounds, final boolean normalized, final boolean threaded, final PcmFloatAudioFormat loopbackAudioFormat) {
68 | super(soundData, maxSounds, loopbackAudioFormat);
69 | try {
70 | for (Map.Entry entry : soundData.entrySet()) {
71 | this.sounds.put(entry.getKey(), AudioIO.readSamples(SoundFileUtil.readAudioFile(new ByteArrayInputStream(entry.getValue())), new PcmFloatAudioFormat(loopbackAudioFormat.getSampleRate(), 1)));
72 | }
73 | } catch (Throwable e) {
74 | throw new RuntimeException("Failed to load sound samples", e);
75 | }
76 | this.audioMixer = new NormalizedAudioMixer(loopbackAudioFormat);
77 | if (!normalized) {
78 | this.audioMixer.getSoundModifiers().remove(this.audioMixer.getNormalizationModifier());
79 | }
80 | if (threaded) {
81 | this.masterMixSound = new ThreadedChannelMixSound(Math.max(1, Runtime.getRuntime().availableProcessors() - 2));
82 | this.audioMixer.playSound(this.masterMixSound);
83 | } else {
84 | this.masterMixSound = this.audioMixer.getMasterMixSound();
85 | }
86 | this.masterMixSound.setMaxSounds(maxSounds);
87 | }
88 |
89 | @Override
90 | public void playSound(final String sound, final float pitch, final float volume, final float panning) {
91 | if (!this.sounds.containsKey(sound)) return;
92 |
93 | this.masterMixSound.playSound(new OptimizedMonoSound(new MonoStaticPcmSource(this.sounds.get(sound)), pitch, volume, panning));
94 | }
95 |
96 | @Override
97 | public void stopAllSounds() {
98 | this.masterMixSound.stopAllSounds();
99 | this.audioMixer.getNormalizationModifier().reset();
100 | }
101 |
102 | @Override
103 | public float[] render(final int frameCount) {
104 | return this.audioMixer.render(frameCount);
105 | }
106 |
107 | @Override
108 | public void close() {
109 | if (this.masterMixSound instanceof ThreadedChannelMixSound threadedMixSound) {
110 | threadedMixSound.close();
111 | }
112 | if (this.audioMixer instanceof SourceDataLineAudioMixer sourceDataLineAudioMixer) {
113 | sourceDataLineAudioMixer.close();
114 | }
115 | }
116 |
117 | @Override
118 | public void setMasterVolume(final float volume) {
119 | this.audioMixer.setMasterVolume(volume);
120 | }
121 |
122 | @Override
123 | public Integer getPlayingSounds() {
124 | return this.masterMixSound.getMixedSounds();
125 | }
126 |
127 | @Override
128 | public Float getCpuLoad() {
129 | if (this.audioMixer instanceof SourceDataLineAudioMixer sourceDataLineAudioMixer) {
130 | return sourceDataLineAudioMixer.getSourceDataLineWriter().getCpuLoad();
131 | } else {
132 | return this.masterMixSound.getCpuLoad();
133 | }
134 | }
135 |
136 | }
137 |
--------------------------------------------------------------------------------
/src/main/java/net/raphimc/noteblocktool/frames/edittabs/MetadataTab.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of NoteBlockTool - https://github.com/RaphiMC/NoteBlockTool
3 | * Copyright (C) 2022-2025 RK_01/RaphiMC and contributors
4 | *
5 | * This program is free software; you can redistribute it and/or
6 | * modify it under the terms of the GNU Lesser General Public
7 | * License as published by the Free Software Foundation; either
8 | * version 3 of the License, or (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package net.raphimc.noteblocktool.frames.edittabs;
19 |
20 | import net.lenni0451.commons.swing.GBC;
21 | import net.lenni0451.commons.swing.components.InvisiblePanel;
22 | import net.raphimc.noteblocklib.format.mcsp2.model.McSp2Song;
23 | import net.raphimc.noteblocklib.format.nbs.model.NbsSong;
24 | import net.raphimc.noteblocklib.model.song.Song;
25 | import net.raphimc.noteblocktool.frames.ListFrame;
26 |
27 | import javax.swing.*;
28 | import java.awt.*;
29 | import java.util.ArrayList;
30 | import java.util.List;
31 | import java.util.function.Consumer;
32 | import java.util.function.Supplier;
33 |
34 | public class MetadataTab extends EditTab {
35 |
36 | private final List saves = new ArrayList<>();
37 | private int gridy;
38 |
39 | public MetadataTab(final List songs) {
40 | super("Metadata", songs);
41 | }
42 |
43 | @Override
44 | protected void initComponents(JPanel center) {
45 | center.setLayout(new GridBagLayout());
46 |
47 | Song song = this.songs.get(0).song();
48 | if (song instanceof NbsSong nbsSong) {
49 | this.addString(center, "Title", () -> nbsSong.getTitleOr(""), nbsSong::setTitle);
50 | this.addString(center, "Author", () -> nbsSong.getAuthorOr(""), nbsSong::setAuthor);
51 | this.addString(center, "Original author", () -> nbsSong.getOriginalAuthorOr(""), nbsSong::setOriginalAuthor);
52 | this.addString(center, "Description", () -> nbsSong.getDescriptionOr(""), nbsSong::setDescription);
53 | this.addBoolean(center, "Auto Save", nbsSong::isAutoSave, nbsSong::setAutoSave);
54 | this.addNumber(center, "AutoSave Interval", nbsSong::getAutoSaveInterval, num -> nbsSong.setAutoSaveInterval(num.intValue()));
55 | this.addNumber(center, "Time Signature", nbsSong::getTimeSignature, num -> nbsSong.setTimeSignature(num.intValue()));
56 | this.addNumber(center, "Minutes Spent", nbsSong::getMinutesSpent, num -> nbsSong.setMinutesSpent(num.intValue()));
57 | this.addNumber(center, "Left Clicks", nbsSong::getLeftClicks, num -> nbsSong.setLeftClicks(num.intValue()));
58 | this.addNumber(center, "Right Clicks", nbsSong::getRightClicks, num -> nbsSong.setRightClicks(num.intValue()));
59 | this.addNumber(center, "Note Blocks Added", nbsSong::getNoteBlocksAdded, num -> nbsSong.setNoteBlocksAdded(num.intValue()));
60 | this.addNumber(center, "Note Blocks Removed", nbsSong::getNoteBlocksRemoved, num -> nbsSong.setNoteBlocksRemoved(num.intValue()));
61 | this.addString(center, "Source File Name", () -> nbsSong.getSourceFileNameOr(""), nbsSong::setSourceFileName);
62 | this.addBoolean(center, "Loop", nbsSong::isLoop, nbsSong::setLoop);
63 | this.addNumber(center, "Max Loop Count", nbsSong::getMaxLoopCount, num -> nbsSong.setMaxLoopCount(num.intValue()));
64 | this.addNumber(center, "Loop Start Tick", nbsSong::getLoopStartTick, num -> nbsSong.setLoopStartTick(num.shortValue()));
65 | } else if (song instanceof McSp2Song mcSp2Song) {
66 | this.addString(center, "Title", () -> mcSp2Song.getTitleOr(""), mcSp2Song::setTitle);
67 | this.addString(center, "Author", () -> mcSp2Song.getAuthorOr(""), mcSp2Song::setAuthor);
68 | this.addString(center, "Original author", () -> mcSp2Song.getOriginalAuthorOr(""), mcSp2Song::setOriginalAuthor);
69 | this.addNumber(center, "AutoSave Interval", mcSp2Song::getAutoSaveInterval, num -> mcSp2Song.setAutoSaveInterval(num.intValue()));
70 | this.addNumber(center, "Minutes Spent", mcSp2Song::getMinutesSpent, num -> mcSp2Song.setMinutesSpent(num.intValue()));
71 | this.addNumber(center, "Left Clicks", mcSp2Song::getLeftClicks, num -> mcSp2Song.setLeftClicks(num.intValue()));
72 | this.addNumber(center, "Right Clicks", mcSp2Song::getRightClicks, num -> mcSp2Song.setRightClicks(num.intValue()));
73 | this.addNumber(center, "Note Blocks Added", mcSp2Song::getNoteBlocksAdded, num -> mcSp2Song.setNoteBlocksAdded(num.intValue()));
74 | this.addNumber(center, "Note Blocks Removed", mcSp2Song::getNoteBlocksRemoved, num -> mcSp2Song.setNoteBlocksRemoved(num.intValue()));
75 | } else {
76 | GBC.create(center).grid(0, this.gridy).insets(5, 5, 0, 5).anchor(GBC.CENTER).add(new JLabel("No metadata available"));
77 | }
78 | GBC.create(center).grid(0, this.gridy++).insets(0).add(new InvisiblePanel(1, 5));
79 | GBC.fillVerticalSpace(center);
80 | }
81 |
82 | private void addBoolean(final JPanel parent, final String name, final Supplier getter, final Consumer setter) {
83 | GBC.create(parent).grid(0, this.gridy).insets(5, 5, 0, 5).anchor(GBC.LINE_START).add(new JLabel(name + ":"));
84 | GBC.create(parent).grid(1, this.gridy++).insets(5, 5, 0, 5).anchor(GBC.LINE_START).add(new JCheckBox("", getter.get()), checkBox -> {
85 | this.saves.add(() -> setter.accept(checkBox.isSelected()));
86 | });
87 | }
88 |
89 | private void addNumber(final JPanel parent, final String name, final Supplier getter, final Consumer setter) {
90 | GBC.create(parent).grid(0, this.gridy).insets(5, 5, 0, 5).anchor(GBC.LINE_START).add(new JLabel(name + ":"));
91 | GBC.create(parent).grid(1, this.gridy++).insets(5, 5, 0, 5).weightx(1).fill(GBC.HORIZONTAL).add(new JSpinner(new SpinnerNumberModel(getter.get(), null, null, 1)), spinner -> {
92 | this.saves.add(() -> setter.accept((Number) spinner.getValue()));
93 | });
94 | }
95 |
96 | private void addString(final JPanel parent, final String name, final Supplier getter, final Consumer setter) {
97 | GBC.create(parent).grid(0, this.gridy).insets(5, 5, 0, 5).anchor(GBC.LINE_START).add(new JLabel(name + ":"));
98 | GBC.create(parent).grid(1, this.gridy++).insets(5, 5, 0, 5).weightx(1).fill(GBC.HORIZONTAL).add(new JTextField(getter.get()), textField -> {
99 | this.saves.add(() -> setter.accept(textField.getText()));
100 | });
101 | }
102 |
103 | @Override
104 | public void apply(final Song song) {
105 | this.saves.forEach(Runnable::run);
106 | }
107 |
108 | }
109 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU LESSER GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 |
9 | This version of the GNU Lesser General Public License incorporates
10 | the terms and conditions of version 3 of the GNU General Public
11 | License, supplemented by the additional permissions listed below.
12 |
13 | 0. Additional Definitions.
14 |
15 | As used herein, "this License" refers to version 3 of the GNU Lesser
16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU
17 | General Public License.
18 |
19 | "The Library" refers to a covered work governed by this License,
20 | other than an Application or a Combined Work as defined below.
21 |
22 | An "Application" is any work that makes use of an interface provided
23 | by the Library, but which is not otherwise based on the Library.
24 | Defining a subclass of a class defined by the Library is deemed a mode
25 | of using an interface provided by the Library.
26 |
27 | A "Combined Work" is a work produced by combining or linking an
28 | Application with the Library. The particular version of the Library
29 | with which the Combined Work was made is also called the "Linked
30 | Version".
31 |
32 | The "Minimal Corresponding Source" for a Combined Work means the
33 | Corresponding Source for the Combined Work, excluding any source code
34 | for portions of the Combined Work that, considered in isolation, are
35 | based on the Application, and not on the Linked Version.
36 |
37 | The "Corresponding Application Code" for a Combined Work means the
38 | object code and/or source code for the Application, including any data
39 | and utility programs needed for reproducing the Combined Work from the
40 | Application, but excluding the System Libraries of the Combined Work.
41 |
42 | 1. Exception to Section 3 of the GNU GPL.
43 |
44 | You may convey a covered work under sections 3 and 4 of this License
45 | without being bound by section 3 of the GNU GPL.
46 |
47 | 2. Conveying Modified Versions.
48 |
49 | If you modify a copy of the Library, and, in your modifications, a
50 | facility refers to a function or data to be supplied by an Application
51 | that uses the facility (other than as an argument passed when the
52 | facility is invoked), then you may convey a copy of the modified
53 | version:
54 |
55 | a) under this License, provided that you make a good faith effort to
56 | ensure that, in the event an Application does not supply the
57 | function or data, the facility still operates, and performs
58 | whatever part of its purpose remains meaningful, or
59 |
60 | b) under the GNU GPL, with none of the additional permissions of
61 | this License applicable to that copy.
62 |
63 | 3. Object Code Incorporating Material from Library Header Files.
64 |
65 | The object code form of an Application may incorporate material from
66 | a header file that is part of the Library. You may convey such object
67 | code under terms of your choice, provided that, if the incorporated
68 | material is not limited to numerical parameters, data structure
69 | layouts and accessors, or small macros, inline functions and templates
70 | (ten or fewer lines in length), you do both of the following:
71 |
72 | a) Give prominent notice with each copy of the object code that the
73 | Library is used in it and that the Library and its use are
74 | covered by this License.
75 |
76 | b) Accompany the object code with a copy of the GNU GPL and this license
77 | document.
78 |
79 | 4. Combined Works.
80 |
81 | You may convey a Combined Work under terms of your choice that,
82 | taken together, effectively do not restrict modification of the
83 | portions of the Library contained in the Combined Work and reverse
84 | engineering for debugging such modifications, if you also do each of
85 | the following:
86 |
87 | a) Give prominent notice with each copy of the Combined Work that
88 | the Library is used in it and that the Library and its use are
89 | covered by this License.
90 |
91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license
92 | document.
93 |
94 | c) For a Combined Work that displays copyright notices during
95 | execution, include the copyright notice for the Library among
96 | these notices, as well as a reference directing the user to the
97 | copies of the GNU GPL and this license document.
98 |
99 | d) Do one of the following:
100 |
101 | 0) Convey the Minimal Corresponding Source under the terms of this
102 | License, and the Corresponding Application Code in a form
103 | suitable for, and under terms that permit, the user to
104 | recombine or relink the Application with a modified version of
105 | the Linked Version to produce a modified Combined Work, in the
106 | manner specified by section 6 of the GNU GPL for conveying
107 | Corresponding Source.
108 |
109 | 1) Use a suitable shared library mechanism for linking with the
110 | Library. A suitable mechanism is one that (a) uses at run time
111 | a copy of the Library already present on the user's computer
112 | system, and (b) will operate properly with a modified version
113 | of the Library that is interface-compatible with the Linked
114 | Version.
115 |
116 | e) Provide Installation Information, but only if you would otherwise
117 | be required to provide such information under section 6 of the
118 | GNU GPL, and only to the extent that such information is
119 | necessary to install and execute a modified version of the
120 | Combined Work produced by recombining or relinking the
121 | Application with a modified version of the Linked Version. (If
122 | you use option 4d0, the Installation Information must accompany
123 | the Minimal Corresponding Source and Corresponding Application
124 | Code. If you use option 4d1, you must provide the Installation
125 | Information in the manner specified by section 6 of the GNU GPL
126 | for conveying Corresponding Source.)
127 |
128 | 5. Combined Libraries.
129 |
130 | You may place library facilities that are a work based on the
131 | Library side by side in a single library together with other library
132 | facilities that are not Applications and are not covered by this
133 | License, and convey such a combined library under terms of your
134 | choice, if you do both of the following:
135 |
136 | a) Accompany the combined library with a copy of the same work based
137 | on the Library, uncombined with any other library facilities,
138 | conveyed under the terms of this License.
139 |
140 | b) Give prominent notice with the combined library that part of it
141 | is a work based on the Library, and explaining where to find the
142 | accompanying uncombined form of the same work.
143 |
144 | 6. Revised Versions of the GNU Lesser General Public License.
145 |
146 | The Free Software Foundation may publish revised and/or new versions
147 | of the GNU Lesser General Public License from time to time. Such new
148 | versions will be similar in spirit to the present version, but may
149 | differ in detail to address new problems or concerns.
150 |
151 | Each version is given a distinguishing version number. If the
152 | Library as you received it specifies that a certain numbered version
153 | of the GNU Lesser General Public License "or any later version"
154 | applies to it, you have the option of following the terms and
155 | conditions either of that published version or of any later version
156 | published by the Free Software Foundation. If the Library as you
157 | received it does not specify a version number of the GNU Lesser
158 | General Public License, you may choose any version of the GNU Lesser
159 | General Public License ever published by the Free Software Foundation.
160 |
161 | If the Library as you received it specifies that a proxy can decide
162 | whether future versions of the GNU Lesser General Public License shall
163 | apply, that proxy's public statement of acceptance of any version is
164 | permanent authorization for you to choose that version for the
165 | Library.
166 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #
4 | # Copyright © 2015 the original authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 | # SPDX-License-Identifier: Apache-2.0
19 | #
20 |
21 | ##############################################################################
22 | #
23 | # Gradle start up script for POSIX generated by Gradle.
24 | #
25 | # Important for running:
26 | #
27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
28 | # noncompliant, but you have some other compliant shell such as ksh or
29 | # bash, then to run this script, type that shell name before the whole
30 | # command line, like:
31 | #
32 | # ksh Gradle
33 | #
34 | # Busybox and similar reduced shells will NOT work, because this script
35 | # requires all of these POSIX shell features:
36 | # * functions;
37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»;
39 | # * compound commands having a testable exit status, especially «case»;
40 | # * various built-in commands including «command», «set», and «ulimit».
41 | #
42 | # Important for patching:
43 | #
44 | # (2) This script targets any POSIX shell, so it avoids extensions provided
45 | # by Bash, Ksh, etc; in particular arrays are avoided.
46 | #
47 | # The "traditional" practice of packing multiple parameters into a
48 | # space-separated string is a well documented source of bugs and security
49 | # problems, so this is (mostly) avoided, by progressively accumulating
50 | # options in "$@", and eventually passing that to Java.
51 | #
52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
54 | # see the in-line comments for details.
55 | #
56 | # There are tweaks for specific operating systems such as AIX, CygWin,
57 | # Darwin, MinGW, and NonStop.
58 | #
59 | # (3) This script is generated from the Groovy template
60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
61 | # within the Gradle project.
62 | #
63 | # You can find Gradle at https://github.com/gradle/gradle/.
64 | #
65 | ##############################################################################
66 |
67 | # Attempt to set APP_HOME
68 |
69 | # Resolve links: $0 may be a link
70 | app_path=$0
71 |
72 | # Need this for daisy-chained symlinks.
73 | while
74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
75 | [ -h "$app_path" ]
76 | do
77 | ls=$( ls -ld "$app_path" )
78 | link=${ls#*' -> '}
79 | case $link in #(
80 | /*) app_path=$link ;; #(
81 | *) app_path=$APP_HOME$link ;;
82 | esac
83 | done
84 |
85 | # This is normally unused
86 | # shellcheck disable=SC2034
87 | APP_BASE_NAME=${0##*/}
88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
90 |
91 | # Use the maximum available, or set MAX_FD != -1 to use that value.
92 | MAX_FD=maximum
93 |
94 | warn () {
95 | echo "$*"
96 | } >&2
97 |
98 | die () {
99 | echo
100 | echo "$*"
101 | echo
102 | exit 1
103 | } >&2
104 |
105 | # OS specific support (must be 'true' or 'false').
106 | cygwin=false
107 | msys=false
108 | darwin=false
109 | nonstop=false
110 | case "$( uname )" in #(
111 | CYGWIN* ) cygwin=true ;; #(
112 | Darwin* ) darwin=true ;; #(
113 | MSYS* | MINGW* ) msys=true ;; #(
114 | NONSTOP* ) nonstop=true ;;
115 | esac
116 |
117 |
118 |
119 | # Determine the Java command to use to start the JVM.
120 | if [ -n "$JAVA_HOME" ] ; then
121 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
122 | # IBM's JDK on AIX uses strange locations for the executables
123 | JAVACMD=$JAVA_HOME/jre/sh/java
124 | else
125 | JAVACMD=$JAVA_HOME/bin/java
126 | fi
127 | if [ ! -x "$JAVACMD" ] ; then
128 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
129 |
130 | Please set the JAVA_HOME variable in your environment to match the
131 | location of your Java installation."
132 | fi
133 | else
134 | JAVACMD=java
135 | if ! command -v java >/dev/null 2>&1
136 | then
137 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
138 |
139 | Please set the JAVA_HOME variable in your environment to match the
140 | location of your Java installation."
141 | fi
142 | fi
143 |
144 | # Increase the maximum file descriptors if we can.
145 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
146 | case $MAX_FD in #(
147 | max*)
148 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
149 | # shellcheck disable=SC2039,SC3045
150 | MAX_FD=$( ulimit -H -n ) ||
151 | warn "Could not query maximum file descriptor limit"
152 | esac
153 | case $MAX_FD in #(
154 | '' | soft) :;; #(
155 | *)
156 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
157 | # shellcheck disable=SC2039,SC3045
158 | ulimit -n "$MAX_FD" ||
159 | warn "Could not set maximum file descriptor limit to $MAX_FD"
160 | esac
161 | fi
162 |
163 | # Collect all arguments for the java command, stacking in reverse order:
164 | # * args from the command line
165 | # * the main class name
166 | # * -classpath
167 | # * -D...appname settings
168 | # * --module-path (only if needed)
169 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
170 |
171 | # For Cygwin or MSYS, switch paths to Windows format before running java
172 | if "$cygwin" || "$msys" ; then
173 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
174 |
175 | JAVACMD=$( cygpath --unix "$JAVACMD" )
176 |
177 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
178 | for arg do
179 | if
180 | case $arg in #(
181 | -*) false ;; # don't mess with options #(
182 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
183 | [ -e "$t" ] ;; #(
184 | *) false ;;
185 | esac
186 | then
187 | arg=$( cygpath --path --ignore --mixed "$arg" )
188 | fi
189 | # Roll the args list around exactly as many times as the number of
190 | # args, so each arg winds up back in the position where it started, but
191 | # possibly modified.
192 | #
193 | # NB: a `for` loop captures its iteration list before it begins, so
194 | # changing the positional parameters here affects neither the number of
195 | # iterations, nor the values presented in `arg`.
196 | shift # remove old arg
197 | set -- "$@" "$arg" # push replacement arg
198 | done
199 | fi
200 |
201 |
202 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
203 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
204 |
205 | # Collect all arguments for the java command:
206 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
207 | # and any embedded shellness will be escaped.
208 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
209 | # treated as '${Hostname}' itself on the command line.
210 |
211 | set -- \
212 | "-Dorg.gradle.appname=$APP_BASE_NAME" \
213 | -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
214 | "$@"
215 |
216 | # Stop when "xargs" is not available.
217 | if ! command -v xargs >/dev/null 2>&1
218 | then
219 | die "xargs is not available"
220 | fi
221 |
222 | # Use "xargs" to parse quoted args.
223 | #
224 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
225 | #
226 | # In Bash we could simply go:
227 | #
228 | # readarray ARGS < <( xargs -n1 <<<"$var" ) &&
229 | # set -- "${ARGS[@]}" "$@"
230 | #
231 | # but POSIX shell has neither arrays nor command substitution, so instead we
232 | # post-process each arg (as a line of input to sed) to backslash-escape any
233 | # character that might be a shell metacharacter, then use eval to reverse
234 | # that process (while maintaining the separation between arguments), and wrap
235 | # the whole thing up as a single "set" statement.
236 | #
237 | # This will of course break if any of these variables contains a newline or
238 | # an unmatched quote.
239 | #
240 |
241 | eval "set -- $(
242 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
243 | xargs -n1 |
244 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
245 | tr '\n' ' '
246 | )" '"$@"'
247 |
248 | exec "$JAVACMD" "$@"
249 |
--------------------------------------------------------------------------------
/src/main/java/net/raphimc/noteblocktool/audio/system/impl/XAudio2AudioSystem.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of NoteBlockTool - https://github.com/RaphiMC/NoteBlockTool
3 | * Copyright (C) 2022-2025 RK_01/RaphiMC and contributors
4 | *
5 | * This program is free software; you can redistribute it and/or
6 | * modify it under the terms of the GNU Lesser General Public
7 | * License as published by the Free Software Foundation; either
8 | * version 3 of the License, or (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package net.raphimc.noteblocktool.audio.system.impl;
19 |
20 | import com.sun.jna.Memory;
21 | import com.sun.jna.ptr.IntByReference;
22 | import com.sun.jna.ptr.PointerByReference;
23 | import net.raphimc.noteblocktool.audio.library.XAudio2Library;
24 | import net.raphimc.noteblocktool.audio.system.AudioSystem;
25 | import net.raphimc.noteblocktool.util.IOUtil;
26 | import net.raphimc.noteblocktool.util.SoundFileUtil;
27 | import net.raphimc.noteblocktool.util.jna.Ole32;
28 |
29 | import javax.sound.sampled.AudioFormat;
30 | import javax.sound.sampled.AudioInputStream;
31 | import java.io.ByteArrayInputStream;
32 | import java.util.ArrayList;
33 | import java.util.HashMap;
34 | import java.util.List;
35 | import java.util.Map;
36 |
37 | public class XAudio2AudioSystem extends AudioSystem {
38 |
39 | static {
40 | if (!XAudio2Library.isLoaded()) {
41 | throw new IllegalStateException("XAudio2 library is not available");
42 | }
43 | }
44 |
45 | private final Map soundBuffers = new HashMap<>();
46 | private final List playingVoices = new ArrayList<>();
47 | private XAudio2Library.XAudio2 xAudio2;
48 | private XAudio2Library.XAudio2MasteringVoice masteringVoice;
49 | private int masteringVoiceChannelMask;
50 | private int masteringVoiceChannels;
51 |
52 | public XAudio2AudioSystem(final Map soundData, final int maxSounds) {
53 | super(soundData, maxSounds);
54 |
55 | this.checkError(Ole32.INSTANCE.CoInitializeEx(null, Ole32.COINIT_MULTITHREADED), "Failed to initialize COM");
56 |
57 | final PointerByReference ppXAudio2 = new PointerByReference();
58 | this.checkError(XAudio2Library.INSTANCE.XAudio2Create(ppXAudio2, 0, XAudio2Library.XAUDIO2_ANY_PROCESSOR), "Failed to create XAudio2 instance");
59 | this.xAudio2 = new XAudio2Library.XAudio2(ppXAudio2.getValue());
60 |
61 | final PointerByReference ppMasteringVoice = new PointerByReference();
62 | this.checkError(this.xAudio2.CreateMasteringVoice(ppMasteringVoice, XAudio2Library.XAUDIO2_DEFAULT_CHANNELS, XAudio2Library.XAUDIO2_DEFAULT_SAMPLERATE, 0, null, null, 0), "Failed to create mastering voice");
63 | this.masteringVoice = new XAudio2Library.XAudio2MasteringVoice(ppMasteringVoice.getValue());
64 | final IntByReference masteringVoiceChannelMask = new IntByReference();
65 | this.checkError(this.masteringVoice.GetChannelMask(masteringVoiceChannelMask), "Failed to get channel mask");
66 | this.masteringVoiceChannelMask = masteringVoiceChannelMask.getValue();
67 | final XAudio2Library.XAUDIO2_VOICE_DETAILS.ByReference masteringVoiceDetails = new XAudio2Library.XAUDIO2_VOICE_DETAILS.ByReference();
68 | this.masteringVoice.GetVoiceDetails(masteringVoiceDetails);
69 | this.masteringVoiceChannels = masteringVoiceDetails.InputChannels;
70 |
71 | for (Map.Entry entry : soundData.entrySet()) {
72 | this.soundBuffers.put(entry.getKey(), this.loadAudioFile(entry.getValue()));
73 | }
74 |
75 | System.out.println("Initialized XAudio2 v2.9");
76 | }
77 |
78 | @Override
79 | public synchronized void preTick() {
80 | final XAudio2Library.XAUDIO2_VOICE_STATE.ByReference voiceState = new XAudio2Library.XAUDIO2_VOICE_STATE.ByReference();
81 | this.playingVoices.removeIf(voice -> {
82 | voice.GetState(voiceState, XAudio2Library.XAUDIO2_VOICE_NOSAMPLESPLAYED);
83 | if (voiceState.BuffersQueued == 0) {
84 | voice.DestroyVoice();
85 | return true;
86 | }
87 | return false;
88 | });
89 | }
90 |
91 | @Override
92 | public synchronized void playSound(final String sound, final float pitch, final float volume, final float panning) {
93 | if (!this.soundBuffers.containsKey(sound)) return;
94 | final SoundBuffer buffer = this.soundBuffers.get(sound);
95 |
96 | if (this.playingVoices.size() >= this.getMaxSounds()) {
97 | this.playingVoices.remove(0).DestroyVoice();
98 | }
99 |
100 | final PointerByReference ppSourceVoice = new PointerByReference();
101 | this.checkError(this.xAudio2.CreateSourceVoice(ppSourceVoice, buffer.waveFormat, 0, 10F, null, null, null), "Failed to create source voice");
102 | final XAudio2Library.XAudio2SourceVoice sourceVoice = new XAudio2Library.XAudio2SourceVoice(ppSourceVoice.getValue());
103 |
104 | this.checkError(sourceVoice.SubmitSourceBuffer(buffer.buffer, null), "Failed to submit source buffer");
105 | this.checkError(sourceVoice.SetFrequencyRatio(pitch, XAudio2Library.XAUDIO2_COMMIT_NOW), "Failed to frequency ratio");
106 | this.checkError(sourceVoice.SetVolume(volume, XAudio2Library.XAUDIO2_COMMIT_NOW), "Failed to set volume");
107 | this.checkError(sourceVoice.SetOutputMatrix(masteringVoice, 1, this.masteringVoiceChannels, this.createPanMatrix(panning), XAudio2Library.XAUDIO2_COMMIT_NOW), "Failed to set output matrix");
108 | this.checkError(sourceVoice.Start(0, XAudio2Library.XAUDIO2_COMMIT_NOW), "Failed to start source voice");
109 | this.playingVoices.add(sourceVoice);
110 | }
111 |
112 | @Override
113 | public synchronized void stopAllSounds() {
114 | for (XAudio2Library.XAudio2SourceVoice voice : this.playingVoices) {
115 | voice.DestroyVoice();
116 | }
117 | this.playingVoices.clear();
118 | }
119 |
120 | @Override
121 | public float[] render(final int frameCount) {
122 | throw new UnsupportedOperationException();
123 | }
124 |
125 | @Override
126 | public synchronized void close() {
127 | if (this.xAudio2 != null) {
128 | this.xAudio2.Release(); // Release before deleting buffers
129 | this.xAudio2 = null;
130 | }
131 | this.masteringVoice = null;
132 | this.masteringVoiceChannelMask = 0;
133 | this.masteringVoiceChannels = 0;
134 | this.soundBuffers.clear();
135 | this.playingVoices.clear();
136 | Ole32.INSTANCE.CoUninitialize();
137 | }
138 |
139 | @Override
140 | public void setMasterVolume(final float volume) {
141 | this.checkError(this.masteringVoice.SetVolume(volume, XAudio2Library.XAUDIO2_COMMIT_NOW), "Failed to set master volume");
142 | }
143 |
144 | @Override
145 | public synchronized Integer getPlayingSounds() {
146 | return this.playingVoices.size();
147 | }
148 |
149 | @Override
150 | public Float getCpuLoad() {
151 | return null;
152 | }
153 |
154 | private SoundBuffer loadAudioFile(final byte[] data) {
155 | try {
156 | final AudioInputStream audioInputStream = SoundFileUtil.readAudioFile(new ByteArrayInputStream(data));
157 | final AudioFormat audioFormat = audioInputStream.getFormat();
158 | final byte[] audioBytes = IOUtil.readFully(audioInputStream);
159 |
160 | final XAudio2Library.WAVEFORMATEX.ByReference waveFormat = new XAudio2Library.WAVEFORMATEX.ByReference();
161 | waveFormat.wFormatTag = XAudio2Library.WAVE_FORMAT_PCM;
162 | waveFormat.nChannels = (short) audioFormat.getChannels();
163 | waveFormat.nSamplesPerSec = (int) audioFormat.getSampleRate();
164 | waveFormat.wBitsPerSample = (short) audioFormat.getSampleSizeInBits();
165 | waveFormat.nBlockAlign = (short) audioFormat.getFrameSize();
166 | waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign;
167 |
168 | final XAudio2Library.XAUDIO2_BUFFER.ByReference buffer = new XAudio2Library.XAUDIO2_BUFFER.ByReference();
169 | buffer.Flags = XAudio2Library.XAUDIO2_END_OF_STREAM;
170 | buffer.AudioBytes = audioBytes.length;
171 | buffer.pAudioData = new Memory(buffer.AudioBytes);
172 | buffer.pAudioData.write(0, audioBytes, 0, audioBytes.length);
173 |
174 | return new SoundBuffer(buffer, waveFormat);
175 | } catch (Throwable e) {
176 | throw new RuntimeException("Failed to load audio file", e);
177 | }
178 | }
179 |
180 | private float[] createPanMatrix(final float pan) {
181 | final float left = 0.5F - pan / 2F;
182 | final float right = 0.5F + pan / 2F;
183 | final float[] outputMatrix = new float[8];
184 |
185 | switch (this.masteringVoiceChannelMask) {
186 | case XAudio2Library.SPEAKER_MONO:
187 | outputMatrix[0] = 1F;
188 | break;
189 | case XAudio2Library.SPEAKER_STEREO:
190 | case XAudio2Library.SPEAKER_2POINT1:
191 | case XAudio2Library.SPEAKER_SURROUND:
192 | outputMatrix[0] = left;
193 | outputMatrix[1] = right;
194 | break;
195 | case XAudio2Library.SPEAKER_QUAD:
196 | outputMatrix[0] = outputMatrix[2] = left;
197 | outputMatrix[1] = outputMatrix[3] = right;
198 | break;
199 | case XAudio2Library.SPEAKER_4POINT1:
200 | outputMatrix[0] = outputMatrix[3] = left;
201 | outputMatrix[1] = outputMatrix[4] = right;
202 | break;
203 | case XAudio2Library.SPEAKER_5POINT1:
204 | case XAudio2Library.SPEAKER_7POINT1:
205 | case XAudio2Library.SPEAKER_5POINT1_SURROUND:
206 | outputMatrix[0] = outputMatrix[4] = left;
207 | outputMatrix[1] = outputMatrix[5] = right;
208 | break;
209 | case XAudio2Library.SPEAKER_7POINT1_SURROUND:
210 | outputMatrix[0] = outputMatrix[4] = outputMatrix[6] = left;
211 | outputMatrix[1] = outputMatrix[5] = outputMatrix[7] = right;
212 | break;
213 | }
214 |
215 | return outputMatrix;
216 | }
217 |
218 | private void checkError(final int result, final String message, final int... allowedErrors) {
219 | if (result < 0) {
220 | for (int ignoreError : allowedErrors) {
221 | if (result == ignoreError) {
222 | return;
223 | }
224 | }
225 |
226 | throw new RuntimeException("XAudio2 error: " + message + " (" + result + ")");
227 | }
228 | }
229 |
230 | private record SoundBuffer(XAudio2Library.XAUDIO2_BUFFER.ByReference buffer, XAudio2Library.WAVEFORMATEX.ByReference waveFormat) {
231 | }
232 |
233 | }
234 |
--------------------------------------------------------------------------------
/src/main/java/net/raphimc/noteblocktool/audio/system/impl/BassAudioSystem.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of NoteBlockTool - https://github.com/RaphiMC/NoteBlockTool
3 | * Copyright (C) 2022-2025 RK_01/RaphiMC and contributors
4 | *
5 | * This program is free software; you can redistribute it and/or
6 | * modify it under the terms of the GNU Lesser General Public
7 | * License as published by the Free Software Foundation; either
8 | * version 3 of the License, or (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package net.raphimc.noteblocktool.audio.system.impl;
19 |
20 | import com.sun.jna.Memory;
21 | import com.sun.jna.ptr.FloatByReference;
22 | import net.raphimc.audiomixer.soundmodifier.impl.NormalizationModifier;
23 | import net.raphimc.audiomixer.util.MathUtil;
24 | import net.raphimc.audiomixer.util.PcmFloatAudioFormat;
25 | import net.raphimc.noteblocktool.audio.library.BassLibrary;
26 | import net.raphimc.noteblocktool.audio.library.BassMixLibrary;
27 | import net.raphimc.noteblocktool.audio.system.AudioSystem;
28 | import net.raphimc.noteblocktool.util.IOUtil;
29 | import net.raphimc.noteblocktool.util.SoundFileUtil;
30 |
31 | import javax.sound.sampled.AudioFormat;
32 | import javax.sound.sampled.AudioInputStream;
33 | import java.io.ByteArrayInputStream;
34 | import java.util.ArrayList;
35 | import java.util.HashMap;
36 | import java.util.List;
37 | import java.util.Map;
38 |
39 | public class BassAudioSystem extends AudioSystem {
40 |
41 | static {
42 | if (!BassLibrary.isLoaded()) {
43 | throw new IllegalStateException("BASS library is not available");
44 | }
45 | /*if (!BassMixLibrary.isLoaded()) { // Currently broken when exported as jar file because JNA renames the dll, but bassmix.dll depends on bass.dll
46 | throw new IllegalStateException("BASS mix library is not available");
47 | }*/
48 | }
49 |
50 | private final Map soundSamples = new HashMap<>();
51 | private final List playingChannels = new ArrayList<>();
52 | private int loopbackChannel;
53 | private Memory loopbackMemory;
54 | private NormalizationModifier loopbackNormalizer;
55 |
56 | @SuppressWarnings("FieldCanBeLocal")
57 | private final BassLibrary.SYNCPROC channelFreeSync = (handle, channel, data, user) -> {
58 | synchronized (this) {
59 | this.playingChannels.remove((Integer) channel);
60 | }
61 | };
62 |
63 | public BassAudioSystem(final Map soundData, final int maxSounds) {
64 | super(soundData, maxSounds);
65 |
66 | final int version = BassLibrary.INSTANCE.BASS_GetVersion();
67 | if (((version >> 16) & 0xFFFF) != BassLibrary.BASSVERSION) {
68 | throw new RuntimeException("BASS version is not correct");
69 | }
70 | if (!BassLibrary.INSTANCE.BASS_Init(-1, 48000, 0, 0, null)) {
71 | this.checkError("Failed to open device");
72 | }
73 | final BassLibrary.BASS_DEVICEINFO.ByReference deviceInfo = new BassLibrary.BASS_DEVICEINFO.ByReference();
74 | if (!BassLibrary.INSTANCE.BASS_GetDeviceInfo(BassLibrary.INSTANCE.BASS_GetDevice(), deviceInfo)) {
75 | this.checkError("Failed to get device info");
76 | }
77 | if (!BassLibrary.INSTANCE.BASS_SetConfig(BassLibrary.BASS_CONFIG_SRC, 0)) { // linear interpolation
78 | this.checkError("Failed to set default sample rate conversion quality");
79 | }
80 |
81 | for (Map.Entry entry : soundData.entrySet()) {
82 | this.soundSamples.put(entry.getKey(), this.loadAudioFile(entry.getValue()));
83 | }
84 |
85 | final String versionString = "v" + ((version >> 24) & 0xFF) + "." + ((version >> 16) & 0xFF) + "." + ((version >> 8) & 0xFF) + "." + (version & 0xFF);
86 | System.out.println("Initialized BASS " + versionString + " on " + deviceInfo.name);
87 | }
88 |
89 | public BassAudioSystem(final Map soundData, final int maxSounds, final PcmFloatAudioFormat loopbackAudioFormat) {
90 | super(soundData, maxSounds, loopbackAudioFormat);
91 |
92 | final int version = BassLibrary.INSTANCE.BASS_GetVersion();
93 | if (((version >> 16) & 0xFFFF) != BassLibrary.BASSVERSION) {
94 | throw new RuntimeException("BASS version is not correct");
95 | }
96 | if (!BassLibrary.INSTANCE.BASS_Init(0, 48000, 0, 0, null)) {
97 | this.checkError("Failed to open device");
98 | }
99 | final BassLibrary.BASS_DEVICEINFO.ByReference deviceInfo = new BassLibrary.BASS_DEVICEINFO.ByReference();
100 | if (!BassLibrary.INSTANCE.BASS_GetDeviceInfo(BassLibrary.INSTANCE.BASS_GetDevice(), deviceInfo)) {
101 | this.checkError("Failed to get device info");
102 | }
103 | if (!BassLibrary.INSTANCE.BASS_SetConfig(BassLibrary.BASS_CONFIG_SRC, 0)) { // linear interpolation
104 | this.checkError("Failed to set default sample rate conversion quality");
105 | }
106 |
107 | this.loopbackChannel = BassMixLibrary.INSTANCE.BASS_Mixer_StreamCreate((int) loopbackAudioFormat.getSampleRate(), loopbackAudioFormat.getChannels(), BassLibrary.BASS_STREAM_DECODE | BassLibrary.BASS_SAMPLE_FLOAT);
108 | this.checkError("Failed to create mixer stream");
109 | if (!BassLibrary.INSTANCE.BASS_ChannelSetAttribute(this.loopbackChannel, BassMixLibrary.BASS_ATTRIB_MIXER_THREADS, Math.min(Runtime.getRuntime().availableProcessors(), 16))) {
110 | this.checkError("Failed to set mixer threads");
111 | }
112 | this.loopbackMemory = new Memory(MathUtil.millisToByteCount(loopbackAudioFormat, 5000));
113 | this.loopbackNormalizer = new NormalizationModifier();
114 |
115 | for (Map.Entry entry : soundData.entrySet()) {
116 | this.soundSamples.put(entry.getKey(), this.loadAudioFile(entry.getValue()));
117 | }
118 |
119 | final String versionString = "v" + ((version >> 24) & 0xFF) + "." + ((version >> 16) & 0xFF) + "." + ((version >> 8) & 0xFF) + "." + (version & 0xFF);
120 | System.out.println("Initialized BASS " + versionString + " on " + deviceInfo.name);
121 | }
122 |
123 | @Override
124 | public synchronized void playSound(final String sound, final float pitch, final float volume, final float panning) {
125 | if (!this.soundSamples.containsKey(sound)) return;
126 |
127 | if (this.playingChannels.size() >= this.getMaxSounds()) {
128 | if (!BassLibrary.INSTANCE.BASS_ChannelFree(this.playingChannels.remove(0))) {
129 | this.checkError("Failed to free audio channel", BassLibrary.BASS_ERROR_HANDLE);
130 | }
131 | }
132 |
133 | final int channel = BassLibrary.INSTANCE.BASS_SampleGetChannel(this.soundSamples.get(sound), BassLibrary.BASS_SAMCHAN_STREAM | (this.getLoopbackAudioFormat() == null ? BassLibrary.BASS_STREAM_AUTOFREE : BassLibrary.BASS_STREAM_DECODE));
134 | if (channel == 0) {
135 | this.checkError("Failed to get audio channel");
136 | }
137 | if (!BassLibrary.INSTANCE.BASS_ChannelSetAttribute(channel, BassLibrary.BASS_ATTRIB_VOL, volume)) {
138 | this.checkError("Failed to set audio channel volume");
139 | }
140 | if (!BassLibrary.INSTANCE.BASS_ChannelSetAttribute(channel, BassLibrary.BASS_ATTRIB_PAN, panning)) {
141 | this.checkError("Failed to set audio channel panning");
142 | }
143 | final FloatByReference freq = new FloatByReference();
144 | if (!BassLibrary.INSTANCE.BASS_ChannelGetAttribute(channel, BassLibrary.BASS_ATTRIB_FREQ, freq)) {
145 | this.checkError("Failed to get audio channel frequency");
146 | }
147 | if (!BassLibrary.INSTANCE.BASS_ChannelSetAttribute(channel, BassLibrary.BASS_ATTRIB_FREQ, freq.getValue() * pitch)) {
148 | this.checkError("Failed to set audio channel frequency");
149 | }
150 | final int sync = BassLibrary.INSTANCE.BASS_ChannelSetSync(channel, BassLibrary.BASS_SYNC_FREE, 0, this.channelFreeSync, null);
151 | if (sync == 0) {
152 | this.checkError("Failed to set audio channel end sync");
153 | }
154 | if (this.getLoopbackAudioFormat() == null) {
155 | if (!BassLibrary.INSTANCE.BASS_ChannelStart(channel)) {
156 | this.checkError("Failed to play audio channel");
157 | }
158 | } else {
159 | if (!BassMixLibrary.INSTANCE.BASS_Mixer_StreamAddChannel(this.loopbackChannel, channel, BassLibrary.BASS_STREAM_AUTOFREE)) {
160 | this.checkError("Failed to add audio channel to mixer");
161 | }
162 | }
163 | this.playingChannels.add(channel);
164 | }
165 |
166 | @Override
167 | public synchronized void stopAllSounds() {
168 | if (!BassLibrary.INSTANCE.BASS_Stop()) {
169 | this.checkError("Failed to stop audio system");
170 | }
171 | if (!BassLibrary.INSTANCE.BASS_Start()) {
172 | this.checkError("Failed to start audio system");
173 | }
174 | this.playingChannels.clear();
175 | }
176 |
177 | @Override
178 | public synchronized float[] render(final int frameCount) {
179 | final int samplesLength = frameCount * this.getLoopbackAudioFormat().getChannels();
180 | if ((long) samplesLength * Float.BYTES > this.loopbackMemory.size()) {
181 | throw new IllegalStateException("Loopback memory is too small");
182 | }
183 | if (BassLibrary.INSTANCE.BASS_ChannelGetData(this.loopbackChannel, this.loopbackMemory, samplesLength * Float.BYTES | BassLibrary.BASS_DATA_FLOAT) < 0) {
184 | this.checkError("Failed to get audio data");
185 | }
186 | final float[] samples = this.loopbackMemory.getFloatArray(0, samplesLength);
187 | this.loopbackNormalizer.modify(this.getLoopbackAudioFormat(), samples);
188 | return samples;
189 | }
190 |
191 | @Override
192 | public synchronized void close() {
193 | this.soundSamples.clear();
194 | this.playingChannels.clear();
195 | if (this.loopbackMemory != null) {
196 | this.loopbackMemory.close();
197 | this.loopbackMemory = null;
198 | }
199 | BassLibrary.INSTANCE.BASS_Free();
200 | }
201 |
202 | @Override
203 | public void setMasterVolume(final float volume) {
204 | if (!BassLibrary.INSTANCE.BASS_SetConfig(BassLibrary.BASS_CONFIG_GVOL_STREAM, (int) (volume * 10000))) {
205 | this.checkError("Failed to set master volume");
206 | }
207 | }
208 |
209 | @Override
210 | public synchronized Integer getPlayingSounds() {
211 | return this.playingChannels.size();
212 | }
213 |
214 | @Override
215 | public Float getCpuLoad() {
216 | return BassLibrary.INSTANCE.BASS_GetCPU();
217 | }
218 |
219 | private int loadAudioFile(final byte[] data) {
220 | try {
221 | AudioInputStream audioInputStream = SoundFileUtil.readAudioFile(new ByteArrayInputStream(data));
222 | final AudioFormat audioFormat = audioInputStream.getFormat();
223 | final AudioFormat targetFormat = new AudioFormat(audioFormat.getSampleRate(), Short.SIZE, audioFormat.getChannels(), true, false);
224 | if (!audioFormat.matches(targetFormat)) audioInputStream = javax.sound.sampled.AudioSystem.getAudioInputStream(targetFormat, audioInputStream);
225 | final byte[] audioBytes = IOUtil.readFully(audioInputStream);
226 |
227 | final int sample = BassLibrary.INSTANCE.BASS_SampleCreate(audioBytes.length, (int) audioFormat.getSampleRate(), audioFormat.getChannels(), 1, 0);
228 | if (sample == 0) {
229 | this.checkError("Failed to create sample");
230 | }
231 | if (!BassLibrary.INSTANCE.BASS_SampleSetData(sample, audioBytes)) {
232 | this.checkError("Failed to set sample data");
233 | }
234 |
235 | return sample;
236 | } catch (Throwable e) {
237 | throw new RuntimeException("Failed to load audio file", e);
238 | }
239 | }
240 |
241 | private void checkError(final String message, final int... allowedErrors) {
242 | final int error = BassLibrary.INSTANCE.BASS_ErrorGetCode();
243 | if (error != BassLibrary.BASS_OK) {
244 | for (int ignoreError : allowedErrors) {
245 | if (error == ignoreError) {
246 | return;
247 | }
248 | }
249 | throw new RuntimeException("BASS error: " + message + " (" + error + ")");
250 | }
251 | }
252 |
253 | }
254 |
--------------------------------------------------------------------------------
/src/main/java/net/raphimc/noteblocktool/audio/library/XAudio2Library.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of NoteBlockTool - https://github.com/RaphiMC/NoteBlockTool
3 | * Copyright (C) 2022-2025 RK_01/RaphiMC and contributors
4 | *
5 | * This program is free software; you can redistribute it and/or
6 | * modify it under the terms of the GNU Lesser General Public
7 | * License as published by the Free Software Foundation; either
8 | * version 3 of the License, or (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package net.raphimc.noteblocktool.audio.library;
19 |
20 | import com.sun.jna.Library;
21 | import com.sun.jna.Native;
22 | import com.sun.jna.Pointer;
23 | import com.sun.jna.Structure;
24 | import com.sun.jna.ptr.IntByReference;
25 | import com.sun.jna.ptr.PointerByReference;
26 | import net.raphimc.noteblocktool.util.jna.COMObject;
27 | import net.raphimc.noteblocktool.util.jna.VTableHandler;
28 |
29 | import java.util.HashMap;
30 | import java.util.Map;
31 |
32 | public interface XAudio2Library extends Library {
33 |
34 | XAudio2Library INSTANCE = loadNative();
35 |
36 | int XAUDIO2_ANY_PROCESSOR = 0xFFFFFFFF;
37 | int XAUDIO2_DEFAULT_CHANNELS = 0;
38 | int XAUDIO2_DEFAULT_SAMPLERATE = 0;
39 | int WAVE_FORMAT_PCM = 1;
40 | int XAUDIO2_END_OF_STREAM = 0x40;
41 | int XAUDIO2_COMMIT_NOW = 0;
42 | int XAUDIO2_VOICE_NOSAMPLESPLAYED = 0x100;
43 |
44 | int SPEAKER_FRONT_LEFT = 0x00000001;
45 | int SPEAKER_FRONT_RIGHT = 0x00000002;
46 | int SPEAKER_FRONT_CENTER = 0x00000004;
47 | int SPEAKER_LOW_FREQUENCY = 0x00000008;
48 | int SPEAKER_BACK_LEFT = 0x00000010;
49 | int SPEAKER_BACK_RIGHT = 0x00000020;
50 | int SPEAKER_FRONT_LEFT_OF_CENTER = 0x00000040;
51 | int SPEAKER_FRONT_RIGHT_OF_CENTER = 0x00000080;
52 | int SPEAKER_BACK_CENTER = 0x00000100;
53 | int SPEAKER_SIDE_LEFT = 0x00000200;
54 | int SPEAKER_SIDE_RIGHT = 0x00000400;
55 | int SPEAKER_TOP_CENTER = 0x00000800;
56 | int SPEAKER_TOP_FRONT_LEFT = 0x00001000;
57 | int SPEAKER_TOP_FRONT_CENTER = 0x00002000;
58 | int SPEAKER_TOP_FRONT_RIGHT = 0x00004000;
59 | int SPEAKER_TOP_BACK_LEFT = 0x00008000;
60 | int SPEAKER_TOP_BACK_CENTER = 0x00010000;
61 | int SPEAKER_TOP_BACK_RIGHT = 0x00020000;
62 | int SPEAKER_MONO = SPEAKER_FRONT_CENTER;
63 | int SPEAKER_STEREO = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
64 | int SPEAKER_2POINT1 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_LOW_FREQUENCY;
65 | int SPEAKER_SURROUND = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_BACK_CENTER;
66 | int SPEAKER_QUAD = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT;
67 | int SPEAKER_4POINT1 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT;
68 | int SPEAKER_5POINT1 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT;
69 | int SPEAKER_7POINT1 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_FRONT_LEFT_OF_CENTER | SPEAKER_FRONT_RIGHT_OF_CENTER;
70 | int SPEAKER_5POINT1_SURROUND = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT;
71 | int SPEAKER_7POINT1_SURROUND = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT;
72 |
73 | static XAudio2Library loadNative() {
74 | final Map options = new HashMap<>();
75 | options.put(Library.OPTION_STRING_ENCODING, "UTF-8");
76 | try {
77 | return Native.load("XAudio2_9", XAudio2Library.class, options);
78 | } catch (Throwable ignored) {
79 | }
80 | return null;
81 | }
82 |
83 | static boolean isLoaded() {
84 | return INSTANCE != null;
85 | }
86 |
87 | int XAudio2Create(final PointerByReference ppXAudio2, final int Flags, final int XAudio2Processor);
88 |
89 | class XAudio2 extends COMObject {
90 |
91 | public XAudio2() {
92 | }
93 |
94 | public XAudio2(final Pointer p) {
95 | super(p);
96 | }
97 |
98 | public int CreateSourceVoice(final PointerByReference ppSourceVoice, final WAVEFORMATEX.ByReference pSourceFormat, final int Flags, final float MaxFrequencyRatio, final Pointer pCallback, final Pointer pSendList, final Pointer pEffectChain) {
99 | return this.getVtableFunction(5).invokeInt(new Object[]{this.getPointer(), ppSourceVoice, pSourceFormat, Flags, MaxFrequencyRatio, pCallback, pSendList, pEffectChain});
100 | }
101 |
102 | public int CreateMasteringVoice(final PointerByReference ppMasteringVoice, final int InputChannels, final int InputSampleRate, final int Flags, final String szDeviceId, final Pointer pEffectChain, final int StreamCategory) {
103 | return this.getVtableFunction(7).invokeInt(new Object[]{this.getPointer(), ppMasteringVoice, InputChannels, InputSampleRate, Flags, szDeviceId, pEffectChain, StreamCategory});
104 | }
105 |
106 | public void GetPerformanceData(final XAUDIO2_PERFORMANCE_DATA.ByReference pPerfData) {
107 | this.getVtableFunction(11).invokeVoid(new Object[]{this.getPointer(), pPerfData});
108 | }
109 |
110 | }
111 |
112 | class XAudio2Voice extends VTableHandler {
113 |
114 | public XAudio2Voice() {
115 | }
116 |
117 | public XAudio2Voice(final Pointer p) {
118 | super(p);
119 | }
120 |
121 | public void GetVoiceDetails(final XAUDIO2_VOICE_DETAILS.ByReference pVoiceDetails) {
122 | this.getVtableFunction(0).invokeVoid(new Object[]{this.getPointer(), pVoiceDetails});
123 | }
124 |
125 | public int SetVolume(final float Volume, final int OperationSet) {
126 | return this.getVtableFunction(12).invokeInt(new Object[]{this.getPointer(), Volume, OperationSet});
127 | }
128 |
129 | public int SetOutputMatrix(final XAudio2Voice pDestinationVoice, final int SourceChannels, final int DestinationChannels, final float[] pLevelMatrix, final int OperationSet) {
130 | return this.getVtableFunction(16).invokeInt(new Object[]{this.getPointer(), pDestinationVoice.getPointer(), SourceChannels, DestinationChannels, pLevelMatrix, OperationSet});
131 | }
132 |
133 | public void DestroyVoice() {
134 | this.getVtableFunction(18).invokeVoid(new Object[]{this.getPointer()});
135 | }
136 |
137 | }
138 |
139 | class XAudio2MasteringVoice extends XAudio2Voice {
140 |
141 | public XAudio2MasteringVoice() {
142 | }
143 |
144 | public XAudio2MasteringVoice(final Pointer p) {
145 | super(p);
146 | }
147 |
148 | public int GetChannelMask(final IntByReference pChannelMask) {
149 | return this.getVtableFunction(19).invokeInt(new Object[]{this.getPointer(), pChannelMask});
150 | }
151 |
152 | }
153 |
154 | class XAudio2SourceVoice extends XAudio2Voice {
155 |
156 | public XAudio2SourceVoice() {
157 | }
158 |
159 | public XAudio2SourceVoice(final Pointer p) {
160 | super(p);
161 | }
162 |
163 | public int Start(final int Flags, final int OperationSet) {
164 | return this.getVtableFunction(19).invokeInt(new Object[]{this.getPointer(), Flags, OperationSet});
165 | }
166 |
167 | public int Stop(final int Flags, final int OperationSet) {
168 | return this.getVtableFunction(20).invokeInt(new Object[]{this.getPointer(), Flags, OperationSet});
169 | }
170 |
171 | public int SubmitSourceBuffer(final XAUDIO2_BUFFER.ByReference pBuffer, final Pointer pBufferWMA) {
172 | return this.getVtableFunction(21).invokeInt(new Object[]{this.getPointer(), pBuffer, pBufferWMA});
173 | }
174 |
175 | public int FlushSourceBuffers() {
176 | return this.getVtableFunction(22).invokeInt(new Object[]{this.getPointer()});
177 | }
178 |
179 | public void GetState(final XAUDIO2_VOICE_STATE.ByReference pVoiceState, final int Flags) {
180 | this.getVtableFunction(25).invokeVoid(new Object[]{this.getPointer(), pVoiceState, Flags});
181 | }
182 |
183 | public int SetFrequencyRatio(final float Ratio, final int OperationSet) {
184 | return this.getVtableFunction(26).invokeInt(new Object[]{this.getPointer(), Ratio, OperationSet});
185 | }
186 |
187 | public int SetSourceSampleRate(final int NewSourceSampleRate) {
188 | return this.getVtableFunction(28).invokeInt(new Object[]{this.getPointer(), NewSourceSampleRate});
189 | }
190 |
191 | }
192 |
193 | @Structure.FieldOrder({"AudioCyclesSinceLastQuery", "TotalCyclesSinceLastQuery", "MinimumCyclesPerQuantum", "MaximumCyclesPerQuantum", "MemoryUsageInBytes", "CurrentLatencyInSamples", "GlitchesSinceEngineStarted", "ActiveSourceVoiceCount", "TotalSourceVoiceCount", "ActiveSubmixVoiceCount", "ActiveResamplerCount", "ActiveMatrixMixCount", "ActiveXmaSourceVoices", "ActiveXmaStreams"})
194 | class XAUDIO2_PERFORMANCE_DATA extends Structure {
195 |
196 | public long AudioCyclesSinceLastQuery;
197 | public long TotalCyclesSinceLastQuery;
198 | public int MinimumCyclesPerQuantum;
199 | public int MaximumCyclesPerQuantum;
200 | public int MemoryUsageInBytes;
201 | public int CurrentLatencyInSamples;
202 | public int GlitchesSinceEngineStarted;
203 | public int ActiveSourceVoiceCount;
204 | public int TotalSourceVoiceCount;
205 | public int ActiveSubmixVoiceCount;
206 | public int ActiveResamplerCount;
207 | public int ActiveMatrixMixCount;
208 | public int ActiveXmaSourceVoices;
209 | public int ActiveXmaStreams;
210 |
211 | public XAUDIO2_PERFORMANCE_DATA() {
212 | }
213 |
214 | public XAUDIO2_PERFORMANCE_DATA(final Pointer p) {
215 | super(p);
216 | this.read();
217 | }
218 |
219 | public static class ByReference extends XAUDIO2_PERFORMANCE_DATA implements Structure.ByReference {
220 | }
221 |
222 | public static class ByValue extends XAUDIO2_PERFORMANCE_DATA implements Structure.ByValue {
223 | }
224 |
225 | }
226 |
227 | @Structure.FieldOrder({"wFormatTag", "nChannels", "nSamplesPerSec", "nAvgBytesPerSec", "nBlockAlign", "wBitsPerSample", "cbSize"})
228 | class WAVEFORMATEX extends Structure {
229 |
230 | public short wFormatTag;
231 | public short nChannels;
232 | public int nSamplesPerSec;
233 | public int nAvgBytesPerSec;
234 | public short nBlockAlign;
235 | public short wBitsPerSample;
236 | public short cbSize;
237 |
238 | public WAVEFORMATEX() {
239 | }
240 |
241 | public WAVEFORMATEX(final Pointer p) {
242 | super(p);
243 | this.read();
244 | }
245 |
246 | public static class ByReference extends WAVEFORMATEX implements Structure.ByReference {
247 | }
248 |
249 | public static class ByValue extends WAVEFORMATEX implements Structure.ByValue {
250 | }
251 |
252 | }
253 |
254 | @Structure.FieldOrder({"Flags", "AudioBytes", "pAudioData", "PlayBegin", "PlayLength", "LoopBegin", "LoopLength", "LoopCount", "pContext"})
255 | class XAUDIO2_BUFFER extends Structure {
256 |
257 | public int Flags;
258 | public int AudioBytes;
259 | public Pointer pAudioData;
260 | public int PlayBegin;
261 | public int PlayLength;
262 | public int LoopBegin;
263 | public int LoopLength;
264 | public int LoopCount;
265 | public Pointer pContext;
266 |
267 | public XAUDIO2_BUFFER() {
268 | }
269 |
270 | public XAUDIO2_BUFFER(final Pointer p) {
271 | super(p);
272 | this.read();
273 | }
274 |
275 | public static class ByReference extends XAUDIO2_BUFFER implements Structure.ByReference {
276 | }
277 |
278 | public static class ByValue extends XAUDIO2_BUFFER implements Structure.ByValue {
279 | }
280 |
281 | }
282 |
283 | @Structure.FieldOrder({"pCurrentBufferContext", "BuffersQueued", "SamplesPlayed"})
284 | class XAUDIO2_VOICE_STATE extends Structure {
285 |
286 | public Pointer pCurrentBufferContext;
287 | public int BuffersQueued;
288 | public long SamplesPlayed;
289 |
290 | public XAUDIO2_VOICE_STATE() {
291 | }
292 |
293 | public XAUDIO2_VOICE_STATE(final Pointer p) {
294 | super(p);
295 | this.read();
296 | }
297 |
298 | public static class ByReference extends XAUDIO2_VOICE_STATE implements Structure.ByReference {
299 | }
300 |
301 | public static class ByValue extends XAUDIO2_VOICE_STATE implements Structure.ByValue {
302 | }
303 |
304 | }
305 |
306 | @Structure.FieldOrder({"CreationFlags", "ActiveFlags", "InputChannels", "InputSampleRate"})
307 | class XAUDIO2_VOICE_DETAILS extends Structure {
308 |
309 | public int CreationFlags;
310 | public int ActiveFlags;
311 | public int InputChannels;
312 | public int InputSampleRate;
313 |
314 | public XAUDIO2_VOICE_DETAILS() {
315 | }
316 |
317 | public XAUDIO2_VOICE_DETAILS(final Pointer p) {
318 | super(p);
319 | this.read();
320 | }
321 |
322 | public static class ByReference extends XAUDIO2_VOICE_DETAILS implements Structure.ByReference {
323 | }
324 |
325 | public static class ByValue extends XAUDIO2_VOICE_DETAILS implements Structure.ByValue {
326 | }
327 |
328 | }
329 |
330 | }
331 |
--------------------------------------------------------------------------------
/src/main/java/net/raphimc/noteblocktool/audio/library/BassLibrary.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of NoteBlockTool - https://github.com/RaphiMC/NoteBlockTool
3 | * Copyright (C) 2022-2025 RK_01/RaphiMC and contributors
4 | *
5 | * This program is free software; you can redistribute it and/or
6 | * modify it under the terms of the GNU Lesser General Public
7 | * License as published by the Free Software Foundation; either
8 | * version 3 of the License, or (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package net.raphimc.noteblocktool.audio.library;
19 |
20 | import com.sun.jna.*;
21 | import com.sun.jna.ptr.FloatByReference;
22 | import com.sun.jna.ptr.PointerByReference;
23 |
24 | import java.util.HashMap;
25 | import java.util.Map;
26 |
27 | public interface BassLibrary extends Library {
28 |
29 | BassLibrary INSTANCE = loadNative();
30 |
31 | int BASSVERSION = 0x204;
32 |
33 | // BASS_Init flags
34 | int BASS_DEVICE_8BITS = 1; // unused
35 | int BASS_DEVICE_MONO = 2; // mono
36 | int BASS_DEVICE_3D = 4; // unused
37 | int BASS_DEVICE_16BITS = 8; // limit output to 16-bit
38 | int BASS_DEVICE_REINIT = 128; // reinitialize
39 | int BASS_DEVICE_LATENCY = 0x100; // unused
40 | int BASS_DEVICE_CPSPEAKERS = 0x400; // unused
41 | int BASS_DEVICE_SPEAKERS = 0x800; // force enabling of speaker assignment
42 | int BASS_DEVICE_NOSPEAKER = 0x1000; // ignore speaker arrangement
43 | int BASS_DEVICE_DMIX = 0x2000; // use ALSA "dmix" plugin
44 | int BASS_DEVICE_FREQ = 0x4000; // set device sample rate
45 | int BASS_DEVICE_STEREO = 0x8000; // limit output to stereo
46 | int BASS_DEVICE_HOG = 0x10000; // hog/exclusive mode
47 | int BASS_DEVICE_AUDIOTRACK = 0x20000; // use AudioTrack output
48 | int BASS_DEVICE_DSOUND = 0x40000; // use DirectSound output
49 | int BASS_DEVICE_SOFTWARE = 0x80000; // disable hardware/fastpath output
50 |
51 | // Error codes returned by BASS_ErrorGetCode
52 | int BASS_OK = 0; // all is OK
53 | int BASS_ERROR_MEM = 1; // memory error
54 | int BASS_ERROR_FILEOPEN = 2; // can't open the file
55 | int BASS_ERROR_DRIVER = 3; // can't find a free/valid driver
56 | int BASS_ERROR_BUFLOST = 4; // the sample buffer was lost
57 | int BASS_ERROR_HANDLE = 5; // invalid handle
58 | int BASS_ERROR_FORMAT = 6; // unsupported sample format
59 | int BASS_ERROR_POSITION = 7; // invalid position
60 | int BASS_ERROR_INIT = 8; // BASS_Init has not been successfully called
61 | int BASS_ERROR_START = 9; // BASS_Start has not been successfully called
62 | int BASS_ERROR_SSL = 10; // SSL/HTTPS support isn't available
63 | int BASS_ERROR_REINIT = 11; // device needs to be reinitialized
64 | int BASS_ERROR_ALREADY = 14; // already initialized/paused/whatever
65 | int BASS_ERROR_NOTAUDIO = 17; // file does not contain audio
66 | int BASS_ERROR_NOCHAN = 18; // can't get a free channel
67 | int BASS_ERROR_ILLTYPE = 19; // an illegal type was specified
68 | int BASS_ERROR_ILLPARAM = 20; // an illegal parameter was specified
69 | int BASS_ERROR_NO3D = 21; // no 3D support
70 | int BASS_ERROR_NOEAX = 22; // no EAX support
71 | int BASS_ERROR_DEVICE = 23; // illegal device number
72 | int BASS_ERROR_NOPLAY = 24; // not playing
73 | int BASS_ERROR_FREQ = 25; // illegal sample rate
74 | int BASS_ERROR_NOTFILE = 27; // the stream is not a file stream
75 | int BASS_ERROR_NOHW = 29; // no hardware voices available
76 | int BASS_ERROR_EMPTY = 31; // the file has no sample data
77 | int BASS_ERROR_NONET = 32; // no internet connection could be opened
78 | int BASS_ERROR_CREATE = 33; // couldn't create the file
79 | int BASS_ERROR_NOFX = 34; // effects are not available
80 | int BASS_ERROR_NOTAVAIL = 37; // requested data/action is not available
81 | int BASS_ERROR_DECODE = 38; // the channel is/isn't a "decoding channel"
82 | int BASS_ERROR_DX = 39; // a sufficient DirectX version is not installed
83 | int BASS_ERROR_TIMEOUT = 40; // connection timedout
84 | int BASS_ERROR_FILEFORM = 41; // unsupported file format
85 | int BASS_ERROR_SPEAKER = 42; // unavailable speaker
86 | int BASS_ERROR_VERSION = 43; // invalid BASS version (used by add-ons)
87 | int BASS_ERROR_CODEC = 44; // codec is not available/supported
88 | int BASS_ERROR_ENDED = 45; // the channel/file has ended
89 | int BASS_ERROR_BUSY = 46; // the device is busy
90 | int BASS_ERROR_UNSTREAMABLE = 47; // unstreamable file
91 | int BASS_ERROR_PROTOCOL = 48; // unsupported protocol
92 | int BASS_ERROR_DENIED = 49; // access denied
93 | int BASS_ERROR_UNKNOWN = -1; // some other mystery problem
94 |
95 | // BASS_SetConfig options
96 | int BASS_CONFIG_BUFFER = 0;
97 | int BASS_CONFIG_UPDATEPERIOD = 1;
98 | int BASS_CONFIG_GVOL_SAMPLE = 4;
99 | int BASS_CONFIG_GVOL_STREAM = 5;
100 | int BASS_CONFIG_GVOL_MUSIC = 6;
101 | int BASS_CONFIG_CURVE_VOL = 7;
102 | int BASS_CONFIG_CURVE_PAN = 8;
103 | int BASS_CONFIG_FLOATDSP = 9;
104 | int BASS_CONFIG_3DALGORITHM = 10;
105 | int BASS_CONFIG_NET_TIMEOUT = 11;
106 | int BASS_CONFIG_NET_BUFFER = 12;
107 | int BASS_CONFIG_PAUSE_NOPLAY = 13;
108 | int BASS_CONFIG_NET_PREBUF = 15;
109 | int BASS_CONFIG_NET_PASSIVE = 18;
110 | int BASS_CONFIG_REC_BUFFER = 19;
111 | int BASS_CONFIG_NET_PLAYLIST = 21;
112 | int BASS_CONFIG_MUSIC_VIRTUAL = 22;
113 | int BASS_CONFIG_VERIFY = 23;
114 | int BASS_CONFIG_UPDATETHREADS = 24;
115 | int BASS_CONFIG_DEV_BUFFER = 27;
116 | int BASS_CONFIG_REC_LOOPBACK = 28;
117 | int BASS_CONFIG_VISTA_TRUEPOS = 30;
118 | int BASS_CONFIG_IOS_SESSION = 34;
119 | int BASS_CONFIG_IOS_MIXAUDIO = 34;
120 | int BASS_CONFIG_DEV_DEFAULT = 36;
121 | int BASS_CONFIG_NET_READTIMEOUT = 37;
122 | int BASS_CONFIG_VISTA_SPEAKERS = 38;
123 | int BASS_CONFIG_IOS_SPEAKER = 39;
124 | int BASS_CONFIG_MF_DISABLE = 40;
125 | int BASS_CONFIG_HANDLES = 41;
126 | int BASS_CONFIG_UNICODE = 42;
127 | int BASS_CONFIG_SRC = 43;
128 | int BASS_CONFIG_SRC_SAMPLE = 44;
129 | int BASS_CONFIG_ASYNCFILE_BUFFER = 45;
130 | int BASS_CONFIG_OGG_PRESCAN = 47;
131 | int BASS_CONFIG_MF_VIDEO = 48;
132 | int BASS_CONFIG_AIRPLAY = 49;
133 | int BASS_CONFIG_DEV_NONSTOP = 50;
134 | int BASS_CONFIG_IOS_NOCATEGORY = 51;
135 | int BASS_CONFIG_VERIFY_NET = 52;
136 | int BASS_CONFIG_DEV_PERIOD = 53;
137 | int BASS_CONFIG_FLOAT = 54;
138 | int BASS_CONFIG_NET_SEEK = 56;
139 | int BASS_CONFIG_AM_DISABLE = 58;
140 | int BASS_CONFIG_NET_PLAYLIST_DEPTH = 59;
141 | int BASS_CONFIG_NET_PREBUF_WAIT = 60;
142 | int BASS_CONFIG_ANDROID_SESSIONID = 62;
143 | int BASS_CONFIG_WASAPI_PERSIST = 65;
144 | int BASS_CONFIG_REC_WASAPI = 66;
145 | int BASS_CONFIG_ANDROID_AAUDIO = 67;
146 | int BASS_CONFIG_SAMPLE_ONEHANDLE = 69;
147 | int BASS_CONFIG_NET_META = 71;
148 | int BASS_CONFIG_NET_RESTRATE = 72;
149 | int BASS_CONFIG_REC_DEFAULT = 73;
150 | int BASS_CONFIG_NORAMP = 74;
151 |
152 | // Channel attributes
153 | int BASS_ATTRIB_FREQ = 1;
154 | int BASS_ATTRIB_VOL = 2;
155 | int BASS_ATTRIB_PAN = 3;
156 | int BASS_ATTRIB_EAXMIX = 4;
157 | int BASS_ATTRIB_NOBUFFER = 5;
158 | int BASS_ATTRIB_VBR = 6;
159 | int BASS_ATTRIB_CPU = 7;
160 | int BASS_ATTRIB_SRC = 8;
161 | int BASS_ATTRIB_NET_RESUME = 9;
162 | int BASS_ATTRIB_SCANINFO = 10;
163 | int BASS_ATTRIB_NORAMP = 11;
164 | int BASS_ATTRIB_BITRATE = 12;
165 | int BASS_ATTRIB_BUFFER = 13;
166 | int BASS_ATTRIB_GRANULE = 14;
167 | int BASS_ATTRIB_USER = 15;
168 | int BASS_ATTRIB_TAIL = 16;
169 | int BASS_ATTRIB_PUSH_LIMIT = 17;
170 | int BASS_ATTRIB_DOWNLOADPROC = 18;
171 | int BASS_ATTRIB_VOLDSP = 19;
172 | int BASS_ATTRIB_VOLDSP_PRIORITY = 20;
173 | int BASS_ATTRIB_MUSIC_AMPLIFY = 0x100;
174 | int BASS_ATTRIB_MUSIC_PANSEP = 0x101;
175 | int BASS_ATTRIB_MUSIC_PSCALER = 0x102;
176 | int BASS_ATTRIB_MUSIC_BPM = 0x103;
177 | int BASS_ATTRIB_MUSIC_SPEED = 0x104;
178 | int BASS_ATTRIB_MUSIC_VOL_GLOBAL = 0x105;
179 | int BASS_ATTRIB_MUSIC_ACTIVE = 0x106;
180 | int BASS_ATTRIB_MUSIC_VOL_CHAN = 0x200; // + channel #
181 | int BASS_ATTRIB_MUSIC_VOL_INST = 0x300; // + instrument #
182 |
183 | // BASS_ChannelGetData flags
184 | int BASS_DATA_AVAILABLE = 0; // query how much data is buffered
185 | int BASS_DATA_NOREMOVE = 0x10000000; // flag: don't remove data from recording buffer
186 | int BASS_DATA_FIXED = 0x20000000; // unused
187 | int BASS_DATA_FLOAT = 0x40000000; // flag: return floating-point sample data
188 | int BASS_DATA_FFT256 = 0x80000000; // 256 sample FFT
189 | int BASS_DATA_FFT512 = 0x80000001; // 512 FFT
190 | int BASS_DATA_FFT1024 = 0x80000002; // 1024 FFT
191 | int BASS_DATA_FFT2048 = 0x80000003; // 2048 FFT
192 | int BASS_DATA_FFT4096 = 0x80000004; // 4096 FFT
193 | int BASS_DATA_FFT8192 = 0x80000005; // 8192 FFT
194 | int BASS_DATA_FFT16384 = 0x80000006; // 16384 FFT
195 | int BASS_DATA_FFT32768 = 0x80000007; // 32768 FFT
196 | int BASS_DATA_FFT_INDIVIDUAL = 0x10; // FFT flag: FFT for each channel, else all combined
197 | int BASS_DATA_FFT_NOWINDOW = 0x20; // FFT flag: no Hanning window
198 | int BASS_DATA_FFT_REMOVEDC = 0x40; // FFT flag: pre-remove DC bias
199 | int BASS_DATA_FFT_COMPLEX = 0x80; // FFT flag: return complex data
200 | int BASS_DATA_FFT_NYQUIST = 0x100; // FFT flag: return extra Nyquist value
201 |
202 | // BASS_SampleGetChannel flags
203 | int BASS_SAMCHAN_NEW = 1; // get a new playback channel
204 | int BASS_SAMCHAN_STREAM = 2; // create a stream
205 |
206 | // BASS_ChannelSetSync types
207 | int BASS_SYNC_POS = 0;
208 | int BASS_SYNC_END = 2;
209 | int BASS_SYNC_META = 4;
210 | int BASS_SYNC_SLIDE = 5;
211 | int BASS_SYNC_STALL = 6;
212 | int BASS_SYNC_DOWNLOAD = 7;
213 | int BASS_SYNC_FREE = 8;
214 | int BASS_SYNC_SETPOS = 11;
215 | int BASS_SYNC_MUSICPOS = 10;
216 | int BASS_SYNC_MUSICINST = 1;
217 | int BASS_SYNC_MUSICFX = 3;
218 | int BASS_SYNC_OGG_CHANGE = 12;
219 | int BASS_SYNC_DEV_FAIL = 14;
220 | int BASS_SYNC_DEV_FORMAT = 15;
221 | int BASS_SYNC_THREAD = 0x20000000; // flag: call sync in other thread
222 | int BASS_SYNC_MIXTIME = 0x40000000; // flag: sync at mixtime, else at playtime
223 | int BASS_SYNC_ONETIME = 0x80000000; // flag: sync only once, else continuously
224 |
225 | int BASS_SAMPLE_8BITS = 1;// 8 bit
226 | int BASS_SAMPLE_FLOAT = 256; // 32 bit floating-point
227 | int BASS_SAMPLE_MONO = 2; // mono
228 | int BASS_SAMPLE_LOOP = 4; // looped
229 | int BASS_SAMPLE_3D = 8; // 3D functionality
230 | int BASS_SAMPLE_SOFTWARE = 16; // unused
231 | int BASS_SAMPLE_MUTEMAX = 32; // mute at max distance (3D only)
232 | int BASS_SAMPLE_VAM = 64; // unused
233 | int BASS_SAMPLE_FX = 128; // unused
234 | int BASS_SAMPLE_OVER_VOL = 0x10000; // override lowest volume
235 | int BASS_SAMPLE_OVER_POS = 0x20000; // override longest playing
236 | int BASS_SAMPLE_OVER_DIST = 0x30000; // override furthest from listener (3D only)
237 |
238 | int BASS_STREAM_DECODE = 0x200000; // don't play the stream, only decode
239 | int BASS_STREAM_AUTOFREE = 0x40000; // automatically free the stream when it stops/ends
240 |
241 | static BassLibrary loadNative() {
242 | final Map options = new HashMap<>();
243 | options.put(Library.OPTION_STRING_ENCODING, "UTF-8");
244 | try {
245 | return Native.load("bass", BassLibrary.class, options);
246 | } catch (Throwable ignored) {
247 | }
248 | return null;
249 | }
250 |
251 | static boolean isLoaded() {
252 | return INSTANCE != null;
253 | }
254 |
255 | boolean BASS_Init(final int device, final int freq, final int flags, final int win, final PointerByReference clsid);
256 |
257 | boolean BASS_Free();
258 |
259 | boolean BASS_Start();
260 |
261 | boolean BASS_Stop();
262 |
263 | int BASS_GetVersion();
264 |
265 | int BASS_GetDevice();
266 |
267 | boolean BASS_GetDeviceInfo(final int device, final BASS_DEVICEINFO.ByReference info);
268 |
269 | int BASS_ErrorGetCode();
270 |
271 | float BASS_GetCPU();
272 |
273 | int BASS_GetConfig(final int option);
274 |
275 | boolean BASS_SetConfig(final int option, final int value);
276 |
277 | int BASS_SampleCreate(final int length, final int freq, final int chans, final int max, final int flags);
278 |
279 | boolean BASS_SampleSetData(final int handle, final byte[] buffer);
280 |
281 | int BASS_SampleGetChannel(final int handle, final int flags);
282 |
283 | boolean BASS_ChannelStart(final int handle);
284 |
285 | boolean BASS_ChannelGetAttribute(final int handle, final int attrib, final FloatByReference value);
286 |
287 | boolean BASS_ChannelSetAttribute(final int handle, final int attrib, final float value);
288 |
289 | int BASS_ChannelSetSync(final int handle, final int type, final long param, final SYNCPROC proc, final Pointer user);
290 |
291 | boolean BASS_ChannelFree(final int handle);
292 |
293 | int BASS_ChannelGetData(final int handle, final Pointer buffer, final int length);
294 |
295 | interface SYNCPROC extends Callback {
296 |
297 | void syncProc(final int handle, final int channel, final int data, final Pointer user);
298 |
299 | }
300 |
301 | @Structure.FieldOrder({"name", "driver", "flags"})
302 | class BASS_DEVICEINFO extends Structure {
303 |
304 | public String name;
305 | public String driver;
306 | public int flags;
307 |
308 | public BASS_DEVICEINFO() {
309 | }
310 |
311 | public BASS_DEVICEINFO(final String name, final String driver, final int flags) {
312 | this.name = name;
313 | this.driver = driver;
314 | this.flags = flags;
315 | }
316 |
317 | public static class ByReference extends BASS_DEVICEINFO implements Structure.ByReference {
318 | }
319 |
320 | public static class ByValue extends BASS_DEVICEINFO implements Structure.ByValue {
321 | }
322 |
323 | }
324 |
325 | }
326 |
--------------------------------------------------------------------------------