/* * Created on Mar 6, 2006 * * 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.project; import com.frinika.tootX.midi.MidiDeviceRouter; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Vector; import java.util.logging.Level; import java.util.logging.Logger; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import java.util.zip.ZipOutputStream; import javax.sound.midi.InvalidMidiDataException; import javax.sound.midi.MetaMessage; import javax.sound.midi.MidiDevice; import javax.sound.midi.MidiDevice.Info; import javax.sound.midi.MidiEvent; import javax.sound.midi.Track; import javax.sound.midi.MidiMessage; import javax.sound.midi.MidiSystem; import javax.sound.midi.MidiUnavailableException; import javax.sound.midi.Sequence; import javax.sound.midi.ShortMessage; import javax.sound.midi.Synthesizer; import javax.swing.JProgressBar; import javax.swing.event.ChangeEvent; import uk.org.toot.audio.core.AudioControlsChain; import uk.org.toot.audio.core.AudioProcess; import uk.org.toot.audio.mixer.AudioMixer; import uk.org.toot.audio.mixer.AudioMixerStrip; import uk.org.toot.audio.mixer.MixControls; import uk.org.toot.audio.mixer.MixerControls; import uk.org.toot.audio.mixer.MixerControlsIds; // import uk.org.toot.audio.mixer.MixerControlsDescriptor; import uk.org.toot.audio.mixer.MixerControlsFactory; import uk.org.toot.audio.server.AudioClient; import uk.org.toot.audio.server.AudioServer; import com.frinika.SplashDialog; import com.frinika.audio.toot.AudioInjector; import com.frinika.project.FrinikaAudioSystem; import com.frinika.tootX.MidiHub; import com.frinika.global.ConfigListener; import com.frinika.global.FrinikaConfig; import com.frinika.midi.DrumMapper; import com.frinika.midi.MidiDebugDevice; import com.frinika.project.gui.ProjectFrame; import com.frinika.project.scripting.FrinikaScriptingEngine; import com.frinika.project.settings.ProjectSettings; import com.frinika.renderer.FrinikaRenderer; import com.frinika.sequencer.FrinikaSequence; import com.frinika.sequencer.FrinikaSequencer; import com.frinika.sequencer.FrinikaTrackWrapper; import com.frinika.sequencer.MidiResource; import com.frinika.sequencer.converter.MidiSequenceConverter; import com.frinika.sequencer.gui.clipboard.MyClipboard; import com.frinika.sequencer.gui.mixer.SynthWrapper; import com.frinika.sequencer.gui.selection.DragList; import com.frinika.sequencer.gui.selection.LaneSelection; import com.frinika.sequencer.gui.selection.MidiSelection; import com.frinika.sequencer.gui.selection.MultiEventSelection; import com.frinika.sequencer.gui.selection.PartSelection; import com.frinika.sequencer.gui.selection.SelectionFocusable; import com.frinika.sequencer.model.AudioLane; import com.frinika.sequencer.model.EditHistoryContainer; import com.frinika.sequencer.model.EditHistoryRecordableAction; import com.frinika.sequencer.model.EditHistoryRecorder; import com.frinika.sequencer.model.Lane; import com.frinika.sequencer.model.MidiLane; import com.frinika.sequencer.model.MidiPart; import com.frinika.sequencer.model.MultiEvent; import com.frinika.sequencer.model.Part; import com.frinika.sequencer.model.ProjectLane; import com.frinika.sequencer.model.RecordableLane; import com.frinika.sequencer.model.SoloManager; import com.frinika.sequencer.model.SynthLane; import com.frinika.sequencer.model.TextLane; import com.frinika.sequencer.model.ViewableLaneList; import com.frinika.audio.io.BufferedRandomAccessFileManager; import com.frinika.audio.DynamicMixer; import com.frinika.sequencer.TempoChangeListener; import com.frinika.sequencer.model.tempo.TempoList; import com.frinika.sequencer.model.timesignature.TimeSignatureList; import com.frinika.sequencer.model.timesignature.TimeSignatureList.TimeSignatureEvent; import com.frinika.sequencer.model.util.TimeUtils; import com.frinika.synth.SynthRack; import com.frinika.synth.settings.SynthSettings; import com.frinika.tools.ObjectInputStreamFixer; import com.frinika.tools.ProgressBarInputStream; import com.frinika.tootX.midi.ControlResolver; import com.frinika.tootX.midi.MidiConsumer; import com.frinika.tootX.midi.MidiRouterSerialization; import uk.org.toot.audio.core.Taps; import uk.org.toot.misc.Tempo; import static com.frinika.localization.CurrentLocale.getMessage; /** * Use to load Frinika projects. * * This class links together all components of a Frinika project, and provides * all operations and features - including a Frinika sequencer instance. * * Information about Midi Devices - naming and how to reopen them is contained * using the MidiDeviceDescriptors. * * Audio files are stored in a folder named audio which is created in the same * folder as where the project is. Thus a good convention is to have one folder * per project. * * @author Peter Johan Salomonsen */ public class ProjectContainer implements EditHistoryRecorder<Lane>,MidiConsumer, Serializable,DynamicMixer { /** * */ private static final long serialVersionUID = 1L; TootMixerSerializer mixerSerializer; ProjectLane projectLane; transient MidiResource midiResource; transient SoloManager soloManager; transient FrinikaSequencer sequencer; transient FrinikaRenderer renderer; transient FrinikaSequence sequence = null; transient EditHistoryContainer editHistoryContainer; transient MultiEventSelection multiEventSelection; transient DragList dragList; transient PartSelection partSelection; transient LaneSelection laneSelection; transient MidiSelection midiSelection; // Jens transient SelectionFocusable selectionFocus; String title = null; File projectFile = null; File audioDir = null; transient SynthRack audioSynthRack; float tempo = 100; transient MidiEvent tempoEvent; transient MixerControls mixerControls; transient FrinikaAudioServer audioServer; transient AudioMixer mixer; transient AudioInjector outputProcess; transient BufferedRandomAccessFileManager audioFileManager; transient AudioClient audioClient; transient MidiDeviceRouter midiDeviceRouter; // maps mididevices onto receivers transient ControlResolver controlResolver; // controls register with this if the want to use the midi mapping stuff. FrinikaScriptingEngine scriptingEngine; // Jens /** * These two are deprecated - but should not be deleted for backwards * compatibility. Replacement is now the midiDeviceDescriptors */ @Deprecated List<SynthSettings> synthSettings; @Deprecated Vector<String> externalMidiDevices; /** * Information about the midi devices used in this project */ List<MidiDeviceDescriptor> midiDeviceDescriptors = new ArrayList<MidiDeviceDescriptor>(); /** * Used to map midiDevices when saving */ transient HashMap<MidiDevice, Integer> midiDeviceIndex; /** * Used to map midiDevices to their descriptors */ transient HashMap<MidiDevice, MidiDeviceDescriptor> midiDeviceDescriptorMap = new HashMap<MidiDevice, MidiDeviceDescriptor>(); TimeSignatureList timeSignitureList; /** * The resolution of the sequence. If you import a midi track they may have * a different resolution - and when saving to a project - the sequence * created when reloading will have the resolution stored here. */ int ticksPerQuarterNote = FrinikaConfig.TICKS_PER_QUARTER; private TempoList tempoList; private double pianoRollSnapQuantization = 0; private double partViewSnapQuantization = 0; private boolean isPianoRollSnapQuantized = true; private boolean isPartViewSnapQuantized = true; /** * Whether to embed externally referenced data or not (e.g. soundfonts) */ private boolean saveReferencedData; transient private MyClipboard myClipboard; long endTick = 100000; // static boolean dynam = true; private transient int count = 1; private transient int pixelsPerRedraw; private long loopStartPoint; private long loopEndPoint; private int loopCount; private MidiRouterSerialization midiRouterSerialization; private String genres; private Long dataBaseID; public Long getDataBaseID() { return dataBaseID; } public void setDataBaseID(Long dataBaseID) { this.dataBaseID = dataBaseID; } public ProjectContainer(int ticksPerBeat) throws Exception { if (ticksPerBeat > 0) { ticksPerQuarterNote = ticksPerBeat; } defaultInit(); sequencer = new FrinikaSequencer(); sequencer.open(); attachTootNotifications(); renderer = new FrinikaRenderer(this); createSequencerPriorityListener(); // This also creates a track for the Tempo Events. createSequence(); System.out.println(sequence.getFrinikaTrackWrappers().size()); projectLane = new ProjectLane(this); midiResource = new MidiResource(sequencer); tempoList = new TempoList(sequence.getResolution(), this); sequencer.setTempoList(tempoList); setTempoInBPM(100); /* * Tempo message ShortMessage msg = new ShortMessage(); * msg.setMessage(ShortMessage.NOTE_ON, 0, 63, 0); // Default tempo = * 100 BPM int mpq = (int) (60000000.0 / 100.0); MetaMessage tempoMsg = * new MetaMessage(); tempoMsg.setMessage(0x51, new byte[] { (byte) (mpq >> * 16 & 0xff), (byte) (mpq >> 8 & 0xff), (byte) (mpq & 0xff) }, 3); * MidiEvent tempoEvent = new MidiEvent(tempoMsg, 0); * ((FrinikaSequence)sequencer.getSequence()).getFrinikaTrackWrappers().get(0).add(tempoEvent); */ postInit(); } // transient Receiver midiReceiver; // transient MidiFilter midiPreFilter; // filter events before any processing public MidiDeviceRouter getMidiDeviceRouter() { return midiDeviceRouter; } public MidiRouterSerialization getMidiRouterSerialization() { return midiRouterSerialization; } /** * set song genres. * * @param string A colon seperate list of genre names. */ public void setGenres(String string) { genres = string; } public String getGenres() { return genres; } /** * Set the title of the song. * * @param t */ public void setTitle(String t) { title = t; } /** * * @return title of the song or file name if title is null */ public String getTitle() { if (title != null) { return title; } if (projectFile != null) { String str = projectFile.getName(); int ind = str.lastIndexOf(".frinika"); if (ind > 0) { str = str.substring(0, ind); } return str; } return "Unnamed"; } // public MidiDeviceRouter getMidiDeviceRouter() { // throw new UnsupportedOperationException("Not yet implemented"); // } /** * Redirects midi events used for controls * * If not consumed event is sent to midiReciever. * * * @param devInfo * @param arg0 * @param arg1 */ public void processMidiMessageFromDevice(Info devInfo, MidiMessage arg0, long arg1) { if (midiDeviceRouter != null && midiDeviceRouter.consume(devInfo, arg0, arg1)) { return; } try { sequencer.getReceiver().send(arg0, arg1); } catch (MidiUnavailableException ex) { Logger.getLogger(ProjectContainer.class.getName()).log(Level.SEVERE, null, ex); } } // /** // * allows one to capture midi events before they are forwarded to the // * sequencer.getReceiver(); // * // * // * @param filter // */ // public void setMidiPreFilter(MidiFilter filter) { // midiPreFilter=filter; // // } // public Receiver getMidiReceiver() { // // if (midiReceiver != null) { // return midiReceiver; // } // // try { // // final Receiver activeReceiver = sequencer.getReceiver(); // // // main recipient of midi events (after filtering) // midiReceiver = new Receiver() { // // public void send(MidiMessage message, long timeStamp) { // // /** // * //PJL // * frinika is not interested in these inputs ? // */ // if (message.getStatus() >= ShortMessage.MIDI_TIME_CODE) { // return; // } // // // if (!(message instanceof ShortMessage)) { // return; // } // // // PJL ... Allow filter to grab events before anyone else // if (midiPreFilter != null) { // if (midiPreFilter.consume(message, timeStamp)) { // return; // } // } // // if (activeReceiver != null) { // activeReceiver.send(message, timeStamp); // } // } // // public void close() { // } // }; // } catch (MidiUnavailableException ex) { // Logger.getLogger(ProjectContainer.class.getName()).log(Level.SEVERE, null, ex); // } // return midiReceiver; // } // can be used to remove channels and groups public void removeStrip(String name) { mixerControls.removeStripControls(name); } public MixControls addMixerInput(AudioProcess audioProcess, String string) { AudioMixerStrip strip = null; MixControls x = null; try { strip = getMixer().getStrip(string); AudioControlsChain controls; // If it exists we have loaded a mixer with the strip so no need to // create it if (strip == null) { controls = mixerControls.createStripControls( MixerControlsIds.CHANNEL_STRIP, count++, string); strip = getMixer().getStrip(string); } else { controls = mixerControls.getStripControls(string); } strip.setInputProcess(audioProcess); x = (MixControls) controls.find(mixerControls.getMainBusControls().getName()); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } return x; } void createMixer() { if (mixerControls != null) { return; } try { mixerControls = new MixerControls("Mixer"); // defaults to having one main bus // create 2 aux send busses for effects and 1 aux monitor bus // because if you don't have any aux busses you can't insert // effects :( MixerControlsFactory.createBusses(mixerControls, 2, 1); MixerControlsFactory.createBusStrips(mixerControls); audioServer = FrinikaAudioSystem.getAudioServer(); List<String> list = audioServer.getAvailableOutputNames(); mixer = new AudioMixer(mixerControls, audioServer); audioClient = new ProjectAudioClient(); FrinikaAudioSystem.installClient(audioClient); String outDev = FrinikaAudioSystem.configureServerOutput(); if (outDev != null) { outputProcess = new AudioInjector(audioServer.openAudioOutput( outDev, "output")); System.out.println("Using " + outDev + " as audio out device"); mixer.getMainBus().setOutputProcess(outputProcess); } else { message(" No output devices found "); } // This should only be done once. // audioServer.start(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); System.err.println(" \n Sorry but I do not want to go on without an audio output device. \n Bye bye . . . "); System.exit(1); } } /** * Mixer the process output with the main mix * * @param process */ public void injectIntoOutput(AudioProcess process) { if (outputProcess != null) { outputProcess.add(process); } } /** * * set up after objects have been created */ void postInit() { new RecordingManager(this, 10000); } /** * * set up default stuff before reading or construction of objects */ void defaultInit() { pixelsPerRedraw = 1; audioFileManager = new BufferedRandomAccessFileManager(); /** * This means the clipboard is really a singleton for pasting between * projects. */ myClipboard = MyClipboard.the(); // new MyClipboard(this); multiEventSelection = new MultiEventSelection(this); partSelection = new PartSelection(this); laneSelection = new LaneSelection(this); midiSelection = new MidiSelection(this); // Jens scriptingEngine = new FrinikaScriptingEngine(this); // Jens editHistoryContainer = new EditHistoryContainer(); dragList = new DragList(this); soloManager = new SoloManager(this); createMixer(); midiDeviceRouter = new MidiDeviceRouter(); controlResolver = new ControlResolver(); } /** * Create empty project * */ public ProjectContainer() throws Exception { this(0); } private void createSequencerPriorityListener() { sequencer.setPlayerPriority(FrinikaConfig.SEQUENCER_PRIORITY); FrinikaConfig.addConfigListener(new ConfigListener() { public void configurationChanged(ChangeEvent event) { if (event.getSource() == FrinikaConfig._SEQUENCER_PRIORITY) { sequencer.setPlayerPriority(FrinikaConfig.SEQUENCER_PRIORITY); } } }); } /** * This will load an old Frinika project (pre 0.2.0) based on the * projectSettings interface * * @param projectSettings * @throws Exception */ private ProjectContainer(ProjectSettings project) throws Exception { defaultInit(); System.out.println(" LOADING PROJECT "); ByteArrayInputStream sequenceInputStream = new ByteArrayInputStream( project.getSequence()); sequencer = new FrinikaSequencer(); sequencer.open(); attachTootNotifications(); createSequencerPriorityListener(); renderer = new FrinikaRenderer(this); sequence = new FrinikaSequence(MidiSequenceConverter.splitChannelsToMultiTrack(MidiSystem.getSequence(sequenceInputStream))); sequencer.setSequence(sequence); tempoList = new TempoList(sequence.getResolution(), this); tempoList.add(0, tempo); sequencer.setTempoList(tempoList); try { // TODO find all tempo changes setTempoInBPM(MidiSequenceConverter.findFirstTempo(sequence)); } catch (Exception e) { System.out.println(e.toString()); } SynthRack synthRack = new SynthRack(null); SynthWrapper midiDev = new SynthWrapper(this, synthRack); synthRack.loadSynthSetup(project.getSynthSettings()); // create a copy Vector<FrinikaTrackWrapper> origTracks = sequence.getFrinikaTrackWrappers(); Vector<FrinikaTrackWrapper> tracks = new Vector<FrinikaTrackWrapper>( origTracks); projectLane = new ProjectLane(this); // we are going to rebuild this origTracks.removeAllElements(); for (FrinikaTrackWrapper ftw : tracks) { // Use the first MidiEvent in ftw to detect the channel used MidiMessage msg = ftw.get(0).getMessage(); if (msg instanceof ShortMessage) { ftw.setMidiDevice(midiDev); ftw.setMidiChannel(((ShortMessage) msg).getChannel()); System.out.println(((ShortMessage) msg).getChannel() + " channel"); } // PJS: The resolving of channel and device has to be done before // the ftw is attached to a lane (cause the following uperation can // change the first midi event) MidiLane lane = new MidiLane(ftw, this); if (ftw.getMidiChannel() > -1) { lane.setProgram(ftw.getMidiChannel(), 0, 0); } projectLane.addChildLane(lane); MidiPart part = new MidiPart(lane); long startTick = 0; long endTick = Long.MAX_VALUE; part.importFromMidiTrack(startTick, endTick); } int ticks = (int) getSequence().getTickLength(); endTick = Math.max(endTick, ticks); addMidiOutDevice(midiDev); postInit(); rebuildGUI(); } /** * Import a Sequence (e.g. obtained from a MidiFile) into a new project * * @param seq * @throws Exception */ public ProjectContainer(Sequence seq) throws Exception { this(seq, null,false); } public ProjectContainer(Sequence seq, MidiDevice midiDevice) throws Exception { this(seq,midiDevice,false); } /** * * @param seq sequence * @param midiDevice assign tracks to mididevice. * @param adjustPPQ recalculate the ticks if sequence PPQ is not the defualt. * * @throws java.lang.Exception */ public ProjectContainer(Sequence seq, MidiDevice midiDevice,boolean adjustPPQ) throws Exception { defaultInit(); System.out.println(" LOADING MIDI SEQUENCE "); sequencer = new FrinikaSequencer(); sequencer.open(); attachTootNotifications(); createSequencerPriorityListener(); renderer = new FrinikaRenderer(this); if (seq.getDivisionType() == Sequence.PPQ) { ticksPerQuarterNote = seq.getResolution(); System.out.println(" Ticks per quater is " + ticksPerQuarterNote); } else { System.out.println("WARNING: The resolution type of the imported Sequence is not supported by Frinika"); } FrinikaSequence seq1 = new FrinikaSequence(MidiSequenceConverter.splitChannelsToMultiTrack(seq)); if (adjustPPQ) { // TODO } try { // TODO } throw new Exception(" adjust PPQ not implemented yet"); } catch (Exception ex) { Logger.getLogger(ProjectContainer.class.getName()).log(Level.SEVERE, null, ex); } } else { sequence=seq1; } // // int cnt = 0; // for (Track track : seq.getTracks()) { // System.out.println("TRACK " + cnt++ + " ==================================================================="); // for (int i = 0; i < track.size(); i++) { // MidiEvent e = track.get(i); // if (e.getMessage() instanceof ShortMessage) { // ShortMessage shm=(ShortMessage)e.getMessage(); // // // if (e.getCommand() == ShortMessage.PROGRAM_CHANGE)) // (e.getMessage().getStatus() == ShortMessage.CONTROL_CHANGE)) { // String tt = MidiDebugDevice.eventToString(e.getMessage()); // System.out.println(e.getTick() + " : " + tt); // } // } // } sequencer.setSequence(sequence); // create a copy Vector<FrinikaTrackWrapper> origTracks = sequence.getFrinikaTrackWrappers(); Vector<FrinikaTrackWrapper> tracks = new Vector<FrinikaTrackWrapper>( origTracks); projectLane = new ProjectLane(this); // we are going to rebuild this but leave in the first track int n=origTracks.size(); for (int i=n-1;i>0;i--) { origTracks.remove(i); } // int count=0; for (FrinikaTrackWrapper ftw : tracks) { // Use the first MidiEvent in ftw to detect the channel used // Detect by first ShortMessage find (FIX by KH) for (int i = 0; i < ftw.size(); i++) { MidiMessage msg = ftw.get(i).getMessage(); if (msg instanceof ShortMessage) { ftw.setMidiChannel(((ShortMessage) msg).getChannel()); System.out.println(((ShortMessage) msg).getChannel() + " channel"); break; } } int progChange = -1; for (int i = 0; i < ftw.size(); i++) { MidiMessage msg = ftw.get(i).getMessage(); if (msg instanceof ShortMessage) { ShortMessage shm = (ShortMessage) msg; if (shm.getCommand() == ShortMessage.PROGRAM_CHANGE) { if (progChange == -1) { String tt = MidiDebugDevice.eventToString(msg); progChange = ((ShortMessage) msg).getData1(); System.out.println(" PROG CHANGE =" + tt + " " + progChange); } else { System.out.println(" MULTIPLE PROG CHANGES !!!!!!"); } } // break; } } // only create a lane if it has a program change (PJL) if (progChange >= 0) { // PJS: The resolving of channel and device has to be done before // the ftw is attached to a lane (cause the following uperation can // change the first midi event) MidiLane lane = new MidiLane(ftw, this); System.out.println(" Creting MidiLane with track " + count); lane.setProgram(progChange, 0, 0); projectLane.addChildLane(lane); MidiPart part = new MidiPart(lane); long startTick = 0; long endTick1 = Long.MAX_VALUE; part.importFromMidiTrack(startTick, endTick1); // Find name of the track from the sequence file for (int i = 0; i < ftw.size(); i++) { MidiEvent event = ftw.get(i); if (event.getTick() > 0) { break; } MidiMessage msg = event.getMessage(); if (msg instanceof MetaMessage) { MetaMessage meta = (MetaMessage) msg; if (meta.getType() == 3) // Track text { if (meta.getLength() > 0) { try { String txt = new String(meta.getData()); lane.setName(txt); } catch (Throwable t) { t.printStackTrace(); } } } } } // // added these 2 because it did not play!! // lane.attachFTW(); // lane.onLoad(); // // } else if (title == null) { // Find name of the track from the sequence file for (int i = 0; i < ftw.size(); i++) { MidiEvent event = ftw.get(i); if (event.getTick() > 0) { break; } MidiMessage msg = event.getMessage(); if (msg instanceof MetaMessage) { MetaMessage meta = (MetaMessage) msg; if (meta.getType() == 3) // Track text { if (meta.getLength() > 0) { try { String txt = new String(meta.getData()); title = txt; System.out.println("setTing title \"" + txt + "\""); } catch (Throwable t) { t.printStackTrace(); } } } } } } count=count+1; } try { // TODO find all tempos setTempoInBPM(MidiSequenceConverter.findFirstTempo(seq)); } catch (Exception e) { e.printStackTrace(); } rebuildGUI(); if (midiDevice != null) { try { // midiDevice.open(); midiDevice = new SynthWrapper(this, midiDevice); addMidiOutDevice(midiDevice); } catch (Exception e2) { e2.printStackTrace(); midiDevice = null; } } for (FrinikaTrackWrapper ftw : tracks) { if (midiDevice != null) { ftw.setMidiDevice(midiDevice); } } postInit(); } public static ProjectContainer loadProject(File file) throws Exception { ProjectContainer proj; if (file.exists()) { if (file.getName().toLowerCase().contains(".mid")) { proj = new ProjectContainer(MidiSystem.getSequence(file)); } else { proj = loadCompressedProject(file); } } else { proj = new ProjectContainer(); } proj.projectFile = file; return proj; } public static ProjectContainer loadCompressedProject(File file) throws Exception { ProjectContainer project = null; // Check if InputStream is compressed InputStream inputStream = new FileInputStream(file); byte[] magic = new byte[4]; inputStream.read(magic); inputStream.close(); // Check if magic is: 0x50,0x4b,0x03,0x04 (ZIP) // If stream is uncompressed then magic is: 0xac,0xed (objectstream) if (magic[3] == (byte) 0x04 && magic[2] == (byte) 0x03 && magic[1] == (byte) 0x4b && magic[0] == (byte) 0x50) { // Use Zip Decoding FileInputStream fileinputStream = new FileInputStream(file); inputStream = fileinputStream; try { ZipInputStream zipi = new ZipInputStream(inputStream); zipi.getNextEntry(); inputStream = zipi; inputStream = new BufferedInputStream(inputStream); project = loadProject(inputStream); project.compression_level = 1; } finally { fileinputStream.close(); } } if (magic[0] == (byte) 0x4c && magic[1] == (byte) 0x5a && magic[2] == (byte) 0x4d && magic[3] == (byte) 0x61) { // User Lzma Decoding FileInputStream fileinputStream = new FileInputStream(file); inputStream = fileinputStream; try { inputStream.read(magic); InputStream inStream = inputStream; int propertiesSize = 5; byte[] properties = new byte[propertiesSize]; if (inStream.read(properties, 0, propertiesSize) != propertiesSize) { throw new Exception("input .lzma file is too short"); } final SevenZip.Compression.LZMA.Decoder decoder = new SevenZip.Compression.LZMA.Decoder(); if (!decoder.SetDecoderProperties(properties)) { throw new Exception("Incorrect stream properties"); } long outSize = 0; for (int i = 0; i < 8; i++) { int v = inStream.read(); if (v < 0) { throw new Exception("Can't read stream size"); } outSize |= ((long) v) << (8 * i); } File tempfile = File.createTempFile("lzma", "temp"); FileOutputStream fos = new FileOutputStream(tempfile); try { try { if (!decoder.Code(inStream, fos, outSize)) { throw new Exception("Can't decode stream"); } } finally { fos.close(); } inputStream = new FileInputStream(tempfile); try { project = loadProject(inputStream); project.compression_level = 2; } finally { inputStream.close(); } } finally { tempfile.delete(); } } finally { fileinputStream.close(); } } if (project == null) { FileInputStream fileinputStream = new FileInputStream(file); try { project = loadProject(fileinputStream); } finally { fileinputStream.close(); } } return project; } public static ProjectContainer loadProject(InputStream inputStream) throws Exception { if (SplashDialog.isSplashVisible()) { SplashDialog splash = SplashDialog.getInstance(); JProgressBar bar = splash.getProgressBar(); bar.setMaximum(inputStream.available()); inputStream = new ProgressBarInputStream(bar, inputStream); } ObjectInputStream in = new ObjectInputStreamFixer(inputStream); Object obj = in.readObject(); if (SplashDialog.isSplashVisible()) { SplashDialog splash = SplashDialog.getInstance(); JProgressBar bar = splash.getProgressBar(); bar.setValue(bar.getMaximum()); } if (obj instanceof ProjectSettings) { return new ProjectContainer((ProjectSettings) obj); } else { return (ProjectContainer) obj; } } /** * Save project to a file * * @param file */ public transient int compression_level = 0; transient private TimeUtils timeUtils; // Keep a note of all open midi out devices private static Vector<MidiDevice> midiOutList = new Vector<MidiDevice>(); public void saveProject(File file) throws IOException { // throw exception // so ProjectFrame // can show error // message, user // should know if // saving went wrong if (mixerSerializer == null) { System.out.println(" Creating serialization for the mixer "); mixerSerializer = new TootMixerSerializer(this); } // try { // Added compression to frinika projects int usecompression = compression_level; // 0=no compression, 1=zip, // 2=Lzma if (usecompression == 2) // Use Lzma Compression { File tempfile = File.createTempFile("lzma", "temp"); FileInputStream fis = null; try { FileOutputStream fos = new FileOutputStream(tempfile); ObjectOutputStream oos = new ObjectOutputStream(fos); try { oos.writeObject(this); } finally { oos.close(); fos.close(); } fis = new FileInputStream(tempfile); byte[] magic = new byte[4]; magic[0] = (byte) 0x4c; magic[1] = (byte) 0x5a; magic[2] = (byte) 0x4d; magic[3] = (byte) 0x61; FileOutputStream outStream = new FileOutputStream(file); outStream.write(magic); InputStream inStream = fis; boolean eos = false; SevenZip.Compression.LZMA.Encoder encoder = new SevenZip.Compression.LZMA.Encoder(); encoder.SetAlgorithm(2); encoder.SetDictionarySize(1 << 23); encoder.SeNumFastBytes(128); encoder.SetMatchFinder(1); encoder.SetLcLpPb(3, 0, 2); encoder.SetEndMarkerMode(eos); encoder.WriteCoderProperties(outStream); long fileSize = tempfile.length(); for (int i = 0; i < 8; i++) { outStream.write((int) (fileSize >>> (8 * i)) & 0xFF); } encoder.Code(inStream, outStream, -1, -1, null); outStream.close(); } finally { if (fis != null) { fis.close(); } tempfile.delete(); } } else if (usecompression == 1) // Use ZIP Compression { FileOutputStream fos = new FileOutputStream(file); ZipOutputStream zos = new ZipOutputStream(fos); zos.putNextEntry(new ZipEntry(file.getName())); BufferedOutputStream bos = new BufferedOutputStream(zos); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(this); bos.flush(); zos.closeEntry(); zos.finish(); fos.close(); } else // Use no compression { FileOutputStream fos = new FileOutputStream(file); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(this); oos.close(); fos.close(); } projectFile = file; editHistoryContainer.updateSavedPosition(); // } catch (FileNotFoundException e) { // // TODO Auto-generated catch block // e.printStackTrace(); // } catch (IOException e) { // // TODO Auto-generated catch block // e.printStackTrace(); // } /*********************************************************************** * // Saving in 0.1 series format try { ByteArrayOutputStream * sequenceOutputStream = new ByteArrayOutputStream(); * MidiSystem.write(sequence, 1, sequenceOutputStream); * * Project20050227 project = new Project20050227(); * project.setSequence(sequenceOutputStream.toByteArray()); MidiDevice * firstDevice = sequencer.listMidiOutDevices().iterator() .next(); if * (firstDevice instanceof SynthRack) * project.setSynthSettings(((SynthRack) firstDevice) .getSynthSetup()); * ObjectOutputStream out = new ObjectOutputStream( new * FileOutputStream(file)); out.writeObject(project); projectFile = * file; } catch (Exception e) { e.printStackTrace(); } **********************************************************************/ } /** * @return Returns the lanes. */ public List<Lane> getLanes() { return projectLane.getFamilyLanes(); } // /** // * Creates a XAudioLane and adds it to the Lane collection // * // * @return // */ // public XAudioLane createXAudioLane() { // XAudioLane lane = new XAudioLane(this); // add(lane); // return lane; // } /** * Creates a AudioLane and adds it to the Lane collection * * @return */ public AudioLane createAudioLane() { AudioLane lane = new AudioLane(this); add(lane); return lane; } /** * Creates a TextLane and adds it to the Lane collection */ public TextLane createTextLane() { // Jens TextLane lane = new TextLane(this); add(lane); return lane; } /** * @return Returns the sequencer. */ public FrinikaSequencer getSequencer() { return sequencer; } /** * Creates a sequence based on the resolution defined in ticksPerQuarterNote * */ public void createSequence() { if (sequence == null) { try { if (ticksPerQuarterNote == 0) { ticksPerQuarterNote = FrinikaConfig.TICKS_PER_QUARTER; } sequence = new FrinikaSequence(Sequence.PPQ, ticksPerQuarterNote, 1); sequencer.setSequence(sequence); } catch (InvalidMidiDataException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } /** * @return Returns the sequence. */ public FrinikaSequence getSequence() { return sequence; } /** * @return Returns the renderer. */ public FrinikaRenderer getRenderer() { return renderer; } /** * If this projectContainer was initialized by a project file -the return * the file * * @return */ public File getProjectFile() { return projectFile; } /** * @return Returns the multiEventSelection. */ public MultiEventSelection getMultiEventSelection() { return multiEventSelection; } /** * * @return the Part selection container for this project. */ public PartSelection getPartSelection() { return partSelection; } /** * * @return the Lane selection container for this project. */ public LaneSelection getLaneSelection() { return laneSelection; } /** * * @return the Midi selection container for this project. */ public MidiSelection getMidiSelection() { // Jens return midiSelection; } public FrinikaScriptingEngine getScriptingEngine() { // Jens return scriptingEngine; } /** * * @return the Edit history container for this project. */ public EditHistoryContainer getEditHistoryContainer() { return editHistoryContainer; } /** * Close the project * */ public void close() { if (renderer != null) { renderer.close(); } sequencer.close(); audioFileManager.stop(); } /** * adds a lane to the project and updates the history */ public void add(Lane lane) { projectLane.addChildLane(lane); // System. out.println(" about to lane.onload");f lane.onLoad(); editHistoryContainer.push(this, EditHistoryRecordableAction.EDIT_HISTORY_TYPE_ADD, lane); } public void add(int index, Lane lane) { projectLane.addChildLane(index, lane); lane.onLoad(); editHistoryContainer.push(this, EditHistoryRecordableAction.EDIT_HISTORY_TYPE_ADD, lane); } public void remove(Lane lane) { projectLane.removeChildLane(lane); getEditHistoryContainer().push(this, EditHistoryRecordableAction.EDIT_HISTORY_TYPE_REMOVE, lane); } /** * * Lanes can contain other lanes. A project is contained within a project * lane. * * @return top level Lane that containes all others. */ public ProjectLane getProjectLane() { return projectLane; } public SynthRack getSynthRack() { return audioSynthRack; } public MidiResource getMidiResource() { return midiResource; } public FrinikaTrackWrapper getTempoTrack() { return sequence.getFrinikaTrackWrappers().get(0); } /** * * Set the tempo of the first event in the tempo list * * @param tempo */ public void setTempoInBPM(float tempo) { getTempoList().add(0, tempo); } public void buildMidiIndex() { int mdIndex = 0; midiDeviceIndex = new HashMap<MidiDevice, Integer>(); for (MidiDevice midiDev : sequencer.listMidiOutDevices()) { if (midiDev instanceof SynthRack) { ((SynthRack) midiDev).setSaveReferencedData(saveReferencedData); } if (midiDev instanceof SynthWrapper) { ((SynthWrapper) midiDev).setSaveReferencedData(saveReferencedData); } System.out.println(midiDeviceDescriptorMap.get(midiDev).getProjectName() + "(" + midiDeviceDescriptorMap.get(midiDev).getMidiDeviceName() + ") has index " + mdIndex); midiDeviceIndex.put(midiDev, mdIndex++); } } private void writeObject(ObjectOutputStream out) throws IOException { // Store the used midi devices // int mdIndex = 0; // midiDeviceIndex = new HashMap<MidiDevice, Integer>(); /** * Use 0.3.0 format instead */ synthSettings = null; externalMidiDevices = null; buildMidiIndex(); // for (MidiDevice midiDev : sequencer.listMidiOutDevices()) { // if (midiDev instanceof SynthRack) { // ((SynthRack) midiDev).setSaveReferencedData(saveReferencedData); // } // if (midiDev instanceof SynthWrapper) { // ((SynthWrapper) midiDev).setSaveReferencedData(saveReferencedData); // } // System.out.println(midiDeviceDescriptorMap.get(midiDev).getProjectName() + "(" + midiDeviceDescriptorMap.get(midiDev).getMidiDeviceName() + ") has index " + mdIndex); // midiDeviceIndex.put(midiDev, mdIndex++); // } /** * Deprecated from 0.3.0 * * synthSettings = new ArrayList<SynthSettings>(); externalMidiDevices = * new Vector<String>(); // Since SynthRack and external midi are * separate arrays - they have to be saved groupwise so that // the * midiDeviceIndexes are correct on reload (This is a fix of bugid * 1506823) * * for (MidiDevice midiDev : sequencer.listMidiOutDevices()) { if * (midiDev instanceof SynthRack) { ((SynthRack) * midiDev).setSaveReferencedData(saveReferencedData); * synthSettings.add(((SynthRack) midiDev).getSynthSetup()); * midiDeviceIndex.put(midiDev, mdIndex++); } } * * for (MidiDevice midiDev : sequencer.listMidiOutDevices()) { if * (!(midiDev instanceof SynthRack)) { * externalMidiDevices.add(midiDev.getDeviceInfo().toString()); * midiDeviceIndex.put(midiDev, mdIndex++); } } */ loopStartPoint = sequencer.getLoopStartPoint(); loopEndPoint = sequencer.getLoopEndPoint(); loopCount = sequencer.getLoopCount(); midiRouterSerialization = new MidiRouterSerialization(); midiRouterSerialization.buildSerialization(controlResolver, midiDeviceRouter); out.defaultWriteObject(); } /** * * Note we don't rebuild the midirouter here because we want to wait untill all the controllers have been created by the GUI. * * * * @param in * @throws java.lang.ClassNotFoundException * @throws java.io.IOException */ @SuppressWarnings("unchecked") private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException { defaultInit(); // --------------- Create a sequencer that we can load data into sequencer = new FrinikaSequencer(); try { sequencer.open(); attachTootNotifications(); midiResource = new MidiResource(sequencer); } catch (Exception e) { e.printStackTrace(); } renderer = new FrinikaRenderer(this); // ------------ Now read the object. This will also generate // FrinikaTrackWrappers in the lanes in.defaultReadObject(); createSequence(); /** * This is for backwards compatibility. The serialized form of * a Frinika SynthRack is for older projects stored directly * in the ProjectContainer in the synthSettings property. * * As you can see below, when the "old" project is loaded * the Frinka Synthrack is put into a MidiDeviceDescriptor, * so that when saved next time it follows the new format. * * In the future we might remove this code - and instruct users * to convert old projects using an older version of Frinika */ if (synthSettings != null || externalMidiDevices != null) { // Make sure that the project doesn't contain both if (midiDeviceDescriptors != null) { this.midiDeviceDescriptors.clear(); } else // Or convert this into a new project version { this.midiDeviceDescriptors = new ArrayList<MidiDeviceDescriptor>(); } this.midiDeviceDescriptorMap = new HashMap<MidiDevice, MidiDeviceDescriptor>(); // ------------ Initialize soft synths for (SynthSettings synthSetup : synthSettings) { SynthRack synthRack = new SynthRack(null); try { MidiDevice midiDevice = new SynthWrapper(this, synthRack); synthRack.loadSynthSetup(synthSetup); addMidiOutDevice(midiDevice); /** * Fix the lane program change event for older projects */ if (!synthSetup.hasProgramChangeEvent()) { FrinikaSynthRackDescriptor.fixLaneProgramChange(this, midiDevice); } } catch (MidiUnavailableException e) { // TODO Auto-generated catch block e.printStackTrace(); } } // ---------- and the other ones if (externalMidiDevices != null) { // allow loading of previous // versions for (String name : externalMidiDevices) { MidiDevice dev = MidiHub.getMidiOutDeviceByName(name); if (dev == null) { System.out.println(" Failed to find MidiDevice " + name); } else { SynthWrapper externMidi = new SynthWrapper(this, dev); try { externMidi.open(); } catch (MidiUnavailableException e) { System.out.println(" Failed to open MidiDevice " + name); e.printStackTrace(); } try { addMidiOutDevice(externMidi); } catch (MidiUnavailableException e) { System.out.println(" Failed to add MidiDevice " + name); e.printStackTrace(); } } } } } else { installMidiDevices(); } /** * Regenerate MIDI events - This has to be done after the * deserialization cause one cannot be sure of the ordering of parts, * lanes and multievents */ validate(); /// DEBUG REMOVE IF IT ANNOYS projectLane.onLoad(); // PJL tempo stuff for old files (before tempo list) if (tempoList == null) { tempoList = new TempoList(sequence.getResolution(), this); tempoList.add(0, tempo); // use the tempo from the old project to // set first event } tempoList.reco(); sequencer.setTempoList(tempoList); sequencer.setLoopStartPoint(loopStartPoint); sequencer.setLoopEndPoint(loopEndPoint); sequencer.setLoopCount(loopCount); postInit(); rebuildGUI(); } /** * Go through the mididevice descriptor map and install mididevices */ public void installMidiDevices() { this.midiDeviceDescriptorMap = new HashMap<MidiDevice, MidiDeviceDescriptor>(); for (MidiDeviceDescriptor midiDeviceDescriptor : midiDeviceDescriptors) { System.out.println("Installing Midi device: " + midiDeviceDescriptor.getMidiDeviceName() + " as " + midiDeviceDescriptor.getProjectName()); midiDeviceDescriptor.install(this); } } public void setSaveReferencedData(boolean saveReferencedData) { this.saveReferencedData = saveReferencedData; } public Integer getMidiDeviceIndex(MidiDevice midiDevice) { return midiDeviceIndex.get(midiDevice); } public SelectionFocusable getSelectionFocus() { return selectionFocus; } public void setSelectionFocus(SelectionFocusable focus) { // System.out.println(" Set project focus " + focus); selectionFocus = focus; } public MyClipboard clipBoard() { // TODO Auto-generated method stub return myClipboard; } public void rebuildGUI() { ViewableLaneList list = new ViewableLaneList(this); list.rebuild(); /* * for (Lane lane : list) { * * if (lane.getParts() != null) { for (Part p : lane.getParts()) { * System.out.println(p); } } } */ // projectLane.setHidden(true); // projectLane.getChildren().get(0).setHidden(true); // TODO should this part of the loadProject ? // Myabe not. Resources vary from machine to machine. // Need to discuss this ? midiResource = new MidiResource(sequencer); } /** * * @return piano roll quantization in ticks */ public double getPianoRollSnapQuantization() { return pianoRollSnapQuantization; } public double getPartViewSnapQuantization() { return partViewSnapQuantization; } public void setPianoRollSnapQuantization(double val) { pianoRollSnapQuantization = val; } public void setPartViewSnapQuantization(double val) { partViewSnapQuantization = val; } public boolean isPianoRollSnapQuantized() { return isPianoRollSnapQuantized; } public boolean isPartViewSnapQuantized() { return isPartViewSnapQuantized; } public void setPianoRollSnapQuantized(boolean val) { isPianoRollSnapQuantized = val; } public void setPartViewSnapQuantized(boolean val) { isPartViewSnapQuantized = val; } public long eventQuantize(long tick) { if (isPianoRollSnapQuantized) { tick = (long) (Math.rint(tick / pianoRollSnapQuantization) * pianoRollSnapQuantization); } return tick; } public long partQuantize(long tick) { double tt = tick; if (isPartViewSnapQuantized) { double quant = partViewSnapQuantization; if (quant > 0.0) { tt = (long) (tt / quant) * quant; } else { double beat = tt / getTicksPerBeat(); TimeSignatureEvent ev = getTimeSignatureList().getEventAtBeat((int) beat); int nBar = (int) ((beat - ev.beat + ev.beatsPerBar / 2.0) / ev.beatsPerBar); tt = (ev.beat + nBar * ev.beatsPerBar) * getTicksPerBeat(); // System.out.println(" STT -ve quant " + tt); } } return (long) tt; } public long getEndTick() { return endTick; } public void setEndTick(long tick) { if (tick == endTick) { return; } endTick = tick; sequencer.notifySongPositionListeners(); } /** * * DEBUGING --- NOT FOR PUBLIC USE * */ public void validate() { validate(projectLane); } /** * * DEBUGING --- NOT FOR PUBLIC USE * * @param parent */ public void validate(Lane parent) { for (Part part : parent.getParts()) { assert (part.getLane() == parent); if (part instanceof MidiPart) { MidiPart midiPart = (MidiPart) part; if (part.getStartTick() > part.getEndTick()) { System.out.println("Correcting invalid data " + part.getStartTick() + "-->" + part.getEndTick()); part.setStartTick(part.getEndTick()); } for (MultiEvent ev : midiPart.getMultiEvents()) { assert (ev.getPart() == part); } } } for (Lane lane : parent.getChildren()) { validate(lane); } } public void resetEndTick() { setEndTick(projectLane); } private void setEndTick(Lane parent) { for (Part part : parent.getParts()) { assert (part.getLane() == parent); // if (part instanceof MidiPart) { // MidiPart midiPart = (MidiPart) part; endTick = Math.max(endTick, part.getEndTick()); // } } for (Lane lane : parent.getChildren()) { setEndTick(lane); } } public Vector<Lane> recordableLaneList() { Vector<Lane> list = new Vector<Lane>(); addRecordableLanes(list, projectLane); return list; } private void addRecordableLanes(Vector<Lane> list, Lane parent) { for (Lane lane : parent.getChildren()) { if (lane instanceof RecordableLane) { list.add(lane); } addRecordableLanes(list, lane); } } public File getFile() { return projectFile; } /** * * @return */ public File getAudioDirectory() { if (audioDir != null) { return audioDir; } else { newAudioDirectory(); } return audioDir; } private void newAudioDirectory() { File file = null; int count = 0; String base = "New"; if (projectFile != null) { base = projectFile.getName(); int index = base.indexOf('.'); if (index > 0) { base = base.substring(0, index); } } file = new File(FrinikaConfig.AUDIO_DIRECTORY, base); while (file.exists()) { file = new File(FrinikaConfig.AUDIO_DIRECTORY, base + "_" + count++); } file.mkdirs(); audioDir = file; // // File audioDir = new File(.getParentFile(), "audio"); // if (!audioDir.exists()) // audioDir.mkdir(); // return audioDir.toString(); } /** * Add a midi device to the project. The midi device will be added to the * sequencer, and a descriptor of how to reopen the mididevice from a saved * instance will be added. Extra information such as custom name and * soundbank info is also part of the descriptor. * * @param midiDev * @throws MidiUnavailableException */ public MidiDeviceDescriptor addMidiOutDevice(MidiDevice midiDev) throws MidiUnavailableException { // First create the MidiDeviceDescriptor MidiDeviceDescriptor descriptor = null; /** * Check if this is an external Javasound plugin or mididevice */ if (midiDev instanceof SynthWrapper) { SynthWrapper synthWrapper = (SynthWrapper) midiDev; if (synthWrapper.getRealDevice().getClass().isAnnotationPresent( MidiDeviceDescriptorClass.class)) { try { descriptor = (MidiDeviceDescriptor) synthWrapper.getRealDevice().getClass().getAnnotation( MidiDeviceDescriptorClass.class).value().newInstance(); } catch (InstantiationException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } } // Check for SynthRack first since this also implements the // Synthesizer if else if (synthWrapper.getRealDevice() instanceof SynthRack) { descriptor = new FrinikaSynthRackDescriptor(synthWrapper); } else if (synthWrapper.getRealDevice() instanceof DrumMapper) { descriptor = new DrumMapperDescriptor(synthWrapper, this); } else if (synthWrapper.getRealDevice() instanceof Synthesizer) { descriptor = new SynthesizerDescriptor((Synthesizer) midiDev); } else { descriptor = new MidiDeviceDescriptor(midiDev); } if (synthWrapper.soundBankFile != null) { if (descriptor instanceof SoundBankNameHolder) { ((SoundBankNameHolder) descriptor).setSoundBankFileName(synthWrapper.soundBankFile); } } } // Set a default name descriptor.setProjectName(midiDev.getDeviceInfo().getName()); // Insert the new descriptor this.midiDeviceDescriptors.add(descriptor); // PJL // OK for rasmusDSP but not a very neat way to find out SynthLane lane = createSynthLane(descriptor); // The correct way is to // check for Mixer // interface if (midiDev instanceof SynthWrapper && ((SynthWrapper) midiDev).getAudioProcess() != null) { lane.attachAudioProcessToMixer(); } // ************* // Finally load the new descriptor loadMidiOutDevice(descriptor); return descriptor; } /** * Package private method used by descriptors to install MidiOutdevices. * Will create the neccesary mappings, and add the device to the sequencer * * You should not use this to add a new Midi device - use the public method * addMidiOutDevice for that * * @param descriptor * @throws MidiUnavailableException */ void loadMidiOutDevice(MidiDeviceDescriptor descriptor) throws MidiUnavailableException { this.midiDeviceDescriptorMap.put(descriptor.getMidiDevice(), descriptor); sequencer.addMidiOutDevice(descriptor.getMidiDevice()); midiOutList.add(descriptor.getMidiDevice()); // PJL keep note of open // devices } /** * */ static public void closeAllMidiOutDevices() { for (MidiDevice dev : midiOutList) { System.out.println(" Closing " + dev); dev.close(); } } /** * Get the midi device descriptor for the given midi device * * @param midiDevice * @return */ public MidiDeviceDescriptor getMidiDeviceDescriptor(MidiDevice midiDevice) { return midiDeviceDescriptorMap.get(midiDevice); } /** * Remove a midiOutDevice from the project * * @param midiDevice */ public void removeMidiOutDevice(MidiDevice midiDevice) { midiDeviceDescriptors.remove(midiDeviceDescriptorMap.get(midiDevice)); midiDeviceDescriptorMap.remove(midiDevice); sequencer.removeMidiOutDevice(midiDevice); } public List<MidiDeviceDescriptor> getMidiDeviceDescriptors() { return midiDeviceDescriptors; } public MixerControls getMixerControls() { return mixerControls; } public FrinikaAudioServer getAudioServer() { return audioServer; } public AudioMixer getMixer() { return mixer; } public AudioInjector getOutputProcess() { return outputProcess; } public void message(String string) { ProjectFrame.staticMessage(this, string); } public SynthLane createSynthLane(MidiDeviceDescriptor desc) { SynthLane lane = new SynthLane(this, desc); add(lane); return lane; } public BufferedRandomAccessFileManager getAudioFileManager() { // TODO Auto-generated method stub return audioFileManager; } // PJL SequencerListener not used ???? I delete to avoid confusion class ProjectAudioClient implements AudioClient { //, SequencerListener { double tick = 0; long framePtr = 0; double sampleRate; double ticksPerBuffer; ProjectAudioClient() { sampleRate = FrinikaConfig.sampleRate; } public void work(int bufsize) { // if (sequencer != null && sequence != null) { // if (sequencer.isRunning()) { // double ticksPerBuffer = FrinikaAudioSystem // .getAudioBufferSize() // / samplesPerTick(); // sequencer.setRealtime(false); // while (sequencer.getTickPosition() < tick){ // sequencer.nonRealtimeNextTick(); // } // tick += ticksPerBuffer; // } // } mixer.work(bufsize); } public void setEnabled(boolean b) { mixer.setEnabled(b); } // public void beforeStart() { // tick = sequencer.getTickPosition(); // framePtr = (long) (tick * samplesPerTick()); // } public void start() { } public void stop() { }// double samplesPerTick() { // // // TODO check if OK for changing tempos ticksPerBeat beatPerMin // double ticksPerSecond = (sequence.getResolution() * sequencer // .getTempoInBPM()) / 60.0; // // return (sampleRate / ticksPerSecond); // } } public AudioClient getAudioClient() { return audioClient; } /** * Creates a MidiLane and adds it to the Lane collection * * @return */ public MidiLane createMidiLane() { sequence.createTrack(); FrinikaTrackWrapper ftw = sequence.getFrinikaTrackWrappers().lastElement(); ftw.setMidiChannel(0); MidiLane lane = new MidiLane(ftw, this); // Set channel 1 (0) as default MIDI channel // MidiLane lane = new MidiLane(ftw,this); add(lane); return lane; } public void createMidiLanesFromSequence(Sequence seq, MidiDevice midiDevice) { // Vector<MidiLane> lanesToLoad = new Vector<MidiLane>(); // FrinikaSequence fSeq = sequence; if (seq.getDivisionType() == Sequence.PPQ) { int ticksPerQuarterNote1 = seq.getResolution(); System.out.println(" Project PPQ = " + ticksPerQuarterNote); System.out.println(" Midi PPQ = " + ticksPerQuarterNote1); } else { System.out.println("WARNING: The resolution type of the imported Sequence is not supported by Frinika"); } // Vector<FrinikaTrackWrapper> origTracks = new // Vector<FrinikaTrackWrapper>( // sequence.getFrinikaTrackWrappers()); // // Vector<FrinikaTrackWrapper> midiTracks = sequence // .addSequence( Sequence splitSeq = MidiSequenceConverter.splitChannelsToMultiTrack(seq); int nTrack = splitSeq.getTracks().length; System.out.println(" Adding " + (nTrack) + " tracks "); // sequencer.setSequence(sequence); // create a copy // we are going to rebuild this // origTracks.removeAllElements(); getEditHistoryContainer().mark( getMessage("sequencer.project.add_midi_lane")); for (int iTrack = 0; iTrack < nTrack; iTrack++) { int chan = 0; Track track = splitSeq.getTracks()[iTrack]; // if (origTracks.contains(ftw)) continue; // Use the first MidiEvent in ftw to detect the channel used // Detect by first ShortMessage find (FIX by KH) for (int i = 0; i < track.size(); i++) { MidiMessage msg = track.get(i).getMessage(); if (msg instanceof ShortMessage) { chan = ((ShortMessage) msg).getChannel(); break; } } // PJS: The resolving of channel and device has to be done before // the ftw is attached to a lane (cause the following uperation can // change the first midi event) // FrinikaTrackWrapper ftw=sequence.createFrinikaTrack(); MidiLane lane = createMidiLane(); // new MidiLane(ftw, this); // lanesToLoad.add(lane); lane.setMidiChannel(chan); MidiPart part = new MidiPart(lane); long startTick = 0; long endTick = Long.MAX_VALUE; part.importFromMidiTrack(track, startTick, endTick); // Find name of the track from the sequence file for (int i = 0; i < track.size(); i++) { MidiEvent event = track.get(i); if (event.getTick() > 0) { break; } MidiMessage msg = event.getMessage(); if (msg instanceof MetaMessage) { MetaMessage meta = (MetaMessage) msg; if (meta.getType() == 3) // Track text { if (meta.getLength() > 0) { try { String txt = new String(meta.getData()); lane.setName(txt); } catch (Throwable t) { t.printStackTrace(); } } } } } part.commitEventsAdd(); // seq.(ftw); // add(lane); // part.onLoad(); } rebuildGUI(); if (midiDevice != null) { try { // midiDevice.open(); midiDevice = new SynthWrapper(this, midiDevice); addMidiOutDevice(midiDevice); } catch (Exception e2) { e2.printStackTrace(); midiDevice = null; } } // for (FrinikaTrackWrapper ftw : midiTracks) { // // if (origTracks.contains(ftw)) continue; // if (midiDevice != null) // ftw.setMidiDevice(midiDevice); // } // for (MidiLane lane : lanesToLoad) { // add(lane); // } getEditHistoryContainer().notifyEditHistoryListeners(); } public DragList getDragList() { return dragList; } public int getPixelsPerRedraw() { return pixelsPerRedraw; } public void setPixelsPerRedraw(int i) { pixelsPerRedraw = i; } // TODO tempo stuff /** * translate microsecond time to ticks */ public double tickAtMicros(double micros) { return tempoList.getTickAtTime(micros / 1000000.0); // TODO Broken (move part) // return micros * (sequence.getResolution() * sequencer.getTempoInBPM()) // / (60.0 * 1000000.0); } /** * translate ticks to microseconds */ public double microsAtTick(double tick) { return 1000000.0 * tempoList.getTimeAtTick(tick); // TODO Broken (move part) // return (60.0 * 1000000.0 * tick) // / (sequence.getResolution() * sequencer.getTempoInBPM()); } /** * Getter for the tempoList * * @return tempoList */ public TempoList getTempoList() { if (tempoList == null) { // I don't think this ever happens System.out.println(" Creating a new tempo list "); tempoList = new TempoList(sequence.getResolution(), this); tempoList.add(0, 100.0); sequencer.setTempoList(tempoList); } return tempoList; } public TimeSignatureList getTimeSignatureList() { if (timeSignitureList == null) { // if we have a legacy project timesig will be null. timeSignitureList = new TimeSignatureList(); timeSignitureList.add(0, 4); } return timeSignitureList; } public int getTicksPerBeat() { return sequence.getResolution(); } /** * * @return a TimeUtils for this project */ public TimeUtils getTimeUtils() { if (timeUtils == null) { timeUtils = new TimeUtils(this); } return timeUtils; } public double tickToSample(long tick) { double tt = tempoList.getTimeAtTick(tick); return tt * audioServer.getSampleRate(); } public SoloManager getSoloManager() { return soloManager; } public ControlResolver getControlResolver() { return controlResolver; } private void attachTootNotifications() { Taps.setAudioServer(audioServer); sequencer.addTempoChangeListener(new TempoChangeListener() { public void notifyTempoChange(float bpm) { Tempo.setTempo(bpm); } }); } }