1
0
Fork 0

opl cleanup

This commit is contained in:
Sen 2025-08-25 15:33:58 +02:00
parent 43956af38d
commit bbbace7f46
Signed by: sen
GPG key ID: 3AC50A6F47D1B722
7 changed files with 8145 additions and 8135 deletions

View file

@ -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

View 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;
}
}

File diff suppressed because it is too large Load diff

View 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;
}
}

View 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]);
}
}
}

View 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;
}
}