diff --git a/client/src/main/java/client/Client.java b/client/src/main/java/client/Client.java index 1c562ec9..b0ade46a 100755 --- a/client/src/main/java/client/Client.java +++ b/client/src/main/java/client/Client.java @@ -296,6 +296,27 @@ public class Client implements IThreadListener { } } + public static class MidiFlagFunction implements BoolFunction { + public void apply(BoolVar cv, boolean value) { + if(Client.CLIENT.getAudioInterface() != null) + GuiPlayer.INSTANCE.updateFlags(); + } + } + + public static class MidiIntFunction implements IntFunction { + public void apply(IntVar cv, int value) { + if(Client.CLIENT.getAudioInterface() != null) + GuiPlayer.INSTANCE.updateFlags(); + } + } + + public static class MidiBankFunction implements EnumFunction { + public void apply(EnumVar cv, MidiBank value) { + if(Client.CLIENT.getAudioInterface() != null) + GuiPlayer.INSTANCE.updateBank(); + } + } + private interface DebugRunner { void execute(Keysym key); } @@ -661,29 +682,31 @@ public class Client implements IThreadListener { @Variable(name = "snd_frame_size", category = CVarCategory.SOUND, min = 2, max = 8192, display = "Intervall") private int soundFrameSize = 32; - @Variable(name = "mid_dont_fade", category = CVarCategory.SOUND, display = "Nicht ausklingen") + @Variable(name = "mid_dont_fade", category = CVarCategory.SOUND, display = "Nicht ausklingen", callback = MidiFlagFunction.class) public boolean midiNoWait = false; @Variable(name = "mid_opl_voices", category = CVarCategory.SOUND, min = 4, max = 192, display = "OPL-Stimmen") public int midiVoices = 64; - @Variable(name = "mid_keep_notes", category = CVarCategory.SOUND, display = "Stimmen behalten") + @Variable(name = "mid_pitchbend_range", category = CVarCategory.SOUND, min = 0, max = 24, display = "Pitch-Bend-Bereich", callback = MidiIntFunction.class) + public int midiPitchBendRange = 2; + @Variable(name = "mid_keep_notes", category = CVarCategory.SOUND, display = "Stimmen behalten", callback = MidiFlagFunction.class) public boolean midiKeep = false; - @Variable(name = "mid_play_unknown", category = CVarCategory.SOUND, display = "Unbekannte Banken") + @Variable(name = "mid_play_unknown", category = CVarCategory.SOUND, display = "Unbekannte Banken", callback = MidiFlagFunction.class) public boolean midiUnknown = true; // STR(MID_VELO_LOG, "", "Log.+Minimum [m+nlog(x)]") // STR(MID_VELO_ATTN, "", "Log. Gedämpft [nlog(x)]") // STR(MID_VELO_LIN, "", "Linear [x]") // STR(MID_VELO_ONE, "", "Vollklang [1]") - @Variable(name = "mid_velocity_func", category = CVarCategory.SOUND, min = -128, max = 127, display = "Anschlag") + @Variable(name = "mid_velocity_func", category = CVarCategory.SOUND, min = -128, max = 127, display = "Anschlag", callback = MidiIntFunction.class) public int midiVelocity = 1; - @Variable(name = "mid_opl_bank", category = CVarCategory.SOUND, display = "Bank") + @Variable(name = "mid_opl_bank", category = CVarCategory.SOUND, display = "Bank", callback = MidiBankFunction.class) public MidiBank midiBank = MidiBank.DMX_DMX; @Variable(name = "mid_debug_events", category = CVarCategory.SOUND, display = "MIDI-Debug", callback = MidiDebugFunction.class) public boolean midiDebug = false; @Variable(name = "mid_visualizer", category = CVarCategory.SOUND, display = "Visualisation", callback = MidiVisFunction.class) public boolean midiVisualizer = true; - @Variable(name = "mid_ch_16_workaround", category = CVarCategory.SOUND, display = "Kanal 16 als Perkussion") + @Variable(name = "mid_ch_16_workaround", category = CVarCategory.SOUND, display = "Kanal 16 als Perkussion", callback = MidiFlagFunction.class) public boolean midiCh16Drums = false; - @Variable(name = "mid_allow_switch_perc_ch", category = CVarCategory.SOUND, display = "Perk.-Kanäle änderbar") + @Variable(name = "mid_allow_switch_perc_ch", category = CVarCategory.SOUND, display = "Perk.-Kanäle änderbar", callback = MidiFlagFunction.class) public boolean midiDrumProgs = false; @Variable(name = "draw_use_shader", category = CVarCategory.RENDER, display = "Shader verwenden") diff --git a/client/src/main/java/client/audio/MidiDecoder.java b/client/src/main/java/client/audio/MidiDecoder.java index 6f2b756e..81be8d8e 100644 --- a/client/src/main/java/client/audio/MidiDecoder.java +++ b/client/src/main/java/client/audio/MidiDecoder.java @@ -132,10 +132,10 @@ public class MidiDecoder { public final Track[] tracks; public final int tpqn; - public final boolean nowait; public final OPLChip chip; public final MidiHandle bank; - + + public volatile boolean nowait; public int uspb; public int ticktime; public long time; @@ -161,7 +161,7 @@ public class MidiDecoder { return value; } - public MidiDecoder(byte[] data, boolean nowait, int voices, MidiBank bank, boolean keep, boolean useunkn, boolean percsw, int velofunc, int[] percussion) throws IOException { + public MidiDecoder(byte[] data, boolean nowait, int voices, MidiBank bank, boolean keep, boolean useunkn, boolean percsw, int pbrange, int velofunc, int[] percussion) throws IOException { int size = data.length; if(size < 14) { throw new IOException("Datei zu klein"); @@ -222,7 +222,7 @@ public class MidiDecoder { this.nowait = nowait; this.setTempo(500000); this.chip = new OPLChip(48000, voices, velofunc, 0, false, false); - this.bank = new MidiHandle(this.chip, bank.getData(), keep, useunkn, percsw, percussion); + this.bank = new MidiHandle(this.chip, bank.getData(), keep, useunkn, percsw, pbrange, percussion); this.playing = true; } @@ -231,12 +231,22 @@ public class MidiDecoder { this.tpqn = note; this.nowait = false; this.chip = new OPLChip(48000, 16, 0, 0, false, false); - this.bank = new MidiHandle(this.chip, bank.getData(), false, true, false, new int[] {10}); + this.bank = new MidiHandle(this.chip, bank.getData(), false, true, false, 2, new int[] {10}); this.ticktime = start; this.uspb = end; this.time = (long)delay * 1000000L; this.debug = true; } + + public void setFlags(boolean nowait, boolean keep, boolean useunkn, boolean percsw, int pbrange, int velofunc, int[] percussion) { + this.bank.setFlags(keep, useunkn, percsw, pbrange, percussion); + this.chip.setVeloFunc(velofunc); + this.nowait = nowait; + } + + public void setBank(MidiBank bank) { + this.bank.setInstruments(bank.getData()); + } private void setTempo(int tempo) { this.uspb = tempo; diff --git a/client/src/main/java/client/audio/MidiHandle.java b/client/src/main/java/client/audio/MidiHandle.java index 752c4106..88d04332 100644 --- a/client/src/main/java/client/audio/MidiHandle.java +++ b/client/src/main/java/client/audio/MidiHandle.java @@ -36,13 +36,11 @@ public class MidiHandle { int volume = 127; int pitch; int program; - Instrument instr; Channel(boolean drum) { Arrays.fill(this.notes, -1); this.volume = 127; this.pbank = drum ? 128 : 0; - this.instr = MidiHandle.this.instruments[drum ? 128 : 0]; for(int z = 0; z < this.keys.length; z++) { this.keys[z] = new Key(); } @@ -50,28 +48,24 @@ public class MidiHandle { } private static final int BANK_MAX = 64; - private static final double BANK_PBRANGE = 2.0; public final Channel[] channels = new Channel[16]; public final Voice[] voices; - public final Instrument[] instruments; - public final boolean keep; - public final boolean useunkn; - public final boolean percsw; - public final boolean[] percussion = new boolean[16]; + + public volatile Instrument[] instruments; + public volatile boolean keep; + public volatile boolean useunkn; + public volatile boolean percsw; + public volatile int pbrange; + public volatile boolean[] percussion; public int index; public int used; - public MidiHandle(OPLChip chip, Instrument[] instr, boolean keep, boolean useunkn, boolean percsw, int[] percussion) { - this.keep = keep; - this.useunkn = useunkn; - this.percsw = percsw; + public MidiHandle(OPLChip chip, Instrument[] instr, boolean keep, boolean useunkn, boolean percsw, int pbrange, int[] percussion) { + this.setFlags(keep, useunkn, percsw, pbrange, percussion); this.instruments = instr; this.voices = new Voice[chip.channel.length]; - for(int ch : percussion) { - this.percussion[ch - 1] = true; - } for(int z = 0; z < this.channels.length; z++) { this.channels[z] = new Channel(this.percussion[z]); } @@ -79,6 +73,21 @@ public class MidiHandle { this.voices[z] = new Voice(chip.channel[z]); } } + + public void setInstruments(Instrument[] instr) { + this.instruments = instr; + } + + public void setFlags(boolean keep, boolean useunkn, boolean percsw, int pbrange, int[] percussion) { + this.keep = keep; + this.useunkn = useunkn; + this.percsw = percsw; + this.pbrange = pbrange; + this.percussion = new boolean[16]; + for(int ch : percussion) { + this.percussion[ch - 1] = true; + } + } private void releaseVoice(Channel ch, Voice voice) { if(voice.note == -1) @@ -106,15 +115,20 @@ public class MidiHandle { } private Voice getVoice(Channel ch, int note, int velocity) { - Instrument instr = ch.instr; + final boolean useunkn = this.useunkn; + final boolean keep = this.keep; + final Instrument[] instruments = this.instruments; + Instrument instr; if(ch.pbank == 128) - instr = this.instruments[128 + note]; - else if(ch.pbank != 0 && !this.useunkn) + instr = instruments[128 + note]; + else if(ch.pbank != 0 && !useunkn) return null; + else + instr = instruments[ch.program]; if(instr.ops.length == 0) return null; if(this.used == this.voices.length) { - if(this.keep) + if(keep) return null; this.releaseKey(this.voices[this.index].channel, this.voices[this.index].note); } @@ -124,7 +138,7 @@ public class MidiHandle { if(this.index >= this.voices.length) this.index = 0; if(vi == this.index) { - if(this.keep) + if(keep) return null; this.releaseKey(this.voices[this.index].channel, this.voices[this.index].note); break; @@ -190,18 +204,6 @@ public class MidiHandle { } } - private void progupdate(Channel ch) { - this.notesoff(ch); - int id = ch.program; - if(ch.pbank == 128) { - id = 128; - } - else if(ch.pbank != 0 && !this.useunkn) { - id = 0; - } - ch.instr = this.instruments[id]; - } - private void levelupdate(Channel ch) { int done = 0; for(int h = 0; (h < BANK_MAX) && (done < ch.active); h++) { @@ -241,16 +243,19 @@ public class MidiHandle { public void progchange(int channel, int program) { Channel ch = this.channels[channel]; ch.program = program; - this.progupdate(ch); + this.notesoff(ch); } public void pitchbend(int channel, int pitch) { + final int pbrange = this.pbrange; Channel ch = this.channels[channel]; - ch.pitch = (int)((((double)pitch) / 8191.0 * BANK_PBRANGE) * 10000.0); + ch.pitch = (int)((((double)pitch) / 8191.0 * (double)pbrange) * 10000.0); this.frequpdate(ch); } public void control(int channel, int control, int value) { + final boolean percsw = this.percsw; + final boolean[] percussion = this.percussion; Channel ch = this.channels[channel]; switch(control) { case 0x00: // bank MSB @@ -258,22 +263,22 @@ public class MidiHandle { // ch.pbank = 128; // } // else { - if(this.percsw || !this.percussion[channel]) { + if(percsw || !percussion[channel]) { ch.pbank &= 0x007f; ch.pbank |= (value << 7) & 0x3f80; } - this.progupdate(ch); + this.notesoff(ch); break; case 0x20: // bank LSB // if((channel == 9) && (value == 0)) { // ch.pbank = 128; // } // else { - if(this.percsw || !this.percussion[channel]) { + if(percsw || !percussion[channel]) { ch.pbank &= 0x3f80; ch.pbank |= value & 0x007f; } - this.progupdate(ch); + this.notesoff(ch); break; case 0x07: // volume MSB ch.volume = value; @@ -284,10 +289,10 @@ public class MidiHandle { this.levelupdate(ch); break; case 0x79: // reset - ch.pbank = this.percussion[channel] ? 128 : 0; + ch.pbank = percussion[channel] ? 128 : 0; ch.volume = 127; ch.pan = 0; - this.progupdate(ch); + this.notesoff(ch); this.levelupdate(ch); break; case 0x7b: // all off diff --git a/client/src/main/java/client/audio/OPLChip.java b/client/src/main/java/client/audio/OPLChip.java index 1c0107ca..cfcd4620 100644 --- a/client/src/main/java/client/audio/OPLChip.java +++ b/client/src/main/java/client/audio/OPLChip.java @@ -529,7 +529,7 @@ public class OPLChip { } private void updateLevel(boolean right, int velocity, int volume, int pan) { - int function = OPLChip.this.velo_func; + final int function = OPLChip.this.velo_func; 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)(right ? -pan : pan))/63.0)); @@ -701,8 +701,8 @@ public class OPLChip { private final int nts; private final int vibshift; private final int tremoloshift; - private final int velo_func; + private volatile int velo_func; private int timer; private long eg_timer; private boolean eg_timerrem; @@ -973,6 +973,10 @@ public class OPLChip { this.channel[z] = new OPLChannel(); } } + + public void setVeloFunc(int velo_func) { + this.velo_func = velo_func; + } public boolean isPlaying() { for(OPLChannel chn : this.channel) { diff --git a/client/src/main/java/client/gui/GuiPlayer.java b/client/src/main/java/client/gui/GuiPlayer.java index 0c480359..20fd104a 100644 --- a/client/src/main/java/client/gui/GuiPlayer.java +++ b/client/src/main/java/client/gui/GuiPlayer.java @@ -261,7 +261,7 @@ public class GuiPlayer extends GuiList { } MidiDecoder midi; try { - midi = new MidiDecoder(data, this.gm.midiNoWait, this.gm.midiVoices, this.gm.midiBank, this.gm.midiKeep, this.gm.midiUnknown, this.gm.midiDrumProgs, this.gm.midiVelocity, this.getPercussionChannels()); + midi = new MidiDecoder(data, this.gm.midiNoWait, this.gm.midiVoices, this.gm.midiBank, this.gm.midiKeep, this.gm.midiUnknown, this.gm.midiDrumProgs, this.gm.midiPitchBendRange, this.gm.midiVelocity, this.getPercussionChannels()); } catch(Throwable e) { Log.SOUND.error(e, "Konnte MIDI '%s' nicht laden", file); @@ -271,6 +271,22 @@ public class GuiPlayer extends GuiList { this.gm.getAudioInterface().alMidi(midi); } + public void updateFlags() { + MidiDecoder midi = this.gm.getAudioInterface().alGetMidi(); + if(midi == null) + midi = this.paused; + if(midi != null) + midi.setFlags(this.gm.midiNoWait, this.gm.midiKeep, this.gm.midiUnknown, this.gm.midiDrumProgs, this.gm.midiPitchBendRange, this.gm.midiVelocity, this.getPercussionChannels()); + } + + public void updateBank() { + MidiDecoder midi = this.gm.getAudioInterface().alGetMidi(); + if(midi == null) + midi = this.paused; + if(midi != null) + midi.setBank(this.gm.midiBank); + } + public void test(int start, int end, int delay, int note) { this.paused = null; this.playing = false; diff --git a/client/src/main/java/client/gui/options/GuiSound.java b/client/src/main/java/client/gui/options/GuiSound.java index 9ad22648..772e7626 100644 --- a/client/src/main/java/client/gui/options/GuiSound.java +++ b/client/src/main/java/client/gui/options/GuiSound.java @@ -49,6 +49,8 @@ public class GuiSound extends GuiOptions { this.addSelector("mid_ch_16_workaround", 0, y += BASE_SHIFT, 0, 0); this.addSelector("mid_allow_switch_perc_ch", H_SHIFT, y, 0, 0); + this.addSelector("mid_pitchbend_range", 0, y += BASE_SHIFT, 0, 0); + super.init(width, height); }