/*
* Created on Dec 14, 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.synths.sampler;
import com.frinika.voiceserver.AudioInput;
import com.frinika.voiceserver.VoiceInterrupt;
import com.frinika.global.FrinikaConfig;
import com.frinika.synth.Oscillator;
import com.frinika.synth.Synth;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryUsage;
import javax.sound.sampled.TargetDataLine;
/**
* Sampler class. Use for capturing inputs and for direct monitoring.
* @author Peter Johan Salomonsen
*
*/
public class SamplerOscillator extends Oscillator {
AudioInput audioInput;
boolean monitoring = false;
boolean recording = false;
// If you use external direct monitoring you want to bypass software monitoring
boolean directMonitoring = false;
boolean stereo = false;
byte[] inBuffer;
int recordBufferSize;
short[] recordBuffer;
/**
* Number of frames in the buffer when starting recording (this should also be added to the latency compensation)
*/
int inputSkip = 0;
// Time when monitoring was started in milliseconds
long monitorStartMillis = 0;
// Number of frames that has been read from the input
long inputFramesReadCount = 0;
private RecordProgressListener recordProgressListener;
/**
* @param synth
*/
public SamplerOscillator(Synth synth) {
super(synth);
this.nextVoice = synth.getPostOscillator();
}
public void startMonitor(final TargetDataLine lineIn, boolean stereo) throws Exception
{
stopMonitor();
// Allocate as much memory as possible for the recording buffer
directMonitoring = FrinikaConfig.getDirectMonitoring();
this.stereo = stereo;
audioInput = new AudioInput(lineIn,FrinikaConfig.sampleRate);
audioInput.start();
audioInput.getLine().start();
monitorStartMillis = System.currentTimeMillis();
inputFramesReadCount = 0;
// Wait max 1 sec to see if there's data on the line
long waitForActiveTimeStart = System.currentTimeMillis();
while(audioInput.getLine().available()==0 && System.currentTimeMillis() - waitForActiveTimeStart < 1000)
Thread.yield();
// If the line is active (data is on the line -> IO is active) then monitoring will start
if(audioInput.getLine().available()>0)
{
MemoryUsage memUsage = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage();
int allocate = (int)((memUsage.getMax()-memUsage.getUsed())*(0.67))/4;
System.out.println("allocate:"+allocate);
recordBuffer = new short[allocate];
recordBufferSize = 0;
synth.getAudioOutput().interruptTransmitter(this, new VoiceInterrupt() {
@Override
public void doInterrupt() {
monitoring = true;
}});
}
else
{
throw new Exception("Couldn't start IO on the selected line");
}
}
public void stopMonitor()
{
if(monitoring)
{
synth.getAudioOutput().interruptTransmitter(this, new VoiceInterrupt() {
@Override
public void doInterrupt() {
try { monitoring = false;
audioInput.stop();
audioInput = null;
} catch(Exception e) {}
}
});
}
}
/**
* Start recording
*
*/
public void startRecording()
{
final long recordingStartFrame = audioInput.getLine().getLongFramePosition(); //((System.currentTimeMillis()-monitorStartMillis) * synth.getAudioOutput().getSampleRate()) / 1000;
synth.getAudioOutput().interruptTransmitter(this, new VoiceInterrupt() {
@Override
public void doInterrupt() {
inputSkip = (int)(recordingStartFrame - inputFramesReadCount);
//System.out.println("input skip:"+inputSkip+" startframe "+recordingStartFrame+" cnt "+inputFramesReadCount);
recordBufferSize = 0;
recording = true;
}
});
System.out.println("Start recording");
}
/**
* Stop recording
*
* @return
*/
public short[][] stopRecording()
{
synth.getAudioOutput().interruptTransmitter(this, new VoiceInterrupt() {
@Override
public void doInterrupt() {
recording = false;
}
});
// Wait for interrupt to occur
while(recording)
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
/**
* Latency compensation is calculated as voiceserver + os latency + 2 times voiceserverlatency
* the latest addition is cause that two times the voiceserver latency is held standby in the
* input fillBuffer session
*/
//int latencyCompensation = (int)(synth.getAudioOutput().getTotalLatencyAsFrames() * (stereo ? 2 : 1) + ( synth.getAudioOutput().getLatencyAsFrames() *2));
int latencyCompensation = inputSkip * (stereo ? 2 : 1) ;
// Half size per buffer if stereo
short[] bufLeft = new short[(recordBufferSize-latencyCompensation) / (stereo ? 2 : 1)];
short[] bufRight = null;
if(stereo)
{
bufRight = new short[(recordBufferSize-latencyCompensation) / 2];
// Must split the buffer if stereo
for(int n=0;n<recordBufferSize-latencyCompensation;n+=2)
{
bufLeft[n/2] = recordBuffer[n-latencyCompensation];
bufRight[n/2] = recordBuffer[n-latencyCompensation+1];
}
}
else
System.arraycopy(recordBuffer,latencyCompensation,bufLeft,0,recordBufferSize-latencyCompensation);
recordBuffer = null;
System.out.println("Stop recording");
if(this.recordProgressListener!=null)
recordProgressListener.finished();
return(new short[][] {bufLeft,bufRight});
}
/* (non-Javadoc)
* @see com.petersalomonsen.mystudio.audio.IAudioOutputGenerator#fillBuffer(int, int, float[])
*/
public void fillBuffer(int startBufferPos, int endBufferPos, float[] buffer) {
if(monitoring)
{
try
{
if(inBuffer == null || inBuffer.length!=buffer.length*2)
inBuffer = new byte[buffer.length * 2];
// Number of bytes to request from the line
int numOfBytes = (endBufferPos-startBufferPos)*2;
if(audioInput.getLine().available()>=numOfBytes)
{
long t1 = System.nanoTime();
/**
* If we have glitches in the output, this means that there will be more data in the input buffer
* than we are able to handle in this fillBuffer session. Unless we compensate here by skipping data from the input,
* the software monitoring would have introduced an extra delay. However we don't want to skip this data in the recording,
* so what we do is to put all this samples in the recordbuffer, but skip to send them to the preoscillator sample buffer.
*/
do
{
audioInput.getLine().read(inBuffer,0,numOfBytes);
inputFramesReadCount+=numOfBytes / 4;
for(int n=0;n<numOfBytes;)
{
short sample = (short)((0xff & inBuffer[n+1]) + ((0xff & inBuffer[n+0]) * 256));
if(recording)
{
if(recordBufferSize<recordBuffer.length)
recordBuffer[recordBufferSize++] = sample;
}
// In case of external direct monitoring you may want to disable software monitoring
if(directMonitoring)
{
if(stereo)
n+=2;
else
n+=4;
}
else
{
synth.getPreOscillator().sampleBuffer[startBufferPos+(n/2)] = sample / 32768f;
if(stereo)
{
n+=2;
}
else
{
synth.getPreOscillator().sampleBuffer[startBufferPos+((n/2)+1)] = sample / 32768f;
n+=4;
}
}
// In case of the output glitch this loop will run again - and will overwrite the data from the previous loop (hence - the previous data is skipped)
}
/**
* Our tolerance concerning input lag will be up two full buffers - if there's more than two full buffers left in the input after this processing,
* then read over again to catch up with the input.
*
* Why two buffers? If it's just one and you read it, then you will experience that you've read too much from the input and there's nothing to get
* for the next fillbuffer. The result will be an input glip (see the else statement below)
*/
} while(audioInput.getLine().available()>inBuffer.length*2);
}
/**
* An input glip we have if there's not enough data on the input line according to what we've requested
*/
else
{
System.out.println("input glip - only "+audioInput.getLine().available()+" / "+(numOfBytes)+" bytes available");
}
if(this.recordProgressListener!=null)
this.recordProgressListener.updateProgress(recordBufferSize);
}
catch(Exception e)
{
e.printStackTrace();
}
}
}
public boolean isMonitoring() {
return monitoring;
}
/**
* @return
*/
public boolean isRecording() {
return recording;
}
public int available() {
return recordBuffer.length-recordBufferSize;
}
public void setRecordProgressListener(RecordProgressListener recordProgressListener) {
this.recordProgressListener = recordProgressListener;
}
}