improved the midi component

This commit is contained in:
hneemann 2019-02-11 14:03:12 +01:00
parent 1590790a90
commit b4d30bfe2e
6 changed files with 190 additions and 29 deletions

View File

@ -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<String> MIDIINSTRUMENT =
new Key<>("midiInstrument", "");
}

View File

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

View File

@ -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<String, Instrument> 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<String, Instrument> getInstumentMap() throws NodeException {
if (instrumentMap == null) {
instrumentMap = new TreeMap<>();
for (Instrument i : getSynthesizer().getAvailableInstruments()) {
instrumentMap.put(i.getName(), i);
}
}
return instrumentMap;
}
}

View File

@ -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 <T> Editor<T> create(Key<T> key, T value) {
if (key == Keys.MIDIINSTRUMENT)
return (Editor<T>) new MidiInstrumentEditor(value.toString());
Class<? extends Editor> 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<String> {
private JComboBox<String> 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);
}
}
}

View File

@ -1002,6 +1002,7 @@ Sind evtl. die Namen der Variablen nicht eindeutig?</string>
unterschiedliche Bezeichnungen haben. Die lexikalische Ordnung legt dann die Reihenfolge der RAMs fest.</string>
<string name="err_midiSystemNotAvailable">Das MIDI-System ist nicht verfügbar.</string>
<string name="err_midiChannel_N_NotAvailable">Der MIDI-Kanal {0} ist nicht verfügbar.</string>
<string name="err_midiInstrument_N_NotAvailable">Das MIDI-Instrument {0} ist nicht verfügbar.</string>
<string name="key_AddrBits">Adress-Bits</string><!-- ROM, RAMDualPort, RAMSinglePort, RAMSinglePortSel, EEPROM -->
<string name="key_AddrBits_tt">Anzahl der Adress-Bits, die verwendet werden.</string>
@ -1302,6 +1303,8 @@ Sind evtl. die Namen der Variablen nicht eindeutig?</string>
<string name="key_midiChannel">MIDI-Kanal</string>
<string name="key_midiChannel_tt">Legt den MIDI-Kanal fest.</string>
<string name="key_midiInstrument">MIDI-Instrument</string>
<string name="key_midiInstrument_tt">Das MIDI-Instrument, welches verwendet werden soll.</string>
<string name="mod_insertWire">Leitung eingefügt.</string>
<string name="mod_insertCopied">Aus Zwischenablage eingefügt.</string>

View File

@ -995,6 +995,7 @@
different names. The lexical order then determines the order of the RAMs.</string>
<string name="err_midiSystemNotAvailable">The MIDI-System is not available.</string>
<string name="err_midiChannel_N_NotAvailable">The MIDI channel {0} is not available.</string>
<string name="err_midiInstrument_N_NotAvailable">The MIDI instrument {0} is not available.</string>
<string name="key_AddrBits">Address Bits</string><!-- ROM, RAMDualPort, RAMSinglePort, RAMSinglePortSel, EEPROM -->
<string name="key_AddrBits_tt">Number of address bits used.</string>
@ -1290,6 +1291,8 @@
<string name="key_midiChannel">MIDI channel</string>
<string name="key_midiChannel_tt">Selects the MIDI channel to use.</string>
<string name="key_midiInstrument">MIDI instrument</string>
<string name="key_midiInstrument_tt">The MIDI instrument to use.</string>
<string name="mod_insertWire">Inserted wire.</string>
<string name="mod_insertCopied">Insert from clipboard.</string>