Last active
May 13, 2016 08:45
-
-
Save mmoczkowski/b6ced92ff4a40aa4647f0c02918c616b to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma GCC optimize ("O3") | |
#include <jni.h> | |
#include <math.h> | |
#include <assert.h> | |
#include <SLES/OpenSLES.h> | |
#include <SLES/OpenSLES_Android.h> | |
#include <android/log.h> | |
#include <stdlib.h> | |
#define APPNAME "Zynth" | |
// Synthesizer parameters | |
#define PARAMETER_OSC_WAVE 0 | |
#define PARAMETER_OSC_GAIN 1 | |
#define PARAMETER_OSC_VOICES 2 | |
#define PARAMETER_LFO_MODE 3 | |
#define PARAMETER_LFO_WAVE 4 | |
#define PARAMETER_LFO_RATE 5 | |
#define PARAMETER_ARP_MODE 6 | |
#define PARAMETER_ARP_RANGE 7 | |
#define PARAMETER_ARP_SYNC 8 | |
#define PARAMETER_ADSR_ATTACK 9 | |
#define PARAMETER_ADSR_DECAY 10 | |
#define PARAMETER_ADSR_SUSTAIN 11 | |
#define PARAMETER_ADSR_RELEASE 12 | |
#define PARAMETERS_REV_LEVEL 13 | |
#define PARAMETERS_REV_DELAY 14 | |
#define PARAMETERS_REV_DECAY 15 | |
#define PARAMETERS_COUNT 16 | |
#define PARAMI(x) (int)params[x] | |
#define PARAMD(x) params[x] | |
double params[PARAMETERS_COUNT]; | |
// engine interfaces | |
static SLObjectItf engineObject; | |
static SLEngineItf engineEngine; | |
// output mix interfaces | |
static SLObjectItf outputMixObject; | |
// interfaces | |
static SLObjectItf bqPlayerObject; | |
static SLPlayItf bqPlayerPlay; | |
static SLAndroidSimpleBufferQueueItf bqPlayerBufferQueue; | |
static SLEffectSendItf bqPlayerEffectSend; | |
static SLEqualizerItf equalizerInterface = NULL; | |
static SLEnvironmentalReverbItf reverbInterface = NULL; | |
typedef int WaveType; | |
// Wave types | |
#define WAVE_SIN 0x00 | |
#define WAVE_SAW 0x01 | |
#define WAVE_TRIANGLE 0x02 | |
#define WAVE_SQUARE 0x03 | |
#define WAVE_RANDOM 0x04 | |
#define DOUBLE_TO_SHORT 32768 | |
#define SHORT_TO_DOUBLE 1./32768. | |
#define CLAMP(v,l,h) ((v) < (l) ? (l) : (v) > (h) ? (h) : (v)) | |
static const double FULLTONE_MULTIPLIER = 1.122462; | |
static const double VOICE_DETUNE = 50; | |
static const double DETUNES[] = {-4, -3, -2, -1, 0, 1, 2, 3, 4}; | |
static double phase = 0; | |
#define BUFFER_SIZE 2048 | |
static short buffer[BUFFER_SIZE]; | |
// Oscillator | |
static WaveType oscWave = WAVE_SAW; | |
static double oscGain = 1.0; | |
static int oscVoices = 8; | |
// LFO | |
#define LFO_OFF 0 | |
#define LFO_PAN 1 | |
#define LFO_VOLUME 2 | |
#define LFO_FREQUENCY 3 | |
static double lfoPhase = 0; | |
static double lfoFrequency = 2; | |
static double lfoFrequencyShift = 1; | |
static double lfoCutoffShift = 1; | |
static double BPM = 120; | |
static double absolutePhase = 0; | |
static SLuint32 sampleRate = 44100000; | |
// Arpeggio | |
static double arpSyncs[] = | |
{ | |
0.03125, //0 | |
0.0625, //2 | |
0.125, //3 | |
0.1875, //4 | |
0.25, //5 | |
0.5, //6 | |
0.75 //7 | |
}; | |
#define ARP_MODE_OFF 0 | |
#define ARP_MODE_UP 1 | |
#define ARP_MODE_DN 2 | |
#define ARP_MODE_UP_DN 3 | |
#define ARP_MODE_RND 4 | |
#define ARP_STATE ((int)fmod(((absolutePhase/44100.0)/(((BPM/4.0)/60.0) * arpSyncs[arpSync])), arpRange)) | |
int previousRawArp = 0; | |
int previousArpState = 0; | |
int arpTimer = 0; | |
int getArpState() { | |
int raw = ((absolutePhase/44100.0)/(((BPM/4.0)/60.0) * PARAMD(PARAMETER_ARP_SYNC))); | |
int arpState = 0; | |
int tmp = 0; | |
switch(PARAMI(PARAMETER_ARP_MODE)) { | |
case ARP_MODE_OFF: return 1; | |
case ARP_MODE_UP: | |
arpState = raw%PARAMI(PARAMETER_ARP_RANGE) + 1; | |
break; | |
case ARP_MODE_DN: | |
arpState = PARAMI(PARAMETER_ARP_RANGE) - raw%PARAMI(PARAMETER_ARP_RANGE); | |
break; | |
case ARP_MODE_UP_DN: | |
if((raw/PARAMI(PARAMETER_ARP_RANGE))%2 == 0) | |
arpState = raw%PARAMI(PARAMETER_ARP_RANGE) + 1; | |
else | |
arpState = PARAMI(PARAMETER_ARP_RANGE) - raw%PARAMI(PARAMETER_ARP_RANGE); | |
break; | |
case ARP_MODE_RND: | |
arpState = raw != previousRawArp? (rand() % PARAMI(PARAMETER_ARP_RANGE) + 1) : previousArpState; | |
break; | |
} | |
previousArpState = arpState; | |
previousRawArp = raw; | |
return arpState; | |
} | |
// Keys frequencies | |
static const double KEY_FREQUENCIES[88] = | |
{ 27.5000 , // 1 | |
29.1352 , // 2 | |
30.8677 , // 3 | |
32.7032 , // 4 | |
34.6478 , // 5 | |
36.7081 , // 6 | |
38.8909 , // 7 | |
41.2034 , // 8 | |
43.6535 , // 9 | |
46.2493 , // 10 | |
48.9994 , // 11 | |
51.9131 , // 12 | |
55.0000 , // 13 | |
58.2705 , // 14 | |
61.7354 , // 15 | |
65.4064 , // 16 | |
69.2957 , // 17 | |
73.4162 , // 18 | |
77.7817 , // 19 | |
82.4069 , // 20 | |
87.3071 , // 21 | |
92.4986 , // 22 | |
97.9989 , // 23 | |
103.826 , // 24 | |
110.000 , // 25 | |
116.541 , // 26 | |
123.471 , // 27 | |
130.813 , // 28 | |
138.591 , // 29 | |
146.832 , // 30 | |
155.563 , // 31 | |
164.814 , // 32 | |
174.614 , // 33 | |
184.997 , // 34 | |
195.998 , // 35 | |
207.652 , // 36 | |
220.000 , // 37 | |
233.082 , // 38 | |
246.942 , // 39 | |
261.626 , // 40 | |
277.183 , // 41 | |
293.665 , // 42 | |
311.127 , // 43 | |
329.628 , // 44 | |
349.228 , // 45 | |
369.994 , // 46 | |
391.995 , // 47 | |
415.305 , // 48 | |
440.000 , // 49 | |
466.164 , // 50 | |
493.883 , // 51 | |
523.251 , // 52 | |
554.365 , // 53 | |
587.330 , // 54 | |
622.254 , // 55 | |
659.255 , // 56 | |
698.456 , // 57 | |
739.989 , // 58 | |
783.991 , // 59 | |
830.609 , // 60 | |
880.000 , // 61 | |
932.328 , // 62 | |
987.767 , // 63 | |
1046.50 , // 64 | |
1108.73 , // 65 | |
1174.66 , // 66 | |
1244.51 , // 67 | |
1318.51 , // 68 | |
1396.91 , // 69 | |
1479.98 , // 70 | |
1567.98 , // 71 | |
1661.22 , // 72 | |
1760.00 , // 73 | |
1864.66 , // 74 | |
1975.53 , // 75 | |
2093.00 , // 76 | |
2217.46 , // 77 | |
2349.32 , // 78 | |
2489.02 , // 79 | |
2637.02 , // 80 | |
2793.83 , // 81 | |
2959.96 , // 82 | |
3135.96 , // 83 | |
3322.44 , // 84 | |
3520.00 , // 85 | |
3729.31 , // 86 | |
3951.07 , // 87 | |
4186.01 }; // 88 | |
// Detune frequencies deltas | |
static const double VOICES_DETUNE[16] = { | |
0 , // 1 | |
-10 , // 2 | |
10 , | |
-20 , // 4 | |
20 , | |
-30 , | |
30 , | |
-40 , // 8 | |
40 , | |
-50 , | |
50 , | |
-60 , | |
60 , | |
-70 , | |
70 , | |
80}; | |
// ADSR phases | |
#define ADSR_ATTACK 0 | |
#define ADSR_DECAY 1 | |
#define ADSR_SUSTAIN 2 | |
#define ADSR_RELEASE 3 | |
// ADSR | |
#define MAX_ADSR_PERIOD 44100*5 // 5 seconds | |
#define MIN_ADSR_PERIOD 1 | |
typedef int ADSRPhase; | |
struct SNote { | |
double frequency; | |
double phase[16]; | |
double time; | |
double gain; | |
int isOn; | |
ADSRPhase adsr; | |
}; | |
// Set paramater value | |
void setParam(int paramId, double value) | |
{ | |
switch(paramId) | |
{ | |
case PARAMETERS_REV_LEVEL: | |
(*reverbInterface)->SetReverbLevel(reverbInterface, (SLmillibel)value); | |
break; | |
case PARAMETERS_REV_DELAY: | |
(*reverbInterface)->SetReverbDelay(reverbInterface, (SLmillibel)value); | |
break; | |
case PARAMETERS_REV_DECAY: | |
(*reverbInterface)->SetDecayTime(reverbInterface, (SLmillisecond)value); | |
break; | |
} | |
params[paramId] = value; | |
} | |
JNIEXPORT void JNICALL Java_eu_sathra_zynth_activities_MainActivity_setParameter(JNIEnv *pEnv, jobject obj, jint paramId, jdouble value) | |
{ | |
//params[paramId] = value; | |
setParam(paramId, value); | |
} | |
JNIEXPORT void JNICALL Java_eu_sathra_zynth_activities_MainActivity_setParameters(JNIEnv * pEnv, jclass obj, jdoubleArray jparams) | |
{ | |
jdouble* srcArray = (*pEnv)->GetDoubleArrayElements(pEnv, jparams, JNI_FALSE); | |
//memcpy(¶ms, srcArray, sizeof(params)); | |
int c = 0; | |
for(c=0;c<PARAMETERS_COUNT;c++) | |
setParam(c, srcArray[c]); | |
(*pEnv)->ReleaseDoubleArrayElements(pEnv, jparams, srcArray, JNI_ABORT); | |
} | |
JNIEXPORT jdouble JNICALL Java_eu_sathra_zynth_activities_MainActivity_getParameter(JNIEnv *pEnv, jclass obj, jint paramId) | |
{ | |
return params[paramId]; | |
} | |
static void getADSRGain(struct SNote* note) { | |
double gain = 0; | |
switch(note->adsr) | |
{ | |
case ADSR_ATTACK: | |
if(absolutePhase-note->time > PARAMD(PARAMETER_ADSR_ATTACK)) { | |
note->adsr = ADSR_DECAY; | |
note->time = absolutePhase; | |
} | |
note->gain= (absolutePhase-note->time) / PARAMD(PARAMETER_ADSR_ATTACK); | |
break; | |
case ADSR_DECAY: | |
gain = 1.0-((absolutePhase-note->time)/PARAMD(PARAMETER_ADSR_DECAY)); | |
if(gain <= PARAMD(PARAMETER_ADSR_SUSTAIN)) | |
{ | |
note->adsr = ADSR_SUSTAIN; | |
note->gain= PARAMD(PARAMETER_ADSR_SUSTAIN); | |
} | |
note->gain= gain; | |
break; | |
case ADSR_SUSTAIN: | |
note->gain = PARAMD(PARAMETER_ADSR_SUSTAIN); | |
break; | |
case ADSR_RELEASE: | |
note->gain -= 1 / PARAMD(PARAMETER_ADSR_RELEASE); | |
if(note->gain <=0) | |
note->isOn = 0; | |
break; | |
} | |
} | |
#define NOTE_COUNT 81 | |
static struct SNote notes[NOTE_COUNT]; | |
double getSignal(WaveType wave, double phase) { | |
switch(wave) { | |
case WAVE_SIN: return (sin(phase)+1.0)/2.0; | |
case WAVE_SAW: return phase - floor(phase); //(phase % M_PI * 2.0); | |
case WAVE_TRIANGLE: return 1.0 - fabs(fmod(2.0*phase,2.0) - 1.0); | |
case WAVE_SQUARE: return (sin(phase) < 0)? 0 : 1; | |
case WAVE_RANDOM: return -1 + (float)rand()/((float)RAND_MAX/(2));; | |
} | |
} | |
void getNextAudio(int length) { | |
int n = 0; | |
int c =0; | |
int v =0; | |
double left = 0; | |
double right = 0; | |
for(c=0;c<length;c+=2) | |
{ | |
buffer[c] = 0; | |
buffer[c+1] = 0; | |
if(!notes[n].isOn) continue; | |
getADSRGain(¬es[n]); | |
left = right =0; | |
for(v=0;v<PARAMI(PARAMETER_OSC_VOICES)/2;v++) | |
{ | |
double arpCoeff = FULLTONE_MULTIPLIER*getArpState(); | |
double frequency = (notes[n].frequency +VOICES_DETUNE[v])*arpCoeff; | |
left = right += (1.0/PARAMI(PARAMETER_OSC_VOICES))*getSignal(PARAMI(PARAMETER_OSC_WAVE), notes[n].phase[v] + frequency*M_PI*lfoFrequencyShift*arpCoeff); | |
notes[n].phase[v]+=frequency*M_PI*2/(44100); | |
} | |
//*************************** | |
for(v=PARAMI(PARAMETER_OSC_VOICES)/2;v<PARAMI(PARAMETER_OSC_VOICES);v++) | |
{ | |
double arpCoeff = FULLTONE_MULTIPLIER*getArpState(); | |
double frequency = (notes[n].frequency +VOICES_DETUNE[v])*arpCoeff; | |
left = right += (1.0/PARAMI(PARAMETER_OSC_VOICES))*getSignal(3, notes[n].phase[v] + frequency*M_PI*lfoFrequencyShift*arpCoeff); | |
notes[n].phase[v]+=frequency*M_PI*2/(44100); | |
} | |
//*************************** | |
left = right *= notes[n].gain; | |
buffer[c] += (short)(left*DOUBLE_TO_SHORT); | |
buffer[c+1] += (short)(right*DOUBLE_TO_SHORT); | |
lfoFrequencyShift = 1; | |
lfoCutoffShift = 1; | |
++absolutePhase; | |
lfoPhase += lfoFrequency*M_PI/(44100); | |
switch (PARAMI(PARAMETER_LFO_MODE)) | |
{ | |
case LFO_VOLUME: | |
buffer[c] = buffer[c+1] *= getSignal(PARAMI(PARAMETER_LFO_WAVE), lfoPhase); | |
//rightChannel *=gain; | |
break; | |
case LFO_PAN: | |
buffer[c] *= getSignal(PARAMI(PARAMETER_LFO_WAVE), lfoPhase); | |
buffer[c+1] *= getSignal(PARAMI(PARAMETER_LFO_WAVE), lfoPhase + M_PI/2); | |
//rightChannel *= C.GetSignal(WaveType.Sine, mLFOPhase + System.Math.PI / 2); | |
break; | |
case LFO_FREQUENCY: | |
lfoFrequencyShift = getSignal(PARAMI(PARAMETER_LFO_WAVE), lfoPhase); | |
break; | |
} | |
} | |
} | |
// this callback handler is called every time a buffer finishes playing | |
void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context) | |
{ | |
getNextAudio(BUFFER_SIZE); | |
(*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, buffer, sizeof(buffer)); | |
} | |
JNIEXPORT jint JNICALL Java_eu_sathra_zynth_activities_MainActivity_initialize(JNIEnv *pEnv, jobject obj) | |
{ | |
SLresult result; | |
// create engine | |
result = slCreateEngine(&(engineObject), 0, NULL, 0, NULL, NULL); | |
if(result != SL_RESULT_SUCCESS) goto engine_end; | |
// realize the engine | |
result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE); | |
if(result != SL_RESULT_SUCCESS) goto engine_end; | |
// get the engine interface, which is needed in order to create other objects | |
result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &(engineEngine)); | |
if(result != SL_RESULT_SUCCESS) goto engine_end; | |
SLuint32 channels = 2; | |
// configure audio source | |
SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2}; | |
const SLInterfaceID ids[] = { SL_IID_ENVIRONMENTALREVERB }; | |
const SLboolean req[] = { SL_BOOLEAN_TRUE }; | |
result = (*engineEngine)->CreateOutputMix(engineEngine, &(outputMixObject), 1, ids, req); | |
if(result != SL_RESULT_SUCCESS) goto engine_end; | |
// realize the output mix | |
result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE); | |
int speakers; | |
if(channels > 1) { | |
speakers = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT; | |
} else | |
speakers = SL_SPEAKER_FRONT_CENTER; | |
} | |
SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM,channels, SL_SAMPLINGRATE_44_1, | |
SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16, | |
speakers, SL_BYTEORDER_LITTLEENDIAN}; | |
SLDataSource audioSrc = {&loc_bufq, &format_pcm}; | |
// configure audio sink | |
SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject}; | |
SLDataSink audioSnk = {&loc_outmix, NULL}; | |
// create audio player | |
const SLInterfaceID ids1[] = { SL_IID_BUFFERQUEUE, SL_IID_EFFECTSEND }; | |
const SLboolean req1[] = { SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE }; | |
result = (*engineEngine)->CreateAudioPlayer(engineEngine, &(bqPlayerObject), &audioSrc, &audioSnk, 2, ids1, req1); | |
if(result != SL_RESULT_SUCCESS) goto engine_end; | |
// realize the player | |
result = (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE); | |
if(result != SL_RESULT_SUCCESS) goto engine_end; | |
// get the play interface | |
result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, &(bqPlayerPlay)); | |
if(result != SL_RESULT_SUCCESS) goto engine_end; | |
// get the buffer queue interface | |
result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_BUFFERQUEUE, &(bqPlayerBufferQueue)); | |
if(result != SL_RESULT_SUCCESS) goto engine_end; | |
// get the effect send | |
result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_EFFECTSEND, &bqPlayerEffectSend); | |
if(result != SL_RESULT_SUCCESS) goto engine_end; | |
// register callback on the buffer queue | |
result = (*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue, bqPlayerCallback, NULL); | |
if(result != SL_RESULT_SUCCESS) goto engine_end; | |
// get the reverb interface | |
result = (*outputMixObject)->GetInterface(outputMixObject, SL_IID_ENVIRONMENTALREVERB, &reverbInterface); | |
if(result != SL_RESULT_SUCCESS) goto engine_end; | |
const SLEnvironmentalReverbSettings reverbSettings = SL_I3DL2_ENVIRONMENT_PRESET_CAVE; | |
result = (*reverbInterface)->SetEnvironmentalReverbProperties(reverbInterface, &reverbSettings); | |
if(result != SL_RESULT_SUCCESS) goto engine_end; | |
// set the player's state to playing | |
result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PLAYING); | |
// get the effect send interface | |
//result = (*outputMixObject)->GetInterface(outputMixObject, SL_IID_EFFECTSEND, &bqPlayerEffectSend); | |
if(result != SL_RESULT_SUCCESS) goto engine_end; | |
//result = (*bqPlayerEffectSend)-> | |
// EnableEffectSend(bqPlayerEffectSend, equalizerInterface, SL_BOOLEAN_TRUE, (SLmillibel) 1000); | |
//result = (*reverbInterface)->SetReverbLevel(reverbInterface, 100); | |
//if(result != SL_RESULT_SUCCESS) goto engine_end; | |
result = (*bqPlayerEffectSend)->EnableEffectSend(bqPlayerEffectSend, reverbInterface, SL_BOOLEAN_TRUE, (SLmillibel) 0); | |
if(result != SL_RESULT_SUCCESS) goto engine_end; | |
int c = 0; | |
engine_end: | |
for(;c<PARAMETERS_COUNT; ++c) { | |
params[c] = 0; | |
} | |
return result; | |
} | |
JNIEXPORT void JNICALL Java_eu_sathra_zynth_activities_MainActivity_playNote(JNIEnv * pEnv, jobject obj, jint id, jdouble frequency) | |
{ | |
notes[0].isOn = 1; | |
notes[0].adsr = ADSR_ATTACK; | |
notes[0].time = absolutePhase; | |
notes[0].frequency = KEY_FREQUENCIES[(int)frequency]; | |
//notes[0].phase = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ,0, 0}; | |
getNextAudio(BUFFER_SIZE); | |
(*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, buffer, sizeof(buffer)); | |
} | |
JNIEXPORT void JNICALL Java_eu_sathra_zynth_activities_MainActivity_stopNote(JNIEnv * pEnv, jobject obj, jint id) | |
{ | |
notes[0].adsr = ADSR_RELEASE; | |
notes[0].time = absolutePhase; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is a fragment of a Zynth Music Synthesizer for Android responsible for generating the sound. Zynth suports the following modules:
This is the only surviving Zynth source I have. It's very messy but you may find some inspiration there.