opl arb update
This commit is contained in:
parent
16b05b2e1e
commit
c0919b0eed
2 changed files with 479 additions and 647 deletions
|
@ -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() {
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue