386 lines
11 KiB
Java
386 lines
11 KiB
Java
|
// MidiSynth.java
|
||
|
// guiserver
|
||
|
//
|
||
|
//
|
||
|
// Copyright (C) 2016 Lutz Mueller
|
||
|
//
|
||
|
// This program is free software: you can redistribute it and/or modify
|
||
|
// it under the terms of the GNU General Public License as published by
|
||
|
// the Free Software Foundation, either version 3 of the License, or
|
||
|
// (at your option) any later version.
|
||
|
//
|
||
|
// This program is distributed in the hope that it will be useful,
|
||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
// GNU General Public License for more details.
|
||
|
//
|
||
|
// You should have received a copy of the GNU General Public License
|
||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
|
//
|
||
|
|
||
|
import java.lang.*;
|
||
|
import java.awt.*;
|
||
|
import java.util.*;
|
||
|
import java.io.File;
|
||
|
import javax.sound.midi.*;
|
||
|
|
||
|
public class MidiSynth
|
||
|
{
|
||
|
static int noteNumber = 0;
|
||
|
static int velocity = 0;
|
||
|
static int duration = 0;
|
||
|
static Synthesizer synth = null;
|
||
|
static Sequencer sequencer = null;
|
||
|
static Receiver receiver = null;
|
||
|
static Transmitter transmitter = null;
|
||
|
static MidiChannel[] channels = null;
|
||
|
static Instrument instruments[] = null;
|
||
|
static Sequence sequence = null;
|
||
|
static Soundbank soundbank = null;
|
||
|
static int sysResolution = 32;
|
||
|
static final int sysBpm = 120;
|
||
|
static int userResolution = 16;
|
||
|
static int userBpm = 120;
|
||
|
static int tickAccum;
|
||
|
static Track track;
|
||
|
static Integer[] channelPrograms = null;
|
||
|
static Integer[] channelBends = null;
|
||
|
static HashMap instrumentNumbers = new HashMap();
|
||
|
|
||
|
@SuppressWarnings("unchecked")
|
||
|
public static void midiInit(StringTokenizer params)
|
||
|
{
|
||
|
String filePath;
|
||
|
try {
|
||
|
synth = MidiSystem.getSynthesizer();
|
||
|
synth.open();
|
||
|
sequencer = MidiSystem.getSequencer();
|
||
|
//transmitter = sequencer.getTransmitter();
|
||
|
//receiver = synth.getReceiver();
|
||
|
//transmitter.setReceiver(receiver);
|
||
|
}
|
||
|
catch (Exception me)
|
||
|
{
|
||
|
ErrorDialog.showExit("gs:midi-init", "MIDI system unavailable - will exit");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if(params.hasMoreTokens())
|
||
|
{
|
||
|
filePath = Base64Coder.decodeString(params.nextToken());
|
||
|
try { soundbank = MidiSystem.getSoundbank(new File(filePath)); }
|
||
|
catch (Exception me) {
|
||
|
ErrorDialog.show("gs:midi-init", "Cannot read soundbank:" + filePath);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
soundbank = synth.getDefaultSoundbank();
|
||
|
|
||
|
instruments = soundbank.getInstruments();
|
||
|
|
||
|
//instruments = synth.getDefaultSoundbank().getInstruments();
|
||
|
//instruments = synth.getAvailableInstruments();
|
||
|
for(int i = 0; i < Math.min(128, instruments.length); i++)
|
||
|
instrumentNumbers.put(instruments[i].getName(), i);
|
||
|
|
||
|
synth.loadInstrument(instruments[0]);
|
||
|
channelPrograms = new Integer[16];
|
||
|
channelBends = new Integer[16];
|
||
|
for(int i = 0; i < 16; i++)
|
||
|
{
|
||
|
channelPrograms[i] = 0;
|
||
|
channelBends[i] = 8192;
|
||
|
}
|
||
|
channels = synth.getChannels();
|
||
|
}
|
||
|
|
||
|
public static void playNote(StringTokenizer params)
|
||
|
{
|
||
|
int bend = 0;
|
||
|
|
||
|
noteNumber = Integer.parseInt(params.nextToken());
|
||
|
noteNumber = Math.min(127, Math.max(0, noteNumber));
|
||
|
|
||
|
duration = Integer.parseInt(params.nextToken());
|
||
|
int milliseconds = (duration * 60000)/(userBpm * userResolution);
|
||
|
|
||
|
velocity = Integer.parseInt(params.nextToken());
|
||
|
velocity = Math.min(127, Math.max(0, velocity));
|
||
|
|
||
|
int chan = Integer.parseInt(params.nextToken());
|
||
|
|
||
|
if(params.hasMoreTokens())
|
||
|
{
|
||
|
bend = Integer.parseInt(params.nextToken());
|
||
|
bend = Math.min(8192, Math.max(-8192, bend));
|
||
|
}
|
||
|
|
||
|
if(channels == null)
|
||
|
{
|
||
|
ErrorDialog.showExit("gs:play-note", "MIDI system no initialized - will exit");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
MidiChannel channel = channels[chan];
|
||
|
channel.setPitchBend(channelBends[chan] + bend);
|
||
|
|
||
|
channel.noteOn(noteNumber, velocity);
|
||
|
|
||
|
try { Thread.sleep(milliseconds);}
|
||
|
catch (InterruptedException e) { }
|
||
|
|
||
|
channel.noteOff(noteNumber);
|
||
|
}
|
||
|
|
||
|
public static void getInstruments()
|
||
|
{
|
||
|
if(instruments == null)
|
||
|
{
|
||
|
ErrorDialog.showExit("gs:get-instruments", "MIDI system no initialized - will exit");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
guiserver.out.print("(set 'gs:instruments '( ");
|
||
|
for(int i = 0; i < Math.min(128, instruments.length); i++)
|
||
|
{
|
||
|
Instrument instrument = instruments[i];
|
||
|
guiserver.out.print("\"" + Base64Coder.encodeString(instrument.getName()) + "\" ");
|
||
|
}
|
||
|
guiserver.out.println(")) ");
|
||
|
guiserver.out.flush();
|
||
|
|
||
|
}
|
||
|
|
||
|
public static void midiPatch(StringTokenizer params)
|
||
|
{
|
||
|
String programName = Base64Coder.decodeString(params.nextToken());
|
||
|
int chan = Integer.parseInt(params.nextToken());
|
||
|
|
||
|
if(synth == null)
|
||
|
ErrorDialog.showExit("gs:midi-patch", "MIDI system no initialized - will exit");
|
||
|
|
||
|
int program = 0;
|
||
|
|
||
|
try { program = (Integer)instrumentNumbers.get(programName); }
|
||
|
catch (Exception ex) {
|
||
|
ErrorDialog.show("gs:midi-patch", "Instrument not known");
|
||
|
}
|
||
|
|
||
|
try { synth.loadInstrument(instruments[program]); }
|
||
|
catch (Exception e) {
|
||
|
ErrorDialog.show("gs:midi-path", "Instrument not available");
|
||
|
}
|
||
|
|
||
|
MidiChannel channel = channels[chan];
|
||
|
channel.programChange(program);
|
||
|
channelPrograms[chan] = program;
|
||
|
}
|
||
|
|
||
|
public static void midiBPM(StringTokenizer params)
|
||
|
{
|
||
|
userBpm = Integer.parseInt(params.nextToken());
|
||
|
if(params.hasMoreTokens())
|
||
|
userResolution = Integer.parseInt(params.nextToken());
|
||
|
}
|
||
|
|
||
|
public static void channelBend(StringTokenizer params)
|
||
|
{
|
||
|
int chan = Integer.parseInt(params.nextToken());
|
||
|
int bend = Integer.parseInt(params.nextToken());
|
||
|
bend = Math.min(8191, Math.max(-8192, bend));
|
||
|
channelBends[chan] = bend + 8192;
|
||
|
|
||
|
MidiChannel channel = channels[chan];
|
||
|
}
|
||
|
|
||
|
public static void channelReverb(StringTokenizer params)
|
||
|
{
|
||
|
final int REVERB = 91;
|
||
|
int chan = Integer.parseInt(params.nextToken());
|
||
|
int reverb = Integer.parseInt(params.nextToken());
|
||
|
reverb = Math.min(127, Math.max(0, reverb));
|
||
|
|
||
|
MidiChannel channel = channels[chan];
|
||
|
channel.controlChange(REVERB, reverb);
|
||
|
}
|
||
|
|
||
|
public static void channelPressure(StringTokenizer params)
|
||
|
{
|
||
|
int chan = Integer.parseInt(params.nextToken());
|
||
|
int pressure = Integer.parseInt(params.nextToken());
|
||
|
pressure = Math.min(127, Math.max(0, pressure));
|
||
|
|
||
|
MidiChannel channel = channels[chan];
|
||
|
channel.setChannelPressure(pressure);
|
||
|
}
|
||
|
|
||
|
public static void addTrack(StringTokenizer params)
|
||
|
{
|
||
|
ShortMessage message;
|
||
|
int chan = Integer.parseInt(params.nextToken());
|
||
|
MidiChannel channel = channels[chan];
|
||
|
int program = channelPrograms[chan];
|
||
|
float tickFactor = ((userResolution * 120) / userBpm);
|
||
|
|
||
|
//System.out.println("tickFactor: " + tickFactor);
|
||
|
|
||
|
if(sequence == null)
|
||
|
try {
|
||
|
//System.out.println("creating sequence: " + resolution);
|
||
|
sequence = new Sequence(Sequence.PPQ, sysResolution);
|
||
|
}
|
||
|
catch (Exception ex) {
|
||
|
ErrorDialog.showExit("gs:add-track", "Cannot create sequence - will exit");
|
||
|
}
|
||
|
|
||
|
|
||
|
track = sequence.createTrack();
|
||
|
tickAccum = 0;
|
||
|
|
||
|
synth.loadInstrument(instruments[program]);
|
||
|
createShortMessage(ShortMessage.PROGRAM_CHANGE, chan, program, 64);
|
||
|
tickAccum = 1;
|
||
|
|
||
|
int oldBend = 8192;
|
||
|
|
||
|
while(params.hasMoreTokens())
|
||
|
{
|
||
|
int key = Integer.parseInt(params.nextToken());
|
||
|
int duration = Integer.parseInt(params.nextToken());
|
||
|
int velocity = Integer.parseInt(params.nextToken());
|
||
|
int bend = Integer.parseInt(params.nextToken());
|
||
|
bend = Math.min(8191, Math.max(-8192, bend)) + channelBends[chan];
|
||
|
|
||
|
//System.out.println("bend: " + bend + " tick: " + tickAccum);
|
||
|
|
||
|
int tickIncrement = (int)((duration * tickFactor)/8);
|
||
|
|
||
|
if(bend != oldBend)
|
||
|
{
|
||
|
createShortMessage(ShortMessage.PITCH_BEND, 0, bend & 0x7F, (bend >> 7) & 0xFF);
|
||
|
tickAccum += 1;
|
||
|
tickIncrement -= 1;
|
||
|
oldBend = bend;
|
||
|
}
|
||
|
|
||
|
createShortMessage(ShortMessage.NOTE_ON, chan, key, velocity);
|
||
|
tickAccum += tickIncrement;
|
||
|
createShortMessage(ShortMessage.NOTE_OFF, chan, key, velocity);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public static void createShortMessage(int type, int chan, int num, int velocity)
|
||
|
{
|
||
|
ShortMessage message = new ShortMessage();
|
||
|
|
||
|
try {
|
||
|
message.setMessage(type, chan, num, velocity);
|
||
|
MidiEvent event = new MidiEvent(message, tickAccum);
|
||
|
track.add(event);
|
||
|
}
|
||
|
catch (InvalidMidiDataException de)
|
||
|
{
|
||
|
ErrorDialog.show("MIDI system", "Cannot create message channel:" + chan + " tick:" + tickAccum );
|
||
|
de.printStackTrace();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public static void muteTrack(StringTokenizer params)
|
||
|
{
|
||
|
int track = Integer.parseInt(params.nextToken());
|
||
|
if(params.nextToken().equals("nil"))
|
||
|
sequencer.setTrackMute(track, false);
|
||
|
else
|
||
|
sequencer.setTrackMute(track, true);
|
||
|
}
|
||
|
|
||
|
// seems not to work on Mac OS X, 2007 MacBook
|
||
|
public static void soloTrack(StringTokenizer params)
|
||
|
{
|
||
|
int track = Integer.parseInt(params.nextToken());
|
||
|
if(params.nextToken().equals("nil"))
|
||
|
sequencer.setTrackSolo(track, false);
|
||
|
else
|
||
|
sequencer.setTrackSolo(track, false);
|
||
|
}
|
||
|
|
||
|
public static void playSequence(StringTokenizer params)
|
||
|
{
|
||
|
if(synth == null)
|
||
|
ErrorDialog.showExit("gs:play-sequence", "MIDI system not initialized - will exit");
|
||
|
|
||
|
if(sequence == null)
|
||
|
ErrorDialog.showExit("gs:play-sequence", "No tracks created - will exit");
|
||
|
|
||
|
int start = Integer.parseInt(params.nextToken()); // default = 0
|
||
|
int loopCount = Integer.parseInt(params.nextToken()); // default = 0
|
||
|
long startPoint = Long.parseLong(params.nextToken()); // default = 0
|
||
|
long endPoint = Long.parseLong(params.nextToken()); // default = -1
|
||
|
|
||
|
|
||
|
try {
|
||
|
if(sequencer.isOpen() != true)
|
||
|
sequencer.open();
|
||
|
|
||
|
sequencer.setSequence(sequence);
|
||
|
|
||
|
if(endPoint == -1) endPoint = sequence.getTickLength();
|
||
|
|
||
|
//System.out.println("start:" + start + " loop count:" + loopCount + " loop start:" + startPoint + " loop end:" + endPoint);
|
||
|
|
||
|
sequencer.setTickPosition(start);
|
||
|
sequencer.start();
|
||
|
sequencer.setLoopStartPoint(startPoint);
|
||
|
sequencer.setLoopEndPoint(endPoint);
|
||
|
sequencer.setLoopCount(loopCount);
|
||
|
sequencer.setTempoInBPM(sysBpm);
|
||
|
}
|
||
|
catch (Exception e)
|
||
|
{ ErrorDialog.showExit("gs:play-sequence", "Cannot start sequencer - will exit"); }
|
||
|
|
||
|
}
|
||
|
|
||
|
public static void stopSequence()
|
||
|
{
|
||
|
if(sequencer == null)
|
||
|
ErrorDialog.show("gs:play-sequence", "No sequencer to stop");
|
||
|
|
||
|
sequencer.stop();
|
||
|
long tickCount = sequencer.getTickPosition();
|
||
|
|
||
|
guiserver.out.print("(set 'gs:tick-position " + tickCount + ")");
|
||
|
guiserver.out.flush();
|
||
|
}
|
||
|
|
||
|
|
||
|
public static void saveSequence(StringTokenizer params)
|
||
|
{
|
||
|
String filePath = Base64Coder.decodeString(params.nextToken());
|
||
|
//System.out.println("MIDI file:" + filePath);
|
||
|
File file = new File(filePath);
|
||
|
try
|
||
|
{
|
||
|
int[] fileTypes = MidiSystem.getMidiFileTypes(sequence);
|
||
|
if (fileTypes.length == 0)
|
||
|
ErrorDialog.show("gs:save-sequence", "Cannot save sequence");
|
||
|
else
|
||
|
if (MidiSystem.write(sequence, fileTypes[0], file) == -1)
|
||
|
ErrorDialog.show("gs:save-sequence", "Cannot write file");
|
||
|
} catch (Exception ex) {ErrorDialog.show("gs:save-sequence", "Cannot write file:" + filePath);}
|
||
|
}
|
||
|
|
||
|
|
||
|
public static void midiClose()
|
||
|
{
|
||
|
if(synth != null) synth.close();
|
||
|
if(sequence != null) sequencer.close();
|
||
|
|
||
|
synth = null;
|
||
|
sequencer = null;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
/* eof */
|