diff --git a/src/main/java/de/neemann/digital/core/element/Keys.java b/src/main/java/de/neemann/digital/core/element/Keys.java index fd2177896..b5a71a87b 100644 --- a/src/main/java/de/neemann/digital/core/element/Keys.java +++ b/src/main/java/de/neemann/digital/core/element/Keys.java @@ -741,6 +741,12 @@ public final class Keys { */ public static final Key.KeyInteger MIDICHANNEL = new Key.KeyInteger("midiChannel", 0) - .setMin(0) - .setMax(15); + .setMin(0) + .setMax(15); + + /** + * Selects the midi channel + */ + public static final Key MIDIINSTRUMENT = + new Key<>("midiInstrument", ""); } diff --git a/src/main/java/de/neemann/digital/core/io/MIDI.java b/src/main/java/de/neemann/digital/core/io/MIDI.java index 2defa0ea2..96d232887 100644 --- a/src/main/java/de/neemann/digital/core/io/MIDI.java +++ b/src/main/java/de/neemann/digital/core/io/MIDI.java @@ -10,12 +10,8 @@ import de.neemann.digital.core.element.Element; import de.neemann.digital.core.element.ElementAttributes; import de.neemann.digital.core.element.ElementTypeDescription; import de.neemann.digital.core.element.Keys; -import de.neemann.digital.lang.Lang; import javax.sound.midi.MidiChannel; -import javax.sound.midi.MidiSystem; -import javax.sound.midi.MidiUnavailableException; -import javax.sound.midi.Synthesizer; import static de.neemann.digital.core.element.PinInfo.input; @@ -33,10 +29,12 @@ public class MIDI extends Node implements Element { input("V"), input("OnOff"), input("C").setClock()) + .addAttribute(Keys.ROTATE) .addAttribute(Keys.MIDICHANNEL) - .addAttribute(Keys.ROTATE); + .addAttribute(Keys.MIDIINSTRUMENT); private final int chanNum; + private final String instrument; private ObservableValue note; private ObservableValue volume; private ObservableValue clock; @@ -51,6 +49,7 @@ public class MIDI extends Node implements Element { */ public MIDI(ElementAttributes attributes) { chanNum = attributes.get(Keys.MIDICHANNEL); + instrument = attributes.get(Keys.MIDIINSTRUMENT); } @Override @@ -86,27 +85,7 @@ public class MIDI extends Node implements Element { @Override public void init(Model model) throws NodeException { - try { - Synthesizer synth = MidiSystem.getSynthesizer(); - synth.open(); - MidiChannel[] channels = synth.getChannels(); - if (chanNum >= channels.length) { - synth.close(); - throw new NodeException(Lang.get("err_midiChannel_N_NotAvailable", chanNum)); - } - - channel = channels[chanNum]; - if (channel == null) { - synth.close(); - throw new NodeException(Lang.get("err_midiChannel_N_NotAvailable", chanNum)); - } - - model.addObserver(event -> { - if (event.equals(ModelEvent.STOPPED)) - synth.close(); - }, ModelEvent.STOPPED); - } catch (MidiUnavailableException e) { - throw new NodeException(Lang.get("err_midiSystemNotAvailable"), e); - } + MIDIHelper.getInstance().open(model); + channel = MIDIHelper.getInstance().getChannel(chanNum, instrument); } } diff --git a/src/main/java/de/neemann/digital/core/io/MIDIHelper.java b/src/main/java/de/neemann/digital/core/io/MIDIHelper.java new file mode 100644 index 000000000..d7a392de1 --- /dev/null +++ b/src/main/java/de/neemann/digital/core/io/MIDIHelper.java @@ -0,0 +1,136 @@ +package de.neemann.digital.core.io; + +import de.neemann.digital.core.Model; +import de.neemann.digital.core.ModelEvent; +import de.neemann.digital.core.NodeException; +import de.neemann.digital.lang.Lang; + +import javax.sound.midi.*; +import java.util.ArrayList; +import java.util.TreeMap; + +/** + * Helper for MIDI functions + */ +public final class MIDIHelper { + private static MIDIHelper ourInstance = new MIDIHelper(); + + /** + * @return the MIDIHelper + */ + public static MIDIHelper getInstance() { + return ourInstance; + } + + private Synthesizer synthesizer; + private boolean isOpen; + private TreeMap instrumentMap; + + private MIDIHelper() { + } + + private Synthesizer getSynthesizer() throws NodeException { + if (synthesizer == null) { + try { + synthesizer = MidiSystem.getSynthesizer(); + if (synthesizer == null) + throw new NodeException(Lang.get("err_midiSystemNotAvailable")); + } catch (MidiUnavailableException e) { + throw new NodeException(Lang.get("err_midiSystemNotAvailable"), e); + } + } + return synthesizer; + } + + /** + * Opens the synthesizer + * + * @param model the mode used. If the model is closed also the synthesizer is closed + * @throws NodeException NodeException + */ + public void open(Model model) throws NodeException { + if (!isOpen) { + try { + getSynthesizer().open(); + } catch (MidiUnavailableException e) { + throw new NodeException(Lang.get("err_midiSystemNotAvailable"), e); + } + isOpen = true; + + model.addObserver(event -> { + if (event.equals(ModelEvent.STOPPED)) + close(); + }, ModelEvent.STOPPED); + } + } + + private void close() { + if (!isOpen) { + synthesizer.close(); + synthesizer = null; + } + } + + /** + * Creates the channel to use + * + * @param num the channel number + * @param instrument the instrument to use + * @return the channel + * @throws NodeException NodeException + */ + public MidiChannel getChannel(int num, String instrument) throws NodeException { + Instrument instr = null; + if (!instrument.isEmpty()) { + instr = getInstrument(instrument); + + if (!getSynthesizer().loadInstrument(instr)) + throw new NodeException(Lang.get("err_midiInstrument_N_NotAvailable", instrument)); + } + + MidiChannel[] channels = getSynthesizer().getChannels(); + if (num >= channels.length) { + close(); + throw new NodeException(Lang.get("err_midiChannel_N_NotAvailable", num)); + } + + MidiChannel channel = channels[num]; + if (channel == null) { + close(); + throw new NodeException(Lang.get("err_midiChannel_N_NotAvailable", num)); + } + + if (instr != null) { + final Patch patch = instr.getPatch(); + channel.programChange(patch.getBank(), patch.getProgram()); + } + + return channel; + } + + /** + * @return the list of available instruments + * @throws NodeException NodeException + */ + public String[] getInstruments() throws NodeException { + return new ArrayList<>(getInstumentMap().keySet()).toArray(new String[0]); + } + + private Instrument getInstrument(String instrument) throws NodeException { + Instrument i = getInstumentMap().get(instrument); + if (i == null) + throw new NodeException(Lang.get("err_midiInstrument_N_NotAvailable", instrument)); + return i; + } + + private TreeMap getInstumentMap() throws NodeException { + if (instrumentMap == null) { + instrumentMap = new TreeMap<>(); + for (Instrument i : getSynthesizer().getAvailableInstruments()) { + instrumentMap.put(i.getName(), i); + } + } + return instrumentMap; + } + +} diff --git a/src/main/java/de/neemann/digital/gui/components/EditorFactory.java b/src/main/java/de/neemann/digital/gui/components/EditorFactory.java index 36f3a56bd..838352f6c 100644 --- a/src/main/java/de/neemann/digital/gui/components/EditorFactory.java +++ b/src/main/java/de/neemann/digital/gui/components/EditorFactory.java @@ -13,6 +13,7 @@ import de.neemann.digital.core.element.*; import de.neemann.digital.core.extern.Application; import de.neemann.digital.core.extern.PortDefinition; import de.neemann.digital.core.io.InValue; +import de.neemann.digital.core.io.MIDIHelper; import de.neemann.digital.core.memory.DataField; import de.neemann.digital.core.memory.ROM; import de.neemann.digital.core.memory.importer.Importer; @@ -94,6 +95,9 @@ public final class EditorFactory { * @return the editor */ public Editor create(Key key, T value) { + if (key == Keys.MIDIINSTRUMENT) + return (Editor) new MidiInstrumentEditor(value.toString()); + Class fac = map.get(key.getValueClass()); if (fac == null) throw new RuntimeException("no editor found for " + key.getValueClass().getSimpleName()); @@ -1037,4 +1041,34 @@ public final class EditorFactory { romManager = value; } } + + private static final class MidiInstrumentEditor extends LabelEditor { + private JComboBox comb; + + private MidiInstrumentEditor(String instrument) { + String[] instruments; + try { + instruments = MIDIHelper.getInstance().getInstruments(); + } catch (NodeException e) { + instruments = new String[]{"MIDI not available"}; + } + comb = new JComboBox<>(instruments); + comb.setSelectedItem(instrument); + } + + @Override + protected JComponent getComponent(ElementAttributes elementAttributes) { + return comb; + } + + @Override + public String getValue() { + return (String) comb.getSelectedItem(); + } + + @Override + public void setValue(String value) { + comb.setSelectedItem(value); + } + } } diff --git a/src/main/resources/lang/lang_de.xml b/src/main/resources/lang/lang_de.xml index edaba7482..ac1aeca50 100644 --- a/src/main/resources/lang/lang_de.xml +++ b/src/main/resources/lang/lang_de.xml @@ -1002,6 +1002,7 @@ Sind evtl. die Namen der Variablen nicht eindeutig? unterschiedliche Bezeichnungen haben. Die lexikalische Ordnung legt dann die Reihenfolge der RAMs fest. Das MIDI-System ist nicht verfügbar. Der MIDI-Kanal {0} ist nicht verfügbar. + Das MIDI-Instrument {0} ist nicht verfügbar. Adress-Bits Anzahl der Adress-Bits, die verwendet werden. @@ -1302,6 +1303,8 @@ Sind evtl. die Namen der Variablen nicht eindeutig? MIDI-Kanal Legt den MIDI-Kanal fest. + MIDI-Instrument + Das MIDI-Instrument, welches verwendet werden soll. Leitung eingefügt. Aus Zwischenablage eingefügt. diff --git a/src/main/resources/lang/lang_en.xml b/src/main/resources/lang/lang_en.xml index 50e489782..5c7997b24 100644 --- a/src/main/resources/lang/lang_en.xml +++ b/src/main/resources/lang/lang_en.xml @@ -995,6 +995,7 @@ different names. The lexical order then determines the order of the RAMs. The MIDI-System is not available. The MIDI channel {0} is not available. + The MIDI instrument {0} is not available. Address Bits Number of address bits used. @@ -1290,6 +1291,8 @@ MIDI channel Selects the MIDI channel to use. + MIDI instrument + The MIDI instrument to use. Inserted wire. Insert from clipboard.