From db6f87a74045f38ed3653ba39872ac44d419c950 Mon Sep 17 00:00:00 2001 From: Sen Date: Sun, 24 Aug 2025 22:01:05 +0200 Subject: [PATCH] initial midi test success!! --- client/src/main/java/client/Client.java | 67 +- .../java/client/audio/AudioInterface.java | 2351 ++++++++++++++++- .../src/main/java/client/audio/OPLChip.java | 978 +++++++ client/src/main/java/client/gui/GuiMenu.java | 21 + .../java/client/gui/options/GuiSound.java | 15 +- .../src/main/java/client/util/FileUtils.java | 2 +- common/src/main/java/common/util/Util.java | 47 +- 7 files changed, 3451 insertions(+), 30 deletions(-) create mode 100644 client/src/main/java/client/audio/OPLChip.java diff --git a/client/src/main/java/client/Client.java b/client/src/main/java/client/Client.java index b062d6a9..b1892ff7 100755 --- a/client/src/main/java/client/Client.java +++ b/client/src/main/java/client/Client.java @@ -14,6 +14,7 @@ import java.net.InetAddress; import java.net.UnknownHostException; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.nio.file.Files; import java.text.SimpleDateFormat; import java.util.ArrayDeque; import java.util.Date; @@ -29,10 +30,13 @@ import java.util.function.Function; import javax.imageio.ImageIO; import javax.swing.JFileChooser; +import org.lwjgl.opengl.GL; import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL13; import client.audio.AudioInterface; +import client.audio.AudioInterface.MidiBank; +import client.audio.AudioInterface.MidiHandle; import client.audio.SoundManager; import client.audio.Volume; import client.gui.FileCallback; @@ -278,6 +282,12 @@ public class Client implements IThreadListener { } } + public static class MidiDebugFunction implements BoolFunction { + public void apply(BoolVar cv, boolean value) { + Client.CLIENT.setMidiDebug(); + } + } + private interface DebugRunner { void execute(Keysym key); } @@ -623,6 +633,27 @@ public class Client implements IThreadListener { @Variable(name = "snd_frame_size", category = CVarCategory.SOUND, min = 2, max = 8192, display = "Intervall") private int soundFrameSize = 32; + @Variable(name = "mid_dont_fade", category = CVarCategory.SOUND, display = "Nicht ausklingen") + private boolean midiNoWait = false; + @Variable(name = "mid_opl_voices", category = CVarCategory.SOUND, min = 4, max = 192, display = "OPL-Stimmen") + private int midiVoices = 64; + @Variable(name = "mid_keep_notes", category = CVarCategory.SOUND, display = "Stimmen behalten") + private boolean midiKeep = false; + @Variable(name = "mid_play_unknown", category = CVarCategory.SOUND, display = "Unbekannte Banken") + private boolean midiUnknown = true; +// STR(MID_VELO_LOG, "", "Log.+Minimum [m+nlog(x)]") +// STR(MID_VELO_ATTN, "", "Log. Gedämpft [nlog(x)]") +// STR(MID_VELO_LIN, "", "Linear [x]") +// STR(MID_VELO_ONE, "", "Vollklang [1]") + @Variable(name = "mid_velocity_func", category = CVarCategory.SOUND, min = -128, max = 127, display = "Anschlag") + private int midiVelocity = 1; + @Variable(name = "mid_opl_bank", category = CVarCategory.SOUND, display = "Bank") + private MidiBank midiBank = MidiBank.DMX_DMX; + @Variable(name = "mid_debug_events", category = CVarCategory.SOUND, display = "MIDI-Debug", callback = MidiDebugFunction.class) + private boolean midiDebug = false; + @Variable(name = "mid_visualizer", category = CVarCategory.SOUND, display = "Visualisation") + private boolean midiVisualizer = true; + public static final Client CLIENT = new Client(); public static void main(String[] args) { @@ -642,8 +673,8 @@ public class Client implements IThreadListener { CLIENT.run(time); Window.end(); } - - private Client() { + + private Client() { } private NetConnection connect(InetAddress address, int port) { @@ -2272,6 +2303,10 @@ public class Client implements IThreadListener { public void run(long time) { if(!Window.createWindow(VERSION, System.getProperty("opengl.debug") != null)) System.exit(1); + if(!GL.getCapabilities().OpenGL15) { + Window.destroyWindow(); + Util.panic("Inkompatible OpenGL-Version", "OpenGL 1.5 oder höher ist erforderlich, um dieses Programm auszuführen."); + } Log.SYSTEM.info("OpenGL %s", GL11.glGetString(GL11.GL_VERSION)); Log.SYSTEM.info("GL_VENDOR: %s", GL11.glGetString(GL11.GL_VENDOR)); Log.SYSTEM.info("GL_RENDERER: %s", GL11.glGetString(GL11.GL_RENDERER)); @@ -2557,6 +2592,14 @@ public class Client implements IThreadListener { this.renderer.setDisplayListEntitiesDirty(); } + public void setMidiDebug() { + if(this.audio != null) { + MidiHandle midi = this.audio.alGetMidi(); + if(midi != null) + midi.setDebug(this.midiDebug); + } + } + private void registerDebug(Keysym key, String help, DebugRunner runner) { if(this.debug.containsKey(key)) throw new IllegalStateException("Debug-Taste " + key.getDisplay() + " ist bereits registriert"); @@ -3407,6 +3450,26 @@ public class Client implements IThreadListener { Log.NETWORK.debug("Variable %s = %s", name, value); } } + + public void playMidi(File file) { + byte[] data; + try { + data = Files.readAllBytes(file.toPath()); + } + catch(Throwable e) { + Log.SOUND.error(e, "Konnte Datei '%s' nicht laden", file); + return; + } + MidiHandle midi; + try { + midi = new MidiHandle(data, this.midiNoWait, this.midiVoices, this.midiBank, this.midiKeep, this.midiUnknown, this.midiVelocity); + } + catch(Throwable e) { + Log.SOUND.error(e, "Konnte MIDI '%s' nicht laden", file); + return; + } + this.audio.alMidi(midi); + } private void displayTick(int posX, int posY, int posZ) { int range = 16; diff --git a/client/src/main/java/client/audio/AudioInterface.java b/client/src/main/java/client/audio/AudioInterface.java index 47dbefd6..416318d6 100644 --- a/client/src/main/java/client/audio/AudioInterface.java +++ b/client/src/main/java/client/audio/AudioInterface.java @@ -1,5 +1,6 @@ package client.audio; +import java.io.IOException; import java.util.Arrays; import java.util.List; @@ -10,10 +11,1456 @@ import javax.sound.sampled.DataLine.Info; import common.collect.Lists; import common.log.Log; +import common.util.Displayable; +import common.util.Identifyable; import javax.sound.sampled.SourceDataLine; +import client.audio.OPLChip.OPLChannel; +import client.audio.OPLChip.OPLSlot; +import client.util.FileUtils; + public class AudioInterface implements Runnable { + public static enum MidiBank implements Identifyable, Displayable { + DMX_DMX("dmx_dmx", "op2"), + DMX_DOOM1("dmx_doom1", "op2"), + DMX_DOOM2("dmx_doom2", "op2"), + DMX_RAPTOR("dmx_raptor", "op2"), + DMX_STRIFE("dmx_strife", "op2"), + WOLFINSTEIN("wolfinstein", "op2"), + STD_O3("std_o3", "skb"), + STD_SB("std_sb", "skb"), + HMI_144("hmi_144", "skb"), + CYBERPUCK("cyberpuck", "tmb"), + D3DTIMBR("d3dtimbr", "tmb"), + D3DTIMBR_MOD("d3dtimbr_mod", "tmb"), + GMOCONEL("gmoconel", "tmb"), + GMOPL_MOD("gmopl_mod", "tmb"), + SWTIMBR("swtimbr", "tmb"), + TMB_DEFAULT("tmb_default", "tmb"), + WALLENCE("wallence", "op3"), + FAT2("fat2", "op3"), + FAT4("fat4", "op3"), + OP2X2("op2x2", "op3"), + WALLACE("wallace", "op3"), + EARTHSIEG("earthsieg", "ad"), + WARCRAFT("warcraft", "ad"), + NEMESIS("nemesis", "opl"), + BANK49("bank49", "opl"), + JV_2OP("jv_2op", "op3"), + GMOPL("gmopl", "ibk"), + MT32("mt32", "ibk"), + INSMAKER_STD("insmaker_std", "bnk"), + BANK53("bank53", "opl"), + DRUMOPL("drumopl", "tmb"); + + private final String name; + private final BankInstr[] data = new BankInstr[256]; + + static { + DMX_DMX + .instr(0, new BankInstr(new BankOperator(0, 43, 1, 15, 0, 0, 3).ksl(1).sustaining().ksr(), new BankOperator(0, 63, 1, 15, 1, 0, 4).sustaining().ksr(), false, 0, 12, 5)) + .instr(1, new BankInstr(new BankOperator(0, 45, 0, 15, 0, 0, 3).ksl(1).sustaining().ksr(), new BankOperator(0, 63, 1, 15, 1, 0, 4).sustaining().ksr(), false, 0, 12, 5)) + .instr(2, + new BankInstr(new BankOperator(0, 49, 1, 14, 1, 0, 3).ksl(1).sustaining().ksr(), new BankOperator(0, 63, 0, 15, 1, 0, 4).sustaining().ksr(), false, 0, 12, 4, + new BankOperator(1, 63, 0, 14, 8, 14, 5).ksr(), new BankOperator(2, 63, 0, 15, 7, 14, 4).ksr(), true, 0, 12, 0)) + .instr(3, + new BankInstr(new BankOperator(0, 48, 1, 15, 1, 10, 3).ksl(1).ksr(), new BankOperator(0, 63, 0, 13, 1, 0, 4).ksr(), false, 0, 12, 3, + new BankOperator(1, 48, 0, 15, 1, 10, 3).ksl(1).ksr(), new BankOperator(1, 63, 0, 13, 1, 0, 4).ksr(), false, 2, 12, 3)) + .instr(4, new BankInstr(new BankOperator(1, 25, 0, 15, 1, 10, 1).ksl(1).sustaining(), new BankOperator(1, 63, 0, 13, 2, 1, 5).sustaining().ksr(), false, 0, 0, 3)) + .instr(5, + new BankInstr(new BankOperator(0, 46, 0, 15, 1, 1, 6).ksl(1).sustaining().ksr(), new BankOperator(0, 63, 0, 15, 1, 1, 5).ksl(1).tremolo().sustaining().ksr(), false, 0, 12, 3, + new BankOperator(2, 60, 0, 15, 2, 8, 9).ksl(1).ksr(), new BankOperator(0, 63, 0, 15, 1, 6, 9).ksl(1).ksr(), true, 0, 12, 4)) + .instr(6, new BankInstr(new BankOperator(0, 56, 2, 15, 2, 15, 1).ksl(2).sustaining().ksr(), new BankOperator(0, 63, 1, 12, 1, 0, 4).sustaining().ksr(), false, 0, 12, 3)) + .instr(7, new BankInstr(new BankOperator(0, 49, 1, 10, 1, 9, 2).ksl(2).tremolo().ksr(), new BankOperator(0, 63, 1, 9, 1, 5, 7).ksr(), false, 0, 12, 6)) + .instr(8, new BankInstr(new BankOperator(8, 48, 1, 15, 2, 9, 4).ksl(1).sustaining(), new BankOperator(1, 63, 0, 15, 2, 1, 4).sustaining().ksr(), false, 0, 0, 4)) + .instr(9, new BankInstr(new BankOperator(3, 49, 0, 9, 1, 14, 1).ksr(), new BankOperator(4, 63, 0, 7, 13, 12, 4).ksr(), true, 0, 0, 4)) + .instr(10, new BankInstr(new BankOperator(2, 48, 0, 15, 6, 11, 1).tremolo().sustaining().ksr(), new BankOperator(0, 63, 0, 13, 2, 6, 2).tremolo().ksr(), false, 0, 12, 0)) + .instr(11, + new BankInstr(new BankOperator(0, 61, 0, 15, 1, 0, 3).tremolo().vibrato().sustaining().ksr(), new BankOperator(2, 63, 0, 15, 1, 0, 4).tremolo().vibrato().sustaining().ksr(), true, + 0, 12, 0)) + .instr(12, new BankInstr(new BankOperator(0, 63, 0, 7, 9, 14, 5).tremolo(), new BankOperator(3, 63, 2, 15, 8, 8, 5).tremolo(), true, 0, 12, 0)) + .instr(13, new BankInstr(new BankOperator(4, 32, 0, 15, 6, 6, 3).ksr(), new BankOperator(0, 63, 0, 15, 6, 10, 3).ksr(), false, 0, 12, 4)) + .instr(14, new BankInstr(new BankOperator(1, 38, 1, 11, 6, 14, 3).ksl(2).tremolo(), new BankOperator(2, 63, 0, 15, 15, 14, 3), false, 0, 0, 5)) + .instr(15, new BankInstr(new BankOperator(0, 56, 0, 9, 1, 14, 1).ksl(1).sustaining().ksr(), new BankOperator(1, 63, 0, 5, 2, 10, 3).ksl(2).ksr(), false, 0, 12, 4)) + .instr(16, + new BankInstr(new BankOperator(0, 55, 0, 11, 1, 14, 6).ksl(2).tremolo().sustaining(), new BankOperator(1, 63, 1, 13, 1, 14, 7).ksl(2).vibrato().sustaining(), true, 0, 12, 3)) + .instr(17, new BankInstr(new BankOperator(0, 63, 1, 15, 1, 15, 5).sustaining().ksr(), new BankOperator(4, 63, 0, 15, 4, 12, 6).tremolo().ksr(), true, 0, 0, 3)) + .instr(18, + new BankInstr(new BankOperator(2, 33, 0, 15, 2, 14, 7).ksl(2).tremolo().vibrato().sustaining(), new BankOperator(0, 63, 1, 15, 15, 15, 7).ksl(2).vibrato().sustaining(), false, 0, + 0, 0, new BankOperator(0, 33, 1, 15, 2, 14, 7).ksl(2).tremolo().vibrato().sustaining(), new BankOperator(0, 63, 0, 15, 15, 15, 7).ksl(2).tremolo().sustaining(), false, 10, 12, + 0)) + .instr(19, new BankInstr(new BankOperator(0, 45, 0, 3, 0, 15, 4).ksl(2).sustaining().ksr(), new BankOperator(1, 63, 1, 5, 4, 14, 4).ksl(2).sustaining().ksr(), true, 0, 12, 4)) + .instr(20, new BankInstr(new BankOperator(0, 54, 0, 8, 0, 14, 7).ksl(1), new BankOperator(1, 63, 1, 6, 0, 14, 7).ksl(2).tremolo(), false, 0, 12, 3)) + .instr(21, + new BankInstr(new BankOperator(0, 55, 0, 10, 2, 14, 5).ksl(1).sustaining(), new BankOperator(1, 63, 1, 4, 1, 13, 6).sustaining().ksr(), false, 0, 12, 5, + new BankOperator(0, 53, 0, 8, 2, 14, 5).ksl(1).sustaining(), new BankOperator(1, 63, 1, 4, 6, 13, 6).sustaining().ksr(), false, -3, 12, 5)) + .instr(22, + new BankInstr(new BankOperator(0, 51, 0, 6, 0, 12, 4).tremolo().sustaining().ksr(), new BankOperator(2, 63, 0, 4, 2, 14, 6).ksl(2).tremolo().sustaining().ksr(), false, 0, 12, 4)) + .instr(23, + new BankInstr(new BankOperator(0, 45, 1, 15, 0, 15, 5).ksl(2).sustaining(), new BankOperator(1, 63, 2, 5, 2, 15, 5).sustaining().ksr(), false, 0, 12, 4, + new BankOperator(0, 45, 1, 15, 0, 15, 5).ksl(2).sustaining(), new BankOperator(1, 63, 2, 5, 2, 15, 5).sustaining().ksr(), false, 1, 12, 0)) + .instr(24, new BankInstr(new BankOperator(0, 50, 0, 15, 1, 0, 5).ksl(2).sustaining(), new BankOperator(0, 63, 0, 15, 1, 0, 6).sustaining(), false, 0, 12, 0)) + .instr(25, new BankInstr(new BankOperator(0, 50, 1, 14, 1, 1, 4).sustaining().ksr(), new BankOperator(0, 63, 0, 15, 2, 1, 3).sustaining().ksr(), false, 0, 12, 5)) + .instr(26, new BankInstr(new BankOperator(0, 30, 2, 15, 1, 14, 15), new BankOperator(0, 63, 0, 15, 4, 7, 8), false, 0, 12, 5)) + .instr(27, new BankInstr(new BankOperator(0, 56, 1, 14, 10, 12, 2).ksl(2).ksr(), new BankOperator(0, 63, 2, 13, 2, 1, 7).ksr(), false, 0, 12, 1)) + .instr(28, new BankInstr(new BankOperator(0, 45, 0, 14, 0, 0, 4).ksl(2).sustaining().ksr(), new BankOperator(0, 63, 0, 15, 2, 0, 5).ksl(2).sustaining().ksr(), false, 0, 12, 0)) + .instr(29, new BankInstr(new BankOperator(0, 51, 1, 15, 0, 14, 3).ksl(2).sustaining(), new BankOperator(2, 63, 0, 14, 3, 13, 7).ksl(2).sustaining(), false, 0, 12, 5)) + .instr(30, new BankInstr(new BankOperator(0, 57, 1, 10, 0, 13, 5).ksl(2).sustaining(), new BankOperator(0, 63, 1, 11, 2, 12, 6).sustaining(), false, 0, 12, 3)) + .instr(31, new BankInstr(new BankOperator(0, 60, 2, 10, 1, 6, 7).ksl(1).ksr(), new BankOperator(1, 63, 0, 14, 1, 1, 7).ksr(), false, 0, 12, 0)) + .instr(32, new BankInstr(new BankOperator(0, 23, 0, 12, 5, 14, 5).sustaining(), new BankOperator(1, 63, 0, 13, 2, 5, 6).sustaining(), false, 0, 12, 6)) + .instr(33, new BankInstr(new BankOperator(0, 41, 0, 15, 0, 0, 15).ksl(2).sustaining().ksr(), new BankOperator(1, 63, 0, 15, 1, 0, 8).sustaining().ksr(), false, 0, 12, 5)) + .instr(34, new BankInstr(new BankOperator(0, 48, 0, 14, 0, 14, 4).ksl(2).sustaining(), new BankOperator(0, 63, 0, 14, 1, 2, 6).ksl(2).sustaining().ksr(), false, 0, 12, 4)) + .instr(35, new BankInstr(new BankOperator(0, 37, 1, 6, 0, 15, 0).ksr(), new BankOperator(0, 63, 0, 8, 1, 0, 6).ksr(), false, 0, 12, 4)) + .instr(36, + new BankInstr(new BankOperator(0, 45, 2, 15, 0, 1, 7).sustaining().ksr(), new BankOperator(1, 63, 0, 15, 1, 11, 7).sustaining().ksr(), false, 0, 0, 0, + new BankOperator(0, 50, 1, 15, 5, 1, 7).ksr(), new BankOperator(0, 63, 2, 15, 6, 1, 7).ksr(), true, 0, 12, 6)) + .instr(37, new BankInstr(new BankOperator(0, 47, 0, 15, 0, 1, 5).ksl(2).sustaining().ksr(), new BankOperator(1, 63, 0, 15, 1, 0, 5).ksl(2).sustaining().ksr(), false, 0, 12, 4)) + .instr(38, new BankInstr(new BankOperator(0, 53, 1, 15, 4, 0, 5).sustaining().ksr(), new BankOperator(0, 63, 0, 15, 3, 0, 6).sustaining().ksr(), false, 0, 12, 5)) + .instr(39, + new BankInstr(new BankOperator(0, 42, 1, 8, 3, 11, 6).sustaining().ksr(), new BankOperator(1, 63, 0, 13, 2, 14, 7).sustaining().ksr(), false, 0, 12, 5, + new BankOperator(0, 42, 1, 8, 3, 11, 6).sustaining().ksr(), new BankOperator(1, 63, 0, 13, 2, 14, 7).sustaining().ksr(), false, -10, 12, 5)) + .instr(40, new BankInstr(new BankOperator(0, 40, 1, 5, 0, 11, 5).vibrato().sustaining(), new BankOperator(1, 63, 1, 6, 1, 11, 6).tremolo().sustaining(), false, 0, 12, 3)) + .instr(41, + new BankInstr(new BankOperator(0, 48, 0, 6, 0, 11, 4).ksl(2).tremolo().vibrato().sustaining().ksr(), new BankOperator(1, 63, 0, 4, 1, 14, 5).vibrato().sustaining().ksr(), false, 0, + 12, 1)) + .instr(42, new BankInstr(new BankOperator(0, 48, 2, 13, 0, 14, 4).tremolo().sustaining().ksr(), new BankOperator(1, 63, 1, 6, 2, 14, 7).ksl(2).vibrato().sustaining(), false, 0, 0, 3)) + .instr(43, + new BankInstr(new BankOperator(0, 53, 2, 11, 1, 14, 1).ksl(2).tremolo().vibrato().sustaining().ksr(), new BankOperator(0, 63, 1, 10, 0, 14, 5).ksl(2).sustaining(), false, 0, 12, + 3)) + .instr(44, + new BankInstr(new BankOperator(0, 54, 2, 12, 3, 15, 1).ksl(2).tremolo().vibrato().sustaining().ksr(), new BankOperator(1, 63, 0, 8, 3, 15, 5).ksl(1).vibrato().sustaining(), false, + 0, 0, 3, new BankOperator(0, 54, 2, 11, 3, 15, 1).ksl(2).vibrato().sustaining().ksr(), new BankOperator(0, 63, 1, 9, 3, 15, 5).ksl(1).vibrato().sustaining(), false, 11, 12, 3)) + .instr(45, new BankInstr(new BankOperator(0, 40, 2, 15, 8, 0, 9).ksl(2).sustaining().ksr(), new BankOperator(0, 63, 0, 7, 6, 1, 6).sustaining(), false, 0, 12, 7)) + .instr(46, new BankInstr(new BankOperator(1, 27, 0, 15, 1, 12, 5).sustaining().ksr(), new BankOperator(0, 63, 0, 15, 3, 4, 3).sustaining(), false, 0, 12, 0)) + .instr(47, new BankInstr(new BankOperator(0, 59, 0, 10, 10, 3, 8), new BankOperator(0, 63, 0, 13, 2, 4, 3).ksr(), false, 0, 12, 5)) + .instr(48, + new BankInstr(new BankOperator(0, 46, 1, 12, 0, 15, 4).ksl(1).vibrato().sustaining(), new BankOperator(1, 63, 1, 5, 5, 15, 4).ksl(2).tremolo().sustaining().ksr(), false, 0, 12, 2, + new BankOperator(0, 45, 1, 9, 0, 15, 4).ksl(1).tremolo().sustaining(), new BankOperator(1, 63, 1, 5, 5, 15, 4).ksl(2).sustaining().ksr(), false, -8, 12, 3)) + .instr(49, + new BankInstr(new BankOperator(0, 46, 1, 9, 0, 15, 5).ksl(1).sustaining(), new BankOperator(1, 63, 1, 3, 5, 15, 5).ksl(2).tremolo().sustaining(), false, 0, 12, 2, + new BankOperator(0, 45, 1, 9, 0, 15, 5).ksl(1).tremolo().sustaining(), new BankOperator(1, 63, 1, 3, 5, 15, 5).ksl(2).sustaining(), false, 5, 12, 3)) + .instr(50, + new BankInstr(new BankOperator(1, 44, 2, 6, 9, 15, 5).ksl(2).tremolo().sustaining(), new BankOperator(1, 63, 2, 6, 6, 15, 2).tremolo().vibrato().sustaining().ksr(), false, 0, 0, 5, + new BankOperator(1, 44, 2, 6, 9, 15, 5).ksl(2).tremolo().sustaining(), new BankOperator(1, 63, 2, 6, 6, 15, 2).tremolo().vibrato().sustaining().ksr(), false, -5, 0, 5)) + .instr(51, + new BankInstr(new BankOperator(1, 50, 0, 1, 1, 15, 3).ksl(1).sustaining(), new BankOperator(0, 63, 1, 3, 1, 15, 4).ksl(2).sustaining(), false, 0, 12, 0, + new BankOperator(0, 61, 2, 1, 1, 12, 3).ksl(2), new BankOperator(0, 63, 1, 3, 1, 12, 6).ksl(2), false, 4, 12, 4)) + .instr(52, + new BankInstr(new BankOperator(0, 41, 0, 9, 0, 10, 4).ksl(1).vibrato().sustaining(), new BankOperator(0, 63, 0, 7, 0, 15, 4).ksl(1).vibrato().sustaining(), false, 0, 12, 0, + new BankOperator(0, 45, 0, 9, 0, 10, 4).ksl(2).sustaining(), new BankOperator(0, 63, 0, 7, 0, 15, 4).ksl(3).vibrato().sustaining(), false, 10, 12, 0)) + .instr(53, new BankInstr(new BankOperator(0, 38, 0, 11, 1, 4, 7).ksl(2).tremolo().sustaining(), new BankOperator(0, 63, 0, 7, 2, 7, 5).ksl(2).tremolo().sustaining(), false, 0, 12, 0)) + .instr(54, + new BankInstr(new BankOperator(0, 37, 0, 6, 0, 10, 4).ksl(1).tremolo().sustaining().ksr(), new BankOperator(0, 63, 0, 3, 0, 8, 4).ksl(2).tremolo().sustaining().ksr(), false, 0, 12, + 0)) + .instr(55, + new BankInstr(new BankOperator(0, 47, 0, 3, 0, 11, 3).ksl(2).ksr(), new BankOperator(0, 63, 0, 6, 4, 14, 4).ksr(), false, 0, -12, 1, + new BankOperator(0, 48, 0, 5, 0, 11, 2).ksl(2).tremolo().ksr(), new BankOperator(1, 63, 0, 5, 4, 11, 5).ksr(), false, 0, 0, 1)) + .instr(56, new BankInstr(new BankOperator(0, 49, 1, 8, 0, 14, 5).ksl(2).sustaining(), new BankOperator(0, 63, 0, 5, 1, 12, 6).sustaining().ksr(), false, 0, 12, 5)) + .instr(57, new BankInstr(new BankOperator(0, 37, 0, 7, 1, 14, 15).tremolo().sustaining().ksr(), new BankOperator(0, 63, 0, 7, 2, 12, 11).ksl(2).sustaining(), false, 0, 12, 7)) + .instr(58, new BankInstr(new BankOperator(0, 41, 0, 5, 0, 11, 6).ksl(2).sustaining(), new BankOperator(0, 63, 1, 9, 2, 10, 6).ksl(2).sustaining(), false, 0, 12, 6)) + .instr(59, new BankInstr(new BankOperator(0, 50, 1, 8, 0, 1, 6).ksl(2).tremolo(), new BankOperator(0, 63, 1, 5, 1, 0, 6).tremolo().ksr(), false, 0, 12, 6)) + .instr(60, + new BankInstr(new BankOperator(0, 29, 0, 7, 0, 4, 8).sustaining(), new BankOperator(0, 63, 0, 6, 1, 6, 6).ksl(2).sustaining(), false, 0, 12, 7, + new BankOperator(0, 28, 0, 7, 0, 4, 8).sustaining(), new BankOperator(0, 63, 0, 6, 1, 6, 6).ksl(2).sustaining(), false, 1, 12, 7)) + .instr(61, + new BankInstr(new BankOperator(0, 49, 1, 6, 0, 14, 5).ksl(2).sustaining(), new BankOperator(0, 63, 0, 5, 1, 12, 6).sustaining().ksr(), false, 0, 12, 5, + new BankOperator(0, 45, 1, 7, 0, 14, 7).ksl(2).sustaining().ksr(), new BankOperator(0, 63, 1, 6, 1, 12, 6).sustaining().ksr(), false, 3, 12, 7)) + .instr(62, + new BankInstr(new BankOperator(0, 50, 2, 9, 1, 5, 6).ksl(1).sustaining(), new BankOperator(0, 63, 1, 8, 1, 6, 7).sustaining(), false, 0, 12, 6, + new BankOperator(0, 51, 2, 9, 1, 5, 6).ksl(2).sustaining(), new BankOperator(0, 63, 1, 9, 1, 6, 7).sustaining(), false, 6, 12, 6)) + .instr(63, + new BankInstr(new BankOperator(0, 47, 2, 8, 1, 5, 6).ksl(1).sustaining().ksr(), new BankOperator(0, 63, 1, 6, 1, 6, 7).sustaining().ksr(), false, 0, 12, 6, + new BankOperator(0, 53, 2, 8, 1, 5, 6).ksl(1).sustaining().ksr(), new BankOperator(0, 63, 1, 6, 1, 6, 7).sustaining().ksr(), false, 6, 12, 5)) + .instr(64, new BankInstr(new BankOperator(0, 41, 0, 6, 0, 15, 5).ksl(2).tremolo().sustaining(), new BankOperator(1, 63, 1, 5, 2, 14, 6).tremolo().sustaining().ksr(), false, 0, 12, 3)) + .instr(65, new BankInstr(new BankOperator(0, 54, 1, 7, 0, 15, 6).ksl(2).tremolo().sustaining(), new BankOperator(0, 63, 1, 6, 2, 14, 6).tremolo().sustaining().ksr(), false, 0, 12, 3)) + .instr(66, + new BankInstr(new BankOperator(0, 53, 0, 9, 8, 15, 11).ksl(1).tremolo().sustaining(), new BankOperator(0, 63, 1, 7, 3, 15, 11).tremolo().sustaining().ksr(), false, 0, 12, 5)) + .instr(67, + new BankInstr(new BankOperator(0, 58, 1, 9, 0, 15, 11).ksl(2).tremolo().sustaining(), new BankOperator(0, 63, 1, 6, 3, 14, 11).tremolo().sustaining().ksr(), false, 0, 12, 5)) + .instr(68, + new BankInstr(new BankOperator(0, 47, 0, 7, 0, 14, 6).ksl(2).vibrato().sustaining().ksr(), new BankOperator(2, 63, 0, 5, 12, 15, 8).ksl(2).tremolo().sustaining(), false, 0, 12, 3)) + .instr(69, new BankInstr(new BankOperator(0, 48, 0, 12, 8, 15, 7).ksl(1).sustaining(), new BankOperator(1, 63, 1, 7, 3, 15, 7).sustaining().ksr(), false, 0, 12, 5)) + .instr(70, new BankInstr(new BankOperator(0, 46, 0, 9, 0, 14, 9).ksl(2).sustaining().ksr(), new BankOperator(1, 63, 0, 6, 1, 14, 11).ksl(2).sustaining().ksr(), false, 0, 12, 5)) + .instr(71, + new BankInstr(new BankOperator(0, 50, 0, 10, 5, 14, 7).ksl(2).sustaining().ksr(), new BankOperator(0, 63, 0, 6, 3, 14, 7).ksl(2).tremolo().sustaining().ksr(), false, 0, 12, 4)) + .instr(72, + new BankInstr(new BankOperator(0, 63, 0, 6, 14, 7, 15).ksl(2).tremolo().vibrato().sustaining().ksr(), new BankOperator(0, 63, 0, 3, 5, 13, 10).vibrato().sustaining().ksr(), false, + 0, 12, 7)) + .instr(73, new BankInstr(new BankOperator(0, 44, 0, 5, 0, 7, 8).ksl(2).tremolo().sustaining(), new BankOperator(0, 63, 0, 5, 5, 13, 10).ksl(2).vibrato().sustaining(), false, 0, 12, 4)) + .instr(74, new BankInstr(new BankOperator(0, 53, 0, 6, 5, 14, 7).sustaining(), new BankOperator(0, 63, 0, 7, 4, 13, 7).tremolo().sustaining(), true, 0, 12, 5)) + .instr(75, + new BankInstr(new BankOperator(0, 59, 1, 2, 4, 13, 7).ksl(2).tremolo().sustaining().ksr(), new BankOperator(0, 63, 0, 4, 5, 14, 7).tremolo().sustaining().ksr(), true, 0, 12, 4)) + .instr(76, + new BankInstr(new BankOperator(1, 18, 0, 5, 7, 15, 4).ksl(2).tremolo().vibrato().sustaining(), new BankOperator(0, 63, 0, 5, 7, 12, 7).vibrato().sustaining(), false, 0, 12, 7)) + .instr(77, + new BankInstr(new BankOperator(1, 23, 3, 5, 7, 12, 4).tremolo().vibrato().sustaining().ksr(), new BankOperator(1, 63, 0, 6, 7, 10, 13).tremolo().vibrato().sustaining(), false, 0, + 0, 7)) + .instr(78, + new BankInstr(new BankOperator(0, 56, 0, 3, 1, 15, 15).ksl(3).tremolo().vibrato().ksr(), new BankOperator(0, 63, 0, 3, 2, 15, 5).vibrato().sustaining().ksr(), true, 0, 12, 5)) + .instr(79, new BankInstr(new BankOperator(0, 56, 0, 5, 1, 15, 5).ksl(3).tremolo().sustaining().ksr(), new BankOperator(0, 63, 0, 4, 2, 13, 9).sustaining().ksr(), true, 0, 12, 5)) + .instr(80, + new BankInstr(new BankOperator(2, 45, 1, 5, 1, 10, 11).ksl(1).sustaining(), new BankOperator(0, 63, 1, 6, 0, 13, 5).sustaining().ksr(), false, 0, 12, 0, + new BankOperator(2, 50, 1, 9, 1, 10, 11).ksl(1).sustaining(), new BankOperator(0, 63, 1, 15, 0, 13, 5).sustaining().ksr(), false, 2, 12, 0)) + .instr(81, + new BankInstr(new BankOperator(0, 60, 1, 12, 1, 6, 11).ksl(1).sustaining(), new BankOperator(1, 63, 1, 12, 0, 9, 5).sustaining().ksr(), false, 0, 12, 4, + new BankOperator(0, 62, 1, 11, 1, 5, 11).ksl(1).vibrato().sustaining(), new BankOperator(1, 63, 0, 15, 1, 15, 5).sustaining().ksr(), false, -1, 12, 4)) + .instr(82, + new BankInstr(new BankOperator(0, 23, 3, 5, 7, 12, 3).tremolo().vibrato().sustaining().ksr(), new BankOperator(0, 63, 0, 6, 7, 15, 7).tremolo().vibrato().sustaining(), false, 0, + 12, 7)) + .instr(83, + new BankInstr(new BankOperator(0, 28, 3, 5, 7, 15, 4).tremolo().vibrato().sustaining(), new BankOperator(0, 63, 0, 6, 7, 11, 13).tremolo().vibrato().sustaining(), false, 0, 12, 7, + new BankOperator(0, 28, 3, 15, 7, 15, 4).tremolo().vibrato().sustaining(), new BankOperator(0, 63, 0, 8, 7, 11, 13).tremolo().vibrato().sustaining(), false, 2, 12, 7)) + .instr(84, new BankInstr(new BankOperator(1, 61, 1, 7, 8, 15, 11).ksl(1).tremolo().sustaining(), new BankOperator(0, 63, 1, 15, 1, 13, 11).sustaining().ksr(), false, 0, 12, 4)) + .instr(85, + new BankInstr(new BankOperator(0, 30, 0, 8, 0, 10, 5).vibrato().sustaining(), new BankOperator(0, 63, 0, 15, 2, 14, 4).tremolo().vibrato().sustaining(), false, 0, 12, 4, + new BankOperator(0, 30, 0, 9, 0, 10, 5).sustaining(), new BankOperator(0, 63, 0, 10, 2, 14, 4).tremolo().sustaining(), false, -6, 12, 4)) + .instr(86, + new BankInstr(new BankOperator(0, 60, 1, 12, 1, 6, 5).ksl(1).sustaining(), new BankOperator(0, 63, 1, 7, 0, 9, 3).ksl(2).tremolo().sustaining().ksr(), false, 0, 12, 5, + new BankOperator(0, 54, 1, 9, 1, 6, 5).ksl(1).tremolo().sustaining(), new BankOperator(1, 63, 1, 6, 1, 9, 3).sustaining().ksr(), false, -3, 7, 5)) + .instr(87, new BankInstr(new BankOperator(4, 63, 1, 5, 1, 15, 7).ksl(1).sustaining(), new BankOperator(0, 63, 2, 15, 13, 13, 9).tremolo().sustaining(), true, 0, 0, 4)) + .instr(88, new BankInstr(new BankOperator(4, 63, 1, 5, 1, 15, 7).ksl(1).sustaining(), new BankOperator(0, 63, 2, 15, 13, 13, 9).tremolo().sustaining(), true, 0, 0, 4)) + .instr(89, + new BankInstr(new BankOperator(0, 63, 0, 3, 2, 15, 5).ksl(3).tremolo(), new BankOperator(0, 63, 0, 3, 3, 15, 5).vibrato().sustaining(), true, 0, 12, 4, + new BankOperator(0, 63, 0, 3, 2, 15, 5).ksl(1).vibrato(), new BankOperator(0, 63, 0, 3, 3, 15, 5).tremolo().vibrato().sustaining(), true, 2, 12, 4)) + .instr(90, + new BankInstr(new BankOperator(0, 48, 2, 10, 1, 5, 5).ksl(2).tremolo().sustaining(), new BankOperator(0, 63, 1, 10, 1, 6, 6).tremolo().sustaining(), false, 0, 12, 6, + new BankOperator(0, 48, 2, 10, 1, 5, 5).ksl(2).tremolo().sustaining(), new BankOperator(0, 63, 1, 10, 1, 6, 6).tremolo().sustaining(), false, 2, 12, 6)) + .instr(91, + new BankInstr(new BankOperator(0, 59, 0, 15, 0, 15, 5).ksl(1).tremolo().vibrato().sustaining(), new BankOperator(0, 63, 0, 8, 1, 10, 4).vibrato().sustaining(), true, 0, 12, 0, + new BankOperator(0, 59, 1, 15, 0, 15, 5).ksl(1).tremolo().vibrato().sustaining(), new BankOperator(0, 63, 0, 7, 1, 10, 4).ksl(2).vibrato().sustaining(), true, 11, 12, 0)) + .instr(92, new BankInstr(new BankOperator(0, 53, 0, 10, 1, 12, 3).ksl(2).tremolo(), new BankOperator(0, 63, 0, 5, 2, 10, 4).tremolo().vibrato().sustaining(), true, 0, 12, 3)) + .instr(93, new BankInstr(new BankOperator(1, 34, 1, 8, 0, 10, 2).ksl(2).tremolo(), new BankOperator(0, 63, 1, 2, 3, 10, 3).ksl(2).vibrato(), false, 0, 12, 7)) + .instr(94, + new BankInstr(new BankOperator(1, 50, 1, 5, 1, 11, 5).ksl(1).tremolo().vibrato().sustaining(), new BankOperator(0, 63, 1, 9, 1, 11, 6).tremolo().sustaining(), false, 0, 12, 0, + new BankOperator(1, 50, 1, 5, 1, 11, 5).ksl(1).tremolo().sustaining(), new BankOperator(0, 63, 1, 8, 1, 11, 6).tremolo().sustaining(), false, -2, 12, 0)) + .instr(95, + new BankInstr(new BankOperator(1, 51, 1, 1, 1, 10, 2).ksl(2).tremolo().vibrato().sustaining(), new BankOperator(0, 63, 1, 8, 0, 8, 3).ksl(2).tremolo().vibrato().sustaining(), + false, 0, 12, 4)) + .instr(96, new BankInstr(new BankOperator(0, 63, 0, 7, 2, 11, 7).ksl(1).sustaining(), new BankOperator(3, 63, 0, 15, 8, 14, 9).tremolo(), true, 0, 12, 5)) + .instr(97, + new BankInstr(new BankOperator(0, 45, 1, 8, 5, 15, 2).ksl(3), new BankOperator(1, 63, 1, 4, 5, 14, 2).tremolo().vibrato(), false, 0, 0, 5, + new BankOperator(2, 45, 0, 4, 5, 15, 3).ksl(3).sustaining(), new BankOperator(3, 63, 2, 3, 5, 12, 5).tremolo().vibrato().sustaining(), false, 8, 7, 5)) + .instr(98, new BankInstr(new BankOperator(4, 63, 0, 15, 6, 8, 4).ksl(3), new BankOperator(2, 63, 0, 10, 3, 13, 4), false, 0, -12, 0)) + .instr(99, + new BankInstr(new BankOperator(0, 49, 0, 12, 0, 2, 2).ksl(2).tremolo().ksr(), new BankOperator(0, 63, 0, 13, 1, 2, 2).sustaining().ksr(), false, 0, 12, 0, + new BankOperator(0, 49, 0, 13, 0, 2, 2).ksl(2).tremolo().ksr(), new BankOperator(0, 63, 0, 15, 1, 2, 2).sustaining().ksr(), false, -2, 12, 0)) + .instr(100, + new BankInstr(new BankOperator(0, 45, 0, 9, 0, 0, 3).tremolo().vibrato().ksr(), new BankOperator(0, 63, 0, 12, 2, 0, 3).tremolo().vibrato(), false, 0, 12, 0, + new BankOperator(0, 45, 0, 9, 0, 0, 3).tremolo().vibrato().ksr(), new BankOperator(0, 63, 0, 12, 2, 0, 2).ksl(2).tremolo().vibrato(), false, -12, 12, 0)) + .instr(101, + new BankInstr(new BankOperator(0, 37, 1, 1, 3, 10, 2).tremolo().vibrato().sustaining(), new BankOperator(1, 63, 2, 3, 3, 14, 3).ksl(2).tremolo().vibrato().sustaining().ksr(), + false, 0, 0, 0)) + .instr(102, + new BankInstr(new BankOperator(0, 37, 0, 4, 5, 4, 10).tremolo().vibrato().sustaining(), new BankOperator(0, 63, 1, 3, 2, 6, 1).tremolo().vibrato().sustaining().ksr(), false, 0, 12, + 0)) + .instr(103, new BankInstr(new BankOperator(0, 39, 1, 5, 8, 15, 2).ksr(), new BankOperator(2, 63, 0, 4, 2, 8, 2), false, 0, 12, 5)) + .instr(104, new BankInstr(new BankOperator(0, 55, 0, 6, 3, 4, 3).sustaining(), new BankOperator(4, 63, 0, 6, 3, 4, 3).sustaining(), false, 0, 12, 1)) + .instr(105, new BankInstr(new BankOperator(0, 50, 0, 7, 7, 14, 2).sustaining().ksr(), new BankOperator(0, 63, 1, 15, 3, 0, 4).ksr(), false, 0, 12, 2)) + .instr(106, + new BankInstr(new BankOperator(0, 63, 0, 15, 9, 12, 3).ksl(2), new BankOperator(0, 63, 2, 15, 4, 8, 3).ksl(2), false, 0, 12, 0, new BankOperator(7, 37, 2, 15, 9, 5, 12), + new BankOperator(15, 63, 2, 15, 9, 13, 9), false, 0, 12, 0)) + .instr(107, new BankInstr(new BankOperator(0, 30, 1, 15, 2, 10, 3).sustaining(), new BankOperator(2, 63, 0, 9, 1, 1, 4).sustaining(), false, 0, 12, 4)) + .instr(108, new BankInstr(new BankOperator(3, 48, 3, 15, 1, 12, 9).ksl(1), new BankOperator(5, 63, 0, 13, 6, 8, 4).ksr(), false, 0, 0, 3)) + .instr(109, new BankInstr(new BankOperator(0, 61, 1, 8, 9, 14, 5).ksl(1).sustaining().ksr(), new BankOperator(1, 63, 2, 6, 11, 15, 7).sustaining(), false, 0, 12, 5)) + .instr(110, new BankInstr(new BankOperator(0, 32, 0, 10, 1, 15, 3).sustaining().ksr(), new BankOperator(1, 63, 0, 5, 2, 13, 6).sustaining(), false, 0, 12, 7)) + .instr(111, new BankInstr(new BankOperator(0, 44, 0, 4, 0, 14, 3).sustaining().ksr(), new BankOperator(0, 63, 1, 6, 1, 14, 6).ksl(2).sustaining().ksr(), false, 0, 12, 4)) + .instr(112, new BankInstr(new BankOperator(3, 63, 0, 10, 1, 12, 2).ksr(), new BankOperator(2, 63, 1, 11, 2, 8, 2).ksl(2).ksr(), true, 0, 5, 0)) + .instr(113, new BankInstr(new BankOperator(5, 62, 0, 14, 7, 15, 1).ksl(2).tremolo().ksr(), new BankOperator(6, 63, 0, 9, 6, 9, 7).ksr(), false, 0, 12, 2)) + .instr(114, new BankInstr(new BankOperator(3, 54, 1, 15, 0, 15, 4).ksl(1), new BankOperator(0, 63, 0, 8, 2, 15, 5).sustaining(), false, 0, 12, 3)) + .instr(115, new BankInstr(new BankOperator(3, 59, 0, 15, 8, 2, 1).ksl(1).ksr(), new BankOperator(2, 63, 0, 15, 5, 8, 8).ksr(), false, 0, 12, 3)) + .instr(116, new BankInstr(new BankOperator(0, 52, 0, 10, 7, 1, 12).ksr(), new BankOperator(0, 63, 0, 13, 5, 0, 5).ksr(), false, 0, 0, 0)) + .instr(117, new BankInstr(new BankOperator(0, 52, 0, 10, 8, 3, 8).sustaining(), new BankOperator(1, 63, 0, 13, 6, 4, 7), false, 0, 0, 0)) + .instr(118, new BankInstr(new BankOperator(0, 52, 0, 15, 8, 3, 4), new BankOperator(0, 63, 0, 13, 3, 4, 7), false, 0, 12, 0)) + .instr(119, new BankInstr(new BankOperator(12, 48, 0, 4, 1, 12, 1).ksl(2), new BankOperator(0, 63, 3, 2, 1, 14, 13).ksl(2).ksr(), false, 0, 0, 7)) + .instr(120, new BankInstr(new BankOperator(2, 30, 1, 3, 4, 4, 3).sustaining().ksr(), new BankOperator(1, 63, 3, 5, 4, 0, 7).sustaining().ksr(), false, 0, 12, 7)) + .instr(121, new BankInstr(new BankOperator(1, 18, 0, 3, 7, 15, 4).ksl(2).tremolo().vibrato().ksr(), new BankOperator(0, 63, 0, 3, 7, 12, 4).vibrato().ksr(), false, 0, 12, 7)) + .instr(122, new BankInstr(new BankOperator(2, 63, 2, 3, 14, 15, 1), new BankOperator(8, 63, 2, 1, 4, 0, 3), false, 0, 0, 7)) + .instr(123, + new BankInstr(new BankOperator(5, 43, 0, 14, 11, 15, 3).ksl(3).tremolo().vibrato().sustaining().ksr(), + new BankOperator(6, 63, 0, 4, 5, 9, 8).tremolo().vibrato().sustaining().ksr(), true, 0, 0, 3)) + .instr(124, + new BankInstr(new BankOperator(0, 63, 1, 13, 10, 8, 1).tremolo().vibrato().sustaining().ksr(), new BankOperator(10, 63, 1, 11, 0, 14, 7).ksl(3).tremolo().vibrato(), false, 0, 0, + 4)) + .instr(125, + new BankInstr(new BankOperator(0, 63, 1, 1, 14, 14, 1).tremolo().vibrato().sustaining().ksr(), new BankOperator(2, 63, 1, 2, 1, 14, 1).ksl(3).tremolo().vibrato().sustaining(), + false, 0, -12, 4).setFixed(17)) + .instr(126, + new BankInstr(new BankOperator(15, 57, 2, 5, 3, 15, 0).ksl(2).tremolo().vibrato().sustaining(), new BankOperator(15, 63, 3, 1, 0, 15, 2).ksl(3).tremolo().vibrato().sustaining(), + false, 0, 12, 7).setFixed(65)) + .instr(127, new BankInstr(new BankOperator(12, 63, 2, 15, 0, 0, 0), new BankOperator(4, 63, 0, 15, 6, 1, 6), false, 0, 0, 7)) + .perc(35, new BankInstr(new BankOperator(0, 63, 2, 15, 9, 10, 7), new BankOperator(0, 63, 0, 15, 11, 11, 6), false, 0, 12, 0).setFixed(38)) + .perc(36, new BankInstr(new BankOperator(0, 63, 0, 15, 10, 11, 7), new BankOperator(0, 63, 0, 15, 9, 15, 6), false, 0, 12, 3).setFixed(25)) + .perc(37, new BankInstr(new BankOperator(2, 63, 0, 15, 13, 9, 7).ksl(2), new BankOperator(3, 63, 0, 15, 7, 8, 8), false, 0, 12, 3).setFixed(83)) + .perc(38, new BankInstr(new BankOperator(15, 58, 2, 15, 7, 14, 4), new BankOperator(0, 63, 2, 15, 9, 11, 7), false, 0, 12, 7).setFixed(32)) + .perc(39, + new BankInstr(new BankOperator(1, 63, 3, 8, 8, 0, 11).tremolo().vibrato().sustaining(), new BankOperator(15, 63, 2, 10, 6, 5, 8).tremolo().vibrato().sustaining().ksr(), true, 0, + 12, 7).setFixed(60)) + .perc(40, + new BankInstr(new BankOperator(1, 63, 0, 15, 12, 8, 5), new BankOperator(0, 63, 0, 15, 8, 12, 7), false, 0, 12, 0, new BankOperator(1, 63, 1, 15, 7, 8, 5).ksl(3), + new BankOperator(0, 63, 3, 15, 12, 12, 6), false, 0, 54, 6).setFixed(36)) + .perc(41, new BankInstr(new BankOperator(2, 63, 0, 15, 5, 9, 12), new BankOperator(3, 63, 1, 15, 7, 12, 8), true, 0, 12, 3).setFixed(15)) + .perc(42, new BankInstr(new BankOperator(12, 63, 2, 9, 8, 10, 14), new BankOperator(15, 63, 3, 15, 11, 15, 6), true, 0, 12, 7).setFixed(88)) + .perc(43, new BankInstr(new BankOperator(2, 63, 0, 15, 5, 8, 8), new BankOperator(0, 63, 1, 15, 7, 12, 7), true, 0, 12, 3).setFixed(19)) + .perc(44, new BankInstr(new BankOperator(12, 63, 2, 7, 8, 10, 14), new BankOperator(10, 63, 3, 8, 10, 13, 11).ksl(2), true, 0, 12, 7).setFixed(88)) + .perc(45, new BankInstr(new BankOperator(2, 63, 0, 15, 5, 12, 7), new BankOperator(2, 63, 1, 15, 7, 12, 7), true, 0, 12, 1).setFixed(21)) + .perc(46, new BankInstr(new BankOperator(0, 58, 2, 12, 7, 15, 1).ksl(1), new BankOperator(11, 63, 2, 15, 9, 12, 3), false, 0, 12, 7).setFixed(79)) + .perc(47, new BankInstr(new BankOperator(2, 63, 0, 15, 5, 12, 7), new BankOperator(2, 63, 1, 15, 7, 12, 7), true, 0, 12, 1).setFixed(26)) + .perc(48, new BankInstr(new BankOperator(2, 63, 0, 15, 5, 12, 7), new BankOperator(2, 63, 1, 15, 7, 12, 7), true, 0, 12, 1).setFixed(28)) + .perc(49, new BankInstr(new BankOperator(4, 47, 0, 12, 2, 1, 6), new BankOperator(0, 63, 3, 14, 8, 11, 3), false, 0, 12, 7).setFixed(60)) + .perc(50, new BankInstr(new BankOperator(2, 63, 0, 15, 5, 12, 7), new BankOperator(2, 63, 1, 15, 7, 12, 7), true, 0, 12, 1).setFixed(32)) + .perc(51, new BankInstr(new BankOperator(3, 63, 2, 15, 13, 14, 2).ksl(2), new BankOperator(2, 63, 2, 15, 13, 15, 5).ksl(2), false, 0, 12, 5).setFixed(60)) + .perc(52, new BankInstr(new BankOperator(0, 63, 0, 14, 4, 7, 5).ksl(2), new BankOperator(0, 63, 2, 13, 7, 12, 4).ksl(2).tremolo().vibrato(), false, 0, 12, 7).setFixed(96)) + .perc(53, new BankInstr(new BankOperator(4, 47, 0, 14, 2, 1, 6).ksl(2), new BankOperator(1, 63, 1, 11, 8, 11, 4), false, 0, 12, 7).setFixed(72)) + .perc(54, new BankInstr(new BankOperator(2, 56, 2, 7, 6, 8, 7).ksl(2), new BankOperator(1, 63, 3, 9, 8, 9, 7), true, 0, 12, 7).setFixed(79)) + .perc(55, new BankInstr(new BankOperator(4, 62, 2, 9, 4, 8, 0).ksl(2), new BankOperator(7, 63, 3, 12, 6, 5, 3), false, 0, 12, 7).setFixed(69)) + .perc(56, new BankInstr(new BankOperator(0, 63, 0, 15, 13, 9, 7), new BankOperator(1, 63, 0, 15, 6, 6, 8), false, 0, 12, 3).setFixed(71)) + .perc(57, new BankInstr(new BankOperator(4, 47, 0, 12, 2, 1, 6), new BankOperator(0, 63, 3, 14, 8, 11, 3), false, 0, 12, 7).setFixed(60)) + .perc(58, new BankInstr(new BankOperator(1, 56, 0, 15, 9, 4, 5), new BankOperator(15, 63, 0, 13, 4, 10, 0).ksl(3).tremolo().sustaining().ksr(), true, 0, 12, 5).setFixed()) + .perc(59, new BankInstr(new BankOperator(3, 63, 2, 15, 13, 14, 2).ksl(2), new BankOperator(2, 63, 2, 15, 13, 15, 5).ksl(2), false, 0, 12, 5).setFixed(60)) + .perc(60, new BankInstr(new BankOperator(0, 63, 2, 15, 11, 10, 6), new BankOperator(0, 63, 0, 15, 10, 13, 6), false, 0, 12, 2).setFixed(60)) + .perc(61, new BankInstr(new BankOperator(0, 63, 2, 15, 11, 10, 6), new BankOperator(0, 63, 0, 15, 10, 13, 6), false, 0, 12, 2).setFixed(54)) + .perc(62, new BankInstr(new BankOperator(0, 63, 2, 15, 11, 10, 6).ksl(2), new BankOperator(0, 63, 0, 15, 7, 14, 7), false, 0, 12, 0).setFixed(72)) + .perc(63, new BankInstr(new BankOperator(0, 63, 2, 15, 11, 10, 6).ksl(2), new BankOperator(0, 63, 0, 15, 7, 14, 7), false, 0, 12, 0).setFixed(67)) + .perc(64, new BankInstr(new BankOperator(0, 63, 2, 15, 11, 10, 6).ksl(2), new BankOperator(0, 63, 0, 15, 7, 14, 7), false, 0, 12, 0).setFixed(60)) + .perc(65, new BankInstr(new BankOperator(3, 62, 0, 15, 11, 10, 6).ksl(2), new BankOperator(0, 63, 0, 15, 7, 14, 7), false, 0, 12, 0).setFixed(55)) + .perc(66, new BankInstr(new BankOperator(3, 62, 0, 15, 11, 10, 6).ksl(2), new BankOperator(0, 63, 0, 15, 7, 14, 7), false, 0, 12, 0).setFixed(48)) + .perc(67, new BankInstr(new BankOperator(1, 63, 3, 15, 13, 9, 7), new BankOperator(1, 63, 0, 15, 6, 6, 8), false, 0, 12, 4).setFixed(77)) + .perc(68, new BankInstr(new BankOperator(1, 63, 3, 15, 13, 9, 7), new BankOperator(1, 63, 0, 15, 6, 6, 8), false, 0, 12, 4).setFixed(72)) + .perc(69, new BankInstr(new BankOperator(12, 63, 2, 7, 8, 10, 14), new BankOperator(10, 63, 3, 8, 10, 13, 11).ksl(2), true, 0, 12, 7).setFixed(88)) + .perc(70, new BankInstr(new BankOperator(0, 49, 2, 5, 10, 2, 6), new BankOperator(15, 63, 0, 15, 15, 0, 15).ksl(3).tremolo().sustaining().ksr(), false, 0, 12, 5).setFixed()) + .perc(71, new BankInstr(new BankOperator(0, 56, 1, 15, 9, 3, 7), new BankOperator(0, 63, 0, 15, 15, 0, 15).ksl(3).tremolo(), false, 0, 12, 5).setFixed(49)) + .perc(72, new BankInstr(new BankOperator(0, 56, 1, 15, 9, 3, 7), new BankOperator(0, 63, 0, 15, 15, 0, 15).ksl(3).tremolo(), false, 0, 12, 5).setFixed(49)) + .perc(73, new BankInstr(new BankOperator(0, 56, 1, 15, 9, 3, 7), new BankOperator(0, 63, 0, 15, 15, 0, 15).ksl(3).tremolo(), false, 0, 12, 5).setFixed(49)) + .perc(74, new BankInstr(new BankOperator(0, 56, 1, 15, 9, 3, 7), new BankOperator(0, 63, 0, 15, 15, 0, 15).ksl(3).tremolo(), false, 0, 12, 5).setFixed(49)) + .perc(75, new BankInstr(new BankOperator(3, 59, 1, 15, 8, 2, 1).ksl(1).ksr(), new BankOperator(2, 63, 0, 15, 5, 8, 8).ksr(), false, 0, 12, 3).setFixed(73)) + .perc(76, new BankInstr(new BankOperator(3, 59, 1, 15, 8, 2, 1).ksl(1).ksr(), new BankOperator(2, 63, 0, 15, 5, 8, 8).ksr(), false, 0, 12, 3).setFixed(68)) + .perc(77, new BankInstr(new BankOperator(3, 59, 1, 15, 8, 2, 1).ksl(1).ksr(), new BankOperator(2, 63, 0, 15, 5, 8, 8).ksr(), false, 0, 12, 3).setFixed(61)) + .perc(78, new BankInstr(new BankOperator(1, 52, 1, 5, 14, 2, 12), new BankOperator(15, 63, 0, 15, 15, 0, 15).ksl(3).tremolo().sustaining().ksr(), false, 0, 12, 5).setFixed()) + .perc(79, new BankInstr(new BankOperator(0, 56, 1, 15, 9, 3, 7), new BankOperator(0, 63, 0, 15, 15, 0, 15).ksl(3).tremolo(), false, 0, 12, 5).setFixed(49)) + .perc(80, + new BankInstr(new BankOperator(5, 48, 0, 15, 2, 9, 0).ksl(1).tremolo().vibrato(), new BankOperator(4, 63, 0, 15, 4, 8, 10).ksl(2).tremolo().vibrato().ksr(), false, 0, 12, 4) + .setFixed(90)) + .perc(81, new BankInstr(new BankOperator(5, 48, 1, 15, 2, 9, 0).ksl(1).tremolo(), new BankOperator(4, 63, 0, 15, 2, 4, 7).ksl(2).tremolo().ksr(), false, 0, 12, 4).setFixed(90)); + } + + private MidiBank(String name, String ext) { + this.name = name; + Arrays.fill(this.data, new BankInstr()); + } + + private MidiBank instr(int id, BankInstr instr) { + this.data[id] = instr; + return this; + } + + private MidiBank perc(int id, BankInstr instr) { + this.data[128 + id] = instr; + return this; + } + + public String toString() { + return this.name; + } + + public String getName() { + return this.name; + } + + public String getDisplay() { + return this.name; + } + + public BankInstr[] getData() { + return this.data; + } + } + + public static class MidiTrack { + byte[] buffer; + int size; + int pos; + byte status; + boolean ending; + int wait; + int trknum; + + public int readInt() { + int value = 0; + for(int h = 0; h < 4; h++) { + if(this.pos >= this.size) { + break; + } + value |= ((int)(this.buffer[this.pos++] & 255)); + value <<= h < 3 ? 8 : 0; + } + return value; + } + + public int readInt24() { + int value = 0; + for(int h = 0; h < 3; h++) { + if(this.pos >= this.size) { + break; + } + value |= ((int)(this.buffer[this.pos++] & 255)); + value <<= h < 2 ? 8 : 0; + } + return value; + } + + public short readShort() { + short value = 0; + for(int h = 0; h < 2; h++) { + if(this.pos >= this.size) { + break; + } + value |= ((short)(this.buffer[this.pos++] & 255)); + value <<= h < 1 ? 8 : 0; + } + return value; + } + + public byte readByte() { + byte value = 0; + if(this.pos < this.size) { + value = this.buffer[this.pos++]; + } + return value; + } + + public void readData(int len, byte[] chars) { + Arrays.fill(chars, (byte)0); + for(int h = 0; h < len; h++) { + if(this.pos >= this.size) { + break; + } + chars[h] = this.buffer[this.pos++]; + } + } + + public int readVarLen() { + int value; + byte bt; + + if(this.pos >= this.size) { + return 0; + } + if(((value = (this.buffer[this.pos++] & 255)) & 0x80) != 0) + { + value &= 0x7f; + do { + if(this.pos >= this.size) { + break; + } + value = (value << 7) + ((bt = this.buffer[this.pos++]) & 0x7f); + } + while((bt & 0x80) != 0); + } + return value; + } + } + + public static class MidiHandle { + private static final byte midev_noteoff = (byte)0x80, + midev_noteon = (byte)0x90, + midev_aftertouch = (byte)0xa0, + midev_control = (byte)0xb0, + midev_progchg = (byte)0xc0, + midev_chnpressure = (byte)0xd0, + midev_pitchbend = (byte)0xe0, + midev_sysex = (byte)0xf0, + midev_songpos = (byte)0xf2, + midev_songsel = (byte)0xf3, + midev_tunereq = (byte)0xf6, + midev_endsysex = (byte)0xf7, + midev_clock = (byte)0xf8, + midev_start = (byte)0xfa, + midev_continue = (byte)0xfb, + midev_stop = (byte)0xfc, + midev_actsense = (byte)0xfe, + midev_meta = (byte)0xff; + + private static final byte midmt_seqnum = 0x00, // nn nn + midmt_text = 0x01, // text... + midmt_copyright = 0x02, // text... + midmt_trackname = 0x03, // text... + midmt_instrname = 0x04, // text... + midmt_lyric = 0x05, // text... + midmt_marker = 0x06, // text... + midmt_cuepoint = 0x07, // text... + midmt_chnprefix = 0x20, // cc + midmt_endtrack = 0x2f, // + midmt_tempo = 0x51, // tt tt tt + midmt_smpte = 0x54, // hr mn se fr ff + midmt_timesig = 0x58, // nn dd cc bb + midmt_keysig = 0x59, // sf mi + midmt_seqspec = 0x7f; // data... + + private static final byte[] mid_hdr = "MThd".getBytes(); + private static final byte[] mid_trk = "MTrk".getBytes(); + + public final MidiTrack[] tracks; + public final int tpqn; + public final boolean nowait; + public final OPLChip chip; + public final BankHandle bank; + + public int uspb; + public int ticktime; + public long time; + public int tick; + public boolean playing; + public boolean debug; + + private static int readInt(byte[] data, int offset) { + int value = 0; + for(int h = 0; h < 4; h++) { + value |= ((int)(data[offset + h] & 255)); + value <<= h < 3 ? 8 : 0; + } + return value; + } + + private static short readShort(byte[] data, int offset) { + short value = 0; + for(int h = 0; h < 2; h++) { + value |= ((short)(data[offset + h] & 255)); + value <<= h < 1 ? 8 : 0; + } + return value; + } + + public MidiHandle(byte[] data, boolean nowait, int voices, MidiBank bank, boolean keep, boolean useunkn, int velofunc) throws IOException { + int size = data.length; + if(size < 14) { + throw new IOException("Datei zu klein"); + } + if(!Arrays.equals(data, 0, 4, mid_hdr, 0, 4)) { + throw new IOException("Fehlerhafter Dateiheader oder falscher Dateityp"); + } + if(readInt(data, 4) != 6) { + throw new IOException("Dateiheader mit falscher Länge"); + } + boolean mtrack; + short type = readShort(data, 8); + if(type == 0) { + mtrack = false; + } + else if((type == 1) || (type == 2)) { + mtrack = true; + } + else { + throw new IOException("Fehlerhaftes Spurformat"); + } + MidiTrack[] tracks = new MidiTrack[readShort(data, 10)]; + if(tracks.length == 0) { + throw new IOException("Keine Spuren definiert"); + } + else if(!mtrack && tracks.length > 1) { + throw new IOException("Einzelspur-Format mit mehreren Spuren"); + } + int tpqn = readShort(data, 12); + if((tpqn & 0x8000) > 0) { + throw new IOException("SMPTE-Zeitformat nicht unterstuetzt"); + } + if(tpqn == 0) { + throw new IOException("Zeitformat teilt durch Null"); + } + int pos = 14; + for(short trk = 0; trk < tracks.length; trk++) { + if(pos + 8 > size) { + throw new IOException("Ende der Datei erreicht"); + } + if(!Arrays.equals(data, pos, pos + 4, mid_trk, 0, 4)) { + throw new IOException("Fehlerhafter Spurheader"); + } + int trks = readInt(data, pos + 4); + pos += 8; + if((pos + trks) > size) { + throw new IOException("Spur #" + (trk + 1) + " außerhalb der Datei"); + } + MidiTrack track = tracks[trk] = new MidiTrack(); + track.buffer = new byte[trks]; + track.trknum = trk; + track.size = trks; + System.arraycopy(data, pos, track.buffer, 0, trks); + pos += trks; + } + this.tracks = tracks; + this.tpqn = tpqn; + this.nowait = nowait; + this.setTempo(500000); + this.chip = new OPLChip(48000, voices, 0, false, false); + this.bank = new BankHandle(this.chip, bank.getData(), keep, useunkn, (byte)velofunc); + this.playing = true; + } + + private void setTempo(int tempo) { + this.uspb = tempo; + this.ticktime = this.uspb / this.tpqn; + } + + private void log(String fmt, Object ... args) { + + } + + private void debug(String fmt, Object ... args) { + if(this.debug) + this.log(fmt, args); + } + + private void processMeta(MidiTrack trk) { + byte meta = trk.readByte(); + int size = trk.readVarLen(); + switch(meta) { + case midmt_seqnum: + if(size == 0) { + this.debug("MIDI Spur #%d Sequenz-Nummer = %d (eigene)", trk.trknum, trk.trknum); + return; + } + else if(size != 2) { + trk.pos += size; + this.log("MIDI Spur #%d Meta-Event 0x%02x - erwartet %d, hat %d", trk.trknum, meta, 2, size); + return; + } + this.debug("MIDI Spur #%d Sequenz-Nummer = %d", trk.trknum, trk.readShort()); + return; + case midmt_text: + case midmt_copyright: + case midmt_trackname: + case midmt_instrname: + case midmt_lyric: + case midmt_marker: + case midmt_cuepoint: { + byte[] dt = new byte[size+1]; + dt[size] = 0; + trk.readData(size, dt); + this.debug("MIDI Text (%d): %s", meta, new String(dt, FileUtils.UTF_8)); + return; + } + case midmt_chnprefix: + if(size != 1) { + trk.pos += size; + this.log("MIDI Spur #%d Meta-Event 0x%02x - erwartet %d, hat %d", trk.trknum, meta, 1, size); + return; + } + this.debug("MIDI Spur #%d Kanal-Präfix = %d", trk.trknum, trk.readByte()); + return; + case midmt_endtrack: + trk.pos += size; + trk.ending = true; + this.debug("MIDI Spur #%d Ende erreicht", trk.trknum); + return; + case midmt_tempo: + if(size != 3) { + trk.pos += size; + this.log("MIDI Spur #%d Meta-Event 0x%02x - erwartet %d, hat %d", trk.trknum, meta, 3, size); + return; + } + this.setTempo(trk.readInt24()); + this.debug("MIDI Tempo = %d", 60000000 / this.uspb); + return; + case midmt_smpte: + if(size != 5) { + trk.pos += size; + this.log("MIDI Spur #%d Meta-Event 0x%02x - erwartet %d, hat %d", trk.trknum, meta, 5, size); + return; + } + trk.pos += 5; + this.debug("MIDI SMPTE-Event (nicht unterstützt)"); + return; + case midmt_timesig: { + if(size != 4) { + trk.pos += size; + this.log("MIDI Spur #%d Meta-Event 0x%02x - erwartet %d, hat %d", trk.trknum, meta, 4, size); + return; + } + byte n = trk.readByte(); + int d = 1 << (int)trk.readByte(); + byte c = trk.readByte(); + byte b = trk.readByte(); + this.debug("MIDI Takt-Signatur = %d/%d - %d CPC - %d 32PQ", n, d, c, b); + return; + } + case midmt_keysig: { + if(size != 2) { + trk.pos += size; + this.log("MIDI Spur #%d Meta-Event 0x%02x - erwartet %d, hat %d", trk.trknum, meta, 2, size); + return; + } + byte s = trk.readByte(); + byte m = trk.readByte(); + this.debug("MIDI Noten-Signatur = %d - %s", s, (m == 0) ? "MAJOR" : ((m == 1) ? "MINOR" : "- ? -")); + return; + } + case midmt_seqspec: { + trk.pos += size; + this.debug("MIDI Spur #%d Sequenzer-Daten empfangen - %d Bytes", trk.trknum, size); + return; + } + default: + trk.pos += size; + this.debug("MIDI Spur #%d Meta-Event 0x%02x (%d)", trk.trknum, meta, size); + return; + } + } + + private void process(MidiTrack trk, BankHandle bank, OPLChip chip) { + if(trk.pos >= trk.size) { + return; + } + byte status = trk.buffer[trk.pos++]; + if((status & 0x80) == 0) { + status = trk.status; + trk.pos -= 1; + } + else { + trk.status = status; + } + if((status & 0xf0) != 0xf0) { + byte channel = (byte)(status & 0x0f); + status &= 0xf0; + switch(status) { + case midev_noteoff: { + byte o_key = trk.readByte(); + byte o_velocity = trk.readByte(); + bank.noteoff(chip, channel, o_key, o_velocity); + this.debug("MIDI Note-Aus - C%02d N%03d V%03d", channel+1, o_key, o_velocity); + break; + } + case midev_noteon: { + byte key = trk.readByte(); + byte velocity = trk.readByte(); + if(velocity == 0) + bank.noteoff(chip, channel, key, velocity); + else + bank.noteon(chip, channel, key, velocity); + this.debug("MIDI Note-An - C%02d N%03d V%03d", channel+1, key, velocity); + break; + } + case midev_aftertouch: { + byte pressure = trk.readByte(); + this.debug("MIDI Aftertouch - C%02d P%03d", channel+1, pressure); + break; + } + case midev_control: { + byte control = trk.readByte(); + byte value = trk.readByte(); + bank.control(chip, channel, control, value); + this.debug("MIDI Controller - C%02d N%03d V%03d", channel+1, control, value); + break; + } + case midev_progchg: { + byte program = trk.readByte(); + bank.progchange(chip, channel, program); + this.debug("MIDI Programm - C%02d P%03d", channel+1, program); + break; + } + case midev_chnpressure: { + byte cpressure = trk.readByte(); + this.debug("MIDI Kanal-Druck - C%02d P%03d", channel+1, cpressure); + break; + } + case midev_pitchbend: { + short pb = (short)(((short)trk.readByte()) | (((short)trk.readByte()) << 7)); + short pitch = (short)(pb - 0x2000); + bank.pitchbend(chip, channel, pitch); + this.debug("MIDI Pitch-Bend - C%02d P%03d", channel+1, pitch); + break; + } + } + } + else { + switch(status) { + case midev_sysex: { + int slen = trk.readVarLen(); + trk.pos += slen; + this.debug("MIDI Sysex (Normal) mit Länge = %d", slen); + break; + } + case midev_songpos: + this.debug("MIDI Song-Position = %d", ((short)trk.readByte()) | (((short)trk.readByte()) << 7)); + break; + case midev_songsel: + this.debug("MIDI Song-Auswahl = %d", trk.readByte()); + break; + case midev_tunereq: + this.debug("MIDI Stimmung angefordert, nichts zu tun?!"); + break; + case midev_endsysex: { + int elen = trk.readVarLen(); + trk.pos += elen; + this.debug("MIDI Sysex (Escape) mit Länge = %d", elen); + break; + } + case midev_clock: + case midev_start: + case midev_continue: + case midev_stop: + case midev_actsense: + this.debug("MIDI Status %d", status); + break; + case midev_meta: + this.processMeta(trk); + break; + default: + this.log("MIDI Status unbekannt: 0x%02x", status); + break; + } + } + } + + private boolean tick(BankHandle bank, OPLChip chip) { + boolean end = true; + for(int trk = 0; trk < this.tracks.length; trk++) { + MidiTrack track = this.tracks[trk]; + if(track.ending) + continue; + if(track.wait > 0) { + track.wait -= 1; + if(track.wait > 0) { + end = false; + continue; + } + } + while(true) { + if(track.pos > 0) { + this.process(track, bank, chip); + if(!track.ending && track.pos >= track.size) { + this.log("MIDI Spur #%d endete zu früh", track.trknum); + track.ending = true; + } + if(track.ending) + break; + } + track.wait = track.readVarLen(); + if(track.wait > 0) + break; + } + end &= track.ending; + } + if(!end) { + this.tick += 1; + this.time += (long)this.ticktime; + } + return !end; + } + + public long process() { + if(!this.playing && (this.nowait || !this.chip.isPlaying())) { + return -1L; + } + else if(this.playing && this.tick(this.bank, this.chip)) { + return ((long)this.ticktime) * 1000L; + } + else if(this.playing) { + this.playing = false; + if(this.bank != null) + this.bank.alloff(); + } + return 1000L; + } + + public void setDebug(boolean debug) { + this.debug = debug; + } + } + + public static class BankHandle { + public class BankVoice { + BankChannel channel; + OPLChannel opl; + byte note; + BankOp op; + int detune; + BankVoice pair; + } + + public class BankKey { + byte note; + byte velocity; + BankVoice voice; + } + + public class BankChannel { + BankKey[] keys = new BankKey[BANK_MAX]; + byte[] notes = new byte[128]; + byte keyindex; + byte active; + int pbank; + byte pan; + byte volume; + short pitch; + byte program; + BankInstr instr; + int ch_num; + } + + private static final int BANK_MAX = 64; + private static final double BANK_PBRANGE = 2.0; + + private static final int[] bank_notes = { + 8175, 8661, 9177, 9722, 10300, 10913, 11562, 12249, + 12978, 13750, 14567, 15433, 16351, 17323, 18354, 19445, + 20601, 21826, 23124, 24499, 25956, 27500, 29135, 30867, + 32703, 34647, 36708, 38890, 41203, 43653, 46249, 48999, + 51913, 55000, 58270, 61735, 65406, 69295, 73416, 77781, + 82406, 87307, 92498, 97998, 103826, 110000, 116540, 123470, + 130812, 138591, 146832, 155563, 164813, 174614, 184997, 195997, + 207652, 220000, 233081, 246941, 261625, 277182, 293664, 311126, + 329627, 349228, 369994, 391995, 415304, 440000, 466163, 493883, + 523251, 554365, 587329, 622253, 659255, 698456, 739988, 783990, + 830609, 880000, 932327, 987766, 1046502, 1108730, 1174659, 1244507, + 1318510, 1396912, 1479977, 1567981, 1661218, 1760000, 1864655, 1975533, + 2093004, 2217461, 2349318, 2489015, 2637020, 2793825, 2959955, 3135963, + 3322437, 3520000, 3729310, 3951066, 4186009, 4434922, 4698636, 4978031, + 5274040, 5587651, 5919910, 6271926, 6644875, 7040000, 7458620, 7902132, + 8372018, 8869844, 9397272, 9956063, 10548081, 11175303, 11839821, 12543853 + }; + + private static final int[] opl3_maxfreq = { + 48503, + 97006, + 194013, + 388026, + 776053, + 1552107, + 3104215, + 6208431 + }; + + public final BankChannel[] channels = new BankChannel[16]; + public final BankVoice[] voices; + public final BankInstr[] instruments; + public final boolean keep; + public final boolean useunkn; + public final byte velo_func; + + public int index; + public int used; + + public BankHandle(OPLChip chip, BankInstr[] instr, boolean keep, boolean useunkn, byte velofunc) { + this.keep = keep; + this.useunkn = useunkn; + this.velo_func = velofunc; + this.instruments = instr; + this.voices = new BankVoice[chip.channel.length]; + this.reset(chip); + } + + public void reset(OPLChip chip) { + this.index = 0; + this.used = 0; + for(int h = 0; h < this.channels.length; h++) { + BankChannel channel = this.channels[h] = new BankChannel(); + Arrays.fill(channel.notes, (byte)0xff); + channel.volume = 127; + channel.pbank = (h == 9) ? 128 : 0; + channel.instr = this.instruments[(h == 9) ? 128 : 0]; + channel.ch_num = h; + for(int z = 0; z < channel.keys.length; z++) { + channel.keys[z] = new BankKey(); + } + } + for(int h = 0; h < this.voices.length; h++) { + BankVoice voice = this.voices[h] = new BankVoice(); + voice.opl = chip.channel[h]; + voice.note = (byte)0xff; + voice.op = BankOp.b_op2; + voice.detune = 0; + } + for(int h = 0; h < this.voices.length; h++) { + this.voices[h].pair = this.voices[h+(((h & 1) == 0) ? 1 : -1)]; + } + } + + private static byte getnote(BankChannel ch, byte key, int id, boolean drum, BankInstr instr) { + int note = (int)key; + if(instr.fixed) { // || drum + note = instr.percnum + (int)instr.channels[id].offset; + } + else { + note += instr.channels[id].offset; + } + note = note < 0 ? 0 : note; + note = note > 127 ? 127 : note; + return (byte)note; + } + + private static void OPL3_ChannelFreqHz(OPLChannel channel, int freq) { + int block = 0; + while(opl3_maxfreq[block] < freq) { + block++; + if(block == 8) { + break; + } + } + if(block == 8) { + channel.setFrequency(7, 1023); + return; + } + double f_num = ((double)freq) / 1000.0 * Math.pow(2.0, (20.0-((double)block))) / 49716.0; + channel.setFrequency(block, (int)f_num); + } + + private static int getfreq(byte key, int pitch, int detune) { + double pfrq = Math.pow(2.0, ((((double)pitch) / 8191.0 * BANK_PBRANGE) + (((double)detune) / 100.0)) / 12.0); + double freq = ((double)bank_notes[key]) * pfrq; + return (int)freq; + } + + private static void OPL3_ChannelNote(OPLChannel channel, byte key, int pitch, int detune) { + OPL3_ChannelFreqHz(channel, getfreq(key, pitch, detune)); + } + + private static double velofunc(double x, double n) { + return 1.0-Math.log(1.0+((1.0-Math.pow(x,n))*(Math.E-1.0))); + } + + private static int getlevel(byte function, byte velocity, byte volume, byte pan) { + double lvl = ((function == -128) ? 1.0 : + (function != 0 ? velofunc(((double)velocity)/127.0, function < 0 ? (0.1+(1.0-((((double)(-function))-1.0)/126.0))*9.9) : (1.0+((((double)function)-1.0)/126.0)*9.0)) : + (((double)velocity)/127.0))) * (((double)volume)/127.0) * (1.0-(0.9*((double)pan)/63.0)); + // fprintf(stderr, "%d===%d-.%.2f\n", function, velocity, lvl); + lvl *= 65536.0; + return (int)lvl; + } + + private static void OPL3_ChannelLevelPan(OPLChannel channel, byte function, byte velocity, byte volume, byte pan) { + channel.setLevel(0, getlevel(function, velocity, volume, pan)); + channel.setLevel(1, getlevel(function, velocity, volume, (byte)-pan)); + } + + private void releaseVoice(BankChannel ch, BankVoice voice) { + if(voice.note == (byte)0xff) { + return; + } + voice.opl.keyOff(); + voice.note = (byte)0xff; + if(voice.op == BankOp.b_op22) { + voice.pair.note = (byte)0xff; + voice.pair.opl.keyOff(); + this.used -= 2; + } + else if(voice.op == BankOp.b_op4) { + voice.pair.note = (byte)0xff; + this.used -= 2; + } + else { + this.used -= 1; + } + } + + private BankVoice releaseKey(BankChannel ch, byte note) { + if(/* ch == null || */ ch.active == 0) { + return null; + } + if(ch.notes[note] == (byte)0xff) { + return null; + } + BankKey key = ch.keys[ch.notes[note]]; + ch.notes[note] = (byte)0xff; + ch.active -= 1; + if(key.voice != null) { + this.releaseVoice(ch, key.voice); + } + key.velocity = 0; + return key.voice; + } + + private void initVoice(OPLChannel channel, BankInstr instr, int id) { + channel.set4Op(instr.op == BankOp.b_op4); + for(int o = 0; o < ((instr.op == BankOp.b_op4) ? 4 : 2); o++) { + BankPair pair = instr.channels[(instr.op == BankOp.b_op4) ? (o >> 1) : id]; + BankOperator op = pair.ops[o & 1]; + OPLSlot slot = (o >= 2) ? (channel.pair.slots[o & 1]) : (channel.slots[o & 1]); + slot.setFlags(op.tremolo, op.vibrato, op.sustaining, op.ksr); + slot.setMultiplier(op.mult); + slot.setKSL(op.ksl); + slot.setLevel(op.level); + slot.setEnvelope(op.attack, op.decay, op.sustain, op.release); + slot.setWaveform(op.waveform); + if((o & 1) == 1) { + OPLChannel chn = (o >= 2) ? channel.pair : channel; + chn.setFeedback(pair.feedback); + chn.setAM(pair.am); + } + } + } + + private BankVoice getVoice(BankChannel ch, byte note, byte velocity) { + BankInstr instr = ch.instr; + if(ch.pbank == 128) { + instr = this.instruments[128 + note]; + } + else if(ch.pbank != 0 && !this.useunkn) { + return null; + } + if(instr.op == BankOp.b_op0) { + return null; + } + if(this.used == this.voices.length) { + if(this.keep) { + return null; + } + if((instr.op != BankOp.b_op2) && ((this.index & 1) != 0)) { + this.index += 1; + if(this.index >= this.voices.length) { + this.index = 0; + } + } + this.releaseKey(this.voices[this.index].channel, this.voices[this.index].note); + } + else if((instr.op != BankOp.b_op2) && ((this.index & 1) != 0)) { + this.index += 1; + if(this.index >= this.voices.length) { + this.index = 0; + } + } + int vi = this.index; + while((this.voices[this.index].note != (byte)0xff) || ((instr.op != BankOp.b_op2) && (this.voices[this.index+1].note != (byte)0xff))) { + this.index += (instr.op != BankOp.b_op2) ? 2 : 1; + if(this.index >= this.voices.length) { + this.index = 0; + } + if(vi == this.index) { + if(this.keep) { + return null; + } + this.releaseKey(this.voices[this.index].channel, this.voices[this.index].note); + if((instr.op != BankOp.b_op2) && (this.voices[this.index+1].note != (byte)0xff)) { + this.releaseKey(this.voices[this.index+1].channel, this.voices[this.index+1].note); + } + break; + } + } + BankVoice voice = this.voices[this.index]; + if((instr.op == BankOp.b_op2) && (voice.op == BankOp.b_op4)) { + int offset = this.index + ((this.index & 1) == 0 ? 1 : -1); + BankVoice voice2 = this.voices[offset]; + if(voice2.note == (byte)0xff) { + voice2.opl.setLevel(0, 0); + voice2.opl.setLevel(1, 0); + } + } + this.index += (instr.op != BankOp.b_op2) ? 2 : 1; + if(this.index >= this.voices.length) { + this.index = 0; + } + voice.channel = ch; + voice.note = note; + voice.op = instr.op; + voice.detune = instr.channels[0].detune; + this.initVoice(voice.opl, instr, 0); + if(voice.op == BankOp.b_op22) { + voice.pair.channel = ch; + voice.pair.note = note; + voice.pair.op = BankOp.b_op22; + voice.pair.detune = instr.channels[1].detune; + this.initVoice(voice.pair.opl, instr, 1); + OPL3_ChannelNote(voice.opl, getnote(ch, note, 0, ch.pbank == 128, instr), ch.pitch, voice.detune); + OPL3_ChannelNote(voice.pair.opl, getnote(ch, note, 1, ch.pbank == 128, instr), ch.pitch, voice.pair.detune); + OPL3_ChannelLevelPan(voice.opl, this.velo_func, velocity, ch.volume, ch.pan); + OPL3_ChannelLevelPan(voice.pair.opl, this.velo_func, velocity, ch.volume, ch.pan); + voice.opl.keyOn(); + voice.pair.opl.keyOn(); + this.used += 2; + } + else if(voice.op == BankOp.b_op4) { + voice.pair.channel = ch; + voice.pair.note = note; + voice.pair.op = BankOp.b_op4; + OPL3_ChannelNote(voice.opl, getnote(ch, note, 0, ch.pbank == 128, instr), ch.pitch, voice.detune); + OPL3_ChannelLevelPan(voice.opl, this.velo_func, velocity, ch.volume, ch.pan); + voice.opl.keyOn(); + this.used += 2; + } + else { + OPL3_ChannelNote(voice.opl, getnote(ch, note, 0, ch.pbank == 128, instr), ch.pitch, voice.detune); + OPL3_ChannelLevelPan(voice.opl, this.velo_func, velocity, ch.volume, ch.pan); + voice.opl.keyOn(); + this.used += 1; + } + return voice; + } + + private BankVoice pressKey(BankChannel ch, byte note, byte velocity) { + if(ch.notes[note] != (byte)0xff) { + this.releaseKey(ch, note); + } + if(ch.active == BANK_MAX) { + return null; + } + byte ki = ch.keyindex; + while(ch.keys[ch.keyindex].velocity != 0) { + ch.keyindex += 1; + if(ch.keyindex == BANK_MAX) { + ch.keyindex = 0; + } + if(ki == ch.keyindex) { + return null; + } + } + BankVoice voice = this.getVoice(ch, note, velocity); + ch.notes[note] = ch.keyindex; + ch.active += 1; + BankKey key = ch.keys[ch.keyindex]; + key.note = note; + key.velocity = velocity; + key.voice = voice; + return voice; + } + + private void notesoff(BankChannel ch) { + for(int h = 0; (h < BANK_MAX) && (ch.active > 0); h++) { + this.releaseKey(ch, (byte)h); + } + } + + private void progupdate(BankChannel ch, OPLChip chip) { + this.notesoff(ch); + short id = ch.program; + if(ch.pbank == 128) { + id = 128; + } + else if(ch.pbank != 0 && !this.useunkn) { + id = 0; + } + ch.instr = this.instruments[id]; + } + + private void levelupdate(BankChannel ch, OPLChip chip) { + byte done = 0; + for(int h = 0; (h < BANK_MAX) && (done < ch.active); h++) { + BankKey key = ch.keys[h]; + if((key.velocity == 0) || (key.voice == null)) { + continue; + } + OPL3_ChannelLevelPan(key.voice.opl, this.velo_func, key.velocity, ch.volume, ch.pan); + if(key.voice.op == BankOp.b_op22) { + OPL3_ChannelLevelPan(key.voice.pair.opl, this.velo_func, key.velocity, ch.volume, ch.pan); + } + done++; + } + } + + private void frequpdate(BankChannel ch, OPLChip chip) { + byte done = 0; + for(int h = 0; (h < BANK_MAX) && (done < ch.active); h++) { + BankKey key = ch.keys[h]; + if((key.velocity == 0) || (key.voice == null)) { + continue; + } + OPL3_ChannelNote(key.voice.opl, getnote(ch, key.note, 0, ch.pbank == 128, ch.instr), ch.pitch, key.voice.detune); + if(key.voice.op == BankOp.b_op22) { + OPL3_ChannelNote(key.voice.pair.opl, getnote(ch, key.note, 1, ch.pbank == 128, ch.instr), ch.pitch, key.voice.pair.detune); + } + done++; + } + } + + public void noteon(OPLChip chip, byte channel, byte key, byte velocity) { + BankChannel ch = this.channels[channel]; + // if((ch.pbank == 128) && ((key < 35) || (key > 81))) { + // return; + // } + this.pressKey(ch, key, velocity); + } + + public void noteoff(OPLChip chip, byte channel, byte key, byte velocity) { + BankChannel ch = this.channels[channel]; + // if((ch.pbank == 128) && ((key < 35) || (key > 81))) { + // return; + // } + this.releaseKey(ch, key); + } + + public void progchange(OPLChip chip, byte channel, byte program) { + BankChannel ch = this.channels[channel]; + ch.program = program; + this.progupdate(ch, chip); + } + + public void pitchbend(OPLChip chip, byte channel, short pitch) { + BankChannel ch = this.channels[channel]; + ch.pitch = pitch; + this.frequpdate(ch, chip); + } + + public void control(OPLChip chip, byte channel, byte control, byte value) { + BankChannel ch = this.channels[channel]; + switch(control) { + case 0x00: // bank MSB + // if((channel == 9) && (value == 0)) { + // ch.pbank = 128; + // } + // else { + if(channel != 9) { + ch.pbank &= 0x007f; + ch.pbank |= (((short)value) << 7) & 0x3f80; + } + this.progupdate(ch, chip); + break; + case 0x20: // bank LSB + // if((channel == 9) && (value == 0)) { + // ch.pbank = 128; + // } + // else { + if(channel != 9) { + ch.pbank &= 0x3f80; + ch.pbank |= ((short)value) & 0x007f; + } + this.progupdate(ch, chip); + break; + case 0x07: // volume MSB + ch.volume = value; + this.levelupdate(ch, chip); + break; + case 0x0a: // pan MSB + ch.pan = (byte)(value - 64); + this.levelupdate(ch, chip); + break; + case 0x79: // reset + ch.pbank = (channel == 9) ? 128 : 0; + ch.volume = 127; + ch.pan = 0; + this.progupdate(ch, chip); + this.levelupdate(ch, chip); + break; + case 0x7b: // all off + this.notesoff(ch); + break; + } + } + + public void alloff() { + for(int h = 0; h < 16; h++) { + this.notesoff(this.channels[h]); + } + } + } + + public static enum BankOp { + b_op2, + b_op4, + b_op22, + b_op0 + }; + + public static class BankOperator { + public final int mult; + public final int level; + public final int waveform; + public final int attack; + public final int decay; + public final int sustain; + public final int release; + + boolean tremolo; + boolean vibrato; + boolean sustaining; + boolean ksr; + int ksl; + + public BankOperator(int mult, int level, int waveform, int a, int d, int s, int r) { + this.mult = mult; + this.level = 63 - level; + this.waveform = waveform; + this.attack = a; + this.decay = d; + this.sustain = 15 - s; + this.release = r; + } + + public BankOperator tremolo() { + this.tremolo = true; + return this; + } + + public BankOperator vibrato() { + this.vibrato = true; + return this; + } + + public BankOperator sustaining() { + this.sustaining = true; + return this; + } + + public BankOperator ksr() { + this.ksr = true; + return this; + } + + public BankOperator ksl(int ksl) { + this.ksl = ksl; + return this; + } + } + + public static class BankPair { + public final BankOperator[] ops; + public final int detune; + public final int offset; + public final int feedback; + public final boolean am; + + public BankPair(BankOperator op1, BankOperator op2, int detune, int offset, int feedback, boolean am) { + this.ops = new BankOperator[] {op1, op2}; + this.detune = detune; + this.offset = offset; + this.feedback = feedback; + this.am = am; + } + } + + public static class BankInstr { + public final BankPair[] channels; + public final BankOp op; + public boolean fixed; + public int percnum; + + private BankInstr(BankPair ch1, BankPair ch2, BankOp op) { + this.channels = new BankPair[] {ch1, ch2}; + this.op = op; + } + + public BankInstr() { + this(null, null, BankOp.b_op0); + } + + public BankInstr(BankOperator op1, BankOperator op2, boolean am, int detune, int offset, int feedback) { + this(new BankPair(op1, op2, detune, offset, feedback, am), null, BankOp.b_op2); + } + + public BankInstr(BankOperator op1A, BankOperator op2A, boolean amA, int detuneA, int offsetA, int feedbackA, + BankOperator op1B, BankOperator op2B, boolean amB, int detuneB, int offsetB, int feedbackB) { + this(new BankPair(op1A, op2A, detuneA, offsetA, feedbackA, amA), new BankPair(op1B, op2B, detuneB, offsetB, feedbackB, amB), BankOp.b_op22); + } + + public BankInstr(BankOperator op1, BankOperator op2, BankOperator op3, BankOperator op4, int algo, int detune, int offset, int feedback) { + this(new BankPair(op1, op2, detune, offset, feedback, (algo & 2) != 0), new BankPair(op3, op4, detune, offset, feedback, (algo & 1) != 0), BankOp.b_op4); + } + + public BankInstr setFixed() { + this.fixed = true; + return this; + } + + public BankInstr setPercNum(int percnum) { + this.percnum = percnum; + return this; + } + + public BankInstr setFixed(int percnum) { + this.fixed = true; + this.percnum = percnum; + return this; + } + } + + private static class Channel { short[] buffer; int pos; @@ -37,7 +1484,8 @@ public class AudioInterface implements Runnable { VOLUME, PLAY, BREAK, - LEVEL + LEVEL, + MIDI }; private final int blocksize; @@ -48,16 +1496,862 @@ public class AudioInterface implements Runnable { private final List cmds = Lists.newArrayList(); private final short[] volumes = new short[Volume.values().length]; private final Channel[] channels = new Channel[32]; + private final short[] data = new short[2]; private SourceDataLine line; private int smppos; private long peak = 32767L; private boolean opened; private boolean waiting; + + private MidiHandle mid; + + /* +STR(OPL_CHANNEL, "", "Kanal") +STR(OPL_MODULATOR, "", "Operator 1 + 3 / Modulator") +STR(OPL_CARRIER, "", "Operator 2 + 4 / Träger") +STR(OPL_POINTER, "", "Stimmen-Zeiger") +STR(OPL_OUTPUT, "", "Pegel") +STR(OPL_MODE, "", "Modus/Al.") +STR(OPL_FEEDBACK, "", "Feedback") +STR(OPL_FREQUENCY, "", "Frequenz") +STR(OPL_FLAGS, "", "T-V-S-K") +STR(OPL_MULTIPLIER, "", "Frq.-Mult.") +STR(OPL_LEVEL, "", "Op.-Pegel") +STR(OPL_KEYSCALE, "", "Tst.-Skale") +STR(OPL_WAVEFORM, "", "Wellenf.") +STR(OPL_ATTACK, "", "Attack") +STR(OPL_DECAY, "", "Decay") +STR(OPL_SUSTAIN, "", "Sustain") +STR(OPL_RELEASE, "", "Release") +STR(OPL_ENVELOPE, "", "Hüllkurve") +STR(OPL_TICK, "", "Tick/Pos.") +STR(OPL_ACTIVE, "", "Aktiv") +STR(OPL_TEMPO, "", "Tempo BPM") - private void push(short data) { +STR(MID_PLAY, "s", "Spiele MIDI '$1'") +STR(CMD_PLAY_QUEUED, "d", "$1 MIDIs werden abgespielt") +STR(CMD_PLAY_STOPPED, "", "Wiedergabe ist angehalten") +STR(CMD_PLAY_PLAYING, "d", "$1 MIDIs sind in der Liste") +STR(CMD_PLAY_ENDED, "", "Wiedergabe gestoppt") +STR(CMD_PLAY_BANKID, "d", "Bank-ID: $1") + +STR(PLR_MODE_NORM, "", "Normal") +STR(PLR_MODE_REPEAT, "", "Wiederholen") +STR(PLR_MODE_LOOP, "", "Schleife") +STR(PLR_MODE_RAND, "", "Zufällig") +STR(PLR_START, "", "Start") +STR(PLR_STOP, "", "Stop") +STR(PLR_PAUSE, "", "Pause") +STR(PLR_MODE, "", "Modus") +STR(PLR_PREV, "", "Vorheriger") +STR(PLR_NEXT, "", "Nächster") +STR(PLR_RANDOM, "", "Zufälliger") +STR(PLR_JUMP, "", "Springe zu") +STR(PLR_INFO, "", "Titelinfo") +STR(PLR_KARAOKE, "", "Karaoke-Texte") + +STR(TITLE_PLAYER, "", "MIDI-Player") + +STR(TITLE_MIDI, "", "Optionen für MIDI-Wiedergabe") +STR(TITLE_PLR_FULL, "", "SKC OPL MIDI Player v 0.0.1 (~ Sen)") + +#define STR_IMID_TITLE "Titel" +#define STR_IMID_INFO "Info" +#define STR_IMID_COPY "Hinweis" +#define STR_IMID_WARN "Lizenz" +#define STR_IMID_LANG "Sprache" +#define STR_IMID_VER "Version" + + +#define SYM_PLAY "\U00100000" +#define SYM_STOP "\U00100001" +#define SYM_PAUSE "\U00100002" +#define SYM_NEXT "\U00100003" +#define SYM_PREV "\U00100004" +#define SYM_FFORWARD "\U00100005" +#define SYM_FREVERSE "\U00100006" +#define SYM_CONTINUOS "\U00100007" +#define SYM_REPEAT "\U00100008" +#define SYM_LOOPED "\U00100009" +#define SYM_SHUFFLE "\U0010000a" +#define SYM_PRANDOM "\U0010000b" +#define SYM_JUMPTO "\U0010000c" +#define SYM_FOLDER "\U0010000d" +#define SYM_DELETE "\U0010000e" +#define SYM_SAVEDISK "\U0010000f" + + +#define SND_INFO 64 +#define SND_KAR 72 +#define SND_KLOG 5 + +#define BNK_IDX_MELO_ONLY 25 +#define BNK_IDX_DRUM_ONLY 30 + +static const char plr_wheel[] = { '/', '-', '\\', '|'}; + +static const char *mid_extensions[] = {"mid", "kar", "midi"}; + +typedef struct { + window_t *player; + ulong mid_poll; + char **mid_queue; + char *mid_list; + byte mid_nowait; + byte mid_repeat; + byte mid_bank; + int mid_queued; + int mid_playpos; + int mid_prevpos; + + byte mid_keep; + byte mid_unknown; + byte mid_visual; + int mid_velo; + int mid_voices; + + byte mid_debug; + byte mid_karaoke; + uint mid_klog_pos; + uint mid_klog_offs; + uint mid_info_offs[6]; + char mid_klogger[SND_KLOG * SND_KAR]; + char mid_info[SND_INFO * 6]; +} snd_t; + +snd_t snd; + +plr_debug(cvar_byte("mid_debug_events")); + + +static const char **plr_descs[] = { &STR_IMID_TITLE, &STR_IMID_INFO, &STR_IMID_COPY, &STR_IMID_WARN, &STR_IMID_LANG, &STR_IMID_VER }; + +byte plr_play_file(const char *midname, byte nowait) { + mid_handle mid; + uint size; + if(!(size = mid_read(&mid, midname))) + return 0; + logi(LOG_CON, STR_MID_PLAY, midname); + snd_instruct(mid.track, size, SND_CMD_LOAD, 0, (uint)mid.tracks | ((uint)mid.tpqn << 16) | ((uint)nowait << 31)); + mem_free(mid.track); + return 1; +} + +void plr_cmlog(byte empty); + +byte plr_play(int pos, byte nowait) { + plr_cmlog(0); + if(pos != (snd.mid_queued - 1)) + snd.mid_queue[pos+1][-1] = 0; + byte status = plr_play_file(&(snd.mid_queue[pos])[6], nowait); + if(pos != (snd.mid_queued - 1)) + snd.mid_queue[pos+1][-1] = '\n'; + snd.mid_queue[pos][0] = COL_GREEN[0]; + if(snd.mid_prevpos >= 0) + snd.mid_queue[snd.mid_prevpos][0] = COL_NEON[0]; + snd.mid_prevpos = pos; + return status; +} + +byte plr_stopped() { + return snd_query(SND_STAT_STOPPED, 0); +} + +// voices -- 2 ~ 192 +byte plr_setup(byte voices, const char *dmxname, const char *drumname, byte keep, byte useunkn, char velofunc) { + // if(dmxname) { + bank_instr *instr = bnk_read_banks(dmxname, drumname, 0); + if(!instr) + return 0; + snd_instruct(instr, sizeof(bank_instr) * 256, SND_CMD_VOICES, 0, 0); + mem_free(instr); + // } + voices = (voices & 1) ? (voices + 1) : voices; + // snd_instruct(NULL, 0, SND_CMD_BANK, dmxname ? 31 : bank, 0); + snd_instruct(NULL, 0, SND_CMD_MKOPL, 0, (uint)voices | ((uint)((byte)(velofunc & 0xff)) << 8) | ((uint)keep << 16) | ((uint)useunkn << 17)); + return 1; +} + +void plr_debug(byte debug) { + snd_instruct(NULL, 0, SND_CMD_DEBUG, 0, debug); + snd.mid_debug = debug; +} + +void plr_volume(byte channel, ushort volume) { + snd_instruct(NULL, 0, SND_CMD_VOLUME, channel, volume); +} + +enum repeat_type { + REPEAT_OFF, + REPEAT_SINGLE, + REPEAT_ALL, + REPEAT_SHUFFLE +}; + +void plr_end() { + if(snd.mid_queue) { + mem_free(snd.mid_queue); + mem_free(snd.mid_list); + } + snd.mid_queue = NULL; + snd.mid_list = NULL; + snd.mid_queued = 0; + snd_instruct(NULL, 0, SND_CMD_LOAD, 0, 0); + snd_instruct(NULL, 0, SND_CMD_MKOPL, 0, 0); +} + +byte plr_queue(const char *dmxname, const char *drumname, const char **files, int count) { + plr_end(); + if(!plr_setup(snd.mid_voices, dmxname, drumname, snd.mid_keep, snd.mid_unknown, (char)snd.mid_velo)) + return 0; + snd.mid_queue = mem_alloc(sizeof(char *) * count, MEM_FILE); + int size = 0; + for(int z = 0; z < count; z++) { + size += strlen(files[z]) + 7; + } + char *list = snd.mid_list = mem_alloc(size, MEM_FILE); + for(int z = 0; z < count; z++) { + snd.mid_queue[z] = list; + list += snprintf(list, 1024, (z == (count - 1)) ? COL_NEON "#%03d %s" : COL_NEON "#%03d %s\n", z + 1, files[z]); + } + snd.mid_queued = count; + snd.mid_playpos = -1; + snd.mid_prevpos = -1; +} + +void plr_mode(byte repeat) { + snd.mid_repeat = repeat; +} + +void plr_pause() { + if(!snd.mid_queue || (snd.mid_playpos == INT_MIN)) + return; + if(snd.mid_playpos >= -1) + snd.mid_playpos = -(snd.mid_playpos + 3); + else + snd.mid_playpos = -(snd.mid_playpos) - 3; + if(snd.mid_playpos >= 0 || snd.mid_playpos < -2) + snd.mid_queue[(snd.mid_playpos < -1) ? (-(snd.mid_playpos) - 3) : snd.mid_playpos][0] = snd.mid_playpos < -1 ? COL_YELLOW[0] : COL_GREEN[0]; + snd_instruct(NULL, 0, SND_CMD_PAUSE, 0, snd.mid_playpos < -1); +} + +void plr_stop() { + if(!snd.mid_queue) + return; + plr_cmlog(1); + snd.mid_playpos = INT_MIN; + if(snd.mid_prevpos >= 0) + snd.mid_queue[snd.mid_prevpos][0] = COL_NEON[0]; + snd.mid_prevpos = -1; + snd_instruct(NULL, 0, SND_CMD_LOAD, 0, 0); +} + +void plr_resume() { + if(!snd.mid_queue) + return; + if(snd.mid_playpos == INT_MIN) + plr_play(snd.mid_playpos = 0, snd.mid_nowait); + else if(snd.mid_playpos < -1) + plr_pause(); +} + +void plr_update() { + if(snd.mid_queue && (sys.tmr_current - snd.mid_poll) >= 250000ULL) { + snd.mid_poll = sys.tmr_current; + if((snd.mid_playpos >= -1) && plr_stopped()) { + if(snd.mid_playpos >= 0) { + switch(snd.mid_repeat) { + case REPEAT_OFF: + snd.mid_playpos += 1; + break; + case REPEAT_SINGLE: + break; + case REPEAT_ALL: + if((snd.mid_playpos += 1) >= snd.mid_queued) + snd.mid_playpos = 0; + break; + case REPEAT_SHUFFLE: + snd.mid_playpos = rand() % snd.mid_queued; + break; + } + } + else { + snd.mid_playpos = 0; + } + if(snd.mid_playpos >= snd.mid_queued) + plr_stop(); + else + plr_play(snd.mid_playpos, snd.mid_nowait); + if(snd.player && snd.player.open) { + gui_update_text(gui_get(snd.player, 1)); + gui_plr_update(snd.player); + } + } + } +} + +void plr_jump(int pos) { + if(!snd.mid_queue) + return; + plr_resume(); + pos = CLAMP_VALUE(pos, 0, snd.mid_queued - 1); + plr_play(snd.mid_playpos = pos, snd.mid_nowait); +} + +void plr_next() { + if(!snd.mid_queue) + return; + plr_resume(); + if((snd.mid_playpos += 1) >= snd.mid_queued) + snd.mid_playpos = 0; + plr_play(snd.mid_playpos, snd.mid_nowait); +} + +void plr_prev() { + if(!snd.mid_queue) + return; + plr_resume(); + if((snd.mid_playpos -= 1) < 0) + snd.mid_playpos = snd.mid_queued - 1; + plr_play(snd.mid_playpos, snd.mid_nowait); +} + +void plr_rand() { + if(!snd.mid_queue) + return; + plr_resume(); + plr_play((snd.mid_playpos = rand() % snd.mid_queued), snd.mid_nowait); +} + +void plr_capture(const char *wavname) { + snd_instruct(wavname, wavname ? (strlen(wavname) + 1) : 0, SND_CMD_WAVECAP, 0, 0); +} + +void plr_clog() { + snd.mid_klog_pos = 0; + snd.mid_klog_offs = 0; + for(int z = 0; z < SND_KLOG; z++) { + memset(&snd.mid_klogger[z * SND_KAR], ' ', SND_KAR - 1); + snd.mid_klogger[z * SND_KAR + (SND_KAR - 1)] = (z == (SND_KLOG - 1)) ? 0 : '\n'; + } + if(snd.player && snd.player.open) + gui_update_text(gui_get(snd.player, 16)); +} + +void plr_cmlog(byte empty) { + snd.mid_karaoke = 0; + for(int z = 0; z < 6; z++) { + snd.mid_info_offs[z] = empty ? 0 : sprintf(&snd.mid_info[z * SND_INFO], "%-12s: ", *(plr_descs[z])); + if(!empty) { + memset(&snd.mid_info[z * SND_INFO + snd.mid_info_offs[z]], ' ', SND_INFO - 1); + snd.mid_info[z * SND_INFO + (SND_INFO - 1)] = (z == 5) ? 0 : '\n'; + } + } + if(empty) + snd.mid_info[0] = 0; + plr_clog(); + if(snd.player && snd.player.open) + gui_update_text(gui_get(snd.player, 15)); +} + +void plr_kar() { + if(snd.mid_karaoke ^ 1) { + snd.mid_karaoke = 1; + plr_clog(); + } +} + +void plr_info(int type, const char *text) { + char *info = &snd.mid_info[type * SND_INFO]; + int offs = snd.mid_info_offs[type]; + int len = strlen(text); + int max = (SND_INFO - 1) - 14; + max -= (offs > 14) ? 2 : 0; + len = (len > (max-offs)) ? (max-offs) : len; + if(len <= 0) { + return; + } + if(offs > 14) { + info[offs++] = ';'; + info[offs++] = ' '; + } + // else { + // max = sprintf(info, "%-12s: ", *(plr_descs[type])); + // memset(info+(max + len), ' ', SND_INFO - (1 + max + len)); + // offs += max; + // } + memcpy(info+offs, text, len); + offs += len; + // info[SND_INFO - 1] = (type == 5) ? 0 : '\n'; + snd.mid_info_offs[type] = offs; + if(snd.player && snd.player.open) + gui_update_text(gui_get(snd.player, 15)); +} + +void plr_tlog(int type, const char *text) { + int slen = strlen(text); + // for(int h = 0; h < slen; h++) { + // text[h] = ((text[h] < 32) && (text[h] >= 0)) ? ((h == (slen-1)) ? 0 : '?') : text[h]; + // } + if((type == 1) && (text[0] == '@')) { + // text += 1; + switch(text[1]) { + case 'T': + plr_info(0, text+2); + return; + case 'I': + plr_info(1, text+2); + return; + case 'K': + plr_info(2, text+2); + return; + case 'W': + plr_info(3, text+2); + return; + case 'L': + plr_info(4, text+2); + return; + case 'V': + plr_info(5, text+2); + return; + } + // text += (text[0] == 0) ? 0 : 1; + } + else if((type == 1) && (text[0] == '%') && (text[1] == '-')) { + plr_kar(); + return; + } + else if(type == 1) { + switch(text[0]) { + case '\\': + plr_kar(); + plr_clog(); + text += 1; + slen -= 1; + break; + case '/': + // mid_kar(); + if(snd.mid_karaoke) { + snd.mid_klog_pos += 1; + snd.mid_klog_offs = 0; + if(snd.mid_klog_pos == SND_KLOG) + plr_clog(); + text += 1; + slen -= 1; + } + break; + } + if(snd.mid_karaoke) { + int len; + while(slen > 0) { + len = (slen > ((SND_KAR - 1) - snd.mid_klog_offs)) ? ((SND_KAR - 1) - snd.mid_klog_offs) : slen; + if(len <= 0) + break; + memcpy(&snd.mid_klogger[snd.mid_klog_pos * SND_KAR + snd.mid_klog_offs], text, len); + snd.mid_klog_offs += len; + // snd.mid_klogger[snd.mid_klog_pos * SND_KAR + snd.mid_klog_offs] = 0; + slen -= len; + if(slen > 0) { + snd.mid_klog_pos += 1; + snd.mid_klog_offs = 0; + if(snd.mid_klog_pos == SND_KLOG) + plr_clog(); + } + } + if(snd.player && snd.player.open) + gui_update_text(gui_get(snd.player, 16)); + return; + } + } + if(snd.mid_debug || (type <= 2) || (type >= 5)) + logd(LOG_CON, STR_MID_TEXT, type, text); +} + + + + + + + + + + + + + + + + + +void gui_fmt_velofunc(gui_t *elem, int value) { + snprintf(elem.text, elem.capacity, "%s %d: %s", elem.format_text, value, (value ? (value == -128 ? STR_MID_VELO_ONE : (value < 0 ? STR_MID_VELO_ATTN : STR_MID_VELO_LOG)) : STR_MID_VELO_LIN)); +} + +byte gui_init_sound(window_t *win, int frame_x, int frame_y, byte open) { + gui_add_cvtoggle(win, 20, 360, 340, 24, STR_MID_USEUNKN, "mid_play_unknown"); + gui_add_cvtoggle(win, 380, 360, 340, 24, STR_MID_KEEPNOTES, "mid_keep_notes"); + gui_add_cvtoggle(win, 20, 400, 340, 24, STR_MID_DONTFADE, "mid_dont_fade"); + gui_add_cvtoggle(win, 380, 400, 340, 24, STR_MID_DEBUGEVT, "mid_debug_events"); + gui_add_cvslider(win, 20, 440, 340, 24, STR_MID_VELOFUNC, "mid_velocity_func").format = gui_fmt_velofunc; + gui_add_cvslider(win, 380, 440, 340, 24, STR_MID_OPVOICES, "mid_opl_voices"); + return 0; +} + +void plr_mode(byte repeat); +void plr_pause(); +void plr_stop(); +void plr_resume(); +void plr_jump(int pos); +void plr_next(); +void plr_prev(); +void plr_rand(); + +void gui_plr_update_text(window_t *win) { + gui_t *elem = gui_get(win, 17); + const char *name = ""; + int pos = (snd.mid_queue && (snd.mid_playpos != INT_MIN)) ? ((snd.mid_playpos < -1) ? (-(snd.mid_playpos) - 3) : snd.mid_playpos) : -1; + if(pos >= 0) { + if(pos != (snd.mid_queued - 1)) + snd.mid_queue[pos+1][-1] = 0; + if(name = strrchr(&(snd.mid_queue[pos])[6], '/')) + name += 1; + else + name = &(snd.mid_queue[pos])[6]; + snprintf(elem.text, elem.capacity, "%s: %d/%d - %s", STR_PLR_INFO, pos + 1, snd.mid_queued, name); + if(pos != (snd.mid_queued - 1)) + snd.mid_queue[pos+1][-1] = '\n'; + } + else { + snprintf(elem.text, elem.capacity, "%s", STR_PLR_INFO); + } + gui_update_text(elem); +} + +void gui_plr_update(window_t *win) { + gui_t *elem = gui_get(win, 2); + snprintf(elem.text, elem.capacity, snd.mid_queue && (snd.mid_playpos >= -1) ? COL_GREEN SYM_PLAY : SYM_PLAY); + gui_update_text(elem); + elem = gui_get(win, 3); + snprintf(elem.text, elem.capacity, !snd.mid_queue || (snd.mid_playpos == INT_MIN) ? COL_RED SYM_STOP : SYM_STOP); + gui_update_text(elem); + elem = gui_get(win, 4); + snprintf(elem.text, elem.capacity, snd.mid_queue && (snd.mid_playpos != INT_MIN) && (snd.mid_playpos < -1) ? COL_YELLOW SYM_PAUSE : SYM_PAUSE); + gui_update_text(elem); + gui_plr_update_text(win); +} + +void gui_fnc_plr_start(gui_t *elem, int value) { + plr_resume(); + gui_update_text(gui_get(elem.window, 1)); + gui_plr_update(elem.window); +} + +void gui_fnc_plr_stop(gui_t *elem, int value) { + plr_stop(); + gui_update_text(gui_get(elem.window, 1)); + gui_plr_update(elem.window); +} + +void gui_fnc_plr_pause(gui_t *elem, int value) { + plr_pause(); + gui_update_text(gui_get(elem.window, 1)); + gui_plr_update(elem.window); +} + +void gui_fnc_plr_mode(gui_t *elem, int value) { + plr_mode((byte)value); +} + +void gui_fnc_plr_prev(gui_t *elem, int value) { + plr_prev(); + gui_update_text(gui_get(elem.window, 1)); + gui_plr_update(elem.window); +} + +void gui_fnc_plr_next(gui_t *elem, int value) { + plr_next(); + gui_update_text(gui_get(elem.window, 1)); + gui_plr_update(elem.window); +} + +void gui_fnc_plr_rand(gui_t *elem, int value) { + plr_rand(); + gui_update_text(gui_get(elem.window, 1)); + gui_plr_update(elem.window); +} + +void gui_fnc_plr_clear(gui_t *elem, int value) { + plr_stop(); + plr_end(); + elem = gui_get(elem.window, 1); + elem.text = &elem.window.strings[GUI_STR_SIZE * elem.id]; + elem.text[0] = 0; + elem.capacity = GUI_STR_SIZE; + gui_update_text(elem); + elem.sel_start = elem.sel_end = elem.sel_drag = 0; + gui_text_update_cur(elem, elem.sel_start, 1); + elem.sel_start = elem.sel_end = elem.sel_drag = -1; + gui_plr_update(elem.window); +} + +void gui_fnc_plr_open(gui_t *elem, int value) { +} + +static const char *gui_plr_modes[] = { + SYM_CONTINUOS, + SYM_REPEAT, + SYM_LOOPED, + SYM_SHUFFLE +}; + +void gui_fnc_plr_jump(gui_t *elem, int value) { + if(!value && str_parse_int(elem.text, &value, -10) && value && (value <= snd.mid_queued)) { + plr_jump(value - 1); + snprintf(elem.text, elem.capacity, SYM_JUMPTO "#"); + gui_update_text(elem); + gui_deselect(elem.window); + gui_text_update_cur(elem, elem.sel_start, 1); + gui_update_text(gui_get(elem.window, 1)); + gui_plr_update(elem.window); + } + else if(value == 4) { + elem.text[0] = 0; + gui_update_text(elem); + gui_text_update_cur(elem, elem.sel_start, 1); + } + else if(value == -4) { + snprintf(elem.text, elem.capacity, SYM_JUMPTO "#"); + gui_update_text(elem); + gui_text_update_cur(elem, elem.sel_start, 1); + } +} + +void gui_fnc_plr_load(gui_t *elem, int value) { + if(!value) { + int count; + const char **files = file_list(&count, elem.text, mid_extensions, 3); + if(files) { + count = count > 999 ? 999 : count; + plr_queue(snd_bankfiles[snd.mid_bank], NULL, files, count); + mem_free(files); + elem.text[0] = 0; + gui_update_text(elem); + gui_deselect(elem.window); + gui_text_update_cur(elem, elem.sel_start, 1); + elem = gui_get(elem.window, 1); + elem.text = snd.mid_list; + elem.capacity = strlen(snd.mid_list) + 1; + gui_update_text(elem); + elem.sel_start = elem.sel_end = elem.sel_drag = 0; + gui_text_update_cur(elem, elem.sel_start, 1); + elem.sel_start = elem.sel_end = elem.sel_drag = -1; + gui_plr_update(elem.window); + } + } +} + +void gui_fmt_bankdrop(gui_t *elem, int value) { + int pos = 0; + for(int z = 0; z < elem.max && pos < (elem.capacity - 1); z++) { + pos += snprintf(&elem.text[pos], elem.capacity - pos, z ? "\n%s%s" : "%s%s", ((const char **)elem.aux_data)[z], z < BNK_IDX_MELO_ONLY ? "" : (z < BNK_IDX_DRUM_ONLY ? " [0-127]" : " [128-255]")); + } +} + +static const uint plr_waveforms[] = {0xff00ff00, 0xffff0000, 0xff0000ff, 0xff00ffff, 0xffff00ff, 0xffffff00, 0xff80ff00, 0xff00ff80}; + +int gui_render_bar(gui_t *elem, int x, int y, int w, int h, uint top, uint bottom, uint bg, int v) { + v = (v < 0) ? 0 : v; + gfx_draw_rect(elem.pos_x + x, elem.pos_y + y, w, h, 0, 0, 0xff000000 | top, 0xff000000 | bottom, 0, 0); + if(++v < h) + gfx_draw_rect(elem.pos_x + x, elem.pos_y + y, w, h - v, 0, 0, 0xff000000 | bg, 0xff000000 | bg, 0, 0); + return h; +} + +int gui_render_grad(gui_t *elem, int x, int y, int w, int h, uint top, uint bottom) { + gfx_draw_rect(elem.pos_x + x, elem.pos_y + y, w, h, 0, 0, 0xff000000 | top, 0xff000000 | bottom, 0, 0); + return h; +} + +int gui_render_rect(gui_t *elem, int x, int y, int w, int h, uint color) { + gfx_draw_rect(elem.pos_x + x, elem.pos_y + y, w, h, 0, 0, 0xff000000 | color, 0xff000000 | color, 0, 0); + return h; +} + +int gui_render_text(gui_t *elem, int x, int y, uint color, const char *text) { + txt_draw(elem.pos_x + x, elem.pos_y + y, 0, sys.font.yglyph, sys.font.yglyph, elem.pos_x + x, elem.pos_y + y, elem.pos_x + elem.size_x, elem.pos_y + elem.size_y, + 0xff000000 | color, 0x00000000, &sys.font, text); + return sys.font.yglyph; +} + +void gui_render_player(gui_t *elem, int value) { + elem.t_dirty = 1; + if(!value) + gui_render_grad(elem, 0, 0, elem.size_x, elem.size_y, sys.style.field_btm, sys.style.field_top); + if(snd.mid_visual ^ 1) + return; + pthread_mutex_lock(&sgt.lock); + if(!sgt.chip || !sgt.bank) { + pthread_mutex_unlock(&sgt.lock); + return; + } + OPLChip chip = sgt.chip; + bank_handle *bank = sgt.bank; + int nch = chip.n_voices; + int bx = elem.size_x / 2 - (nch * 6 + 2) / 2; + int by = elem.size_y / 2 - (698 + 20 * 4) / 2; + int x = bx; + int y = by; + if(value) { + x = bx - 84; + sprintf(&sys.work_buf[str_time(sys.work_buf, sgt.mid_time)], "\n%c%c%c%c %c", + (sgt.mid.tpqn && (((sgt.mid_tick / sgt.mid.tpqn) & 3) == 0)) ? '*' : '.', + (sgt.mid.tpqn && (((sgt.mid_tick / sgt.mid.tpqn) & 3) == 1)) ? '*' : '.', + (sgt.mid.tpqn && (((sgt.mid_tick / sgt.mid.tpqn) & 3) == 2)) ? '*' : '.', + (sgt.mid.tpqn && (((sgt.mid_tick / sgt.mid.tpqn) & 3) == 3)) ? '*' : '.', + plr_wheel[(sgt.mid_tick >> 5) & 3]); + gui_render_text(elem, x, y + 20, 0xffffff, sys.work_buf); + sprintf(sys.work_buf, COL_NEON"%s"COL_RESET"\n%d", STR_OPL_TICK, sgt.mid_tick); + gui_render_text(elem, x, y + 60, 0xffffff, sys.work_buf); + sprintf(sys.work_buf, COL_NEON"%s"COL_RESET"\n%d / %d", STR_OPL_ACTIVE, bank.v_used, bank.voices.length); + gui_render_text(elem, x, y + 100, 0xffffff, sys.work_buf); + sprintf(sys.work_buf, COL_NEON"%s"COL_RESET"\n%d +%dQ", STR_OPL_TEMPO, sgt.mid.uspb ? (60000000 / sgt.mid.uspb) : 0, sgt.mid.tpqn); + gui_render_text(elem, x, y + 140, 0xffffff, sys.work_buf); + x = bx; + pthread_mutex_unlock(&sgt.lock); + y += 3; + gui_render_text(elem, x, y, 0xffffff, STR_OPL_CHANNEL); + gui_render_text(elem, x, y + 20 + 226, 0xffffff, STR_OPL_MODULATOR); + gui_render_text(elem, x, y + 40 + 226 + 232, 0xffffff, STR_OPL_CARRIER); + gui_render_text(elem, x, y + 60 + 226 + 232 * 2, 0xffffff, STR_OPL_POINTER); + y -= 3; + // x -= 86; + x += nch * 6 + 2 + 1; + y += 20; + gui_render_text(elem, x, y + 0, 0xffffff, STR_OPL_OUTPUT); + gui_render_text(elem, x, y + 70, 0xffffff, STR_OPL_MODE); + gui_render_text(elem, x, y + 84, 0xffffff, STR_OPL_FEEDBACK); + gui_render_text(elem, x, y + 98, 0xffffff, STR_OPL_FREQUENCY); + y += 226 + 20; + for(int o = 0; o < 2; o++) { + gui_render_text(elem, x, y + 6, 0xffffff, STR_OPL_FLAGS); + gui_render_text(elem, x, y + 24, 0xffffff, STR_OPL_MULTIPLIER); + gui_render_text(elem, x, y + 42, 0xffffff, STR_OPL_LEVEL); + gui_render_text(elem, x, y + 96, 0xffffff, STR_OPL_KEYSCALE); + gui_render_text(elem, x, y + 110, 0xffffff, STR_OPL_WAVEFORM); + gui_render_text(elem, x, y + 124, 0xffffff, STR_OPL_ATTACK); + gui_render_text(elem, x, y + 142, 0xffffff, STR_OPL_DECAY); + gui_render_text(elem, x, y + 160, 0xffffff, STR_OPL_SUSTAIN); + gui_render_text(elem, x, y + 178, 0xffffff, STR_OPL_RELEASE); + gui_render_text(elem, x, y + 196, 0xffffff, STR_OPL_ENVELOPE); + y += 232 + 20; + } + return; + } + y += 20; + y += gui_render_rect(elem, x, y, nch * 6 + 2, 226, 0x3f0000) + 20; + y += gui_render_rect(elem, x, y, nch * 6 + 2, 232, 0x003f00) + 20; + y += gui_render_rect(elem, x, y, nch * 6 + 2, 232, 0x00003f) + 20; + y += gui_render_rect(elem, x, y, nch * 6 + 2, 8, 0x2f2f2f); + for(int c = 0; c < nch; c++) { + OPLChannel channel = &chip.channel[c]; + x = bx + 2 + 6 * c; + y = by + 2; + short **out = channel.out; + short accm = *out[0] + *out[1] + *out[2] + *out[3]; + int chnlvl = 0; + for(int n = 0; n < 2; n++) { + chnlvl += (short)((accm * channel.level[n]) >> 16); + } + chnlvl = chnlvl < 0 ? (-chnlvl) : chnlvl; + chnlvl = (chnlvl * 16) / 2048; + chnlvl = chnlvl > 64 ? 64 : chnlvl; + int freq = (int)((((float)channel.f_num * 49716.0f) / powf(2.0f, 20.0f - (float)channel.block)) / 6210.0f * 128.0f); + y += 20; + y += gui_render_bar(elem, x, y, 4, 64, 0xff0000, 0x00ff00, 0x000000, chnlvl) + 2; + y += gui_render_rect(elem, x, y, 4, 4, ((channel.slots[0].eg_out <= 0x100) || (channel.slots[1].eg_out <= 0x100)) ? 0x00ff00 : 0x000000) + 2; + y += gui_render_rect(elem, x - (c & 1), y, 5, 4, channel.chtype != ch_2op ? 0xff00ff : 0x000000) + 2; + y += gui_render_grad(elem, x, y, 4, 4, channel.con ? 0x00ff00 : 0x808080, 0x0000ff) + 2; + y += gui_render_bar(elem, x, y, 4, 8, 0x00ff00, 0xffff00, 0x000000, channel.fb) + 2; + y += gui_render_bar(elem, x, y, 4, 128, 0x707070, 0x505050, 0x000000, freq) + 2; + gui_render_rect(elem, x, y - 130 + (127 - freq), 4, 1, 0xff0000); + for(int o = 0; o < 2; o++) { + opl3_slot *slot = channel.slots[o]; + y += 2; + y += 20; + y += gui_render_rect(elem, x, y, 4, 4, slot.key ? 0x00ff00 : 0x000000) + 2; + y += gui_render_rect(elem, x, y, 4, 4, (slot.trem != (byte*)&slot.chip.zeromod) ? 0x5fdf00 : 0x000000); + y += gui_render_rect(elem, x, y, 4, 4, slot.reg_vib ? 0xffff00 : 0x000000); + y += gui_render_rect(elem, x, y, 4, 4, slot.reg_type ? 0xff0000 : 0x000000); + y += gui_render_rect(elem, x, y, 4, 4, slot.reg_ksr ? 0x5f00bf : 0x000000) + 2; + y += gui_render_bar(elem, x, y, 4, 16, 0xff0000, 0xffff00, 0x000000, slot.reg_mult) + 2; + y += gui_render_bar(elem, x, y, 4, 64, 0xff0000, 0x00ff00, 0x000000, 63 - slot.reg_tl) + 2; + y += gui_render_bar(elem, x, y, 4, 4, 0x7f00ff, 0x3f00ff, 0x000000, slot.reg_ksl) + 2; + y += gui_render_rect(elem, x, y, 4, 8, 0x000000) + 2; + gui_render_rect(elem, x, y - 10 + (7 - slot.reg_wf), 4, 1, plr_waveforms[slot.reg_wf]); + + y += gui_render_bar(elem, x, y, 4, 16, slot.eg_gen == 0 ? 0xff0000 : 0xaf0000, slot.eg_gen == 0 ? 0x00ffff : 0x00afaf, 0x000000, slot.reg_ar) + 2; + y += gui_render_bar(elem, x, y, 4, 16, slot.eg_gen == 1 ? 0xff0000 : 0xaf0000, slot.eg_gen == 1 ? 0x00ffff : 0x00afaf, 0x000000, slot.reg_dr) + 2; + y += gui_render_bar(elem, x, y, 4, 16, slot.eg_gen == 2 ? 0xff0000 : 0xaf0000, slot.eg_gen == 2 ? 0x00ffff : 0x00afaf, 0x000000, (slot.reg_sl > 15) ? 0 : (15 - slot.reg_sl)) + 2; + y += gui_render_bar(elem, x, y, 4, 16, slot.eg_gen == 3 ? 0xff0000 : 0xaf0000, slot.eg_gen == 3 ? 0x00ffff : 0x00afaf, 0x000000, slot.reg_rr) + 2; + y += gui_render_bar(elem, x, y, 4, 32, 0xff0000, 0x0000ff, 0x000000, (0x1ff - slot.eg_rout) / 16) + 2; + } + y += 2; + y += 20; + gui_render_rect(elem, x, y, 4, 4, bank.voiceindex == c ? 0x00ffff : 0x000000); + } + pthread_mutex_unlock(&sgt.lock); +} + +byte gui_init_player(window_t *win, int frame_x, int frame_y, byte open) { + gui_add_fill(win, 0, 0, 696, 24, STR_TITLE_PLR_FULL); + + gui_add_field(win, 0, 24, 720, frame_y - 316, snd.mid_queue ? snd.mid_list : "", snd.mid_queue ? (strlen(snd.mid_list) + 1) : 0, NULL, 0, 0); + + gui_add_button(win, 0, frame_y - 24, 24, 24, SYM_PLAY, gui_fnc_plr_start); + gui_add_button(win, 24, frame_y - 24, 24, 24, SYM_STOP, gui_fnc_plr_stop); + gui_add_button(win, 48, frame_y - 24, 24, 24, SYM_PAUSE, gui_fnc_plr_pause); + gui_add_enum(win, 72, frame_y - 24, 24, 24, STR_PLR_MODE, gui_fnc_plr_mode, gui_plr_modes, 4, 0, snd.mid_repeat).format = gui_fmt_enums; + + gui_add_button(win, 120, frame_y - 24, 24, 24, SYM_PREV, gui_fnc_plr_prev); + gui_add_button(win, 144, frame_y - 24, 24, 24, SYM_NEXT, gui_fnc_plr_next); + gui_add_button(win, 168, frame_y - 24, 24, 24, SYM_PRANDOM, gui_fnc_plr_rand); + gui_add_field(win, 192, frame_y - 24, 48, 24, SYM_JUMPTO "#", 0, gui_fnc_plr_jump, 1, 1); + + gui_add_button(win, 264, frame_y - 24, 24, 24, SYM_DELETE, gui_fnc_plr_clear); + cvar_t *cv = cvar_get("mid_opl_bank"); + gui_t *elem = gui_add_dropup(win, 288, frame_y - 24, 192, 24, STR_MID_BANK, gui_fnc_cvar_bool, snd_banknames, cv.max, cv.def, cv.value); + elem.cv_data = cv; + elem = elem + 1; + elem.capacity = GUI_AUX_STR; + elem.text = win.str_aux; + elem.format = gui_fmt_bankdrop; + elem.cv_data = cv; + gui_add_field(win, 480, frame_y - 24, 216, 24, "", 0, gui_fnc_plr_load, 1, 1); + gui_add_button(win, 696, frame_y - 24, 24, 24, SYM_FOLDER, gui_fnc_plr_open); + + gui_add_field(win, 0, frame_y - 268, 720, 120, snd.mid_info, 6 * SND_INFO, NULL, 0, 0); + gui_add_field(win, 0, frame_y - 124, 720, 100, snd.mid_klogger, SND_KLOG * SND_KAR, NULL, 0, 0); + + gui_add_fill(win, 0, frame_y - 292, 720, 24, STR_PLR_INFO); + gui_add_fill(win, 96, frame_y - 24, 24, 24, ""); + gui_add_fill(win, 240, frame_y - 24, 24, 24, ""); + gui_add_fill(win, 0, frame_y - 148, 720, 24, STR_PLR_KARAOKE); + + gui_add_custom(win, 720, 0, frame_x - 720, frame_y, gui_render_player); + + gui_add_cvtoggle(win, 696, 0, 24, 24, "V", "mid_visualizer").format = gui_fmt_keep; + + // gui_add_nbutton(win, 0, 0, 160, 24, STR_BACK, GUI_MENU); + gui_plr_update(win); + // gui_reformat(); + return 0; +} +*/ + + private void push() { for(int z = 0; z < 2 * 2; z++) { - this.buffer[this.smppos * 2 * 2 + z] = (byte)((data >> ((z & 1) * 8)) & 0xff); + this.buffer[this.smppos * 2 * 2 + z] = (byte)((this.data[z >> 1] >> ((z & 1) * 8)) & 0xff); } this.smppos++; if(this.smppos == this.blocksize) { @@ -87,12 +2381,31 @@ public class AudioInterface implements Runnable { } } - private short generate() { - long mix = 0; + private long process() { + if(this.mid != null) { + long elapsed = this.mid.process(); + if(elapsed < 0L) + this.mid = null; + else + return elapsed; + } + return 1000L; + } + + private void generate() { + long mixLeft = 0; + long mixRight = 0; + if(this.mid != null) { + int[] data = this.mid.chip.generate(); + mixLeft = (long)data[0] * (long)this.volumes[Volume.MUSIC.ordinal()] * (long)this.volumes[0]; + mixRight = (long)data[1] * (long)this.volumes[Volume.MUSIC.ordinal()] * (long)this.volumes[0]; + } for(int z = 0; z < this.channels.length; z++) { Channel ch = this.channels[z]; if(ch.run) { - mix += (long)ch.buffer[ch.pos] * (long)ch.level * (((long)this.volumes[ch.type] * (long)this.volumes[0]) / 32767); + long frame = (long)ch.buffer[ch.pos] * (long)ch.level * (((long)this.volumes[ch.type] * (long)this.volumes[0]) / 32767); + mixLeft += frame; + mixRight += frame; if(++ch.pos >= ch.buffer.length) { ch.pos = 0; if(!ch.loop) @@ -100,13 +2413,15 @@ public class AudioInterface implements Runnable { } } } - mix /= 32767L * 32767L; - long amp = Math.abs(mix); + mixLeft /= 32767L * 32767L; + mixRight /= 32767L * 32767L; + long amp = Math.max(Math.abs(mixLeft), Math.abs(mixRight)); if(amp > 32767L) this.peak = Math.max(this.peak, amp); else if(this.peak > 32767L) this.peak = Math.max(this.peak - 3L, 32767L); - return (short)((mix * 32767L) / this.peak); + this.data[0] = (short)((mixLeft * 32767L) / this.peak); + this.data[1] = (short)((mixRight * 32767L) / this.peak); } private void dispatch(Object buf, Cmd op, short addr, int param) { @@ -132,6 +2447,9 @@ public class AudioInterface implements Runnable { case QUERY: this.waiting = false; break; + case MIDI: + this.mid = (MidiHandle)buf; + break; } } @@ -162,9 +2480,10 @@ public class AudioInterface implements Runnable { this.dispatch(cmd.buffer, cmd.command, cmd.address, cmd.param); } } - elapsed += 1000L; + elapsed += this.process(); while((elapsed >= sampletime)) { - this.push(this.generate()); + this.generate(); + this.push(); elapsed -= sampletime; } } @@ -254,5 +2573,13 @@ public class AudioInterface implements Runnable { public void alGain(int source, float value) { this.instruct(null, Cmd.LEVEL, source, (int)(value * 32767.0f)); - } + } + + public void alMidi(MidiHandle midi) { + this.instruct(midi, Cmd.MIDI, 0, 0); + } + + public MidiHandle alGetMidi() { + return this.mid; + } } diff --git a/client/src/main/java/client/audio/OPLChip.java b/client/src/main/java/client/audio/OPLChip.java new file mode 100644 index 00000000..c6909df3 --- /dev/null +++ b/client/src/main/java/client/audio/OPLChip.java @@ -0,0 +1,978 @@ +package client.audio; + +public class OPLChip { + private static enum ChType { + CH2OP, + CH4OP1, + CH4OP2 + }; + + private static enum EnvState { + ATTACK, + DECAY, + SUSTAIN, + RELEASE + }; + + private static interface ModGen { + short getModulation(); + } + + private static interface envelope_sinfunc { + short calcPhase(short phase, short envelope); + } + + public class OPLSlot implements ModGen { + OPLChannel channel; + short out; + short fbmodv; + final ModGen fbmod = new ModGen() { + public short getModulation() { + return OPLSlot.this.fbmodv; + } + }; + ModGen mod; + short prout; + short eg_rout; + short eg_out; + EnvState eg_gen; + byte eg_ksl; + ModGen trem; + byte reg_vib; + byte reg_type; + byte reg_ksr; + byte reg_mult; + byte reg_ksl; + byte reg_tl; + byte reg_ar; + byte reg_dr; + byte reg_sl; + byte reg_rr; + byte reg_wf; + boolean key; + boolean detrigger; + boolean retrigger; + boolean pg_reset; + int pg_phase; + short pg_phase_out; + int slot_num; + + public short getModulation() { + return this.out; + } + + private void process() + { + // feedback + if (this.channel.fb != 0x00) + { + this.fbmodv = (short)((this.prout + this.out) >> (0x09 - this.channel.fb)); + } + else + { + this.fbmodv = 0; + } + this.prout = this.out; + // envelope + boolean nonzero; + byte rate; + byte rate_hi; + byte rate_lo; + byte reg_rate = 0; + byte ks; + byte eg_shift, shift; + short eg_rout; + short eg_inc; + boolean eg_off; + boolean reset = false; + if(this.retrigger) { + this.eg_rout = 0x1ff; + } + this.eg_out = (short)(this.eg_rout + (this.reg_tl << 2) + + (this.eg_ksl >> KSL_SHIFT[this.reg_ksl]) + this.trem.getModulation()); + if (this.key && this.eg_gen == EnvState.RELEASE) + { + reset = true; + reg_rate = this.reg_ar; + } + else + { + switch (this.eg_gen) + { + case ATTACK: + reg_rate = this.reg_ar; + break; + case DECAY: + reg_rate = this.reg_dr; + break; + case SUSTAIN: + if (this.reg_type == 0) + { + reg_rate = this.reg_rr; + } + break; + case RELEASE: + reg_rate = this.reg_rr; + break; + } + } + this.pg_reset = reset; + ks = (byte)(this.channel.ksv >> ((this.reg_ksr ^ 1) << 1)); + nonzero = (reg_rate != 0); + rate = (byte)(ks + (reg_rate << 2)); + rate_hi = (byte)(rate >> 2); + rate_lo = (byte)(rate & 0x03); + if ((rate_hi & 0x10) != 0) + { + rate_hi = 0x0f; + } + eg_shift = (byte)(rate_hi + OPLChip.this.eg_add); + shift = 0; + if (nonzero) + { + if (rate_hi < 12) + { + if (OPLChip.this.eg_state != 0) + { + switch (eg_shift) + { + case 12: + shift = 1; + break; + case 13: + shift = (byte)((rate_lo >> 1) & 0x01); + break; + case 14: + shift = (byte)(rate_lo & 0x01); + break; + default: + break; + } + } + } + else + { + shift = (byte)((rate_hi & 0x03) + EG_INC[rate_lo][OPLChip.this.timer & 0x03]); + if ((shift & 0x04) != 0) + { + shift = 0x03; + } + if (shift == 0) + { + shift = OPLChip.this.eg_state; + } + } + } + eg_rout = this.eg_rout; + eg_inc = 0; + eg_off = false; + /* Instant attack */ + if (reset && rate_hi == 0x0f) + { + eg_rout = 0x00; + } + /* Envelope off */ + if ((this.eg_rout & 0x1f8) == 0x1f8) + { + eg_off = true; + } + if (this.eg_gen != EnvState.ATTACK && !reset && eg_off) + { + eg_rout = 0x1ff; + } + switch (this.eg_gen) + { + case ATTACK: + if (this.eg_rout == 0) + { + this.eg_gen = EnvState.DECAY; + } + else if (this.key && shift > 0 && rate_hi != 0x0f) + { + eg_inc = (short)(~this.eg_rout >> (4 - shift)); + } + break; + case DECAY: + if ((this.eg_rout >> 4) == this.reg_sl) + { + this.eg_gen = EnvState.SUSTAIN; + } + else if (!eg_off && !reset && shift > 0) + { + eg_inc = (short)(1 << (shift - 1)); + } + break; + case SUSTAIN: + case RELEASE: + if (!eg_off && !reset && shift > 0) + { + eg_inc = (short)(1 << (shift - 1)); + } + break; + } + this.eg_rout = (short)((eg_rout + eg_inc) & 0x1ff); + /* Key off */ + if (reset) + { + this.eg_gen = EnvState.ATTACK; + } + if (!(this.key)) + { + this.eg_gen = EnvState.RELEASE; + } + this.detrigger = false; + this.retrigger = false; + // phase + short f_num; + int basefreq; + short phase; + f_num = this.channel.f_num; + if (this.reg_vib != 0) + { + byte range; + byte vibpos; + range = (byte)((f_num >> 7) & 7); + vibpos = OPLChip.this.vibpos; + if ((vibpos & 3) == 0) + { + range = 0; + } + else if ((vibpos & 1) != 0) + { + range >>= 1; + } + range >>= OPLChip.this.vibshift; + if ((vibpos & 4) != 0) + { + range = (byte)-range; + } + f_num += range; + } + basefreq = (f_num << this.channel.block) >> 1; + phase = (short)(this.pg_phase >> 9); + if (this.pg_reset) + { + this.pg_phase = 0; + } + this.pg_phase += (basefreq * MULT[this.reg_mult]) >> 1; + this.pg_phase_out = phase; + // output + this.out = WAVE_FUNCS[this.reg_wf].calcPhase((short)(this.pg_phase_out + this.mod.getModulation()), this.eg_out); + } + + private void updateKSL() + { + short ksl = (short)((KSL[this.channel.f_num >> 6] << 2) + - ((0x08 - this.channel.block) << 5)); + if (ksl < 0) + { + ksl = 0; + } + this.eg_ksl = (byte)ksl; + } + + private void keyOn() + { + this.key = true; + if(this.detrigger) { + this.eg_gen = EnvState.RELEASE; + // this.eg_rout = 0x1ff; + // this.eg_out = this.eg_rout = 0x1ff; + this.detrigger = false; + } + this.retrigger = true; + } + + private void keyOff() + { + this.key = false; + this.detrigger = true; + this.retrigger = false; + } + + public void setFlags(boolean tremolo, boolean vibrato, boolean sustaining, boolean ksr) { + if (tremolo) + { + this.trem = OPLChip.this.tremolo; + } + else + { + this.trem = OPLChip.this.zeromod; + } + this.reg_vib = (byte)(vibrato ? 1 : 0); + this.reg_type = (byte)(sustaining ? 1 : 0); + this.reg_ksr = (byte)(ksr ? 1 : 0); + } + + public void setMultiplier(int mult) { + this.reg_mult = (byte)(mult & 0x0f); + } + + public void setKSL(int ksl) { + this.reg_ksl = (byte)(ksl & 0x03); + this.updateKSL(); + } + + public void setLevel(int level) { + this.reg_tl = (byte)(level & 0x3f); + } + + public void setEnvelope(int attack, int decay, int sustain, int release) { + this.reg_ar = (byte)(attack & 0x0f); + this.reg_dr = (byte)(decay & 0x0f); + this.reg_sl = (byte)(sustain & 0x0f); + if (this.reg_sl == 0x0f) + { + this.reg_sl = 0x1f; + } + this.reg_rr = (byte)(release & 0x0f); + if (this.reg_rr == 0x00) + { + this.reg_rr = 0x01; + } + } + + public void setWaveform(int waveform) { + this.reg_wf = (byte)(waveform & 0x07); + } + }; + + public class OPLChannel { + OPLSlot[] slots = new OPLSlot[2]; + OPLChannel pair; + ModGen[] out = new ModGen[4]; + + int[] level = new int[2]; + + ChType chtype; + short f_num; + byte block; + byte fb; + byte con; + byte alg; + byte ksv; + int ch_num; + + private void setupAlgo() + { + if ((this.alg & 0x08) != 0) + { + return; + } + if ((this.alg & 0x04) != 0) + { + this.pair.out[0] = OPLChip.this.zeromod; + this.pair.out[1] = OPLChip.this.zeromod; + this.pair.out[2] = OPLChip.this.zeromod; + this.pair.out[3] = OPLChip.this.zeromod; + switch (this.alg & 0x03) + { + case 0x00: + this.pair.slots[0].mod = this.pair.slots[0].fbmod; + this.pair.slots[1].mod = this.pair.slots[0]; + this.slots[0].mod = this.pair.slots[1]; + this.slots[1].mod = this.slots[0]; + this.out[0] = this.slots[1]; + this.out[1] = OPLChip.this.zeromod; + this.out[2] = OPLChip.this.zeromod; + this.out[3] = OPLChip.this.zeromod; + break; + case 0x01: + this.pair.slots[0].mod = this.pair.slots[0].fbmod; + this.pair.slots[1].mod = this.pair.slots[0]; + this.slots[0].mod = OPLChip.this.zeromod; + this.slots[1].mod = this.slots[0]; + this.out[0] = this.pair.slots[1]; + this.out[1] = this.slots[1]; + this.out[2] = OPLChip.this.zeromod; + this.out[3] = OPLChip.this.zeromod; + break; + case 0x02: + this.pair.slots[0].mod = this.pair.slots[0].fbmod; + this.pair.slots[1].mod = OPLChip.this.zeromod; + this.slots[0].mod = this.pair.slots[1]; + this.slots[1].mod = this.slots[0]; + this.out[0] = this.pair.slots[0]; + this.out[1] = this.slots[1]; + this.out[2] = OPLChip.this.zeromod; + this.out[3] = OPLChip.this.zeromod; + break; + case 0x03: + this.pair.slots[0].mod = this.pair.slots[0].fbmod; + this.pair.slots[1].mod = OPLChip.this.zeromod; + this.slots[0].mod = this.pair.slots[1]; + this.slots[1].mod = OPLChip.this.zeromod; + this.out[0] = this.pair.slots[0]; + this.out[1] = this.slots[0]; + this.out[2] = this.slots[1]; + this.out[3] = OPLChip.this.zeromod; + break; + } + } + else + { + switch (this.alg & 0x01) + { + case 0x00: + this.slots[0].mod = this.slots[0].fbmod; + this.slots[1].mod = this.slots[0]; + this.out[0] = this.slots[1]; + this.out[1] = OPLChip.this.zeromod; + this.out[2] = OPLChip.this.zeromod; + this.out[3] = OPLChip.this.zeromod; + break; + case 0x01: + this.slots[0].mod = this.slots[0].fbmod; + this.slots[1].mod = OPLChip.this.zeromod; + this.out[0] = this.slots[0]; + this.out[1] = this.slots[1]; + this.out[2] = OPLChip.this.zeromod; + this.out[3] = OPLChip.this.zeromod; + break; + } + } + } + + private void updateAlgo() + { + this.alg = this.con; + if (this.chtype == ChType.CH4OP1) + { + this.pair.alg = (byte)(0x04 | (this.con << 1) | (this.pair.con)); + this.alg = 0x08; + this.pair.setupAlgo(); + } + else if (this.chtype == ChType.CH4OP2) + { + this.alg = (byte)(0x04 | (this.pair.con << 1) | (this.con)); + this.pair.alg = 0x08; + this.setupAlgo(); + } + else + { + this.setupAlgo(); + } + } + + public void setFrequency(int block, int f_num) { + if (this.chtype == ChType.CH4OP2) + { + return; + } + this.f_num = (short)(f_num & 0x3ff); + this.block = (byte)(block & 0x07); + this.ksv = (byte)((this.block << 1) + | ((this.f_num >> (0x09 - OPLChip.this.nts)) & 0x01)); + this.slots[0].updateKSL(); + this.slots[1].updateKSL(); + if (this.chtype == ChType.CH4OP1) + { + this.pair.f_num = this.f_num; + this.pair.block = this.block; + this.pair.ksv = this.ksv; + this.pair.slots[0].updateKSL(); + this.pair.slots[1].updateKSL(); + } + } + + public void setLevel(int output, int level) { + this.level[output & 1] = level; + } + + public void set4Op(boolean op4) { + if (op4) + { + this.chtype = ChType.CH4OP1; + this.pair.chtype = ChType.CH4OP2; + this.updateAlgo(); + } + else + { + this.chtype = ChType.CH2OP; + this.pair.chtype = ChType.CH2OP; + this.updateAlgo(); + this.pair.updateAlgo(); + } + } + + public void setFeedback(int feedback) { + this.fb = (byte)(feedback & 0x07); + } + + public void setAM(boolean am) { + this.con = (byte)(am ? 1 : 0); + this.updateAlgo(); + } + + public void keyOn() + { + if (this.chtype == ChType.CH4OP1) + { + this.slots[0].keyOn(); + this.slots[1].keyOn(); + this.pair.slots[0].keyOn(); + this.pair.slots[1].keyOn(); + } + else if (this.chtype == ChType.CH2OP) + { + this.slots[0].keyOn(); + this.slots[1].keyOn(); + } + } + + public void keyOff() + { + if (this.chtype == ChType.CH4OP1) + { + this.slots[0].keyOff(); + this.slots[1].keyOff(); + this.pair.slots[0].keyOff(); + this.pair.slots[1].keyOff(); + } + else if (this.chtype == ChType.CH2OP) + { + this.slots[0].keyOff(); + this.slots[1].keyOff(); + } + } + } + + private static final int RSM_FRAC = 10; + + //Tables + + private static final short[] LOG_SIN = { + 0x859, 0x6c3, 0x607, 0x58b, 0x52e, 0x4e4, 0x4a6, 0x471, + 0x443, 0x41a, 0x3f5, 0x3d3, 0x3b5, 0x398, 0x37e, 0x365, + 0x34e, 0x339, 0x324, 0x311, 0x2ff, 0x2ed, 0x2dc, 0x2cd, + 0x2bd, 0x2af, 0x2a0, 0x293, 0x286, 0x279, 0x26d, 0x261, + 0x256, 0x24b, 0x240, 0x236, 0x22c, 0x222, 0x218, 0x20f, + 0x206, 0x1fd, 0x1f5, 0x1ec, 0x1e4, 0x1dc, 0x1d4, 0x1cd, + 0x1c5, 0x1be, 0x1b7, 0x1b0, 0x1a9, 0x1a2, 0x19b, 0x195, + 0x18f, 0x188, 0x182, 0x17c, 0x177, 0x171, 0x16b, 0x166, + 0x160, 0x15b, 0x155, 0x150, 0x14b, 0x146, 0x141, 0x13c, + 0x137, 0x133, 0x12e, 0x129, 0x125, 0x121, 0x11c, 0x118, + 0x114, 0x10f, 0x10b, 0x107, 0x103, 0x0ff, 0x0fb, 0x0f8, + 0x0f4, 0x0f0, 0x0ec, 0x0e9, 0x0e5, 0x0e2, 0x0de, 0x0db, + 0x0d7, 0x0d4, 0x0d1, 0x0cd, 0x0ca, 0x0c7, 0x0c4, 0x0c1, + 0x0be, 0x0bb, 0x0b8, 0x0b5, 0x0b2, 0x0af, 0x0ac, 0x0a9, + 0x0a7, 0x0a4, 0x0a1, 0x09f, 0x09c, 0x099, 0x097, 0x094, + 0x092, 0x08f, 0x08d, 0x08a, 0x088, 0x086, 0x083, 0x081, + 0x07f, 0x07d, 0x07a, 0x078, 0x076, 0x074, 0x072, 0x070, + 0x06e, 0x06c, 0x06a, 0x068, 0x066, 0x064, 0x062, 0x060, + 0x05e, 0x05c, 0x05b, 0x059, 0x057, 0x055, 0x053, 0x052, + 0x050, 0x04e, 0x04d, 0x04b, 0x04a, 0x048, 0x046, 0x045, + 0x043, 0x042, 0x040, 0x03f, 0x03e, 0x03c, 0x03b, 0x039, + 0x038, 0x037, 0x035, 0x034, 0x033, 0x031, 0x030, 0x02f, + 0x02e, 0x02d, 0x02b, 0x02a, 0x029, 0x028, 0x027, 0x026, + 0x025, 0x024, 0x023, 0x022, 0x021, 0x020, 0x01f, 0x01e, + 0x01d, 0x01c, 0x01b, 0x01a, 0x019, 0x018, 0x017, 0x017, + 0x016, 0x015, 0x014, 0x014, 0x013, 0x012, 0x011, 0x011, + 0x010, 0x00f, 0x00f, 0x00e, 0x00d, 0x00d, 0x00c, 0x00c, + 0x00b, 0x00a, 0x00a, 0x009, 0x009, 0x008, 0x008, 0x007, + 0x007, 0x007, 0x006, 0x006, 0x005, 0x005, 0x005, 0x004, + 0x004, 0x004, 0x003, 0x003, 0x003, 0x002, 0x002, 0x002, + 0x002, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, + 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000 + }; + + private static final short[] EXP = { + 0x7fa, 0x7f5, 0x7ef, 0x7ea, 0x7e4, 0x7df, 0x7da, 0x7d4, + 0x7cf, 0x7c9, 0x7c4, 0x7bf, 0x7b9, 0x7b4, 0x7ae, 0x7a9, + 0x7a4, 0x79f, 0x799, 0x794, 0x78f, 0x78a, 0x784, 0x77f, + 0x77a, 0x775, 0x770, 0x76a, 0x765, 0x760, 0x75b, 0x756, + 0x751, 0x74c, 0x747, 0x742, 0x73d, 0x738, 0x733, 0x72e, + 0x729, 0x724, 0x71f, 0x71a, 0x715, 0x710, 0x70b, 0x706, + 0x702, 0x6fd, 0x6f8, 0x6f3, 0x6ee, 0x6e9, 0x6e5, 0x6e0, + 0x6db, 0x6d6, 0x6d2, 0x6cd, 0x6c8, 0x6c4, 0x6bf, 0x6ba, + 0x6b5, 0x6b1, 0x6ac, 0x6a8, 0x6a3, 0x69e, 0x69a, 0x695, + 0x691, 0x68c, 0x688, 0x683, 0x67f, 0x67a, 0x676, 0x671, + 0x66d, 0x668, 0x664, 0x65f, 0x65b, 0x657, 0x652, 0x64e, + 0x649, 0x645, 0x641, 0x63c, 0x638, 0x634, 0x630, 0x62b, + 0x627, 0x623, 0x61e, 0x61a, 0x616, 0x612, 0x60e, 0x609, + 0x605, 0x601, 0x5fd, 0x5f9, 0x5f5, 0x5f0, 0x5ec, 0x5e8, + 0x5e4, 0x5e0, 0x5dc, 0x5d8, 0x5d4, 0x5d0, 0x5cc, 0x5c8, + 0x5c4, 0x5c0, 0x5bc, 0x5b8, 0x5b4, 0x5b0, 0x5ac, 0x5a8, + 0x5a4, 0x5a0, 0x59c, 0x599, 0x595, 0x591, 0x58d, 0x589, + 0x585, 0x581, 0x57e, 0x57a, 0x576, 0x572, 0x56f, 0x56b, + 0x567, 0x563, 0x560, 0x55c, 0x558, 0x554, 0x551, 0x54d, + 0x549, 0x546, 0x542, 0x53e, 0x53b, 0x537, 0x534, 0x530, + 0x52c, 0x529, 0x525, 0x522, 0x51e, 0x51b, 0x517, 0x514, + 0x510, 0x50c, 0x509, 0x506, 0x502, 0x4ff, 0x4fb, 0x4f8, + 0x4f4, 0x4f1, 0x4ed, 0x4ea, 0x4e7, 0x4e3, 0x4e0, 0x4dc, + 0x4d9, 0x4d6, 0x4d2, 0x4cf, 0x4cc, 0x4c8, 0x4c5, 0x4c2, + 0x4be, 0x4bb, 0x4b8, 0x4b5, 0x4b1, 0x4ae, 0x4ab, 0x4a8, + 0x4a4, 0x4a1, 0x49e, 0x49b, 0x498, 0x494, 0x491, 0x48e, + 0x48b, 0x488, 0x485, 0x482, 0x47e, 0x47b, 0x478, 0x475, + 0x472, 0x46f, 0x46c, 0x469, 0x466, 0x463, 0x460, 0x45d, + 0x45a, 0x457, 0x454, 0x451, 0x44e, 0x44b, 0x448, 0x445, + 0x442, 0x43f, 0x43c, 0x439, 0x436, 0x433, 0x430, 0x42d, + 0x42a, 0x428, 0x425, 0x422, 0x41f, 0x41c, 0x419, 0x416, + 0x414, 0x411, 0x40e, 0x40b, 0x408, 0x406, 0x403, 0x400 + }; + + private static final byte[] MULT = { + 1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 20, 24, 24, 30, 30 + }; + + private static final byte[] KSL = { + 0, 32, 40, 45, 48, 51, 53, 55, 56, 58, 59, 60, 61, 62, 63, 64 + }; + + private static final byte[] KSL_SHIFT = { + 8, 1, 2, 0 + }; + + private static final byte[][] EG_INC = { + { 0, 0, 0, 0 }, + { 1, 0, 0, 0 }, + { 1, 0, 1, 0 }, + { 1, 1, 1, 0 } + }; + + private static final envelope_sinfunc[] WAVE_FUNCS = { + OPLChip::wave0, + OPLChip::wave1, + OPLChip::wave2, + OPLChip::wave3, + OPLChip::wave4, + OPLChip::wave5, + OPLChip::wave6, + OPLChip::wave7 + }; + + final OPLChannel[] channel; + final OPLSlot[] slot; + final ModGen tremolo = new ModGen() { + public short getModulation() { + return OPLChip.this.tremolov; + } + }; + final ModGen zeromod = new ModGen() { + public short getModulation() { + return 0; + } + }; + private final int[] mixbuff = new int[2]; + private final int[] oldsamples = new int[2]; + private final int[] samples = new int[2]; + private final int[] output = new int[2]; + private final int rateratio; + + private final byte nts; + private final byte vibshift; + private final byte tremoloshift; + + private short timer; + private long eg_timer; + private byte eg_timerrem; + private byte eg_state; + private byte eg_add; + private byte vibpos; + private byte tremolov; + private byte tremolopos; + private int noise; + + private int samplecnt; + + /* input: [0, 256), output: [0, 65536] */ + private static int oplSin(double x) { + return ((int)(Math.sin((x) * Math.PI / 512.0) * 65536.0)); + } + + //Envelope generator + + private static short waveExp(int level) + { + if (level > 0x1fff) + { + level = 0x1fff; + } + return (short)((EXP[level & 0xff] << 1) >> (level >> 8)); + } + + private static short wave0(short phase, short envelope) + { + short out = 0; + short neg = 0; + phase &= 0x3ff; + if ((phase & 0x200) != 0) + { + neg = (short)0xffff; + } + if ((phase & 0x100) != 0) + { + out = LOG_SIN[(phase & 0xff) ^ 0xff]; + } + else + { + out = LOG_SIN[phase & 0xff]; + } + return (short)(waveExp(out + (envelope << 3)) ^ neg); + } + + private static short wave1(short phase, short envelope) + { + short out = 0; + phase &= 0x3ff; + if ((phase & 0x200) != 0) + { + out = 0x1000; + } + else if ((phase & 0x100) != 0) + { + out = LOG_SIN[(phase & 0xff) ^ 0xff]; + } + else + { + out = LOG_SIN[phase & 0xff]; + } + return waveExp(out + (envelope << 3)); + } + + private static short wave2(short phase, short envelope) + { + short out = 0; + phase &= 0x3ff; + if ((phase & 0x100) != 0) + { + out = LOG_SIN[(phase & 0xff) ^ 0xff]; + } + else + { + out = LOG_SIN[phase & 0xff]; + } + return waveExp(out + (envelope << 3)); + } + + private static short wave3(short phase, short envelope) + { + short out = 0; + phase &= 0x3ff; + if ((phase & 0x100) != 0) + { + out = 0x1000; + } + else + { + out = LOG_SIN[phase & 0xff]; + } + return waveExp(out + (envelope << 3)); + } + + private static short wave4(short phase, short envelope) + { + short out = 0; + short neg = 0; + phase &= 0x3ff; + if ((phase & 0x300) == 0x100) + { + neg = (short)0xffff; + } + if ((phase & 0x200) != 0) + { + out = 0x1000; + } + else if ((phase & 0x80) != 0) + { + out = LOG_SIN[((phase ^ 0xff) << 1) & 0xff]; + } + else + { + out = LOG_SIN[(phase << 1) & 0xff]; + } + return (short)(waveExp(out + (envelope << 3)) ^ neg); + } + + private static short wave5(short phase, short envelope) + { + short out = 0; + phase &= 0x3ff; + if ((phase & 0x200) != 0) + { + out = 0x1000; + } + else if ((phase & 0x80) != 0) + { + out = LOG_SIN[((phase ^ 0xff) << 1) & 0xff]; + } + else + { + out = LOG_SIN[(phase << 1) & 0xff]; + } + return waveExp(out + (envelope << 3)); + } + + private static short wave6(short phase, short envelope) + { + short neg = 0; + phase &= 0x3ff; + if ((phase & 0x200) != 0) + { + neg = (short)0xffff; + } + return (short)(waveExp(envelope << 3) ^ neg); + } + + private static short wave7(short phase, short envelope) + { + short out = 0; + short neg = 0; + phase &= 0x3ff; + if ((phase & 0x200) != 0) + { + neg = (short)0xffff; + phase = (short)((phase & 0x1ff) ^ 0x1ff); + } + out = (short)(phase << 3); + return (short)(waveExp(out + (envelope << 3)) ^ neg); + } + + private void genFrame() + { + int[] mix = new int[2]; + + this.samples[0] = this.mixbuff[0]; + this.samples[1] = this.mixbuff[1]; + + for (int ii = 0; ii < this.slot.length; ii++) + { + this.slot[ii].process(); + } + + mix[0] = mix[1] = 0; + for (int ii = 0; ii < this.channel.length; ii++) + { + OPLChannel channel = this.channel[ii]; + ModGen[] out = channel.out; + int accm = out[0].getModulation() + out[1].getModulation() + out[2].getModulation() + out[3].getModulation(); + mix[0] += (accm * channel.level[0]) >> 16; + mix[1] += (accm * channel.level[1]) >> 16; + } + this.mixbuff[0] = mix[0]; + this.mixbuff[1] = mix[1]; + + if ((this.timer & 0x3f) == 0x3f) + { + this.tremolopos = (byte)((this.tremolopos + 1) % 210); + } + if (this.tremolopos < 105) + { + this.tremolov = (byte)(this.tremolopos >> this.tremoloshift); + } + else + { + this.tremolov = (byte)((210 - this.tremolopos) >> this.tremoloshift); + } + + if ((this.timer & 0x3ff) == 0x3ff) + { + this.vibpos = (byte)((this.vibpos + 1) & 7); + } + + this.timer++; + + this.eg_add = 0; + if (this.eg_timer != 0) + { + byte shift = 0; + while (shift < 36 && ((this.eg_timer >> shift) & 1) == 0) + { + shift++; + } + if (shift > 12) + { + this.eg_add = 0; + } + else + { + this.eg_add = (byte)(shift + 1); + } + } + + if (this.eg_timerrem != 0 || this.eg_state != 0) + { + if (this.eg_timer == 0xfffffffffL) + { + this.eg_timer = 0; + this.eg_timerrem = 1; + } + else + { + this.eg_timer++; + this.eg_timerrem = 0; + } + } + + this.eg_state ^= 1; + } + + public int[] generate() + { + while (this.samplecnt >= this.rateratio) + { + this.oldsamples[0] = this.samples[0]; + this.oldsamples[1] = this.samples[1]; + this.genFrame(); + this.samplecnt -= this.rateratio; + } + this.output[0] = (this.oldsamples[0] * (this.rateratio - this.samplecnt) + + this.samples[0] * this.samplecnt) / this.rateratio; + this.output[1] = (this.oldsamples[1] * (this.rateratio - this.samplecnt) + + this.samples[1] * this.samplecnt) / this.rateratio; + this.samplecnt += 1 << RSM_FRAC; + return this.output; + } + + public OPLChip(int samplerate, int voices, int nts, boolean deeptremolo, boolean deepvibrato) { + this.channel = new OPLChannel[voices]; + this.slot = new OPLSlot[voices * 2]; + this.rateratio = (samplerate << RSM_FRAC) / 49716; + this.nts = (byte)nts; + this.tremoloshift = (byte)(deeptremolo ? 2 : 4); + this.vibshift = (byte)(deepvibrato ? 0 : 1); + for (int slotnum = 0; slotnum < this.slot.length; slotnum++) + { + OPLSlot slot = this.slot[slotnum] = new OPLSlot(); + slot.mod = this.zeromod; + slot.eg_rout = 0x1ff; + slot.eg_out = 0x1ff; + slot.eg_gen = EnvState.RELEASE; + slot.trem = this.zeromod; + slot.slot_num = slotnum; + } + for (int channum = 0; channum < this.channel.length; channum++) + { + OPLChannel channel = this.channel[channum] = new OPLChannel(); + channel.slots[0] = this.slot[channum * 2]; + channel.slots[1] = this.slot[channum * 2 + 1]; + this.slot[channum * 2].channel = channel; + this.slot[channum * 2 + 1].channel = channel; + channel.out[0] = this.zeromod; + channel.out[1] = this.zeromod; + channel.out[2] = this.zeromod; + channel.out[3] = this.zeromod; + channel.chtype = ChType.CH2OP; + channel.level[0] = 0x10000; + channel.level[1] = 0x10000; + channel.ch_num = channum; + } + for (int channum = 0; channum < this.channel.length; channum++) + { + this.channel[channum].pair = this.channel[(channum & 1) != 0 ? (channum - 1) : (channum + 1)]; + } + for (int channum = 0; channum < this.channel.length; channum++) + { + this.channel[channum].setupAlgo(); + } + } + + public boolean isPlaying() { + for(OPLSlot slot : this.slot) { + if(slot.eg_out <= 0x100) + return true; + } + return false; + } + +} \ No newline at end of file diff --git a/client/src/main/java/client/gui/GuiMenu.java b/client/src/main/java/client/gui/GuiMenu.java index cb132539..55f59c28 100644 --- a/client/src/main/java/client/gui/GuiMenu.java +++ b/client/src/main/java/client/gui/GuiMenu.java @@ -1,6 +1,9 @@ package client.gui; +import java.io.File; + import client.Client; +import client.Client.FileMode; import client.gui.character.GuiChar; import client.gui.character.GuiCharacters; import client.gui.element.ActButton; @@ -104,6 +107,15 @@ public class GuiMenu extends Gui { GuiMenu.this.gm.interrupted = true; } }, "Client schließen")); + this.add(new ActButton(0, 110, 180, 0, new ButtonCallback() { + public void use(ActButton elem, PressType action) { + GuiMenu.this.gm.showFileDialog(FileMode.FILE_LOAD, "MIDI laden", null, new FileCallback() { + public void selected(File file) { + GuiMenu.this.gm.playMidi(file); + } + }); + } + }, "MIDI-Player")); this.shift(); this.add(new Label(4, 4, 200, 0, Color.VIOLET + Client.VERSION, true)); this.splashLabel = this.add(new Label(0, 100, width, 0, "")); @@ -120,6 +132,15 @@ public class GuiMenu extends Gui { // GuiMenu.this.gm.displayGuiScreen(INSTANCE); } }, "Server verlassen")); + this.add(new ActButton(0, this.gm.charEditor ? 70 : 90, 180, 0, new ButtonCallback() { + public void use(ActButton elem, PressType action) { + GuiMenu.this.gm.showFileDialog(FileMode.FILE_LOAD, "MIDI laden", null, new FileCallback() { + public void selected(File file) { + GuiMenu.this.gm.playMidi(file); + } + }); + } + }, "MIDI-Player")); this.shift(); } } diff --git a/client/src/main/java/client/gui/options/GuiSound.java b/client/src/main/java/client/gui/options/GuiSound.java index 5cfcd23b..1751f9f6 100644 --- a/client/src/main/java/client/gui/options/GuiSound.java +++ b/client/src/main/java/client/gui/options/GuiSound.java @@ -29,7 +29,20 @@ public class GuiSound extends GuiOptions { GuiSound.this.gm.restartSound(false); } }, "Übernehmen und Audio neu starten")); - + + + this.addSelector("mid_opl_voices", 0, 130, 240, 0); + this.addSelector("mid_opl_bank", 242, 130, 240, 0); + + this.addSelector("mid_velocity_func", 0, 150, 240, 0); + this.addSelector("mid_keep_notes", 242, 150, 240, 0); + + this.addSelector("mid_dont_fade", 0, 170, 240, 0); + this.addSelector("mid_play_unknown", 242, 170, 240, 0); + + this.addSelector("mid_visualizer", 0, 190, 240, 0); + this.addSelector("mid_debug_events", 242, 190, 240, 0); + super.init(width, height); } diff --git a/client/src/main/java/client/util/FileUtils.java b/client/src/main/java/client/util/FileUtils.java index 86f2731e..129c5514 100644 --- a/client/src/main/java/client/util/FileUtils.java +++ b/client/src/main/java/client/util/FileUtils.java @@ -10,7 +10,7 @@ import java.io.InputStream; import java.nio.charset.Charset; public class FileUtils { - private static final Charset UTF_8 = Charset.forName("UTF-8"); + public static final Charset UTF_8 = Charset.forName("UTF-8"); public static String read(File file) throws IOException { FileInputStream in = null; diff --git a/common/src/main/java/common/util/Util.java b/common/src/main/java/common/util/Util.java index 779ac110..7107fe31 100644 --- a/common/src/main/java/common/util/Util.java +++ b/common/src/main/java/common/util/Util.java @@ -37,6 +37,24 @@ public abstract class Util { public static final String VERSION = "v" + Version.MAJOR + "." + Version.MINOR + "." + Version.PATCH + Version.RELEASE; private static final String[] TIERS = new String[] {"II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X"}; private static final String[] NUMBERS = new String[] {"Zwei", "Drei", "Vier", "Fünf", "Sechs", "Sieben", "Acht", "Neun", "Zehn", "Elf", "Zwölf"}; + private static final int[] RNG_TABLE = { + 0x95, 0xbc, 0x64, 0x99, 0xc3, 0x31, 0x68, 0xc5, 0xba, 0xe2, 0x67, 0x66, 0xd1, 0x14, 0xce, 0xd7, + 0x4c, 0xdb, 0x78, 0x41, 0xb9, 0xf3, 0x38, 0x55, 0x71, 0x1b, 0x5f, 0x83, 0x3c, 0xaf, 0xa7, 0x9c, + 0x16, 0x9d, 0xe0, 0x54, 0x93, 0x5d, 0xaa, 0x2a, 0xef, 0xff, 0x6b, 0x4a, 0xfa, 0x39, 0x08, 0xcb, + 0x97, 0x8b, 0x86, 0x2c, 0x60, 0xa0, 0xac, 0x4e, 0x7a, 0xc9, 0xd3, 0x1e, 0xee, 0xbb, 0x9b, 0x09, + 0xca, 0x33, 0x9e, 0x0d, 0xbe, 0x75, 0x9a, 0x29, 0xf7, 0x88, 0xdd, 0x5c, 0x7e, 0xa3, 0x58, 0x94, + 0x00, 0x74, 0xad, 0x17, 0x07, 0x84, 0x7c, 0x63, 0x76, 0xb6, 0x1c, 0xa1, 0xb8, 0x79, 0xf0, 0xf9, + 0xb3, 0x98, 0x3e, 0xeb, 0x8a, 0x5e, 0x6e, 0x6a, 0x70, 0x7d, 0xc8, 0xa4, 0x01, 0x42, 0x2f, 0xd9, + 0x2b, 0xf1, 0x90, 0x44, 0xea, 0xa6, 0xcd, 0x72, 0x18, 0x91, 0x22, 0xf4, 0x03, 0x2d, 0xe1, 0xb0, + 0xda, 0xb1, 0x6d, 0xe6, 0x37, 0xc0, 0xfb, 0x65, 0x8d, 0xe9, 0xfe, 0x28, 0xfc, 0x47, 0xd2, 0xe4, + 0xdf, 0x19, 0x4d, 0x8e, 0x80, 0x56, 0x1d, 0x21, 0xc1, 0x82, 0xc2, 0x27, 0x7b, 0x24, 0xa8, 0x8c, + 0x11, 0x43, 0xd8, 0xdc, 0xb7, 0x48, 0x85, 0x30, 0xf5, 0x0e, 0xd4, 0x89, 0x4b, 0x0c, 0x45, 0x87, + 0x04, 0x0b, 0xfd, 0x53, 0xd5, 0xf6, 0x06, 0xcf, 0xf2, 0xe7, 0x51, 0xe5, 0x50, 0x25, 0xed, 0x12, + 0x4f, 0x20, 0x96, 0x81, 0xae, 0x8f, 0xd0, 0x36, 0xc6, 0x3b, 0x1a, 0xb5, 0xa2, 0x13, 0x1f, 0x2e, + 0x0f, 0x34, 0x9f, 0x61, 0x05, 0x0a, 0xbf, 0xc7, 0x10, 0xa9, 0x23, 0x15, 0x5b, 0xf8, 0x6c, 0xd6, + 0xb4, 0xab, 0x49, 0x5a, 0x6f, 0xb2, 0x02, 0x59, 0x92, 0x73, 0xde, 0x3f, 0x3d, 0x46, 0x57, 0xe8, + 0x77, 0xcc, 0xa5, 0x3a, 0x62, 0x69, 0x35, 0xec, 0x26, 0x52, 0x7f, 0xc4, 0xe3, 0x40, 0xbd, 0x32 + }; private static boolean crashed; @@ -231,6 +249,16 @@ public abstract class Util { (((color & 255) * mul) / 255); } + public static void panic(String info, String msg) { + System.err.println("#################################################################"); + System.err.println("*** " + info + " ***"); + System.err.println(msg); + System.err.println("#################################################################"); + if(!GraphicsEnvironment.isHeadless()) + JOptionPane.showMessageDialog(null, msg, info, JOptionPane.ERROR_MESSAGE); + System.exit(1); + } + public static void checkPlatform() { int feature; try { @@ -242,22 +270,13 @@ public abstract class Util { String info = null; String msg = null; if(System.getProperty("os.name").startsWith("Mac")) { - info = "Inkompatibles Betriebssystem"; - msg = "Linux, *BSD oder Windows ist erforderlich, um dieses Programm\n" + - "auszuführen. Alle Versionen von Mac OS (X) sind nicht kompatibel."; + panic("Inkompatibles Betriebssystem", + "Linux, *BSD oder Windows ist erforderlich, um dieses Programm\n" + + "auszuführen. Alle Versionen von Mac OS (X) sind nicht kompatibel."); } else if(feature < MIN_JAVA_FEATURE) { - info = "Inkompatible Java-Version"; - msg = "Java " + MIN_JAVA_FEATURE + " oder höher ist erforderlich, um dieses Programm auszuführen."; - } - if(info != null) { - System.err.println("#################################################################"); - System.err.println("*** " + info + " ***"); - System.err.println(msg); - System.err.println("#################################################################"); - if(!GraphicsEnvironment.isHeadless()) - JOptionPane.showMessageDialog(null, msg, info, JOptionPane.ERROR_MESSAGE); - System.exit(1); + panic("Inkompatible Java-Version", + "Java " + MIN_JAVA_FEATURE + " oder höher ist erforderlich, um dieses Programm auszuführen."); } }