/**
* Copyright (c) 2005 - Bob Lang (http://www.cems.uwe.ac.uk/~lrlang/)
*
* 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.contrib.boblang;
import javax.sound.sampled.*;
import javax.swing.*;
/**
A polyphonic note player for the Bezier Synthesizer.
@author Bob Lang
@version 25 April 2004
*/
public class BezierSynthPlayer extends Thread {
// Constants imported from Ant Params
private static final int
MAX_AMPLITUDE = BezierParamsV3_5.MAX_AMPLITUDE,
MAX_PITCH = BezierParamsV3_5.MAX_PITCH,
HIGH_PITCH = BezierParamsV3_5.HIGH_PITCH,
LOW_PITCH = BezierParamsV3_5.LOW_PITCH;
// Constants imported from Command List
private static final int
NOTE_ON = CommandList.NOTE_ON,
NOTE_OFF = CommandList.NOTE_OFF;
// Other constants
private static final int
SAMPLES_PER_CYCLE = 128, // Number of samples written
MAX_16BITS = 32767; // Maximum 16 bit value
// Undersampling rate derived from sample rate and output rate
private int
underSampling;
// Polyphony (number of simultaneous notes)
private int
polyphony;
// Java write buffer
private int
writeBufferLength; // Length of line write buffer
// Links to other objects
private SourceDataLine
line; // Output sound device
private BezierEnvelopeShaper
shaper; // Envelope shaper
private BezierParams
params; // Copy of synth parameters
private CommandList
commands; // Circular buffer of note on/off commands
private BezierPlayingNote []
playingNote;
/**
Constructor for this class.
*/
public BezierSynthPlayer (BezierParams inParams,
CommandList inCommands,
int inMaxPolyphony)
{
// Copy the parameters
params = inParams;
// Calculate the current undersampling rate
underSampling = params.getSampleRate()/params.getOutputRate();
// Save the amount of polyphony
polyphony = inMaxPolyphony;
playingNote = new BezierPlayingNote [polyphony];
// Buffer length and number of bytes to write
writeBufferLength = params.getBufferLength();
// Create the command list
commands = inCommands;
// Try to access the sound and midi system devices
try {
// Format of audio output buffer
AudioFormat
format = new AudioFormat ((float) params.getOutputRate(), 16, 1, true, false);
// Find a suitable SourceDataLine and set it up
DataLine.Info
dataLineInfo = new DataLine.Info (SourceDataLine.class, format);
line = (SourceDataLine) AudioSystem.getMixer(AudioSystem.getMixerInfo()[0]).getLine (dataLineInfo);
// Open the line and start it running.
line.open (format, writeBufferLength);
//line.open (format);
//System.out.println (line.getBufferSize ());
line.start ();
// Turn off reverb
//Mixer.Info [] mixerInfo = AudioSystem.getMixerInfo ();
//System.out.println ("Mixer=" + mixerInfo [0]);
//Mixer mixer = AudioSystem.getMixer (mixerInfo [0]);
//Control [] ctrlList = mixer.getControls ();
//EnumControl cont = (EnumControl) ctrlList[0];
//Object [] valueList = cont.getValues();
//for (int obj=0; obj<valueList.length; obj++) {
//System.out.println (" Obj= " + valueList [obj]);
//}
//cont.setValue (valueList [0]);
//ctrlList = mixer.getControls ();
//System.out.println (ctrlList [0]);
}
catch (Exception e) {
String m = "BezierSynthPlayer reports exception\n";
m += e.toString ();
JOptionPane.showMessageDialog (null,
m,
null,
JOptionPane.ERROR_MESSAGE);
System.out.println ("BezierSynthPlayer reports exception");
System.out.println ("Unable to get sound system resource");
System.out.println (e);
} // catch
} // BezierSynthPlayer ()
/**
Change the instrument patch, so that it plays new sounds.
*/
public void changePatch (BezierParams inParams) {
// Save the parameters
params = inParams;
// Create a new envelope shaper
shaper = new BezierEnvelopeShaper (inParams);
} // changePatch ()
/**
Run method for this thread performs all the necessary thread
actions.
*/
public void run () {
// Polyphonic sound buffer and byte equivalent
short [][] extract = new short [polyphony][SAMPLES_PER_CYCLE];
byte [] byteData = new byte [2*SAMPLES_PER_CYCLE];
int playingCount;
// Main loop to process commands and play notes
while (true) {
// Extract any commands waiting
if (commands.isCommandWaiting (0L)) {
// Get the command and the note number
int [] commandData = commands.getCommand ();
int commandType = commandData [0];
int commandNote = commandData [1];
// Process note on and note off commands
if (commandType == NOTE_ON) {
// Start a new note playing
startPlaying (commandNote);
} // if
else if (commandType == NOTE_OFF) {
// Release a note that's currently playing
releasePlaying (commandNote);
} // if
} // if any commands queued
// Process all the notes currently playing
playingCount = 0;
for (int poly = 0; poly < polyphony; poly++) {
// Ignore a null entry in the table
if (playingNote [poly] != null) {
// Non null entry is a note that's currently playing
BezierPlayingNote note = playingNote [poly];
// Extract more sound to keep the note playing
note.getBuffer (extract [playingCount], SAMPLES_PER_CYCLE);
playingCount++;
// See if the note has finished
if (note.isFinished ()) {
// Discard the note
playingNote [poly] = null;
} // if finished
} // if note is not null
} // for poly
// Add the extracts together to give the final buffer
addExtracts (extract, playingCount, byteData);
//Write the data to the output buffer
//System.out.print ("*");
line.write (byteData, 0, byteData.length);
} // while
} // run ()
/**
Add the latest extract to the current buffer
*/
private void addExtracts (short [][] extract, int extractCount, byte [] byteBuffer) {
if (extractCount > 0) {
int k=0;
int len=extract [0].length;
short value = 0;
// Add the extract to the current data buffer
for (int i = 0; i < len; i++) {
value = extract [0][i];
for (int j=1; j < extractCount; j++) {
value += extract [j][i];
} // for
byteBuffer [k] = (byte) (value & 255);
k++;
byteBuffer [k] = (byte) (value >>> 8);
k++;
} // for
} // extract count valid
//$ System.out.print (extractCount);
} // addExtracts ()
/**
Start playing a note. Put an entry in the playingNote array.
*/
private void startPlaying (int pitch) {
// Ensure note is in valid range
if (pitch >= LOW_PITCH && pitch <= HIGH_PITCH) {
// Information message
//$ System.out.print ("On " + pitch);
// Initialisation
boolean entered = false;
// Search for a free entry in the polyphony table
int polyIndex = 0;
while (polyIndex < polyphony && !entered) {
// Is this a free entry?
if (playingNote [polyIndex] == null) {
// Start the note playing and stop searching
BezierSynth synth = new BezierSynth (pitch, params);
playingNote [polyIndex] =
new BezierPlayingNote (synth, shaper, pitch, underSampling);
entered = true;
//$ System.out.println ("P->" + polyIndex);
} // if
// Advance to next entry in the table
polyIndex++;
} // while
// If note not actually entered, search for a released note to replace
polyIndex = 0;
while (polyIndex < polyphony && !entered) {
// Is this a released note?
if (playingNote [polyIndex].isReleased ()) {
// Overwrite the playing note by the new note
BezierSynth synth = new BezierSynth (pitch, params);
playingNote [polyIndex] =
new BezierPlayingNote (synth, shaper, pitch, underSampling);
entered = true;
//$ System.out.println ("P+>" + polyIndex);
} // if
// Advance to next entry in the table
polyIndex++;
} // while
} // if note in valid range
} // startPlaying ()
/**
Release a note that's presently playing.
*/
private void releasePlaying (int pitch) {
// Ensure note is in valid range
if (pitch >= LOW_PITCH && pitch <= HIGH_PITCH) {
// Display information message
//$ System.out.print ("Off " + pitch);
// Search the polyphony table for the note to release
boolean found = false;
int polyIndex = 0;
while (polyIndex < polyphony && !found) {
// Extract the current note
BezierPlayingNote note = playingNote [polyIndex];
// Don't process null notes!
if (note != null) {
// Is this the note we should release?
if (note.getPitch () == pitch && !note.isReleased ()) {
// Release the note and mark entry as found
note.setRelease ();
found = true;
//$ System.out.println ("R->" + polyIndex);
} // if
} // if
// Advance to the next note in the polyphony table
polyIndex++;
} // while
} // if note in valid range
} // releasePlaying ()
} // BezierSynthPlayer