public
Last active

DSP Engine from Ethereal Dialpad

  • Download Gist
Dac.java
Java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
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();
}
}
Delay.java
Java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
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;
}
}
ExpEnv.java
Java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
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;
}
}
UGen.java
Java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
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;
}
}
WtOsc.java
Java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
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;
}
}
usage.java
Java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
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();
}

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.