Skip to content

Instantly share code, notes, and snippets.

@rndmcnlly
Created April 23, 2010 00:40
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save rndmcnlly/376028 to your computer and use it in GitHub Desktop.
Save rndmcnlly/376028 to your computer and use it in GitHub Desktop.
DSP Engine from Ethereal Dialpad
package as.adamsmith.etherealdialpad.dsp;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
public class Dac extends UGen {
private final float[] localBuffer;
private boolean isClean;
private final AudioTrack track;
private final short [] target = new short[UGen.CHUNK_SIZE];
private final short [] silentTarget = new short[UGen.CHUNK_SIZE];
public Dac() {
localBuffer = new float[CHUNK_SIZE];
int minSize = AudioTrack.getMinBufferSize(
UGen.SAMPLE_RATE,
AudioFormat.CHANNEL_CONFIGURATION_MONO,
AudioFormat.ENCODING_PCM_16BIT);
track = new AudioTrack(
AudioManager.STREAM_MUSIC,
UGen.SAMPLE_RATE,
AudioFormat.CHANNEL_CONFIGURATION_MONO,
AudioFormat.ENCODING_PCM_16BIT,
Math.max(UGen.CHUNK_SIZE*4, minSize),
AudioTrack.MODE_STREAM);
}
public boolean render(final float[] _buffer) {
if(!isClean) {
zeroBuffer(localBuffer);
isClean = true;
}
// localBuffer is always clean right here, does it stay that way?
isClean = !renderKids(localBuffer);
return !isClean; // we did some work if the buffer isn't clean
}
public void open() {
track.play();
}
public void tick() {
render(localBuffer);
if(isClean) {
// sleeping is messy, so lets just queue this silent buffer
track.write(silentTarget, 0, silentTarget.length);
} else {
for(int i = 0; i < CHUNK_SIZE; i++) {
target[i] = (short)(32768.0f*localBuffer[i]);
}
track.write(target, 0, target.length);
}
}
public void close() {
track.stop();
track.release();
}
}
package as.adamsmith.etherealdialpad.dsp;
public class Delay extends UGen {
final float[] line;
int pointer;
public Delay(int length) {
super();
line = new float[length];
}
public boolean render(final float[] buffer) {
renderKids(buffer);
final float[] localLine = line;
final int lineLength = line.length;
for(int i = 0; i < CHUNK_SIZE; i++) {
buffer[i] = buffer[i] - 0.5f*localLine[pointer];
localLine[pointer] = buffer[i];
pointer = (pointer+1)%lineLength;
}
// ugh, looks like we can never be sure it's silent
// without checking every sample because of the feedback
return true;
}
}
package as.adamsmith.etherealdialpad.dsp;
public class ExpEnv extends UGen {
public static float hardFactor = 0.005f;
public static float softFactor = 0.00005f;
boolean state;
float attenuation;
float factor = softFactor;
final float idealMarker = 0.25f;
float marker = idealMarker;
public synchronized void setActive(boolean nextState) {
state = nextState;
}
public synchronized void setFactor(float nextFactor) {
factor = nextFactor;
}
public synchronized void setGain(float gain) {
marker = gain * idealMarker;
}
public synchronized boolean render(final float[] buffer) {
if(!state && attenuation < 0.0001f) return false;
if(!renderKids(buffer)) return false;
for(int i = 0; i < CHUNK_SIZE; i++) {
buffer[i] *= attenuation;
if(!state) {
attenuation += (0-attenuation)*factor;
} else {
attenuation += (marker-attenuation)*factor;
}
}
return true;
}
}
package as.adamsmith.etherealdialpad.dsp;
import java.util.ArrayList;
public abstract class UGen {
public static final int CHUNK_SIZE = 256;
public static final int SAMPLE_RATE = 22050;
ArrayList<UGen> kids = new ArrayList<UGen>(0);
// fill CHUNK_SIZE samples
// and return true if you actually did any work
abstract public boolean render(final float[] buffer);
final public synchronized UGen chuck(UGen that) {
if(!that.kids.contains(this)) that.kids.add(this);
return that; // returns RHS
}
final public synchronized UGen unchuck(UGen that) {
if(that.kids.contains(this)) that.kids.remove(this);
return that; // returns RHS
}
protected void zeroBuffer(final float[] buffer) {
for(int i = 0; i < CHUNK_SIZE; i++) {
buffer[i] = 0;
}
}
protected boolean renderKids(final float[] buffer) {
boolean didSomeRealWork = false;
for(int k = 0; k < kids.size(); k++) {
didSomeRealWork |= kids.get(k).render(buffer);
}
return didSomeRealWork;
}
}
init {
WtOsc ugOscA1 = new WtOsc();
WtOsc ugOscA2 = new WtOsc();
ExpEnv ugEnvA = new ExpEnv();
ugOscA1.fillWithHardSin(7.0f);
ugOscA2.fillWithHardSin(2.0f);
Dac ugDac = new Dac();
Delay ugDelay = new Delay(UGen.SAMPLE_RATE/2);
ugEnvA.chuck(ugDelay);
ugDelay.chuck(ugDac);
ugOscA1.chuck(ugEnvA);
ugOscA2.chuck(ugEnvA);
ugEnvA.setFactor(ExpEnv.hardFactor);
}
touch {
ugOscA1.setFreq(buildFrequency(scale, octaves, x));
ugOscA2.setFreq(buildFrequency(scale, octaves, y));
}
run {
ugDac.open();
while(!isInterrupted()) {
ugDac.tick();
}
ugDac.close();
}
package as.adamsmith.etherealdialpad.dsp;
import android.util.FloatMath;
public class WtOsc extends UGen {
public static final int BITS = 8;
public static final int ENTRIES = 1<<(BITS-1);
public static final int MASK = ENTRIES-1;
private float phase;
private float cyclesPerSample;
final float[] table;
public WtOsc () {
table = new float[ENTRIES];
}
public synchronized void setFreq(float freq) {
cyclesPerSample = freq/SAMPLE_RATE;
}
public synchronized boolean render(final float[] buffer) { // assume t is in 0.0 to 1.0
for(int i = 0; i < CHUNK_SIZE; i++) {
float scaled = phase*ENTRIES;
final float fraction = scaled-(int)scaled;
final int index = (int)scaled;
buffer[i] += (1.0f-fraction)*table[index&MASK]+fraction*table[(index+1)&MASK];
phase = (phase+cyclesPerSample) - (int)phase;
}
return true;
}
public WtOsc fillWithSin() {
final float dt = (float)(2.0*Math.PI/ENTRIES);
for(int i = 0; i < ENTRIES; i++) {
table[i] = FloatMath.sin(i*dt);
}
return this;
}
public WtOsc fillWithHardSin(final float exp) {
final float dt = (float)(2.0*Math.PI/ENTRIES);
for(int i = 0; i < ENTRIES; i++) {
table[i] = (float) Math.pow(FloatMath.sin(i*dt),exp);
}
return this;
}
public WtOsc fillWithZero() {
for(int i = 0; i < ENTRIES; i++) {
table[i] = 0;
}
return this;
}
public WtOsc fillWithSqr() {
for(int i = 0; i < ENTRIES; i++) {
table[i] = i<ENTRIES/2?1f:-1f;
}
return this;
}
public WtOsc fillWithSqrDuty(float fraction) {
for(int i = 0; i < ENTRIES; i++) {
table[i] = (float)i/ENTRIES<fraction?1f:-1f;
}
return this;
}
public WtOsc fillWithSaw() {
float dt = (float)(2.0/ENTRIES);
for(int i = 0; i < ENTRIES; i++) {
table[i] = 1.0f-i*dt;
}
return this;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment