Skip to content

Instantly share code, notes, and snippets.

@mmoczkowski
Last active May 13, 2016 08:45
Show Gist options
  • Save mmoczkowski/b6ced92ff4a40aa4647f0c02918c616b to your computer and use it in GitHub Desktop.
Save mmoczkowski/b6ced92ff4a40aa4647f0c02918c616b to your computer and use it in GitHub Desktop.
#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(&params, 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(&notes[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;
}
@mmoczkowski
Copy link
Author

This is a fragment of a Zynth Music Synthesizer for Android responsible for generating the sound. Zynth suports the following modules:

  • Two oscillators: Sine, Triangle, Saw, Square and Random wave shapes
  • LFO: Pan, Volume and oscillators frequencies modulation
  • ADSR
  • Arpeggiator
  • Reverberation
  • Delay
  • Low-pass/High-pass filter

This is the only surviving Zynth source I have. It's very messy but you may find some inspiration there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment