/* * Created on Sep 11, 2004 * * Copyright (c) 2005 Peter Johan Salomonsen (http://www.petersalomonsen.com) * * http://www.frinika.com * * This file is part of Frinika. * * Frinika 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 2 of the License, or * (at your option) any later version. * * Frinika 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 Frinika; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package com.frinika.synth; import com.frinika.voiceserver.VoiceServer; import java.awt.Graphics2D; import java.awt.Image; import java.awt.image.BufferedImage; import java.io.File; import java.io.FileOutputStream; import java.io.FileInputStream; import java.io.ObjectOutputStream; import java.io.ObjectInputStream; import java.io.Serializable; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; import java.util.ArrayList; import java.util.List; import java.util.Vector; import com.frinika.audio.*; import com.frinika.sequencer.ChannelListProvider; import com.frinika.sequencer.gui.mixer.MidiDeviceIconProvider; import com.frinika.sequencer.midi.MidiListProvider; import com.frinika.sequencer.model.ControllerListProvider; import com.frinika.sequencer.model.FrinikaControllerList; import com.frinika.synth.settings.SynthSettings; import com.frinika.synth.settings.synthsettingsversions.SynthSettings20070815; import com.frinika.synth.soundbank.SynthRackInstrumentIF; import com.frinika.synth.soundbank.SynthRackSoundbank; import javax.annotation.Resource; import javax.sound.midi.*; import javax.sound.sampled.AudioFormat; import javax.sound.sampled.Control; import javax.sound.sampled.Line; import javax.sound.sampled.LineListener; import javax.sound.sampled.LineUnavailableException; import javax.sound.sampled.Mixer; import javax.sound.sampled.TargetDataLine; import javax.sound.sampled.Control.Type; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.JFrame; import javax.swing.JScrollPane; import rasmus.midi.provider.RasmusSynthesizer; /** * The SynthRack class can hold 16 Frinika soft synths - one for each MidiChannel. Also making it * ideal for connecting to your MidiKeyboard for live playing. You can then setup 16 synths * and shift between them using the channel switch on your master keyboard. * * @author Peter Salomonsen * */ public class SynthRack implements Synthesizer, InstrumentNameListener,MidiListProvider,ChannelListProvider,MidiDeviceIconProvider,Mixer { public static final double GAIN = 0.5; VoiceServer voiceServer; @Resource(name="Samplerate") int samplerate; private static Icon icon = new javax.swing.ImageIcon(RasmusSynthesizer.class.getResource("/icons/frinika.png")); public Icon getIcon() { if(icon.getIconHeight() > 16 || icon.getIconWidth() > 16) { BufferedImage img = new BufferedImage(icon.getIconWidth(), icon.getIconHeight(), BufferedImage.TYPE_4BYTE_ABGR); Graphics2D g = img.createGraphics(); icon.paintIcon(null, g, 0, 0); g.dispose(); Image im = img.getScaledInstance(16 , 16, Image.SCALE_SMOOTH); icon = new ImageIcon(im); } return icon; } static private FrinikaControllerList controllerList=new FrinikaControllerList(); boolean saveReferencedData; public static class SynthRackInfo extends MidiDevice.Info { SynthRackInfo() { super("Frinika SynthRack","petersalomonsen.com","A MIDI container for Frinika soft synths","0.4.0"); } } MidiDevice.Info deviceInfo = new SynthRackInfo(); Receiver receiver = new Receiver(){ /* (non-Javadoc) * @see javax.sound.midi.Receiver#send(javax.sound.midi.MidiMessage, long) */ public void send(MidiMessage message, long timeStamp) { try { if(ShortMessage.class.isInstance(message)) { ShortMessage shm = (ShortMessage)message; int synthIndex = shm.getChannel(); MidiChannel channel = midiChannels[shm.getChannel()]; if(channel != null) { if(shm.getCommand() == ShortMessage.NOTE_ON) { if(shm.getData2()==0) channel.noteOff(shm.getData1()); else if(!channel.getMute()) channel.noteOn(shm.getData1(),shm.getData2()); } else if(shm.getCommand() == ShortMessage.NOTE_OFF) channel.noteOff(shm.getData1()); else if(shm.getCommand() == ShortMessage.CONTROL_CHANGE) { channel.controlChange(shm.getData1(),shm.getData2()); if(gui!=null && shm.getData1()==7) gui.synthStrips[synthIndex].setVolume(shm.getData2()); else if(gui!=null && shm.getData1()==10) gui.synthStrips[synthIndex].setPan(shm.getData2()); } else if(shm.getCommand() == ShortMessage.PITCH_BEND) channel.setPitchBend((0xff & shm.getData1())+((0xff & shm.getData2()) << 7)); } if(shm.getCommand() == ShortMessage.PROGRAM_CHANGE) { midiChannels[shm.getChannel()] = synths[shm.getData1()]; } } else if(MetaMessage.class.isInstance(message)) { byte[] msgBytes = message.getMessage(); // Handle tempo message if (msgBytes[0] == -1 && msgBytes[1] == 0x51 && msgBytes[2] == 3) { int mpq = ((msgBytes[3] & 0xff) << 16) | ((msgBytes[4] & 0xff) << 8) | (msgBytes[5] & 0xff); tempoBPM = ((60000000f / mpq)); java.util.logging.Logger.getLogger(this.getClass().getName()).fine("Tempo set to "+tempoBPM+" bpm"); } } } catch(Exception e) { // For debugging // e.printStackTrace(); } } /* (non-Javadoc) * @see javax.sound.midi.Receiver#close() */ public void close() { // TODO Auto-generated method stub } }; List<Receiver> receivers = new ArrayList<Receiver>(); { receivers.add(receiver); } List<Transmitter> transmitters = new ArrayList<Transmitter>(); SynthRackGUI gui; // Previously - one synth per channel - now one synth per program (in the future also banks will be supported) Synth[] synths= new Synth[256]; MidiChannel[] midiChannels = new MidiChannel[16]; private Vector<GlobalInstrumentNameListener> globalInstrumentNameListeners = new Vector<GlobalInstrumentNameListener>(); private SynthRackSoundbank soundbank = new SynthRackSoundbank(); private float tempoBPM = 100f; /** * Construct a Frinika Synth * @param voiceServer * @throws Exception */ public SynthRack(VoiceServer voiceServer) { this.voiceServer = voiceServer; if(voiceServer!=null) MasterVoice.getDefaultInstance().initialize(voiceServer); } public void setSynth(int index, Synth synth) { if(synths[index]!=null) synths[index].close(); if(synth!=null) { synth.addInstrumentNameListener(this); Patch patch = new Patch(0,index); soundbank.createAndRegisterInstrument(patch,synth); } synths[index] = synth; if(gui!=null) gui.synthStrips[index].setSynth(synth); } public Synth getSynth(int index) { return(synths[index]); } public SynthSettings getSynthSetup() { SynthSettings setup = new SynthSettings20070815(); String[] synthClassNames = setup.getSynthClassNames(); Serializable[] synthSettings = setup.getSynthSettings(); for(int n=0;n<synths.length;n++) { try { synthClassNames[n] = synths[n].getClass().getName(); synthSettings[n] = synths[n].getSettings(); } catch(NullPointerException nex ) {} } return(setup); } public void clearSynths() { for(int n=0;n<synths.length;n++) { try { setSynth(n,null); } catch(Exception e) {} } } public void loadSynthSetup(final SynthSettings setup) { if(gui!=null) { gui.initLoadSynthSetupProgress( new Thread() { public void run() { loadSynthSetupThread(setup); } }); } else loadSynthSetupThread(setup); } public void loadSynthSetupThread(SynthSettings setup) { if(voiceServer == null) setVoiceServer(new VoiceServer() { @Override public void configureAudioOutput(JFrame frame) { // TODO Auto-generated method stub } }); clearSynths(); for(int n=0;n<setup.getSynthClassNames().length;n++) { try { String synthName = setup.getSynthClassNames()[n]; if(synthName==null) break; // Handle older fileformats if( synthName.equals("com.petersalomonsen.mystudio.mysynth.synths.SoundFont") || synthName.equals("com.petersalomonsen.mystudio.mysynth.synths.MySampler") ) synthName = com.frinika.synth.synths.MySampler.class.getName(); setSynth(n,(Synth)Class.forName(synthName).getConstructors()[0].newInstance(new Object[]{this})); synths[n].loadSettings(setup.getSynthSettings()[n]); if(gui!=null) gui.notifyLoadSynthSetupProgress((n*100)/setup.getSynthClassNames().length,synths[n].getInstrumentName()); } catch(Exception e) { e.printStackTrace(); } } if(gui!=null) gui.notifyLoadSynthSetupProgress(100,"Completed"); } public void save(File file) { try { SynthSettings setup = getSynthSetup(); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file)); out.writeObject(setup); } catch(Exception e) { e.printStackTrace(); } } public void load(File file) { try { ObjectInputStream in = new ObjectInputStream(new FileInputStream(file)); SynthSettings setup = (SynthSettings)in.readObject(); loadSynthSetup(setup); } catch(Exception e) { e.printStackTrace(); } } /* (non-Javadoc) * @see javax.sound.midi.Synthesizer#getMaxPolyphony() */ public int getMaxPolyphony() { // TODO Auto-generated method stub return 0; } /* (non-Javadoc) * @see javax.sound.midi.Synthesizer#getLatency() */ public long getLatency() { return voiceServer.getLatency(); } /* (non-Javadoc) * @see javax.sound.midi.Synthesizer#getChannels() */ public MidiChannel[] getChannels() { return null; } /* (non-Javadoc) * @see javax.sound.midi.Synthesizer#getVoiceStatus() */ public VoiceStatus[] getVoiceStatus() { // TODO Auto-generated method stub return null; } /* (non-Javadoc) * @see javax.sound.midi.Synthesizer#isSoundbankSupported(javax.sound.midi.Soundbank) */ public boolean isSoundbankSupported(Soundbank soundbank) { // TODO Auto-generated method stub return false; } /* (non-Javadoc) * @see javax.sound.midi.Synthesizer#loadInstrument(javax.sound.midi.Instrument) */ public boolean loadInstrument(Instrument instrument) { // TODO Auto-generated method stub return false; } /* (non-Javadoc) * @see javax.sound.midi.Synthesizer#unloadInstrument(javax.sound.midi.Instrument) */ public void unloadInstrument(Instrument instrument) { // TODO Auto-generated method stub } /* (non-Javadoc) * @see javax.sound.midi.Synthesizer#remapInstrument(javax.sound.midi.Instrument, javax.sound.midi.Instrument) */ public boolean remapInstrument(Instrument from, Instrument to) { // TODO Auto-generated method stub return false; } /* (non-Javadoc) * @see javax.sound.midi.Synthesizer#getDefaultSoundbank() */ public Soundbank getDefaultSoundbank() { // TODO Auto-generated method stub return null; } /* (non-Javadoc) * @see javax.sound.midi.Synthesizer#getAvailableInstruments() */ public Instrument[] getAvailableInstruments() { // No default instruments for SynthRack - hence no instruments returned return new Instrument[0]; } /* (non-Javadoc) * @see javax.sound.midi.Synthesizer#getLoadedInstruments() */ public Instrument[] getLoadedInstruments() { return soundbank.getInstruments(); } /* (non-Javadoc) * @see javax.sound.midi.Synthesizer#loadAllInstruments(javax.sound.midi.Soundbank) */ public boolean loadAllInstruments(Soundbank soundbank) { // TODO Auto-generated method stub return false; } /* (non-Javadoc) * @see javax.sound.midi.Synthesizer#unloadAllInstruments(javax.sound.midi.Soundbank) */ public void unloadAllInstruments(Soundbank soundbank) { // TODO Auto-generated method stub } /* (non-Javadoc) * @see javax.sound.midi.Synthesizer#loadInstruments(javax.sound.midi.Soundbank, javax.sound.midi.Patch[]) */ public boolean loadInstruments(Soundbank soundbank, Patch[] patchList) { // TODO Auto-generated method stub return false; } /* (non-Javadoc) * @see javax.sound.midi.Synthesizer#unloadInstruments(javax.sound.midi.Soundbank, javax.sound.midi.Patch[]) */ public void unloadInstruments(Soundbank soundbank, Patch[] patchList) { // TODO Auto-generated method stub } /* (non-Javadoc) * @see javax.sound.midi.MidiDevice#getDeviceInfo() */ public MidiDevice.Info getDeviceInfo() { return deviceInfo; } /* (non-Javadoc) * @see javax.sound.midi.MidiDevice#open() */ public void open(){ } /* (non-Javadoc) * @see javax.sound.midi.MidiDevice#close() */ public void close() { } /* (non-Javadoc) * @see javax.sound.midi.MidiDevice#isOpen() */ public boolean isOpen() { // TODO Auto-generated method stub return false; } /* (non-Javadoc) * @see javax.sound.midi.MidiDevice#getMicrosecondPosition() */ public long getMicrosecondPosition() { // TODO Auto-generated method stub return 0; } /* (non-Javadoc) * @see javax.sound.midi.MidiDevice#getMaxReceivers() */ public int getMaxReceivers() { return -1; } /* (non-Javadoc) * @see javax.sound.midi.MidiDevice#getMaxTransmitters() */ public int getMaxTransmitters() { return 0; } /* (non-Javadoc) * @see javax.sound.midi.MidiDevice#getReceiver() */ public Receiver getReceiver() throws MidiUnavailableException { return receiver; } /* (non-Javadoc) * @see javax.sound.midi.MidiDevice#getReceivers() */ @SuppressWarnings("unchecked") public List getReceivers() { return receivers; } /* (non-Javadoc) * @see javax.sound.midi.MidiDevice#getTransmitter() */ public Transmitter getTransmitter() throws MidiUnavailableException { return null; } /* (non-Javadoc) * @see javax.sound.midi.MidiDevice#getTransmitters() */ @SuppressWarnings("unchecked") public List getTransmitters() { return transmitters; } /** * @param box */ public void addGlobalInstrumentNameListener(GlobalInstrumentNameListener globalInstrumentNameListener) { globalInstrumentNameListeners.add(globalInstrumentNameListener); } /* (non-Javadoc) * @see com.petersalomonsen.mystudio.mysynth.InstrumentNameListener#instrumentNameChange(java.lang.String) */ public void instrumentNameChange(Synth synth,String instrumentName) { for(int n=0;n<synths.length;n++) if(synth.equals(synths[n])) for(GlobalInstrumentNameListener globalInstrumentNameListener : globalInstrumentNameListeners) globalInstrumentNameListener.instrumentNameChange(n,instrumentName); } public int getNumberOfSynths() { return synths.length; } /** * @return Returns the voiceServer. */ public VoiceServer getVoiceServer() { return voiceServer; } /** * over to provide easier GUI manufactoring */ public String toString() { return getDeviceInfo().toString(); } public Object[] getList() { // TODO Auto-generated method stub return synths; } /** * @return Returns the saveReferencedData. */ public boolean isSaveReferencedData() { return saveReferencedData; } /** * @param saveReferencedData The saveReferencedData to set. */ public void setSaveReferencedData(boolean saveReferencedData) { this.saveReferencedData = saveReferencedData; } public ControllerListProvider getControllerList() { return controllerList; } public void setVoiceServer(VoiceServer voiceServer) { this.voiceServer = voiceServer; if(voiceServer!=null) MasterVoice.getDefaultInstance().initialize(voiceServer); } public Line getLine(javax.sound.sampled.Line.Info info) throws LineUnavailableException { return new TargetDataLine() { ByteBuffer buf = null; float[] floatBuffer = null; FloatBuffer flView = null; public void open(AudioFormat format) throws LineUnavailableException { } public void open(AudioFormat format, int bufferSize) throws LineUnavailableException { // TODO Auto-generated method stub } public int read(byte[] b, int off, int len) { // TODO implement a proper read method if(buf==null || buf.capacity()!=len) { buf = ByteBuffer.allocate(b.length).order(ByteOrder.nativeOrder()); flView = buf.asFloatBuffer(); floatBuffer = new float[flView.capacity()]; } for(int n=0;n<floatBuffer.length;n++) floatBuffer[n] = 0; voiceServer.read(floatBuffer); flView.position(0); flView.put(floatBuffer); buf.position(0); buf.get(b); return len; } public int available() { // TODO Auto-generated method stub return 0; } public void drain() { // TODO Auto-generated method stub } public void flush() { // TODO Auto-generated method stub } public int getBufferSize() { // TODO Auto-generated method stub return 0; } public AudioFormat getFormat() { // TODO Auto-generated method stub return null; } public int getFramePosition() { // TODO Auto-generated method stub return 0; } public float getLevel() { // TODO Auto-generated method stub return 0; } public long getLongFramePosition() { // TODO Auto-generated method stub return 0; } public long getMicrosecondPosition() { // TODO Auto-generated method stub return 0; } public boolean isActive() { // TODO Auto-generated method stub return false; } public boolean isRunning() { // TODO Auto-generated method stub return false; } public void start() { // TODO Auto-generated method stub } public void stop() { // TODO Auto-generated method stub } public void addLineListener(LineListener listener) { // TODO Auto-generated method stub } public void close() { // TODO Auto-generated method stub } public Control getControl(Type control) { // TODO Auto-generated method stub return null; } public Control[] getControls() { // TODO Auto-generated method stub return null; } public javax.sound.sampled.Line.Info getLineInfo() { // TODO Auto-generated method stub return null; } public boolean isControlSupported(Type control) { // TODO Auto-generated method stub return false; } public boolean isOpen() { // TODO Auto-generated method stub return false; } public void open() throws LineUnavailableException { // TODO Auto-generated method stub } public void removeLineListener(LineListener listener) { // TODO Auto-generated method stub } }; } public int getMaxLines(javax.sound.sampled.Line.Info info) { // TODO Auto-generated method stub return 0; } public javax.sound.sampled.Mixer.Info getMixerInfo() { // TODO Auto-generated method stub return null; } public javax.sound.sampled.Line.Info[] getSourceLineInfo() { // TODO Auto-generated method stub return null; } public javax.sound.sampled.Line.Info[] getSourceLineInfo(javax.sound.sampled.Line.Info info) { // TODO Auto-generated method stub return null; } public Line[] getSourceLines() { // TODO Auto-generated method stub return null; } public javax.sound.sampled.Line.Info[] getTargetLineInfo() { // TODO Auto-generated method stub return null; } public javax.sound.sampled.Line.Info[] getTargetLineInfo(javax.sound.sampled.Line.Info info) { // TODO Auto-generated method stub return null; } public Line[] getTargetLines() { // TODO Auto-generated method stub return null; } public boolean isLineSupported(javax.sound.sampled.Line.Info info) { // TODO Auto-generated method stub return false; } public boolean isSynchronizationSupported(Line[] lines, boolean maintainSync) { // TODO Auto-generated method stub return false; } public void synchronize(Line[] lines, boolean maintainSync) { // TODO Auto-generated method stub } public void unsynchronize(Line[] lines) { // TODO Auto-generated method stub } public void addLineListener(LineListener listener) { // TODO Auto-generated method stub } public Control getControl(Type control) { // TODO Auto-generated method stub return null; } public Control[] getControls() { // TODO Auto-generated method stub return null; } public javax.sound.sampled.Line.Info getLineInfo() { // TODO Auto-generated method stub return null; } public boolean isControlSupported(Type control) { // TODO Auto-generated method stub return false; } public void removeLineListener(LineListener listener) { // TODO Auto-generated method stub } public float getTempoBPM() { return tempoBPM; } /** * Frinika specific method to show gui of this synth * */ public void show() { JFrame f = new JFrame(); JScrollPane s = new JScrollPane(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); if(gui==null) gui = new SynthRackGUI(f,this); f.add(s); f.setVisible(true); f.setSize(600,400); s.getViewport().add(gui); } /** * Local test program for SynthRack with GUI - opens a SynthRack midi device and creates the GUI dialog * @param args * @throws java.lang.Exception */ public static void main(String args[]) throws Exception { MidiDevice.Info info = null; for(MidiDevice.Info inf : MidiSystem.getMidiDeviceInfo()) { if(inf.getClass().equals(SynthRack.SynthRackInfo.class)) info = inf; } SynthRack s = ((SynthRack)MidiSystem.getMidiDevice(info)); s.show(); s.gui.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } }