/* * * Copyright (c) 2006-2007 Paul John Leonard * * 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.sequencer.model; import com.frinika.audio.toot.AudioPeakMonitor; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.ObjectInputStream; import javax.sound.sampled.AudioFormat; import javax.swing.Icon; import rasmus.midi.provider.RasmusSynthesizer; import uk.org.toot.audio.core.AudioBuffer; import uk.org.toot.audio.core.AudioProcess; import uk.org.toot.audio.core.ChannelFormat; import uk.org.toot.audio.mixer.MixControls; import uk.org.toot.audio.server.AudioServer; import uk.org.toot.audio.server.IOAudioProcess; import com.frinika.project.FrinikaAudioSystem; import com.frinika.global.FrinikaConfig; import com.frinika.project.ProjectContainer; import com.frinika.sequencer.FrinikaSequencer; import com.frinika.sequencer.SequencerListener; import com.frinika.audio.io.AudioWriter; import static com.frinika.localization.CurrentLocale.getMessage; public class AudioLane extends Lane implements RecordableLane, SequencerListener { transient AudioProcess audioInProcess = null; // audio input protected transient AudioProcess audioInsert = null; static Icon icon = new javax.swing.ImageIcon(RasmusSynthesizer.class .getResource("/icons/audiolane.png")); // THIS IS TEMPORARY - have to be able to add mixerslots dynamically // we still need a unique id. But maybe move this up to Lane ? public static int stripNo = 1; /** * Audio Process to be connected to the project mixer */ transient AudioProcess audioProcess; transient AudioPeakMonitor peakMonitor; transient boolean armed = false; // armed for recording transient boolean isRecording = false; // is sequencer running && armed // // recording transient boolean hasRecorded = false; // true if any data has been saved transient AudioWriter writer = null; // direct to disk writer transient private long recordStartTimeInMicros; transient private FrinikaSequencer sequencer; transient private MixControls mixerControls = null; transient int stripInt=-1; private static final long serialVersionUID = 1L; protected transient File clipFile; static int nameCount = 0; public AudioLane(ProjectContainer project) { super("Audio " + nameCount++, project); attachAudioProcessToMixer(); } public void dispose() { project.getSequencer().removeSequencerListener(this); writer.discard(); } public void removeFromModel() { project.removeStrip(stripInt+""); super.removeFromModel(); } private void attachAudioProcessToMixer() { peakMonitor = new AudioPeakMonitor(); audioProcess = new AudioProcess() { public void close() { } public void open() { } public int processAudio(AudioBuffer buffer) { // Process audio of all parts in this lane // do we need to zero the buffer here ? if (armed) { audioInProcess.processAudio(buffer); peakMonitor.processAudio(buffer); if (audioInsert != null ) audioInsert.processAudio(buffer); if (isRecording) { // TODO handle DISCONNECT writer.processAudio(buffer); hasRecorded = true; } if (FrinikaConfig.getDirectMonitoring()) { buffer.makeSilence(); } } else { if (project.getSequencer().isRunning()) { buffer.setChannelFormat(ChannelFormat.STEREO); buffer.makeSilence(); for (Part part : getParts()) { if (((AudioPart) part).getAudioProcess() != null) ((AudioPart) part).getAudioProcess() .processAudio(buffer); } peakMonitor.processAudio(buffer); } else { buffer.makeSilence(); } } buffer.setMetaInfo(channelLabel); return AUDIO_OK; } }; try { mixerControls = project.addMixerInput(audioProcess, (stripInt=stripNo++) + ""); // project.getMixer().getStrip((stripNo++) + "").setInputProcess( // audioProcess); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } sequencer = project.getSequencer(); sequencer.addSequencerListener(this); } public void restoreFromClone(EditHistoryRecordable object) { System.out.println("AudioLane restroeFromClone"); } public Selectable deepCopy(Selectable parent) { return null; } public void deepMove(long tick) { // TODO Auto-generated method stub } public boolean isRecording() { return armed; } public boolean isMute() { return mixerControls.isMute(); } public boolean isSolo() { return mixerControls.isSolo(); } public void setRecording(boolean b) { if (b && audioInProcess == null) { armed = false; project.message(getMessage("recording.please_select_audio_input")); return; } armed = b; } public void setMute(boolean b) { mixerControls.getMuteControl().setValue(b); } public void setSolo(boolean b) { mixerControls.getSoloControl().setValue(b); } private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException { in.defaultReadObject(); attachAudioProcessToMixer(); } public AudioProcess getAudioInDevice() { return audioInProcess; } public void setAudioInDevice(AudioProcess handle) { audioInProcess = handle; if (writer != null) writer.close(); writer = newAudioWriter(); } public double getMonitorValue() { return peakMonitor.getPeak(); } /** * * Creates a new audio file handle to save a clip. * */ public AudioWriter newAudioWriter() { clipFile = newFilename(); AudioFormat format = new AudioFormat( FrinikaConfig.sampleRate, 16, ((IOAudioProcess) audioInProcess).getChannelFormat().getCount(), true, false); try { return new AudioWriter(clipFile, format); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } public File newFilename() { ProjectContainer proj = getProject(); File audioDir = proj.getAudioDirectory(); String audioFileName = getName() + ".wav"; File clipFile = new File(audioDir, audioFileName); int cnt = 1; while (clipFile.exists()) { audioFileName = getName() + "_" + (cnt++) + ".wav"; clipFile = new File(audioDir, audioFileName); } return clipFile; } public void beforeStart() { } public void start() { isRecording = project.getSequencer().isRecording(); if (isRecording) { recordStartTimeInMicros = sequencer.getMicrosecondPosition(); } } public void stop() { isRecording = false; if (hasRecorded) { project.getEditHistoryContainer().mark( getMessage("sequencer.audiolane.record")); writer.close(); hasRecorded = false; AudioServer server = project.getAudioServer(); int latencyInframes = project.getAudioServer().getTotalLatencyFrames(); System.out.println(" latency in frames is " + latencyInframes); double latencyInMicros = latencyInframes * 1000000.0 / server.getSampleRate(); // shift record time back in time because we play along with a delay // output. recordStartTimeInMicros -= latencyInMicros; // TODO Latency compensation AudioPart part; try { part = new AudioPart(this, writer.getFile(), recordStartTimeInMicros); part.onLoad(); writer = newAudioWriter(); } catch (FileNotFoundException e) { e.printStackTrace(); } project.getEditHistoryContainer().notifyEditHistoryListeners(); } } public MixControls getMixerControls() { return mixerControls; } /** * */ @Override public Part createPart() { try { throw new Exception(" Attempt to create an AudiPart"); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } // TODO Auto-generated method stub return null; } @Override public Icon getIcon() { return icon; } }