opl cleanup
This commit is contained in:
parent
43956af38d
commit
bbbace7f46
7 changed files with 8145 additions and 8135 deletions
|
@ -35,8 +35,8 @@ 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.MidiBank;
|
||||
import client.audio.MidiDecoder;
|
||||
import client.audio.SoundManager;
|
||||
import client.audio.Volume;
|
||||
import client.gui.FileCallback;
|
||||
|
@ -2594,7 +2594,7 @@ public class Client implements IThreadListener {
|
|||
|
||||
public void setMidiDebug() {
|
||||
if(this.audio != null) {
|
||||
MidiHandle midi = this.audio.alGetMidi();
|
||||
MidiDecoder midi = this.audio.alGetMidi();
|
||||
if(midi != null)
|
||||
midi.setDebug(this.midiDebug);
|
||||
}
|
||||
|
@ -3460,9 +3460,9 @@ public class Client implements IThreadListener {
|
|||
Log.SOUND.error(e, "Konnte Datei '%s' nicht laden", file);
|
||||
return;
|
||||
}
|
||||
MidiHandle midi;
|
||||
MidiDecoder midi;
|
||||
try {
|
||||
midi = new MidiHandle(data, this.midiNoWait, this.midiVoices, this.midiBank, this.midiKeep, this.midiUnknown, this.midiVelocity);
|
||||
midi = new MidiDecoder(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);
|
||||
|
@ -3473,7 +3473,7 @@ public class Client implements IThreadListener {
|
|||
}
|
||||
|
||||
public void testMidi(int start) {
|
||||
MidiHandle midi = new MidiHandle(this.midiVoices, this.midiBank, this.midiKeep, this.midiUnknown, this.midiVelocity, start);
|
||||
MidiDecoder midi = new MidiDecoder(this.midiVoices, this.midiBank, this.midiKeep, this.midiUnknown, this.midiVelocity, start);
|
||||
midi.setDebug(this.midiDebug);
|
||||
this.audio.alMidi(midi);
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
61
client/src/main/java/client/audio/Instrument.java
Normal file
61
client/src/main/java/client/audio/Instrument.java
Normal file
|
@ -0,0 +1,61 @@
|
|||
package client.audio;
|
||||
|
||||
public class Instrument {
|
||||
public final Operator[] ops;
|
||||
public final int feedback1;
|
||||
public final int feedback2;
|
||||
public final int algo;
|
||||
|
||||
public boolean fixed;
|
||||
public int percnum;
|
||||
|
||||
public Instrument(int feedback1, int feedback2, int algo, Operator ... ops) {
|
||||
this.ops = ops;
|
||||
this.feedback1 = feedback1;
|
||||
this.feedback2 = feedback2;
|
||||
this.algo = algo;
|
||||
}
|
||||
|
||||
public Instrument() {
|
||||
this(0, 0, 0);
|
||||
}
|
||||
|
||||
public Instrument(Operator op1, Operator op2, boolean am, int detune, int offset, int feedback) {
|
||||
this(feedback, 0, am ? 1 : 0, op1, op2);
|
||||
op1.offset(offset).detune(detune);
|
||||
op2.offset(offset).detune(detune);
|
||||
}
|
||||
|
||||
public Instrument(Operator op1A, Operator op2A, boolean amA, int detuneA, int offsetA, int feedbackA,
|
||||
Operator op1B, Operator op2B, boolean amB, int detuneB, int offsetB, int feedbackB) {
|
||||
this(feedbackA, feedbackB, 0x08 | (amA ? 1 : 0) | (amB ? 2 : 0), op1A, op2A, op1B, op2B);
|
||||
op1A.offset(offsetA).detune(detuneA);
|
||||
op2A.offset(offsetA).detune(detuneA);
|
||||
op1B.offset(offsetB).detune(detuneB);
|
||||
op2B.offset(offsetB).detune(detuneB);
|
||||
}
|
||||
|
||||
public Instrument(Operator op1, Operator op2, Operator op3, Operator op4, int algo, int detune, int offset, int feedback) {
|
||||
this(feedback, 0, 0x04 | (algo & 3), op1, op2, op3, op4);
|
||||
op1.offset(offset).detune(detune);
|
||||
op2.offset(offset).detune(detune);
|
||||
op3.offset(offset).detune(detune);
|
||||
op4.offset(offset).detune(detune);
|
||||
}
|
||||
|
||||
public Instrument setFixed() {
|
||||
this.fixed = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Instrument setPercNum(int percnum) {
|
||||
this.percnum = percnum;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Instrument setFixed(int percnum) {
|
||||
this.fixed = true;
|
||||
this.percnum = percnum;
|
||||
return this;
|
||||
}
|
||||
}
|
7192
client/src/main/java/client/audio/MidiBank.java
Normal file
7192
client/src/main/java/client/audio/MidiBank.java
Normal file
File diff suppressed because it is too large
Load diff
520
client/src/main/java/client/audio/MidiDecoder.java
Normal file
520
client/src/main/java/client/audio/MidiDecoder.java
Normal file
|
@ -0,0 +1,520 @@
|
|||
package client.audio;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import client.util.FileUtils;
|
||||
import common.log.Log;
|
||||
|
||||
public class MidiDecoder {
|
||||
private class Track {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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 Track[] tracks;
|
||||
public final int tpqn;
|
||||
public final boolean nowait;
|
||||
public final OPLChip chip;
|
||||
public final MidiHandle 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 MidiDecoder(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");
|
||||
}
|
||||
Track[] tracks = new Track[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");
|
||||
}
|
||||
Track track = tracks[trk] = new Track();
|
||||
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, velofunc, 0, false, false);
|
||||
this.bank = new MidiHandle(this.chip, bank.getData(), keep, useunkn);
|
||||
this.playing = true;
|
||||
}
|
||||
|
||||
public MidiDecoder(int voices, MidiBank bank, boolean keep, boolean useunkn, int velofunc, int start) {
|
||||
this.tracks = null;
|
||||
this.tpqn = 0;
|
||||
this.nowait = false;
|
||||
this.chip = new OPLChip(48000, voices, velofunc, 0, false, false);
|
||||
this.bank = new MidiHandle(this.chip, bank.getData(), keep, useunkn);
|
||||
this.ticktime = start;
|
||||
}
|
||||
|
||||
private void setTempo(int tempo) {
|
||||
this.uspb = tempo;
|
||||
this.ticktime = this.uspb / this.tpqn;
|
||||
}
|
||||
|
||||
private void log(String fmt, Object ... args) {
|
||||
Log.SOUND.info(fmt, args);
|
||||
}
|
||||
|
||||
private void debug(String fmt, Object ... args) {
|
||||
if(this.debug)
|
||||
Log.SOUND.debug(fmt, args);
|
||||
}
|
||||
|
||||
private void processMeta(Track 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(Track trk, MidiHandle bank) {
|
||||
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(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(channel, key, velocity);
|
||||
else
|
||||
bank.noteon(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(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(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(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(MidiHandle bank) {
|
||||
boolean end = true;
|
||||
for(int trk = 0; trk < this.tracks.length; trk++) {
|
||||
Track 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);
|
||||
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.tracks == null) {
|
||||
this.bank.progchange(0, this.ticktime < 128 ? this.ticktime : 0);
|
||||
this.bank.progchange(9, 0);
|
||||
Log.SOUND.user("MIDI-Test %s%d", this.ticktime < 128 ? "#" : "P", this.ticktime < 128 ? this.ticktime : this.ticktime - 128);
|
||||
this.bank.noteon(this.ticktime < 128 ? 0 : 9, this.ticktime < 128 ? 36 : this.ticktime - 128, 127);
|
||||
this.ticktime++;
|
||||
if(this.ticktime == 128)
|
||||
this.ticktime = 128 + 35;
|
||||
else if(this.ticktime == 128 + 82)
|
||||
this.ticktime = 0;
|
||||
return 500000000L;
|
||||
}
|
||||
|
||||
if(!this.playing && (this.nowait || !this.chip.isPlaying())) {
|
||||
return -1L;
|
||||
}
|
||||
else if(this.playing && this.tick(this.bank)) {
|
||||
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;
|
||||
}
|
||||
}
|
298
client/src/main/java/client/audio/MidiHandle.java
Normal file
298
client/src/main/java/client/audio/MidiHandle.java
Normal file
|
@ -0,0 +1,298 @@
|
|||
package client.audio;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import client.audio.OPLChip.OPLChannel;
|
||||
import client.audio.OPLChip.OPLSlot;
|
||||
|
||||
public class MidiHandle {
|
||||
public class Voice {
|
||||
final OPLChannel opl;
|
||||
|
||||
Channel channel;
|
||||
int note = -1;
|
||||
|
||||
Voice(OPLChannel opl) {
|
||||
this.opl = opl;
|
||||
}
|
||||
}
|
||||
|
||||
public class Key {
|
||||
int velocity;
|
||||
Voice voice;
|
||||
|
||||
Key() {
|
||||
}
|
||||
}
|
||||
|
||||
public class Channel {
|
||||
final Key[] keys = new Key[BANK_MAX];
|
||||
final int[] notes = new int[128];
|
||||
|
||||
int keyindex;
|
||||
int active;
|
||||
int pbank;
|
||||
int pan;
|
||||
int volume = 127;
|
||||
int pitch;
|
||||
int program;
|
||||
Instrument instr;
|
||||
|
||||
Channel(boolean drum) {
|
||||
Arrays.fill(this.notes, -1);
|
||||
this.volume = 127;
|
||||
this.pbank = drum ? 128 : 0;
|
||||
this.instr = MidiHandle.this.instruments[drum ? 128 : 0];
|
||||
for(int z = 0; z < this.keys.length; z++) {
|
||||
this.keys[z] = new Key();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final int BANK_MAX = 64;
|
||||
private static final double BANK_PBRANGE = 2.0;
|
||||
|
||||
public final Channel[] channels = new Channel[16];
|
||||
public final Voice[] voices;
|
||||
public final Instrument[] instruments;
|
||||
public final boolean keep;
|
||||
public final boolean useunkn;
|
||||
|
||||
public int index;
|
||||
public int used;
|
||||
|
||||
public MidiHandle(OPLChip chip, Instrument[] instr, boolean keep, boolean useunkn) {
|
||||
this.keep = keep;
|
||||
this.useunkn = useunkn;
|
||||
this.instruments = instr;
|
||||
this.voices = new Voice[chip.channel.length];
|
||||
for(int z = 0; z < this.channels.length; z++) {
|
||||
this.channels[z] = new Channel(z == 9);
|
||||
}
|
||||
for(int z = 0; z < this.voices.length; z++) {
|
||||
this.voices[z] = new Voice(chip.channel[z]);
|
||||
}
|
||||
}
|
||||
|
||||
private void releaseVoice(Channel ch, Voice voice) {
|
||||
if(voice.note == -1)
|
||||
return;
|
||||
voice.opl.key(false);
|
||||
voice.note = -1;
|
||||
this.used -= 1;
|
||||
}
|
||||
|
||||
private Voice releaseKey(Channel ch, int note) {
|
||||
if(/* ch == null || */ ch.active == 0) {
|
||||
return null;
|
||||
}
|
||||
if(ch.notes[note] == -1) {
|
||||
return null;
|
||||
}
|
||||
Key key = ch.keys[ch.notes[note]];
|
||||
ch.notes[note] = -1;
|
||||
ch.active -= 1;
|
||||
if(key.voice != null) {
|
||||
this.releaseVoice(ch, key.voice);
|
||||
}
|
||||
key.velocity = 0;
|
||||
return key.voice;
|
||||
}
|
||||
|
||||
private Voice getVoice(Channel ch, int note, int velocity) {
|
||||
Instrument instr = ch.instr;
|
||||
if(ch.pbank == 128)
|
||||
instr = this.instruments[128 + note];
|
||||
else if(ch.pbank != 0 && !this.useunkn)
|
||||
return null;
|
||||
if(instr.ops.length == 0)
|
||||
return null;
|
||||
if(this.used == this.voices.length) {
|
||||
if(this.keep)
|
||||
return null;
|
||||
this.releaseKey(this.voices[this.index].channel, this.voices[this.index].note);
|
||||
}
|
||||
int vi = this.index;
|
||||
while(this.voices[this.index].note != -1) {
|
||||
this.index += 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);
|
||||
break;
|
||||
}
|
||||
}
|
||||
Voice voice = this.voices[this.index];
|
||||
this.index += 1;
|
||||
if(this.index >= this.voices.length)
|
||||
this.index = 0;
|
||||
voice.channel = ch;
|
||||
voice.note = note;
|
||||
voice.opl.setAlgo(instr.algo, instr.feedback1, instr.feedback2);
|
||||
voice.opl.setLevel(velocity, ch.volume, ch.pan);
|
||||
for(int o = 0; o < instr.ops.length; o++) {
|
||||
Operator op = instr.ops[o];
|
||||
OPLSlot slot = voice.opl.slots[o];
|
||||
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);
|
||||
slot.setOffset(op.offset);
|
||||
slot.setDetune(op.detune);
|
||||
slot.setFixed(instr.fixed ? instr.percnum : -1);
|
||||
slot.setNote(note);
|
||||
slot.setPitch(ch.pitch);
|
||||
}
|
||||
voice.opl.key(true);
|
||||
this.used += 1;
|
||||
return voice;
|
||||
}
|
||||
|
||||
private Voice pressKey(Channel ch, int note, int velocity) {
|
||||
if(ch.notes[note] != -1) {
|
||||
this.releaseKey(ch, note);
|
||||
}
|
||||
if(ch.active == BANK_MAX) {
|
||||
return null;
|
||||
}
|
||||
int 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;
|
||||
}
|
||||
}
|
||||
Voice voice = this.getVoice(ch, note, velocity);
|
||||
ch.notes[note] = ch.keyindex;
|
||||
ch.active += 1;
|
||||
Key key = ch.keys[ch.keyindex];
|
||||
key.velocity = velocity;
|
||||
key.voice = voice;
|
||||
return voice;
|
||||
}
|
||||
|
||||
private void notesoff(Channel ch) {
|
||||
for(int h = 0; h < ch.notes.length && ch.active > 0; h++) {
|
||||
this.releaseKey(ch, h);
|
||||
}
|
||||
}
|
||||
|
||||
private void progupdate(Channel ch) {
|
||||
this.notesoff(ch);
|
||||
int 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(Channel ch) {
|
||||
int done = 0;
|
||||
for(int h = 0; (h < BANK_MAX) && (done < ch.active); h++) {
|
||||
Key key = ch.keys[h];
|
||||
if((key.velocity == 0) || (key.voice == null)) {
|
||||
continue;
|
||||
}
|
||||
key.voice.opl.setLevel(key.velocity, ch.volume, ch.pan);
|
||||
done++;
|
||||
}
|
||||
}
|
||||
|
||||
private void frequpdate(Channel ch) {
|
||||
int done = 0;
|
||||
for(int h = 0; (h < BANK_MAX) && (done < ch.active); h++) {
|
||||
Key key = ch.keys[h];
|
||||
if((key.velocity == 0) || (key.voice == null)) {
|
||||
continue;
|
||||
}
|
||||
for(OPLSlot slot : key.voice.opl.slots) {
|
||||
slot.setPitch(ch.pitch);
|
||||
}
|
||||
done++;
|
||||
}
|
||||
}
|
||||
|
||||
public void noteon(int channel, int key, int velocity) {
|
||||
Channel ch = this.channels[channel];
|
||||
this.pressKey(ch, key, velocity);
|
||||
}
|
||||
|
||||
public void noteoff(int channel, int key, int velocity) {
|
||||
Channel ch = this.channels[channel];
|
||||
this.releaseKey(ch, key);
|
||||
}
|
||||
|
||||
public void progchange(int channel, int program) {
|
||||
Channel ch = this.channels[channel];
|
||||
ch.program = program;
|
||||
this.progupdate(ch);
|
||||
}
|
||||
|
||||
public void pitchbend(int channel, int pitch) {
|
||||
Channel ch = this.channels[channel];
|
||||
ch.pitch = (int)((((double)pitch) / 8191.0 * BANK_PBRANGE) * 10000.0);
|
||||
this.frequpdate(ch);
|
||||
}
|
||||
|
||||
public void control(int channel, int control, int value) {
|
||||
Channel 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 |= (value << 7) & 0x3f80;
|
||||
}
|
||||
this.progupdate(ch);
|
||||
break;
|
||||
case 0x20: // bank LSB
|
||||
// if((channel == 9) && (value == 0)) {
|
||||
// ch.pbank = 128;
|
||||
// }
|
||||
// else {
|
||||
if(channel != 9) {
|
||||
ch.pbank &= 0x3f80;
|
||||
ch.pbank |= value & 0x007f;
|
||||
}
|
||||
this.progupdate(ch);
|
||||
break;
|
||||
case 0x07: // volume MSB
|
||||
ch.volume = value;
|
||||
this.levelupdate(ch);
|
||||
break;
|
||||
case 0x0a: // pan MSB
|
||||
ch.pan = value - 64;
|
||||
this.levelupdate(ch);
|
||||
break;
|
||||
case 0x79: // reset
|
||||
ch.pbank = (channel == 9) ? 128 : 0;
|
||||
ch.volume = 127;
|
||||
ch.pan = 0;
|
||||
this.progupdate(ch);
|
||||
this.levelupdate(ch);
|
||||
break;
|
||||
case 0x7b: // all off
|
||||
this.notesoff(ch);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void alloff() {
|
||||
for(int h = 0; h < 16; h++) {
|
||||
this.notesoff(this.channels[h]);
|
||||
}
|
||||
}
|
||||
}
|
64
client/src/main/java/client/audio/Operator.java
Normal file
64
client/src/main/java/client/audio/Operator.java
Normal file
|
@ -0,0 +1,64 @@
|
|||
package client.audio;
|
||||
|
||||
public class Operator {
|
||||
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;
|
||||
|
||||
public boolean tremolo;
|
||||
public boolean vibrato;
|
||||
public boolean sustaining;
|
||||
public boolean ksr;
|
||||
public int ksl;
|
||||
public int offset;
|
||||
public int detune;
|
||||
|
||||
public Operator(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 Operator tremolo() {
|
||||
this.tremolo = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Operator vibrato() {
|
||||
this.vibrato = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Operator sustaining() {
|
||||
this.sustaining = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Operator ksr() {
|
||||
this.ksr = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Operator ksl(int ksl) {
|
||||
this.ksl = ksl;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Operator offset(int offset) {
|
||||
this.offset = offset;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Operator detune(int detune) {
|
||||
this.detune = detune;
|
||||
return this;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue