SFX Audio on Android
Once upon the time all game audio was synthesized. We were amazed with the chips that would come out from the ZX Spectrum or the commodore 64 but nowadays we can still make cool explosion sounds for small devices such as the android mobile phones.
I will not diverge into much details on audio synthetising since I am not an expert myself. If you want information about that you can look here:
- http://designingsound.org/
- http://mitpress.mit.edu/catalog/item/default.asp?ttype=2&tid=11652
- http://www.procedural-audio.com/
So i implemented a simple SFX util class based on an old project (but very useful: http://www.drpetter.se/project_sfxr.html) to generate sounds for Android games. The code is the following:
package com.jetdrone.and2d.sfx;
import java.util.Random;
import android.util.FloatMath;
public class SFXR {
// 0: square, 1: sawtooth, 2: sine, 3: noise
private int wave_type;
private float p_base_freq; // Start Frequency
private float p_freq_limit; // Min Frequency
private float p_freq_ramp; // Slide
private float p_freq_dramp; // Delta Slide
private float p_duty; // Square Duty
private float p_duty_ramp; // Duty Sweep
private float p_vib_strength; // Vibrato Depth
private float p_vib_speed; // Vibrato Speed
private float p_vib_delay; // ???
private float p_env_attack; // Attack Time
private float p_env_sustain; // Sustain Time
private float p_env_decay; // Decay Time
private float p_env_punch; // Sustain Punch
boolean filter_on; // ???
private float p_lpf_resonance; // LP Filter Resonance
private float p_lpf_freq; // LP Filter Cutoff
private float p_lpf_ramp; // LP Filter Cutoff Sweep
private float p_hpf_freq; // HP Filter Cutoff
private float p_hpf_ramp; // HP Filter Cutoff Sweep
private float p_pha_offset; // Phaser Offset
private float p_pha_ramp; // Phaser Sweep
private float p_repeat_speed; // Repeat Speed
private float p_arp_speed; // Change Speed
private float p_arp_mod; // Change Amount
private float master_vol = 0.05f;
private float sound_vol = 0.5f;
private boolean playing_sample = false;
private int phase;
private float fperiod;
private float fmaxperiod;
private float fslide;
private float fdslide;
private int period;
private float square_duty;
private float square_slide;
private int env_stage;
private int env_time;
private int[] env_length = new int[3];
private float env_vol;
private float fphase;
private float fdphase;
private int iphase;
private float[] phaser_buffer = new float[1024];
private int ipp;
private float[] noise_buffer = new float[32];
private float fltp;
private float fltdp;
private float fltw;
private float fltw_d;
private float fltdmp;
private float fltphp;
private float flthp;
private float flthp_d;
private float vib_phase;
private float vib_speed;
private float vib_amp;
private int rep_time;
private int rep_limit;
private int arp_time;
private int arp_limit;
private float arp_mod;
private final Random rnd = new Random();
public void init(long seed) {
rnd.setSeed(seed);
resetParams();
playing_sample = true;
}
public void init() {
resetParams();
playing_sample = true;
}
private final float frnd(float v) {
return rnd.nextFloat() * v;
}
private static final float fpow(float a, float b) {
return (float) Math.pow(a, b);
}
private static final float fsin(float a) {
return FloatMath.sin(a);
}
private final int rnd(int v) {
return rnd.nextInt(v+1);
}
public void resetParams() {
wave_type = 0;
p_base_freq = 0.3f;
p_freq_limit = 0.0f;
p_freq_ramp = 0.0f;
p_freq_dramp = 0.0f;
p_duty = 0.0f;
p_duty_ramp = 0.0f;
p_vib_strength = 0.0f;
p_vib_speed = 0.0f;
p_vib_delay = 0.0f;
p_env_attack = 0.0f;
p_env_sustain = 0.3f;
p_env_decay = 0.4f;
p_env_punch = 0.0f;
filter_on = false;
p_lpf_resonance = 0.0f;
p_lpf_freq = 1.0f;
p_lpf_ramp = 0.0f;
p_hpf_freq = 0.0f;
p_hpf_ramp = 0.0f;
p_pha_offset = 0.0f;
p_pha_ramp = 0.0f;
p_repeat_speed = 0.0f;
p_arp_speed = 0.0f;
p_arp_mod = 0.0f;
}
public void resetSample(boolean restart) {
if (!restart)
phase = 0;
fperiod = 100.0f / (p_base_freq * p_base_freq + 0.001f);
period = (int) fperiod;
fmaxperiod = 100.0f / (p_freq_limit * p_freq_limit + 0.001f);
fslide = 1.0f - fpow(p_freq_ramp, 3.0f) * 0.01f;
fdslide = -fpow(p_freq_dramp, 3.0f) * 0.000001f;
square_duty = 0.5f - p_duty * 0.5f;
square_slide = -p_duty_ramp * 0.00005f;
if (p_arp_mod >= 0.0f)
arp_mod = 1.0f - fpow(p_arp_mod, 2.0f) * 0.9f;
else
arp_mod = 1.0f + fpow(p_arp_mod, 2.0f) * 10.0f;
arp_time = 0;
arp_limit = (int) (fpow(1.0f - p_arp_speed, 2.0f) * 20000 + 32);
if (p_arp_speed == 1.0f)
arp_limit = 0;
if (!restart) {
// reset filter
fltp = 0.0f;
fltdp = 0.0f;
fltw = fpow(p_lpf_freq, 3.0f) * 0.1f;
fltw_d = 1.0f + p_lpf_ramp * 0.0001f;
fltdmp = 5.0f / (1.0f + fpow(p_lpf_resonance, 2.0f) * 20.0f)
* (0.01f + fltw);
if (fltdmp > 0.8f)
fltdmp = 0.8f;
fltphp = 0.0f;
flthp = fpow(p_hpf_freq, 2.0f) * 0.1f;
flthp_d = 1.0f + p_hpf_ramp * 0.0003f;
// reset vibrato
vib_phase = 0.0f;
vib_speed = fpow(p_vib_speed, 2.0f) * 0.01f;
vib_amp = p_vib_strength * 0.5f;
// reset envelope
env_vol = 0.0f;
env_stage = 0;
env_time = 0;
env_length[0] = (int) (p_env_attack * p_env_attack * 100000.0f);
env_length[1] = (int) (p_env_sustain * p_env_sustain * 100000.0f);
env_length[2] = (int) (p_env_decay * p_env_decay * 100000.0f);
fphase = fpow(p_pha_offset, 2.0f) * 1020.0f;
if (p_pha_offset < 0.0f)
fphase = -fphase;
fdphase = fpow(p_pha_ramp, 2.0f) * 1.0f;
if (p_pha_ramp < 0.0f)
fdphase = -fdphase;
iphase = Math.abs((int) fphase);
ipp = 0;
for (int i = 0; i < 1024; i++)
phaser_buffer[i] = 0.0f;
for (int i = 0; i < 32; i++)
noise_buffer[i] = frnd(2.0f) - 1.0f;
rep_time = 0;
rep_limit = (int) (fpow(1.0f - p_repeat_speed, 2.0f) * 20000 + 32);
if (p_repeat_speed == 0.0f)
rep_limit = 0;
}
}
public float synthSample() {
if (!playing_sample)
return 0.0f;
rep_time++;
if (rep_limit != 0 && rep_time >= rep_limit) {
rep_time = 0;
resetSample(true);
}
// frequency envelopes/arpeggios
arp_time++;
if (arp_limit != 0 && arp_time >= arp_limit) {
arp_limit = 0;
fperiod *= arp_mod;
}
fslide += fdslide;
fperiod *= fslide;
if (fperiod > fmaxperiod) {
fperiod = fmaxperiod;
if (p_freq_limit > 0.0f)
playing_sample = false;
}
float rfperiod = fperiod;
if (vib_amp > 0.0f) {
vib_phase += vib_speed;
rfperiod = fperiod * (1.0f + fsin(vib_phase) * vib_amp);
}
period = (int) rfperiod;
if (period < 8)
period = 8;
square_duty += square_slide;
if (square_duty < 0.0f)
square_duty = 0.0f;
if (square_duty > 0.5f)
square_duty = 0.5f;
// volume envelope
env_time++;
if (env_time > env_length[env_stage]) {
env_time = 0;
env_stage++;
if (env_stage == 3)
playing_sample = false;
}
if (env_stage == 0) {
env_vol = (float) env_time / env_length[0];
}
if (env_stage == 1) {
env_vol = 1.0f
+ fpow(1.0f - (float) env_time / env_length[1], 1.0f)
* 2.0f * p_env_punch;
}
if (env_stage == 2) {
env_vol = 1.0f - (float) env_time / env_length[2];
}
// phaser step
fphase += fdphase;
iphase = Math.abs((int) fphase);
if (iphase > 1023)
iphase = 1023;
if (flthp_d != 0.0f) {
flthp *= flthp_d;
if (flthp < 0.00001f)
flthp = 0.00001f;
if (flthp > 0.1f)
flthp = 0.1f;
}
float ssample = 0.0f;
for (int si = 0; si < 8; si++) // 8x supersampling
{
float sample = 0.0f;
phase++;
if (phase >= period) {
// phase=0;
phase %= period;
if (wave_type == 3) {
for (int j = 0; j < 32; j++) {
noise_buffer[j] = frnd(2.0f) - 1.0f;
}
}
}
// base waveform
float fp = (float) phase / period;
switch (wave_type) {
case 0: // square
if (fp < square_duty)
sample = 0.5f;
else
sample = -0.5f;
break;
case 1: // sawtooth
sample = 1.0f - fp * 2;
break;
case 2: // sine
sample = fsin(fp * 2 * (float) Math.PI);
break;
case 3: // noise
sample = noise_buffer[phase * 32 / period];
break;
}
// lp filter
float pp = fltp;
fltw *= fltw_d;
if (fltw < 0.0f)
fltw = 0.0f;
if (fltw > 0.1f)
fltw = 0.1f;
if (p_lpf_freq != 1.0f) {
fltdp += (sample - fltp) * fltw;
fltdp -= fltdp * fltdmp;
} else {
fltp = sample;
fltdp = 0.0f;
}
fltp += fltdp;
// hp filter
fltphp += fltp - pp;
fltphp -= fltphp * flthp;
sample = fltphp;
// phaser
phaser_buffer[ipp & 1023] = sample;
sample += phaser_buffer[(ipp - iphase + 1024) & 1023];
ipp = (ipp + 1) & 1023;
// final accumulation and envelope application
ssample += sample * env_vol;
}
ssample = ssample / 8 * master_vol;
ssample *= 2.0f * sound_vol;
if (ssample > 1.0f)
ssample = 1.0f;
if (ssample < -1.0f)
ssample = -1.0f;
return ssample;
// }
}
public void random(int i) {
switch (i) {
case 0: // pickup/coin
resetParams();
p_base_freq = 0.4f + frnd(0.5f);
p_env_attack = 0.0f;
p_env_sustain = frnd(0.1f);
p_env_decay = 0.1f + frnd(0.4f);
p_env_punch = 0.3f + frnd(0.3f);
if (rnd(1) > 0) {
p_arp_speed = 0.5f + frnd(0.2f);
p_arp_mod = 0.2f + frnd(0.4f);
}
break;
case 1: // laser/shoot
resetParams();
wave_type = rnd(2);
if (wave_type == 2 && rnd(1) > 0)
wave_type = rnd(1);
p_base_freq = 0.5f + frnd(0.5f);
p_freq_limit = p_base_freq - 0.2f - frnd(0.6f);
if (p_freq_limit < 0.2f)
p_freq_limit = 0.2f;
p_freq_ramp = -0.15f - frnd(0.2f);
if (rnd(2) == 0) {
p_base_freq = 0.3f + frnd(0.6f);
p_freq_limit = frnd(0.1f);
p_freq_ramp = -0.35f - frnd(0.3f);
}
if (rnd(1) > 0) {
p_duty = frnd(0.5f);
p_duty_ramp = frnd(0.2f);
} else {
p_duty = 0.4f + frnd(0.5f);
p_duty_ramp = -frnd(0.7f);
}
p_env_attack = 0.0f;
p_env_sustain = 0.1f + frnd(0.2f);
p_env_decay = frnd(0.4f);
if (rnd(1) > 0)
p_env_punch = frnd(0.3f);
if (rnd(2) == 0) {
p_pha_offset = frnd(0.2f);
p_pha_ramp = -frnd(0.2f);
}
if (rnd(1) > 0)
p_hpf_freq = frnd(0.3f);
break;
case 2: // explosion
resetParams();
wave_type = 3;
if (rnd(1) > 0) {
p_base_freq = 0.1f + frnd(0.4f);
p_freq_ramp = -0.1f + frnd(0.4f);
} else {
p_base_freq = 0.2f + frnd(0.7f);
p_freq_ramp = -0.2f - frnd(0.2f);
}
p_base_freq *= p_base_freq;
if (rnd(4) == 0)
p_freq_ramp = 0.0f;
if (rnd(2) == 0)
p_repeat_speed = 0.3f + frnd(0.5f);
p_env_attack = 0.0f;
p_env_sustain = 0.1f + frnd(0.3f);
p_env_decay = frnd(0.5f);
if (rnd(1) == 0) {
p_pha_offset = -0.3f + frnd(0.9f);
p_pha_ramp = -frnd(0.3f);
}
p_env_punch = 0.2f + frnd(0.6f);
if (rnd(1) > 0) {
p_vib_strength = frnd(0.7f);
p_vib_speed = frnd(0.6f);
}
if (rnd(2) == 0) {
p_arp_speed = 0.6f + frnd(0.3f);
p_arp_mod = 0.8f - frnd(1.6f);
}
break;
case 3: // powerup
resetParams();
if (rnd(1) > 0)
wave_type = 1;
else
p_duty = frnd(0.6f);
if (rnd(1) > 0) {
p_base_freq = 0.2f + frnd(0.3f);
p_freq_ramp = 0.1f + frnd(0.4f);
p_repeat_speed = 0.4f + frnd(0.4f);
} else {
p_base_freq = 0.2f + frnd(0.3f);
p_freq_ramp = 0.05f + frnd(0.2f);
if (rnd(1) > 0) {
p_vib_strength = frnd(0.7f);
p_vib_speed = frnd(0.6f);
}
}
p_env_attack = 0.0f;
p_env_sustain = frnd(0.4f);
p_env_decay = 0.1f + frnd(0.4f);
break;
case 4: // hit/hurt
resetParams();
wave_type = rnd(2);
if (wave_type == 2)
wave_type = 3;
if (wave_type == 0)
p_duty = frnd(0.6f);
p_base_freq = 0.2f + frnd(0.6f);
p_freq_ramp = -0.3f - frnd(0.4f);
p_env_attack = 0.0f;
p_env_sustain = frnd(0.1f);
p_env_decay = 0.1f + frnd(0.2f);
if (rnd(1) > 0)
p_hpf_freq = frnd(0.3f);
break;
case 5: // jump
resetParams();
wave_type = 0;
p_duty = frnd(0.6f);
p_base_freq = 0.3f + frnd(0.3f);
p_freq_ramp = 0.1f + frnd(0.2f);
p_env_attack = 0.0f;
p_env_sustain = 0.1f + frnd(0.3f);
p_env_decay = 0.1f + frnd(0.2f);
if (rnd(1) > 0)
p_hpf_freq = frnd(0.3f);
if (rnd(1) > 0)
p_lpf_freq = 1.0f - frnd(0.6f);
break;
case 6: // blip/select
resetParams();
wave_type = rnd(1);
if (wave_type == 0)
p_duty = frnd(0.6f);
p_base_freq = 0.2f + frnd(0.4f);
p_env_attack = 0.0f;
p_env_sustain = 0.1f + frnd(0.1f);
p_env_decay = frnd(0.2f);
p_hpf_freq = 0.1f;
break;
default:
break;
}
}
public void randomize() {
p_base_freq = fpow(frnd(2.0f) - 1.0f, 2.0f);
if (rnd(1) > 0)
p_base_freq = fpow(frnd(2.0f) - 1.0f, 3.0f) + 0.5f;
p_freq_limit = 0.0f;
p_freq_ramp = fpow(frnd(2.0f) - 1.0f, 5.0f);
if (p_base_freq > 0.7f && p_freq_ramp > 0.2f)
p_freq_ramp = -p_freq_ramp;
if (p_base_freq < 0.2f && p_freq_ramp < -0.05f)
p_freq_ramp = -p_freq_ramp;
p_freq_dramp = fpow(frnd(2.0f) - 1.0f, 3.0f);
p_duty = frnd(2.0f) - 1.0f;
p_duty_ramp = fpow(frnd(2.0f) - 1.0f, 3.0f);
p_vib_strength = fpow(frnd(2.0f) - 1.0f, 3.0f);
p_vib_speed = frnd(2.0f) - 1.0f;
p_vib_delay = frnd(2.0f) - 1.0f;
p_env_attack = fpow(frnd(2.0f) - 1.0f, 3.0f);
p_env_sustain = fpow(frnd(2.0f) - 1.0f, 2.0f);
p_env_decay = frnd(2.0f) - 1.0f;
p_env_punch = fpow(frnd(0.8f), 2.0f);
if (p_env_attack + p_env_sustain + p_env_decay < 0.2f) {
p_env_sustain += 0.2f + frnd(0.3f);
p_env_decay += 0.2f + frnd(0.3f);
}
p_lpf_resonance = frnd(2.0f) - 1.0f;
p_lpf_freq = 1.0f - fpow(frnd(1.0f), 3.0f);
p_lpf_ramp = fpow(frnd(2.0f) - 1.0f, 3.0f);
if (p_lpf_freq < 0.1f && p_lpf_ramp < -0.05f)
p_lpf_ramp = -p_lpf_ramp;
p_hpf_freq = fpow(frnd(1.0f), 5.0f);
p_hpf_ramp = fpow(frnd(2.0f) - 1.0f, 5.0f);
p_pha_offset = fpow(frnd(2.0f) - 1.0f, 3.0f);
p_pha_ramp = fpow(frnd(2.0f) - 1.0f, 3.0f);
p_repeat_speed = frnd(2.0f) - 1.0f;
p_arp_speed = frnd(2.0f) - 1.0f;
p_arp_mod = frnd(2.0f) - 1.0f;
}
public void mutate() {
if (rnd(1) > 0)
p_base_freq += frnd(0.1f) - 0.05f;
if (rnd(1) > 0)
p_freq_ramp += frnd(0.1f) - 0.05f;
if (rnd(1) > 0)
p_freq_dramp += frnd(0.1f) - 0.05f;
if (rnd(1) > 0)
p_duty += frnd(0.1f) - 0.05f;
if (rnd(1) > 0)
p_duty_ramp += frnd(0.1f) - 0.05f;
if (rnd(1) > 0)
p_vib_strength += frnd(0.1f) - 0.05f;
if (rnd(1) > 0)
p_vib_speed += frnd(0.1f) - 0.05f;
if (rnd(1) > 0)
p_vib_delay += frnd(0.1f) - 0.05f;
if (rnd(1) > 0)
p_env_attack += frnd(0.1f) - 0.05f;
if (rnd(1) > 0)
p_env_sustain += frnd(0.1f) - 0.05f;
if (rnd(1) > 0)
p_env_decay += frnd(0.1f) - 0.05f;
if (rnd(1) > 0)
p_env_punch += frnd(0.1f) - 0.05f;
if (rnd(1) > 0)
p_lpf_resonance += frnd(0.1f) - 0.05f;
if (rnd(1) > 0)
p_lpf_freq += frnd(0.1f) - 0.05f;
if (rnd(1) > 0)
p_lpf_ramp += frnd(0.1f) - 0.05f;
if (rnd(1) > 0)
p_hpf_freq += frnd(0.1f) - 0.05f;
if (rnd(1) > 0)
p_hpf_ramp += frnd(0.1f) - 0.05f;
if (rnd(1) > 0)
p_pha_offset += frnd(0.1f) - 0.05f;
if (rnd(1) > 0)
p_pha_ramp += frnd(0.1f) - 0.05f;
if (rnd(1) > 0)
p_repeat_speed += frnd(0.1f) - 0.05f;
if (rnd(1) > 0)
p_arp_speed += frnd(0.1f) - 0.05f;
if (rnd(1) > 0)
p_arp_mod += frnd(0.1f) - 0.05f;
}
}
This class lets you generate sounds, but this is not enough since you also wants to listen to them. For this i created another helper class (AudioClip) that will let you play the sound on your Android device:
package com.jetdrone.and2d;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
public class AudioClip {
private AudioTrack track;
private int sampleRateInHz = 22050;
private short[] audioData;
public AudioClip(int sampleRateInHz) {
this.sampleRateInHz = sampleRateInHz;
}
public AudioClip(int sampleRateInHz, short[] audioData) {
this.sampleRateInHz = sampleRateInHz;
setData(audioData);
}
public void setData(short[] audioData) {
if(track != null) {
track.release();
}
final int bufSize = Math.max(sampleRateInHz, audioData.length);
this.audioData = new short[bufSize];
track = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRateInHz, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT, bufSize, AudioTrack.MODE_STATIC);
if(track.getState() == AudioTrack.STATE_NO_STATIC_DATA) {
System.arraycopy(audioData, 0, this.audioData, 0, audioData.length);
track.write(audioData, 0, audioData.length);
}
}
public void play() {
if(track != null) {
track.play();
track.stop();
track.reloadStaticData();
}
}
public void release() {
if(track != null) {
track.release();
}
}
@Override
public void finalize() throws Throwable {
release();
super.finalize();
}
}
Now you can generate your sound in background and play the in the foreground, pretty neat! :)
Related tags: sfx audio android code
Comments