allow changing MIDI parameters while playing
This commit is contained in:
parent
2d9ea3d654
commit
6c9640203b
6 changed files with 115 additions and 55 deletions
|
@ -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<MidiBank> {
|
||||
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")
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -261,7 +261,7 @@ public class GuiPlayer extends GuiList<GuiPlayer.SongEntry> {
|
|||
}
|
||||
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<GuiPlayer.SongEntry> {
|
|||
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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue