
org.monte.media.eightsvx.EightSVXAudioClip Maven / Gradle / Ivy
/*
* @(#)EightSVXAudioClip.java 1.1 2003-04-05
*
* Copyright (c) 1999 Werner Randelshofer, Goldau, Switzerland.
* All rights reserved.
*
* You may not use, copy or modify this file, except in compliance with the
* license agreement you entered into with Werner Randelshofer.
* For details see accompanying license terms.
*
*
* Parts of this software (as marked) is
* Copyright (c) 21 Jan 1985 Steve Hayes, Electronic Arts
*
* This software is in the public domain.
*
* Published in:
* Commodore Electronics Ltd. (1991) Amiga ROM Kernel Manual. Reference Manual.
* Devices. Third Edition. Addison-Wesley: Reading.
*
* Parts of this software (as marked) is
* Copyright (c) 30.4.2000 Olli Niemitalo.
*
* I, as the author and copyright holder, allow you to do anything you wish
* with this book free of charge, including copying, printing and republishing.
* In return, you must preserve this notification and the book�s website URL
* on the title page.
*
* Published In:
* Niemitalo, O. (2000). Audio DSP for the Braindead.
* Online: http://www.student.oulu.fi/�oniemita/DSP/INDEX.HTM
*
* Parts of this software (as marked) is
* Copyright (c) 1999,2000 by Florian Bomers
* Copyright (c) 2000 by Matthias Pfisterer
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as published
* by the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* Published In:
* Tritonus 0.3.1.
*/
package org.monte.media.eightsvx;
import java.lang.Object;
import java.io.*;
import java.applet.AudioClip;
import sun.applet.AppletAudioClip;
import java.lang.reflect.*;
/**
* Represents an audio sample of type IFF 8SVX.
*
* Supported audio formats:
*
8 bit linear and fibonacci encoded data samples.
*
All sample rates
*
Stereo and Mono
*
* Unsupported features:
*
Attack and Release information is ignored.
*
Multi octave samples are not handled.
*
* Known Issues
*
This class has been implemented with JDK 1.1 in mind. JDK 1.1 does not
* have a public API for Sound. This class will thus work only on a small number
* of Java VMS.
*
Poor sound qualitiy: All data is being converted to U-Law 8000 Hertz,
* since this is the only kind of audio data that JDK 1.1 supports (As far as I know).
*
Stereo sound is converted to mono. As far as I know there is now stereo
* support built in JDK 1.1.
*
* @author Werner Randelshofer, Hausmatt 10, CH-6405 Goldau, Switzerland
* @version 1.1 2003-04-05 Revised.
*
1.0 1999-10-19
*/
public class EightSVXAudioClip
implements LoopableAudioClip {
/* Instance variables */
private String name_ = "";
private String author_ = "";
private String copyright_ = "";
private String remark_ = "";
private byte[] body_;
private long
oneShotHiSamples_, // # samples in the high octave 1-shot part
repeatHiSamples_, // # samples in the high octave repeat part
samplesPerHiCycle_; // # samples/cycle in high octave, else 0
private int
sampleRate_, // data sampling rate
ctOctave_; // # octaves of waveforms
public final static int S_CMP_NONE = 0; // not compressed
public final static int S_CMP_FIB_DELTA = 1; // Fibonacci-delta encoding.
private int sCompression_;
// data compression technique used
private final static double UNITY = 0x10000;
private int volume_; // playback volume from 0 to UNITY (full
// volume). Map this value into the output
// hardware's dynamic range.
private LoopableAudioClip cachedAudioClip_;
private int cachedSampleRate_;
public final static int RIGHT=4, LEFT=2, STEREO=6;
private int sampleType_;
private static Boolean javaxAudioIsPresent;
/* Constructors */
/* Accessors */
protected void setName(String value) { name_ = value; }
protected String getName() { return name_; }
protected void setAuthor(String value) {author_ = value; }
protected String getAuthor() { return author_; }
protected void setCopyright(String value) { copyright_ = value; }
protected String getCopyright() { return copyright_; }
protected void setRemark(String value) { remark_ = value; }
protected String getRemark() { return remark_; }
public void set8SVXBody(byte[] value) {
body_ = value;
cachedAudioClip_ = null;
//toAudioData();
}
public byte[] get8SVXBody() { return body_; }
public void setOneShotHiSamples(long value) { oneShotHiSamples_ = value; }
public void setRepeatHiSamples(long value) { repeatHiSamples_ = value; }
public void setSamplesPerHiCycle(long value) { samplesPerHiCycle_ = value; }
public void setSampleType(int value) { sampleType_ = value; }
public void setSampleRate(int value) { sampleRate_ = value; }
public void setCtOctave(int value) { ctOctave_ = value; }
public void setSCompression(int value) { sCompression_ = value; }
public void setVolume(int value) { volume_ = value; }
public long getOneShotHiSamples() { return oneShotHiSamples_; }
public long getRepeatHiSamples() { return repeatHiSamples_; }
public long getSamplesPerHiCycle() { return samplesPerHiCycle_; }
public long getSampleType() { return sampleType_; }
public int getSampleRate() { return sampleRate_; }
public int getCtOctave() { return ctOctave_; }
public int getVolume() { return volume_; }
public int getSCompression() { return sCompression_; }
/* Actions */
public String toString() {
StringBuffer buf = new StringBuffer();
if (getName().length() == 0) buf.append("");
else buf.append(getName());
if (getAuthor().length() != 0) {
buf.append(", ");
buf.append(getAuthor());
}
if (getCopyright().length() != 0) {
buf.append(", � ");
buf.append(getCopyright());
}
buf.append(' ');
buf.append(Integer.toString(getSampleRate()));
buf.append(" Hz");
return buf.toString();
}
public LoopableAudioClip createAudioClip() {
return createAudioClip(getSampleRate(), volume_, 0f);
}
/*
* Does the real work of creating an AudioClip.
*
* @param volume The volume setting controls the loudness of the sound.
* range 0 (mute) to 64 (maximal volume).
* @param pan The relative pan of a stereo signal between two stereo
* speakers. The valid range of values is -1.0 (left channel only) to 1.0
* (right channel only). The default is 0.0 (centered).
*/
public LoopableAudioClip createAudioClip(int sampleRate, int volume, float pan) {
if (javaxAudioIsPresent == null || javaxAudioIsPresent == Boolean.TRUE) {
try {
LoopableAudioClip clip = createJDK13AudioClip(sampleRate, volume, pan);
javaxAudioIsPresent = Boolean.TRUE;
return clip;
} catch (Throwable t) {
t.printStackTrace();
javaxAudioIsPresent = Boolean.FALSE;
}
}
return createJDK10AudioClip(sampleRate);
}
public LoopableAudioClip createJDK13AudioClip(int sampleRate , int volume, float pan) {
AudioClip audioClip = null;
// Decompress the sound data
if (sCompression_ == S_CMP_FIB_DELTA) {
body_ = unpackFibonacciDeltaCompression(body_);
sCompression_ = S_CMP_NONE;
}
// Make it mono
if (sampleType_ == STEREO) {
double volumeCorrection = computeStereoVolumeCorrection(body_);
body_ = linear8StereoToMono(body_,volumeCorrection);
sampleType_ = LEFT;
}
byte[] samples = get8SVXBody();
if (samples.length > 1000000) {
return new JDK13LongAudioClip(samples, sampleRate, volume, pan);
} else {
return new JDK13ShortAudioClip(samples, sampleRate, volume, pan);
}
/*
try {
return new JDK13AppletAudioClip(get8SVXBody(), sampleRate, volume, pan);
} catch (IOException e) {
throw new InternalError(e.toString());
}*/
}
/*
private static Constructor jdk13AudioClipConstructor;
private static Constructor getJDK13AudioClipConstructor() throws Exception {
if (jdk13AudioClipConstructor == null) {
Class c = Class.forName("org.monte.media.eightsvx.JDK13AppletAudioClip");
Class[] parameterTypes = new Class[] {
(new byte[0]).getClass(),
Integer.TYPE,
Integer.TYPE,
Float.TYPE
};
jdk13AudioClipConstructor = c.getConstructor(parameterTypes);
}
return jdk13AudioClipConstructor;
}
*/
public LoopableAudioClip createJDK10AudioClip(int sampleRate /*, int volume*/) {
LoopableAudioClip audioClip = null;
// Decompress the sound data
if (sCompression_ == S_CMP_FIB_DELTA) {
body_ = unpackFibonacciDeltaCompression(body_);
sCompression_ = S_CMP_NONE;
}
// Make it mono
if (sampleType_ == STEREO) {
double volumeCorrection = computeStereoVolumeCorrection(body_);
body_ = linear8StereoToMono(body_,volumeCorrection);
sampleType_ = LEFT;
}
byte[] samples = get8SVXBody();
samples = resample(samples, sampleRate, 8000);
samples = linear8ToULaw(samples);
return new JDK10AudioClip(samples, 8000);
}
public void play() {
stop();
if (cachedAudioClip_ == null) {
cachedAudioClip_ = createAudioClip();
}
cachedAudioClip_.play();
}
public void loop() {
stop();
if (cachedAudioClip_ == null) {
cachedAudioClip_ = createAudioClip();
}
cachedAudioClip_.loop();
}
public void stop() {
if (cachedAudioClip_ != null) {
cachedAudioClip_.stop();
}
}
/**
* Make this clip ready for playback.
*/
public void prepare() {
if (cachedAudioClip_ == null) {
cachedAudioClip_ = createAudioClip();
}
}
/* Class methods */
/**
* This finds the volume correction needed when converting
* this stereo sample to mono.
*
* @param stereo Stereo data linear 8. The first half of the
* array contains the sound for the left speaker,
* the second half the sound for the right speaker.
* @return volumeCorrection
* Combining the two channels into one increases the
* sound volume. This can exceed the maximum volume
* that can be represented by the linear8 sample model.
* To avoid this, the volume must be corrected to fit
* into the sample model.
*/
public static double computeStereoVolumeCorrection(byte[] stereo) {
int half = stereo.length / 2;
int max = 0;
for (int i=0; i < half; i++) {
max = Math.max(max,Math.abs(stereo[i]+stereo[half+i]));
}
if (max < 128) {
return 1.0;
} else {
return 128d / max;
}
}
/**
* This converts a stereo sample to mono.
*
* @param stereo Stereo data linear 8. The first half of the
* array contains the sound for the left speaker,
* the second half the sound for the right speaker.
* @param volumeCorrection
* Combining the two channels into one increases the
* sound volume. This can exceed the maximum volume
* that can be represented by the linear8 sample model.
* To avoid this, the volume must be corrected to fit
* into the sample model.
*/
public static byte[] linear8StereoToMono(byte[] stereo, double volumeCorrection) {
int half = stereo.length / 2;
byte[] mono = new byte[half];
for (int i=0; i < half; i++) {
mono[i] = (byte) ((stereo[i]+stereo[half+i]) * volumeCorrection);
}
return mono;
}
/**
* Resamples audio data to match the given sample rate and applies
* a lowpass filter if necessary.
*
* @param input Linear8 encoded audio data.
* @param inputSampleRate The sample rate of the input data
* @param outputSampleRate The sample rate of the output data.
*
* @return Linear8 encoded audio data.
*/
public static byte[] resample(byte[] input, int inputSampleRate, int outputSampleRate) {
if (inputSampleRate == outputSampleRate) {
// No sample rate conversion needed.
return input;
} else if (inputSampleRate > outputSampleRate) {
// Sample rate conversion with downsampling needed.
// We have to apply a lowpass filter to remove sound
// frequencies that are higher than half of our destSampleRate
// (this is the Nyquist frequency).
//input = lowpassV2(input, inputSampleRate, outputSampleRate / 4f, 128f);
float factor = inputSampleRate / (float) outputSampleRate;
byte[] output = new byte[(int) Math.floor(input.length / factor)];
for (int i=0; i < output.length; i++) {
output[i] = input[(int) (i * factor)];
}
return output;
} else {
// Sample rate conversion with upsampling needed.
// We insert samples from our input array multiple times into
// the output data array.
float factor = inputSampleRate / (float) outputSampleRate;
byte[] output = new byte[(int) Math.ceil(input.length / factor)];
for (int i=0; i < output.length; i++) {
output[i] = input[(int) (i * factor)];
}
return output;
}
}
/**
* Applies a lowpass filter to the linear 8 data to avoid distortion when
* converting from the source sample rate to the destination sample rate.
* After that the sample data is converted into uLAW.
*
* @param linear8 The samples to convert from linear 8 to uLAW.
* @param off Start offset in the linear8 array.
* @param len The number of bytes to convert.
* @param sourceSampleRate Samples per second of the linear8 data.
* @param destSampleRate Samples per second of the uLAW data (must be 8000 hertz).
* @param volume The volume of the sound (1.0 == Unity).
* /
* public static byte[] linear8ToULawWithLowpassAndHeader(byte[] linear8, int off, int len, int sourceSampleRate, int destSampleRate, double volume) {
* // Compute the factor between the source sample rate and destination sample rate.
* double factor = (double)sourceSampleRate / (double)destSampleRate;
*
* int headersize = 24;
* int datasize = (sourceSampleRate == destSampleRate) ? len : (int) Math.ceil(len / factor);
*
* // create the header
* byte[] header = {
* // Sun magic = ".snd"
* (byte) 0x2e, (byte) 0x73, (byte) 0x6e, (byte) 0x64,
* // header size in bytes
* (byte) (headersize >>> 24 & 0xff), (byte) (headersize >>> 16 & 0xff),
* (byte) (headersize >>> 8 & 0xff), (byte) (headersize & 0xff),
* // data size in bytes
* (byte) (datasize >>> 24 & 0xff), (byte) (datasize >>> 16 & 0xff),
* (byte) (datasize >>> 8 & 0xff), (byte) (datasize & 0xff),
* /*
* // data size in bytes (0)
* (byte) 0, (byte) 0, (byte) 0, 0,
* /
* // Sun uLaw format
* (byte) 0, (byte) 0, (byte) 0, (byte) 1,
* // sample rate (only 8000 is supported by Java 1.1)
* (byte) (destSampleRate >>> 24 & 0xff), (byte) (destSampleRate >>> 16 & 0xff),
* (byte) (destSampleRate >>> 8 & 0xff), (byte) (destSampleRate & 0xff),
* // one channel for mono (don't care for left or right speakers).
* (byte) 0, (byte) 0, (byte) 0, (byte) 1
* };
*
* // Create the output array
* byte[] ulaw = new byte[datasize + headersize];
* System.arraycopy(header, 0, ulaw, 0, header.length);
*
* // uLaw encodes sound values from -8192 to +8191,
* // This is 32 times more accurate than linear8.
* // We multiply this value into the volume value
* // to save one computional step within the for-loop.
* volume = volume * 32;
*
* if (sourceSampleRate == destSampleRate) {
* // No sample rate conversion needed.
* // We simply convert each linear8 sample into a ulaw sample.
*
* int mask;
* for (int i=0; i < datasize; i++) {
* // pick a sample of the linear 8-bit data,
* // convert it to two's complement,
* // and finally multiply it with the volume factor.
* int ch = (int)( (0x80 - linear8[i + off]) * volume);
*
* if (ch < 0) {
* ch = -ch;
* mask = 0x7f;
* }
* else {
* mask = 0xff;
* }
*
* if (ch < 32) { ch = 0xf0 | 15 - (ch / 2); }
* else if (ch < 96) { ch = 0xe0 | 15 - (ch - 32) / 4; }
* else if (ch < 224) { ch = 0xd0 | 15 - (ch - 96) / 8; }
* else if (ch < 480) { ch = 0xc0 | 15 - (ch - 224) / 16; }
* else if (ch < 992) { ch = 0xb0 | 15 - (ch - 480) / 32; }
* else if (ch < 2016) { ch = 0xa0 | 15 - (ch - 992) / 64; }
* else if (ch < 4064) { ch = 0x90 | 15 - (ch - 2016) / 128; }
* else if (ch < 8160) { ch = 0x80 | 15 - (ch - 4064) / 256; }
* else { ch = 0x80; }
*
* ulaw[i + headersize] = (byte)(mask & ch);
* }
* } else if (factor > 1.0) {
* // Sample rate conversion with downsampling needed.
* // We have to apply a lowpass filter to remove sound
* // frequencies that are higher than half of our destSampleRate
* // (this is the Nyquist frequency).
*
* // Prepare the lowpass filter
* double resofreq = destSampleRate / 4; // resonation frequency (must be < sampleRate / 4)
* double amp = 1.0; // magnitude of the resonation frequency (?)
* double fx = Math.cos(2*Math.PI*resofreq / sourceSampleRate);
* double c = 2-2*fx;
* double r = (Math.sqrt(2)*Math.sqrt(Math.pow(-(fx-1),3))+amp*(fx-1))/(amp*fx-1);
* double pos = 0;
* double speed = 0;
*
*
* int mask; // mask for the sign in the uLaw code
* for (int i=0; i < len; i++) {
* speed = speed + (linear8[i+off] - pos) * c;
* pos += speed;
* speed *= r;
*
* int j = (int)( i / factor );
* if (ulaw[j + headersize] == 0) // try to optimize a little bit
* {
* int ch = (int)( (0x80 - (byte)pos) * volume );
*
* if (ch < 0) {
* ch = -ch;
* mask = 0x7f;
* } else {
* mask = 0xff;
* }
*
* if (ch < 32) { ch = 0xf0 | 15 - (ch / 2); }
* else if (ch < 96) { ch = 0xe0 | 15 - (ch - 32) / 4; }
* else if (ch < 224) { ch = 0xd0 | 15 - (ch - 96) / 8; }
* else if (ch < 480) { ch = 0xc0 | 15 - (ch - 224) / 16; }
* else if (ch < 992) { ch = 0xb0 | 15 - (ch - 480) / 32; }
* else if (ch < 2016) { ch = 0xa0 | 15 - (ch - 992) / 64; }
* else if (ch < 4064) { ch = 0x90 | 15 - (ch - 2016) / 128; }
* else if (ch < 8160) { ch = 0x80 | 15 - (ch - 4064) / 256; }
* else { ch = 0x80; }
*
* ulaw[j + headersize] = (byte)(mask & ch);
* }
* }
* } else {
* // Sample rate conversion with upsampling needed.
* // We insert samples from our source data multiple times into
* // the ulaw data array.
*
* int mask;
* for (int i=0; i < datasize; i++) {
* // pick a sample of the linear 8-bit data,
* // convert it to two's complement,
* // and finally multiply it with the volume factor.
* int ch = (int)( (0x80 - linear8[ (int)(off+i*factor) ]) * volume);
*
* if (ch < 0) {
* ch = -ch;
* mask = 0x7f;
* } else {
* mask = 0xff;
* }
*
* if (ch < 32) { ch = 0xf0 | 15 - (ch / 2); }
* else if (ch < 96) { ch = 0xe0 | 15 - (ch - 32) / 4; }
* else if (ch < 224) { ch = 0xd0 | 15 - (ch - 96) / 8; }
* else if (ch < 480) { ch = 0xc0 | 15 - (ch - 224) / 16; }
* else if (ch < 992) { ch = 0xb0 | 15 - (ch - 480) / 32; }
* else if (ch < 2016) { ch = 0xa0 | 15 - (ch - 992) / 64; }
* else if (ch < 4064) { ch = 0x90 | 15 - (ch - 2016) / 128; }
* else if (ch < 8160) { ch = 0x80 | 15 - (ch - 4064) / 256; }
* else { ch = 0x80; }
*
* ulaw[i + headersize] = (byte)(mask & ch);
* }
* }
* return ulaw;
* }*/
/**
* Applies a lowpass filter to the linear 8 data to avoid distortion when
* converting from the source sample rate to the destination sample rate.
* After that the sample data is converted into uLAW.
*
* @param linear8 The samples to convert from linear 8 to uLAW.
* @param off Start offset in the linear8 array.
* @param len The number of bytes to convert.
* @param sourceSampleRate Samples per second of the linear8 data.
* @param destSampleRate Samples per second of the uLAW data (must be 8000 hertz).
* @param volume The volume of the sound (1.0 == Unity).
* /
* public static byte[] linear8ToULawWithLowpass(byte[] linear8, int off, int len, int sourceSampleRate, int destSampleRate, double volume) {
* // Compute the factor between the source sample rate and destination sample rate.
* double factor = (double)sourceSampleRate / (double)destSampleRate;
* int datasize = (sourceSampleRate == destSampleRate) ? len : (int) Math.ceil(len / factor);
*
* // Create the output array
* byte[] ulaw = new byte[datasize];
*
* // uLaw encodes sound values from -8192 to +8191,
* // This is 32 times more accurate than linear8.
* // We multiply this value into the volume value
* // to save one computional step within the for-loop.
* volume = volume * 32;
*
* if (sourceSampleRate == destSampleRate) {
* // No sample rate conversion needed.
* // We simply convert each linear8 sample into a ulaw sample.
*
* int mask;
* for (int i=0; i < datasize; i++) {
* // pick a sample of the linear 8-bit data,
* // convert it to two's complement,
* // and finally multiply it with the volume factor.
* int ch = (int)( (0x80 - linear8[i + off]) * volume);
*
* if (ch < 0) {
* ch = -ch;
* mask = 0x7f;
* }
* else {
* mask = 0xff;
* }
*
* if (ch < 32) { ch = 0xf0 | 15 - (ch / 2); }
* else if (ch < 96) { ch = 0xe0 | 15 - (ch - 32) / 4; }
* else if (ch < 224) { ch = 0xd0 | 15 - (ch - 96) / 8; }
* else if (ch < 480) { ch = 0xc0 | 15 - (ch - 224) / 16; }
* else if (ch < 992) { ch = 0xb0 | 15 - (ch - 480) / 32; }
* else if (ch < 2016) { ch = 0xa0 | 15 - (ch - 992) / 64; }
* else if (ch < 4064) { ch = 0x90 | 15 - (ch - 2016) / 128; }
* else if (ch < 8160) { ch = 0x80 | 15 - (ch - 4064) / 256; }
* else { ch = 0x80; }
*
* ulaw[i] = (byte)(mask & ch);
* }
* } else if (factor > 1.0) {
* // Sample rate conversion with downsampling needed.
* // We have to apply a lowpass filter to remove sound
* // frequencies that are higher than half of our destSampleRate
* // (this is the Nyquist frequency).
*
* // Prepare the lowpass filter
* double resofreq = destSampleRate / 4; // resonation frequency (must be < sampleRate / 4)
* double amp = 1.0; // magnitude of the resonation frequency (?)
* double fx = Math.cos(2*Math.PI*resofreq / sourceSampleRate);
* double c = 2-2*fx;
* double r = (Math.sqrt(2)*Math.sqrt(Math.pow(-(fx-1),3))+amp*(fx-1))/(amp*fx-1);
* double pos = 0;
* double speed = 0;
*
*
* int mask; // mask for the sign in the uLaw code
* for (int i=0; i < len; i++) {
* speed = speed + (linear8[i+off] - pos) * c;
* pos += speed;
* speed *= r;
*
* int j = (int)( i / factor );
* if (ulaw[j] == 0) // try to optimize a little bit
* {
* int ch = (int)( (0x80 - (byte)pos) * volume );
*
* if (ch < 0) {
* ch = -ch;
* mask = 0x7f;
* } else {
* mask = 0xff;
* }
*
* if (ch < 32) { ch = 0xf0 | 15 - (ch / 2); }
* else if (ch < 96) { ch = 0xe0 | 15 - (ch - 32) / 4; }
* else if (ch < 224) { ch = 0xd0 | 15 - (ch - 96) / 8; }
* else if (ch < 480) { ch = 0xc0 | 15 - (ch - 224) / 16; }
* else if (ch < 992) { ch = 0xb0 | 15 - (ch - 480) / 32; }
* else if (ch < 2016) { ch = 0xa0 | 15 - (ch - 992) / 64; }
* else if (ch < 4064) { ch = 0x90 | 15 - (ch - 2016) / 128; }
* else if (ch < 8160) { ch = 0x80 | 15 - (ch - 4064) / 256; }
* else { ch = 0x80; }
*
* ulaw[j] = (byte)(mask & ch);
* }
* }
* } else {
* // Sample rate conversion with upsampling needed.
* // We insert samples from our source data multiple times into
* // the ulaw data array.
*
* int mask;
* for (int i=0; i < datasize; i++) {
* // pick a sample of the linear 8-bit data,
* // convert it to two's complement,
* // and finally multiply it with the volume factor.
* int ch = (int)( (0x80 - linear8[ (int)(off+i*factor) ]) * volume);
*
* if (ch < 0) {
* ch = -ch;
* mask = 0x7f;
* } else {
* mask = 0xff;
* }
*
* if (ch < 32) { ch = 0xf0 | 15 - (ch / 2); }
* else if (ch < 96) { ch = 0xe0 | 15 - (ch - 32) / 4; }
* else if (ch < 224) { ch = 0xd0 | 15 - (ch - 96) / 8; }
* else if (ch < 480) { ch = 0xc0 | 15 - (ch - 224) / 16; }
* else if (ch < 992) { ch = 0xb0 | 15 - (ch - 480) / 32; }
* else if (ch < 2016) { ch = 0xa0 | 15 - (ch - 992) / 64; }
* else if (ch < 4064) { ch = 0x90 | 15 - (ch - 2016) / 128; }
* else if (ch < 8160) { ch = 0x80 | 15 - (ch - 4064) / 256; }
* else { ch = 0x80; }
*
* ulaw[i] = (byte)(mask & ch);
* }
* }
*
* return ulaw;
* }
*/
/**
* Applies a lowpass filter to the audio data.
*
* @param input Linear8 encoded audio data.
* @param c The filter ratio. 1.0 passes all, 0.0 passes nothing.
*
* @return Linear 8 encoded audio data.
* /
* public static byte[] lowpass(byte[] input, float c) {
* /* Algorithm taken from
* "Yehar's digital sound processing tutorial for the braindead!"
* version 2001.05.02
* http://www.student.oulu.fi/~oniemita/DSP/DSPSTUFF.TXT
*
* *** Fastest and simplest "lowpass" ever! ***
*
* c = 0..1 (1 = passes all, 0 = passes nothing)
*
* output(t) = output(t-1) + c*(input(t)-output(t-1))
*
* /
*
* // Create the output array
* byte[] output = new byte[input.length];
*
* output[0] = 0;
* for (int t=1; t < input.length; t++) {
* output[t] = (byte) (output[t - 1] + c * (input[t] - output[t - 1]));
* }
*
* return output;
* }*/
/**
* Applies a lowpass filter to the audio data.
*
* @param input Linear8 encoded audio data.
* @param resofreq Resonation frequency (must be < sampleRate / 4)
* @param r 0..1, but not 1
*
* @return Linear8 encoded audio data.
* /
* public static byte[] lowpassV1(byte[] input, int sampleRate, float resofreq, float r) {
* /* Algorithm taken from
* "Yehar's digital sound processing tutorial for the braindead!"
* version 2001.05.02
* http://www.student.oulu.fi/~oniemita/DSP/DSPSTUFF.TXT
*
* *** Fast lowpass with resonance v1 ***
*
* Parameters:
* resofreq = resonation frequency (must be < SR/4)
* r = 0..1, but not 1
*
* Init:
* c = 2-2*cos(2*pi*resofreq / samplerate)
* pos = 0
* speed = 0
*
* Loop:
* speed = speed + (input(t) - pos) * c
* pos = pos + speed
* speed = speed * r
* output(t) = pos
* /
*
* // Create the output array
* byte[] output = new byte[input.length];
*
* // Init
* float c = (float) (2d - 2d * Math.cos(2d * Math.PI * resofreq / sampleRate));
* float pos = 0;
* float speed = 0;
*
* for (int t=0; t < input.length; t++) {
* speed = speed + (input[t] - pos) * c;
* pos += speed;
* speed *= r;
*
* output[t] = (byte) pos;
* }
*
* return output;
* }*/
/**
* Applies a lowpass filter to the audio data.
*
* @param input Linear8 encoded audio data.
* @param resofreq Resonation frequency (must be < sampleRate / 4)
* @param amp Magnitude at the resonation frequency
*
* @return Linear8 encoded audio data.
* /
* public static byte[] lowpassV2(byte[] input, int sampleRate, float resofreq, float amp) {
* /* Algorithm taken from
* "Yehar's digital sound processing tutorial for the braindead!"
* version 2001.05.02
* http://www.student.oulu.fi/~oniemita/DSP/DSPSTUFF.TXT
*
* *** Fast lowpass with resonance v2 ***
*
* Parameters:
* resofreq = resonation frequency (must be < SR/4)
* amp = magnitude at the resonation frequency
*
* Init:
* fx = cos(2*pi*resofreq / samplerate)
* c = 2-2*fx
* r = (sqrt(2)*sqrt(-(fx-1)^3)+amp*(fx-1))/(amp*(fx-1))
* pos = 0
* speed = 0
*
* Loop:
* speed = speed + (input(t) - pos) * c
* pos = pos + speed
* speed = speed * r
* output(t) = pos
* /
*
* // Create the output array
* byte[] output = new byte[input.length];
*
* // Init
* float fx = (float) Math.cos(2*Math.PI*resofreq / sampleRate);
* float c = 2 - 2 * fx;
* float r = (float) (Math.sqrt(2)*Math.sqrt(Math.pow(-(fx-1),3))+amp*(fx-1))/(amp*(fx-1));
* float pos = 0;
* float speed = 0;
*
* for (int t=0; t < input.length; t++) {
* speed = speed + (input[t] - pos) * c;
* pos += speed;
* speed *= r;
*
* output[t] = (byte) pos;
* }
*
* return output;
* }*/
/**
* Applies a lowpass filter to the audio data.
*
* @param input Linear8 encoded audio data.
* @param resofreq Resonation frequency (must be < sampleRate / 4)
* @param amp Magnitude at the resonation frequency
*
* @return Linear8 encoded audio data.
* /
* public static int[] lowpassV2(int[] input, int sampleRate, float resofreq, float amp) {
* /* Algorithm taken from
* "Yehar's digital sound processing tutorial for the braindead!"
* version 2001.05.02
* http://www.student.oulu.fi/~oniemita/DSP/DSPSTUFF.TXT
*
* *** Fast lowpass with resonance v2 ***
*
* Parameters:
* resofreq = resonation frequency (must be < SR/4)
* amp = magnitude at the resonation frequency
*
* Init:
* fx = cos(2*pi*resofreq / samplerate)
* c = 2-2*fx
* r = (sqrt(2)*sqrt(-(fx-1)^3)+amp*(fx-1))/(amp*(fx-1))
* pos = 0
* speed = 0
*
* Loop:
* speed = speed + (input(t) - pos) * c
* pos = pos + speed
* speed = speed * r
* output(t) = pos
* /
*
* // Create the output array
* int[] output = new int[input.length];
*
* // Init
* double fx = Math.cos(2*Math.PI*resofreq / sampleRate);
* double c = 2 - 2 * fx;
* double r = (Math.sqrt(2)*Math.sqrt(Math.pow(-(fx-1),3))+amp*(fx-1))/(amp*(fx-1));
* double pos = 0;
* double speed = 0;
*
* for (int t=0; t < input.length; t++) {
* speed = speed + (input[t] - pos) * c;
* pos += speed;
* speed *= r;
*
* output[t] = (int) pos;
* }
*
* return output;
* }*/
/**
* Halfband lowpass.
* /
* public static byte[] halfbandLowpass(byte[] input) {
* /* Algorithm taken from
* "Yehar's digital sound processing tutorial for the braindead!"
* version 2001.05.02
* http://www.student.oulu.fi/~oniemita/DSP/DSPSTUFF.TXT
*** Halfband lowpass ***
*
* b1 = 0.641339 b10 = -0.0227141
* b2 = -3.02936
* b3 = 1.65298 a0a12 = 0.008097
* b4 = -3.4186 a1a11 = 0.048141
* b5 = 1.50021 a2a10 = 0.159244
* b6 = -1.73656 a3a9 = 0.365604
* b7 = 0.554138 a4a8 = 0.636780
* b8 = -0.371742 a5a7 = 0.876793
* b9 = 0.0671787 a6 = 0.973529
*
* output(t) = a0a12*(input(t ) + input(t-12))
* + a1a11*(input(t-1) + input(t-11))
* + a2a10*(input(t-2) + input(t-10))
* + a3a9* (input(t-3) + input(t-9 ))
* + a4a8* (input(t-4) + input(t-8 ))
* + a5a7* (input(t-5) + input(t-7 )) + a6*input(t-6)
* + b1*output(t-1) + b2*output(t-2) + b3*output(t-3)
* + b4*output(t-4) + b5*output(t-5) + b6*output(t-6)
* + b7*output(t-7) + b8*output(t-8) + b9*output(t-9)
* + b10*output(t-10)
* /
* double b1 = 0.641339, b10 = -0.0227141,
* b2 = -3.02936,
* b3 = 1.65298, a0a12 = 0.008097,
* b4 = -3.4186 , a1a11 = 0.048141,
* b5 = 1.50021, a2a10 = 0.159244,
* b6 = -1.73656, a3a9 = 0.365604,
* b7 = 0.554138, a4a8 = 0.636780,
* b8 = -0.371742 , a5a7 = 0.876793,
* b9 = 0.0671787 , a6 = 0.973529;
*
* // Create the output array
* byte[] output = new byte[input.length];
*
* for (int t=12; t < input.length; t++) {
* output[t] = (byte) (
* a0a12*(input[t] + input[t-12])
* + a1a11*(input[t-1] + input[t-11])
* + a2a10*(input[t-2] + input[t-10])
* + a3a9* (input[t-3] + input[t-9 ])
* + a4a8* (input[t-4] + input[t-8 ])
* + a5a7* (input[t-5] + input[t-7 ]) + a6*input[t-6]
* + b1*output[t-1] + b2*output[t-2] + b3*output[t-3]
* + b4*output[t-4] + b5*output[t-5] + b6*output[t-6]
* + b7*output[t-7] + b8*output[t-8] + b9*output[t-9]
* + b10*output[t-10]
* );
* }
* return output;
* }
* /**
* Converts a buffer of signed 8bit samples to uLaw.
* The uLaw bytes overwrite the original 8 bit values.
* The first byte-offset of the uLaw bytes is byteOffset.
* It will be written sampleCount bytes.
*/
public static byte[] linear8ToULaw(byte[] linear8) {
byte[] ulaw = new byte[linear8.length];
for (int i=0; i < linear8.length; i++) {
ulaw[i] = linear16ToULaw(linear8[i] << 8);
}
return ulaw;
}
/**
* Converts a buffer of signed 8bit samples to uLaw.
* The uLaw bytes overwrite the original 8 bit values.
* The first byte-offset of the uLaw bytes is byteOffset.
* It will be written sampleCount bytes.
*/
public static byte[] linear16ToULaw(int[] linear16) {
byte[] ulaw = new byte[linear16.length];
for (int i=0; i < linear16.length; i++) {
ulaw[i] = linear16ToULaw(linear16[i]);
}
return ulaw;
}
/* ---------------------------------------------------------------------
* The following section of this software is
* Copyright 1989 by Steve Hayes
*/
/**
* This is Steve Hayes' Fibonacci Delta sound compression technique.
* It's like the traditional delta encoding but encodes each delta
* in a mere 4 bits. The compressed data is half the size of the
* original data plus a 2-byte overhead for the initial value.
* This much compression introduces some distortion, so try it out
* and use it with discretion.
*
* To achieve a reasonable slew rate, this algorithm looks up each
* stored 4-bit value in a table of Fibonacci numbers. So very small
* deltas are encoded precisely while larger deltas are approximated.
* When it has to make approximations, the compressor should adjust
* all the values (forwards and backwards in time) for minimal overall
* distortion.
*/
/** Fibonacci delta encoding for sound data. */
private final static byte[] CODE_TO_DELTA = {-34,-21,-13,-8,-5,-3,-2,-1,0,1,2,3,5,8,13,21};
/**
* Unpack Fibonacci-delta encoded data from n byte source buffer
* into 2*(n-2) byte dest buffer. Source buffer has a pad byte, an 8-bit
* initial value, followed by n bytes comprising 2*(n) 4-bit
* encoded samples.
*
*/
public static byte[] unpackFibonacciDeltaCompression(byte[] source) {
/* Original algorithm by Steve Hayes
int n = source.length - 2;
int lim = n * 2;
byte[] dest = new byte[lim];
int x = source[1];
int d;
int j=2;
for (int i=0; i < lim; i++)
{ // Decode a data nibble; high nibble then low nibble.
d = source[j]; // get a pair of nibbles
if ( (i & 1) == 1) // select low or high nibble?
{
j++;
}
else
{ d >>= 4; } // shift to get the high nibble
x += CODE_TO_DELTA[d & 0xf]; // add in the decoded delta
dest[i] = (byte)x; // store a 1-byte sample
}
*/
/* Improved algorithm (faster) */
int n = source.length - 2;
int lim = n * 2;
byte[] dest = new byte[lim];
int x = source[1];
int d;
int i=0;
for (int j=2; j < n; j++) {
// Decode a data nibble; high nibble then low nibble.
d = source[j]; // Get one byte containig a pair of nibbles
x += CODE_TO_DELTA[ (d >> 4) & 0xf];
// shift to get the high nibble and add in the
// decoded delta.
dest[i++] = (byte)x;
// store a 1-byte sample
x += CODE_TO_DELTA[ d & 0xf ];
// get the low nibble and add in the
// decoded delta.
dest[i++] = (byte)x;
// store a 1-byte sample
}
return dest;
}
/* ---------------------------------------------------------------------
* The following section of this software is
* Copyright (c) 1989 by Rich Gopstein and Harris Corporation
*/
/**
* Write a "standard" sun header.
*
* @param sampleType Specify STEREO, LEFT or RIGHT.
*/
public static void writeSunAudioHeader(OutputStream outfile, int dataSize, int sampleRate, int sampleType)
throws IOException {
wrulong(outfile,0x2e736e64); // Sun magic = ".snd"
wrulong(outfile,24); // header size in bytes
wrulong(outfile,dataSize); // data size
wrulong(outfile,1); // Sun uLaw format
wrulong(outfile, sampleRate); // sample rate (only 8000 is supported by Java 1.1)
// two channels for stereo sound,
// one channel for mono (don't care for left or right speakers).
wrulong(outfile, sampleType == STEREO ? 2 : 1);
}
/**
* Write an unsigned long (Motorola 68000 CPU format).
*/
public static void wrulong(OutputStream outfile, int ulong)
throws IOException {
outfile.write(ulong >> 24 & 0xff);
outfile.write(ulong >> 16 & 0xff);
outfile.write(ulong >> 8 & 0xff);
outfile.write(ulong >> 0 & 0xff);
}
/* ---------------------------------------------------------------------
* The following section of this software is
* Copyright (c) 1999,2000 by Florian Bomers
* Copyright (c) 2000 by Matthias Pfisterer
*/
private static final boolean ZEROTRAP=true;
private static final short BIAS=0x84;
private static final int CLIP=32635;
private static final int exp_lut1[] ={
0,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3,
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7
};
/**
* Converts a linear signed 16bit sample to a uLaw byte.
* Ported to Java by fb.
*
Originally by:
* Craig Reese: IDA/Supercomputing Research Center
* Joe Campbell: Department of Defense
* 29 September 1989
*/
private static byte linear16ToULaw(int sample) {
int sign, exponent, mantissa, ulawbyte;
if (sample>32767) sample=32767;
else if (sample<-32768) sample=-32768;
/* Get the sample into sign-magnitude. */
sign = (sample >> 8) & 0x80; /* set aside the sign */
if (sign != 0) sample = -sample; /* get magnitude */
if (sample > CLIP) sample = CLIP; /* clip the magnitude */
/* Convert from 16 bit linear to ulaw. */
sample = sample + BIAS;
exponent = exp_lut1[(sample >> 7) & 0xFF];
mantissa = (sample >> (exponent + 3)) & 0x0F;
ulawbyte = ~(sign | (exponent << 4) | mantissa);
if (ZEROTRAP)
if (ulawbyte == 0) ulawbyte = 0x02; /* optional CCITT trap */
return((byte) ulawbyte);
}
/** Starts looping playback from the current position. Playback will
* continue to the loop's end point, then loop back to the loop start point
* count
times, and finally continue playback to the end of
* the clip.
*
* If the current position when this method is invoked is greater than the
* loop end point, playback simply continues to the
* end of the clip without looping.
*
* A count
value of 0 indicates that any current looping should
* cease and playback should continue to the end of the clip. The behavior
* is undefined when this method is invoked with any other value during a
* loop operation.
*
* If playback is stopped during looping, the current loop status is
* cleared; the behavior of subsequent loop and start requests is not
* affected by an interrupted loop operation.
*
* @param count the number of times playback should loop back from the
* loop's end position to the loop's start position, or
* {@link #LOOP_CONTINUOUSLY}
to indicate that looping should
* continue until interrupted
*
*/
public void loop(int count) {
stop();
if (cachedAudioClip_ == null) {
cachedAudioClip_ = createAudioClip();
}
cachedAudioClip_.loop(count);
}
}