1
0
Fork 0

opl arb update

This commit is contained in:
Sen 2025-08-25 13:17:58 +02:00
parent 16b05b2e1e
commit c0919b0eed
Signed by: sen
GPG key ID: 3AC50A6F47D1B722
2 changed files with 479 additions and 647 deletions

View file

@ -16,6 +16,8 @@ import common.util.Identifyable;
import javax.sound.sampled.SourceDataLine; import javax.sound.sampled.SourceDataLine;
import client.audio.AudioInterface.BankInstr;
import client.audio.AudioInterface.BankHandle.BankChannel;
import client.audio.OPLChip.OPLChannel; import client.audio.OPLChip.OPLChannel;
import client.audio.OPLChip.OPLSlot; import client.audio.OPLChip.OPLSlot;
import client.util.FileUtils; import client.util.FileUtils;
@ -7865,8 +7867,8 @@ public class AudioInterface implements Runnable {
this.tpqn = tpqn; this.tpqn = tpqn;
this.nowait = nowait; this.nowait = nowait;
this.setTempo(500000); this.setTempo(500000);
this.chip = new OPLChip(48000, voices, 0, false, false); this.chip = new OPLChip(48000, voices, velofunc, 0, false, false);
this.bank = new BankHandle(this.chip, bank.getData(), keep, useunkn, (byte)velofunc); this.bank = new BankHandle(this.chip, bank.getData(), keep, useunkn);
this.playing = true; this.playing = true;
} }
@ -7874,8 +7876,8 @@ public class AudioInterface implements Runnable {
this.tracks = null; this.tracks = null;
this.tpqn = 0; this.tpqn = 0;
this.nowait = false; this.nowait = false;
this.chip = new OPLChip(48000, voices, 0, false, false); this.chip = new OPLChip(48000, voices, velofunc, 0, false, false);
this.bank = new BankHandle(this.chip, bank.getData(), keep, useunkn, (byte)velofunc); this.bank = new BankHandle(this.chip, bank.getData(), keep, useunkn);
this.ticktime = start; this.ticktime = start;
} }
@ -7989,7 +7991,7 @@ public class AudioInterface implements Runnable {
} }
} }
private void process(MidiTrack trk, BankHandle bank, OPLChip chip) { private void process(MidiTrack trk, BankHandle bank) {
if(trk.pos >= trk.size) { if(trk.pos >= trk.size) {
return; return;
} }
@ -8008,7 +8010,7 @@ public class AudioInterface implements Runnable {
case midev_noteoff: { case midev_noteoff: {
byte o_key = trk.readByte(); byte o_key = trk.readByte();
byte o_velocity = trk.readByte(); byte o_velocity = trk.readByte();
bank.noteoff(chip, channel, o_key, o_velocity); bank.noteoff(channel, o_key, o_velocity);
this.debug("MIDI Note-Aus - C%02d N%03d V%03d", channel+1, o_key, o_velocity); this.debug("MIDI Note-Aus - C%02d N%03d V%03d", channel+1, o_key, o_velocity);
break; break;
} }
@ -8016,9 +8018,9 @@ public class AudioInterface implements Runnable {
byte key = trk.readByte(); byte key = trk.readByte();
byte velocity = trk.readByte(); byte velocity = trk.readByte();
if(velocity == 0) if(velocity == 0)
bank.noteoff(chip, channel, key, velocity); bank.noteoff(channel, key, velocity);
else else
bank.noteon(chip, channel, key, velocity); bank.noteon(channel, key, velocity);
this.debug("MIDI Note-An - C%02d N%03d V%03d", channel+1, key, velocity); this.debug("MIDI Note-An - C%02d N%03d V%03d", channel+1, key, velocity);
break; break;
} }
@ -8030,13 +8032,13 @@ public class AudioInterface implements Runnable {
case midev_control: { case midev_control: {
byte control = trk.readByte(); byte control = trk.readByte();
byte value = trk.readByte(); byte value = trk.readByte();
bank.control(chip, channel, control, value); bank.control(channel, control, value);
this.debug("MIDI Controller - C%02d N%03d V%03d", channel+1, control, value); this.debug("MIDI Controller - C%02d N%03d V%03d", channel+1, control, value);
break; break;
} }
case midev_progchg: { case midev_progchg: {
byte program = trk.readByte(); byte program = trk.readByte();
bank.progchange(chip, channel, program); bank.progchange(channel, program);
this.debug("MIDI Programm - C%02d P%03d", channel+1, program); this.debug("MIDI Programm - C%02d P%03d", channel+1, program);
break; break;
} }
@ -8048,7 +8050,7 @@ public class AudioInterface implements Runnable {
case midev_pitchbend: { case midev_pitchbend: {
short pb = (short)(((short)trk.readByte()) | (((short)trk.readByte()) << 7)); short pb = (short)(((short)trk.readByte()) | (((short)trk.readByte()) << 7));
short pitch = (short)(pb - 0x2000); short pitch = (short)(pb - 0x2000);
bank.pitchbend(chip, channel, pitch); bank.pitchbend(channel, pitch);
this.debug("MIDI Pitch-Bend - C%02d P%03d", channel+1, pitch); this.debug("MIDI Pitch-Bend - C%02d P%03d", channel+1, pitch);
break; break;
} }
@ -8094,7 +8096,7 @@ public class AudioInterface implements Runnable {
} }
} }
private boolean tick(BankHandle bank, OPLChip chip) { private boolean tick(BankHandle bank) {
boolean end = true; boolean end = true;
for(int trk = 0; trk < this.tracks.length; trk++) { for(int trk = 0; trk < this.tracks.length; trk++) {
MidiTrack track = this.tracks[trk]; MidiTrack track = this.tracks[trk];
@ -8109,7 +8111,7 @@ public class AudioInterface implements Runnable {
} }
while(true) { while(true) {
if(track.pos > 0) { if(track.pos > 0) {
this.process(track, bank, chip); this.process(track, bank);
if(!track.ending && track.pos >= track.size) { if(!track.ending && track.pos >= track.size) {
this.log("MIDI Spur #%d endete zu früh", track.trknum); this.log("MIDI Spur #%d endete zu früh", track.trknum);
track.ending = true; track.ending = true;
@ -8132,10 +8134,10 @@ public class AudioInterface implements Runnable {
public long process() { public long process() {
if(this.tracks == null) { if(this.tracks == null) {
this.bank.progchange(this.chip, (byte)0, (byte)(this.ticktime < 128 ? this.ticktime : 0)); this.bank.progchange(0, this.ticktime < 128 ? this.ticktime : 0);
this.bank.progchange(this.chip, (byte)9, (byte)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); Log.SOUND.user("MIDI-Test %s%d", this.ticktime < 128 ? "#" : "P", this.ticktime < 128 ? this.ticktime : this.ticktime - 128);
this.bank.noteon(this.chip, this.ticktime < 128 ? (byte)0 : (byte)9, (byte)(this.ticktime < 128 ? (byte)36 : (this.ticktime - 128)), (byte)127); this.bank.noteon(this.ticktime < 128 ? 0 : 9, this.ticktime < 128 ? 36 : this.ticktime - 128, 127);
this.ticktime++; this.ticktime++;
if(this.ticktime == 128) if(this.ticktime == 128)
this.ticktime = 128 + 35; this.ticktime = 128 + 35;
@ -8147,7 +8149,7 @@ public class AudioInterface implements Runnable {
if(!this.playing && (this.nowait || !this.chip.isPlaying())) { if(!this.playing && (this.nowait || !this.chip.isPlaying())) {
return -1L; return -1L;
} }
else if(this.playing && this.tick(this.bank, this.chip)) { else if(this.playing && this.tick(this.bank)) {
return ((long)this.ticktime) * 1000L; return ((long)this.ticktime) * 1000L;
} }
else if(this.playing) { else if(this.playing) {
@ -8165,198 +8167,87 @@ public class AudioInterface implements Runnable {
public static class BankHandle { public static class BankHandle {
public class BankVoice { public class BankVoice {
final OPLChannel opl;
BankChannel channel; BankChannel channel;
OPLChannel opl; int note = -1;
byte note;
BankOp op; BankVoice(OPLChannel opl) {
int detune; this.opl = opl;
BankVoice pair; }
} }
public class BankKey { public class BankKey {
byte note; int velocity;
byte velocity;
BankVoice voice; BankVoice voice;
} }
public class BankChannel { public class BankChannel {
BankKey[] keys = new BankKey[BANK_MAX]; final BankKey[] keys = new BankKey[BANK_MAX];
byte[] notes = new byte[128]; final int[] notes = new int[128];
byte keyindex;
byte active; int keyindex;
int active;
int pbank; int pbank;
byte pan; int pan;
byte volume; int volume = 127;
short pitch; int pitch;
byte program; int program;
BankInstr instr; BankInstr instr;
int ch_num;
BankChannel(boolean drum) {
Arrays.fill(this.notes, -1);
this.volume = 127;
this.pbank = drum ? 128 : 0;
this.instr = BankHandle.this.instruments[drum ? 128 : 0];
for(int z = 0; z < this.keys.length; z++) {
this.keys[z] = new BankKey();
}
}
} }
private static final int BANK_MAX = 64; private static final int BANK_MAX = 64;
private static final double BANK_PBRANGE = 2.0; 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 BankChannel[] channels = new BankChannel[16];
public final BankVoice[] voices; public final BankVoice[] voices;
public final BankInstr[] instruments; public final BankInstr[] instruments;
public final boolean keep; public final boolean keep;
public final boolean useunkn; public final boolean useunkn;
public final byte velo_func;
public int index; public int index;
public int used; public int used;
public BankHandle(OPLChip chip, BankInstr[] instr, boolean keep, boolean useunkn, byte velofunc) { public BankHandle(OPLChip chip, BankInstr[] instr, boolean keep, boolean useunkn) {
this.keep = keep; this.keep = keep;
this.useunkn = useunkn; this.useunkn = useunkn;
this.velo_func = velofunc;
this.instruments = instr; this.instruments = instr;
this.voices = new BankVoice[chip.channel.length]; this.voices = new BankVoice[chip.channel.length];
this.reset(chip); for(int z = 0; z < this.channels.length; z++) {
} this.channels[z] = new BankChannel(z == 9);
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++) { for(int z = 0; z < this.voices.length; z++) {
BankVoice voice = this.voices[h] = new BankVoice(); this.voices[z] = new BankVoice(chip.channel[z]);
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 + 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) { private void releaseVoice(BankChannel ch, BankVoice voice) {
if(voice.note == (byte)0xff) { if(voice.note == -1)
return; return;
} voice.opl.key(false);
voice.opl.keyOff(); voice.note = -1;
voice.note = (byte)0xff; this.used -= 1;
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) { private BankVoice releaseKey(BankChannel ch, int note) {
if(/* ch == null || */ ch.active == 0) { if(/* ch == null || */ ch.active == 0) {
return null; return null;
} }
if(ch.notes[note] == (byte)0xff) { if(ch.notes[note] == -1) {
return null; return null;
} }
BankKey key = ch.keys[ch.notes[note]]; BankKey key = ch.keys[ch.notes[note]];
ch.notes[note] = (byte)0xff; ch.notes[note] = -1;
ch.active -= 1; ch.active -= 1;
if(key.voice != null) { if(key.voice != null) {
this.releaseVoice(ch, key.voice); this.releaseVoice(ch, key.voice);
@ -8365,130 +8256,67 @@ public class AudioInterface implements Runnable {
return key.voice; return key.voice;
} }
private void initVoice(OPLChannel channel, BankInstr instr, int id) { private BankVoice getVoice(BankChannel ch, int note, int velocity) {
channel.set4Op(instr.op == BankOp.b_op4); BankInstr instr = ch.instr;
for(int o = 0; o < ((instr.op == BankOp.b_op4) ? 4 : 2); o++) { if(ch.pbank == 128)
BankPair pair = instr.channels[(instr.op == BankOp.b_op4) ? (o >> 1) : id]; instr = this.instruments[128 + note];
BankOperator op = pair.ops[o & 1]; else if(ch.pbank != 0 && !this.useunkn)
OPLSlot slot = (o >= 2) ? (channel.pair.slots[o & 1]) : (channel.slots[o & 1]); 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;
}
}
BankVoice 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++) {
BankOperator op = instr.ops[o];
OPLSlot slot = voice.opl.slots[o];
slot.setFlags(op.tremolo, op.vibrato, op.sustaining, op.ksr); slot.setFlags(op.tremolo, op.vibrato, op.sustaining, op.ksr);
slot.setMultiplier(op.mult); slot.setMultiplier(op.mult);
slot.setKSL(op.ksl); slot.setKSL(op.ksl);
slot.setLevel(op.level); slot.setLevel(op.level);
slot.setEnvelope(op.attack, op.decay, op.sustain, op.release); slot.setEnvelope(op.attack, op.decay, op.sustain, op.release);
slot.setWaveform(op.waveform); slot.setWaveform(op.waveform);
if((o & 1) == 1) { slot.setOffset(op.offset);
OPLChannel chn = (o >= 2) ? channel.pair : channel; slot.setDetune(op.detune);
chn.setFeedback(pair.feedback); slot.setFixed(instr.fixed ? instr.percnum : -1);
chn.setAM(pair.am); slot.setNote(note);
} slot.setPitch(ch.pitch);
}
}
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;
} }
voice.opl.key(true);
this.used += 1;
return voice; return voice;
} }
private BankVoice pressKey(BankChannel ch, byte note, byte velocity) { private BankVoice pressKey(BankChannel ch, int note, int velocity) {
if(ch.notes[note] != (byte)0xff) { if(ch.notes[note] != -1) {
this.releaseKey(ch, note); this.releaseKey(ch, note);
} }
if(ch.active == BANK_MAX) { if(ch.active == BANK_MAX) {
return null; return null;
} }
byte ki = ch.keyindex; int ki = ch.keyindex;
while(ch.keys[ch.keyindex].velocity != 0) { while(ch.keys[ch.keyindex].velocity != 0) {
ch.keyindex += 1; ch.keyindex += 1;
if(ch.keyindex == BANK_MAX) { if(ch.keyindex == BANK_MAX) {
@ -8502,21 +8330,20 @@ public class AudioInterface implements Runnable {
ch.notes[note] = ch.keyindex; ch.notes[note] = ch.keyindex;
ch.active += 1; ch.active += 1;
BankKey key = ch.keys[ch.keyindex]; BankKey key = ch.keys[ch.keyindex];
key.note = note;
key.velocity = velocity; key.velocity = velocity;
key.voice = voice; key.voice = voice;
return voice; return voice;
} }
private void notesoff(BankChannel ch) { private void notesoff(BankChannel ch) {
for(int h = 0; (h < BANK_MAX) && (ch.active > 0); h++) { for(int h = 0; h < ch.notes.length && ch.active > 0; h++) {
this.releaseKey(ch, (byte)h); this.releaseKey(ch, h);
} }
} }
private void progupdate(BankChannel ch, OPLChip chip) { private void progupdate(BankChannel ch) {
this.notesoff(ch); this.notesoff(ch);
short id = ch.program; int id = ch.program;
if(ch.pbank == 128) { if(ch.pbank == 128) {
id = 128; id = 128;
} }
@ -8526,37 +8353,33 @@ public class AudioInterface implements Runnable {
ch.instr = this.instruments[id]; ch.instr = this.instruments[id];
} }
private void levelupdate(BankChannel ch, OPLChip chip) { private void levelupdate(BankChannel ch) {
byte done = 0; int done = 0;
for(int h = 0; (h < BANK_MAX) && (done < ch.active); h++) { for(int h = 0; (h < BANK_MAX) && (done < ch.active); h++) {
BankKey key = ch.keys[h]; BankKey key = ch.keys[h];
if((key.velocity == 0) || (key.voice == null)) { if((key.velocity == 0) || (key.voice == null)) {
continue; continue;
} }
OPL3_ChannelLevelPan(key.voice.opl, this.velo_func, key.velocity, ch.volume, ch.pan); key.voice.opl.setLevel(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++; done++;
} }
} }
private void frequpdate(BankChannel ch, OPLChip chip) { private void frequpdate(BankChannel ch) {
byte done = 0; int done = 0;
for(int h = 0; (h < BANK_MAX) && (done < ch.active); h++) { for(int h = 0; (h < BANK_MAX) && (done < ch.active); h++) {
BankKey key = ch.keys[h]; BankKey key = ch.keys[h];
if((key.velocity == 0) || (key.voice == null)) { if((key.velocity == 0) || (key.voice == null)) {
continue; continue;
} }
OPL3_ChannelNote(key.voice.opl, getnote(ch, key.note, 0, ch.pbank == 128, ch.instr), ch.pitch, key.voice.detune); for(OPLSlot slot : key.voice.opl.slots) {
if(key.voice.op == BankOp.b_op22) { slot.setPitch(ch.pitch);
OPL3_ChannelNote(key.voice.pair.opl, getnote(ch, key.note, 1, ch.pbank == 128, ch.instr), ch.pitch, key.voice.pair.detune);
} }
done++; done++;
} }
} }
public void noteon(OPLChip chip, byte channel, byte key, byte velocity) { public void noteon(int channel, int key, int velocity) {
BankChannel ch = this.channels[channel]; BankChannel ch = this.channels[channel];
// if((ch.pbank == 128) && ((key < 35) || (key > 81))) { // if((ch.pbank == 128) && ((key < 35) || (key > 81))) {
// return; // return;
@ -8564,7 +8387,7 @@ public class AudioInterface implements Runnable {
this.pressKey(ch, key, velocity); this.pressKey(ch, key, velocity);
} }
public void noteoff(OPLChip chip, byte channel, byte key, byte velocity) { public void noteoff(int channel, int key, int velocity) {
BankChannel ch = this.channels[channel]; BankChannel ch = this.channels[channel];
// if((ch.pbank == 128) && ((key < 35) || (key > 81))) { // if((ch.pbank == 128) && ((key < 35) || (key > 81))) {
// return; // return;
@ -8572,19 +8395,19 @@ public class AudioInterface implements Runnable {
this.releaseKey(ch, key); this.releaseKey(ch, key);
} }
public void progchange(OPLChip chip, byte channel, byte program) { public void progchange(int channel, int program) {
BankChannel ch = this.channels[channel]; BankChannel ch = this.channels[channel];
ch.program = program; ch.program = program;
this.progupdate(ch, chip); this.progupdate(ch);
} }
public void pitchbend(OPLChip chip, byte channel, short pitch) { public void pitchbend(int channel, int pitch) {
BankChannel ch = this.channels[channel]; BankChannel ch = this.channels[channel];
ch.pitch = pitch; ch.pitch = (int)((((double)pitch) / 8191.0 * BANK_PBRANGE) * 10000.0);
this.frequpdate(ch, chip); this.frequpdate(ch);
} }
public void control(OPLChip chip, byte channel, byte control, byte value) { public void control(int channel, int control, int value) {
BankChannel ch = this.channels[channel]; BankChannel ch = this.channels[channel];
switch(control) { switch(control) {
case 0x00: // bank MSB case 0x00: // bank MSB
@ -8594,9 +8417,9 @@ public class AudioInterface implements Runnable {
// else { // else {
if(channel != 9) { if(channel != 9) {
ch.pbank &= 0x007f; ch.pbank &= 0x007f;
ch.pbank |= (((short)value) << 7) & 0x3f80; ch.pbank |= (value << 7) & 0x3f80;
} }
this.progupdate(ch, chip); this.progupdate(ch);
break; break;
case 0x20: // bank LSB case 0x20: // bank LSB
// if((channel == 9) && (value == 0)) { // if((channel == 9) && (value == 0)) {
@ -8605,24 +8428,24 @@ public class AudioInterface implements Runnable {
// else { // else {
if(channel != 9) { if(channel != 9) {
ch.pbank &= 0x3f80; ch.pbank &= 0x3f80;
ch.pbank |= ((short)value) & 0x007f; ch.pbank |= value & 0x007f;
} }
this.progupdate(ch, chip); this.progupdate(ch);
break; break;
case 0x07: // volume MSB case 0x07: // volume MSB
ch.volume = value; ch.volume = value;
this.levelupdate(ch, chip); this.levelupdate(ch);
break; break;
case 0x0a: // pan MSB case 0x0a: // pan MSB
ch.pan = (byte)(value - 64); ch.pan = value - 64;
this.levelupdate(ch, chip); this.levelupdate(ch);
break; break;
case 0x79: // reset case 0x79: // reset
ch.pbank = (channel == 9) ? 128 : 0; ch.pbank = (channel == 9) ? 128 : 0;
ch.volume = 127; ch.volume = 127;
ch.pan = 0; ch.pan = 0;
this.progupdate(ch, chip); this.progupdate(ch);
this.levelupdate(ch, chip); this.levelupdate(ch);
break; break;
case 0x7b: // all off case 0x7b: // all off
this.notesoff(ch); this.notesoff(ch);
@ -8637,13 +8460,6 @@ public class AudioInterface implements Runnable {
} }
} }
public static enum BankOp {
b_op2,
b_op4,
b_op22,
b_op0
};
public static class BankOperator { public static class BankOperator {
public final int mult; public final int mult;
public final int level; public final int level;
@ -8658,6 +8474,8 @@ public class AudioInterface implements Runnable {
boolean sustaining; boolean sustaining;
boolean ksr; boolean ksr;
int ksl; int ksl;
int offset;
int detune;
public BankOperator(int mult, int level, int waveform, int a, int d, int s, int r) { public BankOperator(int mult, int level, int waveform, int a, int d, int s, int r) {
this.mult = mult; this.mult = mult;
@ -8693,50 +8511,58 @@ public class AudioInterface implements Runnable {
this.ksl = ksl; this.ksl = ksl;
return this; return this;
} }
}
public static class BankPair { public BankOperator offset(int offset) {
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.offset = offset;
this.feedback = feedback; return this;
this.am = am; }
public BankOperator detune(int detune) {
this.detune = detune;
return this;
} }
} }
public static class BankInstr { public static class BankInstr {
public final BankPair[] channels; public final BankOperator[] ops;
public final BankOp op; public final int feedback1;
public final int feedback2;
public final int algo;
public boolean fixed; public boolean fixed;
public int percnum; public int percnum;
private BankInstr(BankPair ch1, BankPair ch2, BankOp op) { public BankInstr(int feedback1, int feedback2, int algo, BankOperator ... ops) {
this.channels = new BankPair[] {ch1, ch2}; this.ops = ops;
this.op = op; this.feedback1 = feedback1;
this.feedback2 = feedback2;
this.algo = algo;
} }
public BankInstr() { public BankInstr() {
this(null, null, BankOp.b_op0); this(0, 0, 0);
} }
public BankInstr(BankOperator op1, BankOperator op2, boolean am, int detune, int offset, int feedback) { 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); this(feedback, 0, am ? 1 : 0, op1, op2);
op1.offset(offset).detune(detune);
op2.offset(offset).detune(detune);
} }
public BankInstr(BankOperator op1A, BankOperator op2A, boolean amA, int detuneA, int offsetA, int feedbackA, 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) { 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); 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 BankInstr(BankOperator op1, BankOperator op2, BankOperator op3, BankOperator op4, int algo, int detune, int offset, int feedback) { 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); 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 BankInstr setFixed() { public BankInstr setFixed() {

View file

@ -1,12 +1,6 @@
package client.audio; package client.audio;
public class OPLChip { public class OPLChip {
private static enum ChType {
CH2OP,
CH4OP1,
CH4OP2
};
private static enum EnvState { private static enum EnvState {
ATTACK, ATTACK,
DECAY, DECAY,
@ -23,39 +17,54 @@ public class OPLChip {
} }
public class OPLSlot implements ModGen { public class OPLSlot implements ModGen {
OPLChannel channel; private final OPLChannel channel;
short out;
short fbmodv; public short out;
final ModGen fbmod = new ModGen() { public short fbmodv;
public short getModulation() { public final ModGen fbmod = () -> OPLSlot.this.fbmodv;
return OPLSlot.this.fbmodv; public ModGen mod;
} public int fb;
}; public short prout;
ModGen mod; public short eg_rout;
short prout; public short eg_out;
short eg_rout; public EnvState eg_gen;
short eg_out; public byte eg_ksl;
EnvState eg_gen; public ModGen trem;
byte eg_ksl; public boolean reg_vib;
ModGen trem; public boolean reg_type;
byte reg_vib; public boolean reg_ksr;
byte reg_type; public byte reg_mult;
byte reg_ksr; public byte reg_ksl;
byte reg_mult; public byte reg_tl;
byte reg_ksl; public byte reg_ar;
byte reg_tl; public byte reg_dr;
byte reg_ar; public byte reg_sl;
byte reg_dr; public byte reg_rr;
byte reg_sl; public int reg_wf;
byte reg_rr; public boolean key;
byte reg_wf; public boolean detrigger;
boolean key; public boolean retrigger;
boolean detrigger; public boolean pg_reset;
boolean retrigger; public int pg_phase;
boolean pg_reset; public int pg_phase_out;
int pg_phase; public int f_num;
short pg_phase_out; public int block;
int slot_num; public int ksv;
public int frequency = -1;
public int offset;
public int fixed;
public int note;
public int detune;
public int pitch;
public OPLSlot(OPLChannel channel) {
this.channel = channel;
this.mod = OPLChip.this.zeromod;
this.eg_rout = 0x1ff;
this.eg_out = 0x1ff;
this.eg_gen = EnvState.RELEASE;
this.trem = OPLChip.this.zeromod;
}
public short getModulation() { public short getModulation() {
return this.out; return this.out;
@ -64,9 +73,9 @@ public class OPLChip {
private void process() private void process()
{ {
// feedback // feedback
if (this.channel.fb != 0x00) if (this.fb != 0x00)
{ {
this.fbmodv = (short)((this.prout + this.out) >> (0x09 - this.channel.fb)); this.fbmodv = (short)((this.prout + this.out) >> (0x09 - this.fb));
} }
else else
{ {
@ -106,7 +115,7 @@ public class OPLChip {
reg_rate = this.reg_dr; reg_rate = this.reg_dr;
break; break;
case SUSTAIN: case SUSTAIN:
if (this.reg_type == 0) if (!this.reg_type)
{ {
reg_rate = this.reg_rr; reg_rate = this.reg_rr;
} }
@ -117,7 +126,7 @@ public class OPLChip {
} }
} }
this.pg_reset = reset; this.pg_reset = reset;
ks = (byte)(this.channel.ksv >> ((this.reg_ksr ^ 1) << 1)); ks = (byte)(this.ksv >> (this.reg_ksr ? 0 : 2));
nonzero = (reg_rate != 0); nonzero = (reg_rate != 0);
rate = (byte)(ks + (reg_rate << 2)); rate = (byte)(ks + (reg_rate << 2));
rate_hi = (byte)(rate >> 2); rate_hi = (byte)(rate >> 2);
@ -132,7 +141,7 @@ public class OPLChip {
{ {
if (rate_hi < 12) if (rate_hi < 12)
{ {
if (OPLChip.this.eg_state != 0) if (OPLChip.this.eg_state)
{ {
switch (eg_shift) switch (eg_shift)
{ {
@ -159,7 +168,7 @@ public class OPLChip {
} }
if (shift == 0) if (shift == 0)
{ {
shift = OPLChip.this.eg_state; shift = (byte)(OPLChip.this.eg_state ? 1 : 0);
} }
} }
} }
@ -223,15 +232,15 @@ public class OPLChip {
this.detrigger = false; this.detrigger = false;
this.retrigger = false; this.retrigger = false;
// phase // phase
short f_num; int f_num;
int basefreq; int basefreq;
short phase; int phase;
f_num = this.channel.f_num; f_num = this.f_num;
if (this.reg_vib != 0) if (this.reg_vib)
{ {
byte range; int range;
byte vibpos; int vibpos;
range = (byte)((f_num >> 7) & 7); range = (f_num >> 7) & 7;
vibpos = OPLChip.this.vibpos; vibpos = OPLChip.this.vibpos;
if ((vibpos & 3) == 0) if ((vibpos & 3) == 0)
{ {
@ -248,12 +257,10 @@ public class OPLChip {
} }
f_num += range; f_num += range;
} }
basefreq = (f_num << this.channel.block) >> 1; basefreq = (f_num << this.block) >> 1;
phase = (short)(this.pg_phase >> 9); phase = this.pg_phase >> 9;
if (this.pg_reset) if (this.pg_reset)
{
this.pg_phase = 0; this.pg_phase = 0;
}
this.pg_phase += (basefreq * MULT[this.reg_mult]) >> 1; this.pg_phase += (basefreq * MULT[this.reg_mult]) >> 1;
this.pg_phase_out = phase; this.pg_phase_out = phase;
// output // output
@ -262,8 +269,8 @@ public class OPLChip {
private void updateKSL() private void updateKSL()
{ {
short ksl = (short)((KSL[this.channel.f_num >> 6] << 2) short ksl = (short)((KSL[this.f_num >> 6] << 2)
- ((0x08 - this.channel.block) << 5)); - ((0x08 - this.block) << 5));
if (ksl < 0) if (ksl < 0)
{ {
ksl = 0; ksl = 0;
@ -290,18 +297,15 @@ public class OPLChip {
this.retrigger = false; this.retrigger = false;
} }
private void setFeedback(int feedback) {
this.fb = feedback;
}
public void setFlags(boolean tremolo, boolean vibrato, boolean sustaining, boolean ksr) { public void setFlags(boolean tremolo, boolean vibrato, boolean sustaining, boolean ksr) {
if (tremolo) this.trem = tremolo ? OPLChip.this.tremolo : OPLChip.this.zeromod;
{ this.reg_vib = vibrato;
this.trem = OPLChip.this.tremolo; this.reg_type = sustaining;
} this.reg_ksr = ksr;
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) { public void setMultiplier(int mult) {
@ -335,205 +339,212 @@ public class OPLChip {
public void setWaveform(int waveform) { public void setWaveform(int waveform) {
this.reg_wf = (byte)(waveform & 0x07); this.reg_wf = (byte)(waveform & 0x07);
} }
private void updateFrequency() {
int freq = this.frequency;
if(freq < 0) {
int key = this.note;
if(this.fixed >= 0)
key = this.fixed + this.offset;
else
key += this.offset;
key = key < 0 ? 0 : key;
key = key > 127 ? 127 : key;
double pfrq = Math.pow(2.0, ((((double)this.pitch) / 10000.0) + (((double)this.detune) / 100.0)) / 12.0);
double dfreq = ((double)NOTES[key]) * pfrq;
freq = (int)dfreq;
}
int block = 0;
while(MAX_FREQ[block] < freq) {
block++;
if(block == 8) {
break;
}
}
if(block == 8) {
this.block = 7;
this.f_num = 1023;
}
else {
double f_num = ((double)freq) / 1000.0 * Math.pow(2.0, (20.0-((double)block))) / 49716.0;
this.block = block;
this.f_num = (int)f_num;
}
this.ksv = (this.block << 1) | ((this.f_num >> (0x09 - OPLChip.this.nts)) & 0x01);
this.updateKSL();
}
public void setNote(int note) {
this.note = note;
this.frequency = -1;
this.updateFrequency();
}
public void setOffset(int offset) {
this.offset = offset;
this.frequency = -1;
this.updateFrequency();
}
public void setFixed(int fixed) {
this.fixed = fixed;
this.frequency = -1;
this.updateFrequency();
}
public void setDetune(int detune) {
this.detune = detune;
this.frequency = -1;
this.updateFrequency();
}
public void setPitch(int pitch) {
this.pitch = pitch;
this.frequency = -1;
this.updateFrequency();
}
public void setFrequency(int freq) {
this.frequency = freq;
this.updateFrequency();
}
}; };
public class OPLChannel { public class OPLChannel {
OPLSlot[] slots = new OPLSlot[2]; public final OPLSlot[] slots = new OPLSlot[4];
OPLChannel pair; public final ModGen[] out = new ModGen[this.slots.length];
ModGen[] out = new ModGen[4]; public final int[] level = new int[2];
int[] level = new int[2]; public OPLChannel() {
for(int z = 0; z < this.slots.length; z++) {
ChType chtype; this.slots[z] = new OPLSlot(this);
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.setAlgo(0, 0, 0);
this.setLevel(0, 0, 0);
}
public void setAlgo(int algo, int feedback1, int feedback2) {
if ((algo & 0x04) != 0)
{ {
this.pair.out[0] = OPLChip.this.zeromod; switch (algo & 0x03)
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: case 0x00:
this.pair.slots[0].mod = this.pair.slots[0].fbmod; this.slots[2].setFeedback(feedback1);
this.pair.slots[1].mod = this.pair.slots[0]; this.slots[2].mod = this.slots[2].fbmod;
this.slots[0].mod = this.pair.slots[1]; this.slots[3].mod = this.slots[2];
this.slots[1].mod = this.slots[0]; this.slots[0].mod = this.slots[3];
this.out[0] = this.slots[1]; this.slots[1].mod = this.slots[0];
this.out[1] = OPLChip.this.zeromod; this.out[0] = this.slots[1];
this.out[2] = OPLChip.this.zeromod; this.out[1] = OPLChip.this.zeromod;
this.out[3] = OPLChip.this.zeromod; this.out[2] = OPLChip.this.zeromod;
break; this.out[3] = OPLChip.this.zeromod;
case 0x01: break;
this.pair.slots[0].mod = this.pair.slots[0].fbmod; case 0x01:
this.pair.slots[1].mod = this.pair.slots[0]; this.slots[2].setFeedback(feedback1);
this.slots[0].mod = OPLChip.this.zeromod; this.slots[2].mod = this.slots[2].fbmod;
this.slots[1].mod = this.slots[0]; this.slots[3].mod = this.slots[2];
this.out[0] = this.pair.slots[1]; this.slots[0].mod = OPLChip.this.zeromod;
this.out[1] = this.slots[1]; this.slots[1].mod = this.slots[0];
this.out[2] = OPLChip.this.zeromod; this.out[0] = this.slots[3];
this.out[3] = OPLChip.this.zeromod; this.out[1] = this.slots[1];
break; this.out[2] = OPLChip.this.zeromod;
case 0x02: this.out[3] = OPLChip.this.zeromod;
this.pair.slots[0].mod = this.pair.slots[0].fbmod; break;
this.pair.slots[1].mod = OPLChip.this.zeromod; case 0x02:
this.slots[0].mod = this.pair.slots[1]; this.slots[2].setFeedback(feedback1);
this.slots[1].mod = this.slots[0]; this.slots[2].mod = this.slots[2].fbmod;
this.out[0] = this.pair.slots[0]; this.slots[3].mod = OPLChip.this.zeromod;
this.out[1] = this.slots[1]; this.slots[0].mod = this.slots[3];
this.out[2] = OPLChip.this.zeromod; this.slots[1].mod = this.slots[0];
this.out[3] = OPLChip.this.zeromod; this.out[0] = this.slots[2];
break; this.out[1] = this.slots[1];
case 0x03: this.out[2] = OPLChip.this.zeromod;
this.pair.slots[0].mod = this.pair.slots[0].fbmod; this.out[3] = OPLChip.this.zeromod;
this.pair.slots[1].mod = OPLChip.this.zeromod; break;
this.slots[0].mod = this.pair.slots[1]; case 0x03:
this.slots[1].mod = OPLChip.this.zeromod; this.slots[2].setFeedback(feedback1);
this.out[0] = this.pair.slots[0]; this.slots[2].mod = this.slots[2].fbmod;
this.out[1] = this.slots[0]; this.slots[3].mod = OPLChip.this.zeromod;
this.out[2] = this.slots[1]; this.slots[0].mod = this.slots[3];
this.out[3] = OPLChip.this.zeromod; this.slots[1].mod = OPLChip.this.zeromod;
break; this.out[0] = this.slots[2];
this.out[1] = this.slots[0];
this.out[2] = this.slots[1];
this.out[3] = OPLChip.this.zeromod;
break;
} }
} }
else else
{ {
switch (this.alg & 0x01) this.slots[0].setFeedback(feedback1);
this.slots[0].mod = this.slots[0].fbmod;
switch (algo & 0x01)
{ {
case 0x00: case 0x00:
this.slots[0].mod = this.slots[0].fbmod; this.slots[1].mod = this.slots[0];
this.slots[1].mod = this.slots[0]; this.out[0] = this.slots[1];
this.out[0] = this.slots[1]; this.out[1] = OPLChip.this.zeromod;
this.out[1] = OPLChip.this.zeromod; break;
case 0x01:
this.slots[1].mod = OPLChip.this.zeromod;
this.out[0] = this.slots[0];
this.out[1] = this.slots[1];
break;
}
if((algo & 0x08) != 0) {
this.slots[2].setFeedback(feedback2);
this.slots[2].mod = this.slots[2].fbmod;
switch (algo & 0x02)
{
case 0x00:
this.slots[3].mod = this.slots[2];
this.out[2] = this.slots[3];
this.out[3] = OPLChip.this.zeromod;
break;
case 0x02:
this.slots[3].mod = OPLChip.this.zeromod;
this.out[2] = this.slots[2];
this.out[3] = this.slots[3];
break;
}
}
else {
this.slots[2].mod = OPLChip.this.zeromod;
this.slots[3].mod = OPLChip.this.zeromod;
this.out[2] = OPLChip.this.zeromod; this.out[2] = OPLChip.this.zeromod;
this.out[3] = 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() 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)));
this.alg = this.con; }
if (this.chtype == ChType.CH4OP1)
{ private void updateLevel(boolean right, int velocity, int volume, int pan) {
this.pair.alg = (byte)(0x04 | (this.con << 1) | (this.pair.con)); int function = (int)OPLChip.this.velo_func;
this.alg = 0x08; double lvl = ((function == -128) ? 1.0 :
this.pair.setupAlgo(); (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)(right ? -pan : pan))/63.0));
else if (this.chtype == ChType.CH4OP2) // fprintf(stderr, "%d===%d-.%.2f\n", function, velocity, lvl);
{ lvl *= 65536.0;
this.alg = (byte)(0x04 | (this.pair.con << 1) | (this.con)); this.level[right ? 1 : 0] = (int)lvl;
this.pair.alg = 0x08; }
this.setupAlgo();
} public void setLevel(int velocity, int volume, int pan) {
this.updateLevel(false, velocity, volume, pan);
this.updateLevel(true, velocity, volume, pan);
}
public void key(boolean on) {
if(on)
for(OPLSlot slot : this.slots) {
slot.keyOn();
}
else else
{ for(OPLSlot slot : this.slots) {
this.setupAlgo(); slot.keyOff();
} }
}
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();
}
} }
} }
@ -611,7 +622,7 @@ public class OPLChip {
0x414, 0x411, 0x40e, 0x40b, 0x408, 0x406, 0x403, 0x400 0x414, 0x411, 0x40e, 0x40b, 0x408, 0x406, 0x403, 0x400
}; };
private static final byte[] MULT = { private static final int[] MULT = {
1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 20, 24, 24, 30, 30 1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 20, 24, 24, 30, 30
}; };
@ -630,6 +641,36 @@ public class OPLChip {
{ 1, 1, 1, 0 } { 1, 1, 1, 0 }
}; };
private static final int[] 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[] MAX_FREQ = {
48503,
97006,
194013,
388026,
776053,
1552107,
3104215,
6208431
};
private static final WaveFunc[] WAVE_FUNCS = { private static final WaveFunc[] WAVE_FUNCS = {
OPLChip::wave0, OPLChip::wave0,
OPLChip::wave1, OPLChip::wave1,
@ -641,18 +682,9 @@ public class OPLChip {
OPLChip::wave7 OPLChip::wave7
}; };
final OPLChannel[] channel; public final OPLChannel[] channel;
final OPLSlot[] slot; public final ModGen tremolo = () -> OPLChip.this.tremolov;
final ModGen tremolo = new ModGen() { public final ModGen zeromod = () -> 0;
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[] mixbuff = new int[2];
private final int[] oldsamples = new int[2]; private final int[] oldsamples = new int[2];
private final int[] samples = new int[2]; private final int[] samples = new int[2];
@ -662,11 +694,12 @@ public class OPLChip {
private final byte nts; private final byte nts;
private final byte vibshift; private final byte vibshift;
private final byte tremoloshift; private final byte tremoloshift;
private final byte velo_func;
private short timer; private short timer;
private long eg_timer; private long eg_timer;
private byte eg_timerrem; private byte eg_timerrem;
private byte eg_state; private boolean eg_state;
private byte eg_add; private byte eg_add;
private byte vibpos; private byte vibpos;
private short tremolov; private short tremolov;
@ -829,17 +862,19 @@ public class OPLChip {
this.samples[0] = this.mixbuff[0]; this.samples[0] = this.mixbuff[0];
this.samples[1] = this.mixbuff[1]; this.samples[1] = this.mixbuff[1];
for (int ii = 0; ii < this.slot.length; ii++)
{
this.slot[ii].process();
}
mix[0] = mix[1] = 0; mix[0] = mix[1] = 0;
for (int ii = 0; ii < this.channel.length; ii++) for (int ii = 0; ii < this.channel.length; ii++)
{ {
OPLChannel channel = this.channel[ii]; OPLChannel channel = this.channel[ii];
for (OPLSlot slot : channel.slots)
{
slot.process();
}
ModGen[] out = channel.out; ModGen[] out = channel.out;
int accm = out[0].getModulation() + out[1].getModulation() + out[2].getModulation() + out[3].getModulation(); int accm = 0;
for(int z = 0; z < out.length; z++) {
accm += out[z].getModulation();
}
mix[0] += (accm * channel.level[0]) >> 16; mix[0] += (accm * channel.level[0]) >> 16;
mix[1] += (accm * channel.level[1]) >> 16; mix[1] += (accm * channel.level[1]) >> 16;
} }
@ -884,7 +919,7 @@ public class OPLChip {
} }
} }
if (this.eg_timerrem != 0 || this.eg_state != 0) if (this.eg_timerrem != 0 || this.eg_state)
{ {
if (this.eg_timer == 0xfffffffffL) if (this.eg_timer == 0xfffffffffL)
{ {
@ -898,7 +933,7 @@ public class OPLChip {
} }
} }
this.eg_state ^= 1; this.eg_state ^= true;
} }
public int[] generate() public int[] generate()
@ -918,53 +953,24 @@ public class OPLChip {
return this.output; return this.output;
} }
public OPLChip(int samplerate, int voices, int nts, boolean deeptremolo, boolean deepvibrato) { public OPLChip(int samplerate, int voices, int velo_func, int nts, boolean deeptremolo, boolean deepvibrato) {
this.channel = new OPLChannel[voices]; this.channel = new OPLChannel[voices];
this.slot = new OPLSlot[voices * 2];
this.rateratio = (samplerate << RSM_FRAC) / 49716; this.rateratio = (samplerate << RSM_FRAC) / 49716;
this.velo_func = (byte)velo_func;
this.nts = (byte)nts; this.nts = (byte)nts;
this.tremoloshift = (byte)(deeptremolo ? 2 : 4); this.tremoloshift = (byte)(deeptremolo ? 2 : 4);
this.vibshift = (byte)(deepvibrato ? 0 : 1); this.vibshift = (byte)(deepvibrato ? 0 : 1);
for (int slotnum = 0; slotnum < this.slot.length; slotnum++) for(int z = 0; z < this.channel.length; z++) {
{ this.channel[z] = new OPLChannel();
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() { public boolean isPlaying() {
for(OPLSlot slot : this.slot) { for(OPLChannel chn : this.channel) {
if(slot.eg_out <= 0x100) for(OPLSlot slot : chn.slots) {
return true; if(slot.eg_out <= 0x100)
return true;
}
} }
return false; return false;
} }