/*
* Created on 5.3.2007
*
* Copyright (c) 2007 Karl Helgason
*
* 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.renderer;
import java.io.File;
import java.util.ArrayList;
import uk.org.toot.audio.core.AudioBuffer;
import com.frinika.project.MidiDeviceDescriptor;
import com.frinika.sequencer.FrinikaSequence;
import com.frinika.sequencer.FrinikaTrackWrapper;
public class FrinikaChannelRenderer implements Runnable {
public final static long DEFAULT_PACKET_LENGTH = 1000000; // 1 sec
public final static int PREBUFFER_PACKET_COUNT = 3; // 3 sec
int pipesize;
int buffersize;
long packetlen;
FrinikaDeviceRenderer dev_render;
int channel;
ArrayList<FrinikaTrackWrapper> tracks = new ArrayList<FrinikaTrackWrapper>();
FrinikaMidiPacketProvider packetprovider;
Thread render_thread;
CachedRender packet_render;
volatile boolean active = false;
public FrinikaChannelRenderer(FrinikaDeviceRenderer dev_render, int channel)
{
this.dev_render = dev_render;
this.channel = channel;
packetlen = DEFAULT_PACKET_LENGTH;
buffersize = ((int) (44100.0 * (DEFAULT_PACKET_LENGTH/1000000.0))) * 2;
pipesize = PREBUFFER_PACKET_COUNT;
circularbuffer = new float[pipesize][buffersize];
}
public void addTrack(FrinikaTrackWrapper track)
{
tracks.add(track);
}
public void beforeStart()
{
packetprovider = new FrinikaMidiPacketProvider(packetlen, dev_render.renderer.seqr, dev_render.renderer.project.getSequence(), tracks);
FrinikaSequence seq = dev_render.renderer.project.getSequence();
long recordStartTimeInMicros = dev_render.renderer.seqr.getMicrosecondPosition();
process_index = (int) ( recordStartTimeInMicros / DEFAULT_PACKET_LENGTH );
process_index_bufferpos = ((int) (44100.0 * ((recordStartTimeInMicros % DEFAULT_PACKET_LENGTH)/1000000.0))) * 2;
current_render_index = (int) ( recordStartTimeInMicros / DEFAULT_PACKET_LENGTH );
current_index = -1;
current_next_index = current_render_index;
File projectfile = dev_render.renderer.project.getFile();
String path = projectfile.getPath();
if(path.toLowerCase().endsWith(".frinika")) path = path.substring(0, path.length() - 8);
MidiDeviceDescriptor mdesc = dev_render.renderer.project.getMidiDeviceDescriptor(dev_render.dev);
int d_id = dev_render.renderer.project.getMidiDeviceDescriptors().indexOf(mdesc);
path += ".d" + d_id + "c" + channel + ".rendercache";
File cachefile = new File(path);
packet_render = new CachedRender(cachefile, packetprovider, (MidiRenderFactory)dev_render.dev, 44100, 2, dev_render.renderer.rendermode);
active = true;
render_thread = new Thread(this);
render_thread.setPriority(Thread.MIN_PRIORITY);
render_thread.start();
}
public void beforeStart2()
{
// Wait for pipe to be full
while(true)
{
synchronized (this) {
if( circularbuffer_avail == pipesize )
return;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
return;
}
}
}
int process_index;
int process_index_bufferpos;
float[] process_buffer;
int current_index;
int current_next_index;
int current_render_index;
float[][] circularbuffer;
int circularbuffer_read_pos = 0;
int circularbuffer_write_pos = 0;
int circularbuffer_avail = 0;
public void run()
{
while(active)
{
boolean pipefull = false;
synchronized (this) {
pipefull = circularbuffer_avail == pipesize;
}
if(pipefull)
{
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
return;
}
continue;
}
float[] buffer = circularbuffer[circularbuffer_write_pos];
packet_render.getPacket(current_render_index, buffer, 0, buffersize);
current_render_index++;
synchronized(this)
{
circularbuffer_write_pos = (circularbuffer_write_pos + 1) % pipesize;
circularbuffer_avail++;
}
}
}
public void nextBuffer()
{
if(process_buffer != null)
{
synchronized (this) {
circularbuffer_avail--;
}
}
boolean pipeempty;
do
{
synchronized (this) {
pipeempty = circularbuffer_avail == 0;
}
if(pipeempty)
{
try {
System.out.println("Render Underflow");
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
return;
}
}
}
while(pipeempty);
current_index = current_next_index;
current_next_index++;
float[] buffer = circularbuffer[circularbuffer_read_pos];
circularbuffer_read_pos = (circularbuffer_read_pos + 1) % pipesize;
process_buffer = buffer;
}
public void nextProcessBuffer()
{
nextBuffer();
process_index++;
process_index_bufferpos = 0;
}
public void stop()
{
active = false;
packet_render.close();
}
public void processAudio(AudioBuffer buffer)
{
float[] left = buffer.getChannel(0);
float[] right = buffer.getChannel(1);
int sampleCount = buffer.getSampleCount();
int writeCount = 0;
if(process_buffer == null) nextBuffer();
while(sampleCount - writeCount != 0)
{
if(process_index_bufferpos == buffersize) nextProcessBuffer();
int avail = (buffersize - process_index_bufferpos)/2;
if(avail > sampleCount - writeCount) avail = sampleCount - writeCount;
for (int i = 0; i < avail; i++) {
left[writeCount] += process_buffer[process_index_bufferpos++];
right[writeCount] += process_buffer[process_index_bufferpos++];
writeCount++;
}
}
}
}