-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathsynth.cpp
605 lines (509 loc) · 16 KB
/
synth.cpp
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
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
/*
MINI VIRTUAL ANALOG SYNTHESIZER
Copyright 2014 Kenneth D. Miller III
*/
#include "StdAfx.h"
#include "Debug.h"
#include "Console.h"
#include "Math.h"
#include "Random.h"
#include "Menu.h"
#include "Keys.h"
#include "Voice.h"
#include "Midi.h"
#include "Control.h"
#include "PolyBLEP.h"
#include "Oscillator.h"
#include "OscillatorLFO.h"
#include "OscillatorNote.h"
#include "SubOscillator.h"
#include "Wave.h"
#include "Filter.h"
#include "Amplifier.h"
#include "Effect.h"
#include "DisplaySpectrumAnalyzer.h"
#include "DisplayKeyVolumeEnvelope.h"
#include "DisplayOscillatorWaveform.h"
#include "DisplayOscillatorFrequency.h"
#include "DisplayFilterFrequency.h"
#include "DisplayLowFrequencyOscillator.h"
BASS_INFO info;
HSTREAM stream; // the stream
// display error message, clean up, and exit
void Error(char const *text)
{
fprintf(stderr, "Error(%d): %s\n", BASS_ErrorGetCode(), text);
BASS_Free();
ExitProcess(0);
}
// window title
char const title_text[] = ">>> MINI VIRTUAL ANALOG SYNTHESIZER";
// output scale factor
float output_scale = 0.25f; // 0.25f;
static size_t const BLOCK_UPDATE_SAMPLES = 16;
// apply low-frequency oscillator value
static void ApplyLFO(float lfo)
{
// compute shared oscillator values
for (int o = 0; o < NUM_OSCILLATORS; ++o)
{
osc_config[o].Modulate(lfo);
}
// set up sync phases
for (int o = 1; o < NUM_OSCILLATORS; ++o)
{
if (osc_config[o].sync_enable)
osc_config[o].sync_phase = osc_config[o].frequency / osc_config[0].frequency;
}
}
DWORD CALLBACK WriteStream(HSTREAM handle, float *buffer, DWORD length, void *user)
{
// get active voices
int index[VOICES];
int active = 0;
for (int v = 0; v < VOICES; v++)
{
if (amp_env_state[v].state != EnvelopeState::OFF)
{
index[active++] = v;
}
}
// number of samples
size_t count = length / (2 * sizeof(buffer[0]));
// key frequencies
float osc_key_freq[VOICES][NUM_OSCILLATORS];
float flt_key_freq[VOICES];
// for each active voice...
for (int i = 0; i < active; ++i)
{
// get the voice index
int const v = index[i];
// compute oscillator key frequency
for (int o = 0; o < NUM_OSCILLATORS; ++o)
{
osc_key_freq[v][o] = NoteFrequency(voice_note[v], osc_config[o].key_follow);
}
// compute filter key frequency
flt_key_freq[v] = NoteFrequency(voice_note[v], flt_config.key_follow);
}
// low-frequency oscillator value
// (updated every BLOCK_UPDATE_SAMPLES)
float lfo = 0;
if (active == 0)
{
// clear buffer
memset(buffer, 0, length);
// get low-frequency oscillator value
if (lfo_config.enable)
lfo = lfo_state.Update(lfo_config, float(count) / info.freq);
// apply low-frequency oscillator
ApplyLFO(lfo);
return length;
}
// flush denormals
unsigned int prev;
_controlfp_s(&prev, _DN_FLUSH, _MCW_DN);
// time step per output sample
float const step = 1.0f / info.freq;
// time step per output block
float const block_step = step * BLOCK_UPDATE_SAMPLES;
// if the low-frequency oscillator is off...
if (!lfo_config.enable)
{
ApplyLFO(0);
}
// for each output sample...
for (size_t c = 0; c < count; ++c)
{
if ((c & (BLOCK_UPDATE_SAMPLES - 1)) == 0)
{
// apply low-frequency oscillator
if (lfo_config.enable)
{
// get low-frequency oscillator value
lfo = lfo_state.Update(lfo_config, block_step);
// apply low-frequency oscillator
ApplyLFO(lfo);
}
}
// accumulated sample value
float sample = 0.0f;
// for each active voice...
for (int i = 0; i < active; ++i)
{
// get the voice index
int const v = index[i];
// update volume envelope generator
float const amp_env_amplitude = amp_env_state[v].Update(amp_env_config, step);
// if the envelope generator finished...
if (amp_env_state[v].state == EnvelopeState::OFF)
{
// remove from active oscillators
--active;
index[i] = index[active];
--i;
continue;
}
// key velocity
float const key_vel = voice_vel[v] / 64.0f;
// update oscillators
// (assume key follow)
float osc_value = 0.0f;
for (int o = 0; o < NUM_OSCILLATORS; ++o)
{
if (!osc_config[o].enable)
continue;
float const key_step = osc_key_freq[v][o] * step;
if (osc_config[o].sub_osc_mode && osc_config[o].sub_osc_amplitude)
osc_value += osc_config[o].sub_osc_amplitude * SubOscillator(osc_config[o], osc_state[v][o], key_step);
osc_value += osc_state[v][o].Update(osc_config[o], key_step);
}
// update filter
if (flt_config.enable)
{
if ((c & (BLOCK_UPDATE_SAMPLES - 1)) == 0)
{
// update filter envelope generator
float const flt_env_amplitude = flt_env_state[v].Update(flt_env_config, block_step);
// compute cutoff frequency
float const cutoff = flt_key_freq[v] * flt_config.GetCutoff(lfo, flt_env_amplitude, key_vel);
// set up the filter
flt_state[v].Setup(cutoff, flt_config.resonance, step);
}
// get filtered oscillator value
osc_value = flt_state[v].Update(flt_config, osc_value);
}
// apply amplifier level and accumulate result
sample += osc_value * amp_config.GetLevel(amp_env_amplitude, key_vel);
}
// left and right channels are the same
//short const output = short(Clamp(int(sample * output_scale * 32768), SHRT_MIN, SHRT_MAX));
//short const output = short(FastTanh(sample * output_scale) * 32767);
//float const output = FastTanh(sample * output_scale);
float const output = sample * output_scale;
*buffer++ = output;
*buffer++ = output;
}
// restore denormal
_controlfp_s(&prev, prev, _MCW_DN);
return length;
}
void PrintOutputScale(HANDLE hOut)
{
COORD const pos = { 1, SPECTRUM_HEIGHT + 2 };
PrintConsoleWithAttribute(hOut, { pos.X, pos.Y }, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | BACKGROUND_RED, "-");
PrintConsoleWithAttribute(hOut, { pos.X + 2, pos.Y }, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | BACKGROUND_BLUE, "+");
PrintConsole(hOut, { pos.X + 4, pos.Y }, "Output: %5.1f%%", output_scale * 100.0f);
}
void PrintKeyOctave(HANDLE hOut)
{
COORD const pos = { 21, SPECTRUM_HEIGHT + 2 };
PrintConsoleWithAttribute(hOut, { pos.X, pos.Y }, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | BACKGROUND_RED, "[");
PrintConsoleWithAttribute(hOut, { pos.X + 2, pos.Y }, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | BACKGROUND_BLUE, "]");
PrintConsole(hOut, { pos.X + 4, pos.Y }, "Key Octave: %d", keyboard_octave);
}
void PrintGoToMain(HANDLE hOut)
{
COORD const pos = { 41, SPECTRUM_HEIGHT + 2 };
PrintConsole(hOut, pos, "F10 Go To Main ");
}
void PrintGoToEffects(HANDLE hOut)
{
COORD const pos = { 41, SPECTRUM_HEIGHT + 2 };
PrintConsole(hOut, pos, "F11 Go To Effects ");
}
void PrintAntialias(HANDLE hOut)
{
COORD const pos = { 61, SPECTRUM_HEIGHT + 2 };
PrintConsole(hOut, pos, "F12 Antialias:");
if (use_antialias)
PrintConsoleWithAttribute(hOut, { pos.X + 15, pos.Y }, FOREGROUND_GREEN, " ON");
else
PrintConsoleWithAttribute(hOut, { pos.X + 15, pos.Y }, FOREGROUND_RED, "OFF");
}
void __cdecl main(int argc, char **argv)
{
HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
int running = 1;
#ifdef WIN32
if (IsDebuggerPresent())
{
// turn on floating-point exceptions
unsigned int prev;
_clearfp();
_controlfp_s(&prev, 0, _EM_ZERODIVIDE|_EM_INVALID);
}
#endif
// check the correct BASS was loaded
if (HIWORD(BASS_GetVersion()) != BASSVERSION)
{
fprintf(stderr, "An incorrect version of BASS.DLL was loaded");
return;
}
// set the window title
SetConsoleTitle(TEXT(title_text));
// set the console buffer size
static const COORD bufferSize = { WINDOW_WIDTH, WINDOW_HEIGHT };
SetConsoleScreenBufferSize(hOut, bufferSize);
// set the console window size
static const SMALL_RECT windowSize = { 0, 0, WINDOW_WIDTH - 1, WINDOW_HEIGHT - 1 };
SetConsoleWindowInfo(hOut, TRUE, &windowSize);
// clear the window
Clear(hOut);
// hide the cursor
static const CONSOLE_CURSOR_INFO cursorInfo = { 100, FALSE };
SetConsoleCursorInfo(hOut, &cursorInfo);
// set input mode
SetConsoleMode(hIn, ENABLE_MOUSE_INPUT);
// 10ms update period
const DWORD STREAM_UPDATE_PERIOD = 10;
BASS_SetConfig(BASS_CONFIG_UPDATEPERIOD, STREAM_UPDATE_PERIOD);
// initialize BASS sound library
const DWORD STREAM_FREQUENCY = 48000;
if (!BASS_Init(-1, STREAM_FREQUENCY, BASS_DEVICE_LATENCY, 0, NULL))
Error("Can't initialize device\n");
// get device info
BASS_GetInfo(&info);
// if the device's output rate is unknown default to stream frequency
if (!info.freq) info.freq = STREAM_FREQUENCY;
// debug print info
DebugPrint("frequency: %d (min %d, max %d)\n", info.freq, info.minrate, info.maxrate);
DebugPrint("device latency: %dms\n", info.latency);
DebugPrint("device minbuf: %dms\n", info.minbuf);
DebugPrint("ds version: %d (effects %s)\n", info.dsver, info.dsver < 8 ? "disabled" : "enabled");
// default buffer size = update period + 'minbuf' + 1ms extra margin
BASS_SetConfig(BASS_CONFIG_BUFFER, STREAM_UPDATE_PERIOD + info.minbuf + 1);
DebugPrint("using a %dms buffer\r", BASS_GetConfig(BASS_CONFIG_BUFFER));
// create a stream, stereo so that effects sound nice
stream = BASS_StreamCreate(info.freq, 2, BASS_SAMPLE_FLOAT, (STREAMPROC*)WriteStream, 0);
// set channel to apply effects
fx_channel = stream;
#ifdef BANDLIMITED_SAWTOOTH
// initialize bandlimited sawtooth tables
InitSawtooth();
#endif
// initialize waves
InitWave();
// enable the first oscillator
osc_config[0].enable = true;
// reset all controllers
Control::ResetAll();
// start playing the audio stream
BASS_ChannelPlay(stream, FALSE);
// get the number of midi devices
UINT midiInDevs = Midi::Input::GetNumDevices();
DebugPrint("MIDI input devices: %d\n", midiInDevs);
// print device names
for (UINT i = 0; i < midiInDevs; ++i)
{
MIDIINCAPS midiInCaps;
if (Midi::Input::GetDeviceCaps(i, midiInCaps) == 0)
{
DebugPrint("%d: %s\n", i, midiInCaps.szPname);
}
}
// if there are any devices available...
if (midiInDevs > 0)
{
// open and start midi input
// TO DO: select device number via a configuration setting
Midi::Input::Open(0);
Midi::Input::Start();
}
// initialize to middle c
note_most_recent = 60;
voice_note[voice_most_recent] = unsigned char(note_most_recent);
DisplaySpectrumAnalyzer displaySpectrumAnalyzer;
DisplayKeyVolumeEnvelope displayKeyVolumeEnvelope;
DisplayOscillatorWaveform displayOscillatorWaveform;
DisplayOscillatorFrequency displayOscillatorFrequency;
DisplayLowFrequencyOscillator displayLowFrequencyOscillator;
DisplayFilterFrequency displayFilterFrequency;
// initialize spectrum analyzer
displaySpectrumAnalyzer.Init(stream, info);
// initialize key display
displayKeyVolumeEnvelope.Init(hOut);
// show output scale and key octave
PrintOutputScale(hOut);
PrintKeyOctave(hOut);
PrintGoToEffects(hOut);
PrintAntialias(hOut);
// initialize the menu system
Menu::Init();
// show main page
Menu::SetActivePage(hOut, Menu::PAGE_MAIN);
while (running)
{
// if there are any pending input events...
DWORD numEvents = 0;
while (GetNumberOfConsoleInputEvents(hIn, &numEvents) && numEvents > 0)
{
// get the next input event
INPUT_RECORD keyin;
ReadConsoleInput(hIn, &keyin, 1, &numEvents);
if (keyin.EventType == KEY_EVENT)
{
// handle interface keys
if (keyin.Event.KeyEvent.bKeyDown)
{
WORD code = keyin.Event.KeyEvent.wVirtualKeyCode;
DWORD modifiers = keyin.Event.KeyEvent.dwControlKeyState;
if (code == VK_ESCAPE)
{
running = 0;
break;
}
else if (code == VK_OEM_MINUS || code == VK_SUBTRACT)
{
Menu::UpdatePercentageProperty(output_scale, -1, modifiers, 0, 4);
PrintOutputScale(hOut);
}
else if (code == VK_OEM_PLUS || code == VK_ADD)
{
Menu::UpdatePercentageProperty(output_scale, +1, modifiers, 0, 4);
PrintOutputScale(hOut);
}
else if (code == VK_OEM_4) // '['
{
if (keyboard_octave > 1)
{
for (int k = 0; k < KEYS; ++k)
{
if (key_down[k])
NoteOff(k + keyboard_octave * 12);
}
--keyboard_octave;
for (int k = 0; k < KEYS; ++k)
{
if (key_down[k])
NoteOn(k + keyboard_octave * 12);
}
PrintKeyOctave(hOut);
}
}
else if (code == VK_OEM_6) // ']'
{
if (keyboard_octave < 9)
{
for (int k = 0; k < KEYS; ++k)
{
if (key_down[k])
NoteOff(k + keyboard_octave * 12);
}
++keyboard_octave;
for (int k = 0; k < KEYS; ++k)
{
if (key_down[k])
NoteOn(k + keyboard_octave * 12);
}
PrintKeyOctave(hOut);
}
}
else if (code == VK_F12)
{
use_antialias = !use_antialias;
PrintAntialias(hOut);
}
else if (code >= VK_F1 && code < VK_F10)
{
Menu::SetActiveMenu(hOut, code - VK_F1);
}
else if (code == VK_F10)
{
PrintGoToEffects(hOut);
Menu::SetActivePage(hOut, Menu::PAGE_MAIN);
}
else if (code == VK_F11)
{
PrintGoToMain(hOut);
Menu::SetActivePage(hOut, Menu::PAGE_FX);
}
else if (code == VK_TAB)
{
if (modifiers & SHIFT_PRESSED)
Menu::PrevMenu(hOut);
else
Menu::NextMenu(hOut);
}
else if (code == VK_UP || code == VK_DOWN || code == VK_RIGHT || code == VK_LEFT)
{
Menu::Handler(hOut, code, modifiers);
}
}
// handle note keys
for (int k = 0; k < KEYS; k++)
{
if (keyin.Event.KeyEvent.wVirtualKeyCode == keys[k])
{
// key down
bool down = (keyin.Event.KeyEvent.bKeyDown != 0);
// if key down state changed...
if (key_down[k] != down)
{
// update state
key_down[k] = down;
// if pressing the key
if (down)
{
// note on
NoteOn(k + keyboard_octave * 12);
}
else
{
// note off
NoteOff(k + keyboard_octave * 12);
}
}
break;
}
}
}
else if (keyin.EventType == MOUSE_EVENT)
{
if (keyin.Event.MouseEvent.dwEventFlags == 0 && (keyin.Event.MouseEvent.dwButtonState & FROM_LEFT_1ST_BUTTON_PRESSED))
{
Menu::Click(hOut, keyin.Event.MouseEvent.dwMousePosition);
}
}
}
// center frequency of the zeroth semitone band
// (one octave down from the lowest key)
float const freq_min = powf(2, float(keyboard_octave - 6)) * middle_c_frequency;
// update the spectrum analyzer display
displaySpectrumAnalyzer.Update(hOut, stream, info, freq_min);
// update note key volume envelope display
displayKeyVolumeEnvelope.Update(hOut);
if (Menu::active_page == Menu::PAGE_MAIN)
{
// update the oscillator waveform display
displayOscillatorWaveform.Update(hOut, info, voice_most_recent);
// update the oscillator frequency displays
for (int o = 0; o < NUM_OSCILLATORS; ++o)
{
if (osc_config[o].enable)
displayOscillatorFrequency.Update(hOut, voice_most_recent, o);
}
// update the low-frequency oscillator display
displayLowFrequencyOscillator.Update(hOut);
// update the filter frequency display
if (flt_config.enable)
displayFilterFrequency.Update(hOut, voice_most_recent);
}
// show CPU usage
PrintConsole(hOut, { WINDOW_WIDTH - 7, WINDOW_HEIGHT - 1 }, "%6.2f%%", BASS_GetCPU());
// sleep for 1/60th of second
Sleep(16);
}
if (midiInDevs)
{
// stop and close midi input
Midi::Input::Stop();
Midi::Input::Close();
}
// clean up spectrum analyzer
displaySpectrumAnalyzer.Cleanup(stream);
// clear the window
Clear(hOut);
BASS_Free();
}