-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSynthNote.java
More file actions
294 lines (232 loc) · 8.37 KB
/
SynthNote.java
File metadata and controls
294 lines (232 loc) · 8.37 KB
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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
/**
SynthNote creates a thread in which the attack decay sustain release envelopes
are applied on the oscillator selected. The thread uses a buffer to write the bytes to.
When the buffer is full, the listener's callback function is used, where the bytes then can
be written to the line out. When the SynthNote has completed, the remaining bytes in the
buffer are drained. The SynthNote can also be cancelled as this is a monophonic synth.
**/
public class SynthNote extends Thread {
private Buffer buffer;
private Oscillator osc;
private static final int DEFAULT_SAMPLE_RATE = 44100;
private static final int DEFUALT_BUFFER_SIZE = 256;
private double sustainAmplitude;
private double maxAmplitude;
private double currentAmplitude;
private double attackTime;
private double decayTime;
private double releaseTime;
private double currentTime;
/* Notes may be in the pressed state
where the note will sustain, the release state,
where the note will release and eventually finish,
or the cancelled state, where the note is immediately
finished */
private final int NOTE_PRESSED = 0;
private final int NOTE_RELEASED = 1;
private final int NOTE_CANCELLED = 2;
private int noteState; // Current note state
private double frequency; // Current frequency of the oscillator
public SynthNote(
Oscillator osc,
double freq, double maxAmplitude,
double a, double d, double s, double r,
OnBufferFullListener listener) {
this(osc, DEFAULT_SAMPLE_RATE, freq, maxAmplitude, a, d, s, r, listener);
}
public SynthNote(
Oscillator osc,
int sampleRate, double freq, double maxAmplitude,
double a, double d, double s, double r,
OnBufferFullListener listener) {
// Initallising the buffer
this.buffer = new Buffer(DEFUALT_BUFFER_SIZE, listener);
this.osc = osc;
// Initiallising the current amplitude & time
this.currentAmplitude = 0;
this.currentTime = 0;
this.maxAmplitude = maxAmplitude;
this.frequency = freq;
// attackTime, decayTime, releaseTime are calculated relative to the sampleRate
this.attackTime = a * sampleRate;
this.decayTime = d * sampleRate;
this.releaseTime = r * sampleRate;
// The sustainAmplitude is calculated relative to the maxAmplitude
this.sustainAmplitude = s * (maxAmplitude / 100.0);
}
public void run() {
this.osc.setFreq(this.frequency); // Setting osc freq to the SynthNote freq
this.noteState = NOTE_PRESSED; // User is pressing the key currently
// While the note's time is still in the attack envelope,
// and the key is still pressed
while (this.currentTime <= this.attackTime &&
this.noteState == NOTE_PRESSED) {
// Continue with the attack envelope
attackEnv();
this.incrementTime();
}
// The attack envelope has finished
// Calculating the slope and c value for the decay envelope
double m = (this.sustainAmplitude - this.maxAmplitude) / this.decayTime; // m = y2-y1/x2-x1
double c = (-m * this.attackTime) + (this.maxAmplitude); // c = -mx + y
// While the note's time is still in the decay envelope
// and the key is still pressed
while (this.currentTime <= this.attackTime + this.decayTime &&
// attackTime + decayTime is the end of the decay envelope
this.noteState == NOTE_PRESSED) {
// Continue with the decay envelope
decayEnv(m, c);
this.incrementTime();
}
// The decay envelope has finished
// While the user is still pressing the key
while (this.noteState == NOTE_PRESSED) {
// Continue sustaining the note
sustainEnv();
this.incrementTime();
}
// The user has now let go of the key
// Calculating the slope and c value for the release envelope
// The sustain has finished, so we know the current time is sustainEnd
double sustainEnd = this.currentTime;
// The end of the release envelope is the end of the sustain envelope + the release time
double releaseEnd = this.currentTime + this.releaseTime;
m = -this.currentAmplitude / this.releaseTime; // m = y2-y1/x2-x1
c = -m * (sustainEnd + this.releaseTime); // c = -mx + y
// While the note has not gotten to the end of the release envelope
// and the note has not been cancelled
while (this.currentTime <= releaseEnd && this.noteState != NOTE_CANCELLED) {
// Continue releasing the note
releaseEnv(m, c);
this.incrementTime();
}
// Release has finished
this.buffer.drain(); // Output any remaining bytes in the buffer
}
/**
Calculates the amolitude value for the attack envelope and adds the
frequency to the output buffer
**/
private void attackEnv() {
this.currentAmplitude = (this.currentTime) /
(this.attackTime / this.maxAmplitude); // amp = m(time)
osc.setAmplitude(this.currentAmplitude);
// Add the byte from wave at time t to the buffer
this.buffer.add(osc.createWave(this.currentTime));
}
/**
Calculates the amplitude value for the decay envelope and adds the
frequency to the output buffer
@param m slope of decay
@param c c value of decay
**/
private void decayEnv(double m, double c) {
this.currentAmplitude = (m * this.currentTime) + c; // amp = m(time) + c
osc.setAmplitude(this.currentAmplitude);
// Add the byte from wave at time t to the buffer
this.buffer.add(osc.createWave(this.currentTime));
}
/**
Adds the frequency with the constant sustain amplitude to the output buffer
**/
private void sustainEnv() {
// No need for sleop or c as the suatin amplitude is constant
this.currentAmplitude = this.sustainAmplitude;
osc.setAmplitude(this.currentAmplitude);
// Add the byte from wave at time t to the buffer
this.buffer.add(osc.createWave(this.currentTime));
}
/**
Calculates the amplitude value for the release envelope and adds the
frequency to the output buffer
@param m slope of release
@param c c value of release
**/
private void releaseEnv(double m, double c) {
this.currentAmplitude = (m * this.currentTime) + c; // amp = m(time) + c
osc.setAmplitude(this.currentAmplitude);
// Add the byte from wave at time t to the buffer
this.buffer.add(osc.createWave(this.currentTime));
}
/**
Increments the current relative time of the SynthNote
**/
private void incrementTime() {
this.currentTime += 1;
}
// Public methods
/**
Notifies the SynthNote thread that the key has been released
by the user
**/
public void release() {
this.noteState = NOTE_RELEASED;
}
/**
Notifies the SynthNote thread that the note has been cancelled
by the user as they have pressed another key
**/
public void cancel() {
this.noteState = NOTE_CANCELLED;
}
/**
A simple private buffer class used to output bytes to the line.
**/
private class Buffer {
int size; // Max number of items in the buffer
int itemCount; // Current number of bytes in the buffer
byte[] buffer; // Buffer byte array
OnBufferFullListener listener;
/**
Constructor for the byte buffer
@param size Max number of bytes in the buffer
@param listener
**/
Buffer(int size, OnBufferFullListener listener) {
this.itemCount = 0;
this.size = size;
this.listener = listener;
this.buffer = new byte[size]; // Initiallising buffer array
}
/**
Used to add a byte to the buffer, if buffer is full, then the buffer
is reset and the callback function is called.
@param b byte to add to the buffer
**/
void add(byte b) {
// If the buffer still has room for another byte
if (this.itemCount < this.size) {
// Incrementing itemCount and adding byte to array
this.buffer[itemCount++] = b;
} else {
// Otherwise the buffer is full
// Passing array of bytes to the callback function
this.listener.onFull(this.buffer);
this.reset(); // Clear array
this.buffer[itemCount++] = b; // Add byte to cleared array
}
}
/**
Resets and emptys buffer
**/
void reset() {
this.buffer = new byte[size];
this.itemCount = 0;
}
/**
Used to drain the remaining bytes from the buffer, as
the buffer is not always going to be completely full when the note
ends, so the onFull callback will not be called and the buffer
will be partially full
**/
void drain() {
this.listener.onDrain(this.buffer);
}
}
// OnBufferFullListener used for callback functions
public interface OnBufferFullListener {
void onFull(byte[] buffer); // Called when the buffer is full
void onDrain(byte[] buffer); // Called when the buffer is partially full
// and needs to be emptied and written to line
}
}