1
0
Fork 0

allow changing MIDI parameters while playing

This commit is contained in:
Sen 2025-09-05 09:30:20 +02:00
parent 2d9ea3d654
commit 6c9640203b
Signed by: sen
GPG key ID: 3AC50A6F47D1B722
6 changed files with 115 additions and 55 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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);
}